### Eclipse Workspace Patch 1.0 #P org.eclipse.jdt.core Index: compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java,v retrieving revision 1.67 diff -u -r1.67 Annotation.java --- compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java 20 Jul 2010 20:23:19 -0000 1.67 +++ compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java 2 Dec 2010 10:30:29 -0000 @@ -161,6 +161,12 @@ case TypeIds.T_JavaLangSuppressWarnings : tagBits |= TagBits.AnnotationSuppressWarnings; break; + case TypeIds.T_ConfiguredAnnotationNullable : + tagBits |= TagBits.AnnotationNullable; + break; + case TypeIds.T_ConfiguredAnnotationNonNull : + tagBits |= TagBits.AnnotationNonNull; + break; } return tagBits; } Index: compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java,v retrieving revision 1.76 diff -u -r1.76 MethodDeclaration.java --- compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java 3 Nov 2009 15:37:46 -0000 1.76 +++ compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java 2 Dec 2010 10:30:29 -0000 @@ -78,6 +78,11 @@ if (this.arguments != null) { for (int i = 0, count = this.arguments.length; i < count; i++) { flowInfo.markAsDefinitelyAssigned(this.arguments[i].binding); + long argumentTagBits = this.arguments[i].binding.tagBits; + if ((argumentTagBits & TagBits.AnnotationNullable) != 0) + flowInfo.markPotentiallyNullBit(this.arguments[i].binding); + else if ((argumentTagBits & TagBits.AnnotationNonNull) != 0) + flowInfo.markAsDefinitelyNonNull(this.arguments[i].binding); } } // propagate to statements Index: compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java,v retrieving revision 1.235 diff -u -r1.235 CompilerOptions.java --- compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java 21 Oct 2010 19:59:58 -0000 1.235 +++ compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java 2 Dec 2010 10:30:33 -0000 @@ -138,6 +138,11 @@ public static final String OPTION_ReportMissingAnnotation = "org.eclipse.jdt.core.compiler.problem.missingAnnotation"; //$NON-NLS-1$ public static final String OPTION_ReportMissingJavadoc = "org.eclipse.jdt.core.compiler.problem.missingJavadoc"; //$NON-NLS-1$ + public static final String OPTION_NullableAnnotationNames = "org.eclipse.jdt.core.compiler.annotation.nullable"; //$NON-NLS-1$ + public static final String OPTION_NonNullAnnotationNames = "org.eclipse.jdt.core.compiler.annotation.nonnull"; //$NON-NLS-1$ + public static final String OPTION_EmulateNullAnnotationTypes = "org.eclipse.jdt.core.compiler.annotation.emulate"; //$NON-NLS-1$ + public static final String OPTION_DefaultImportNullAnnotationTypes = "org.eclipse.jdt.core.compiler.annotation.defaultImport"; //$NON-NLS-1$ + /** * Possible values for configurable options */ @@ -357,6 +362,14 @@ public boolean ignoreMethodBodies; /** Raise null related warnings for variables tainted inside an assert statement (java 1.4 and above)*/ public boolean includeNullInfoFromAsserts; + /** List of fully qualified annotation names to use as marker for nullable types. */ + public char[][][] nullableAnnotationNames; + /** List of fully qualified annotation names to use as marker for nonnull types. */ + public char[][][] nonNullAnnotationNames; + /** Should null annotation types be emulated by synthetic bindings? */ + public boolean emulateNullAnnotationTypes; + /** Should null annotation types be imported by default? */ + public boolean defaultImportNullAnnotationTypes; // keep in sync with warningTokenToIrritant and warningTokenFromIrritant public final static String[] warningTokens = { @@ -921,6 +934,22 @@ optionsMap.put(OPTION_ReportTasks, getSeverityString(Tasks)); optionsMap.put(OPTION_ReportUnusedObjectAllocation, getSeverityString(UnusedObjectAllocation)); optionsMap.put(OPTION_IncludeNullInfoFromAsserts, this.includeNullInfoFromAsserts ? ENABLED : DISABLED); + if (this.nullableAnnotationNames != null) { + char[][] compoundNames = new char[this.nullableAnnotationNames.length][]; + for (int i = 0; i < this.nullableAnnotationNames.length; i++) + compoundNames[i] = CharOperation.concatWith(this.nullableAnnotationNames[i], '.'); + char[] allNames = CharOperation.concatWith(compoundNames, ','); + optionsMap.put(OPTION_NullableAnnotationNames, String.valueOf(allNames)); + } + if (this.nonNullAnnotationNames != null) { + char[][] compoundNames = new char[this.nonNullAnnotationNames.length][]; + for (int i = 0; i < this.nonNullAnnotationNames.length; i++) + compoundNames[i] = CharOperation.concatWith(this.nonNullAnnotationNames[i], '.'); + char[] allNames = CharOperation.concatWith(compoundNames, ','); + optionsMap.put(OPTION_NonNullAnnotationNames, String.valueOf(allNames)); + } + optionsMap.put(OPTION_EmulateNullAnnotationTypes, this.emulateNullAnnotationTypes ? ENABLED : DISABLED); + optionsMap.put(OPTION_DefaultImportNullAnnotationTypes, this.defaultImportNullAnnotationTypes ? ENABLED : DISABLED); return optionsMap; } @@ -1452,6 +1481,32 @@ this.storeAnnotations = false; } } + if ((optionValue = optionsMap.get(OPTION_NullableAnnotationNames)) != null) { + String[] allNames = ((String)optionValue).split(","); //$NON-NLS-1$ + this.nullableAnnotationNames = new char[allNames.length][][]; + for (int i = 0; i < allNames.length; i++) + this.nullableAnnotationNames[i] = CharOperation.splitAndTrimOn('.', allNames[i].toCharArray()); + } + if ((optionValue = optionsMap.get(OPTION_NonNullAnnotationNames)) != null) { + String[] allNames = ((String)optionValue).split(","); //$NON-NLS-1$ + this.nonNullAnnotationNames = new char[allNames.length][][]; + for (int i = 0; i < allNames.length; i++) + this.nonNullAnnotationNames[i] = CharOperation.splitAndTrimOn('.', allNames[i].toCharArray()); + } + if ((optionValue = optionsMap.get(OPTION_EmulateNullAnnotationTypes)) != null) { + if (ENABLED.equals(optionValue)) { + this.emulateNullAnnotationTypes = true; + } else if (DISABLED.equals(optionValue)) { + this.emulateNullAnnotationTypes = false; + } + } + if ((optionValue = optionsMap.get(OPTION_DefaultImportNullAnnotationTypes)) != null) { + if (ENABLED.equals(optionValue)) { + this.defaultImportNullAnnotationTypes = true; + } else if (DISABLED.equals(optionValue)) { + this.defaultImportNullAnnotationTypes = false; + } + } } public String toString() { StringBuffer buf = new StringBuffer("CompilerOptions:"); //$NON-NLS-1$ Index: compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java,v retrieving revision 1.128 diff -u -r1.128 CompilationUnitScope.java --- compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java 12 Aug 2010 08:51:55 -0000 1.128 +++ compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java 2 Dec 2010 10:30:36 -0000 @@ -303,6 +303,13 @@ return; // can be called when a field constant is resolved before static imports if (this.referenceContext.imports == null) { this.typeOrPackageCache = new HashtableOfObject(1); + for (int i = 0; i < this.imports.length; i++) { + // cache default-imported null annotation types: + if (!this.imports[i].onDemand) { + char[][] importName = this.imports[i].compoundName; + this.typeOrPackageCache.put(importName[importName.length-1], this.imports[i].resolvedImport); + } + } return; } @@ -612,8 +619,19 @@ BinaryTypeBinding missingObject = this.environment.createMissingType(null, TypeConstants.JAVA_LANG_OBJECT); importBinding = missingObject.fPackage; } - - return this.environment.defaultImports = new ImportBinding[] {new ImportBinding(TypeConstants.JAVA_LANG, true, importBinding, null)}; + ImportBinding javaLangImport = new ImportBinding(TypeConstants.JAVA_LANG, true, importBinding, null); + ImportBinding[] nullAnnotationImports = this.environment.makeNullAnnotationTypeImports(); // trigger regardless of option below + if (this.environment.globalOptions.defaultImportNullAnnotationTypes) { + ImportBinding[] allDefaultImports = new ImportBinding[nullAnnotationImports.length+1]; + allDefaultImports[0] = javaLangImport; + System.arraycopy(nullAnnotationImports, 0, + allDefaultImports, 1, + nullAnnotationImports.length); + this.environment.defaultImports = allDefaultImports; + } else { + this.environment.defaultImports = new ImportBinding[] {javaLangImport}; + } + return this.environment.defaultImports; } // NOT Public API public final Binding getImport(char[][] compoundName, boolean onDemand, boolean isStaticImport) { Index: compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java,v retrieving revision 1.109 diff -u -r1.109 LookupEnvironment.java --- compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java 1 Nov 2010 14:15:47 -0000 1.109 +++ compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java 2 Dec 2010 10:30:38 -0000 @@ -1314,6 +1314,53 @@ return this.nameEnvironment.isPackage(null, name); return this.nameEnvironment.isPackage(compoundName, name); } + +private ReferenceBinding makeNullAnnotationType(char[][] compoundName) { + if (!this.globalOptions.emulateNullAnnotationTypes) + return getType(compoundName); + BinaryTypeBinding emulatedType = new BinaryTypeBinding(); + emulatedType.compoundName = compoundName; + emulatedType.modifiers = ClassFileConstants.AccAnnotation | ClassFileConstants.AccPublic; + emulatedType.fields = Binding.NO_FIELDS; + emulatedType.methods = Binding.NO_METHODS; + emulatedType.memberTypes = Binding.NO_MEMBER_TYPES; + emulatedType.superclass = getType(TypeConstants.JAVA_LANG_OBJECT); + emulatedType.superInterfaces = Binding.NO_SUPERINTERFACES; + emulatedType.fPackage = new PackageBinding(CharOperation.subarray(compoundName, 0, compoundName.length-2), null, this); + emulatedType.typeVariables = Binding.NO_TYPE_VARIABLES; + emulatedType.tagBits = TagBits.AreFieldsComplete | TagBits.AreFieldsSorted + | TagBits.AreMethodsComplete | TagBits.AreMethodsSorted + | TagBits.HasNoMemberTypes | TagBits.TypeVariablesAreConnected + | TagBits.AnnotationClassRetention + | TagBits.AnnotationForMethod | TagBits.AnnotationForParameter ; + + // TODO(SH): make emulated type visible via knownPackages, too + return emulatedType; +} + +protected ImportBinding[] makeNullAnnotationTypeImports() { + char[][][] nullableAnnotationNames = this.globalOptions.nullableAnnotationNames; + char[][][] nonNullAnnotationNames = this.globalOptions.nonNullAnnotationNames; + if (nullableAnnotationNames == null || nonNullAnnotationNames == null) { + // TODO(SH): check and report inconsistency with other options + return new ImportBinding[0]; + } + int totalImports = nullableAnnotationNames.length + nonNullAnnotationNames.length; + ImportBinding[] emulatedNullAnnotationImports = new ImportBinding[totalImports]; + for (int i = 0; i < nullableAnnotationNames.length; i++) { + ReferenceBinding annotationType = makeNullAnnotationType(nullableAnnotationNames[i]); + annotationType.id = TypeIds.T_ConfiguredAnnotationNullable; + emulatedNullAnnotationImports[i] = new ImportBinding(nullableAnnotationNames[i], false, annotationType, null); + } + int offset = nullableAnnotationNames.length; + for (int i = 0; i < nonNullAnnotationNames.length; i++) { + ReferenceBinding annotationType = makeNullAnnotationType(nonNullAnnotationNames[i]); + annotationType.id = TypeIds.T_ConfiguredAnnotationNonNull; + emulatedNullAnnotationImports[i+offset] = new ImportBinding(nonNullAnnotationNames[i], false, annotationType, null); + } + return emulatedNullAnnotationImports; +} + // The method verifier is lazily initialized to guarantee the receiver, the compiler & the oracle are ready. public MethodVerifier methodVerifier() { if (this.verifier == null) Index: compiler/org/eclipse/jdt/internal/compiler/lookup/TagBits.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TagBits.java,v retrieving revision 1.44 diff -u -r1.44 TagBits.java --- compiler/org/eclipse/jdt/internal/compiler/lookup/TagBits.java 21 Sep 2010 14:02:58 -0000 1.44 +++ compiler/org/eclipse/jdt/internal/compiler/lookup/TagBits.java 2 Dec 2010 10:30:38 -0000 @@ -116,10 +116,13 @@ long AnnotationInherited = ASTNode.Bit49L; long AnnotationOverride = ASTNode.Bit50L; long AnnotationSuppressWarnings = ASTNode.Bit51L; - long AllStandardAnnotationsMask = AnnotationTargetMASK | AnnotationRetentionMASK | AnnotationDeprecated | AnnotationDocumented | AnnotationInherited | AnnotationOverride | AnnotationSuppressWarnings; + long AnnotationNullable = ASTNode.Bit52L; + long AnnotationNonNull = ASTNode.Bit53L; + long AllStandardAnnotationsMask = AnnotationTargetMASK | AnnotationRetentionMASK | AnnotationDeprecated | AnnotationDocumented + | AnnotationInherited | AnnotationOverride | AnnotationSuppressWarnings | AnnotationNullable | AnnotationNonNull; - long DefaultValueResolved = ASTNode.Bit52L; + long DefaultValueResolved = ASTNode.Bit54L; // set when type contains non-private constructor(s) - long HasNonPrivateConstructor = ASTNode.Bit53L; + long HasNonPrivateConstructor = ASTNode.Bit55L; } Index: compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java,v retrieving revision 1.34 diff -u -r1.34 TypeIds.java --- compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java 7 Mar 2009 01:08:09 -0000 1.34 +++ compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java 2 Dec 2010 10:30:38 -0000 @@ -89,6 +89,9 @@ final int T_JavaUtilCollection = 59; + final int T_ConfiguredAnnotationNullable = 60; + final int T_ConfiguredAnnotationNonNull = 61; + final int NoId = Integer.MAX_VALUE; public static final int IMPLICIT_CONVERSION_MASK = 0xFF; #P org.eclipse.jdt.core.tests.compiler Index: src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java =================================================================== RCS file: src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java diff -N src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2010 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Stephan Herrmann - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.core.tests.compiler.regression; + +import java.util.Map; + +import junit.framework.Test; + +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; + +public class NullAnnotationTest extends AbstractComparableTest { + +public NullAnnotationTest(String name) { + super(name); +} + +// Static initializer to specify tests subset using TESTS_* static variables +// All specified tests which does not belong to the class are skipped... +// Only the highest compliance level is run; add the VM argument +// -Dcompliance=1.4 (for example) to lower it if needed +static { +// TESTS_NAMES = new String[] { "testBug325229" }; +// TESTS_NUMBERS = new int[] { 561 }; +// TESTS_RANGE = new int[] { 1, 2049 }; +} + +public static Test suite() { + return buildComparableTestSuite(testClass()); +} + +public static Class testClass() { + return NullAnnotationTest.class; +} + +// Conditionally augment problem detection settings +static boolean setNullRelatedOptions = true; +protected Map getCompilerOptions() { + Map defaultOptions = super.getCompilerOptions(); + if (setNullRelatedOptions) { + defaultOptions.put(CompilerOptions.OPTION_ReportNullReference, CompilerOptions.ERROR); + defaultOptions.put(CompilerOptions.OPTION_ReportPotentialNullReference, CompilerOptions.ERROR); + defaultOptions.put(CompilerOptions.OPTION_ReportRedundantNullCheck, CompilerOptions.ERROR); + defaultOptions.put(CompilerOptions.OPTION_ReportRawTypeReference, CompilerOptions.IGNORE); + defaultOptions.put(CompilerOptions.OPTION_IncludeNullInfoFromAsserts, CompilerOptions.ENABLED); + + defaultOptions.put(CompilerOptions.OPTION_NullableAnnotationNames, "org.eclipse.jdt.annotation.Nullable"); + defaultOptions.put(CompilerOptions.OPTION_NonNullAnnotationNames, "org.eclipse.jdt.annotation.NonNull"); + defaultOptions.put(CompilerOptions.OPTION_EmulateNullAnnotationTypes, CompilerOptions.ENABLED); + defaultOptions.put(CompilerOptions.OPTION_DefaultImportNullAnnotationTypes, CompilerOptions.ENABLED); + } + return defaultOptions; +} + +// +public void test0001_nonnull_parameter() { + runNegativeTest( + new String[] { + "X.java", + "public class X {\n" + + " void foo(@NonNull Object o) {\n" + + " if (o != null)\n" + + " System.out.print(o.toString());\n" + + " }\n" + + "}\n"}, + "----------\n" + + "1. ERROR in X.java (at line 3)\n" + + " if (o != null)\n" + + " ^\n" + + "Redundant null check: The variable o cannot be null at this location\n" + + "----------\n", + JavacTestOptions.Excuse.EclipseWarningConfiguredAsError); +} +// +public void test0002_nonnull_parameter() { + runConformTest( + new String[] { + "X.java", + "public class X {\n" + + " void foo(@NonNull Object o) {\n" + + " System.out.print(o.toString());\n" + + " }\n" + + " public static void main(String... args) {\n" + + " new X().foo(\"OK\");\n" + + " }\n" + + "}\n"}, + "OK"); +} +// +public void test0003_nullable_paramter() { + runNegativeTest( + new String[] { + "X.java", + "public class X {\n" + + " void foo(@Nullable Object o) {\n" + + " System.out.print(o.toString());\n" + + " }\n" + + "}\n"}, + "----------\n" + + "1. ERROR in X.java (at line 3)\n" + + " System.out.print(o.toString());\n" + + " ^\n" + + "Potential null pointer access: The variable o may be null at this location\n" + + "----------\n", + JavacTestOptions.Excuse.EclipseWarningConfiguredAsError); +} +}