Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

false positive contradictory null annotations since 2024-03 #2226

Merged
merged 1 commit into from Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -589,7 +589,7 @@ void faultInImports() {
this.typeOrPackageCache = new HashtableOfObject(length);
for (int i = 0; i < length; i++) {
ImportBinding binding = this.imports[i];
if (!binding.onDemand && binding.resolvedImport instanceof ReferenceBinding || binding instanceof ImportConflictBinding)
if (!binding.onDemand && binding.getResolvedBindingKind() == Binding.TYPE || binding instanceof ImportConflictBinding)
this.typeOrPackageCache.put(binding.getSimpleName(), binding);
}
this.skipCachingImports = this.environment.suppressImportErrors && unresolvedFound;
Expand Down Expand Up @@ -768,24 +768,34 @@ ImportBinding[] getDefaultImports() {
BinaryTypeBinding missingObject = this.environment.createMissingType(null, TypeConstants.JAVA_LANG_OBJECT);
importBinding = missingObject.fPackage;
}
ReferenceBinding templateSTR;
ImportBinding[] allImports = null;
if (this.environment.globalOptions.complianceLevel >= ClassFileConstants.JDK21) {
boolean old = this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled;
this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled = false;
templateSTR = (ReferenceBinding) ((PackageBinding) importBinding).getTypeOrPackage(TypeConstants.JAVA_LANG_STRING_TEMPLATE_STR[2], module(), false);
if (templateSTR != null) {
FieldBinding str = templateSTR.getField("STR".toCharArray(), true); //$NON-NLS-1$
ImportBinding ibinding = new ImportBinding(TypeConstants.JAVA_LANG_STRING_TEMPLATE_STR, false, str, null) {
@Override
public boolean isStatic() {
return true;
ImportBinding ibinding = new ImportBinding(TypeConstants.JAVA_LANG_STRING_TEMPLATE_STR, false, importBinding, null) {
@Override
public boolean isStatic() {
return true;
}
@Override
public int getResolvedBindingKind() {
return Binding.FIELD; // avoid resolving during faultInImports()
}
@Override
public Binding getResolvedImport() {
// resolve lazily:
Binding resolvedImport = super.getResolvedImport();
if (resolvedImport instanceof PackageBinding) { // package was past into the constructor, need to dig deeper now:
ReferenceBinding templateSTR = (ReferenceBinding) ((PackageBinding) resolvedImport).getTypeOrPackage(TypeConstants.JAVA_LANG_STRING_TEMPLATE_STR[2], module(), false);
if (templateSTR != null) {
FieldBinding fieldBinding = templateSTR.getField("STR".toCharArray(), true); //$NON-NLS-1$
if (fieldBinding != null)
return setResolvedImport(fieldBinding);
}
}
};
allImports = new ImportBinding[] {
new ImportBinding(TypeConstants.JAVA_LANG, true, importBinding, null), ibinding};
}
this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled = old;
return resolvedImport;
}
};
allImports = new ImportBinding[] {
new ImportBinding(TypeConstants.JAVA_LANG, true, importBinding, null), ibinding};
}
if (allImports == null){
allImports = new ImportBinding[] {new ImportBinding(TypeConstants.JAVA_LANG, true, importBinding, null)};
Expand Down Expand Up @@ -936,11 +946,14 @@ void recordTypeReferences(TypeBinding[] types) {
}
}
Binding resolveSingleImport(ImportBinding importBinding, int mask) {
if (importBinding.resolvedImport == null) {
importBinding.resolvedImport = findSingleImport(importBinding.compoundName, mask, importBinding.isStatic());
if (!importBinding.resolvedImport.isValidBinding() || importBinding.resolvedImport instanceof PackageBinding) {
if (importBinding.resolvedImport.problemId() == ProblemReasons.Ambiguous)
return importBinding.resolvedImport;
Binding resolvedBinding = importBinding.getResolvedImport();
if (resolvedBinding != null) {
return resolvedBinding;
} else {
resolvedBinding = importBinding.setResolvedImport(findSingleImport(importBinding.compoundName, mask, importBinding.isStatic()));
if (!resolvedBinding.isValidBinding() || resolvedBinding instanceof PackageBinding) {
if (resolvedBinding.problemId() == ProblemReasons.Ambiguous)
return resolvedBinding;
if (this.imports != null) {
ImportBinding[] newImports = new ImportBinding[this.imports.length - 1];
for (int i = 0, n = 0, max = this.imports.length; i < max; i++)
Expand All @@ -950,8 +963,8 @@ Binding resolveSingleImport(ImportBinding importBinding, int mask) {
}
return null;
}
return resolvedBinding;
}
return importBinding.resolvedImport;
}
public void storeDependencyInfo() {
// add the type hierarchy of each referenced supertype
Expand Down Expand Up @@ -1109,7 +1122,7 @@ private int checkAndRecordImportBinding(
recordImportBinding(new ImportBinding(compoundName, false, importBinding, importReference));
}
}
} else if (resolved.resolvedImport == referenceBinding) {
} else if (resolved.getResolvedImport() == referenceBinding) {
if (importReference.isStatic() != resolved.isStatic()) {
recordImportBinding(new ImportBinding(compoundName, false, importBinding, importReference));
}
Expand All @@ -1128,10 +1141,12 @@ private int checkAndRecordImportBinding(
// 7.5.3 says nothing about collision of single static imports and JDK8 tolerates them, though use is flagged.
for (int j = 0; j < this.importPtr; j++) {
ImportBinding resolved = this.tempImports[j];
if (resolved.isStatic() && resolved.resolvedImport instanceof ReferenceBinding && importBinding != resolved.resolvedImport) {
Binding resolvedImport = resolved.getResolvedImport();
if (resolved.isStatic()
&& resolvedImport instanceof ReferenceBinding type
&& importBinding != resolvedImport) {
if (CharOperation.equals(compoundName[compoundName.length - 1], resolved.compoundName[resolved.compoundName.length - 1])) {
ReferenceBinding type = (ReferenceBinding) resolved.resolvedImport;
resolved.resolvedImport = new ProblemReferenceBinding(new char[][] { name }, type, ProblemReasons.Ambiguous);
resolved.setResolvedImport(new ProblemReferenceBinding(new char[][] { name }, type, ProblemReasons.Ambiguous));
return -1;
}
}
Expand All @@ -1145,12 +1160,14 @@ private int checkAndRecordImportBinding(
for (int j = 0; j < this.importPtr; j++) {
ImportBinding resolved = this.tempImports[j];
// find other static fields with the same name
if (resolved.isStatic() && resolved.resolvedImport instanceof FieldBinding && importBinding != resolved.resolvedImport) {
Binding resolvedImport = resolved.getResolvedImport();
if (resolved.isStatic()
&& resolvedImport instanceof FieldBinding field
&& importBinding != resolvedImport) {
if (CharOperation.equals(name, resolved.compoundName[resolved.compoundName.length - 1])) {
if (compilerOptions().sourceLevel >= ClassFileConstants.JDK1_8) {
// 7.5.3 says nothing about collision of single static imports and JDK8 tolerates them, though use is flagged.
FieldBinding field = (FieldBinding) resolved.resolvedImport;
resolved.resolvedImport = new ProblemFieldBinding(field, field.declaringClass, name, ProblemReasons.Ambiguous);
resolved.setResolvedImport(new ProblemFieldBinding(field, field.declaringClass, name, ProblemReasons.Ambiguous));
return -1;
} else {
problemReporter().duplicateImport(importReference);
Expand Down
Expand Up @@ -21,7 +21,7 @@ public class ImportBinding extends Binding {
public boolean onDemand;
public ImportReference reference;

public Binding resolvedImport; // must ensure the import is resolved
private Binding resolvedImport;

public ImportBinding(char[][] compoundName, boolean isOnDemand, Binding binding, ImportReference reference) {
this.compoundName = compoundName;
Expand Down Expand Up @@ -54,6 +54,20 @@ public char[] readableName() {
else
return CharOperation.concatWith(this.compoundName, '.');
}
public int getResolvedBindingKind() {
if (this.resolvedImport == null)
return 0;
return this.resolvedImport.kind() & (Binding.TYPE | Binding.FIELD | Binding.METHOD);
}
public Binding getResolvedImport() {
return this.resolvedImport;
}

public Binding setResolvedImport(Binding resolvedImport) {
this.resolvedImport = resolvedImport;
return resolvedImport;
}

@Override
public String toString() {
return "import : " + new String(readableName()); //$NON-NLS-1$
Expand Down
Expand Up @@ -2233,8 +2233,10 @@ public Binding getBinding(char[] name, int mask, InvocationSite invocationSite,
for (ImportBinding importBinding : imports) {
if (importBinding.isStatic() && !importBinding.onDemand) {
if (CharOperation.equals(importBinding.getSimpleName(), name)) {
if (unitScope.resolveSingleImport(importBinding, Binding.TYPE | Binding.FIELD | Binding.METHOD) != null && importBinding.resolvedImport instanceof FieldBinding) {
foundField = (FieldBinding) importBinding.resolvedImport;
if (unitScope.resolveSingleImport(importBinding, Binding.TYPE | Binding.FIELD | Binding.METHOD) != null
&& importBinding.getResolvedImport() instanceof FieldBinding resolvedField)
{
foundField = resolvedField;
ImportReference importReference = importBinding.reference;
if (importReference != null && needResolve) {
importReference.bits |= ASTNode.Used;
Expand All @@ -2259,7 +2261,7 @@ public Binding getBinding(char[] name, int mask, InvocationSite invocationSite,
ReferenceBinding sourceCodeReceiver = null;
for (ImportBinding importBinding : imports) {
if (importBinding.isStatic() && importBinding.onDemand) {
Binding resolvedImport = importBinding.resolvedImport;
Binding resolvedImport = importBinding.getResolvedImport();
if (resolvedImport instanceof ReferenceBinding) {
ReferenceBinding importedReferenceBinding = (ReferenceBinding) resolvedImport;
FieldBinding temp = findField(importedReferenceBinding, name, invocationSite, needResolve);
Expand Down Expand Up @@ -2745,7 +2747,7 @@ public MethodBinding getImplicitMethod(char[] selector, TypeBinding[] argumentTy
boolean skipOnDemand = false; // set to true when matched static import of method name so stop looking for on demand methods
for (ImportBinding importBinding : imports) {
if (importBinding.isStatic()) {
Binding resolvedImport = importBinding.resolvedImport;
Binding resolvedImport = importBinding.getResolvedImport();
MethodBinding possible = null;
if (importBinding.onDemand) {
if (!skipOnDemand && resolvedImport instanceof ReferenceBinding) {
Expand Down Expand Up @@ -3519,13 +3521,14 @@ final Binding getTypeOrPackage(char[] name, int mask, boolean needResolve) {
if (cachedBinding instanceof ImportBinding) { // single type import cached in faultInImports(), replace it in the cache with the type
ImportBinding importBinding = (ImportBinding) cachedBinding;
ImportReference importReference = importBinding.reference;
if (importReference != null && !isUnnecessarySamePackageImport(importBinding.resolvedImport, unitScope)) {
Binding resolvedImport = importBinding.getResolvedImport();
if (importReference != null && !isUnnecessarySamePackageImport(resolvedImport, unitScope)) {
importReference.bits |= ASTNode.Used;
}
if (cachedBinding instanceof ImportConflictBinding)
typeOrPackageCache.put(name, cachedBinding = ((ImportConflictBinding) cachedBinding).conflictingTypeBinding); // already know its visible
else
typeOrPackageCache.put(name, cachedBinding = importBinding.resolvedImport); // already know its visible
typeOrPackageCache.put(name, cachedBinding = resolvedImport); // already know its visible
}
if ((mask & Binding.TYPE) != 0) {
if (foundType != null && foundType.problemId() != ProblemReasons.NotVisible && cachedBinding.problemId() != ProblemReasons.Ambiguous)
Expand All @@ -3549,7 +3552,7 @@ final Binding getTypeOrPackage(char[] name, int mask, boolean needResolve) {
if (resolvedImport == null) continue nextImport;
if (resolvedImport instanceof TypeBinding) {
ImportReference importReference = importBinding.reference;
if (importReference != null && !isUnnecessarySamePackageImport(importBinding.resolvedImport, unitScope))
if (importReference != null && !isUnnecessarySamePackageImport(importBinding.getResolvedImport(), unitScope))
importReference.bits |= ASTNode.Used;
return resolvedImport; // already know its visible
}
Expand Down Expand Up @@ -3581,7 +3584,7 @@ final Binding getTypeOrPackage(char[] name, int mask, boolean needResolve) {
ReferenceBinding type = null;
for (ImportBinding someImport : imports) {
if (someImport.onDemand) {
Binding resolvedImport = someImport.resolvedImport;
Binding resolvedImport = someImport.getResolvedImport();
ReferenceBinding temp = null;
if (resolvedImport instanceof PackageBinding) {
temp = findType(name, (PackageBinding) resolvedImport, currentPackage);
Expand Down
Expand Up @@ -19,6 +19,7 @@

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.tests.util.Util;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;

import junit.framework.Test;
Expand Down Expand Up @@ -999,6 +1000,8 @@ public class X {
}

public void testGH1964_since_22() {
if (this.complianceLevel < ClassFileConstants.JDK22)
return;
Runner runner = new Runner();
runner.customOptions = getCompilerOptions();
runner.customOptions.put(CompilerOptions.OPTION_EnablePreviews, CompilerOptions.ENABLED);
Expand Down
Binary file removed org.eclipse.jdt.core.tests.model/JCL/JclMin21.jar
Binary file not shown.
Binary file removed org.eclipse.jdt.core.tests.model/JCL/JclMin21src.zip
Binary file not shown.
Binary file added org.eclipse.jdt.core.tests.model/JCL/jclMin21.jar
Binary file not shown.
Binary file not shown.
Expand Up @@ -3713,18 +3713,18 @@ public void setUpJCLClasspathVariables(String compliance, boolean useFullJCL) th
}
} else if ("21".equals(compliance)) {
if (JavaCore.getClasspathVariable("JCL_21_LIB") == null) {
setupExternalJCL("jclMin17");
setupExternalJCL("jclMin21");
JavaCore.setClasspathVariables(
new String[] {"JCL_17_LIB", "JCL_17_SRC", "JCL_SRCROOT"},
new IPath[] {getExternalJCLPath("17"), getExternalJCLSourcePath("17"), getExternalJCLRootSourcePath()},
new String[] {"JCL_21_LIB", "JCL_21_SRC", "JCL_SRCROOT"},
new IPath[] {getExternalJCLPath("21"), getExternalJCLSourcePath("21"), getExternalJCLRootSourcePath()},
null);
}
} else if ("22".equals(compliance)) {
if (JavaCore.getClasspathVariable("JCL_22_LIB") == null) {
setupExternalJCL("jclMin17");
setupExternalJCL("jclMin21");
JavaCore.setClasspathVariables(
new String[] {"JCL_17_LIB", "JCL_17_SRC", "JCL_SRCROOT"},
new IPath[] {getExternalJCLPath("17"), getExternalJCLSourcePath("17"), getExternalJCLRootSourcePath()},
new String[] {"JCL_21_LIB", "JCL_21_SRC", "JCL_SRCROOT"},
new IPath[] {getExternalJCLPath("21"), getExternalJCLSourcePath("21"), getExternalJCLRootSourcePath()},
null);
}
} else {
Expand Down
Expand Up @@ -269,7 +269,10 @@ void setupJavaProject(String name, boolean useFullJCL, boolean addAnnotationLib)
}

void myCreateJavaProject(String name) throws CoreException {
this.project = createJavaProject(name, new String[]{"src"}, new String[]{this.jclLib}, null, null, "bin", null, null, null, this.compliance);
myCreateJavaProject(name, this.compliance, this.jclLib);
}
void myCreateJavaProject(String name, String projectCompliance, String projectJclLib) throws CoreException {
this.project = createJavaProject(name, new String[]{"src"}, new String[]{projectJclLib}, null, null, "bin", null, null, null, projectCompliance);
addLibraryEntry(this.project, this.ANNOTATION_LIB, false);
Map options = this.project.getOptions(true);
options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
Expand Down Expand Up @@ -3353,4 +3356,76 @@ Integer test() {
IProblem[] problems = reconciled.getProblems();
assertNoProblems(problems);
}
public void testGH2178() throws CoreException, IOException {
myCreateJavaProject("GH2178", "21", "JCL_21_LIB");
Map options = this.project.getOptions(true);
options.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, JavaCore.ERROR);
options.put(JavaCore.COMPILER_PB_NULL_ANNOTATION_INFERENCE_CONFLICT, JavaCore.ERROR);
this.project.setOptions(options);

addLibraryWithExternalAnnotations(this.project, "21", "jreext.jar", "annots", new String[] {
"/UnannotatedLib/lib/Objects.java",
"""
package lib;
public class Objects {
public static <T> T requireNonNull(T t) { return t; }
}
"""
}, null);
createFileInProject("annots/java/lang", "String.eea",
"""
class java/lang/String
valueOf
(Z)Ljava/lang/String;
(Z)L1java/lang/String;
""");
createFileInProject("annots/lib", "Objects.eea",
"""
class lib/Objects
requireNonNull
<T:Ljava/lang/Object;>(TT;)TT;
<T:Ljava/lang/Object;>(T0T;)T1T;
""");
addEeaToVariableEntry("JCL_21_LIB", "/GH2178/annots");


IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("repro", true, null);
ICompilationUnit unit = fragment.createCompilationUnit("ExternalNullAnnotationsConfusion.java",
"""
package repro;

import lib.Objects;

import org.eclipse.jdt.annotation.NonNull;

public class ExternalNullAnnotationsConfusion {

public String conflictingNonNullAndNullable() {

// String.valueOf() is annotated to return @NonNull
@NonNull
String valueOfAnnotatedNonNull = String.valueOf(false);

// Objects.requireNonNull is annotated to take a @Nullable parameter
@NonNull
String result = Objects.requireNonNull(valueOfAnnotatedNonNull);

// error marker at the last argument in the previous line:
// Contradictory null annotations:
// method was inferred as '@NonNull String requireNonNull(@Nullable @NonNull String)',
// but only one of '@NonNull' and '@Nullable' can be effective at any location
return result;
}
}
""",
true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
IProblem[] problems = reconciled.getProblems();
assertNoProblems(problems);

this.project.getProject().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, null);
IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
assertNoMarkers(markers);
}

}