Skip to content

Commit

Permalink
Use of eclipse external annotations (EEA) can cause false positive wa…
Browse files Browse the repository at this point in the history
…rning: Null type safety: generic needs unchecked conversion to conform to @nonnull type. (#1913)

fixes #1008

Improve propagation of annotations during inference
  • Loading branch information
stephan-herrmann committed Jan 25, 2024
1 parent 2c30f7e commit 06c7a67
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -1837,6 +1837,8 @@ public TypeBinding[] getNonWildcardParameters(Scope scope) {
types[i] = typeParameters[i].superclass; // assumably j.l.Object?
break;
}
if (types[i] != null)
types[i] = wildcard.propagateNonConflictingNullAnnotations(types[i]);
} else {
// If Ai is a type, then Ti = Ai.
types[i] = typeArgument;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,8 @@ TypeBinding substituteInferenceVariable(InferenceVariable var, TypeBinding subst
}
haveSubstitution |= currentOtherBounds != null;
if (haveSubstitution) {
return this.environment.createWildcard(this.genericType, this.rank, currentBound, currentOtherBounds, this.boundKind);
WildcardBinding wildcard = this.environment.createWildcard(this.genericType, this.rank, currentBound, currentOtherBounds, this.boundKind);
return propagateNonConflictingNullAnnotations(wildcard);
}
return this;
}
Expand Down Expand Up @@ -1092,4 +1093,21 @@ public long updateTagBits() {
public boolean isNonDenotable() {
return true;
}

/** When substituting this wildcard with 'type', perhaps null tagbits on this wildcard should be propagated. */
TypeBinding propagateNonConflictingNullAnnotations(TypeBinding type) {
if (!this.environment.usesNullTypeAnnotations())
return type;
if (type instanceof InferenceVariable) {
// just accumulate any hints:
((InferenceVariable) type).nullHints |= (this.tagBits & TagBits.AnnotationNullMASK);
return type;
}
if ((type.tagBits & TagBits.AnnotationNullMASK) != 0)
return type; // already annotated
AnnotationBinding[] annots = this.environment.nullAnnotationsFromTagBits(this.tagBits & TagBits.AnnotationNullMASK);
if (annots == null)
return type;
return this.environment.createAnnotatedType(type, annots);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3273,4 +3273,84 @@ public void testAnnotatedSourceSharesOutputFolder() throws CoreException {
prj1.getProject().delete(true, true , null);
}
}

public void testGH1008() throws Exception {
myCreateJavaProject("ValueOf");
Map options = this.project.getOptions(true);
options.put(JavaCore.COMPILER_PB_NULL_UNCHECKED_CONVERSION, JavaCore.ERROR);
this.project.setOptions(options);

addLibraryWithExternalAnnotations(this.project, "jreext.jar", "annots", new String[] {
"/UnannotatedLib/java/lang/Integer.java",
"""
package java.lang;
public class Integer {
public static Integer valueOf(String s) { return null; }
}
""",
"/UnannotatedLib/java/util/function/Function.java",
"""
package java.util.function;
public interface Function<T,R> {
R apply(T t);
}
"""
}, null);
createFileInProject("annots/java/lang", "Integer.eea",
"""
class java/lang/Integer
valueOf
(Ljava/lang/String;)Ljava/lang/Integer;
(L1java/lang/String;)L1java/lang/Integer;
""");

IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("test", true, null);
ICompilationUnit unit = fragment.createCompilationUnit("UncheckedConversionFalsePositive.java",
"""
package test;
import org.eclipse.jdt.annotation.Checks;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
public class UncheckedConversionFalsePositive {
public static @NonNull Integer doSomething(@NonNull final String someValue) {
return Integer.valueOf(someValue);
}
Integer test() {
final @Nullable String nullableString = "12";
@Nullable Integer result;
// This first example that uses my own annotated method and not eclipse external annotations
// works no problem...
result = Checks.applyIfNonNull(nullableString, UncheckedConversionFalsePositive::doSomething);
// But now, if I do this, which relies on EEA annotations instead of my in-code annotations....
result = Checks.applyIfNonNull(nullableString, Integer::valueOf);
// Then Integer::valueOf is flagged with the following message:
/*
* Null type safety: parameter 1 provided via method descriptor
* Function<String,Integer>.apply(String) needs unchecked conversion to conform to '@NonNull
* String'
*/
// Note that the same warning is shown on "someValue" below when using a lambda expression
// instead of a method reference.
result = Checks.applyIfNonNull(nullableString, someValue -> Integer.valueOf(someValue));
// The workaround to eliminate this warning without suppressing it is to make the
// generic parameters explicit with @NonNull but this is very verbose and should
// ideally be unnecessary...
result = Checks.<@NonNull String, Integer>applyIfNonNull(nullableString, Integer::valueOf);
return result;
}
}
""",
true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
IProblem[] problems = reconciled.getProblems();
assertNoProblems(problems);
}
}

0 comments on commit 06c7a67

Please sign in to comment.