Skip to content

Commit d988ce2

Browse files
authored
Fix a false positive in the Resource Leak Checker (#7060)
1 parent 9710220 commit d988ce2

File tree

5 files changed

+75
-26
lines changed

5 files changed

+75
-26
lines changed

checker/src/main/java/org/checkerframework/checker/calledmethods/CalledMethodsTransfer.java

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,6 @@
3939
/** A transfer function that accumulates the names of methods called. */
4040
public class CalledMethodsTransfer extends AccumulationTransfer {
4141

42-
/**
43-
* {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} requires a TransferInput,
44-
* but the actual exceptional stores need to be modified in {@link #accumulate(Node,
45-
* TransferResult, String...)}, which only has access to a TransferResult. So this field is set to
46-
* non-null in {@link #visitMethodInvocation(MethodInvocationNode, TransferInput)} via a call to
47-
* {@link #makeExceptionalStores(MethodInvocationNode, TransferInput)} (which reads the CFStores
48-
* from the TransferInput) before the call to accumulate(); accumulate() can then use this field
49-
* to read the CFStores; and then finally this field is then reset to null afterwards to prevent
50-
* it from being used somewhere it shouldn't be.
51-
*/
52-
private @Nullable Map<TypeMirror, AccumulationStore> exceptionalStores;
53-
5442
/**
5543
* The element for the CalledMethods annotation's value element. Stored in a field in this class
5644
* to prevent the need to cast to CalledMethods ATF every time it's used.
@@ -118,10 +106,22 @@ protected boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree l
118106
@Override
119107
public TransferResult<AccumulationValue, AccumulationStore> visitMethodInvocation(
120108
MethodInvocationNode node, TransferInput<AccumulationValue, AccumulationStore> input) {
121-
exceptionalStores = makeExceptionalStores(node, input);
109+
110+
// The call to `super.visitMethodInvocation()` modifies the input store in-place. So if we end
111+
// up needing to create the exceptional stores, then we'll need this copy taken beforehand.
112+
AccumulationStore inputStore = input.getRegularStore().copy();
113+
122114
TransferResult<AccumulationValue, AccumulationStore> superResult =
123115
super.visitMethodInvocation(node, input);
124116

117+
// Ensure that the result has a store for each possible exception. This affects the behavior of
118+
// accumulate(), which will accumulate values into the result's exceptional stores as well.
119+
Map<TypeMirror, AccumulationStore> exceptionalStores = superResult.getExceptionalStores();
120+
if (exceptionalStores == null) {
121+
exceptionalStores = makeExceptionalStores(node, inputStore);
122+
superResult = superResult.withExceptionalStores(exceptionalStores);
123+
}
124+
125125
ExecutableElement method = TreeUtils.elementFromUse(node.getTree());
126126
handleEnsuresCalledMethodsVarargs(node, method, superResult);
127127
handleEnsuresCalledMethodsOnException(node, method, exceptionalStores);
@@ -134,20 +134,19 @@ public TransferResult<AccumulationValue, AccumulationStore> visitMethodInvocatio
134134
.adjustMethodNameUsingValueChecker(methodName, node.getTree());
135135
accumulate(receiver, superResult, methodName);
136136
}
137-
TransferResult<AccumulationValue, AccumulationStore> finalResult =
138-
new ConditionalTransferResult<>(
139-
superResult.getResultValue(),
140-
superResult.getThenStore(),
141-
superResult.getElseStore(),
142-
exceptionalStores);
143-
exceptionalStores = null;
144-
return finalResult;
137+
return new ConditionalTransferResult<>(
138+
superResult.getResultValue(),
139+
superResult.getThenStore(),
140+
superResult.getElseStore(),
141+
exceptionalStores);
145142
}
146143

147144
@Override
148145
public void accumulate(
149146
Node node, TransferResult<AccumulationValue, AccumulationStore> result, String... values) {
150147
super.accumulate(node, result, values);
148+
149+
Map<TypeMirror, AccumulationStore> exceptionalStores = result.getExceptionalStores();
151150
if (exceptionalStores == null) {
152151
return;
153152
}
@@ -186,23 +185,21 @@ public void accumulate(
186185
* node} was definitely called.
187186
*
188187
* @param node a method invocation
189-
* @param input the transfer input associated with the method invocation
188+
* @param inputStore the transfer input associated with the method invocation
190189
* @return a map from types to stores. The keys are the same keys used by {@link
191190
* ExceptionBlock#getExceptionalSuccessors()}. The values are copies of the regular store from
192191
* {@code input}.
193192
*/
194193
private Map<TypeMirror, AccumulationStore> makeExceptionalStores(
195-
MethodInvocationNode node, TransferInput<AccumulationValue, AccumulationStore> input) {
194+
MethodInvocationNode node, AccumulationStore inputStore) {
196195
if (!(node.getBlock() instanceof ExceptionBlock)) {
197196
// This can happen in some weird (buggy?) cases:
198197
// see https://github.com/typetools/checker-framework/issues/3585
199198
return Collections.emptyMap();
200199
}
201200
ExceptionBlock block = (ExceptionBlock) node.getBlock();
202201
Map<TypeMirror, AccumulationStore> result = new LinkedHashMap<>();
203-
block
204-
.getExceptionalSuccessors()
205-
.forEach((tm, b) -> result.put(tm, input.getRegularStore().copy()));
202+
block.getExceptionalSuccessors().forEach((tm, b) -> result.put(tm, inputStore.copy()));
206203
return result;
207204
}
208205

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// For some expressions like `alloc()`, the Resource Leak Checker creates anonymous "temporary
2+
// variables" to track must-call obligations. These tests exercise some interesting interactions
3+
// involving temporary variables.
4+
5+
import java.io.*;
6+
import org.checkerframework.checker.calledmethods.qual.*;
7+
import org.checkerframework.checker.mustcall.qual.*;
8+
9+
abstract class TemporaryVariables {
10+
public abstract Closeable alloc();
11+
12+
public void test1() throws IOException {
13+
alloc().close();
14+
}
15+
16+
// Identical to test1() but with explicit exception handling.
17+
public void test2() throws IOException {
18+
try {
19+
alloc().close();
20+
} catch (IOException e) {
21+
throw e;
22+
}
23+
}
24+
25+
@EnsuresCalledMethods(value = "#1", methods = "close")
26+
@EnsuresCalledMethodsOnException(value = "#1", methods = "close")
27+
public void close(Closeable x) throws IOException {
28+
x.close();
29+
}
30+
}

dataflow/src/main/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,11 @@ public String toString() {
150150
public boolean storeChanged() {
151151
return storeChanged;
152152
}
153+
154+
@Override
155+
public ConditionalTransferResult<V, S> withExceptionalStores(
156+
Map<TypeMirror, S> exceptionalStores) {
157+
return new ConditionalTransferResult<>(
158+
resultValue, thenStore, elseStore, exceptionalStores, storeChanged);
159+
}
153160
}

dataflow/src/main/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,9 @@ public String toString() {
150150
public boolean storeChanged() {
151151
return storeChanged;
152152
}
153+
154+
@Override
155+
public RegularTransferResult<V, S> withExceptionalStores(Map<TypeMirror, S> exceptionalStores) {
156+
return new RegularTransferResult<>(resultValue, store, exceptionalStores, storeChanged);
157+
}
153158
}

dataflow/src/main/java/org/checkerframework/dataflow/analysis/TransferResult.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,14 @@ public void setResultValue(V resultValue) {
141141
* changed the regularStore, elseStore, or thenStore
142142
*/
143143
public abstract boolean storeChanged();
144+
145+
/**
146+
* Construct a shallow copy of this {@code TransferResult}, but with the given {@code
147+
* exceptionalStores}.
148+
*
149+
* @param exceptionalStores the new exceptional stores to use
150+
* @return a copy of this object modified to use the given exceptional stores
151+
* @see #getExceptionalStores()
152+
*/
153+
public abstract TransferResult<V, S> withExceptionalStores(Map<TypeMirror, S> exceptionalStores);
144154
}

0 commit comments

Comments
 (0)