diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java index fc21f6d..fc4d329 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java @@ -1915,7 +1915,7 @@ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" + "----------\n"); } -// package level default is consumed from package-info.class +// package level default is consumed from package-info.class, similarly for type level default public void test_default_nullness_003a() { Map customOptions = getCompilerOptions(); // customOptions.put(CompilerOptions.OPTION_ReportPotentialNullSpecViolation, JavaCore.ERROR); @@ -1972,6 +1972,69 @@ "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" + "----------\n"); } +// same as test_default_nullness_003b, but default-induced annotations are combined with explicit ones (not null related) +public void test_default_nullness_003b() { + Map customOptions = getCompilerOptions(); + runConformTestWithLibs( + new String[] { + "p1/Annot.java", + "package p1;\n" + + "import static java.lang.annotation.ElementType.*;\n" + + "import java.lang.annotation.*;\n" + + "@Retention(RetentionPolicy.CLASS)\n" + + "@Target({METHOD,PARAMETER})\n" + + "public @interface Annot {}\n", + "p1/X.java", + "package p1;\n" + + "import org.eclipse.jdt.annotation.*;\n" + + "@NonNullByDefault\n" + + "public class X {\n" + + " protected @Annot Object getObject(@Annot @Nullable Object o) {\n" + + " return new Object();\n" + + " }\n" + + " protected @Annot void bar(@Annot Object o2) { }\n" + // parameter is nonnull per type default + "}\n", + "p2/package-info.java", + "@org.eclipse.jdt.annotation.NonNullByDefault\n" + + "package p2;\n", + }, + customOptions, + ""); + // check if default is visible from package-info.class. + runNegativeTestWithLibs( + false, // don't flush + new String[] { + "p2/Y.java", + "package p2;\n" + + "import org.eclipse.jdt.annotation.*;\n" + + "public class Y extends p1.X {\n" + + " @Override\n" + + " protected @Nullable Object getObject(@Nullable Object o) {\n" + // can't override inherited default nonnull + " bar(o);\n" + // parameter is nonnull in super class's .class file + " accept(o);\n" + + " return o;\n" + + " }\n" + + " void accept(@p1.Annot Object a) {}\n" + // governed by package level default + "}\n" + }, + customOptions, + "----------\n" + + "1. ERROR in p2\\Y.java (at line 5)\n" + + " protected @Nullable Object getObject(@Nullable Object o) {\n" + + " ^^^^^^^^^^^^^^^^\n" + + "The return type is incompatible with the @NonNull return from X.getObject(Object)\n" + + "----------\n" + + "2. ERROR in p2\\Y.java (at line 6)\n" + + " bar(o);\n" + + " ^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" + + "----------\n" + + "3. ERROR in p2\\Y.java (at line 7)\n" + + " accept(o);\n" + + " ^\n" + + "Type mismatch: required \'@NonNull Object\' but the provided value can be null\n" + + "----------\n"); +} // don't apply type-level default to non-reference type public void test_default_nullness_004() { Map customOptions = getCompilerOptions(); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java index 5035d83..9deaade 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ClassFile.java @@ -53,6 +53,7 @@ import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.impl.StringConstant; import org.eclipse.jdt.internal.compiler.lookup.Binding; +import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; @@ -324,7 +325,7 @@ if (typeDeclaration != null) { final Annotation[] annotations = typeDeclaration.annotations; if (annotations != null) { - attributesNumber += generateRuntimeAnnotations(annotations); + attributesNumber += generateRuntimeAnnotations(annotations, 0); } } } @@ -413,7 +414,7 @@ if (fieldDeclaration != null) { Annotation[] annotations = fieldDeclaration.annotations; if (annotations != null) { - attributesNumber += generateRuntimeAnnotations(annotations); + attributesNumber += generateRuntimeAnnotations(annotations, 0); } } } @@ -2683,8 +2684,9 @@ AbstractMethodDeclaration methodDeclaration = methodBinding.sourceMethod(); if (methodDeclaration != null) { Annotation[] annotations = methodDeclaration.annotations; - if (annotations != null) { - attributesNumber += generateRuntimeAnnotations(annotations); + int modifiers = methodBinding.modifiers; + if (annotations != null || (modifiers & ExtraCompilerModifiers.AccImplicitlyNonNull) != 0) { + attributesNumber += generateRuntimeAnnotations(annotations, methodBinding.modifiers); } if ((methodBinding.tagBits & TagBits.HasParameterAnnotations) != 0) { Argument[] arguments = methodDeclaration.arguments; @@ -2868,23 +2870,53 @@ } } + private int generateNonNullAttribute() { + TypeBinding nonNullAnnotation; + try { + nonNullAnnotation = this.referenceBinding.fPackage.environment.getNullAnnotationBinding(TagBits.AnnotationNonNull, true); + } catch (NullPointerException npe) { + return 0; // above chain of dereferencing is quite long, difficult to analyse if all are safe + } + int localContentsOffset = this.contentsOffset; + if (localContentsOffset + 6 >= this.contents.length) { + resizeContents(6); + } + int nonNullAttributeNameIndex = this.constantPool.literalIndex(nonNullAnnotation); + this.contents[localContentsOffset++] = (byte) (nonNullAttributeNameIndex >> 8); + this.contents[localContentsOffset++] = (byte) nonNullAttributeNameIndex; + // the length of a nonNull attribute is equals to 0 + this.contents[localContentsOffset++] = 0; + this.contents[localContentsOffset++] = 0; + this.contents[localContentsOffset++] = 0; + this.contents[localContentsOffset++] = 0; + this.contentsOffset = localContentsOffset; + return 1; + } + /** * @param annotations + * @param modifiers modifiers that could trigger generating an implicit annotation * @return the number of attributes created while dumping the annotations in the .class file */ - private int generateRuntimeAnnotations(final Annotation[] annotations) { + private int generateRuntimeAnnotations(final Annotation[] annotations, int modifiers) { int attributesNumber = 0; - final int length = annotations.length; int visibleAnnotationsCounter = 0; int invisibleAnnotationsCounter = 0; - - for (int i = 0; i < length; i++) { - Annotation annotation = annotations[i]; - if (annotation.isRuntimeInvisible()) { - invisibleAnnotationsCounter++; - } else if (annotation.isRuntimeVisible()) { - visibleAnnotationsCounter++; + + if (annotations != null) { + final int length = annotations.length; + for (int i = 0; i < length; i++) { + Annotation annotation = annotations[i]; + if (annotation.isRuntimeInvisible()) { + invisibleAnnotationsCounter++; + } else if (annotation.isRuntimeVisible()) { + visibleAnnotationsCounter++; + } } + } + boolean isImplicitlyNonNull = (modifiers & ExtraCompilerModifiers.AccImplicitlyNonNull) != 0; + if (isImplicitlyNonNull) { + visibleAnnotationsCounter++; } int annotationAttributeOffset = this.contentsOffset; @@ -2905,15 +2937,18 @@ this.contentsOffset += 2; // leave space for the annotations length int counter = 0; - loop: for (int i = 0; i < length; i++) { - if (invisibleAnnotationsCounter == 0) break loop; - Annotation annotation = annotations[i]; - if (annotation.isRuntimeInvisible()) { - int currentAnnotationOffset = this.contentsOffset; - generateAnnotation(annotation, currentAnnotationOffset); - invisibleAnnotationsCounter--; - if (this.contentsOffset != currentAnnotationOffset) { - counter++; + if (annotations != null) { + final int length = annotations.length; + loop: for (int i = 0; i < length; i++) { + if (invisibleAnnotationsCounter == 0) break loop; + Annotation annotation = annotations[i]; + if (annotation.isRuntimeInvisible()) { + int currentAnnotationOffset = this.contentsOffset; + generateAnnotation(annotation, currentAnnotationOffset); + invisibleAnnotationsCounter--; + if (this.contentsOffset != currentAnnotationOffset) { + counter++; + } } } } @@ -2952,15 +2987,22 @@ this.contentsOffset += 2; // leave space for the annotations length int counter = 0; - loop: for (int i = 0; i < length; i++) { - if (visibleAnnotationsCounter == 0) break loop; - Annotation annotation = annotations[i]; - if (annotation.isRuntimeVisible()) { - visibleAnnotationsCounter--; - int currentAnnotationOffset = this.contentsOffset; - generateAnnotation(annotation, currentAnnotationOffset); - if (this.contentsOffset != currentAnnotationOffset) { - counter++; + if (isImplicitlyNonNull) { + counter += generateNonNullAttribute(); + visibleAnnotationsCounter--; + } + if (annotations != null) { + final int length = annotations.length; + loop: for (int i = 0; i < length; i++) { + if (visibleAnnotationsCounter == 0) break loop; + Annotation annotation = annotations[i]; + if (annotation.isRuntimeVisible()) { + visibleAnnotationsCounter--; + int currentAnnotationOffset = this.contentsOffset; + generateAnnotation(annotation, currentAnnotationOffset); + if (this.contentsOffset != currentAnnotationOffset) { + counter++; + } } } } @@ -3002,6 +3044,9 @@ visibleParametersAnnotationsCounter++; } } + } + if ((argument.binding.modifiers & ExtraCompilerModifiers.AccImplicitlyNonNull) != 0) { + visibleParametersAnnotationsCounter++; } } int attributesNumber = 0; @@ -3085,13 +3130,13 @@ this.contents[this.contentsOffset++] = (byte) 0; this.contents[this.contentsOffset++] = (byte) 0; } else { + Argument argument = arguments[i]; final int numberOfVisibleAnnotations = annotationsCounters[i][VISIBLE_INDEX]; int visibleAnnotationsOffset = this.contentsOffset; // leave space for number of annotations this.contentsOffset += 2; int counter = 0; if (numberOfVisibleAnnotations != 0) { - Argument argument = arguments[i]; Annotation[] annotations = argument.annotations; for (int j = 0, max = annotations.length; j < max; j++) { Annotation annotation = annotations[j]; @@ -3106,6 +3151,12 @@ } } } + if ((argument.binding.modifiers & ExtraCompilerModifiers.AccImplicitlyNonNull) != 0) { + int generated = generateNonNullAttribute(); + counter+=generated; + globalCounter+=generated; + visibleParametersAnnotationsCounter--; + } this.contents[visibleAnnotationsOffset++] = (byte) (counter >> 8); this.contents[visibleAnnotationsOffset] = (byte) counter; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java index 4cedddc..0795ad6 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ASTNode.java @@ -256,8 +256,6 @@ // this is only used for method invocation as the expression inside an expression statement public static final int InsideExpressionStatement = Bit5; - // for annotation reference, signal if annotation was created from a default: - public static final int IsSynthetic = ASTNode.Bit7; // for name reference within a memberValuePair of an annotation: public static final int IsMemberValueReference = ASTNode.Bit15; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java index f6d98fd..7af8bbd 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java @@ -11,8 +11,6 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; -import java.util.Arrays; - import org.eclipse.jdt.core.compiler.*; import org.eclipse.jdt.internal.compiler.*; import org.eclipse.jdt.internal.compiler.flow.FlowInfo; @@ -69,42 +67,6 @@ default : throw new AbortMethod(this.compilationResult, problem); } - } - - /** - * Materialize a null annotation that has been added from the current default, - * in order to ensure that this annotation will be generated into the .class file, too. - */ - public void addNullnessAnnotation(ReferenceBinding annotationBinding) { - this.annotations = addAnnotation(this, this.annotations, annotationBinding); - } - - /** - * Materialize a null parameter annotation that has been added from the current default, - * in order to ensure that this annotation will be generated into the .class file, too. - */ - public void addParameterNonNullAnnotation(int i, ReferenceBinding annotationBinding) { - Argument argument = this.arguments[i]; - if (argument.type != null) // null happens for constructors of anonymous classes - argument.annotations = addAnnotation(argument.type, argument.annotations, annotationBinding); - } - - private Annotation[] addAnnotation(ASTNode location, Annotation[] oldAnnotations, ReferenceBinding annotationBinding) { - long pos = ((long)location.sourceStart<<32) + location.sourceEnd; - long[] poss = new long[annotationBinding.compoundName.length]; - Arrays.fill(poss, pos); - MarkerAnnotation annotation = new MarkerAnnotation(new QualifiedTypeReference(annotationBinding.compoundName, poss), location.sourceStart); - annotation.declarationSourceEnd = location.sourceEnd; - annotation.resolvedType = annotationBinding; - annotation.bits = IsSynthetic; - if (oldAnnotations == null) { - oldAnnotations = new Annotation[] {annotation}; - } else { - int len = oldAnnotations.length; - System.arraycopy(oldAnnotations, 0, oldAnnotations=new Annotation[len+1], 1, len); - oldAnnotations[0] = annotation; - } - return oldAnnotations; } /** diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java index 4a1bad4..125a30c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java @@ -1218,7 +1218,7 @@ IBinaryAnnotation[] annotations = binaryType.getAnnotations(); if (annotations != null) { long annotationBit = 0L; - TypeBinding defaultNullness = null; + int defaultNullness = NO_NULL_DEFAULT; for (int i = 0; i < annotations.length; i++) { char[] annotationTypeName = annotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) @@ -1233,19 +1233,19 @@ { // parameter is 'false': this means we cancel defaults from outer scopes: annotationBit = TagBits.AnnotationNullUnspecifiedByDefault; - defaultNullness = ReferenceBinding.NULL_UNSPECIFIED; + defaultNullness = NULL_UNSPECIFIED_BY_DEFAULT; break; } } annotationBit = TagBits.AnnotationNonNullByDefault; - defaultNullness = this.environment.getNullAnnotationBinding(TagBits.AnnotationNonNull, false/*resolve*/); + defaultNullness = NONNULL_BY_DEFAULT; break; } } if (annotationBit != 0L) { this.tagBits |= annotationBit; if (CharOperation.equals(this.sourceName(), TypeConstants.PACKAGE_INFO_NAME)) - this.getPackage().nullnessDefaultAnnotation = defaultNullness; + this.getPackage().defaultNullness = defaultNullness; } } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Binding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Binding.java index d0e769e..3c32855 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Binding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Binding.java @@ -48,6 +48,11 @@ public static final MethodBinding[] UNINITIALIZED_METHODS = new MethodBinding[0]; public static final ReferenceBinding[] UNINITIALIZED_REFERENCE_TYPES = new ReferenceBinding[0]; + // Nullness defaults: + public static final int NO_NULL_DEFAULT = 0; + public static final int NULL_UNSPECIFIED_BY_DEFAULT = 1; + public static final int NONNULL_BY_DEFAULT = 2; + /* * Answer the receiver's binding type from Binding.BindingID. */ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExtraCompilerModifiers.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExtraCompilerModifiers.java index 9927aaa..d2d4fe0 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExtraCompilerModifiers.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExtraCompilerModifiers.java @@ -41,4 +41,5 @@ final int AccOverriding = ASTNode.Bit29; // record fact a method overrides another one final int AccImplementing = ASTNode.Bit30; // record fact a method implements another one (it is concrete and overrides an abstract one) final int AccGenericSignature = ASTNode.Bit31; // record fact a type/method/field involves generics in its signature (and need special signature attr) + final int AccImplicitlyNonNull = ASTNode.Bit32; // record fact that a method/argument should be seen as NonNull due to an applicable default } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java index 659ef81..b2a3ccf 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java @@ -1113,21 +1113,6 @@ return getTypeFromCompoundName(name, false, false); } -/** - * Inspect the given tag bits and answer a corresponding null annotation type binding - * @param defaultTagBit tag bits representing the default applicable at the current code location - * @param resolve should the resulting type binding be resolved? - * @return the corresponding concrete annotation type binding (@NonNull or @Nullable) - * or null if no bits of a default-annotation are contained in the given tag bits. - */ -public TypeBinding getNullAnnotationBindingFromDefault(long defaultTagBit, boolean resolve) { - if ((defaultTagBit & TagBits.AnnotationNullUnspecifiedByDefault) != 0) - return ReferenceBinding.NULL_UNSPECIFIED; - if ((defaultTagBit & TagBits.AnnotationNonNullByDefault) != 0) - return getNullAnnotationBinding(TagBits.AnnotationNonNull, resolve); - return null; -} - TypeBinding getNullAnnotationResolved(TypeBinding nullAnnotation, Scope scope) { // avoid unspecific error "The type in.valid cannot be resolved. It is indirectly referenced from required .class files" boolean tolerateMissing = this.mayTolerateMissingType; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java index 0e1140d..84ca7ce 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java @@ -444,35 +444,30 @@ } /** - * After method verifier has finished, fill in missing nullness values from the applicable default. - * @param annotationBinding the null annotation specified to be the default at the current code location. + * After method verifier has finished, fill in missing @NonNull specification from the applicable default. */ -protected void fillInDefaultNonNullness(TypeBinding annotationBinding) { +protected void fillInDefaultNonNullness() { if (this.parameterNonNullness == null) this.parameterNonNullness = new Boolean[this.parameters.length]; AbstractMethodDeclaration sourceMethod = sourceMethod(); for (int i = 0; i < this.parameterNonNullness.length; i++) { if (this.parameters[i].isBaseType()) continue; - boolean added = false; if (this.parameterNonNullness[i] == null) { - added = true; this.parameterNonNullness[i] = Boolean.TRUE; if (sourceMethod != null) - sourceMethod.addParameterNonNullAnnotation(i, (ReferenceBinding)annotationBinding); + sourceMethod.arguments[i].binding.modifiers |= ExtraCompilerModifiers.AccImplicitlyNonNull; + this.tagBits |= TagBits.HasParameterAnnotations; } else if (this.parameterNonNullness[i].booleanValue()) { sourceMethod.scope.problemReporter().nullAnnotationIsRedundant(sourceMethod, i); } - if (added) - this.tagBits |= TagBits.HasParameterAnnotations; } if ( this.returnType != null && !this.returnType.isBaseType() && (this.tagBits & (TagBits.AnnotationNonNull|TagBits.AnnotationNullable)) == 0) { this.tagBits |= TagBits.AnnotationNonNull; - if (sourceMethod != null) - sourceMethod.addNullnessAnnotation((ReferenceBinding)annotationBinding); + this.modifiers |= ExtraCompilerModifiers.AccImplicitlyNonNull; } else if ((this.tagBits & TagBits.AnnotationNonNull) != 0) { sourceMethod.scope.problemReporter().nullAnnotationIsRedundant(sourceMethod, -1/*signifies method return*/); } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java index 8d7c02d..965878b 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/PackageBinding.java @@ -24,8 +24,9 @@ HashtableOfType knownTypes; HashtableOfPackage knownPackages; - // annotation type binding representing the default that has been defined for this package (using @NonNullByDefault) - protected TypeBinding nullnessDefaultAnnotation; + // code representing the default that has been defined for this package (using @NonNullByDefault) + // one of Binding.{NO_NULL_DEFAULT,NULL_UNSPECIFIED_BY_DEFAULT,NONNULL_BY_DEFAULT} + protected int defaultNullness = NO_NULL_DEFAULT; protected PackageBinding() { // for creating problem package @@ -267,22 +268,19 @@ if (this.environment.nullableAnnotationPackage == this && CharOperation.equals(type.compoundName, this.environment.getNullableAnnotationName())) { type.id = TypeIds.T_ConfiguredAnnotationNullable; - this.environment.nullableAnnotationPackage = null; // don't check again + if (!(type instanceof UnresolvedReferenceBinding)) // unresolved type will come back here + this.environment.nullableAnnotationPackage = null; // don't check again } else if (this.environment.nonnullAnnotationPackage == this && CharOperation.equals(type.compoundName, this.environment.getNonNullAnnotationName())) { type.id = TypeIds.T_ConfiguredAnnotationNonNull; - this.environment.nonnullAnnotationPackage = null; // don't check again + if (!(type instanceof UnresolvedReferenceBinding)) // unresolved type will come back here + this.environment.nonnullAnnotationPackage = null; // don't check again } else if (this.environment.nonnullByDefaultAnnotationPackage == this && CharOperation.equals(type.compoundName, this.environment.getNonNullByDefaultAnnotationName())) { type.id = TypeIds.T_ConfiguredAnnotationNonNullByDefault; - this.environment.nonnullByDefaultAnnotationPackage = null; // don't check again + if (!(type instanceof UnresolvedReferenceBinding)) // unresolved type will come back here + this.environment.nonnullByDefaultAnnotationPackage = null; // don't check again } -} - -public TypeBinding getNullnessDefaultAnnotation(Scope scope) { - if (this.nullnessDefaultAnnotation instanceof UnresolvedReferenceBinding) - return this.nullnessDefaultAnnotation = this.environment.getNullAnnotationResolved(this.nullnessDefaultAnnotation, scope); - return this.nullnessDefaultAnnotation; } public char[] readableName() /*java.lang*/ { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java index 75de854..5116382 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java @@ -50,14 +50,6 @@ public boolean hasTypeBit(int bit) { return false; } }; - /** - * This faked annotation type binding marks types with unspecified nullness. - * For use in {@link PackageBinding#nullnessDefaultAnnotation} and SourceTypeBinding#nullnessDefaultAnnotation - */ - final static ReferenceBinding NULL_UNSPECIFIED = new ReferenceBinding() { /* faked type binding */ - public boolean hasTypeBit(int bit) { return false; } - }; - private static final Comparator FIELD_COMPARATOR = new Comparator() { public int compare(Object o1, Object o2) { char[] n1 = ((FieldBinding) o1).name; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java index 094c155..faa5a51 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java @@ -56,7 +56,7 @@ private SimpleLookupTable storedAnnotations = null; // keys are this ReferenceBinding & its fields and methods, value is an AnnotationHolder - private TypeBinding nullnessDefaultAnnotation; + private int defaultNullness; private int nullnessDefaultInitialized = 0; // 0: nothing; 1: type; 2: package public SourceTypeBinding(char[][] compoundName, PackageBinding fPackage, ClassScope scope) { @@ -1618,9 +1618,9 @@ if (methodDecl != null) { if (method.parameters != Binding.NO_PARAMETERS) methodDecl.createArgumentBindings(); - TypeBinding annotationBinding = findDefaultNullness(method, methodDecl.scope.environment()); - if (annotationBinding != null && annotationBinding.id == TypeIds.T_ConfiguredAnnotationNonNull) - method.fillInDefaultNonNullness(annotationBinding); + if (hasNonNullDefault(method, methodDecl.scope.environment())) { + method.fillInDefaultNonNullness(); + } } } private void evaluateNullAnnotations(long annotationTagBits) { @@ -1628,69 +1628,66 @@ return; this.nullnessDefaultInitialized = 1; // transfer nullness info from tagBits to this.nullnessDefaultAnnotation - TypeBinding defaultAnnotation = getPackage().environment - .getNullAnnotationBindingFromDefault(annotationTagBits, false/*resolve*/); - if (defaultAnnotation != null) { + int newDefaultNullness = NO_NULL_DEFAULT; + if ((annotationTagBits & TagBits.AnnotationNullUnspecifiedByDefault) != 0) + newDefaultNullness = NULL_UNSPECIFIED_BY_DEFAULT; + else if ((annotationTagBits & TagBits.AnnotationNonNullByDefault) != 0) + newDefaultNullness = NONNULL_BY_DEFAULT; + if (newDefaultNullness != NO_NULL_DEFAULT) { if (CharOperation.equals(this.sourceName, TypeConstants.PACKAGE_INFO_NAME)) { - getPackage().nullnessDefaultAnnotation = defaultAnnotation; + getPackage().defaultNullness = newDefaultNullness; } else { - this.nullnessDefaultAnnotation = defaultAnnotation; + this.defaultNullness = newDefaultNullness; } } } -private TypeBinding getNullnessDefaultAnnotation() { - if (this.nullnessDefaultAnnotation instanceof UnresolvedReferenceBinding) - this.nullnessDefaultAnnotation = this.scope.environment().getNullAnnotationResolved(this.nullnessDefaultAnnotation, this.scope); - return this.nullnessDefaultAnnotation; -} /** - * Answer the nullness default applicable at the given method binding. - * Possible values: + * Answer whether the given method binding is subject to a @NonNull default */ -private TypeBinding findDefaultNullness(MethodBinding methodBinding, LookupEnvironment environment) { +private boolean hasNonNullDefault(MethodBinding methodBinding, LookupEnvironment environment) { + int foundDefaultNullness = findNonNullDefault(methodBinding, environment); + if (foundDefaultNullness == NONNULL_BY_DEFAULT) { + // requesting implicit use of @NonNull, check if the annotation type is present: + if (environment.getNullAnnotationBinding(TagBits.AnnotationNonNull, true) == null) { + this.scope.problemReporter().missingNullAnnotationType(environment.getNonNullAnnotationName()); + environment.globalOptions.defaultNonNullness = 0; + return false; + } + return true; + } + return false; +} +private int findNonNullDefault(MethodBinding methodBinding, LookupEnvironment environment) { // find the applicable default inside->out: // method - TypeBinding annotationBinding = environment.getNullAnnotationBindingFromDefault(methodBinding.tagBits, true/*resolve*/); - if (annotationBinding != null) - return annotationBinding; + if ((methodBinding.tagBits & TagBits.AnnotationNonNullByDefault) != 0) + return NONNULL_BY_DEFAULT; + if ((methodBinding.tagBits & TagBits.AnnotationNullUnspecifiedByDefault) != 0) + return NULL_UNSPECIFIED_BY_DEFAULT; // type ReferenceBinding type = methodBinding.declaringClass; ReferenceBinding currentType = type; + int foundDefaultNullness = NO_NULL_DEFAULT; while (currentType instanceof SourceTypeBinding) { - annotationBinding = ((SourceTypeBinding) currentType).getNullnessDefaultAnnotation(); - if (annotationBinding != null) - return annotationBinding; + foundDefaultNullness = ((SourceTypeBinding)currentType).defaultNullness; + if (foundDefaultNullness != NO_NULL_DEFAULT) { + return foundDefaultNullness; + } currentType = currentType.enclosingType(); } // package - annotationBinding = type.getPackage().getNullnessDefaultAnnotation(this.scope); - if (annotationBinding != null) - return annotationBinding; + foundDefaultNullness = type.getPackage().defaultNullness; + if (foundDefaultNullness != NO_NULL_DEFAULT) { + return foundDefaultNullness; + } // global - long defaultNullness = environment.globalOptions.defaultNonNullness; - if (defaultNullness != 0) { - // we have a default, so we need an annotation type to record this during compile and in the byte code - annotationBinding = environment.getNullAnnotationBinding(defaultNullness, true/*resolve*/); - if (annotationBinding != null) - return annotationBinding; - - // on this branch default was not defined using an annotation, thus annotation type can still be missing - if (defaultNullness == TagBits.AnnotationNonNull) - this.scope.problemReporter().missingNullAnnotationType(environment.getNonNullAnnotationName()); - else - this.scope.problemReporter().abortDueToInternalError("Illegal default nullness value: "+defaultNullness); //$NON-NLS-1$ - // reset default to avoid duplicate errors: - environment.globalOptions.defaultNonNullness = 0; - } - return null; + if (environment.globalOptions.defaultNonNullness == TagBits.AnnotationNonNull) + return NONNULL_BY_DEFAULT; + return NO_NULL_DEFAULT; } public AnnotationHolder retrieveAnnotationHolder(Binding binding, boolean forceInitialization) { diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTConverter.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTConverter.java index a2d1924..b72a6fb 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTConverter.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTConverter.java @@ -4361,7 +4361,6 @@ int indexInAnnotations = 0; while ((token = this.scanner.getNextToken()) != TerminalTokens.TokenNameEOF) { IExtendedModifier modifier = null; - switchToken: switch(token) { case TerminalTokens.TokenNameabstract: modifier = createModifier(Modifier.ModifierKeyword.ABSTRACT_KEYWORD); @@ -4399,13 +4398,7 @@ case TerminalTokens.TokenNameAT : // we have an annotation if (annotations != null && indexInAnnotations < annotations.length) { - // method may have synthetic annotations, skip them: - org.eclipse.jdt.internal.compiler.ast.Annotation annotation; - do { - if (indexInAnnotations == annotations.length) - break switchToken; - annotation = annotations[indexInAnnotations++]; - } while ((annotation.bits & org.eclipse.jdt.internal.compiler.ast.ASTNode.IsSynthetic) != 0); + org.eclipse.jdt.internal.compiler.ast.Annotation annotation = annotations[indexInAnnotations++]; modifier = convert(annotation); this.scanner.resetTo(annotation.declarationSourceEnd + 1, modifiersEnd); } @@ -4519,7 +4512,6 @@ int token; while ((token = this.scanner.getNextToken()) != TerminalTokens.TokenNameEOF) { IExtendedModifier modifier = null; - switchToken: switch(token) { case TerminalTokens.TokenNameabstract: modifier = createModifier(Modifier.ModifierKeyword.ABSTRACT_KEYWORD); @@ -4557,13 +4549,7 @@ case TerminalTokens.TokenNameAT : // we have an annotation if (annotations != null && indexInAnnotations < annotations.length) { - // argument may have synthetic annotations, skip them: - org.eclipse.jdt.internal.compiler.ast.Annotation annotation; - do { - if (indexInAnnotations == annotations.length) - break switchToken; - annotation = annotations[indexInAnnotations++]; - } while ((annotation.bits & org.eclipse.jdt.internal.compiler.ast.ASTNode.IsSynthetic) != 0); + org.eclipse.jdt.internal.compiler.ast.Annotation annotation = annotations[indexInAnnotations++]; modifier = convert(annotation); this.scanner.resetTo(annotation.declarationSourceEnd + 1, this.compilationUnitSourceLength); } @@ -4637,7 +4623,6 @@ break; case TerminalTokens.TokenNameAT : // we have an annotation - // (local variable has no synthetic annotations, no need to skip them) if (annotations != null && indexInAnnotations < annotations.length) { org.eclipse.jdt.internal.compiler.ast.Annotation annotation = annotations[indexInAnnotations++]; modifier = convert(annotation); @@ -4740,7 +4725,6 @@ break; case TerminalTokens.TokenNameAT : // we have an annotation - // (local variable has no synthetic annotations, no need to skip them) if (annotations != null && indexInAnnotations < annotations.length) { org.eclipse.jdt.internal.compiler.ast.Annotation annotation = annotations[indexInAnnotations++]; modifier = convert(annotation); @@ -4822,7 +4806,6 @@ break; case TerminalTokens.TokenNameAT : // we have an annotation - // (local variable has no synthetic annotations, no need to skip them) if (annotations != null && indexInAnnotations < annotations.length) { org.eclipse.jdt.internal.compiler.ast.Annotation annotation = annotations[indexInAnnotations++]; modifier = convert(annotation);