From bdc449f24f99559d7c14357356d0f48bcea736fd Mon Sep 17 00:00:00 2001 From: Stephan Herrmann Date: Wed, 27 Nov 2024 00:18:33 +0100 Subject: [PATCH] Annotation-based analysis breaks when annotations are not on the build path + tag elements annotated with a missing type relevant for analysis + report the missing annotation at client sites when possibly relevant + also: treat Closeable lambda as fresh resource Fixes https://github.com/eclipse-jdt/eclipse.jdt.core/ --- JCL/jclMin23/src/java/io/InputStream.java | 4 +- .../src/java/lang/AssertionError.java | 18 + JCL/jclMin23/src/java/lang/AutoCloseable.java | 4 + .../lang/IncompatibleClassChangeError.java | 2 + JCL/jclMin23/src/java/lang/LinkageError.java | 4 +- .../src/java/lang/NoClassDefFoundError.java | 2 + .../src/java/lang/NoSuchFieldError.java | 2 + .../java/lang/invoke/LambdaMetafactory.java | 3 + .../META-INF/MANIFEST.MF | 2 +- .../eclipse/jdt/core/compiler/IProblem.java | 6 + .../jdt/internal/compiler/ast/ASTNode.java | 27 + .../compiler/ast/FakedTrackingVariable.java | 25 +- .../jdt/internal/compiler/ast/Invocation.java | 6 +- .../compiler/lookup/ExtendedTagBits.java | 2 + .../compiler/lookup/FieldBinding.java | 3 +- .../compiler/lookup/LookupEnvironment.java | 19 + .../compiler/lookup/MethodBinding.java | 12 + .../jdt/internal/compiler/lookup/TypeIds.java | 2 + .../compiler/problem/ProblemReporter.java | 53 ++ .../compiler/problem/messages.properties | 3 + .../regression/CompilerInvocationTests.java | 6 + .../ResourceLeakAnnotatedTests.java | 129 +++++ .../JCL/jclMin23.jar | Bin 29552 -> 30936 bytes .../JCL/jclMin23src.zip | Bin 21602 -> 22562 bytes .../core/tests/model/AllJavaModelTests.java | 3 + .../model/OwningAnnotationModelTests.java | 519 ++++++++++++++++++ 26 files changed, 846 insertions(+), 10 deletions(-) create mode 100644 JCL/jclMin23/src/java/lang/AssertionError.java create mode 100644 JCL/jclMin23/src/java/lang/AutoCloseable.java create mode 100644 JCL/jclMin23/src/java/lang/invoke/LambdaMetafactory.java create mode 100644 org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/OwningAnnotationModelTests.java diff --git a/JCL/jclMin23/src/java/io/InputStream.java b/JCL/jclMin23/src/java/io/InputStream.java index 12017e76a4b..ff133a644a7 100644 --- a/JCL/jclMin23/src/java/io/InputStream.java +++ b/JCL/jclMin23/src/java/io/InputStream.java @@ -1,5 +1,5 @@ package java.io; -public class InputStream { - +public class InputStream implements AutoCloseable { + public void close() {} } diff --git a/JCL/jclMin23/src/java/lang/AssertionError.java b/JCL/jclMin23/src/java/lang/AssertionError.java new file mode 100644 index 00000000000..c2032d58df8 --- /dev/null +++ b/JCL/jclMin23/src/java/lang/AssertionError.java @@ -0,0 +1,18 @@ +package java.lang; + +public class AssertionError extends Error { + + static final long serialVersionUID = 1L; + + public AssertionError(java.lang.String s) { + super(s); + } + + public AssertionError(java.lang.String s, java.lang.Throwable cause) { + super(s, cause); + } + + public AssertionError() { + } + +} diff --git a/JCL/jclMin23/src/java/lang/AutoCloseable.java b/JCL/jclMin23/src/java/lang/AutoCloseable.java new file mode 100644 index 00000000000..46c293d3be9 --- /dev/null +++ b/JCL/jclMin23/src/java/lang/AutoCloseable.java @@ -0,0 +1,4 @@ +package java.lang; +public interface AutoCloseable { + void close() throws Exception; +} \ No newline at end of file diff --git a/JCL/jclMin23/src/java/lang/IncompatibleClassChangeError.java b/JCL/jclMin23/src/java/lang/IncompatibleClassChangeError.java index b8e171ea5b6..eaa29701a53 100644 --- a/JCL/jclMin23/src/java/lang/IncompatibleClassChangeError.java +++ b/JCL/jclMin23/src/java/lang/IncompatibleClassChangeError.java @@ -3,6 +3,8 @@ public class IncompatibleClassChangeError extends LinkageError { + static final long serialVersionUID = 1L; + public IncompatibleClassChangeError () { super(); } diff --git a/JCL/jclMin23/src/java/lang/LinkageError.java b/JCL/jclMin23/src/java/lang/LinkageError.java index 72ee8ffbd11..26dc1290549 100644 --- a/JCL/jclMin23/src/java/lang/LinkageError.java +++ b/JCL/jclMin23/src/java/lang/LinkageError.java @@ -3,7 +3,9 @@ public class LinkageError extends Error { - public LinkageError() { + static final long serialVersionUID = 1L; + + public LinkageError() { super(); } diff --git a/JCL/jclMin23/src/java/lang/NoClassDefFoundError.java b/JCL/jclMin23/src/java/lang/NoClassDefFoundError.java index 80b2dfed6d6..790f4e5b4ed 100644 --- a/JCL/jclMin23/src/java/lang/NoClassDefFoundError.java +++ b/JCL/jclMin23/src/java/lang/NoClassDefFoundError.java @@ -3,6 +3,8 @@ public class NoClassDefFoundError extends LinkageError { + static final long serialVersionUID = 1L; + public NoClassDefFoundError() { super(); } diff --git a/JCL/jclMin23/src/java/lang/NoSuchFieldError.java b/JCL/jclMin23/src/java/lang/NoSuchFieldError.java index 0ce8c8ebaf4..db00e4f6cab 100644 --- a/JCL/jclMin23/src/java/lang/NoSuchFieldError.java +++ b/JCL/jclMin23/src/java/lang/NoSuchFieldError.java @@ -3,6 +3,8 @@ public class NoSuchFieldError extends IncompatibleClassChangeError { + static final long serialVersionUID = 1L; + public NoSuchFieldError() { super(); } diff --git a/JCL/jclMin23/src/java/lang/invoke/LambdaMetafactory.java b/JCL/jclMin23/src/java/lang/invoke/LambdaMetafactory.java new file mode 100644 index 00000000000..b8d89314345 --- /dev/null +++ b/JCL/jclMin23/src/java/lang/invoke/LambdaMetafactory.java @@ -0,0 +1,3 @@ +package java.lang.invoke; +public class LambdaMetafactory { +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.compiler.batch/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.compiler.batch/META-INF/MANIFEST.MF index 53cc25974bc..d501f898fb4 100644 --- a/org.eclipse.jdt.core.compiler.batch/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.core.compiler.batch/META-INF/MANIFEST.MF @@ -3,7 +3,7 @@ Main-Class: org.eclipse.jdt.internal.compiler.batch.Main Bundle-ManifestVersion: 2 Bundle-Name: Eclipse Compiler for Java(TM) Bundle-SymbolicName: org.eclipse.jdt.core.compiler.batch -Bundle-Version: 3.40.100.qualifier +Bundle-Version: 3.41.0.qualifier Bundle-ClassPath: . Bundle-Vendor: Eclipse.org Automatic-Module-Name: org.eclipse.jdt.core.compiler.batch diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java index 42d2fd9d5de..37304653708 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java @@ -1995,6 +1995,12 @@ public interface IProblem { * @since 3.36 */ int NullityUncheckedTypeAnnotation = Internal + 986; + /** @since 3.40 */ + int MessageSendWithUnresolvedOwningAnnotation = Internal + 987; + /** @since 3.40 */ + int ParameterWithUnresolvedOwningAnnotation = Internal + 988; + /** @since 3.40 */ + int FieldWithUnresolvedOwningAnnotation = Internal + 989; // Java 8 work diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java index 80050df0af0..a23949f1d3d 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java @@ -1000,6 +1000,7 @@ public static void resolveAnnotations(BlockScope scope, Annotation[] sourceAnnot if (annotations != null) { annotations[i] = annotation.getCompilerAnnotation(); } + tagMissingAnalysisAnnotation(scope, recipient, annotation); } } @@ -1075,6 +1076,32 @@ public static void resolveAnnotations(BlockScope scope, Annotation[] sourceAnnot return annotations; } + private static void tagMissingAnalysisAnnotation(Scope scope, Binding recipient, Annotation annotation) { + long extendedTagBits = scope.environment().checkForMissingAnalysisAnnotation(annotation.resolvedType); + if (extendedTagBits != 0) { + if (recipient instanceof MethodBinding method) { + method.extendedTagBits |= extendedTagBits; + } else if (recipient instanceof VariableBinding variable) { + if (extendedTagBits == ExtendedTagBits.HasMissingOwningAnnotation + && scope instanceof MethodScope methodScope + && variable.isParameter() + && variable instanceof LocalVariableBinding local + && local.declaration instanceof Argument arg) + { + Argument[] arguments = methodScope.referenceMethod().arguments; + for (int j=0;j < arguments.length;j++){ + if (arguments[j] == arg) { + methodScope.referenceMethodBinding().original().markMissingOwningAnnotationOnParameter(j); + break; + } + } + } else if (variable instanceof FieldBinding field) { + field.extendedTagBits |= extendedTagBits; + } + } + } + } + /** Resolve JSR308 annotations on a type reference, array creation expression or a wildcard. Type parameters go directly to the method/ctor, By construction the bindings associated with QTR, PQTR etc get resolved first and then annotations for different levels get resolved and applied at one go. Likewise for multidimensional arrays. diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java index 56ce1d92bf5..f48813dd78f 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java @@ -342,6 +342,18 @@ public static FakedTrackingVariable preConnectTrackerAcrossAssignment(ASTNode lo } return closeTracker; } + static void checkMethodForMissingAnnotation(MessageSend messageSend, Scope scope) { + if ((messageSend.binding.original().extendedTagBits & ExtendedTagBits.HasMissingOwningAnnotation) != 0) + scope.problemReporter().messageWithUnresolvedOwningAnnotation(messageSend, scope.environment()); + } + static void checkParameterForMissingAnnotation(ASTNode location, MethodBinding method, int rank, Scope scope) { + if (method != null && method.parameterHasMissingOwningAnnotation(rank)) + scope.problemReporter().parameterWithUnresolvedOwningAnnotation(location, method.original(), rank, scope.environment()); + } + static void checkFieldForMissingAnnotation(ASTNode location, FieldBinding fieldBinding, Scope scope) { + if (fieldBinding != null && (fieldBinding.extendedTagBits & ExtendedTagBits.HasMissingOwningAnnotation) != 0) + scope.problemReporter().fieldWithUnresolvedOwningAnnotation(location, fieldBinding, scope.environment()); + } private static boolean containsAllocation(SwitchExpression location) { for (Expression re : location.resultExpressions()) { @@ -510,6 +522,7 @@ public static FlowInfo analyseCloseableAcquisition(BlockScope scope, FlowInfo fl } else { // regular resource FakedTrackingVariable tracker = acquisition.closeTracker; if (scope.compilerOptions().isAnnotationBasedResourceAnalysisEnabled) { + checkMethodForMissingAnnotation(acquisition, scope); long owningTagBits = acquisition.binding.tagBits & TagBits.AnnotationOwningMASK; int initialNullStatus = (owningTagBits == TagBits.AnnotationNotOwning) ? FlowInfo.NON_NULL : FlowInfo.NULL; if (tracker == null) { @@ -839,6 +852,7 @@ public static void handleResourceFieldAssignment(BlockScope scope, FlowInfo flow field = fieldReference.binding; } if (field != null&& (field.tagBits & TagBits.AnnotationNotOwning) == 0) { // assignment to @NotOwned has no meaning + checkFieldForMissingAnnotation(lhs, field, scope); if ((field.tagBits & TagBits.AnnotationOwning) != 0) { rhsTrackVar.markNullStatus(flowInfo, flowContext, FlowInfo.NON_NULL); } else { @@ -914,6 +928,7 @@ else if (expression instanceof CastExpression) if (useAnnotations && (expression.bits & RestrictiveFlagMASK) == Binding.FIELD) { // field read FieldBinding fieldBinding = ((Reference) expression).lastFieldBinding(); + checkFieldForMissingAnnotation(expression, fieldBinding, scope); long owningBits = 0; if (fieldBinding != null) { owningBits = fieldBinding.getAnnotationTagBits() & TagBits.AnnotationOwningMASK; @@ -946,7 +961,7 @@ else if (expression instanceof CastExpression) if (isBlacklistedMethod(expression)) { initialNullStatus = FlowInfo.NULL; } else if (useAnnotations) { - initialNullStatus = getNullStatusFromMessageSend(expression); + initialNullStatus = getNullStatusFromMessageSend(expression, scope); } if (initialNullStatus != 0) return new FakedTrackingVariable(local, location, flowInfo, flowContext, initialNullStatus, useAnnotations); @@ -968,6 +983,9 @@ else if (expression instanceof CastExpression) // leave state as UNKNOWN, the bit OWNED_BY_OUTSIDE will prevent spurious warnings return tracker; } + } else if (expression instanceof LambdaExpression) { + // treat fresh lambda like a fresh resource + return new FakedTrackingVariable(local, location, flowInfo, flowContext, FlowInfo.NULL, useAnnotations); } if (local.closeTracker != null) @@ -992,8 +1010,9 @@ private static boolean isBlacklistedMethod(Expression expression) { } /* pre: usesOwningAnnotations. */ - protected static int getNullStatusFromMessageSend(Expression expression) { - if (expression instanceof MessageSend) { + protected static int getNullStatusFromMessageSend(Expression expression, Scope scope) { + if (expression instanceof MessageSend message) { + checkMethodForMissingAnnotation(message, scope); if ((((MessageSend) expression).binding.tagBits & TagBits.AnnotationNotOwning) != 0) return FlowInfo.NON_NULL; return FlowInfo.NULL; // per default assume responsibility to close diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Invocation.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Invocation.java index 2e84117b5f1..49400b9421f 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Invocation.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Invocation.java @@ -60,9 +60,11 @@ default FlowInfo handleResourcePassedToInvocation(BlockScope currentScope, Metho if (currentScope.compilerOptions().isAnnotationBasedResourceAnalysisEnabled) { FakedTrackingVariable trackVar = FakedTrackingVariable.getCloseTrackingVariable(argument, flowInfo, flowContext, true); if (trackVar != null) { - if (methodBinding.ownsParameter(rank)) { + int safeRank = Math.min(rank, methodBinding.parameters.length-1); // account for varargs + FakedTrackingVariable.checkParameterForMissingAnnotation(argument, methodBinding, safeRank, currentScope); + if (methodBinding.ownsParameter(safeRank)) { trackVar.markOwnedByOutside(flowInfo, flowContext); - } else if (methodBinding.notownsParameter(rank)) { + } else if (methodBinding.notownsParameter(safeRank)) { // ignore, no relevant change } else { trackVar.markAsShared(); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ExtendedTagBits.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ExtendedTagBits.java index e7b7b5d32b5..5d6f3b15789 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ExtendedTagBits.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ExtendedTagBits.java @@ -31,4 +31,6 @@ public interface ExtendedTagBits { // @Owning / closing int IsClosingMethod = ASTNode.Bit1; // method + + int HasMissingOwningAnnotation = ASTNode.Bit2; // method/ctor or field } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/FieldBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/FieldBinding.java index eaafde60124..22cc530ee5e 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/FieldBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/FieldBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2020 IBM Corporation and others. + * Copyright (c) 2000, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -38,6 +38,7 @@ public class FieldBinding extends VariableBinding { public int compoundUseFlag = 0; // number or accesses via postIncrement or compoundAssignment public FakedTrackingVariable closeTracker; + public long extendedTagBits; protected FieldBinding() { super(null, null, 0, null); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java index fef2eb95c2f..87bc8b26ca4 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/LookupEnvironment.java @@ -1656,6 +1656,25 @@ int getAnalysisAnnotationBit(char[][] qualifiedTypeName) { Integer typeBit = this.allAnalysisAnnotations.get(qualifiedTypeString); return typeBit == null ? 0 : typeBit; } +/** + * Check if the given type is a missing type that could be relevant for static analysis. + * @return A bit from {@link ExtendedTagBits} encoding the check result, or {@code 0}. + */ +public long checkForMissingAnalysisAnnotation(TypeBinding resolvedType) { + if (resolvedType instanceof MissingTypeBinding missing) { + if ((getAnalysisAnnotationBit(missing.compoundName) & TypeIds.BitAnyOwningAnnotation) != 0) + return ExtendedTagBits.HasMissingOwningAnnotation; + char[] simpleName = missing.compoundName[missing.compoundName.length-1]; + if (matchesSimpleName(simpleName, this.globalOptions.owningAnnotationName) + || matchesSimpleName(simpleName, this.globalOptions.notOwningAnnotationName)) + return ExtendedTagBits.HasMissingOwningAnnotation; + } + return 0; +} +private boolean matchesSimpleName(char[] simpleName, char[][] qualifiedName) { + return CharOperation.equals(simpleName, qualifiedName[qualifiedName.length-1]); +} + public boolean isNullnessAnnotationPackage(PackageBinding pkg) { return this.nonnullAnnotationPackage == pkg || this.nullableAnnotationPackage == pkg || this.nonnullByDefaultAnnotationPackage == pkg; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java index b2e0e94bde0..28cc9c3ac24 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/MethodBinding.java @@ -78,6 +78,7 @@ public class MethodBinding extends Binding { public static byte PARAM_NULLITY = (byte) (PARAM_NONNULL | PARAM_NULLABLE); public static byte PARAM_OWNING = 4; public static byte PARAM_NOTOWNING = 8; + public static byte PARAM_MISSING_OWNING_ANN = 16; public static byte flowBitFromAnnotationTagBit(long tagBit) { if (tagBit == TagBits.AnnotationNonNull) @@ -1509,6 +1510,17 @@ public boolean notownsParameter(int i) { return (this.parameterFlowBits[i] & PARAM_NOTOWNING) != 0; return false; } +public boolean parameterHasMissingOwningAnnotation(int rank) { + if (this.parameterFlowBits != null) + return (this.parameterFlowBits[rank] & PARAM_MISSING_OWNING_ANN) != 0; + return false; +} +public void markMissingOwningAnnotationOnParameter(int rank) { + if (this.parameterFlowBits == null) + this.parameterFlowBits = new byte[this.parameters.length]; + this.parameterFlowBits[rank] |= PARAM_MISSING_OWNING_ANN; +} + /** @return TRUE means @NonNull declared, FALSE means @Nullable declared, null means nothing declared */ public Boolean getParameterNullness(int idx) { if (this.parameterFlowBits != null) { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java index e0c4a672010..8a503d5c7cd 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeIds.java @@ -289,6 +289,8 @@ public interface TypeIds { /** Mark the type as notowning-annotation for resource analysis. */ final int BitNotOwningAnnotation = 4096; + final int BitAnyOwningAnnotation = BitOwningAnnotation | BitNotOwningAnnotation; + /** * Set of type bits that should be inherited by any sub types. */ diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java index 0a838e896cc..d44a5e6dcb5 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java @@ -6410,6 +6410,59 @@ public void nullAnnotationUnsupportedLocation(TypeReference type) { handle(IProblem.NullAnnotationUnsupportedLocationAtType, NoArgument, NoArgument, type.sourceStart, sourceEnd); } +private char[][] missingAnalysisAnnotationName(AnnotationBinding[] annotations, LookupEnvironment environment) { + for (AnnotationBinding annotationBinding : annotations) { + if (annotationBinding.getAnnotationType() instanceof ReferenceBinding type + && environment.checkForMissingAnalysisAnnotation(type) != 0) + return type.compoundName; + } + return null; +} +public void messageWithUnresolvedOwningAnnotation(MessageSend send, LookupEnvironment environment) { + char[][] compoundName = missingAnalysisAnnotationName(send.binding.original().getAnnotations(), environment); + if (compoundName == null) + return; + String selector = String.valueOf(send.selector); + handle(IProblem.MessageSendWithUnresolvedOwningAnnotation, + new String[] {selector, CharOperation.toString(compoundName)}, + new String[] {selector, String.valueOf(compoundName[compoundName.length-1])}, + ProblemSeverities.Warning, + send.sourceStart, + send.nameSourceEnd()); +} +public void parameterWithUnresolvedOwningAnnotation(ASTNode location, MethodBinding method, int rank, LookupEnvironment environment) { + // locate the missing annotation on parameter #rank: + AnnotationBinding[][] parameterAnnotations = method.original().getParameterAnnotations(); + char[][] compoundName = missingAnalysisAnnotationName(parameterAnnotations[rank], environment); + if (compoundName == null) + return; + String parameterName = method.parameterNames != Binding.NO_PARAMETER_NAMES + ? String.valueOf(method.parameterNames[rank]) + : "arg"+rank; //$NON-NLS-1$ + String[] args = { + String.valueOf(parameterName), + String.valueOf(method.selector), + CharOperation.toString(compoundName) + }; + handle(IProblem.ParameterWithUnresolvedOwningAnnotation, + args, + args, + ProblemSeverities.Warning, + location.sourceStart, + location.sourceEnd); +} +public void fieldWithUnresolvedOwningAnnotation(ASTNode location, FieldBinding fieldBinding, LookupEnvironment environment) { + char[][] compoundName = missingAnalysisAnnotationName(fieldBinding.getAnnotations(), environment); + if (compoundName == null) + return; + String selector = String.valueOf(fieldBinding.name); + handle(IProblem.FieldWithUnresolvedOwningAnnotation, + new String[] {selector, CharOperation.toString(compoundName)}, + new String[] {selector, String.valueOf(compoundName[compoundName.length-1])}, + ProblemSeverities.Warning, + location.sourceStart, + location.sourceEnd); +} public void localVariableNullInstanceof(LocalVariableBinding local, ASTNode location) { int severity = computeSeverity(IProblem.NullLocalVariableInstanceofYieldsFalse); if (severity == ProblemSeverities.Ignore) return; diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties index b1d22ac8767..f928348c096 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties @@ -870,6 +870,9 @@ 985 = This array dimension with declared element type {0} will be initialized with ''null'' entries # same text as 955: 986 = Null type safety (type annotations): The expression of type ''{1}'' needs unchecked conversion to conform to ''{0}'' +987 = Method ''{0}'' has an unresolved annotation ''{1}'' that could be relevant for static analysis +988 = Parameter ''{0}'' of method ''{1}'' has an unresolved annotation ''{2}'' that could be relevant for static analysis +989 = Field ''{0}'' has an unresolved annotation ''{1}'' that could be relevant for static analysis # Java 8 1001 = Syntax error, modifiers and annotations are not allowed for the lambda parameter {0} as its type is elided diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java index f0ecad9241e..96c45a4075c 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java @@ -522,6 +522,7 @@ class ProblemAttributes { expectedProblemAttributes.put("FieldTypeInternalNameProvided", DEPRECATED); expectedProblemAttributes.put("FieldTypeNotFound", DEPRECATED); expectedProblemAttributes.put("FieldTypeNotVisible", DEPRECATED); + expectedProblemAttributes.put("FieldWithUnresolvedOwningAnnotation", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); expectedProblemAttributes.put("FinalBoundForTypeVariable", new ProblemAttributes(CategorizedProblem.CAT_CODE_STYLE)); expectedProblemAttributes.put("FinalFieldAssignment", new ProblemAttributes(CategorizedProblem.CAT_MEMBER)); expectedProblemAttributes.put("FinalMethodCannotBeOverridden", new ProblemAttributes(CategorizedProblem.CAT_MEMBER)); @@ -811,6 +812,7 @@ class ProblemAttributes { expectedProblemAttributes.put("MaskedCatch", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); expectedProblemAttributes.put("MandatoryCloseNotShown", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); expectedProblemAttributes.put("MandatoryCloseNotShownAtExit", new ProblemAttributes(CategorizedProblem.CAT_POTENTIAL_PROGRAMMING_PROBLEM)); + expectedProblemAttributes.put("MessageSendWithUnresolvedOwningAnnotation", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); expectedProblemAttributes.put("MethodButWithConstructorName", new ProblemAttributes(CategorizedProblem.CAT_CODE_STYLE)); expectedProblemAttributes.put("MethodCanBePotentiallyStatic", new ProblemAttributes(CategorizedProblem.CAT_CODE_STYLE)); expectedProblemAttributes.put("MethodCanBeStatic", new ProblemAttributes(CategorizedProblem.CAT_CODE_STYLE)); @@ -951,6 +953,7 @@ class ProblemAttributes { expectedProblemAttributes.put("ParameterMismatch", new ProblemAttributes(CategorizedProblem.CAT_MEMBER)); expectedProblemAttributes.put("ParameterizedConstructorArgumentTypeMismatch", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); expectedProblemAttributes.put("ParameterizedMethodArgumentTypeMismatch", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); + expectedProblemAttributes.put("ParameterWithUnresolvedOwningAnnotation", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); expectedProblemAttributes.put("ParsingError", new ProblemAttributes(CategorizedProblem.CAT_SYNTAX)); expectedProblemAttributes.put("ParsingErrorDeleteToken", new ProblemAttributes(CategorizedProblem.CAT_SYNTAX)); expectedProblemAttributes.put("ParsingErrorDeleteTokens", new ProblemAttributes(CategorizedProblem.CAT_SYNTAX)); @@ -1656,6 +1659,7 @@ class ProblemAttributes { expectedProblemAttributes.put("FieldTypeInternalNameProvided", SKIP); expectedProblemAttributes.put("FieldTypeNotFound", SKIP); expectedProblemAttributes.put("FieldTypeNotVisible", SKIP); + expectedProblemAttributes.put("FieldWithUnresolvedOwningAnnotation", SKIP); expectedProblemAttributes.put("FinalBoundForTypeVariable", new ProblemAttributes(JavaCore.COMPILER_PB_FINAL_PARAMETER_BOUND)); expectedProblemAttributes.put("FinalFieldAssignment", SKIP); expectedProblemAttributes.put("FinalMethodCannotBeOverridden", SKIP); @@ -1947,6 +1951,7 @@ class ProblemAttributes { expectedProblemAttributes.put("MaskedCatch", new ProblemAttributes(JavaCore.COMPILER_PB_HIDDEN_CATCH_BLOCK)); expectedProblemAttributes.put("MandatoryCloseNotShown", new ProblemAttributes(JavaCore.COMPILER_PB_UNCLOSED_CLOSEABLE)); expectedProblemAttributes.put("MandatoryCloseNotShownAtExit", new ProblemAttributes(JavaCore.COMPILER_PB_UNCLOSED_CLOSEABLE)); + expectedProblemAttributes.put("MessageSendWithUnresolvedOwningAnnotation", SKIP); expectedProblemAttributes.put("MethodButWithConstructorName", new ProblemAttributes(JavaCore.COMPILER_PB_METHOD_WITH_CONSTRUCTOR_NAME)); expectedProblemAttributes.put("MethodCanBePotentiallyStatic", new ProblemAttributes(JavaCore.COMPILER_PB_POTENTIALLY_MISSING_STATIC_ON_METHOD)); expectedProblemAttributes.put("MethodCanBeStatic", new ProblemAttributes(JavaCore.COMPILER_PB_MISSING_STATIC_ON_METHOD)); @@ -2087,6 +2092,7 @@ class ProblemAttributes { expectedProblemAttributes.put("ParameterMismatch", SKIP); expectedProblemAttributes.put("ParameterizedConstructorArgumentTypeMismatch", SKIP); expectedProblemAttributes.put("ParameterizedMethodArgumentTypeMismatch", SKIP); + expectedProblemAttributes.put("ParameterWithUnresolvedOwningAnnotation", SKIP); expectedProblemAttributes.put("ParsingError", SKIP); expectedProblemAttributes.put("ParsingErrorDeleteToken", SKIP); expectedProblemAttributes.put("ParsingErrorDeleteTokens", SKIP); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakAnnotatedTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakAnnotatedTests.java index 0b3d03ee0d2..ddc0e3da95e 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakAnnotatedTests.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakAnnotatedTests.java @@ -13,9 +13,11 @@ *******************************************************************************/ package org.eclipse.jdt.core.tests.compiler.regression; +import java.io.File; import java.util.Map; import junit.framework.Test; import junit.framework.TestSuite; +import org.eclipse.jdt.core.tests.util.Util; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; @@ -1708,4 +1710,131 @@ public class NotOwningTest { "", options); } +public void testGH3278_OK() { + Map options = getCompilerOptions(); + options.put(CompilerOptions.OPTION_ReportPotentiallyUnclosedCloseable, CompilerOptions.ERROR); + options.put(CompilerOptions.OPTION_ReportUnclosedCloseable, CompilerOptions.ERROR); + runLeakTestWithAnnotations(new String[] { + "annotated/TestNotOwning.java", + """ + package annotated; + + import java.io.Closeable; + import java.io.IOException; + import java.util.ArrayList; + import java.util.List; + + import org.eclipse.jdt.annotation.NotOwning; + import org.eclipse.jdt.annotation.Owning; + + public class TestNotOwning implements Closeable { + private final List toClose = new ArrayList<>(); + + @NotOwning + public T register(@Owning + T closeable) throws IOException { + closeable.close(); + return closeable; + } + + @Override + public void close() throws IOException { + for (Closeable closeable : toClose.reversed()) { + closeable.close(); // Ignore error handling for this demonstration + } + } + + public static void client() throws IOException { + try (TestNotOwning t = new TestNotOwning()) { + Closeable a = () -> {} ; // produces warning + Closeable b = t.register(() -> {} ); // produces no warning + assert a != null; + assert b != null; + } + } + } + """ + }, + "----------\n" + + "1. ERROR in annotated\\TestNotOwning.java (at line 30)\n" + + " Closeable a = () -> {} ; // produces warning\n" + + " ^\n" + + "Resource leak: \'a\' is never closed\n" + + "----------\n", + options); +} +public void testGH3278_missingAnnotations() { + runLeakTestWithAnnotations(new String[] { + "annotated/TestNotOwning.java", + """ + package annotated; + + import java.io.Closeable; + import java.io.IOException; + import java.util.ArrayList; + import java.util.List; + + import org.eclipse.jdt.annotation.NotOwning; + import org.eclipse.jdt.annotation.Owning; + + public class TestNotOwning implements Closeable { + private final List toClose = new ArrayList<>(); + + @NotOwning + public T register(@Owning + T closeable) throws IOException { + closeable.close(); + return closeable; + } + + @Override + public void close() throws IOException { + for (Closeable closeable : toClose.reversed()) { + closeable.close(); // Ignore error handling for this demonstration + } + } + } + """ + }, + "", + getCompilerOptions()); + Util.delete(new File(OUTPUT_DIR, "org/eclipse/jdt/annotation/Owning.class".replaceAll("/", File.separator))); + Util.delete(new File(OUTPUT_DIR, "org/eclipse/jdt/annotation/NotOwning.class".replaceAll("/", File.separator))); + + Map options = getCompilerOptions(); + options.put(CompilerOptions.OPTION_ReportPotentiallyUnclosedCloseable, CompilerOptions.ERROR); + options.put(CompilerOptions.OPTION_ReportUnclosedCloseable, CompilerOptions.ERROR); + options.put(CompilerOptions.OPTION_AnnotationBasedResourceAnalysis, CompilerOptions.ENABLED); + runLeakTest(new String[] { + "client/TestingAnnotated.java", + """ + package client; + + import java.io.Closeable; + import java.io.IOException; + + import annotated.TestNotOwning; + + public class TestingAnnotated { + + public static void client() throws IOException { + try (TestNotOwning t = new TestNotOwning()) { + Closeable a = () -> {} ; // produces warning + Closeable b = t.register(() -> {} ); // produces no warning + assert a != null; + assert b != null; + } + } + } + """ + }, + "----------\n" + + "1. ERROR in client\\TestingAnnotated.java (at line 12)\n" + + " Closeable a = () -> {} ; // produces warning\n" + + " ^\n" + + "Resource leak: \'a\' is never closed\n" + + "----------\n", + options, + false); +} } diff --git a/org.eclipse.jdt.core.tests.model/JCL/jclMin23.jar b/org.eclipse.jdt.core.tests.model/JCL/jclMin23.jar index 1c543708419b9302c0501c39479a25532577c35e..281e5bbd8b8cc49ea846a3998c4146a86b7a1a30 100644 GIT binary patch delta 5433 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 diff --git a/org.eclipse.jdt.core.tests.model/JCL/jclMin23src.zip b/org.eclipse.jdt.core.tests.model/JCL/jclMin23src.zip index e4f0a35959a177781a2fa2ed757bbd03618b59f8..04ffe6be61a615c687a3809697761750ca6569aa 100644 GIT binary patch delta 2331 zcmaJ>2~bm67XAMPj9>^`*b;%zh(aJHKs(Zim_PyvNgx41a2ui`wz4SVzJ%yViy(qm zx@KHZP*71Y5w+D8C1}&&0wQH=y8e+t>jgx$+yX za~q^L6gRxR;+Fbw8&@?o@*)-TtJ2sbT~9my(ZjjcEXNm{UR|_^GyTr%4BWap4sme} zaYBM2@b1|I#9m%FhB;#lY_WQ!nwO$ZOXdleFHg$I{60NRoRN{9F_B!rF)L#rc{rIw zqG-p|F?D>a>}Q=b9^4J_3UHtbsfHUnJ}{e^7SeOF*n*F%4%(OHwyl2m=*@-Mxm|e{ z1~XoK;9EtRX2t#6O-w#roH#yYY-<0Qu3EuQl8wCn;@+9wIgYIZhP9b(o6_01{lolt zn=^Bo)aDTTxi?}KuRc2;>bjXQCwXE-ajkN32zp?%wFcm7?o?H7L0 z{c%)d?bSO#QOYPL<`_15G)C52Ue)s`hM*mbVH#}g$bwg7riZ4aFHcg>OG)}tFA-=2 znMA^_VG2EqO~Y;`M7F3Lc(n`is9vYPiSTScob#%;e)7ewnWaeqy7q40_{w>c9$BAN z2Bb=8Fyr zrI*17N4@)Cj0o&fmL&29Re5Nb1s|z z_>}6mkM_c~JHM)jYM!C6=!(XDxP*r+Ob`o3L6o z)}+++=pE8{-)tT3-KW<{+I2cUKfOGB-R1nYZL4Mn?yB;UtB2Y>ac_u8!60W`Ccu z=lH*Pezq5Hl^kABbnwm41WlgyO_o{y$E;g5_{iz+5)Y#N1YJ4~_iVj1^v_k0E)rX| z8_etYwCra3ApN@@aeq*dVvBaQ)7s|qXJhBj+8t@~>S5|Ws?bo-x_{c^XUctg_kec0 zXL;q`uYH;y+|7=Y_`R&CqJ0G=vLxNk=qL0y$$vM=%LW3%Ao9~)G|ES!2NBZYx zH`ju*QPsd`w6`YbN}J$2C756wL_8CqfX`sb)v5Cqs1-?>>V@k0ndup8zTDs-V0pOt zT)XaltvT{^7spd^wNV}?X_ER(jO&+PS42qPD!5X)Og^!ZS26uaSA{879qrv}XVhX( z;cD_-eNb5Pze=nMoq=b;(NHmRra*t_gujqevZtSLNLxz5{=c{?WjJC`{!*z3q56tL z+Cq~NTYZ)-JP9_6oq#1`B0p1@EDA@znnIRHfPzOsok&HArDAZ-lmh}W2VtWjMJ%Rl z9gV?@QA}teTw*gA5(`m_8B7hEhK9{yW0(N7j)B@R5h|m>sBi(|TYw~7MY(5zK`_k+ z(!*)&ky?de-d}lQhqU>0cpE+gwIIF>xiHZI}9G%vLRVz1^y9Cbju#1B9v&Y z1C&HWp)f~y9U(-kSujZ^q&yjmfr04=KZvbh9g7YpWHfZ#8ERxgl;*NiK7&%}ib1Y3 zo9KWQY?CulHXF+2LW;r-18-Ls7_H!-2oLyHA*4(lj{$3(lX0sO1rym$0Cl7TP(3W+ zUKkDf6fWrSC)Xar^?M`@ZibqGIFgC9oErL@6aJXqxnG@f@J=LsigeyHw>EmJ>Z;*gK8$jQ&kM=3xvSvXf!q$@}tF+ z_rVwlf*heYnu88b17^%L`C#4|etE^Dk2Wwma#YT!c_A+$S^=4sn(XHRMxJ zfTO|&MUz(_>>|dRuz`S9Thy3aSSnF)mBXuY8=BbMKUvQ}(Xh#x8A~9F-*b0xXPo_b zi-yrK1)PwGUc7y)G~~!M`sXee5DP~YS_#XIw|$TjtJ%CrWby(zw9`OOnY{Lob&hGL-rqbo zY#CNIqkfVZYS-GU3F$#+xVNSFD%D;6#{6QxpH13Hl-SnQ*}w2$JlN-JyE%UOhHr=V zu6ckGkt-0?M|aD)IlV;?hZ@+{*APS~4U%h*X`lvV%9}P6>M;PW2 zBOOwv4K?Z~q=mhWYEs=gqn?(UQ8qg1j&OD52z5H6!7h|;SXWZlaVhzEZ%^;h5l`nt z-{#c)&0^$nk^(70VlmbIIe8WShs!kjfPV-#t0 zg}C5`vz|KkYs^T@SdMX03H1yG;U(Mo^&TT1>nUkpuGw^J zfCxa7l_7t}2%aTXu*XJ|jU<9{VFG+*qp@57_iSd|CIExI0{01_*^=ECFPq+JFTrAhb~DeNpYlY7Gv zxFWP{n1Z{dDx4w%kuwiASp^A8e-3WCk1g0TBZC*50X!N3pE`@NiGl@ZISx_aDof)k z6Bkv>>NWnqGIkBN46>qnUy%T(%4pmW1>cpW@U5Qc3n9&tg83*&aAotiE|tKOtYFL6 z3IzWs)$opsCLtQZ{g^cP-bLelCCJ?6I4lM>x-B>_4oL@<@C=11{IxeD8oV1>s*%99b)r6lo#UY{nH|G#m#29A3y_-_r|@RZ;y zsc_C43q=)qxHgT4v+0p=qJqZ7T7LV9j)EB<1*BJnZ>IC1r@d?NSse@2(B(DZQ+gIw z;mtIEXsV2b_bQXHHv@<&1zgV-!A4^^T(6|@X~SaZ^DG%;`Di?A=K^KqM}-KETT&qupmDkx<^w7@gBaAOK toClose; + + @NotOwning + public T register(Object dontCare, @Owning T closeable) throws Exception { + closeable.close(); + return closeable; + } + + public void close() throws Exception { + for (AutoCloseable closeable : toClose) { + closeable.close(); // Ignore error handling for this demonstration + } + } + } + """; + createFile(testNotOwningPath, testNotOwningSource); + + this.problemRequestor.initialize(testNotOwningSource.toCharArray()); + ICompilationUnit unit = getCompilationUnit(testNotOwningPath).getWorkingCopy(this.wcOwner, null); + assertNoProblem(unit.getBuffer().getCharacters(), unit); + + p1.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null); + IMarker[] markers = p1.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); + assertMarkers("Unexpected markers", "", markers); + + + IJavaProject p2 = createJavaProject("P2", new String[] {""}, new String[] {"JCL_23_LIB"}, "bin", "23"); + p2.setOption(JavaCore.COMPILER_ANNOTATION_RESOURCE_ANALYSIS, JavaCore.ENABLED); + addClasspathEntry(p2, JavaCore.newProjectEntry(p1.getPath())); // no access to the annotation lib, since not re-exported + + createFolder("/P2/client"); + String testingAnnotatedPath = "/P2/client/TestingAnnotated.java"; + String testingAnnotatedSource = """ + package client; + + import annotated.TestNotOwning; + import java.io.InputStream; + + public class TestingAnnotated { + + public static void client() throws Exception { + try (TestNotOwning t = new TestNotOwning()) { + AutoCloseable a = () -> {} ; + AutoCloseable b = t.register(null, new InputStream()); + assert a != null; + assert b != null; + } + } + } + """; + createFile(testingAnnotatedPath, testingAnnotatedSource); + + // Challenge CompilationUnitProblemFinder: + this.problemRequestor.initialize(testingAnnotatedSource.toCharArray()); + unit = getCompilationUnit(testingAnnotatedPath).getWorkingCopy(this.wcOwner, null); + String expectedError = + """ + ---------- + 1. WARNING in /P2/client/TestingAnnotated.java (at line 10) + AutoCloseable a = () -> {} ; + ^ + Resource leak: 'a' is never closed + ---------- + 2. WARNING in /P2/client/TestingAnnotated.java (at line 11) + AutoCloseable b = t.register(null, new InputStream()); + ^ + Resource leak: 'b' is never closed + ---------- + 3. WARNING in /P2/client/TestingAnnotated.java (at line 11) + AutoCloseable b = t.register(null, new InputStream()); + ^^^^^^^^^^ + Method 'register' has an unresolved annotation 'NotOwning' that could be relevant for static analysis + ---------- + 4. WARNING in /P2/client/TestingAnnotated.java (at line 11) + AutoCloseable b = t.register(null, new InputStream()); + ^^^^^^^^^^^^^^^^^ + Parameter 'closeable' of method 'register' has an unresolved annotation 'Owning' that could be relevant for static analysis + ---------- + 5. WARNING in /P2/client/TestingAnnotated.java (at line 11) + AutoCloseable b = t.register(null, new InputStream()); + ^^^^^^^^^^^^^^^^^ + Mandatory close of resource '' has not been shown + ---------- + """; + assertProblems("Unexpected problems from CompilationUnitProblemFinder", expectedError); + + // Challenge JavaBuilder (using class files from p1 we only see one problem): + assertMarkersFromJavaBuilder(p2, "Resource leak: 'a' is never closed", "/P2/client/TestingAnnotated.java"); + + assertErrorFromCompilationUnitResolver(p2, unit, expectedError); + } finally { + deleteProject("P1"); + deleteProject("P2"); + } + } + + public void testAnnotationNotRexeported_srcModular() throws CoreException, InterruptedException { + try { + // Resources creation + IJavaProject p1 = createJavaProject("P1", new String[] {""}, new String[] {"JCL_23_LIB"}, "bin", "23"); + addLibraryEntry(p1, new Path(this.ANNOTATION_LIB), null, null, null, null, + new IClasspathAttribute[] {JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true")}, false); + p1.setOption(JavaCore.COMPILER_ANNOTATION_RESOURCE_ANALYSIS, JavaCore.ENABLED); + p1.setOption(JavaCore.COMPILER_SOURCE, "23"); + p1.setOption(JavaCore.COMPILER_COMPLIANCE, "23"); + + createFile("/P1/module-info.java", + """ + module P1 { + requires org.eclipse.jdt.annotation; + exports annotated; + } + """); + createFolder("/P1/annotated"); + String testNotOwningPath = "/P1/annotated/TestNotOwning.java"; + String testNotOwningSource = """ + package annotated; + import java.util.List; + import org.eclipse.jdt.annotation.NotOwning; + import org.eclipse.jdt.annotation.Owning; + + public class TestNotOwning implements AutoCloseable { + private List toClose; + + @NotOwning + public T register(@Owning T closeable) throws Exception { + closeable.close(); + return closeable; + } + + public void close() throws Exception { + for (AutoCloseable closeable : toClose) { + closeable.close(); // Ignore error handling for this demonstration + } + } + } + """; + createFile(testNotOwningPath, testNotOwningSource); + + this.problemRequestor.initialize(testNotOwningSource.toCharArray()); + ICompilationUnit unit = getCompilationUnit(testNotOwningPath).getWorkingCopy(this.wcOwner, null); + assertNoProblem(unit.getBuffer().getCharacters(), unit); + + p1.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null); + IMarker[] markers = p1.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); + assertMarkers("Unexpected markers", "", markers); + + + IJavaProject p2 = createJavaProject("P2", new String[] {""}, new String[] {"JCL_23_LIB"}, "bin", "23"); + p2.setOption(JavaCore.COMPILER_ANNOTATION_RESOURCE_ANALYSIS, JavaCore.ENABLED); + p2.setOption(JavaCore.COMPILER_SOURCE, "23"); + p2.setOption(JavaCore.COMPILER_COMPLIANCE, "23"); + addClasspathEntry(p2, + JavaCore.newProjectEntry(p1.getPath(), + null, + false, + new IClasspathAttribute[] {JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true")}, + false) ); + // no access to the annotation lib, since not re-exported + + createFile("/P2/module-info.java", + """ + module P2 { + requires P1; + } + """); + + createFolder("/P2/client"); + String testingAnnotatedPath = "/P2/client/TestingAnnotated.java"; + String testingAnnotatedSource = """ + package client; + + import annotated.TestNotOwning; + + public class TestingAnnotated { + + public static void client() throws Exception { + try (TestNotOwning t = new TestNotOwning()) { + AutoCloseable a = () -> {} ; // produces warning + AutoCloseable b = t.register(() -> {} ); // produces no warning + assert a != null; + assert b != null; + } + } + } + """; + createFile(testingAnnotatedPath, testingAnnotatedSource); + + // Challenge CompilationUnitProblemFinder: + this.problemRequestor.initialize(testingAnnotatedSource.toCharArray()); + unit = getCompilationUnit(testingAnnotatedPath).getWorkingCopy(this.wcOwner, null); + String expectedError = + """ + ---------- + 1. WARNING in /P2/client/TestingAnnotated.java (at line 9) + AutoCloseable a = () -> {} ; // produces warning + ^ + Resource leak: 'a' is never closed + ---------- + 2. WARNING in /P2/client/TestingAnnotated.java (at line 10) + AutoCloseable b = t.register(() -> {} ); // produces no warning + ^ + Resource leak: 'b' is never closed + ---------- + 3. WARNING in /P2/client/TestingAnnotated.java (at line 10) + AutoCloseable b = t.register(() -> {} ); // produces no warning + ^^^^^^^^^^ + Method 'register' has an unresolved annotation 'NotOwning' that could be relevant for static analysis + ---------- + """; + assertProblems("Unexpected problems from CompilationUnitProblemFinder", expectedError); + + // Challenge JavaBuilder (using class files from p1 we only see one problem): + assertMarkersFromJavaBuilder(p2, "Resource leak: 'a' is never closed", "/P2/client/TestingAnnotated.java"); + + assertErrorFromCompilationUnitResolver(p2, unit, expectedError); + } finally { + deleteProject("P1"); + deleteProject("P2"); + } + } + public void testAnnotationNotRexeported_bin() throws Exception { + try { + // using class TestNotOwning from a jar, all three strategies show only one problem: + + IJavaProject p = createJavaProject("P", new String[] {""}, new String[] {"JCL_23_LIB", this.ANNOTATION_LIB}, "bin", "23"); + p.setOption(JavaCore.COMPILER_ANNOTATION_RESOURCE_ANALYSIS, JavaCore.ENABLED); + String jarAbsPath = p.getProject().getLocation()+"/annotated.jar"; + + createJar(new String[] { + "annotated/TestNotOwning.java", + """ + package annotated; + import java.util.List; + import org.eclipse.jdt.annotation.*; + + public class TestNotOwning implements AutoCloseable { + private List toClose; + + @NotOwning + public T register(@Owning T closeable) throws Exception { + closeable.close(); + return closeable; + } + + public void close() throws Exception { + for (AutoCloseable closeable : toClose) { + closeable.close(); // Ignore error handling for this demonstration + } + } + } + """ + }, + jarAbsPath, + new String[] {this.ANNOTATION_LIB}, + "23"); + + // no access to the annotation lib + addClasspathEntry(p, JavaCore.newLibraryEntry(new Path(jarAbsPath), null, null)); + + createFolder("/P/client"); + String testingAnnotatedPath = "/P/client/TestingAnnotated.java"; + String testingAnnotatedSource = """ + package client; + + import annotated.TestNotOwning; + + public class TestingAnnotated { + + public static void client() throws Exception { + try (TestNotOwning t = new TestNotOwning()) { + AutoCloseable a = () -> {} ; // produces warning + AutoCloseable b = t.register(() -> {} ); // produces no warning + assert a != null; + assert b != null; + } + } + } + """; + createFile(testingAnnotatedPath, testingAnnotatedSource); + + // Challenge CompilationUnitProblemFinder: + this.problemRequestor.initialize(testingAnnotatedSource.toCharArray()); + ICompilationUnit unit = getCompilationUnit(testingAnnotatedPath).getWorkingCopy(this.wcOwner, null); + String expectedError = + """ + ---------- + 1. WARNING in /P/client/TestingAnnotated.java (at line 9) + AutoCloseable a = () -> {} ; // produces warning + ^ + Resource leak: 'a' is never closed + ---------- + """; + assertProblems("Unexpected problems from CompilationUnitProblemFinder", expectedError); + + assertMarkersFromJavaBuilder(p, "Resource leak: 'a' is never closed", "/P/client/TestingAnnotated.java"); + + assertErrorFromCompilationUnitResolver(p, unit, expectedError); + } finally { + deleteProject("P"); + } + } + + public void testAnnotationNotRexeported_src_inheritedField() throws CoreException, InterruptedException { + try { + // Resources creation + IJavaProject p1 = createJavaProject("P1", new String[] {""}, new String[] {"JCL_23_LIB", this.ANNOTATION_LIB}, "bin", "23"); + p1.setOption(JavaCore.COMPILER_ANNOTATION_RESOURCE_ANALYSIS, JavaCore.ENABLED); + + createFolder("/P1/annotated"); + String testNotOwningPath = "/P1/annotated/TestOwning.java"; + String testNotOwningSource = """ + package annotated; + + import org.eclipse.jdt.annotation.Owning; + + public class TestOwning implements AutoCloseable { + protected @Owning AutoCloseable cached; + + public void close() throws Exception { + cached.close(); // Ignore error handling for this demonstration + } + } + """; + createFile(testNotOwningPath, testNotOwningSource); + + this.problemRequestor.initialize(testNotOwningSource.toCharArray()); + ICompilationUnit unit = getCompilationUnit(testNotOwningPath).getWorkingCopy(this.wcOwner, null); + assertNoProblem(unit.getBuffer().getCharacters(), unit); + + p1.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null); + IMarker[] markers = p1.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); + assertMarkers("Unexpected markers", "", markers); + + + IJavaProject p2 = createJavaProject("P2", new String[] {""}, new String[] {"JCL_23_LIB"}, "bin", "23"); + p2.setOption(JavaCore.COMPILER_ANNOTATION_RESOURCE_ANALYSIS, JavaCore.ENABLED); + addClasspathEntry(p2, JavaCore.newProjectEntry(p1.getPath())); // no access to the annotation lib, since not re-exported + + createFolder("/P2/client"); + String testingAnnotatedPath = "/P2/client/TestingAnnotated.java"; + String testingAnnotatedSource = """ + package client; + + import annotated.TestOwning; + import java.io.InputStream; + + public class TestingAnnotated extends TestOwning { + + public void client() throws Exception { + this.cached = new InputStream(); + } + } + """; + createFile(testingAnnotatedPath, testingAnnotatedSource); + + // Challenge CompilationUnitProblemFinder: + this.problemRequestor.initialize(testingAnnotatedSource.toCharArray()); + unit = getCompilationUnit(testingAnnotatedPath).getWorkingCopy(this.wcOwner, null); + String expectedError = + """ + ---------- + 1. WARNING in /P2/client/TestingAnnotated.java (at line 9) + this.cached = new InputStream(); + ^^^^^^^^^^^ + Field 'cached' has an unresolved annotation 'Owning' that could be relevant for static analysis + ---------- + 2. WARNING in /P2/client/TestingAnnotated.java (at line 9) + this.cached = new InputStream(); + ^^^^^^^^^^^^^^^^^ + Mandatory close of resource '' has not been shown + ---------- + """; + assertProblems("Unexpected problems from CompilationUnitProblemFinder", expectedError); + + // Challenge JavaBuilder (using binary types from p1 we have no problem): + assertMarkersFromJavaBuilder(p2, "", null); + + assertErrorFromCompilationUnitResolver(p2, unit, expectedError); + } finally { + deleteProject("P1"); + deleteProject("P2"); + } + } +}