diff --git a/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/Owning.java b/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/Owning.java index 9189b4bf2a5..741d4cb2f8d 100644 --- a/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/Owning.java +++ b/org.eclipse.jdt.annotation/src/org/eclipse/jdt/annotation/Owning.java @@ -44,6 +44,10 @@ * enclosing object. In order to allow for precise analysis in this situation, it is recommended that the enclosing * class of such a field implements {@link AutoCloseable}. In that case, it will be the responsibility of the class's * {@code close()} implementation to also close all resources stored in {@code @Owning} fields. + *
Method receiver (via {@link ElementType#TYPE_USE})
+ *
Annotating a method receiver (explicit {@code this} parameter) as owning signals that the method is responsible + * for closing all contained resources. The method will be treated as a "custom close method".
+ * The annotation is not evaluated in any other TYPE_USE locations. * *

Responsibility to close a resource may initially arise from these situations:

* *

Responsibility to close a resource may be fulfilled by ensuring any of these measures on every possible path:

* * * @since 2.3 */ @Retention(RetentionPolicy.CLASS) @Documented -@Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD }) +@Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE_USE }) public @interface Owning { // no details } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java index c2e3e6dfc4d..d55aa04c79b 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/AbstractMethodDeclaration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2023 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 @@ -670,6 +670,15 @@ public void resolveReceiver() { if (this.receiver.type.hasNullTypeAnnotation(AnnotationPosition.ANY)) { this.scope.problemReporter().nullAnnotationUnsupportedLocation(this.receiver.type); } + if (this.scope.compilerOptions().isAnnotationBasedResourceAnalysisEnabled && this.receiver.type.resolvedType != null) { + for (AnnotationBinding annotationBinding : this.receiver.type.resolvedType.getTypeAnnotations()) { + ReferenceBinding annotationType = annotationBinding.getAnnotationType(); + if (annotationType != null && annotationType.hasTypeBit(TypeIds.BitOwningAnnotation)) { + this.binding.extendedTagBits = ExtendedTagBits.IsClosingMethod; + break; + } + } + } } public void resolveJavadoc() { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MessageSend.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MessageSend.java index 55e33065db8..e5a6cb83fd9 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MessageSend.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MessageSend.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2023 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 @@ -160,7 +160,7 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl if (analyseResources) { if (nonStatic) { // closeable.close() - if (CharOperation.equals(TypeConstants.CLOSE, this.selector)) { + if (this.binding.isClosingMethod()) { recordCallingClose(currentScope, flowContext, flowInfo, this.receiver); } } else if (this.arguments != null && this.arguments.length > 0 && FakedTrackingVariable.isAnyCloseable(this.arguments[0].resolvedType)) { @@ -340,20 +340,13 @@ private void yieldQualifiedCheck(BlockScope currentScope) { currentScope.problemReporter().switchExpressionsYieldUnqualifiedMethodError(this); } private void recordCallingClose(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo, Expression closeTarget) { - FakedTrackingVariable trackingVariable = FakedTrackingVariable.getCloseTrackingVariable(closeTarget, flowInfo, flowContext, - currentScope.compilerOptions().isAnnotationBasedResourceAnalysisEnabled); - if (trackingVariable != null) { // null happens if target is not a variable or not an AutoCloseable - if (trackingVariable.methodScope == null || trackingVariable.methodScope == currentScope.methodScope()) { - trackingVariable.markClose(flowInfo, flowContext); - } else { - trackingVariable.markClosedInNestedMethod(); - } - } else if (closeTarget instanceof SuperReference) { - ReferenceBinding currentClass = this.binding.declaringClass; + if (closeTarget.isThis() || closeTarget.isSuper()) { + // this / super calls of closing method take care of all owning fields + ReferenceBinding currentClass = this.binding.declaringClass; // responsibility starts at the class and upwards while (currentClass != null) { for (FieldBinding fieldBinding : currentClass.fields()) { if (fieldBinding.closeTracker != null) { - trackingVariable = fieldBinding.closeTracker; + FakedTrackingVariable trackingVariable = fieldBinding.closeTracker; if (trackingVariable.methodScope == null || trackingVariable.methodScope == currentScope.methodScope()) { trackingVariable.markClose(flowInfo, flowContext); } else { @@ -363,6 +356,16 @@ private void recordCallingClose(BlockScope currentScope, FlowContext flowContext } currentClass = currentClass.superclass(); } + } else { + FakedTrackingVariable trackingVariable = FakedTrackingVariable.getCloseTrackingVariable(closeTarget, flowInfo, flowContext, + currentScope.compilerOptions().isAnnotationBasedResourceAnalysisEnabled); + if (trackingVariable != null) { // null happens if target is not a variable or not an AutoCloseable + if (trackingVariable.methodScope == null || trackingVariable.methodScope == currentScope.methodScope()) { + trackingVariable.markClose(flowInfo, flowContext); + } else { + trackingVariable.markClosedInNestedMethod(); + } + } } } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java index c4e2e0190e5..c7366a17a59 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2022 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 @@ -148,9 +148,7 @@ public void analyseCode(ClassScope classScope, FlowContext flowContext, FlowInfo } CompilerOptions compilerOptions = this.scope.compilerOptions(); if (compilerOptions.isAnnotationBasedResourceAnalysisEnabled - && this.binding.declaringClass.hasTypeBit(TypeIds.BitAutoCloseable|TypeIds.BitCloseable) - && CharOperation.equals(this.selector, TypeConstants.CLOSE) - && this.arguments == null) + && this.binding.isClosingMethod()) { // implementation of AutoCloseable.close() should close all @Owning fields, create the obligation now: ReferenceBinding currentClass = this.binding.declaringClass; diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java index 8af073c66b6..c40e1f466bb 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java @@ -2344,6 +2344,20 @@ private boolean scanMethodForOwningAnnotations(IBinaryMethod method, MethodBindi // return: methodBinding.tagBits |= scanForOwningAnnotation(method.getAnnotations()); + // check for "@Owning MyType this": + IBinaryTypeAnnotation[] methodTypeAnnotations = method.getTypeAnnotations(); + if (methodTypeAnnotations != null) { + ITypeAnnotationWalker walker = new TypeAnnotationWalker(methodTypeAnnotations).toReceiver(); + IBinaryAnnotation[] receiverTypeAnnotations = walker.getAnnotationsAtCursor(0, false); + if (receiverTypeAnnotations != null) { + for (IBinaryAnnotation binaryAnnotation : receiverTypeAnnotations) { + int typeBit = this.environment.getAnalysisAnnotationBit(signature2qualifiedTypeName(binaryAnnotation.getTypeName())); + if ((typeBit & TypeIds.BitOwningAnnotation) != 0) + methodBinding.extendedTagBits |= ExtendedTagBits.IsClosingMethod; + } + } + } + // parameters: TypeBinding[] parameters = methodBinding.parameters; int numVisibleParams = parameters.length; 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 6d87856bbe3..6a383e6324f 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020, 2021 IBM Corporation and others. + * Copyright (c) 2020, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -17,7 +17,7 @@ public interface ExtendedTagBits { - int AreRecordComponentsComplete = ASTNode.Bit1; + int AreRecordComponentsComplete = ASTNode.Bit1; // type int HasUnresolvedPermittedSubtypes = ASTNode.Bit2; /** From Java 16 @@ -29,4 +29,7 @@ public interface ExtendedTagBits { int IsCanonicalConstructor = ASTNode.Bit4; // constructor int isImplicit = ASTNode.Bit5; // constructor and method + // @Owning / closing + int IsClosingMethod = ASTNode.Bit1; // method + } 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 6a2bb145ecf..1d62487de06 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 @@ -1475,6 +1475,12 @@ public boolean hasPolymorphicSignature(Scope scope) { return false; } +public boolean isClosingMethod() { + if (!this.declaringClass.hasTypeBit(TypeIds.BitAutoCloseable)) + return false; + return (this.extendedTagBits & ExtendedTagBits.IsClosingMethod) != 0 // custom closing method with "@Owning MyType this" + || (CharOperation.equals(this.selector, TypeConstants.CLOSE) && this.parameters == NO_PARAMETERS); // AutoCloseable.close() +} public boolean ownsParameter(int i) { if (this.parameterFlowBits != null) return (this.parameterFlowBits[i] & PARAM_OWNING) != 0; 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 5586ff233e8..f9c74ba24ba 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 @@ -119,7 +119,7 @@ protected Map getCompilerOptions() { """ package org.eclipse.jdt.annotation; import java.lang.annotation.*; - @Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) + @Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE_USE}) public @interface Owning {} """; protected static final String NOTOWNING_JAVA = "org/eclipse/jdt/annotation/NotOwning.java"; @@ -1361,4 +1361,102 @@ public void close() throws IOException { "", null); } +public void testConsumingMethod_nok() { + runLeakTestWithAnnotations( + new String[] { + "F.java", + """ + import org.eclipse.jdt.annotation.*; + public class F implements AutoCloseable { + @Owning AutoCloseable rc1; + @Owning AutoCloseable rc2; + public void close() throws Exception { consume(); } + public void consume(@Owning F this) throws Exception { + rc1.close(); + } + } + """ + }, + """ + ---------- + 1. INFO in F.java (at line 6) + public void consume(@Owning F this) throws Exception { + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Resource 'rc1' should be managed by try-with-resource + ---------- + 2. ERROR in F.java (at line 6) + public void consume(@Owning F this) throws Exception { + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Resource leak: 'this.rc2' is never closed + ---------- + """, + null); +} +public void testConsumingMethodUse() { + runLeakTestWithAnnotations( + new String[] { + "F.java", + """ + import org.eclipse.jdt.annotation.*; + public class F implements AutoCloseable { + public void close() {} + public void consume(@Owning F this) { + } + static void test() { + F f = new F(); + f.consume(); + } + } + """ + }, + """ + ---------- + 1. INFO in F.java (at line 7) + F f = new F(); + ^ + Resource 'f' should be managed by try-with-resource + ---------- + """, + null); +} +public void testConsumingMethodUse_binary() { + runLeakTestWithAnnotations( + new String[] { + "p1/F.java", + """ + package p1; + import org.eclipse.jdt.annotation.*; + public class F implements AutoCloseable { + public void close() {} + public void consume(@Owning F this) { + } + } + """ + }, + "", + null); + runLeakTestWithAnnotations( + new String[] { + "Test.java", + """ + import p1.F; + public class Test { + static void test() { + F f = new F(); + f.consume(); + } + } + """ + }, + """ + ---------- + 1. INFO in Test.java (at line 4) + F f = new F(); + ^ + Resource 'f' should be managed by try-with-resource + ---------- + """, + null, + false); + } }