diff options
author | Stephan Herrmann | 2015-12-08 18:38:48 +0000 |
---|---|---|
committer | Stephan Herrmann | 2015-12-08 18:38:48 +0000 |
commit | d1a89d726f70e4e87570ae93c703b6ccb95d1980 (patch) | |
tree | 84266cfcf9d82aba32000218bc51d927c3d2cb68 /org.eclipse.jdt.core/compiler/org/eclipse/jdt | |
parent | 78f7dfc554cc396c300f5e2309fef78cc569d57e (diff) | |
download | org.eclipse.objectteams-d1a89d726f70e4e87570ae93c703b6ccb95d1980.tar.gz org.eclipse.objectteams-d1a89d726f70e4e87570ae93c703b6ccb95d1980.tar.xz org.eclipse.objectteams-d1a89d726f70e4e87570ae93c703b6ccb95d1980.zip |
Update jdt.core & tests to I20151208-0800 for 4.6 M4
Diffstat (limited to 'org.eclipse.jdt.core/compiler/org/eclipse/jdt')
60 files changed, 1295 insertions, 436 deletions
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.java index df0ad2fa8..e62e8e210 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/CharOperation.java @@ -8,6 +8,7 @@ * Contributors: * IBM Corporation - initial API and implementation * Luiz-Otavio Zorzella <zorzella at gmail dot com> - Improve CamelCase algorithm + * Gábor Kövesdán - Contribution for Bug 350000 - [content assist] Include non-prefix matches in auto-complete suggestions *******************************************************************************/ package org.eclipse.jdt.core.compiler; @@ -694,6 +695,94 @@ public static final boolean camelCaseMatch(char[] pattern, int patternStart, int } /** + * Answers true if the characters of the pattern are contained in the + * name as a substring, in a case-insensitive way. + * + * @param pattern the given pattern + * @param name the given name + * @return true if the pattern matches the given name, false otherwise + * @since 3.12 + */ +public static final boolean substringMatch(String pattern, String name) { + if (pattern == null || pattern.length() == 0) { + return true; + } + if (name == null) { + return false; + } + return checkSubstringMatch(pattern.toCharArray(), name.toCharArray()); +} + +/** + * Answers true if the characters of the pattern are contained in the + * name as a substring, in a case-insensitive way. + * + * @param pattern the given pattern + * @param name the given name + * @return true if the pattern matches the given name, false otherwise + * @since 3.12 + */ +public static final boolean substringMatch(char[] pattern, char[] name) { + if (pattern == null || pattern.length == 0) { + return true; + } + if (name == null) { + return false; + } + return checkSubstringMatch(pattern, name); +} + +/** + * Internal substring matching method; called after the null and length + * checks are performed. + * + * @param pattern the given pattern + * @param name the given name + * @return true if the pattern matches the given name, false otherwise + * + * @see CharOperation#substringMatch(char[], char[]) + */ +private static final boolean checkSubstringMatch(char[] pattern, char[] name) { + +/* XXX: to be revised/enabled + + // allow non-consecutive occurrence of pattern characters + if (pattern.length >= 3) { + int pidx = 0; + + for (int nidx = 0; nidx < name.length; nidx++) { + if (Character.toLowerCase(name[nidx]) == + Character.toLowerCase(pattern[pidx])) + pidx++; + if (pidx == pattern.length) + return true; + } + + // for short patterns only allow consecutive occurrence + } else { +*/ + // outer loop iterates on the characters of the name; trying to + // match at any possible position + outer: for (int nidx = 0; nidx < name.length - pattern.length + 1; nidx++) { + // inner loop iterates on pattern characters + for (int pidx = 0; pidx < pattern.length; pidx++) { + if (Character.toLowerCase(name[nidx + pidx]) != + Character.toLowerCase(pattern[pidx])) { + // no match until parameter list; do not match parameter list + if ((name[nidx + pidx] == '(') || (name[nidx + pidx] == ':')) + return false; + continue outer; + } + if (pidx == pattern.length - 1) + return true; + } + } + // XXX: } + + return false; +} + +/** * Returns the char arrays as an array of Strings * * @param charArrays the char array to convert @@ -1156,6 +1245,148 @@ public static final char[] concat( System.arraycopy(third, 0, result, length1 + length2 + 2, length3); return result; } +/** + * Answers the concatenation of the two arrays inserting the separator character between the two arrays. + * It answers null if the two arrays are null. + * If the first array is null or is empty, then the second array is returned. + * If the second array is null or is empty, then the first array is returned. + * <br> + * <br> + * For example: + * <ol> + * <li><pre> + * first = null + * second = { 'a' } + * separator = '/' + * => result = { ' a' } + * </pre> + * </li> + * <li><pre> + * first = { ' a' } + * second = null + * separator = '/' + * => result = { ' a' } + * </pre> + * </li> + * <li><pre> + * first = { ' a' } + * second = { ' b' } + * separator = '/' + * => result = { ' a' , '/', 'b' } + * </pre> + * </li> + * * <li><pre> + * first = { ' a' } + * second = { } + * separator = '/' + * => result = { ' a'} + * </pre> + * </li> + + * </ol> + * + * @param first the first array to concatenate + * @param second the second array to concatenate + * @param separator the character to insert + * @return the concatenation of the two arrays inserting the separator character + * between the two arrays , or null if the two arrays are null. + * @since 3.12 + */ +public static final char[] concatNonEmpty( + char[] first, + char[] second, + char separator) { + if (first == null || first.length == 0) + return second; + if (second == null || second.length == 0) + return first; + return concat(first, second, separator); +} +/** + * Answers the concatenation of the three arrays inserting the sep1 character between the + * first two arrays and sep2 between the last two. + * It answers null if the three arrays are null. + * If the first array is null or empty, then it answers the concatenation of second and third inserting + * the sep2 character between them. + * If the second array is null or empty, then it answers the concatenation of first and third inserting + * the sep1 character between them. + * If the third array is null or empty, then it answers the concatenation of first and second inserting + * the sep1 character between them. + * <br> + * <br> + * For example: + * <ol> + * <li><pre> + * first = null + * sep1 = '/' + * second = { 'a' } + * sep2 = ':' + * third = { 'b' } + * => result = { ' a' , ':', 'b' } + * </pre> + * </li> + * <li><pre> + * first = { 'a' } + * sep1 = '/' + * second = null + * sep2 = ':' + * third = { 'b' } + * => result = { ' a' , '/', 'b' } + * </pre> + * </li> + * <li><pre> + * first = { 'a' } + * sep1 = '/' + * second = { 'b' } + * sep2 = ':' + * third = null + * => result = { ' a' , '/', 'b' } + * </pre> + * </li> + * <li><pre> + * first = { 'a' } + * sep1 = '/' + * second = { 'b' } + * sep2 = ':' + * third = { 'c' } + * => result = { ' a' , '/', 'b' , ':', 'c' } + * </pre> + * </li> + * <li><pre> + * first = { 'a' } + * sep1 = '/' + * second = { } + * sep2 = ':' + * third = { 'c' } + * => result = { ' a', ':', 'c' } + * </pre> + * </li> + * </ol> + * + * @param first the first array to concatenate + * @param sep1 the character to insert + * @param second the second array to concatenate + * @param sep2 the character to insert + * @param third the second array to concatenate + * @return the concatenation of the three arrays inserting the sep1 character between the + * two arrays and sep2 between the last two. + * @since 3.12 + */ +public static final char[] concatNonEmpty( + char[] first, + char sep1, + char[] second, + char sep2, + char[] third) { + if (first == null || first.length == 0) + return concatNonEmpty(second, third, sep2); + if (second == null || second.length == 0) + return concatNonEmpty(first, third, sep1); + if (third == null || third.length == 0) + return concatNonEmpty(first, second, sep1); + + return concat(first, sep1, second, sep2, third); +} /** * Answers a new array with prepending the prefix character and appending the suffix @@ -1384,6 +1615,65 @@ public static final char[] concatWith(char[][] array, char separator) { } /** + * Answers the concatenation of the given array parts using the given separator between each part + * irrespective of whether an element is a zero length array or not. + * <br> + * <br> + * For example:<br> + * <ol> + * <li><pre> + * array = { { 'a' }, {}, { 'b' } } + * separator = '' + * => result = { 'a', '/', '/', 'b' } + * </pre> + * </li> + * <li><pre> + * array = { { 'a' }, { 'b' } } + * separator = '.' + * => result = { 'a', '.', 'b' } + * </pre> + * </li> + * <li><pre> + * array = null + * separator = '.' + * => result = { } + * </pre></li> + * </ol> + * + * @param array the given array + * @param separator the given separator + * @return the concatenation of the given array parts using the given separator between each part + * @since 3.12 + */ +public static final char[] concatWithAll(char[][] array, char separator) { + int length = array == null ? 0 : array.length; + if (length == 0) + return CharOperation.NO_CHAR; + + int size = length - 1; + int index = length; + while (--index >= 0) { + size += array[index].length; + } + char[] result = new char[size]; + index = length; + while (--index >= 0) { + length = array[index].length; + if (length > 0) { + System.arraycopy( + array[index], + 0, + result, + (size -= length), + length); + } + if (--size >= 0) + result[size] = separator; + } + return result; +} + +/** * Answers true if the array contains an occurrence of character, false otherwise. * * <br> @@ -3554,6 +3844,94 @@ public static final char[][] splitOn( } /** + * Return a new array which is the split of the given array using the given divider ignoring the + * text between (possibly nested) openEncl and closingEncl. If there are no openEncl in the code + * this is identical to {@link CharOperation#splitOn(char, char[], int, int)}. The given end + * is exclusive and the given start is inclusive. + * <br> + * <br> + * For example: + * <ol> + * <li><pre> + * divider = ',' + * array = { 'A' , '<', 'B', ',', 'C', '>', ',', 'D' } + * start = 0 + * end = 8 + * result => { { 'A' , '<', 'B', ',', 'C', '>'}, { 'D' }} + * </pre> + * </li> + * </ol> + * + * @param divider the given divider + * @param openEncl the opening enclosure + * @param closeEncl the closing enclosure + * @param array the given array + * @param start the given starting index + * @param end the given ending index + * @return a new array which is the split of the given array using the given divider + * @throws ArrayIndexOutOfBoundsException if start is lower than 0 or end is greater than the array length + * @since 3.12 + */ +public static final char[][] splitOnWithEnclosures( + char divider, + char openEncl, + char closeEncl, + char[] array, + int start, + int end) { + int length = array == null ? 0 : array.length; + if (length == 0 || start > end) + return NO_CHAR_CHAR; + + int wordCount = 1; + int enclCount = 0; + for (int i = start; i < end; i++) { + if (array[i] == openEncl) + enclCount++; + else if (array[i] == divider) + wordCount++; + } + if (enclCount == 0) + return CharOperation.splitOn(divider, array, start, end); + + int nesting = 0; + if (openEncl == divider || closeEncl == divider) // divider should be distinct + return CharOperation.NO_CHAR_CHAR; + + int[][] splitOffsets = new int[wordCount][2]; //maximum + int last = start, currentWord = 0, prevOffset = start; + for (int i = start; i < end; i++) { + if (array[i] == openEncl) { + ++nesting; + continue; + } + if (array[i] == closeEncl) { + if (nesting > 0) + --nesting; + continue; + } + if (array[i] == divider && nesting == 0) { + splitOffsets[currentWord][0] = prevOffset; + last = splitOffsets[currentWord++][1] = i; + prevOffset = last + 1; + } + } + if (last < end - 1) { + splitOffsets[currentWord][0] = prevOffset; + splitOffsets[currentWord++][1] = end; + } + char[][] split = new char[currentWord][]; + for (int i = 0; i < currentWord; ++i) { + int sStart = splitOffsets[i][0]; + int sEnd = splitOffsets[i][1]; + int size = sEnd - sStart; + split[i] = new char[size]; + System.arraycopy(array, sStart, split[i], 0, size); + } + return split; + } + +/** * Answers a new array which is a copy of the given array starting at the given start and * ending at the given end. The given start is inclusive and the given end is exclusive. * Answers null if start is greater than end, if start is lower than 0 or if end is greater diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java index f6a849161..89c602edd 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java @@ -1818,6 +1818,8 @@ void setSourceStart(int sourceStart); int ContradictoryNullAnnotationsInferredFunctionType = MethodRelated + 973; /** @since 3.11 */ int IllegalReturnNullityRedefinitionFreeTypeVariable = MethodRelated + 974; + /** @since 3.12 */ + int IllegalRedefinitionOfTypeVariable = 975; // Java 8 work 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 50a948d6a..aacac0a0f 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 @@ -735,12 +735,17 @@ public abstract class ASTNode implements TypeConstants, TypeIds { continue; // not much we can do without a target type, assume it only happens after some resolve error if (argumentTypes[i] != null && argumentTypes[i].isPolyType()) { argument.setExpectedType(parameterType); - TypeBinding updatedArgumentType = argument.resolveType(scope); + TypeBinding updatedArgumentType; if (argument instanceof LambdaExpression) { - // LE.resolveType may return a valid binding because resolve does not detect structural errors at this point. LambdaExpression lambda = (LambdaExpression) argument; + // avoid complaining about non-kosher descriptor as secondary problem + boolean skipKosherCheck = method.problemId() == ProblemReasons.Ambiguous; + updatedArgumentType = lambda.resolveType(scope, skipKosherCheck); + // additional checks, because LE.resolveType may return a valid binding even in the presence of structural errors if (!lambda.isCompatibleWith(parameterType, scope) || lambda.hasErrors()) continue; + } else { + updatedArgumentType = argument.resolveType(scope); } if (updatedArgumentType != null && updatedArgumentType.kind() != Binding.POLY_TYPE) argumentTypes[i] = updatedArgumentType; @@ -1040,10 +1045,10 @@ public abstract class ASTNode implements TypeConstants, TypeIds { System.arraycopy(se8Annotations, 0, se8Annotations = new AnnotationBinding[se8count + 1], 0, se8count); se8Annotations[se8count++] = annotation; } - if (annotationType.id == TypeIds.T_ConfiguredAnnotationNonNull) { + if (annotationType.hasNullBit(TypeIds.BitNonNullAnnotation)) { se8nullBits |= TagBits.AnnotationNonNull; se8NullAnnotation = annotations[i]; - } else if (annotationType.id == TypeIds.T_ConfiguredAnnotationNullable) { + } else if (annotationType.hasNullBit(TypeIds.BitNullableAnnotation)) { se8nullBits |= TagBits.AnnotationNullable; se8NullAnnotation = annotations[i]; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java index 1d5b5abfe..315d40b4e 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java @@ -215,9 +215,10 @@ public Expression enclosingInstance() { } public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { + cleanUpInferenceContexts(); if (!valueRequired) currentScope.problemReporter().unusedObjectAllocation(this); - + //{ObjectTeams: redirect? if (this.roleCreatorCall != null) { this.roleCreatorCall.generateCode(currentScope, codeStream, valueRequired); @@ -556,7 +557,6 @@ public TypeBinding resolveType(BlockScope scope) { this.resolvedType = this.type.resolvedType = this.binding.declaringClass; resolvePolyExpressionArguments(this, this.binding, this.argumentTypes, scope); } else { - //{ObjectTeams: may need to instantiate parameters of constructor AnchorMapping anchorMapping = AnchorMapping.setupNewMapping(null, this.arguments, scope); try { @@ -625,7 +625,7 @@ public TypeBinding resolveType(BlockScope scope) { if (this.binding instanceof ParameterizedGenericMethodBinding && this.typeArguments != null) { TypeVariableBinding[] typeVariables = this.binding.original().typeVariables(); for (int i = 0; i < this.typeArguments.length; i++) - this.typeArguments[i].checkNullConstraints(scope, typeVariables, i); + this.typeArguments[i].checkNullConstraints(scope, (ParameterizedGenericMethodBinding) this.binding, typeVariables, i); } } } @@ -957,6 +957,17 @@ public InferenceContext18 getInferenceContext(ParameterizedMethodBinding method) return null; return (InferenceContext18) this.inferenceContexts.get(method); } + +@Override +public void cleanUpInferenceContexts() { + if (this.inferenceContexts == null) + return; + for (Object value : this.inferenceContexts.valueTable) + if (value != null) + ((InferenceContext18) value).cleanUp(); + this.inferenceContexts = null; +} + //-- interface InvocationSite: -- public ExpressionContext getExpressionContext() { return this.expressionContext; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java index e37bca254..828fa15f1 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Annotation.java @@ -400,34 +400,32 @@ public abstract class Annotation extends Expression { tagBits |= TagBits.AnnotationInstantiation; break; // SH} - case TypeIds.T_ConfiguredAnnotationNullable : - tagBits |= TagBits.AnnotationNullable; - break; - case TypeIds.T_ConfiguredAnnotationNonNull : - tagBits |= TagBits.AnnotationNonNull; - break; - case TypeIds.T_ConfiguredAnnotationNonNullByDefault : - // seeing this id implies that null annotation analysis is enabled - Object value = null; - if (valueAttribute != null) { - if (valueAttribute.compilerElementPair != null) - value = valueAttribute.compilerElementPair.value; - } else { // fetch default value - TODO: cache it? - MethodBinding[] methods = annotationType.methods(); - if (methods != null && methods.length == 1) - value = methods[0].getDefaultValue(); - else - tagBits |= TagBits.AnnotationNonNullByDefault; // custom unconfigurable NNBD - } - if (value instanceof BooleanConstant) { - // boolean value is used for declaration annotations, signal using the annotation tag bit: - tagBits |= ((BooleanConstant)value).booleanValue() ? TagBits.AnnotationNonNullByDefault : TagBits.AnnotationNullUnspecifiedByDefault; - } else if (value != null) { - // non-boolean value signals type annotations, evaluate from DefaultLocation[] to bitvector a la Binding#NullnessDefaultMASK: - tagBits |= nullLocationBitsFromAnnotationValue(value); - } - break; } + if (annotationType.hasNullBit(TypeIds.BitNullableAnnotation)) { + tagBits |= TagBits.AnnotationNullable; + } else if (annotationType.hasNullBit(TypeIds.BitNonNullAnnotation)) { + tagBits |= TagBits.AnnotationNonNull; + } else if (annotationType.hasNullBit(TypeIds.BitNonNullByDefaultAnnotation)) { + Object value = null; + if (valueAttribute != null) { + if (valueAttribute.compilerElementPair != null) + value = valueAttribute.compilerElementPair.value; + } else { // fetch default value - TODO: cache it? + MethodBinding[] methods = annotationType.methods(); + if (methods != null && methods.length == 1) + value = methods[0].getDefaultValue(); + else + tagBits |= TagBits.AnnotationNonNullByDefault; // custom unconfigurable NNBD + } + if (value instanceof BooleanConstant) { + // boolean value is used for declaration annotations, signal using the annotation tag bit: + tagBits |= ((BooleanConstant)value).booleanValue() ? TagBits.AnnotationNonNullByDefault : TagBits.AnnotationNullUnspecifiedByDefault; + } else if (value != null) { + // non-boolean value signals type annotations, evaluate from DefaultLocation[] to bitvector a la Binding#NullnessDefaultMASK: + tagBits |= nullLocationBitsFromAnnotationValue(value); + } + } + return tagBits; } @@ -1200,8 +1198,7 @@ public abstract class Annotation extends Expression { QualifiedTypeReference.rejectAnnotationsOnStaticMemberQualififer(scope, currentType, new Annotation [] { annotation }); continue nextAnnotation; } else { - int id = annotation.resolvedType.id; - if (id == TypeIds.T_ConfiguredAnnotationNonNull || id == TypeIds.T_ConfiguredAnnotationNullable) { + if (annotation.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { scope.problemReporter().nullAnnotationUnsupportedLocation(annotation); continue nextAnnotation; } @@ -1212,6 +1209,10 @@ public abstract class Annotation extends Expression { } } + public boolean hasNullBit(int bit) { + return this.resolvedType instanceof ReferenceBinding && ((ReferenceBinding) this.resolvedType).hasNullBit(bit); + } + public abstract void traverse(ASTVisitor visitor, BlockScope scope); public abstract void traverse(ASTVisitor visitor, ClassScope scope); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayAllocationExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayAllocationExpression.java index 6aaaa5c25..a922effd6 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayAllocationExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayAllocationExpression.java @@ -48,6 +48,8 @@ public class ArrayAllocationExpression extends Expression { public Annotation [][] annotationsOnDimensions; // jsr308 style annotations. public ArrayInitializer initializer; + private TypeBinding expectedType; + public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { for (int i = 0, max = this.dimensions.length; i < max; i++) { Expression dim; @@ -184,6 +186,21 @@ public class ArrayAllocationExpression extends Expression { { scope.problemReporter().contradictoryNullAnnotations(this.type.annotations[this.type.annotations.length-1]); } + LookupEnvironment environment = scope.environment(); + if (environment.usesNullTypeAnnotations() + && this.annotationsOnDimensions == null // don't annotate if explicit annotations are given on dimensions ... + && ((referenceType.tagBits & TagBits.AnnotationNullMASK) == 0) // ... or leaf type + && this.expectedType != null) // need this to determine our action + { + Expression lastDim = this.dimensions[this.dimensions.length-1]; + if (lastDim instanceof IntLiteral && ((IntLiteral) lastDim).value == 0) { + long tagBit = this.expectedType.leafComponentType().tagBits & TagBits.AnnotationNullMASK; + // let new X[0] be seen as "@NonNull X[]", or "@Nullable X[]" just as expected + AnnotationBinding[] nullAnnotations = environment.nullAnnotationsFromTagBits(tagBit); + if (nullAnnotations != null) + referenceType = environment.createAnnotatedType(referenceType, nullAnnotations); + } + } //{ObjectTeams: referenceType = RoleTypeCreator.maybeWrapUnqualifiedRoleType(referenceType, scope, this); // SH} @@ -214,6 +231,10 @@ public class ArrayAllocationExpression extends Expression { return this.resolvedType; } + @Override + public void setExpectedType(TypeBinding expectedType) { + this.expectedType = expectedType; + } public void traverse(ASTVisitor visitor, BlockScope scope) { if (visitor.visit(this, scope)) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java index 0f13ed995..b8163091e 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ArrayReference.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2013 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -60,7 +60,7 @@ public FlowInfo analyseAssignment(BlockScope currentScope, FlowContext flowConte } public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - this.receiver.checkNPE(currentScope, flowContext, flowInfo); + this.receiver.checkNPE(currentScope, flowContext, flowInfo, 1); flowInfo = this.receiver.analyseCode(currentScope, flowContext, flowInfo); flowInfo = this.position.analyseCode(currentScope, flowContext, flowInfo); this.position.checkNPEbyUnboxing(currentScope, flowContext, flowInfo); @@ -69,12 +69,12 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl return flowInfo; } -public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { if ((this.resolvedType.tagBits & TagBits.AnnotationNullable) != 0) { scope.problemReporter().arrayReferencePotentialNullReference(this); return true; } else { - return super.checkNPE(scope, flowContext, flowInfo); + return super.checkNPE(scope, flowContext, flowInfo, ttlForFieldCheck); } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java index 80f55b404..13f06d9e1 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CastExpression.java @@ -359,9 +359,9 @@ public static void checkNeedForArgumentCasts(BlockScope scope, int operator, int } } -public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { checkNPEbyUnboxing(scope, flowContext, flowInfo); - return this.expression.checkNPE(scope, flowContext, flowInfo); + return this.expression.checkNPE(scope, flowContext, flowInfo, ttlForFieldCheck); } private static void checkAlternateBinding(BlockScope scope, Expression receiver, TypeBinding receiverType, MethodBinding binding, Expression[] arguments, TypeBinding[] originalArgumentTypes, TypeBinding[] alternateArgumentTypes, final InvocationSite invocationSite) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ClassLiteralAccess.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ClassLiteralAccess.java index c12c1756e..c375e8aa2 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ClassLiteralAccess.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ClassLiteralAccess.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2012 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -120,7 +120,8 @@ public class ClassLiteralAccess extends Expression { Corollary wise, we should resolve the type of the class literal expression to be a raw type as class literals exist only for the raw underlying type. */ - this.targetType = scope.environment().convertToRawType(this.targetType, true /* force conversion of enclosing types*/); + LookupEnvironment environment = scope.environment(); + this.targetType = environment.convertToRawType(this.targetType, true /* force conversion of enclosing types*/); if (this.targetType.isArrayType()) { ArrayBinding arrayBinding = (ArrayBinding) this.targetType; @@ -174,11 +175,13 @@ public class ClassLiteralAccess extends Expression { // Integer.class --> Class<Integer>, perform boxing of base types (int.class --> Class<Integer>) TypeBinding boxedType = null; if (this.targetType.id == T_void) { - boxedType = scope.environment().getResolvedType(JAVA_LANG_VOID, scope); + boxedType = environment.getResolvedType(JAVA_LANG_VOID, scope); } else { boxedType = scope.boxing(this.targetType); } - this.resolvedType = scope.environment().createParameterizedType(classType, new TypeBinding[]{ boxedType }, null/*not a member*/); + if (environment.usesNullTypeAnnotations()) + boxedType = environment.createAnnotatedType(boxedType, new AnnotationBinding[] { environment.getNonNullAnnotation() }); + this.resolvedType = environment.createParameterizedType(classType, new TypeBinding[]{ boxedType }, null/*not a member*/); } else { this.resolvedType = classType; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java index 4eb6fb133..dee664d14 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration.java @@ -196,6 +196,9 @@ public void cleanUp() { } this.suppressWarningAnnotations = null; + + if (this.scope != null) + this.scope.cleanUpInferenceContexts(); } private void cleanUp(TypeDeclaration type) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java index d285c6d72..57665e9e4 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java @@ -187,7 +187,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, return mergedInfo; } - public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { if ((this.nullStatus & FlowInfo.NULL) != 0) scope.problemReporter().expressionNullReference(this); else if ((this.nullStatus & FlowInfo.POTENTIALLY_NULL) != 0) diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java index fab1d4d99..b640d8991 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ExplicitConstructorCall.java @@ -887,6 +887,11 @@ public class ExplicitConstructorCall extends Statement implements Invocation { return null; } + @Override + public void cleanUpInferenceContexts() { + // Nothing to do. + } + public Expression[] arguments() { return this.arguments; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java index f082e6835..e5b0f4dff 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Expression.java @@ -765,9 +765,10 @@ boolean handledByGeneratedMethod(Scope scope, TypeBinding castType, TypeBinding * @param scope the scope of the analysis * @param flowContext the current flow context * @param flowInfo the upstream flow info; caveat: may get modified + * @param ttlForFieldCheck if this is a reference to a field we will mark that field as nonnull for the specified timeToLive * @return could this expression be checked by the current implementation? */ -public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { boolean isNullable = false; if (this.resolvedType != null) { // 1. priority: @NonNull @@ -800,6 +801,9 @@ public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flow } return false; // not checked } +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + return checkNPE(scope, flowContext, flowInfo, 0); // default: don't mark field references as checked for null +} /** If this expression requires unboxing check if that operation can throw NPE. */ protected void checkNPEbyUnboxing(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java index 46ebd3afb..25b874298 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FieldReference.java @@ -187,7 +187,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl boolean nonStatic = !this.binding.isStatic(); this.receiver.analyseCode(currentScope, flowContext, flowInfo, nonStatic); if (nonStatic) { - this.receiver.checkNPE(currentScope, flowContext, flowInfo); + this.receiver.checkNPE(currentScope, flowContext, flowInfo, 1); } if (valueRequired || currentScope.compilerOptions().complianceLevel >= ClassFileConstants.JDK1_4) { @@ -196,11 +196,11 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl return flowInfo; } -public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { if (flowContext.isNullcheckedFieldAccess(this)) { return true; // enough seen } - return checkNullableFieldDereference(scope, this.binding, this.nameSourcePosition); + return checkNullableFieldDereference(scope, this.binding, this.nameSourcePosition, flowContext, ttlForFieldCheck); } /** diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java index 47db93b27..c92668eda 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ForeachStatement.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2014 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -106,7 +106,7 @@ public class ForeachStatement extends Statement { int initialComplaintLevel = (flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0 ? Statement.COMPLAINED_FAKE_REACHABLE : Statement.NOT_COMPLAINED; // process the element variable and collection - this.collection.checkNPE(currentScope, flowContext, flowInfo); + this.collection.checkNPE(currentScope, flowContext, flowInfo, 1); flowInfo = this.elementVariable.analyseCode(this.scope, flowContext, flowInfo); FlowInfo condInfo = this.collection.analyseCode(this.scope, flowContext, flowInfo.copy()); LocalVariableBinding elementVarBinding = this.elementVariable.binding; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FunctionalExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FunctionalExpression.java index fa1201380..a765f78d1 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FunctionalExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FunctionalExpression.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013, 2014 IBM Corporation and others. + * Copyright (c) 2013, 2015 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 @@ -167,6 +167,10 @@ public abstract class FunctionalExpression extends Expression { } public TypeBinding resolveType(BlockScope blockScope) { + return resolveType(blockScope, false); + } + + public TypeBinding resolveType(BlockScope blockScope, boolean skipKosherCheck) { this.constant = Constant.NotAConstant; this.enclosingScope = blockScope; MethodBinding sam = this.expectedType == null ? null : this.expectedType.getSingleAbstractMethod(blockScope, argumentsTypeElided()); @@ -179,7 +183,7 @@ public abstract class FunctionalExpression extends Expression { } this.descriptor = sam; - if (kosherDescriptor(blockScope, sam, true)) { + if (skipKosherCheck || kosherDescriptor(blockScope, sam, true)) { if (blockScope.environment().globalOptions.isAnnotationBasedNullAnalysisEnabled) NullAnnotationMatching.checkForContradictions(sam, this, blockScope); return this.resolvedType = this.expectedType; @@ -268,7 +272,7 @@ public abstract class FunctionalExpression extends Expression { status = false; if (!inspector.visible(sam.thrownExceptions)) status = false; - if (!inspector.visible(sam.declaringClass)) + if (!inspector.visible(this.expectedType)) status = false; return status; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Invocation.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Invocation.java index f77cd139a..2f30eea26 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Invocation.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Invocation.java @@ -42,6 +42,8 @@ public interface Invocation extends InvocationSite { */ InferenceContext18 getInferenceContext(ParameterizedMethodBinding method); + void cleanUpInferenceContexts(); + /** Record result against target type */ void registerResult(TypeBinding targetType, MethodBinding method); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java index 12cf94c5f..b1242e0cd 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LambdaExpression.java @@ -233,7 +233,7 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre * @see org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding.resolveTypesFor(MethodBinding) * @see org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.resolve(ClassScope) */ - public TypeBinding resolveType(BlockScope blockScope) { + public TypeBinding resolveType(BlockScope blockScope, boolean skipKosherCheck) { boolean argumentsTypeElided = argumentsTypeElided(); int argumentsLength = this.arguments == null ? 0 : this.arguments.length; @@ -257,11 +257,11 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre this.scope = new MethodScope(blockScope, this, methodScope.isStatic, methodScope.lastVisibleFieldID); this.scope.isConstructorCall = methodScope.isConstructorCall; - super.resolveType(blockScope); // compute & capture interface function descriptor. + super.resolveType(blockScope, skipKosherCheck); // compute & capture interface function descriptor. final boolean haveDescriptor = this.descriptor != null; - if (!haveDescriptor || this.descriptor.typeVariables != Binding.NO_TYPE_VARIABLES) // already complained in kosher* + if (!skipKosherCheck && (!haveDescriptor || this.descriptor.typeVariables != Binding.NO_TYPE_VARIABLES)) // already complained in kosher* return this.resolvedType = null; this.binding = new MethodBinding(ClassFileConstants.AccPrivate | ClassFileConstants.AccSynthetic | ExtraCompilerModifiers.AccUnresolved, @@ -465,10 +465,14 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre if (targetType instanceof ReferenceBinding && targetType.isValidBinding()) { ParameterizedTypeBinding withWildCards = InferenceContext18.parameterizedWithWildcard(targetType); if (withWildCards != null) { - if (!argumentTypesElided) - return new InferenceContext18(blockScope).inferFunctionalInterfaceParameterization(this, blockScope, withWildCards); - else + if (!argumentTypesElided) { + InferenceContext18 freshInferenceContext = new InferenceContext18(blockScope); + ReferenceBinding inferredType = freshInferenceContext.inferFunctionalInterfaceParameterization(this, blockScope, withWildCards); + freshInferenceContext.cleanUp(); + return inferredType; + } else { return findGroundTargetTypeForElidedLambda(blockScope, withWildCards); + } } return (ReferenceBinding) targetType; } @@ -577,13 +581,8 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre AnnotationBinding [] annotations = descParameters[i].getTypeAnnotations(); for (int j = 0, length = annotations.length; j < length; j++) { AnnotationBinding annotation = annotations[j]; - if (annotation != null) { - switch (annotation.getAnnotationType().id) { - case TypeIds.T_ConfiguredAnnotationNullable : - case TypeIds.T_ConfiguredAnnotationNonNull : - ourParameters[i] = env.createAnnotatedType(ourParameters[i], new AnnotationBinding [] { annotation }); - break; - } + if (annotation != null && annotation.getAnnotationType().hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { + ourParameters[i] = env.createAnnotatedType(ourParameters[i], new AnnotationBinding [] { annotation }); } } } @@ -609,7 +608,30 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre } } - public boolean isPertinentToApplicability(TypeBinding targetType, MethodBinding method) { + public boolean isPertinentToApplicability(final TypeBinding targetType, final MethodBinding method) { + + class NotPertientToApplicability extends RuntimeException { + private static final long serialVersionUID = 1L; + } + class ResultsAnalyser extends ASTVisitor { + public boolean visit(TypeDeclaration type, BlockScope skope) { + return false; + } + public boolean visit(TypeDeclaration type, ClassScope skope) { + return false; + } + public boolean visit(LambdaExpression type, BlockScope skope) { + return false; + } + public boolean visit(ReturnStatement returnStatement, BlockScope skope) { + if (returnStatement.expression != null) { + if (!returnStatement.expression.isPertinentToApplicability(targetType, method)) + throw new NotPertientToApplicability(); + } + return false; + } + } + if (targetType == null) // assumed to signal another primary error return true; @@ -624,9 +646,18 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre return false; } else { Expression [] returnExpressions = this.resultExpressions; - for (int i = 0, length = returnExpressions.length; i < length; i++) { - if (!returnExpressions[i].isPertinentToApplicability(targetType, method)) + if (returnExpressions != NO_EXPRESSIONS) { + for (int i = 0, length = returnExpressions.length; i < length; i++) { + if (!returnExpressions[i].isPertinentToApplicability(targetType, method)) + return false; + } + } else { + // return expressions not yet discovered by resolveType(), so traverse no looking just for one that's not pertinent + try { + this.body.traverse(new ResultsAnalyser(), this.scope); + } catch (NotPertientToApplicability npta) { return false; + } } } @@ -806,6 +837,10 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre if (!isPertinentToApplicability(targetType, null)) return true; + // catch up on one check deferred via skipKosherCheck=true (only if pertinent for applicability) + if (!kosherDescriptor(this.enclosingScope, sam, false)) + return false; + Expression [] returnExpressions = copy.resultExpressions; for (int i = 0, length = returnExpressions.length; i < length; i++) { if (this.enclosingScope.parameterCompatibilityLevel(returnExpressions[i].resolvedType, sam.returnType) == Scope.NOT_COMPATIBLE) { @@ -855,7 +890,7 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre copy.setExpressionContext(this.expressionContext); copy.setExpectedType(targetType); copy.inferenceContext = context; - TypeBinding type = copy.resolveType(this.enclosingScope); + TypeBinding type = copy.resolveType(this.enclosingScope, true); if (type == null || !type.isValidBinding()) return null; @@ -1035,8 +1070,12 @@ public class LambdaExpression extends FunctionalExpression implements IPolyExpre switch(parent.kind) { case Scope.CLASS_SCOPE: case Scope.METHOD_SCOPE: - parent.referenceContext().tagAsHavingErrors(); - return; + ReferenceContext parentAST = parent.referenceContext(); + if (parentAST != this) { + parentAST.tagAsHavingErrors(); + return; + } + break; default: parent = parent.parent; break; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java index 08bfabc69..ef581a557 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java @@ -250,7 +250,8 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl } if (nonStatic) { - this.receiver.checkNPE(currentScope, flowContext, flowInfo); + int timeToLive = ((this.bits & ASTNode.InsideExpressionStatement) != 0) ? 3 : 2; + this.receiver.checkNPE(currentScope, flowContext, flowInfo, timeToLive); } if (this.arguments != null) { @@ -497,7 +498,7 @@ private FlowInfo analyseNullAssertion(BlockScope currentScope, Expression argume return flowInfo; } -public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { // message send as a receiver if ((nullStatus(flowInfo, flowContext) & FlowInfo.POTENTIALLY_NULL) != 0) // note that flowInfo is not used inside nullStatus(..) scope.problemReporter().messageSendPotentialNullReference(this.binding, this); @@ -557,6 +558,7 @@ public void computeConversion(Scope scope, TypeBinding runtimeTimeType, TypeBind * @param valueRequired boolean */ public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { + cleanUpInferenceContexts(); int pc = codeStream.position; // generate receiver/enclosing instance access MethodBinding codegenBinding = this.binding instanceof PolymorphicMethodBinding ? this.binding : this.binding.original(); @@ -1181,7 +1183,7 @@ public TypeBinding resolveType(BlockScope scope) { if (this.binding instanceof ParameterizedGenericMethodBinding && this.typeArguments != null) { TypeVariableBinding[] typeVariables = this.binding.original().typeVariables(); for (int i = 0; i < this.typeArguments.length; i++) - this.typeArguments[i].checkNullConstraints(scope, typeVariables, i); + this.typeArguments[i].checkNullConstraints(scope, (ParameterizedGenericMethodBinding) this.binding, typeVariables, i); } } } @@ -1675,6 +1677,15 @@ public InferenceContext18 getInferenceContext(ParameterizedMethodBinding method) return null; return (InferenceContext18) this.inferenceContexts.get(method); } +@Override +public void cleanUpInferenceContexts() { + if (this.inferenceContexts == null) + return; + for (Object value : this.inferenceContexts.valueTable) + if (value != null) + ((InferenceContext18) value).cleanUp(); + this.inferenceContexts = null; +} public Expression[] arguments() { return this.arguments; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java index 8f590f7a9..1d5bdd85c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/NullAnnotationMatching.java @@ -27,6 +27,7 @@ import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; import org.eclipse.jdt.internal.compiler.lookup.RawTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.Scope; +import org.eclipse.jdt.internal.compiler.lookup.Substitution; import org.eclipse.jdt.internal.compiler.lookup.TagBits; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBindingVisitor; @@ -50,10 +51,26 @@ public class NullAnnotationMatching { public enum CheckMode { /** in this mode we check normal assignment compatibility. */ COMPATIBLE, - /** in this mode we do not tolerate incompatibly missing annotations on type parameters (for overriding analysis) */ - OVERRIDE, + /** in this mode we check similar to isTypeArgumentContained. */ + EXACT, /** in this mode we check compatibility of a type argument against the corresponding type parameter. */ - BOUND_CHECK + BOUND_CHECK, + /** allow covariant return types, but no other deviations. */ + OVERRIDE_RETURN { + @Override CheckMode toDetail() { + return OVERRIDE; + } + }, + /** in this mode we do not tolerate incompatibly missing annotations on type parameters (for overriding analysis) */ + OVERRIDE { + @Override CheckMode toDetail() { + return OVERRIDE; + } + }; + + CheckMode toDetail() { + return CheckMode.EXACT; + } } /** 0 = OK, 1 = unchecked, 2 = definite mismatch */ @@ -126,7 +143,7 @@ public class NullAnnotationMatching { * @return a status object representing the severity of mismatching plus optionally a supertype hint */ public static NullAnnotationMatching analyse(TypeBinding requiredType, TypeBinding providedType, int nullStatus) { - return analyse(requiredType, providedType, null, nullStatus, CheckMode.COMPATIBLE); + return analyse(requiredType, providedType, null, null, nullStatus, CheckMode.COMPATIBLE); } /** * Find any mismatches between the two given types, which are caused by null type annotations. @@ -134,11 +151,12 @@ public class NullAnnotationMatching { * @param providedType * @param providedSubstitute in inheritance situations this maps the providedType into the realm of the subclass, needed for TVB identity checks. * Pass null if not interested in these added checks. + * @param substitution TODO * @param nullStatus we are only interested in NULL or NON_NULL, -1 indicates that we are in a recursion, where flow info is ignored * @param mode controls the kind of check performed (see {@link CheckMode}). * @return a status object representing the severity of mismatching plus optionally a supertype hint */ - public static NullAnnotationMatching analyse(TypeBinding requiredType, TypeBinding providedType, TypeBinding providedSubstitute, int nullStatus, CheckMode mode) { + public static NullAnnotationMatching analyse(TypeBinding requiredType, TypeBinding providedType, TypeBinding providedSubstitute, Substitution substitution, int nullStatus, CheckMode mode) { if (!requiredType.enterRecursiveFunction()) return NullAnnotationMatching.NULL_ANNOTATIONS_OK; try { @@ -150,23 +168,32 @@ public class NullAnnotationMatching { return NullAnnotationMatching.NULL_ANNOTATIONS_OK_NONNULL; return okStatus; } + if (requiredType instanceof TypeVariableBinding && substitution != null && (mode == CheckMode.EXACT || mode == CheckMode.COMPATIBLE)) { + requiredType.exitRecursiveFunction(); + requiredType = Scope.substitute(substitution, requiredType); + if (!requiredType.enterRecursiveFunction()) + return NullAnnotationMatching.NULL_ANNOTATIONS_OK; + } if (mode == CheckMode.BOUND_CHECK && requiredType instanceof TypeVariableBinding) { - // during bound check against a type variable check the provided type against all upper bounds: - TypeBinding superClass = requiredType.superclass(); - if (superClass != null && superClass.hasNullTypeAnnotations()) { - NullAnnotationMatching status = analyse(superClass, providedType, null, nullStatus, mode); - severity = Math.max(severity, status.severity); - if (severity == 2) - return new NullAnnotationMatching(severity, nullStatus, superTypeHint); - } - TypeBinding[] superInterfaces = requiredType.superInterfaces(); - if (superInterfaces != null) { - for (int i = 0; i < superInterfaces.length; i++) { - if (superInterfaces[i].hasNullTypeAnnotations()) { - NullAnnotationMatching status = analyse(superInterfaces[i], providedType, null, nullStatus, mode); - severity = Math.max(severity, status.severity); - if (severity == 2) - return new NullAnnotationMatching(severity, nullStatus, superTypeHint); + boolean passedBoundCheck = (substitution instanceof ParameterizedTypeBinding) && (((ParameterizedTypeBinding) substitution).tagBits & TagBits.PassedBoundCheck) != 0; + if (!passedBoundCheck) { + // during bound check against a type variable check the provided type against all upper bounds: + TypeBinding superClass = requiredType.superclass(); + if (superClass != null && (superClass.hasNullTypeAnnotations() || substitution != null)) { // annotations may enter when substituting a nested type variable + NullAnnotationMatching status = analyse(superClass, providedType, null, substitution, nullStatus, CheckMode.COMPATIBLE); + severity = Math.max(severity, status.severity); + if (severity == 2) + return new NullAnnotationMatching(severity, nullStatus, superTypeHint); + } + TypeBinding[] superInterfaces = requiredType.superInterfaces(); + if (superInterfaces != null) { + for (int i = 0; i < superInterfaces.length; i++) { + if (superInterfaces[i].hasNullTypeAnnotations() || substitution != null) { // annotations may enter when substituting a nested type variable + NullAnnotationMatching status = analyse(superInterfaces[i], providedType, null, substitution, nullStatus, CheckMode.COMPATIBLE); + severity = Math.max(severity, status.severity); + if (severity == 2) + return new NullAnnotationMatching(severity, nullStatus, superTypeHint); + } } } } @@ -185,7 +212,7 @@ public class NullAnnotationMatching { long providedBits = validNullTagBits(providedDimsTagBits[i]); if (i > 0) currentNullStatus = -1; // don't use beyond the outermost dimension - severity = Math.max(severity, computeNullProblemSeverity(requiredBits, providedBits, currentNullStatus, mode == CheckMode.OVERRIDE && nullStatus == -1)); + severity = Math.max(severity, computeNullProblemSeverity(requiredBits, providedBits, currentNullStatus, i == 0 ? mode : mode.toDetail(), false)); if (severity == 2) return NullAnnotationMatching.NULL_ANNOTATIONS_MISMATCH; if (severity == 0) @@ -202,7 +229,7 @@ public class NullAnnotationMatching { || nullStatus == -1) // only at detail/recursion even nullable must be matched exactly { long providedBits = providedNullTagBits(providedType); - int s = computeNullProblemSeverity(requiredBits, providedBits, nullStatus, mode == CheckMode.OVERRIDE && nullStatus == -1); + int s = computeNullProblemSeverity(requiredBits, providedBits, nullStatus, mode, requiredType.isTypeVariable()); severity = Math.max(severity, s); if (severity == 0 && (providedBits & TagBits.AnnotationNonNull) != 0) okStatus = NullAnnotationMatching.NULL_ANNOTATIONS_OK_NONNULL; @@ -219,7 +246,7 @@ public class NullAnnotationMatching { if (requiredArguments != null && providedArguments != null && requiredArguments.length == providedArguments.length) { for (int i = 0; i < requiredArguments.length; i++) { TypeBinding providedArgSubstitute = providedSubstitutes != null ? providedSubstitutes[i] : null; - NullAnnotationMatching status = analyse(requiredArguments[i], providedArguments[i], providedArgSubstitute, -1, mode); + NullAnnotationMatching status = analyse(requiredArguments[i], providedArguments[i], providedArgSubstitute, substitution, -1, mode.toDetail()); severity = Math.max(severity, status.severity); if (severity == 2) return new NullAnnotationMatching(severity, nullStatus, superTypeHint); @@ -230,7 +257,7 @@ public class NullAnnotationMatching { TypeBinding providedEnclosing = providedType.enclosingType(); if (requiredEnclosing != null && providedEnclosing != null) { TypeBinding providedEnclSubstitute = providedSubstitute != null ? providedSubstitute.enclosingType() : null; - NullAnnotationMatching status = analyse(requiredEnclosing, providedEnclosing, providedEnclSubstitute, -1, mode); + NullAnnotationMatching status = analyse(requiredEnclosing, providedEnclosing, providedEnclSubstitute, substitution, -1, mode); severity = Math.max(severity, status.severity); } } @@ -307,8 +334,13 @@ public class NullAnnotationMatching { return TagBits.AnnotationNullable; // type cannot require @NonNull } } - if (mode != CheckMode.BOUND_CHECK) // no pessimistic checks during boundcheck (we *have* the instantiation) - return TagBits.AnnotationNonNull; // instantiation could require @NonNull + switch (mode) { + case BOUND_CHECK: // no pessimistic checks during boundcheck (we *have* the instantiation) + case OVERRIDE_RETURN: // allow covariance + break; + default: + return TagBits.AnnotationNonNull; // instantiation could require @NonNull + } } return 0; @@ -398,27 +430,60 @@ public class NullAnnotationMatching { * @param requiredBits null tagBits of the required type * @param providedBits null tagBits of the provided type * @param nullStatus -1 means: don't use, other values see constants in FlowInfo - * @param overrideDetailChecking true enables strictest mode during override analysis when checking type details (type argument, array content) + * @param mode check mode (see {@link CheckMode}) + * @param requiredIsTypeVariable is the required type a type variable (possibly: "free type variable")? * @return see {@link #severity} for interpretation of values */ - private static int computeNullProblemSeverity(long requiredBits, long providedBits, int nullStatus, boolean overrideDetailChecking) { - // nullStatus: - // overrideDetailChecking: - if ((requiredBits != 0 || overrideDetailChecking) && requiredBits != providedBits) { - if (requiredBits == TagBits.AnnotationNonNull && nullStatus == FlowInfo.NON_NULL) { - return 0; // OK by flow analysis + private static int computeNullProblemSeverity(long requiredBits, long providedBits, int nullStatus, CheckMode mode, boolean requiredIsTypeVariable) { + if (requiredBits == providedBits) + return 0; + if (requiredBits == 0) { + switch (mode) { + case COMPATIBLE: + case BOUND_CHECK: + case EXACT: + return 0; + case OVERRIDE_RETURN: + if (providedBits == TagBits.AnnotationNonNull) + return 0; // covariant redefinition to nonnull is good + if (!requiredIsTypeVariable) + return 0; // refining an unconstrained non-TVB return to nullable is also legal + return 1; + case OVERRIDE: + return 1; // warn about dropped annotation } - if (requiredBits == TagBits.AnnotationNullMASK) - return 0; // OK since LHS accepts either - if (nullStatus != -1 && !overrideDetailChecking && requiredBits == TagBits.AnnotationNullable) - return 0; // when using flow info, everything is compatible to nullable - if (providedBits != 0) { - return 2; // mismatching annotations - } else { - return 1; // need unchecked conversion regarding type detail + } else if (requiredBits == TagBits.AnnotationNullMASK) { + return 0; // OK since LHS accepts either + } else if (requiredBits == TagBits.AnnotationNonNull) { + switch (mode) { + case COMPATIBLE: + if (nullStatus == FlowInfo.NON_NULL) + return 0; // OK by flow analysis + //$FALL-THROUGH$ + case BOUND_CHECK: + case EXACT: + case OVERRIDE_RETURN: + case OVERRIDE: + if (providedBits == 0) + return 1; + return 2; + } + + } else if (requiredBits == TagBits.AnnotationNullable) { + switch (mode) { + case COMPATIBLE: + case OVERRIDE_RETURN: + return 0; // in these modes everything is compatible to nullable + case BOUND_CHECK: + case EXACT: + if (providedBits == 0) + return 1; + return 2; + case OVERRIDE: + return 2; } } - return 0; // OK by tagBits + return 0; // shouldn't get here, requiredBits should be one of the listed cases } static class SearchContradictions extends TypeBindingVisitor { @@ -565,4 +630,17 @@ public class NullAnnotationMatching { } return mainType; } + + @SuppressWarnings("nls") + @Override + public String toString() { + if (this == NULL_ANNOTATIONS_OK) return "OK"; + if (this == NULL_ANNOTATIONS_MISMATCH) return "MISMATCH"; + if (this == NULL_ANNOTATIONS_OK_NONNULL) return "OK NonNull"; + if (this == NULL_ANNOTATIONS_UNCHECKED) return "UNCHECKED"; + StringBuilder buf = new StringBuilder(); + buf.append("Analysis result: severity="+this.severity); + buf.append(" nullStatus="+this.nullStatus); + return buf.toString(); + } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ParameterizedQualifiedTypeReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ParameterizedQualifiedTypeReference.java index 241dc3dd5..c641fcca2 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ParameterizedQualifiedTypeReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ParameterizedQualifiedTypeReference.java @@ -103,7 +103,6 @@ public class ParameterizedQualifiedTypeReference extends ArrayQualifiedTypeRefer parameterizedType.boundCheck(scope, this.typeArguments[index]); } } - checkNullConstraints(scope, this.typeArguments[index]); } public TypeReference augmentTypeWithAdditionalDimensions(int additionalDimensions, Annotation[][] additionalAnnotations, boolean isVarargs) { int totalDimensions = this.dimensions() + additionalDimensions; @@ -216,9 +215,9 @@ public class ParameterizedQualifiedTypeReference extends ArrayQualifiedTypeRefer TypeBinding type = internalResolveLeafType(scope, checkBounds); createArrayType(scope); resolveAnnotations(scope, location); - if (this.typeArguments != null && checkBounds) + if (this.typeArguments != null) // relevant null annotations are on the inner most type: - checkNullConstraints(scope, this.typeArguments[this.typeArguments.length-1]); + checkIllegalNullAnnotations(scope, this.typeArguments[this.typeArguments.length-1]); return type == null ? type : this.resolvedType; } private TypeBinding internalResolveLeafType(Scope scope, boolean checkBounds) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ParameterizedSingleTypeReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ParameterizedSingleTypeReference.java index 4540e62f5..293f83191 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ParameterizedSingleTypeReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ParameterizedSingleTypeReference.java @@ -92,14 +92,11 @@ public class ParameterizedSingleTypeReference extends ArrayTypeReference { if (this.resolvedType.leafComponentType() instanceof ParameterizedTypeBinding) { ParameterizedTypeBinding parameterizedType = (ParameterizedTypeBinding) this.resolvedType.leafComponentType(); - ReferenceBinding currentType = parameterizedType.genericType(); - TypeVariableBinding[] typeVariables = currentType.typeVariables(); TypeBinding[] argTypes = parameterizedType.arguments; - if (argTypes != null && typeVariables != null) { // may be null in error cases + if (argTypes != null) { // may be null in error cases parameterizedType.boundCheck(scope, this.typeArguments); } } - checkNullConstraints(scope, this.typeArguments); } public TypeReference augmentTypeWithAdditionalDimensions(int additionalDimensions, Annotation [][] additionalAnnotations, boolean isVarargs) { @@ -268,8 +265,6 @@ public class ParameterizedSingleTypeReference extends ArrayTypeReference { // note: handling of arrays differs for role and regular types if (len == 0) { resolveAnnotations(scope, location); - if (checkBounds) - checkNullConstraints(scope, this.typeArguments); return this.resolvedType; // we're done } @@ -302,21 +297,15 @@ public class ParameterizedSingleTypeReference extends ArrayTypeReference { if (type == null) { this.resolvedType = createArrayType(scope, this.resolvedType); resolveAnnotations(scope, 0); // no defaultNullness for buggy type - if (checkBounds) - checkNullConstraints(scope, this.typeArguments); return null; // (1) no useful type, but still captured dimensions into this.resolvedType } else { type = createArrayType(scope, type); if (!this.resolvedType.isValidBinding() && this.resolvedType.dimensions() == type.dimensions()) { resolveAnnotations(scope, 0); // no defaultNullness for buggy type - if (checkBounds) - checkNullConstraints(scope, this.typeArguments); return type; // (2) found some error, but could recover useful type (like closestMatch) } else { this.resolvedType = type; // (3) no complaint, keep fully resolved type (incl. dimensions) resolveAnnotations(scope, location); - if (checkBounds) - checkNullConstraints(scope, this.typeArguments); return this.resolvedType; // pick up any annotated type. } } @@ -458,6 +447,7 @@ public class ParameterizedSingleTypeReference extends ArrayTypeReference { return null; } } + //{ObjectTeams: already done? if (!isDiamond && argLength == 0) return this.resolvedType; @@ -472,7 +462,7 @@ public class ParameterizedSingleTypeReference extends ArrayTypeReference { } ParameterizedTypeBinding parameterizedType = scope.environment().createParameterizedType(currentOriginal, argTypes, anchor, valParPos, enclosingType, currentAnnotations); /* orig: - ParameterizedTypeBinding parameterizedType = scope.environment().createParameterizedType(currentOriginal, argTypes, enclosingType); + ParameterizedTypeBinding parameterizedType = scope.environment().createParameterizedType(currentOriginal, argTypes, enclosingType); :giro */ // SH} // check argument type compatibility for non <> cases - <> case needs no bounds check, we will scream foul if needed during inference. @@ -487,6 +477,8 @@ public class ParameterizedSingleTypeReference extends ArrayTypeReference { if (isTypeUseDeprecated(parameterizedType, scope)) reportDeprecatedType(parameterizedType, scope); + checkIllegalNullAnnotations(scope, this.typeArguments); + if (!this.resolvedType.isValidBinding()) { return parameterizedType; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java index 7bd040497..47c44101b 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java @@ -240,6 +240,7 @@ public static abstract class AbstractQualifiedAllocationExpression extends Alloc } public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { + cleanUpInferenceContexts(); if (!valueRequired) currentScope.problemReporter().unusedObjectAllocation(this); int pc = codeStream.position; @@ -372,7 +373,7 @@ public static abstract class AbstractQualifiedAllocationExpression extends Alloc if (this.binding instanceof ParameterizedGenericMethodBinding && this.typeArguments != null) { TypeVariableBinding[] typeVariables = this.binding.original().typeVariables(); for (int i = 0; i < this.typeArguments.length; i++) - this.typeArguments[i].checkNullConstraints(scope, typeVariables, i); + this.typeArguments[i].checkNullConstraints(scope, (ParameterizedGenericMethodBinding) this.binding, typeVariables, i); } } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java index 78d7d9d30..d06692fff 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedNameReference.java @@ -292,18 +292,18 @@ private void checkInternalNPE(BlockScope scope, FlowContext flowContext, FlowInf if (this.otherBindings != null) { if ((this.bits & ASTNode.RestrictiveFlagMASK) == Binding.FIELD) { // is the first field dereferenced annotated Nullable? If so, report immediately - checkNullableFieldDereference(scope, (FieldBinding) this.binding, this.sourcePositions[this.indexOfFirstFieldBinding-1]); + checkNullableFieldDereference(scope, (FieldBinding) this.binding, this.sourcePositions[this.indexOfFirstFieldBinding-1], flowContext, 0); } // look for annotated fields, they do not depend on flow context -> check immediately: int length = this.otherBindings.length - 1; // don't check the last binding for (int i = 0; i < length; i++) { - checkNullableFieldDereference(scope, this.otherBindings[i], this.sourcePositions[this.indexOfFirstFieldBinding+i]); + checkNullableFieldDereference(scope, this.otherBindings[i], this.sourcePositions[this.indexOfFirstFieldBinding+i], flowContext, 0); } } } -public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { - if (super.checkNPE(scope, flowContext, flowInfo)) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { + if (super.checkNPE(scope, flowContext, flowInfo, ttlForFieldCheck)) { return true; } FieldBinding fieldBinding = null; @@ -318,7 +318,7 @@ public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flow position = this.sourcePositions[this.sourcePositions.length - 1]; } if (fieldBinding != null) { - return checkNullableFieldDereference(scope, fieldBinding, position); + return checkNullableFieldDereference(scope, fieldBinding, position, flowContext, ttlForFieldCheck); } return false; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java index 281de66ff..a8cff6cdf 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Reference.java @@ -68,22 +68,26 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl return flowInfo; } -public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { if (flowContext.isNullcheckedFieldAccess(this)) { return true; // enough seen } - return super.checkNPE(scope, flowContext, flowInfo); + return super.checkNPE(scope, flowContext, flowInfo, ttlForFieldCheck); } -protected boolean checkNullableFieldDereference(Scope scope, FieldBinding field, long sourcePosition) { - // preference to type annotations if we have any - if ((field.type.tagBits & TagBits.AnnotationNullable) != 0) { - scope.problemReporter().dereferencingNullableExpression(sourcePosition, scope.environment()); - return true; - } - if ((field.tagBits & TagBits.AnnotationNullable) != 0) { - scope.problemReporter().nullableFieldDereference(field, sourcePosition); - return true; +protected boolean checkNullableFieldDereference(Scope scope, FieldBinding field, long sourcePosition, FlowContext flowContext, int ttlForFieldCheck) { + if (field != null) { + if (ttlForFieldCheck > 0 && scope.compilerOptions().enableSyntacticNullAnalysisForFields) + flowContext.recordNullCheckedFieldReference(this, ttlForFieldCheck); + // preference to type annotations if we have any + if ((field.type.tagBits & TagBits.AnnotationNullable) != 0) { + scope.problemReporter().dereferencingNullableExpression(sourcePosition, scope.environment()); + return true; + } + if ((field.tagBits & TagBits.AnnotationNullable) != 0) { + scope.problemReporter().nullableFieldDereference(field, sourcePosition); + return true; + } } return false; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java index 1950dde5f..9f7216e75 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SingleNameReference.java @@ -281,14 +281,12 @@ public TypeBinding checkFieldAccess(BlockScope scope) { } -public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { - if (!super.checkNPE(scope, flowContext, flowInfo)) { +public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { + if (!super.checkNPE(scope, flowContext, flowInfo, ttlForFieldCheck)) { CompilerOptions compilerOptions = scope.compilerOptions(); if (compilerOptions.isAnnotationBasedNullAnalysisEnabled) { - VariableBinding var = nullAnnotatedVariableBinding(compilerOptions.sourceLevel >= ClassFileConstants.JDK1_8); - if (var instanceof FieldBinding) { - checkNullableFieldDereference(scope, (FieldBinding) var, ((long)this.sourceStart<<32)+this.sourceEnd); - return true; + if (this.binding instanceof FieldBinding) { + return checkNullableFieldDereference(scope, (FieldBinding) this.binding, ((long)this.sourceStart<<32)+this.sourceEnd, flowContext, ttlForFieldCheck); } } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java index 9b7120fb6..12d85d563 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java @@ -200,6 +200,11 @@ void internalAnalyseOneArgument18(BlockScope currentScope, FlowContext flowConte // immediate reporting: currentScope.problemReporter().nullityMismatchingTypeAnnotation(argument, argument.resolvedType, expectedType, annotationStatus); } else if (annotationStatus.isAnyMismatch() || (statusFromAnnotatedNull & FlowInfo.POTENTIALLY_NULL) != 0) { + if (!expectedType.hasNullTypeAnnotations() && expectedNonNullness == Boolean.TRUE) { + // improve problem rendering when using a declaration annotation in a 1.8 setting + LookupEnvironment env = currentScope.environment(); + expectedType = env.createAnnotatedType(expectedType, new AnnotationBinding[] {env.getNonNullAnnotation()}); + } flowContext.recordNullityMismatch(currentScope, argument, argument.resolvedType, expectedType, flowInfo, nullStatus, annotationStatus); } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java index 5f6e1110d..bd51350fd 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2014 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -79,7 +79,7 @@ public class SwitchStatement extends Statement { if ((this.expression.implicitConversion & TypeIds.UNBOXING) != 0 || (this.expression.resolvedType != null && (this.expression.resolvedType.id == T_JavaLangString || this.expression.resolvedType.isEnum()))) { - this.expression.checkNPE(currentScope, flowContext, flowInfo); + this.expression.checkNPE(currentScope, flowContext, flowInfo, 1); } SwitchFlowContext switchContext = new SwitchFlowContext(flowContext, this, (this.breakLabel = new BranchLabel()), true); diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SynchronizedStatement.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SynchronizedStatement.java index cd2c2a408..61d86d63d 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SynchronizedStatement.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/SynchronizedStatement.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2011 IBM Corporation and others. + * Copyright (c) 2000, 2011, 2015 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 @@ -7,6 +7,7 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Carmi Grushko - Bug 465048 - Binding is null for class literals in synchronized blocks *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; @@ -170,31 +171,31 @@ public void resolve(BlockScope upperScope) { // special scope for secret locals optimization. this.scope = new BlockScope(upperScope); TypeBinding type = this.expression.resolveType(this.scope); - if (type == null) - return; - switch (type.id) { - case T_boolean : - case T_char : - case T_float : - case T_double : - case T_byte : - case T_short : - case T_int : - case T_long : - this.scope.problemReporter().invalidTypeToSynchronize(this.expression, type); - break; - case T_void : - this.scope.problemReporter().illegalVoidExpression(this.expression); - break; - case T_null : - this.scope.problemReporter().invalidNullToSynchronize(this.expression); - break; + if (type != null) { + switch (type.id) { + case T_boolean : + case T_char : + case T_float : + case T_double : + case T_byte : + case T_short : + case T_int : + case T_long : + this.scope.problemReporter().invalidTypeToSynchronize(this.expression, type); + break; + case T_void : + this.scope.problemReporter().illegalVoidExpression(this.expression); + break; + case T_null : + this.scope.problemReporter().invalidNullToSynchronize(this.expression); + break; + } + //continue even on errors in order to have the TC done into the statements + this.synchroVariable = new LocalVariableBinding(SecretLocalDeclarationName, type, ClassFileConstants.AccDefault, false); + this.scope.addLocalVariable(this.synchroVariable); + this.synchroVariable.setConstant(Constant.NotAConstant); // not inlinable + this.expression.computeConversion(this.scope, type, type); } - //continue even on errors in order to have the TC done into the statements - this.synchroVariable = new LocalVariableBinding(SecretLocalDeclarationName, type, ClassFileConstants.AccDefault, false); - this.scope.addLocalVariable(this.synchroVariable); - this.synchroVariable.setConstant(Constant.NotAConstant); // not inlinable - this.expression.computeConversion(this.scope, type, type); this.block.resolveUsing(this.scope); } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThisReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThisReference.java index b9e751876..76c802117 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThisReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThisReference.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2014 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -78,7 +78,7 @@ public class ThisReference extends Reference { return true; } - public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo) { + public boolean checkNPE(BlockScope scope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { return true; // never problematic } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeReference.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeReference.java index 0822a281a..0101b0d5c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeReference.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TypeReference.java @@ -842,42 +842,39 @@ protected void resolveAnnotations(Scope scope, int location) { public int getAnnotatableLevels() { return 1; } -/** Check all typeArguments against null constraints on their corresponding type variables. */ -protected void checkNullConstraints(Scope scope, TypeReference[] typeArguments) { - if (scope.environment().usesNullTypeAnnotations() - && typeArguments != null) - { - TypeVariableBinding[] typeVariables = this.resolvedType.original().typeVariables(); +/** Check all typeArguments for illegal null annotations on base types. */ +protected void checkIllegalNullAnnotations(Scope scope, TypeReference[] typeArguments) { + if (scope.environment().usesNullTypeAnnotations() && typeArguments != null) { for (int i = 0; i < typeArguments.length; i++) { TypeReference arg = typeArguments[i]; if (arg.resolvedType != null) - arg.checkNullConstraints(scope, typeVariables, i); + arg.checkIllegalNullAnnotation(scope); } } } /** Check whether this type reference conforms to the null constraints defined for the corresponding type variable. */ -protected void checkNullConstraints(Scope scope, TypeBinding[] variables, int rank) { +protected void checkNullConstraints(Scope scope, Substitution substitution, TypeBinding[] variables, int rank) { if (variables != null && variables.length > rank) { TypeBinding variable = variables[rank]; if (variable.hasNullTypeAnnotations()) { - if (NullAnnotationMatching.analyse(variable, this.resolvedType, null, -1, CheckMode.BOUND_CHECK).isAnyMismatch()) + if (NullAnnotationMatching.analyse(variable, this.resolvedType, null, substitution, -1, CheckMode.BOUND_CHECK).isAnyMismatch()) scope.problemReporter().nullityMismatchTypeArgument(variable, this.resolvedType, this); } } - if (this.resolvedType.leafComponentType().isBaseType() && hasNullTypeAnnotation(AnnotationPosition.LEAF_TYPE)) { - scope.problemReporter().illegalAnnotationForBaseType(this, this.annotations[0], this.resolvedType.tagBits & TagBits.AnnotationNullMASK); - } + checkIllegalNullAnnotation(scope); +} +protected void checkIllegalNullAnnotation(Scope scope) { + if (this.resolvedType.leafComponentType().isBaseType() && hasNullTypeAnnotation(AnnotationPosition.LEAF_TYPE)) + scope.problemReporter().illegalAnnotationForBaseType(this, this.annotations[0], this.resolvedType.tagBits & TagBits.AnnotationNullMASK); } /** Retrieve the null annotation that has been translated to the given nullTagBits. */ public Annotation findAnnotation(long nullTagBits) { if (this.annotations != null) { Annotation[] innerAnnotations = this.annotations[this.annotations.length-1]; if (innerAnnotations != null) { - int annId = nullTagBits == TagBits.AnnotationNonNull ? TypeIds.T_ConfiguredAnnotationNonNull : TypeIds.T_ConfiguredAnnotationNullable; + int annBit = nullTagBits == TagBits.AnnotationNonNull ? TypeIds.BitNonNullAnnotation : TypeIds.BitNullableAnnotation; for (int i = 0; i < innerAnnotations.length; i++) { - if (innerAnnotations[i] != null - && innerAnnotations[i].resolvedType != null - && innerAnnotations[i].resolvedType.id == annId) + if (innerAnnotations[i] != null && innerAnnotations[i].hasNullBit(annBit)) return innerAnnotations[i]; } } @@ -901,10 +898,7 @@ public boolean hasNullTypeAnnotation(AnnotationPosition position) { public static boolean containsNullAnnotation(Annotation[] annotations) { if (annotations != null) { for (int i = 0; i < annotations.length; i++) { - if (annotations[i] != null - && annotations[i].resolvedType != null - && (annotations[i].resolvedType.id == TypeIds.T_ConfiguredAnnotationNonNull - || annotations[i].resolvedType.id == TypeIds.T_ConfiguredAnnotationNullable)) + if (annotations[i] != null && (annotations[i].hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation))) return true; } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java index dc163a571..4828a697c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java @@ -31,6 +31,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants; import org.eclipse.jdt.internal.compiler.codegen.AttributeNamesConstants; import org.eclipse.jdt.internal.compiler.env.*; import org.eclipse.jdt.internal.compiler.impl.Constant; @@ -1149,6 +1150,9 @@ public boolean hasStructuralChanges(byte[] newBytes, boolean orderRequired, bool // annotations if (hasStructuralAnnotationChanges(getAnnotations(), newClassFile.getAnnotations())) return true; + if (this.version >= ClassFileConstants.JDK1_8 + && hasStructuralTypeAnnotationChanges(getTypeAnnotations(), newClassFile.getTypeAnnotations())) + return true; // generic signature if (!CharOperation.equals(getGenericSignature(), newClassFile.getGenericSignature())) @@ -1275,43 +1279,48 @@ private boolean hasStructuralAnnotationChanges(IBinaryAnnotation[] currentAnnota if (currentAnnotationsLength != otherAnnotationsLength) return true; for (int i = 0; i < currentAnnotationsLength; i++) { - if (!CharOperation.equals(currentAnnotations[i].getTypeName(), otherAnnotations[i].getTypeName())) - return true; - IBinaryElementValuePair[] currentPairs = currentAnnotations[i].getElementValuePairs(); - IBinaryElementValuePair[] otherPairs = otherAnnotations[i].getElementValuePairs(); - int currentPairsLength = currentPairs == null ? 0 : currentPairs.length; - int otherPairsLength = otherPairs == null ? 0 : otherPairs.length; - if (currentPairsLength != otherPairsLength) - return true; - for (int j = 0; j < currentPairsLength; j++) { - if (!CharOperation.equals(currentPairs[j].getName(), otherPairs[j].getName())) - return true; - final Object value = currentPairs[j].getValue(); - final Object value2 = otherPairs[j].getValue(); - if (value instanceof Object[]) { - Object[] currentValues = (Object[]) value; - if (value2 instanceof Object[]) { - Object[] currentValues2 = (Object[]) value2; - final int length = currentValues.length; - if (length != currentValues2.length) { - return true; - } - for (int n = 0; n < length; n++) { - if (!currentValues[n].equals(currentValues2[n])) { - return true; - } + Boolean match = matchAnnotations(currentAnnotations[i], otherAnnotations[i]); + if (match != null) + return match.booleanValue(); + } + return false; +} +private Boolean matchAnnotations(IBinaryAnnotation currentAnnotation, IBinaryAnnotation otherAnnotation) { + if (!CharOperation.equals(currentAnnotation.getTypeName(), otherAnnotation.getTypeName())) + return true; + IBinaryElementValuePair[] currentPairs = currentAnnotation.getElementValuePairs(); + IBinaryElementValuePair[] otherPairs = otherAnnotation.getElementValuePairs(); + int currentPairsLength = currentPairs == null ? 0 : currentPairs.length; + int otherPairsLength = otherPairs == null ? 0 : otherPairs.length; + if (currentPairsLength != otherPairsLength) + return Boolean.TRUE; + for (int j = 0; j < currentPairsLength; j++) { + if (!CharOperation.equals(currentPairs[j].getName(), otherPairs[j].getName())) + return Boolean.TRUE; + final Object value = currentPairs[j].getValue(); + final Object value2 = otherPairs[j].getValue(); + if (value instanceof Object[]) { + Object[] currentValues = (Object[]) value; + if (value2 instanceof Object[]) { + Object[] currentValues2 = (Object[]) value2; + final int length = currentValues.length; + if (length != currentValues2.length) { + return Boolean.TRUE; + } + for (int n = 0; n < length; n++) { + if (!currentValues[n].equals(currentValues2[n])) { + return Boolean.TRUE; } - return false; } - return true; - } else if (!value.equals(value2)) { - return true; + return Boolean.FALSE; } + return Boolean.TRUE; + } else if (!value.equals(value2)) { + return Boolean.TRUE; } } - return false; + return null; } - private boolean hasStructuralFieldChanges(FieldInfo currentFieldInfo, FieldInfo otherFieldInfo) { // generic signature if (!CharOperation.equals(currentFieldInfo.getGenericSignature(), otherFieldInfo.getGenericSignature())) @@ -1322,6 +1331,9 @@ private boolean hasStructuralFieldChanges(FieldInfo currentFieldInfo, FieldInfo return true; if (hasStructuralAnnotationChanges(currentFieldInfo.getAnnotations(), otherFieldInfo.getAnnotations())) return true; + if (this.version >= ClassFileConstants.JDK1_8 + && hasStructuralTypeAnnotationChanges(currentFieldInfo.getTypeAnnotations(), otherFieldInfo.getTypeAnnotations())) + return true; if (!CharOperation.equals(currentFieldInfo.getName(), otherFieldInfo.getName())) return true; if (!CharOperation.equals(currentFieldInfo.getTypeName(), otherFieldInfo.getTypeName())) @@ -1378,6 +1390,9 @@ private boolean hasStructuralMethodChanges(MethodInfo currentMethodInfo, MethodI if (hasStructuralAnnotationChanges(currentMethodInfo.getParameterAnnotations(i, this.classFileName), otherMethodInfo.getParameterAnnotations(i, this.classFileName))) return true; } + if (this.version >= ClassFileConstants.JDK1_8 + && hasStructuralTypeAnnotationChanges(currentMethodInfo.getTypeAnnotations(), otherMethodInfo.getTypeAnnotations())) + return true; if (!CharOperation.equals(currentMethodInfo.getSelector(), otherMethodInfo.getSelector())) return true; @@ -1400,6 +1415,45 @@ private boolean hasStructuralMethodChanges(MethodInfo currentMethodInfo, MethodI return false; } +private boolean hasStructuralTypeAnnotationChanges(IBinaryTypeAnnotation[] currentTypeAnnotations, IBinaryTypeAnnotation[] otherTypeAnnotations) { + if (otherTypeAnnotations != null) { + // copy so we can delete matched annotations: + int len = otherTypeAnnotations.length; + System.arraycopy(otherTypeAnnotations, 0, otherTypeAnnotations = new IBinaryTypeAnnotation[len], 0, len); + } + if (currentTypeAnnotations != null) { + loopCurrent: + for (IBinaryTypeAnnotation currentAnnotation : currentTypeAnnotations) { + if (!affectsSignature(currentAnnotation)) continue; + if (otherTypeAnnotations == null) + return true; + for (int i = 0; i < otherTypeAnnotations.length; i++) { + IBinaryTypeAnnotation otherAnnotation = otherTypeAnnotations[i]; + if (otherAnnotation != null && matchAnnotations(currentAnnotation.getAnnotation(), otherAnnotation.getAnnotation()) == Boolean.TRUE) { + otherTypeAnnotations[i] = null; // matched + continue loopCurrent; + } + } + return true; // not matched + } + } + if (otherTypeAnnotations != null) { + for (IBinaryTypeAnnotation otherAnnotation : otherTypeAnnotations) { + if (affectsSignature(otherAnnotation)) + return true; + } + } + return false; +} + +private boolean affectsSignature(IBinaryTypeAnnotation typeAnnotation) { + if (typeAnnotation == null) return false; + int targetType = typeAnnotation.getTargetType(); + if (targetType >= AnnotationTargetTypeConstants.LOCAL_VARIABLE && targetType <= AnnotationTargetTypeConstants.METHOD_REFERENCE_TYPE_ARGUMENT) + return false; // affects detail within a block + return true; +} + /** * This method is used to fully initialize the contents of the receiver. All methodinfos, fields infos * will be therefore fully initialized and we can get rid of the bytes. diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java index bdce24ef5..cbb2bc1ca 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationProvider.java @@ -146,7 +146,7 @@ public class ExternalAnnotationProvider { * Assert that the given line is a class header for 'typeName' (slash-separated qualified name). */ public static void assertClassHeader(String line, String typeName) throws IOException { - if (line.startsWith(CLASS_PREFIX)) { + if (line != null && line.startsWith(CLASS_PREFIX)) { line = line.substring(CLASS_PREFIX.length()); } else { throw new IOException("missing class header in annotation file"); //$NON-NLS-1$ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java index 383f3d1be..eb58e53ca 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/codegen/CodeStream.java @@ -3491,8 +3491,17 @@ public static TypeBinding getConstantPoolDeclaringClass(Scope currentScope, Meth && (options.complianceLevel >= ClassFileConstants.JDK1_4 || !(isImplicitThisReceiver && codegenBinding.isStatic())) && codegenBinding.declaringClass.id != TypeIds.T_JavaLangObject) // no change for Object methods || !codegenBinding.declaringClass.canBeSeenBy(currentScope)) { - if (!actualReceiverType.isIntersectionType18()) // no constant pool representation. FIXME, visibility issue not handled. + if (actualReceiverType.isIntersectionType18()) { + TypeBinding[] intersectingTypes = ((IntersectionTypeBinding18)actualReceiverType).getIntersectingTypes(); + for(int i = 0; i < intersectingTypes.length; i++) { + if (intersectingTypes[i].findSuperTypeOriginatingFrom(constantPoolDeclaringClass) != null) { + constantPoolDeclaringClass = intersectingTypes[i]; + break; + } + } + } else { constantPoolDeclaringClass = actualReceiverType.erasure(); + } } } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java index c6dcc0aa3..6b1a868d9 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java @@ -82,10 +82,10 @@ public class FlowContext implements TypeConstants { public TypeBinding[][] providedExpectedTypes = null; // record field references known to be non-null - // this array will never shrink, only grow. reset happens by nulling the first cell - // adding elements after reset ensures that the valid part of the array is always null-terminated + // this array will never shrink, only grow. reset happens by nulling expired entries + // this array grows in lock step with timesToLiveForNullCheckInfo, which controls expiration private Reference[] nullCheckedFieldReferences = null; - private int timeToLiveForNullCheckInfo = -1; + private int[] timesToLiveForNullCheckInfo = null; public static final int DEFER_NULL_DIAGNOSTIC = 0x1; public static final int PREEMPT_NULL_DIAGNOSTIC = 0x2; @@ -131,6 +131,7 @@ public FlowContext(FlowContext parent, ASTNode associatedNode) { this.initsOnFinally = parent.initsOnFinally; this.conditionalLevel = parent.conditionalLevel; this.nullCheckedFieldReferences = parent.nullCheckedFieldReferences; // re-use list if there is one + this.timesToLiveForNullCheckInfo = parent.timesToLiveForNullCheckInfo; } } @@ -141,33 +142,35 @@ public FlowContext(FlowContext parent, ASTNode associatedNode) { * @param timeToLive control how many expire events are needed to expire this information */ public void recordNullCheckedFieldReference(Reference reference, int timeToLive) { - this.timeToLiveForNullCheckInfo = timeToLive; if (this.nullCheckedFieldReferences == null) { // first entry: - this.nullCheckedFieldReferences = new Reference[2]; - this.nullCheckedFieldReferences[0] = reference; + this.nullCheckedFieldReferences = new Reference[] { reference, null }; + this.timesToLiveForNullCheckInfo = new int[] { timeToLive, -1 }; } else { int len = this.nullCheckedFieldReferences.length; // insert into first empty slot: for (int i=0; i<len; i++) { if (this.nullCheckedFieldReferences[i] == null) { this.nullCheckedFieldReferences[i] = reference; - if (i+1 < len) { - this.nullCheckedFieldReferences[i+1] = null; // lazily mark next as empty - } + this.timesToLiveForNullCheckInfo[i] = timeToLive; return; } } - // grow array: + // grow arrays: System.arraycopy(this.nullCheckedFieldReferences, 0, this.nullCheckedFieldReferences=new Reference[len+2], 0, len); + System.arraycopy(this.timesToLiveForNullCheckInfo, 0, this.timesToLiveForNullCheckInfo=new int[len+2], 0, len); this.nullCheckedFieldReferences[len] = reference; + this.timesToLiveForNullCheckInfo[len] = timeToLive; } } /** If a null checked field has been recorded recently, increase its time to live. */ public void extendTimeToLiveForNullCheckedField(int t) { - if (this.timeToLiveForNullCheckInfo > 0) - this.timeToLiveForNullCheckInfo += t; + if (this.timesToLiveForNullCheckInfo != null) { + for (int i = 0; i < this.timesToLiveForNullCheckInfo.length; i++) + if (this.timesToLiveForNullCheckInfo[i] > 0) + this.timesToLiveForNullCheckInfo[i] += t; + } } /** @@ -178,8 +181,9 @@ public void extendTimeToLiveForNullCheckedField(int t) { */ public void expireNullCheckedFieldInfo() { if (this.nullCheckedFieldReferences != null) { - if (--this.timeToLiveForNullCheckInfo == 0) { - this.nullCheckedFieldReferences[0] = null; // lazily wipe + for (int i = 0; i < this.nullCheckedFieldReferences.length; i++) { + if (--this.timesToLiveForNullCheckInfo[i] == 0) + this.nullCheckedFieldReferences[i] = null; } } } @@ -196,7 +200,7 @@ public boolean isNullcheckedFieldAccess(Reference reference) { for (int i=0; i<len; i++) { Reference checked = this.nullCheckedFieldReferences[i]; if (checked == null) { - return false; + continue; } if (checked.isEquivalent(reference)) { return true; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java index ff1a847fd..4ba481557 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java @@ -262,6 +262,9 @@ public class CompilerOptions { public static final String OPTION_NullableAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nullable"; //$NON-NLS-1$ public static final String OPTION_NonNullAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nonnull"; //$NON-NLS-1$ public static final String OPTION_NonNullByDefaultAnnotationName = "org.eclipse.jdt.core.compiler.annotation.nonnullbydefault"; //$NON-NLS-1$ + public static final String OPTION_NullableAnnotationSecondaryNames = "org.eclipse.jdt.core.compiler.annotation.nullable.secondary"; //$NON-NLS-1$ + public static final String OPTION_NonNullAnnotationSecondaryNames = "org.eclipse.jdt.core.compiler.annotation.nonnull.secondary"; //$NON-NLS-1$ + public static final String OPTION_NonNullByDefaultAnnotationSecondaryNames = "org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary"; //$NON-NLS-1$ public static final String OPTION_ReportUninternedIdentityComparison = "org.eclipse.jdt.core.compiler.problem.uninternedIdentityComparison"; //$NON-NLS-1$ // defaults for the above: static final char[][] DEFAULT_NULLABLE_ANNOTATION_NAME = CharOperation.splitOn('.', "org.eclipse.jdt.annotation.Nullable".toCharArray()); //$NON-NLS-1$ @@ -303,6 +306,8 @@ public class CompilerOptions { public static final String NO_TAG = "no_tag"; //$NON-NLS-1$ public static final String ALL_STANDARD_TAGS = "all_standard_tags"; //$NON-NLS-1$ + private static final String[] NO_STRINGS = new String[0]; + /** * Bit mask for configurable problems (error/warning threshold) * Note: bitmask assumes 3 highest bits to denote irritant group (to allow storing 8 groups of 29 bits each @@ -572,6 +577,12 @@ public class CompilerOptions { public char[][] nonNullAnnotationName; /** Fully qualified name of annotation to use as marker for default nonnull. */ public char[][] nonNullByDefaultAnnotationName; + /** Fully qualified names of secondary annotations to use as marker for nullable types. */ + public String[] nullableAnnotationSecondaryNames = NO_STRINGS; + /** Fully qualified names of secondary annotations to use as marker for nonnull types. */ + public String[] nonNullAnnotationSecondaryNames = NO_STRINGS; + /** Fully qualified names of secondary annotations to use as marker for default nonnull. */ + public String[] nonNullByDefaultAnnotationSecondaryNames = NO_STRINGS; /** TagBits-encoded default for non-annotated types. */ public long intendedDefaultNonNullness; // 0 or TagBits#AnnotationNonNull /** Should resources (objects of type Closeable) be analysed for matching calls to close()? */ @@ -1534,6 +1545,9 @@ public class CompilerOptions { optionsMap.put(OPTION_NullableAnnotationName, String.valueOf(CharOperation.concatWith(this.nullableAnnotationName, '.'))); optionsMap.put(OPTION_NonNullAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullAnnotationName, '.'))); optionsMap.put(OPTION_NonNullByDefaultAnnotationName, String.valueOf(CharOperation.concatWith(this.nonNullByDefaultAnnotationName, '.'))); + optionsMap.put(OPTION_NullableAnnotationSecondaryNames, nameListToString(this.nullableAnnotationSecondaryNames)); + optionsMap.put(OPTION_NonNullAnnotationSecondaryNames, nameListToString(this.nonNullAnnotationSecondaryNames)); + optionsMap.put(OPTION_NonNullByDefaultAnnotationSecondaryNames, nameListToString(this.nonNullByDefaultAnnotationSecondaryNames)); optionsMap.put(OPTION_ReportMissingNonNullByDefaultAnnotation, getSeverityString(MissingNonNullByDefaultAnnotation)); optionsMap.put(OPTION_ReportUnusedTypeParameter, getSeverityString(UnusedTypeParameter)); optionsMap.put(OPTION_SyntacticNullAnalysisForFields, this.enableSyntacticNullAnalysisForFields ? ENABLED : DISABLED); @@ -2112,6 +2126,15 @@ public class CompilerOptions { if ((optionValue = optionsMap.get(OPTION_NonNullByDefaultAnnotationName)) != null) { this.nonNullByDefaultAnnotationName = CharOperation.splitAndTrimOn('.', optionValue.toCharArray()); } + if ((optionValue = optionsMap.get(OPTION_NullableAnnotationSecondaryNames)) != null) { + this.nullableAnnotationSecondaryNames = stringToNameList(optionValue); + } + if ((optionValue = optionsMap.get(OPTION_NonNullAnnotationSecondaryNames)) != null) { + this.nonNullAnnotationSecondaryNames = stringToNameList(optionValue); + } + if ((optionValue = optionsMap.get(OPTION_NonNullByDefaultAnnotationSecondaryNames)) != null) { + this.nonNullByDefaultAnnotationSecondaryNames = stringToNameList(optionValue); + } if ((optionValue = optionsMap.get(OPTION_ReportMissingNonNullByDefaultAnnotation)) != null) updateSeverity(MissingNonNullByDefaultAnnotation, optionValue); if ((optionValue = optionsMap.get(OPTION_SyntacticNullAnalysisForFields)) != null) { this.enableSyntacticNullAnalysisForFields = ENABLED.equals(optionValue); @@ -2257,6 +2280,26 @@ public class CompilerOptions { } } } + + private String[] stringToNameList(String optionValue) { + String[] result = optionValue.split(","); //$NON-NLS-1$ + if (result == null) + return NO_STRINGS; + for (int i = 0; i < result.length; i++) + result[i] = result[i].trim(); + return result; + } + + String nameListToString(String[] names) { + if (names == null) return ""; //$NON-NLS-1$ + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < names.length; i++) { + if (i > 0) buf.append(','); + buf.append(names[i]); + } + return buf.toString(); + } + public String toString() { StringBuffer buf = new StringBuffer("CompilerOptions:"); //$NON-NLS-1$ buf.append("\n\t- local variables debug attributes: ").append((this.produceDebugAttributes & ClassFileConstants.ATTR_VARS) != 0 ? "ON" : " OFF"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java index ac12a5e08..f4c1572cf 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ArrayBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2014 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -414,15 +414,12 @@ public void setTypeAnnotations(AnnotationBinding[] annotations, boolean evalNull for (int i = 0, length = annotations.length; i < length; i++) { AnnotationBinding annotation = annotations[i]; if (annotation != null) { - switch (annotation.type.id) { - case TypeIds.T_ConfiguredAnnotationNullable : - nullTagBits |= TagBits.AnnotationNullable; - this.tagBits |= TagBits.HasNullTypeAnnotation; - break; - case TypeIds.T_ConfiguredAnnotationNonNull : - nullTagBits |= TagBits.AnnotationNonNull; - this.tagBits |= TagBits.HasNullTypeAnnotation; - break; + if (annotation.type.hasNullBit(TypeIds.BitNullableAnnotation)) { + nullTagBits |= TagBits.AnnotationNullable; + this.tagBits |= TagBits.HasNullTypeAnnotation; + } else if (annotation.type.hasNullBit(TypeIds.BitNonNullAnnotation)) { + nullTagBits |= TagBits.AnnotationNonNull; + this.tagBits |= TagBits.HasNullTypeAnnotation; } } else { // null signals end of annotations for the current dimension in the serialized form. @@ -507,4 +504,10 @@ public TypeBinding uncapture(Scope scope) { public boolean acceptsNonNullDefault() { return true; } +@Override +public long updateTagBits() { + if (this.leafComponentType != null) + this.tagBits |= this.leafComponentType.updateTagBits(); + return super.updateTagBits(); +} } 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 4b534395e..4ba51baa1 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 @@ -751,10 +751,9 @@ private ITypeAnnotationWalker getTypeAnnotationWalker(IBinaryTypeAnnotation[] an private int getNullDefaultFrom(IBinaryAnnotation[] declAnnotations) { if (declAnnotations != null) { - char[][] nonNullByDefaultAnnotationName = this.environment.getNonNullByDefaultAnnotationName(); for (IBinaryAnnotation annotation : declAnnotations) { char[][] typeName = signature2qualifiedTypeName(annotation.getTypeName()); - if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) + if (this.environment.getNullAnnotationBit(typeName) == TypeIds.BitNonNullByDefaultAnnotation) return getNonNullByDefaultValue(annotation); } } @@ -1958,10 +1957,6 @@ private void scanFieldForNullAnnotation(IBinaryField field, FieldBinding fieldBi } // global option is checked by caller - char[][] nullableAnnotationName = this.environment.getNullableAnnotationName(); - char[][] nonNullAnnotationName = this.environment.getNonNullAnnotationName(); - if (nullableAnnotationName == null || nonNullAnnotationName == null) - return; // not well-configured to use null annotations if (fieldBinding.type == null || fieldBinding.type.isBaseType()) return; // null annotations are only applied to reference types @@ -1975,13 +1970,13 @@ private void scanFieldForNullAnnotation(IBinaryField field, FieldBinding fieldBi char[] annotationTypeName = annotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) continue; - char[][] typeName = signature2qualifiedTypeName(annotationTypeName); - if (CharOperation.equals(typeName, nonNullAnnotationName)) { + int typeBit = this.environment.getNullAnnotationBit(signature2qualifiedTypeName(annotationTypeName)); + if (typeBit == TypeIds.BitNonNullAnnotation) { fieldBinding.tagBits |= TagBits.AnnotationNonNull; explicitNullness = true; break; } - if (CharOperation.equals(typeName, nullableAnnotationName)) { + if (typeBit == TypeIds.BitNullableAnnotation) { fieldBinding.tagBits |= TagBits.AnnotationNullable; explicitNullness = true; break; @@ -2015,11 +2010,6 @@ private void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding met return; } } - char[][] nullableAnnotationName = this.environment.getNullableAnnotationName(); - char[][] nonNullAnnotationName = this.environment.getNonNullAnnotationName(); - char[][] nonNullByDefaultAnnotationName = this.environment.getNonNullByDefaultAnnotationName(); - if (nullableAnnotationName == null || nonNullAnnotationName == null || nonNullByDefaultAnnotationName == null) - return; // not well-configured to use null annotations // return: ITypeAnnotationWalker returnWalker = externalAnnotationWalker.toMethodReturn(); @@ -2031,16 +2021,21 @@ private void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding met char[] annotationTypeName = annotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) continue; - char[][] typeName = signature2qualifiedTypeName(annotationTypeName); - if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) { + int typeBit = this.environment.getNullAnnotationBit(signature2qualifiedTypeName(annotationTypeName)); + if (typeBit == TypeIds.BitNonNullByDefaultAnnotation) { methodBinding.defaultNullness = getNonNullByDefaultValue(annotations[i]); - if (methodBinding.defaultNullness == Binding.NULL_UNSPECIFIED_BY_DEFAULT) + if (methodBinding.defaultNullness == Binding.NULL_UNSPECIFIED_BY_DEFAULT) { methodBinding.tagBits |= TagBits.AnnotationNullUnspecifiedByDefault; - else if (methodBinding.defaultNullness != 0) + } else if (methodBinding.defaultNullness != 0) { methodBinding.tagBits |= TagBits.AnnotationNonNullByDefault; - } else if (CharOperation.equals(typeName, nonNullAnnotationName)) { + if (methodBinding.defaultNullness == Binding.NONNULL_BY_DEFAULT && this.environment.usesNullTypeAnnotations()) { + // reading a decl-nnbd in a project using type annotations, mimic corresponding semantics by enumerating: + methodBinding.defaultNullness |= Binding.DefaultLocationParameter | Binding.DefaultLocationReturnType; + } + } + } else if (typeBit == TypeIds.BitNonNullAnnotation) { methodBinding.tagBits |= TagBits.AnnotationNonNull; - } else if (CharOperation.equals(typeName, nullableAnnotationName)) { + } else if (typeBit == TypeIds.BitNullableAnnotation) { methodBinding.tagBits |= TagBits.AnnotationNullable; } } @@ -2065,13 +2060,13 @@ private void scanMethodForNullAnnotation(IBinaryMethod method, MethodBinding met char[] annotationTypeName = paramAnnotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) continue; - char[][] typeName = signature2qualifiedTypeName(annotationTypeName); - if (CharOperation.equals(typeName, nonNullAnnotationName)) { + int typeBit = this.environment.getNullAnnotationBit(signature2qualifiedTypeName(annotationTypeName)); + if (typeBit == TypeIds.BitNonNullAnnotation) { if (methodBinding.parameterNonNullness == null) methodBinding.parameterNonNullness = new Boolean[numVisibleParams]; methodBinding.parameterNonNullness[j] = Boolean.TRUE; break; - } else if (CharOperation.equals(typeName, nullableAnnotationName)) { + } else if (typeBit == TypeIds.BitNullableAnnotation) { if (methodBinding.parameterNonNullness == null) methodBinding.parameterNonNullness = new Boolean[numVisibleParams]; methodBinding.parameterNonNullness[j] = Boolean.FALSE; @@ -2103,14 +2098,18 @@ private void scanTypeForNullDefaultAnnotation(IBinaryType binaryType, PackageBin char[] annotationTypeName = annotations[i].getTypeName(); if (annotationTypeName[0] != Util.C_RESOLVED) continue; - char[][] typeName = signature2qualifiedTypeName(annotationTypeName); - if (CharOperation.equals(typeName, nonNullByDefaultAnnotationName)) { + int typeBit = this.environment.getNullAnnotationBit(signature2qualifiedTypeName(annotationTypeName)); + if (typeBit == TypeIds.BitNonNullByDefaultAnnotation) { // using NonNullByDefault we need to inspect the details of the value() attribute: nullness = getNonNullByDefaultValue(annotations[i]); if (nullness == NULL_UNSPECIFIED_BY_DEFAULT) { annotationBit = TagBits.AnnotationNullUnspecifiedByDefault; } else if (nullness != 0) { annotationBit = TagBits.AnnotationNonNullByDefault; + if (nullness == Binding.NONNULL_BY_DEFAULT && this.environment.usesNullTypeAnnotations()) { + // reading a decl-nnbd in a project using type annotations, mimic corresponding semantics by enumerating: + nullness |= Binding.DefaultLocationParameter | Binding.DefaultLocationReturnType | Binding.DefaultLocationField; + } } this.defaultNullness = nullness; break; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java index 869e8b8e9..ee47b4da4 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java @@ -590,8 +590,10 @@ class BoundSet { // not per JLS: if the new constraint relates types where at least one has a null annotations, // record all null tagBits as hints for the final inference solution. long nullHints = (newConstraint.left.tagBits | newConstraint.right.tagBits) & TagBits.AnnotationNullMASK; - boundI.nullHints |= nullHints; - boundJ.nullHints |= nullHints; + if (nullHints != 0 && TypeBinding.equalsEquals(boundI.left, boundJ.left)) { + boundI.nullHints |= nullHints; + boundJ.nullHints |= nullHints; + } } } ConstraintFormula[] typeArgumentConstraints = deriveTypeArgumentConstraints ? deriveTypeArgumentConstraints(boundI, boundJ) : null; @@ -778,9 +780,12 @@ class BoundSet { // α = U and S <: T imply ⟨S[α:=U] <: T[α:=U]⟩ TypeBinding u = boundS.right; if (u.isProperType(true)) { - TypeBinding left = (TypeBinding.equalsEquals(alpha, boundT.left)) ? u : boundT.left; + boolean substitute = TypeBinding.equalsEquals(alpha, boundT.left); + TypeBinding left = substitute ? u : boundT.left; TypeBinding right = boundT.right.substituteInferenceVariable(alpha, u); - return ConstraintTypeFormula.create(left, right, boundT.relation, boundT.isSoft||boundS.isSoft); + substitute |= TypeBinding.notEquals(right, boundT.right); + if (substitute) // avoid redundant constraint + return ConstraintTypeFormula.create(left, right, boundT.relation, boundT.isSoft||boundS.isSoft); } return null; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java index e430d9b1f..c6f68376e 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java @@ -1682,7 +1682,7 @@ public class ClassScope extends Scope { sourceType.tagBits |= (superType.tagBits & TagBits.HierarchyHasProblems); // propagate if missing supertpye sourceType.setSuperClass(superType); // bound check (in case of bogus definition of Enum type) - if (refTypeVariables[0].boundCheck(superType, sourceType, this) != TypeConstants.OK) { + if (!refTypeVariables[0].boundCheck(superType, sourceType, this, null).isOKbyJLS()) { problemReporter().typeMismatchError(rootEnumType, refTypeVariables[0], sourceType, null); } return !foundCycle; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java index 1f013a4cd..96ae35acd 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java @@ -17,6 +17,8 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; +import java.util.ArrayList; + import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.ast.*; @@ -91,6 +93,7 @@ public class CompilationUnitScope extends Scope { private boolean skipCachingImports; boolean connectingHierarchy; + private ArrayList<Invocation> inferredInvocations; //{ObjectTeams: when used as a baseimport scope, remember the original scope during this current lookup public Scope originalScope; // SH} @@ -1296,4 +1299,16 @@ public boolean hasDefaultNullnessFor(int location) { return (this.fPackage.defaultNullness & location) != 0; return false; } +public void registerInferredInvocation(Invocation invocation) { + if (this.inferredInvocations == null) + this.inferredInvocations = new ArrayList<>(); + this.inferredInvocations.add(invocation); +} +public void cleanUpInferenceContexts() { + if (this.inferredInvocations == null) + return; + for (Invocation invocation : this.inferredInvocations) + invocation.cleanUpInferenceContexts(); + this.inferredInvocations = null; +} } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java index 10675ce15..7247fc3bb 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ImplicitNullAnnotationVerifier.java @@ -306,7 +306,7 @@ public class ImplicitNullAnnotationVerifier { ParameterizedGenericMethodBinding substitute = this.environment.createParameterizedGenericMethod(currentMethod, typeVariables); substituteReturnType = substitute.returnType; } - if (NullAnnotationMatching.analyse(inheritedMethod.returnType, currentMethod.returnType, substituteReturnType, 0, CheckMode.OVERRIDE).isAnyMismatch()) { + if (NullAnnotationMatching.analyse(inheritedMethod.returnType, currentMethod.returnType, substituteReturnType, null, 0, CheckMode.OVERRIDE_RETURN).isAnyMismatch()) { if (srcMethod != null) scope.problemReporter().illegalReturnRedefinition(srcMethod, inheritedMethod, this.environment.getNonNullAnnotationName()); @@ -437,7 +437,7 @@ public class ImplicitNullAnnotationVerifier { if (useTypeAnnotations) { TypeBinding inheritedParameter = inheritedMethod.parameters[i]; TypeBinding substituteParameter = substituteParameters != null ? substituteParameters[i] : null; - if (NullAnnotationMatching.analyse(currentMethod.parameters[i], inheritedParameter, substituteParameter, 0, CheckMode.OVERRIDE).isAnyMismatch()) { + if (NullAnnotationMatching.analyse(currentMethod.parameters[i], inheritedParameter, substituteParameter, null, 0, CheckMode.OVERRIDE).isAnyMismatch()) { if (currentArgument != null) scope.problemReporter().illegalParameterRedefinition(currentArgument, inheritedMethod.declaringClass, inheritedParameter); else @@ -446,6 +446,18 @@ public class ImplicitNullAnnotationVerifier { } } } + + if (shouldComplain && useTypeAnnotations && srcMethod != null) { + TypeVariableBinding[] currentTypeVariables = currentMethod.typeVariables(); + TypeVariableBinding[] inheritedTypeVariables = inheritedMethod.typeVariables(); + if (currentTypeVariables != Binding.NO_TYPE_VARIABLES && currentTypeVariables.length == inheritedTypeVariables.length) { + for (int i = 0; i < currentTypeVariables.length; i++) { + TypeVariableBinding inheritedVariable = inheritedTypeVariables[i]; + if (NullAnnotationMatching.analyse(inheritedVariable, currentTypeVariables[i], null, null, -1, CheckMode.BOUND_CHECK).isAnyMismatch()) + scope.problemReporter().cannotRedefineTypeArgumentNullity(inheritedVariable, inheritedMethod, srcMethod.typeParameters()[i]); + } + } + } } void applyReturnNullBits(MethodBinding method, long nullnessBits) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java index 45b6c032a..5fecda127 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java @@ -212,6 +212,8 @@ public class InferenceContext18 { this.invocationArguments = arguments; this.currentInvocation = site; this.outerContext = outerContext; + if (site instanceof Invocation) + scope.compilationUnitScope().registerInferredInvocation((Invocation) site); } public InferenceContext18(Scope scope) { @@ -1656,4 +1658,9 @@ public class InferenceContext18 { } } } + + public void cleanUp() { + this.b2 = null; + this.currentBounds = null; + } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/IntersectionTypeBinding18.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/IntersectionTypeBinding18.java index fd43592f8..a93c1b59b 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/IntersectionTypeBinding18.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/IntersectionTypeBinding18.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2014 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -292,4 +292,10 @@ public class IntersectionTypeBinding18 extends ReferenceBinding { // abstraction } return false; } + @Override + public long updateTagBits() { + for (TypeBinding intersectingType : this.intersectingTypes) + this.tagBits |= intersectingType.updateTagBits(); + return super.updateTagBits(); + } } 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 3bb73da32..a4269c4c1 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 @@ -160,6 +160,8 @@ public class LookupEnvironment implements ProblemReasons, TypeConstants { AnnotationBinding nonNullAnnotation; AnnotationBinding nullableAnnotation; + Map<String,Integer> allNullAnnotations = null; + final List<MethodBinding> deferredEnumMethods = new ArrayList<>(); // during early initialization we cannot mark Enum-methods as nonnull. /** Global access to the outermost active inference context as the universe for inference variable interning. */ @@ -1262,9 +1264,10 @@ public TypeBinding createAnnotatedType(TypeBinding type, AnnotationBinding[] new continue; } long tagBits = 0; - switch (newbies[i].type.id) { - case TypeIds.T_ConfiguredAnnotationNonNull : tagBits = TagBits.AnnotationNonNull; break; - case TypeIds.T_ConfiguredAnnotationNullable : tagBits = TagBits.AnnotationNullable; break; + if (newbies[i].type.hasNullBit(TypeIds.BitNonNullAnnotation)) { + tagBits = TagBits.AnnotationNonNull; + } else if (newbies[i].type.hasNullBit(TypeIds.BitNullableAnnotation)) { + tagBits = TagBits.AnnotationNullable; } if ((tagBitsSeen & tagBits) == 0) { tagBitsSeen |= tagBits; @@ -1368,6 +1371,27 @@ public char[][] getNonNullByDefaultAnnotationName() { return this.globalOptions.nonNullByDefaultAnnotationName; } +int getNullAnnotationBit(char[][] qualifiedTypeName) { + if (this.allNullAnnotations == null) { + this.allNullAnnotations = new HashMap<>(); + this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nonNullAnnotationName), TypeIds.BitNonNullAnnotation); + this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nullableAnnotationName), TypeIds.BitNullableAnnotation); + this.allNullAnnotations.put(CharOperation.toString(this.globalOptions.nonNullByDefaultAnnotationName), TypeIds.BitNonNullByDefaultAnnotation); + for (String name : this.globalOptions.nullableAnnotationSecondaryNames) + this.allNullAnnotations.put(name, TypeIds.BitNullableAnnotation); + for (String name : this.globalOptions.nonNullAnnotationSecondaryNames) + this.allNullAnnotations.put(name, TypeIds.BitNonNullAnnotation); + for (String name : this.globalOptions.nonNullByDefaultAnnotationSecondaryNames) + this.allNullAnnotations.put(name, TypeIds.BitNonNullByDefaultAnnotation); + } + String qualifiedTypeString = CharOperation.toString(qualifiedTypeName); + Integer typeBit = this.allNullAnnotations.get(qualifiedTypeString); + return typeBit == null ? 0 : typeBit; +} +public boolean isNullnessAnnotationPackage(PackageBinding pkg) { + return this.nonnullAnnotationPackage == pkg || this.nullableAnnotationPackage == pkg || this.nonnullByDefaultAnnotationPackage == pkg; +} + public boolean usesNullTypeAnnotations() { if (this.globalOptions.useNullTypeAnnotations != null) return this.globalOptions.useNullTypeAnnotations; @@ -2045,8 +2069,7 @@ public AnnotationBinding[] filterNullTypeAnnotations(AnnotationBinding[] typeAnn if (typeAnnotation == null) { count++; // sentinel in annotation sequence for array dimensions } else { - int id = typeAnnotation.type.id; - if (id != TypeIds.T_ConfiguredAnnotationNonNull && id != TypeIds.T_ConfiguredAnnotationNullable) + if (!typeAnnotation.type.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) filtered[count++] = typeAnnotation; } } @@ -2061,15 +2084,13 @@ public AnnotationBinding[] filterNullTypeAnnotations(AnnotationBinding[] typeAnn public boolean containsNullTypeAnnotation(IBinaryAnnotation[] typeAnnotations) { if (typeAnnotations.length == 0) return false; - char[][] nonNullAnnotationName = this.getNonNullAnnotationName(); - char[][] nullableAnnotationName = this.getNullableAnnotationName(); for (int i = 0; i < typeAnnotations.length; i++) { IBinaryAnnotation typeAnnotation = typeAnnotations[i]; char[] typeName = typeAnnotation.getTypeName(); // typeName must be "Lfoo/X;" if (typeName == null || typeName.length < 3 || typeName[0] != 'L') continue; char[][] name = CharOperation.splitOn('/', typeName, 1, typeName.length-1); - if (CharOperation.equals(name, nonNullAnnotationName) || CharOperation.equals(name, nullableAnnotationName)) + if (getNullAnnotationBit(name) != 0) return true; } return false; 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 0d53059a3..41307d3d6 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2013 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -283,22 +283,24 @@ private boolean isPackageOfQualifiedTypeName(char[][] packageName, char[][] type void checkIfNullAnnotationType(ReferenceBinding type) { // check if type is one of the configured null annotation types - // if so mark as a well known type using the corresponding typeID: + // if so mark as a well known type using the corresponding typeBit: if (this.environment.nullableAnnotationPackage == this && CharOperation.equals(type.compoundName, this.environment.getNullableAnnotationName())) { - type.id = TypeIds.T_ConfiguredAnnotationNullable; + type.typeBits |= TypeIds.BitNullableAnnotation; if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type 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; + type.typeBits |= TypeIds.BitNonNullAnnotation; if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type 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; + type.typeBits |= TypeIds.BitNonNullByDefaultAnnotation; if (!(type instanceof UnresolvedReferenceBinding)) // unresolved will need to check back for the resolved type this.environment.nonnullByDefaultAnnotationPackage = null; // don't check again + } else { + type.typeBits |= this.environment.getNullAnnotationBit(type.compoundName); } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java index 983e84f5c..82e4b2d60 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedGenericMethodBinding.java @@ -27,6 +27,7 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.Invocation; import org.eclipse.jdt.internal.compiler.ast.MessageSend; @@ -193,12 +194,12 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin if (actualReceiverType instanceof ReferenceBinding) actualReceiverRefType = (ReferenceBinding) actualReceiverType; } - switch (typeVariable.boundCheck(substitution, substituteForChecks, actualReceiverRefType, scope)) { + switch (typeVariable.boundCheck(substitution, substituteForChecks, actualReceiverRefType, scope, null)) { /* orig: - switch (typeVariable.boundCheck(substitution, substituteForChecks, scope)) { + switch (typeVariable.boundCheck(substitution, substituteForChecks, scope, null)) { :giro */ // SH} - case TypeConstants.MISMATCH : + case MISMATCH : // incompatible due to bound check int argLength = arguments.length; TypeBinding[] augmentedArguments = new TypeBinding[argLength + 2]; // append offending substitute and typeVariable @@ -206,10 +207,12 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin augmentedArguments[argLength] = substitute; augmentedArguments[argLength+1] = typeVariable; return new ProblemMethodBinding(methodSubstitute, originalMethod.selector, augmentedArguments, ProblemReasons.ParameterBoundMismatch); - case TypeConstants.UNCHECKED : + case UNCHECKED : // tolerate unchecked bounds methodSubstitute.tagBits |= TagBits.HasUncheckedTypeArgumentForBoundCheck; break; + default: + break; } } // check presence of unchecked argument conversion a posteriori (15.12.2.6) @@ -314,12 +317,7 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin if (invocationTypeInferred) { if (compilerOptions.isAnnotationBasedNullAnalysisEnabled) NullAnnotationMatching.checkForContradictions(methodSubstitute, invocationSite, scope); -//{ObjectTeams: 2nd arg added: -/* orig: - MethodBinding problemMethod = methodSubstitute.boundCheck18(scope, arguments); - :giro */ - MethodBinding problemMethod = methodSubstitute.boundCheck18(scope, invocationSite, arguments); -// SH} + MethodBinding problemMethod = methodSubstitute.boundCheck18(scope, arguments, invocationSite); if (problemMethod != null) { return problemMethod; } @@ -343,12 +341,7 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin } } -//{ObjectTeams: second arg added: -/*orig: - MethodBinding boundCheck18(Scope scope, TypeBinding[] arguments) { - :giro */ - MethodBinding boundCheck18(Scope scope, InvocationSite invocationSite, TypeBinding[] arguments) { -// SH} + MethodBinding boundCheck18(Scope scope, TypeBinding[] arguments, InvocationSite site) { Substitution substitution = this; ParameterizedGenericMethodBinding methodSubstitute = this; TypeVariableBinding[] originalTypeVariables = this.originalMethod.typeVariables; @@ -369,17 +362,18 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin //{ObjectTeams: methods with generic declared lifting need to be checked in knowledge of the actual receiver type: ReferenceBinding actualReceiverRefType = null; - if (invocationSite instanceof MessageSend) { - TypeBinding actualReceiverType = ((MessageSend)invocationSite).actualReceiverType; + if (site instanceof MessageSend) { + TypeBinding actualReceiverType = ((MessageSend)site).actualReceiverType; if (actualReceiverType instanceof ReferenceBinding) actualReceiverRefType = (ReferenceBinding) actualReceiverType; } - switch (typeVariable.boundCheck(substitution, substituteForChecks, actualReceiverRefType, scope)) { + ASTNode location = site instanceof ASTNode ? (ASTNode) site : null; + switch (typeVariable.boundCheck(substitution, substituteForChecks, actualReceiverRefType, scope, location)) { /* orig: - switch (typeVariable.boundCheck(substitution, substituteForChecks, scope)) { + switch (typeVariable.boundCheck(substitution, substituteForChecks, scope, location)) { :giro */ // SH} - case TypeConstants.MISMATCH : + case MISMATCH : // incompatible due to bound check int argLength = arguments.length; TypeBinding[] augmentedArguments = new TypeBinding[argLength + 2]; // append offending substitute and typeVariable @@ -387,10 +381,12 @@ public class ParameterizedGenericMethodBinding extends ParameterizedMethodBindin augmentedArguments[argLength] = substitute; augmentedArguments[argLength+1] = typeVariable; return new ProblemMethodBinding(methodSubstitute, this.originalMethod.selector, augmentedArguments, ProblemReasons.ParameterBoundMismatch); - case TypeConstants.UNCHECKED : + case UNCHECKED : // tolerate unchecked bounds methodSubstitute.tagBits |= TagBits.HasUncheckedTypeArgumentForBoundCheck; break; + default: + break; } } return null; diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedMethodBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedMethodBinding.java index 514c3e69e..40c4f6aba 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedMethodBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedMethodBinding.java @@ -324,10 +324,18 @@ public class ParameterizedMethodBinding extends MethodBinding { ReferenceBinding genericClassType = scope.getJavaLangClass(); LookupEnvironment environment = scope.environment(); TypeBinding rawType = environment.convertToRawType(receiverType.erasure(), false /*do not force conversion of enclosing types*/); + if (environment.usesNullTypeAnnotations()) + rawType = environment.createAnnotatedType(rawType, new AnnotationBinding[] { environment.getNonNullAnnotation() }); method.returnType = environment.createParameterizedType( genericClassType, new TypeBinding[] { environment.createWildcard(genericClassType, 0, rawType, null /*no extra bound*/, Wildcard.EXTENDS) }, null); + if (environment.globalOptions.isAnnotationBasedNullAnalysisEnabled) { + if (environment.usesNullTypeAnnotations()) + method.returnType = environment.createAnnotatedType(method.returnType, new AnnotationBinding[] { environment.getNonNullAnnotation() }); + else + method.tagBits |= TagBits.AnnotationNonNull; + } if ((method.returnType.tagBits & TagBits.HasMissingType) != 0) { method.tagBits |= TagBits.HasMissingType; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java index 1056a1c71..246f94c3c 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java @@ -56,6 +56,7 @@ import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants.BoundCheckStatus; import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.DependentTypeBinding; import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.ITeamAnchor; import org.eclipse.objectteams.otdt.internal.core.compiler.util.RoleTypeCreator.TypeArgumentUpdater; @@ -149,13 +150,12 @@ public class ParameterizedTypeBinding extends ReferenceBinding implements Substi TypeVariableBinding[] typeVariables = this.type.typeVariables(); if (this.arguments != null && typeVariables != null) { // arguments may be null in error cases for (int i = 0, length = typeVariables.length; i < length; i++) { - if (typeVariables[i].boundCheck(this, this.arguments[i], scope) != TypeConstants.OK) { - hasErrors = true; - if ((this.arguments[i].tagBits & TagBits.HasMissingType) == 0) { - // do not report secondary error, if type reference already got complained against - scope.problemReporter().typeMismatchError(this.arguments[i], typeVariables[i], this.type, argumentReferences[i]); - } - } + BoundCheckStatus checkStatus = typeVariables[i].boundCheck(this, this.arguments[i], scope, argumentReferences[i]); + hasErrors |= checkStatus != BoundCheckStatus.OK; + if (!checkStatus.isOKbyJLS() && (this.arguments[i].tagBits & TagBits.HasMissingType) == 0) { + // do not report secondary error, if type reference already got complained against + scope.problemReporter().typeMismatchError(this.arguments[i], typeVariables[i], this.type, argumentReferences[i]); + } } } if (!hasErrors) this.tagBits |= TagBits.PassedBoundCheck; // no need to recheck it in the future @@ -1610,7 +1610,7 @@ public class ParameterizedTypeBinding extends ReferenceBinding implements Substi declaringType = scope.environment().createParameterizedType(genericType, types, genericType.enclosingType()); TypeVariableBinding [] typeParameters = genericType.typeVariables(); for (int i = 0, length = typeParameters.length; i < length; i++) { - if (typeParameters[i].boundCheck(declaringType, types[i], scope) != TypeConstants.OK) + if (!typeParameters[i].boundCheck(declaringType, types[i], scope, null).isOKbyJLS()) return this.singleAbstractMethod[index] = new ProblemMethodBinding(TypeConstants.ANONYMOUS_METHOD, null, ProblemReasons.NotAWellFormedParameterizedType); } ReferenceBinding substitutedDeclaringType = (ReferenceBinding) declaringType.findSuperTypeOriginatingFrom(theAbstractMethod.declaringClass); @@ -1693,6 +1693,13 @@ public class ParameterizedTypeBinding extends ReferenceBinding implements Substi } return types; } + @Override + public long updateTagBits() { + if (this.arguments != null) + for (TypeBinding argument : this.arguments) + this.tagBits |= argument.updateTagBits(); + return super.updateTagBits(); + } //{ObjectTeams: recursive role wrapping: @Override 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 a1d8b7c6a..0c878c44e 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 @@ -1442,6 +1442,11 @@ public final boolean hasRestrictedAccess() { return (this.modifiers & ExtraCompilerModifiers.AccRestrictedAccess) != 0; } +/** Query typeBits without triggering supertype lookup. */ +public boolean hasNullBit(int mask) { + return (this.typeBits & mask) != 0; +} + //{ObjectTeams: support asymmetric comparison. // FIXME(SH): is this needed or is super-impl smart enough?? @Override public boolean isProvablyDistinct(TypeBinding otherType) { @@ -2066,8 +2071,8 @@ protected void appendNullAnnotation(StringBuffer nameBuffer, CompilerOptions opt if (options.isAnnotationBasedNullAnalysisEnabled) { if (options.usesNullTypeAnnotations()) { for (AnnotationBinding annotation : this.typeAnnotations) { - TypeBinding annotationType = annotation.getAnnotationType(); - if (annotationType.id == TypeIds.T_ConfiguredAnnotationNonNull || annotation.type.id == TypeIds.T_ConfiguredAnnotationNullable) { + ReferenceBinding annotationType = annotation.getAnnotationType(); + if (annotationType.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { nameBuffer.append('@').append(annotationType.shortReadableName()).append(' '); } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java index 58bfb49f0..aaf60c319 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java @@ -1044,8 +1044,14 @@ public abstract class Scope { } // after bounds have been resolved we're ready for resolving the type parameter itself, // which includes resolving/evaluating type annotations and checking for inconsistencies - for (int i = 0; i < paramLength; i++) + boolean declaresNullTypeAnnotation = false; + for (int i = 0; i < paramLength; i++) { resolveTypeParameter(typeParameters[i]); + declaresNullTypeAnnotation |= typeParameters[i].binding.hasNullTypeAnnotations(); + } + if (declaresNullTypeAnnotation) + for (int i = 0; i < paramLength; i++) + typeParameters[i].binding.updateTagBits(); // <T extends List<U>, @NonNull U> --> tag T as having null type annotations return noProblems; } @@ -3665,7 +3671,7 @@ public abstract class Scope { unitScope.recordSimpleReference(name); if ((mask & Binding.PACKAGE) != 0) { PackageBinding packageBinding = unitScope.environment.getTopLevelPackage(name); - if (packageBinding != null) { + if (packageBinding != null && (packageBinding.tagBits & TagBits.HasMissingType) == 0) { if (typeOrPackageCache != null) typeOrPackageCache.put(name, packageBinding); return packageBinding; 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 069edf181..bb9e35f32 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 @@ -2789,8 +2789,7 @@ public void evaluateNullAnnotations() { for (int i = 0; i < annotations.length; i++) { ReferenceBinding annotationType = annotations[i].getCompilerAnnotation().getAnnotationType(); if (annotationType != null) { - if (annotationType.id == TypeIds.T_ConfiguredAnnotationNonNull - || annotationType.id == TypeIds.T_ConfiguredAnnotationNullable) { + if (annotationType.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { this.scope.problemReporter().nullAnnotationUnsupportedLocation(annotations[i]); this.tagBits &= ~TagBits.AnnotationNullMASK; } @@ -2802,10 +2801,7 @@ public void evaluateNullAnnotations() { PackageBinding pkg = getPackage(); boolean isInDefaultPkg = (pkg.compoundName == CharOperation.NO_CHAR_CHAR); if (!isPackageInfo) { - boolean isInNullnessAnnotationPackage = - pkg == this.scope.environment().nonnullAnnotationPackage - || pkg == this.scope.environment().nullableAnnotationPackage - || pkg == this.scope.environment().nonnullByDefaultAnnotationPackage; + boolean isInNullnessAnnotationPackage = this.scope.environment().isNullnessAnnotationPackage(pkg); if (pkg.defaultNullness == NO_NULL_DEFAULT && !isInDefaultPkg && !isInNullnessAnnotationPackage && !(this instanceof NestedTypeBinding)) { ReferenceBinding packageInfo = pkg.getType(TypeConstants.PACKAGE_INFO_NAME); if (packageInfo == null) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java index f3ba9bc59..3a97bb9b1 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2014 IBM Corporation and others. + * Copyright (c) 2000, 2015 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 @@ -1599,14 +1599,10 @@ public void setTypeAnnotations(AnnotationBinding[] annotations, boolean evalNull for (int i = 0, length = annotations.length; i < length; i++) { AnnotationBinding annotation = annotations[i]; if (annotation != null) { - switch (annotation.type.id) { - case TypeIds.T_ConfiguredAnnotationNullable : - this.tagBits |= TagBits.AnnotationNullable | TagBits.HasNullTypeAnnotation; - break; - case TypeIds.T_ConfiguredAnnotationNonNull : - this.tagBits |= TagBits.AnnotationNonNull | TagBits.HasNullTypeAnnotation; - break; - } + if (annotation.type.hasNullBit(TypeIds.BitNullableAnnotation)) + this.tagBits |= TagBits.AnnotationNullable | TagBits.HasNullTypeAnnotation; + else if (annotation.type.hasNullBit(TypeIds.BitNonNullAnnotation)) + this.tagBits |= TagBits.AnnotationNonNull | TagBits.HasNullTypeAnnotation; } } // we do accept contradictory tagBits here, to support detecting contradictions caused by type substitution @@ -1778,4 +1774,11 @@ public void exitRecursiveFunction() { public boolean isFunctionalType() { return false; } +/** + * Refresh some tagBits from details into the main type. + * Currently handled: TagBits.HasNullTypeAnnotation + */ +public long updateTagBits() { + return this.tagBits & TagBits.HasNullTypeAnnotation; // subclasses to override +} } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java index 0284bd1c6..baa1c68f3 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java @@ -345,10 +345,25 @@ public interface TypeConstants { int CONSTRAINT_EXTENDS = 1; // Actual << Formal int CONSTRAINT_SUPER = 2; // Actual >> Formal - // Constants used to perform bound checks - int OK = 0; - int UNCHECKED = 1; - int MISMATCH = 2; + // status of bound checks + public static enum BoundCheckStatus { + OK, NULL_PROBLEM, UNCHECKED, MISMATCH; + /** true if no problem or only a null problem. */ + boolean isOKbyJLS() { + switch (this) { + case OK: + case NULL_PROBLEM: + return true; + default: + return false; + } + } + public BoundCheckStatus betterOf(BoundCheckStatus other) { + if (this.ordinal() < other.ordinal()) + return this; + return other; + } + } // Synthetics char[] INIT = "<init>".toCharArray(); //$NON-NLS-1$ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java index 1693a6587..af32c7801 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java @@ -108,10 +108,7 @@ public interface TypeIds { // java 7 java.lang.AutoCloseable final int T_JavaLangAutoCloseable = 62; - // new in 3.8 for null annotations: - final int T_ConfiguredAnnotationNullable = 65; - final int T_ConfiguredAnnotationNonNull = 66; - final int T_ConfiguredAnnotationNonNullByDefault = 67; + // new in 3.8 for null annotations, removed in 4.6 (ids 65-67) // new in 3.8 to identify org.eclipse.core.runtime.Assert final int T_OrgEclipseCoreRuntimeAssert = 68; @@ -247,6 +244,14 @@ public interface TypeIds { final int BitResourceFreeCloseable = 8; final int BitUninternedType = 16; + + /** Bit for a type configured as a @NonNull annotation. */ + final int BitNonNullAnnotation = 32; + /** Bit for a type configured as a @Nullable annotation. */ + final int BitNullableAnnotation = 64; + /** Bit for a type configured as a @NonNullByDefault annotation. */ + final int BitNonNullByDefaultAnnotation = 128; + /** * Set of type bits that should be inherited by any sub types. */ diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeSystem.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeSystem.java index 74e292b43..cbf69ab1e 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeSystem.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeSystem.java @@ -11,6 +11,8 @@ * Bug 434602 - Possible error with inferred null annotations leading to contradictory null annotations * Bug 456497 - [1.8][null] during inference nullness from target type is lost against weaker hint from applicability analysis * Bug 456487 - [1.8][null] @Nullable type variant of @NonNull-constrained type parameter causes grief + * Till Brychcy - Contribution for + * Bug 473713 - [1.8][null] Type mismatch: cannot convert from @NonNull A1 to @NonNull A1 *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; @@ -217,14 +219,19 @@ public class TypeSystem { // Given a type, answer its unannotated aka naked prototype. This is also a convenient way to "register" a type with TypeSystem and have it id stamped. public final TypeBinding getUnannotatedType(TypeBinding type) { UnresolvedReferenceBinding urb = null; - if (type.isUnresolvedType() && CharOperation.indexOf('$', type.sourceName()) > 0) { + if (type.isUnresolvedType()) { urb = (UnresolvedReferenceBinding) type; - boolean mayTolerateMissingType = this.environment.mayTolerateMissingType; - this.environment.mayTolerateMissingType = true; - try { - type = BinaryTypeBinding.resolveType(type, this.environment, true); // to ensure unique id assignment (when enclosing type is parameterized, inner type is also) - } finally { - this.environment.mayTolerateMissingType = mayTolerateMissingType; + ReferenceBinding resolvedType = urb.resolvedType; + if (resolvedType != null) { + type = resolvedType; + } else if (CharOperation.indexOf('$', type.sourceName()) > 0) { + boolean mayTolerateMissingType = this.environment.mayTolerateMissingType; + this.environment.mayTolerateMissingType = true; + try { + type = BinaryTypeBinding.resolveType(type, this.environment, true); // to ensure unique id assignment (when enclosing type is parameterized, inner type is also) + } finally { + this.environment.mayTolerateMissingType = mayTolerateMissingType; + } } } if (type.id == TypeIds.NoId) { diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java index 633240832..fa31d41af 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/TypeVariableBinding.java @@ -47,8 +47,10 @@ import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching; import org.eclipse.jdt.internal.compiler.ast.TypeParameter; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.ast.Wildcard; +import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching.CheckMode; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants.BoundCheckStatus; import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.CallinCalloutBinding; import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.DependentTypeBinding; import org.eclipse.objectteams.otdt.internal.core.compiler.lookup.ITeamAnchor; @@ -117,38 +119,39 @@ public class TypeVariableBinding extends ReferenceBinding { /** * Returns true if the argument type satisfies all bounds of the type parameter + * @param location if non-null this may be used for reporting errors relating to null type annotations (if enabled) */ + public TypeConstants.BoundCheckStatus boundCheck(Substitution substitution, TypeBinding argumentType, Scope scope, ASTNode location) { //{ObjectTeams: added optional argument actualReceiverType: - public int boundCheck(Substitution substitution, TypeBinding argumentType, Scope scope) { - return boundCheck(substitution, argumentType, null, scope); + return boundCheck(substitution, argumentType, null, scope, location); } - public int boundCheck(Substitution substitution, TypeBinding argumentType, ReferenceBinding actualReceiverType, Scope scope) { - int code = internalBoundCheck(substitution, argumentType, actualReceiverType, scope); + public TypeConstants.BoundCheckStatus boundCheck(Substitution substitution, TypeBinding argumentType, ReferenceBinding actualReceiverType, Scope scope, ASTNode location) { + TypeConstants.BoundCheckStatus code = internalBoundCheck(substitution, argumentType, actualReceiverType, scope, location); // SH} - if (code == TypeConstants.MISMATCH) { + if (code == BoundCheckStatus.MISMATCH) { if (argumentType instanceof TypeVariableBinding && scope != null) { TypeBinding bound = ((TypeVariableBinding)argumentType).firstBound; if (bound instanceof ParameterizedTypeBinding) { - int code2 = boundCheck(substitution, bound.capture(scope, -1, -1), scope); // no position needed as this capture will never escape this context - return Math.min(code, code2); + BoundCheckStatus code2 = boundCheck(substitution, bound.capture(scope, -1, -1), scope, location); // no capture position needed as this capture will never escape this context + return code.betterOf(code2); } } } return code; } //{ObjectTeams: added optional argument actualReceiverType: - private int internalBoundCheck(Substitution substitution, TypeBinding argumentType, ReferenceBinding actualReceiverType, Scope scope) { + private TypeConstants.BoundCheckStatus internalBoundCheck(Substitution substitution, TypeBinding argumentType, ReferenceBinding actualReceiverType, Scope scope, ASTNode location) { // SH} if (argumentType == TypeBinding.NULL || TypeBinding.equalsEquals(argumentType, this)) { - return TypeConstants.OK; + return BoundCheckStatus.OK; } boolean hasSubstitution = substitution != null; if (!(argumentType instanceof ReferenceBinding || argumentType.isArrayType())) - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; // special case for re-entrant source types (selection, code assist, etc)... // can request additional types during hierarchy walk that are found as source types that also 'need' to connect their hierarchy if (this.superclass == null) - return TypeConstants.OK; + return BoundCheckStatus.OK; if (argumentType.kind() == Binding.WILDCARD_TYPE) { WildcardBinding wildcard = (WildcardBinding) argumentType; @@ -156,30 +159,30 @@ public class TypeVariableBinding extends ReferenceBinding { case Wildcard.EXTENDS : TypeBinding wildcardBound = wildcard.bound; if (TypeBinding.equalsEquals(wildcardBound, this)) - return TypeConstants.OK; + return BoundCheckStatus.OK; boolean isArrayBound = wildcardBound.isArrayType(); if (!wildcardBound.isInterface()) { TypeBinding substitutedSuperType = hasSubstitution ? Scope.substitute(substitution, this.superclass) : this.superclass; if (substitutedSuperType.id != TypeIds.T_JavaLangObject) { if (isArrayBound) { if (!wildcardBound.isCompatibleWith(substitutedSuperType, scope)) - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } else { TypeBinding match = wildcardBound.findSuperTypeOriginatingFrom(substitutedSuperType); if (match != null) { if (substitutedSuperType.isProvablyDistinct(match)) { - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } } else { match = substitutedSuperType.findSuperTypeOriginatingFrom(wildcardBound); if (match != null) { if (match.isProvablyDistinct(wildcardBound)) { - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } } else { if (denotesRelevantSuperClass(wildcardBound) && denotesRelevantSuperClass(substitutedSuperType)) { // non-object real superclass should have produced a valid 'match' above - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } } } @@ -191,15 +194,15 @@ public class TypeVariableBinding extends ReferenceBinding { TypeBinding substitutedSuperType = hasSubstitution ? Scope.substitute(substitution, this.superInterfaces[i]) : this.superInterfaces[i]; if (isArrayBound) { if (!wildcardBound.isCompatibleWith(substitutedSuperType, scope)) - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } else { TypeBinding match = wildcardBound.findSuperTypeOriginatingFrom(substitutedSuperType); if (match != null) { if (substitutedSuperType.isProvablyDistinct(match)) { - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } } else if (mustImplement) { - return TypeConstants.MISMATCH; // cannot be extended further to satisfy missing bounds + return BoundCheckStatus.MISMATCH; // cannot be extended further to satisfy missing bounds } } @@ -210,19 +213,21 @@ public class TypeVariableBinding extends ReferenceBinding { // if the wildcard is lower-bounded by a type variable that has no relevant upper bound there's nothing to check here (bug 282152): if (wildcard.bound.isTypeVariable() && ((TypeVariableBinding)wildcard.bound).superclass.id == TypeIds.T_JavaLangObject) break; - return boundCheck(substitution, wildcard.bound, scope); + return boundCheck(substitution, wildcard.bound, scope, location); case Wildcard.UNBOUND : break; } - return TypeConstants.OK; + return BoundCheckStatus.OK; } boolean unchecked = false; + boolean checkNullAnnotations = scope.environment().usesNullTypeAnnotations(); + boolean haveReportedNullProblem = false; if (this.superclass.id != TypeIds.T_JavaLangObject) { TypeBinding substitutedSuperType = hasSubstitution ? Scope.substitute(substitution, this.superclass) : this.superclass; if (TypeBinding.notEquals(substitutedSuperType, argumentType)) { if (!argumentType.isCompatibleWith(substitutedSuperType, scope)) { - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } TypeBinding match = argumentType.findSuperTypeOriginatingFrom(substitutedSuperType); if (match != null){ @@ -231,6 +236,12 @@ public class TypeVariableBinding extends ReferenceBinding { unchecked = true; } } + if (location != null && checkNullAnnotations) { + if (NullAnnotationMatching.analyse(this, argumentType, substitutedSuperType, substitution, -1, CheckMode.BOUND_CHECK).isAnyMismatch()) { + scope.problemReporter().nullityMismatchTypeArgument(this, argumentType, location); + haveReportedNullProblem = true; + } + } } //{ObjectTeams: type check <B base R> if (this.roletype != null) { @@ -239,14 +250,14 @@ public class TypeVariableBinding extends ReferenceBinding { TypeVariableBinding argTypeVar = (TypeVariableBinding) argumentType; if (argTypeVar.roletype != null) { if (this.roletype.isCompatibleWith(argTypeVar.roletype)) - return TypeConstants.OK; + return BoundCheckStatus.OK; else - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } } TypeBinding substitutedRoleType = hasSubstitution ? Scope.substitute(substitution, this.roletype) : this.roletype; if (!substitutedRoleType.isRole()) - return TypeConstants.MISMATCH; // actually roletype was already checked for role-ness. + return BoundCheckStatus.MISMATCH; // actually roletype was already checked for role-ness. // FIXME(SH): handle arrays? enums? nested generics? if (actualReceiverType != null) substitutedRoleType = TeamModel.strengthenRoleType(actualReceiverType, substitutedRoleType); @@ -254,21 +265,21 @@ public class TypeVariableBinding extends ReferenceBinding { for (ReferenceBinding aRoletype : roletypes) { ReferenceBinding basetype = aRoletype.baseclass(); // non-null by definition of getBoundDescendants() if (TypeBinding.equalsEquals(basetype, argumentType) || argumentType.isCompatibleWith(basetype)) { - return TypeConstants.OK; + return BoundCheckStatus.OK; } } - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } // check value-dependent type-variable: if (this.anchors != null && substitution != null) { ITeamAnchor anchor = substitution.substituteAnchor(this.anchors[0], 0); // TODO(SH): support multiple anchors if (anchor != null) { if (!(argumentType instanceof DependentTypeBinding)) { - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } else { ITeamAnchor argAnchor = ((DependentTypeBinding)argumentType).getAnchor(); if (!argAnchor.hasSameBestNameAs(anchor)) - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } } } @@ -277,7 +288,7 @@ public class TypeVariableBinding extends ReferenceBinding { TypeBinding substitutedSuperType = hasSubstitution ? Scope.substitute(substitution, this.superInterfaces[i]) : this.superInterfaces[i]; if (TypeBinding.notEquals(substitutedSuperType, argumentType)) { if (!argumentType.isCompatibleWith(substitutedSuperType, scope)) { - return TypeConstants.MISMATCH; + return BoundCheckStatus.MISMATCH; } TypeBinding match = argumentType.findSuperTypeOriginatingFrom(substitutedSuperType); if (match != null){ @@ -286,15 +297,21 @@ public class TypeVariableBinding extends ReferenceBinding { unchecked = true; } } + if (location != null && checkNullAnnotations) { + if (NullAnnotationMatching.analyse(this, argumentType, substitutedSuperType, substitution, -1, CheckMode.BOUND_CHECK).isAnyMismatch()) { + scope.problemReporter().nullityMismatchTypeArgument(this, argumentType, location); + haveReportedNullProblem = true; + } + } } - long nullTagBits = NullAnnotationMatching.validNullTagBits(this.tagBits); - if (nullTagBits != 0) { - long argBits = NullAnnotationMatching.validNullTagBits(argumentType.tagBits); - if (argBits != nullTagBits) { -// System.err.println("TODO(stephan): issue proper error: bound conflict at "+String.valueOf(this.declaringElement.readableName())); - } + if (location != null && checkNullAnnotations && !haveReportedNullProblem) { + long nullBits = this.tagBits & TagBits.AnnotationNullMASK; + if (nullBits != 0 && nullBits != (argumentType.tagBits & TagBits.AnnotationNullMASK)) { + scope.problemReporter().nullityMismatchTypeArgument(this, argumentType, location); + haveReportedNullProblem = true; + } } - return unchecked ? TypeConstants.UNCHECKED : TypeConstants.OK; + return unchecked ? BoundCheckStatus.UNCHECKED : haveReportedNullProblem ? BoundCheckStatus.NULL_PROBLEM : BoundCheckStatus.OK; } boolean denotesRelevantSuperClass(TypeBinding type) { @@ -1133,4 +1150,21 @@ public class TypeVariableBinding extends ReferenceBinding { public boolean acceptsNonNullDefault() { return false; } + + @Override + public long updateTagBits() { + if (!this.inRecursiveFunction) { + this.inRecursiveFunction = true; + try { + if (this.superclass != null) + this.tagBits |= this.superclass.updateTagBits(); + if (this.superInterfaces != null) + for (TypeBinding superIfc : this.superInterfaces) + this.tagBits |= superIfc.updateTagBits(); + } finally { + this.inRecursiveFunction = false; + } + } + return super.updateTagBits(); + } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/WildcardBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/WildcardBinding.java index 3ff8e77bb..1f859b702 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/WildcardBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/WildcardBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2014 IBM Corporation and others. + * Copyright (c) 2005, 2015 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 @@ -994,4 +994,22 @@ public class WildcardBinding extends ReferenceBinding { public boolean acceptsNonNullDefault() { return false; } + + @Override + public long updateTagBits() { + if (!this.inRecursiveFunction) { + this.inRecursiveFunction = true; + try { + if (this.bound != null) + this.tagBits |= this.bound.updateTagBits(); + if (this.otherBounds != null) { + for (int i = 0, length = this.otherBounds.length; i < length; i++) + this.tagBits |= this.otherBounds[i].updateTagBits(); + } + } finally { + this.inRecursiveFunction = false; + } + } + return super.updateTagBits(); + } } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java index 689813bb3..59c3bcd1d 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java @@ -462,6 +462,7 @@ public static int getIrritant(int problemID) { case IProblem.IllegalReturnNullityRedefinition: case IProblem.IllegalReturnNullityRedefinitionFreeTypeVariable: case IProblem.IllegalRedefinitionToNonNullParameter: + case IProblem.IllegalRedefinitionOfTypeVariable: case IProblem.IllegalDefinitionToNonNullParameter: case IProblem.ParameterLackingNullableAnnotation: case IProblem.CannotImplementIncompatibleNullness: @@ -9532,9 +9533,6 @@ private boolean excludeDueToAnnotation(Annotation[] annotations, int problemId) case TypeIds.T_JavaLangSuppressWarnings: case TypeIds.T_JavaLangDeprecated: case TypeIds.T_JavaLangSafeVarargs: - case TypeIds.T_ConfiguredAnnotationNonNull: - case TypeIds.T_ConfiguredAnnotationNullable: - case TypeIds.T_ConfiguredAnnotationNonNullByDefault: break; case TypeIds.T_JavaxInjectInject: case TypeIds.T_ComGoogleInjectInject: @@ -9543,8 +9541,10 @@ private boolean excludeDueToAnnotation(Annotation[] annotations, int problemId) return true; // @Inject on method/ctor does constitute a relevant use, just on fields it doesn't break; default: - // non-standard annotation found, don't warn - return true; + if (resolvedType instanceof ReferenceBinding) + if (((ReferenceBinding) resolvedType).hasNullBit(TypeIds.BitNullableAnnotation|TypeIds.BitNonNullAnnotation|TypeIds.BitNonNullByDefaultAnnotation)) + break; + return true; // non-standard annotation found, don't warn } } } @@ -13668,9 +13668,7 @@ public void illegalRedefinitionToNonNullParameter(Argument argument, ReferenceBi if (argument.annotations != null) { for (int i=0; i<argument.annotations.length; i++) { Annotation annotation = argument.annotations[i]; - if ( annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNullable - || annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNonNull) - { + if (annotation.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { sourceStart = annotation.sourceStart; break; } @@ -13721,9 +13719,7 @@ public void illegalParameterRedefinition(Argument argument, ReferenceBinding dec if (argument.annotations != null) { for (int i=0; i<argument.annotations.length; i++) { Annotation annotation = argument.annotations[i]; - if ( annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNullable - || annotation.resolvedType.id == TypeIds.T_ConfiguredAnnotationNonNull) - { + if (annotation.hasNullBit(TypeIds.BitNonNullAnnotation|TypeIds.BitNullableAnnotation)) { sourceStart = annotation.sourceStart; break; } @@ -13752,7 +13748,7 @@ public void illegalReturnRedefinition(AbstractMethodDeclaration abstractMethodDe .append(inheritedMethod.shortReadableName()); int sourceStart = methodDecl.returnType.sourceStart; Annotation[] annotations = methodDecl.annotations; - Annotation annotation = findAnnotation(annotations, TypeIds.T_ConfiguredAnnotationNullable); + Annotation annotation = findAnnotation(annotations, TypeIds.BitNullableAnnotation); if (annotation != null) { sourceStart = annotation.sourceStart; } @@ -13907,7 +13903,7 @@ public void nullAnnotationIsRedundant(AbstractMethodDeclaration sourceMethod, in int sourceStart, sourceEnd; if (i == -1) { MethodDeclaration methodDecl = (MethodDeclaration) sourceMethod; - Annotation annotation = findAnnotation(methodDecl.annotations, TypeIds.T_ConfiguredAnnotationNonNull); + Annotation annotation = findAnnotation(methodDecl.annotations, TypeIds.BitNonNullAnnotation); sourceStart = annotation != null ? annotation.sourceStart : methodDecl.returnType.sourceStart; sourceEnd = methodDecl.returnType.sourceEnd; } else { @@ -13919,14 +13915,14 @@ public void nullAnnotationIsRedundant(AbstractMethodDeclaration sourceMethod, in } public void nullAnnotationIsRedundant(FieldDeclaration sourceField) { - Annotation annotation = findAnnotation(sourceField.annotations, TypeIds.T_ConfiguredAnnotationNonNull); + Annotation annotation = findAnnotation(sourceField.annotations, TypeIds.BitNonNullAnnotation); int sourceStart = annotation != null ? annotation.sourceStart : sourceField.type.sourceStart; int sourceEnd = sourceField.type.sourceEnd; this.handle(IProblem.RedundantNullAnnotation, ProblemHandler.NoArgument, ProblemHandler.NoArgument, sourceStart, sourceEnd); } public void nullDefaultAnnotationIsRedundant(ASTNode location, Annotation[] annotations, Binding outer) { - Annotation annotation = findAnnotation(annotations, TypeIds.T_ConfiguredAnnotationNonNullByDefault); + Annotation annotation = findAnnotation(annotations, TypeIds.BitNonNullByDefaultAnnotation); int start = annotation != null ? annotation.sourceStart : location.sourceStart; int end = annotation != null ? annotation.sourceEnd : location.sourceStart; String[] args = NoArgument; @@ -14046,13 +14042,13 @@ public void conflictingInheritedNullAnnotations(ASTNode location, boolean previo public void illegalAnnotationForBaseType(TypeReference type, Annotation[] annotations, long nullAnnotationTagBit) { - int typeId = (nullAnnotationTagBit == TagBits.AnnotationNullable) - ? TypeIds.T_ConfiguredAnnotationNullable : TypeIds.T_ConfiguredAnnotationNonNull; + int typeBit = (nullAnnotationTagBit == TagBits.AnnotationNullable) + ? TypeIds.BitNullableAnnotation : TypeIds.BitNonNullAnnotation; char[][] annotationNames = (nullAnnotationTagBit == TagBits.AnnotationNonNull) ? this.options.nonNullAnnotationName : this.options.nullableAnnotationName; String[] args = new String[] { new String(annotationNames[annotationNames.length-1]), new String(type.getLastToken()) }; - Annotation annotation = findAnnotation(annotations, typeId); + Annotation annotation = findAnnotation(annotations, typeBit); int start = annotation != null ? annotation.sourceStart : type.sourceStart; int end = annotation != null ? annotation.sourceEnd : type.sourceEnd; this.handle(IProblem.IllegalAnnotationForBaseType, @@ -14114,12 +14110,12 @@ String internalAnnotatedTypeName(char[] annotationName, char[] typeName, int dim } return String.valueOf(fullName); } -private Annotation findAnnotation(Annotation[] annotations, int typeId) { +private Annotation findAnnotation(Annotation[] annotations, int typeBit) { if (annotations != null) { // should have a @NonNull/@Nullable annotation, search for it: int length = annotations.length; for (int j=0; j<length; j++) { - if (annotations[j].resolvedType != null && annotations[j].resolvedType.id == typeId) { + if (annotations[j].hasNullBit(typeBit)) { return annotations[j]; } } @@ -14261,6 +14257,27 @@ public void nullityMismatchTypeArgument(TypeBinding typeVariable, TypeBinding ty location.sourceEnd); } +public void cannotRedefineTypeArgumentNullity(TypeBinding typeVariable, Binding superElement, ASTNode location) { + String[] arguments = new String[2]; + String[] shortArguments = new String[2]; + arguments[0] = String.valueOf(typeVariable.nullAnnotatedReadableName(this.options, false)); + shortArguments[0] = String.valueOf(typeVariable.nullAnnotatedReadableName(this.options, true)); + if (superElement instanceof MethodBinding) { + ReferenceBinding declaringClass = ((MethodBinding) superElement).declaringClass; + arguments[1] = String.valueOf(CharOperation.concat(declaringClass.readableName(), superElement.shortReadableName(), '.')); + shortArguments[1] = String.valueOf(CharOperation.concat(declaringClass.shortReadableName(), superElement.shortReadableName(), '.')); + } else { + arguments[1] = String.valueOf(superElement.readableName()); + shortArguments[1] = String.valueOf(superElement.shortReadableName()); + } + this.handle( + IProblem.IllegalRedefinitionOfTypeVariable, + arguments, + shortArguments, + location.sourceStart, + location.sourceEnd); +} + public void implicitObjectBoundNoNullDefault(TypeReference reference) { this.handle(IProblem.ImplicitObjectBoundNoNullDefault, NoArgument, NoArgument, diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties index c1fdaa424..353784da1 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/problem/messages.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2000, 2014 IBM Corporation and others. +# Copyright (c) 2000, 2015 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 @@ -833,6 +833,7 @@ 972 = Illegal redefinition of parameter {0}, inherited method from {1} declares this parameter as ''{2}'' (mismatching null constraints) 973 = Contradictory null annotations: function type was inferred as ''{2} ({4})'', but only one of ''@{0}'' and ''@{1}'' can be effective at any location 974 = The return type is incompatible with the free type variable ''{1}'' returned from {0} (mismatching null constraints) +975 = Cannot redefine null constraints of type variable ''{0}'' declared in ''{1}'' # Java 8 1001 = Syntax error, modifiers and annotations are not allowed for the lambda parameter {0} as its type is elided |