Lines 48-56
Link Here
|
48 |
private static final int CLOSED_IN_NESTED_METHOD = 4; |
48 |
private static final int CLOSED_IN_NESTED_METHOD = 4; |
49 |
// a location independent issue has been reported already against this resource: |
49 |
// a location independent issue has been reported already against this resource: |
50 |
private static final int REPORTED = 8; |
50 |
private static final int REPORTED = 8; |
|
|
51 |
// a resource is wrapped in another resource: |
52 |
private static final int WRAPPED = 16; |
51 |
|
53 |
|
52 |
/** |
54 |
/** |
53 |
* Bitset of {@link #CLOSE_SEEN}, {@link #PASSED_TO_OUTSIDE}, {@link #CLOSED_IN_NESTED_METHOD} and {@link #REPORTED}. |
55 |
* Bitset of {@link #CLOSE_SEEN}, {@link #PASSED_TO_OUTSIDE}, {@link #CLOSED_IN_NESTED_METHOD}, {@link #REPORTED} and {@link #WRAPPED}. |
54 |
*/ |
56 |
*/ |
55 |
private int globalClosingState = 0; |
57 |
private int globalClosingState = 0; |
56 |
|
58 |
|
Lines 58-67
Link Here
|
58 |
|
60 |
|
59 |
public LocalVariableBinding originalBinding; // the real local being tracked |
61 |
public LocalVariableBinding originalBinding; // the real local being tracked |
60 |
|
62 |
|
61 |
HashMap recordedLocations; // initially null, ASTNode -> Integer |
63 |
HashMap recordedLocations; // initially null, ASTNode -> Integer |
62 |
|
64 |
|
|
|
65 |
public FakedTrackingVariable innerTracker; // chained tracking variable of a chained (wrapped) resource |
63 |
|
66 |
|
64 |
public FakedTrackingVariable(LocalVariableBinding original, Statement location) { |
67 |
// marker instance to signal this pattern: resource = new Wrapper(resource); |
|
|
68 |
final private static FakedTrackingVariable SELF_WRAP = new FakedTrackingVariable(); |
69 |
|
70 |
private FakedTrackingVariable() { |
71 |
super(null, 0, 0); |
72 |
/* empty ctor for marker instance */ |
73 |
} |
74 |
|
75 |
public FakedTrackingVariable(LocalVariableBinding original, ASTNode location) { |
65 |
super(original.name, location.sourceStart, location.sourceEnd); |
76 |
super(original.name, location.sourceStart, location.sourceEnd); |
66 |
this.type = new SingleTypeReference( |
77 |
this.type = new SingleTypeReference( |
67 |
TypeConstants.OBJECT, |
78 |
TypeConstants.OBJECT, |
Lines 129-164
Link Here
|
129 |
|
140 |
|
130 |
/** |
141 |
/** |
131 |
* Check if the rhs of an assignment or local declaration is an (Auto)Closeable. |
142 |
* Check if the rhs of an assignment or local declaration is an (Auto)Closeable. |
132 |
* If so create or re-use a tracking variable, and wire and initialize everything. |
143 |
* If so create or re-use a tracking variable, and wire and initialize everything. |
|
|
144 |
* @param upstreamInfo info without analysis of the rhs, use this to determine the status of a resource being disconnected |
145 |
* @param flowInfo info with analysis of the rhs, use this for recording resource status because this will be passed downstream |
146 |
* @param location where to report warnigs/errors against |
147 |
* @param rhs the right hand side of the assignment, this expression is to be analyzed. |
148 |
* The caller has already checked that the rhs is either of a closeable type or null. |
149 |
* @param local the local variable into which the rhs is being assigned |
133 |
*/ |
150 |
*/ |
134 |
public static void handleResourceAssignment(FlowInfo flowInfo, Statement location, Expression rhs, LocalVariableBinding local, |
151 |
public static void handleResourceAssignment(FlowInfo upstreamInfo, FlowInfo flowInfo, ASTNode location, Expression rhs, LocalVariableBinding local) |
135 |
LocalVariableBinding previousTrackerBinding) |
|
|
136 |
{ |
152 |
{ |
137 |
if (isAutoCloseable(rhs.resolvedType)) { |
153 |
FakedTrackingVariable previousTracker = null; |
|
|
154 |
FakedTrackingVariable disconnectedTracker = null; |
155 |
if (local.closeTracker != null) { |
156 |
// assigning to a variable already holding an AutoCloseable, has it been closed before? |
157 |
previousTracker = local.closeTracker; |
158 |
int status = upstreamInfo.nullStatus(local); |
159 |
if (status != FlowInfo.NULL && status != FlowInfo.UNKNOWN) // only if previous value may be relevant |
160 |
disconnectedTracker = previousTracker; |
161 |
} |
162 |
|
163 |
if (rhs.resolvedType.id != TypeIds.T_null) { |
138 |
// new value is AutoCloseable, start tracking, possibly re-using existing tracker var: |
164 |
// new value is AutoCloseable, start tracking, possibly re-using existing tracker var: |
139 |
|
|
|
140 |
FakedTrackingVariable rhsTrackVar = getCloseTrackingVariable(rhs); |
165 |
FakedTrackingVariable rhsTrackVar = getCloseTrackingVariable(rhs); |
141 |
if (rhsTrackVar != null) { // 1. share tracking variable with RHS? |
166 |
if (rhsTrackVar != null) { // 1. share tracking variable with RHS? |
142 |
local.closeTracker = rhsTrackVar; |
167 |
local.closeTracker = rhsTrackVar; |
143 |
// keep null-status unchanged across this assignment |
168 |
// keep null-status unchanged across this assignment |
144 |
} else if (previousTrackerBinding != null) { // 2. re-use tracking variable from the LHS? |
169 |
} else if (previousTracker != null) { // 2. re-use tracking variable from the LHS? |
145 |
// re-assigning from a fresh, mark as not-closed again: |
170 |
// re-assigning from a fresh, mark as not-closed again: |
146 |
flowInfo.markAsDefinitelyNull(previousTrackerBinding); |
171 |
flowInfo.markAsDefinitelyNull(previousTracker.binding); |
|
|
172 |
local.closeTracker = analyseCloseableExpression(flowInfo, local, location, rhs, previousTracker); |
173 |
if (local.closeTracker == SELF_WRAP) { |
174 |
local.closeTracker = previousTracker; |
175 |
return; // avoid calling recordErrorLocation below |
176 |
} |
147 |
} else { // 3. no re-use, create a fresh tracking variable: |
177 |
} else { // 3. no re-use, create a fresh tracking variable: |
148 |
local.closeTracker = new FakedTrackingVariable(local, location); |
178 |
rhsTrackVar = analyseCloseableExpression(flowInfo, local, location, rhs, null); |
149 |
// a fresh resource, mark as not-closed: |
179 |
if (rhsTrackVar != null) { |
150 |
flowInfo.markAsDefinitelyNull(local.closeTracker.binding); |
180 |
local.closeTracker = rhsTrackVar; |
|
|
181 |
// a fresh resource, mark as not-closed: |
182 |
flowInfo.markAsDefinitelyNull(local.closeTracker.binding); |
151 |
// TODO(stephan): this might be useful, but I could not find a test case for it: |
183 |
// TODO(stephan): this might be useful, but I could not find a test case for it: |
152 |
// if (flowContext.initsOnFinally != null) |
184 |
// if (flowContext.initsOnFinally != null) |
153 |
// flowContext.initsOnFinally.markAsDefinitelyNonNull(trackerBinding); |
185 |
// flowContext.initsOnFinally.markAsDefinitelyNonNull(trackerBinding); |
|
|
186 |
} |
154 |
} |
187 |
} |
155 |
} |
188 |
} |
|
|
189 |
|
190 |
if (disconnectedTracker != null) |
191 |
disconnectedTracker.recordErrorLocation(location, upstreamInfo.nullStatus(disconnectedTracker.binding)); |
192 |
} |
193 |
/** |
194 |
* analyze structure of a closeable expression, matching (chained) resources against our white lists. |
195 |
* See Bug 358903 - Filter practically unimportant resource leak warnings |
196 |
* @param flowInfo used when recursing back into {@link #handleResourceAssignment} |
197 |
* @param local local variable to which the closeable is being assigned |
198 |
* @param location where to flag errors/warnings against |
199 |
* @param expression expression to be analyzed |
200 |
* @param previousTracker when analyzing a re-assignment we may already have a tracking variable for local, |
201 |
* which we should then re-use |
202 |
* @return a tracking variable associated with local or null if no need to track |
203 |
*/ |
204 |
private static FakedTrackingVariable analyseCloseableExpression(FlowInfo flowInfo, |
205 |
LocalVariableBinding local, ASTNode location, Expression expression, FakedTrackingVariable previousTracker) |
206 |
{ |
207 |
if (expression.resolvedType instanceof ReferenceBinding) { |
208 |
ReferenceBinding resourceType = (ReferenceBinding) expression.resolvedType; |
209 |
|
210 |
if (resourceType.hasTypeBit(TypeIds.BitResourceFreeCloseable)) { |
211 |
// (a) resource-free closeable: -> null |
212 |
return null; |
213 |
} |
214 |
|
215 |
if (resourceType.hasTypeBit(TypeIds.BitWrapperCloseable)) { |
216 |
// (b) wrapper |
217 |
Expression innerExpression = expression; |
218 |
if (innerExpression instanceof Assignment) |
219 |
innerExpression = ((Assignment)innerExpression).expression; |
220 |
if (innerExpression instanceof AllocationExpression) { |
221 |
Expression[] args = ((AllocationExpression) innerExpression).arguments; |
222 |
if (args != null && args.length == 1) { |
223 |
// (b.1) wrapper allocation with argument |
224 |
return analyseCloseableAllocationArgument(flowInfo, local, location, args[0], previousTracker); |
225 |
} |
226 |
} |
227 |
LocalVariableBinding innerLocal = innerExpression.localVariableBinding(); |
228 |
if (innerLocal != null && innerLocal.closeTracker != null) { |
229 |
FakedTrackingVariable outerTracker = previousTracker != null ? previousTracker : new FakedTrackingVariable(local, location); |
230 |
outerTracker.innerTracker = innerLocal.closeTracker; |
231 |
innerLocal.closeTracker.globalClosingState |= WRAPPED; |
232 |
return outerTracker; |
233 |
} |
234 |
// (b.2) wrapper with irrelevant inner: -> null |
235 |
return null; |
236 |
} |
237 |
} |
238 |
if (local.closeTracker != null) |
239 |
// (c): inner has already been analysed: -> re-use track var |
240 |
return local.closeTracker; |
241 |
// (d): normal resource: -> normal tracking var |
242 |
if (previousTracker != null) |
243 |
return previousTracker; // (d.1): re-use existing tracking var |
244 |
return new FakedTrackingVariable(local, location); |
245 |
} |
246 |
|
247 |
// an outer allocation expression has an argument, recursively analyze whether the arg is closeable |
248 |
// return (1) a possible nested tracker for the outer expression or (2) null signaling no relevant resource contained |
249 |
static FakedTrackingVariable analyseCloseableAllocationArgument(FlowInfo flowInfo, LocalVariableBinding outerLocal, ASTNode outerLocation, |
250 |
Expression arg, FakedTrackingVariable previousTracker) |
251 |
{ |
252 |
if (arg instanceof Assignment) { |
253 |
Assignment assign = (Assignment)arg; |
254 |
LocalVariableBinding innerLocal = assign.localVariableBinding(); |
255 |
if (innerLocal != null) { |
256 |
// nested assignment has already been processed |
257 |
if (innerLocal.closeTracker != null && innerLocal.closeTracker.originalBinding == outerLocal) |
258 |
return SELF_WRAP; // signal special case to our caller: resource = new Wrapper(resource); |
259 |
return innerLocal.closeTracker; // FIXME do we need a nested tracker here? see test061a/e |
260 |
} else { |
261 |
arg = assign.expression; // unwrap assignment and fall through |
262 |
} |
263 |
} |
264 |
if (arg instanceof SingleNameReference) { |
265 |
SingleNameReference ref = (SingleNameReference) arg; |
266 |
if (ref.binding instanceof LocalVariableBinding) { |
267 |
// allocation arg is a reference to an existing closeable? |
268 |
return getTrackingVarForNested(flowInfo, outerLocal, outerLocation, (LocalVariableBinding)ref.binding, ref, ref, previousTracker); |
269 |
} |
270 |
} else if (arg instanceof AllocationExpression && arg.resolvedType instanceof ReferenceBinding) { |
271 |
// nested allocation |
272 |
ReferenceBinding innerType = (ReferenceBinding)arg.resolvedType; |
273 |
if (innerType.hasTypeBit(TypeIds.BitResourceFreeCloseable)) { |
274 |
return null; // leaf of wrapper-chain is irrelevant |
275 |
} else if (innerType.hasTypeBit(TypeIds.BitWrapperCloseable)) { |
276 |
// nested wrapper -> nested tracking variables may skip this level as it is not bound to a local variable |
277 |
Expression[] args = ((AllocationExpression) arg).arguments; |
278 |
if (args != null && args.length > 0) |
279 |
return analyseCloseableAllocationArgument(flowInfo, outerLocal, arg, args[0], previousTracker); |
280 |
return null; // wrapper with no arg? shouldn't occur actually |
281 |
} else { |
282 |
// (c) wrapper alloc with direct nested alloc of regular: -> normal track var (no local represents inner) |
283 |
return previousTracker != null ? previousTracker : new FakedTrackingVariable(outerLocal, outerLocation); |
284 |
} |
285 |
} |
286 |
return null; |
287 |
} |
288 |
|
289 |
// an outer allocation expression has an argument, create/link outer and inner tracking variable and return the outer |
290 |
// return null if inner is not tracked |
291 |
private static FakedTrackingVariable getTrackingVarForNested(FlowInfo flowInfo, LocalVariableBinding outerLocal, ASTNode outerLocation, |
292 |
LocalVariableBinding innerLocal, ASTNode innerLocation, Expression innerExpression, FakedTrackingVariable previousTracker) |
293 |
{ |
294 |
if (outerLocal == innerLocal) |
295 |
return SELF_WRAP; |
296 |
FakedTrackingVariable innerTracker = analyseCloseableExpression(flowInfo, innerLocal, innerLocation, innerExpression, null); |
297 |
if (innerTracker == SELF_WRAP) |
298 |
return SELF_WRAP; |
299 |
if (innerTracker != null) { |
300 |
FakedTrackingVariable outerTracker = previousTracker != null ? previousTracker : new FakedTrackingVariable(outerLocal, outerLocation); |
301 |
outerTracker.innerTracker = innerTracker; |
302 |
innerTracker.globalClosingState |= WRAPPED; |
303 |
return outerTracker; |
304 |
} |
305 |
return null; |
156 |
} |
306 |
} |
157 |
|
307 |
|
158 |
/** Answer wither the given type binding is a subtype of java.lang.AutoCloseable. */ |
308 |
/** Answer wither the given type binding is a subtype of java.lang.AutoCloseable. */ |
159 |
public static boolean isAutoCloseable(TypeBinding typeBinding) { |
309 |
public static boolean isAutoCloseable(TypeBinding typeBinding) { |
160 |
return typeBinding instanceof ReferenceBinding |
310 |
return typeBinding instanceof ReferenceBinding |
161 |
&& ((ReferenceBinding)typeBinding).hasTypeBit(TypeIds.BitAutoCloseable|TypeIds.BitCloseable); |
311 |
&& ((ReferenceBinding)typeBinding).hasTypeBit(TypeIds.BitAutoCloseable|TypeIds.BitCloseable); |
|
|
312 |
} |
313 |
|
314 |
public int findMostSpecificStatus(FlowInfo flowInfo, BlockScope currentScope, BlockScope locationScope) { |
315 |
int status = FlowInfo.UNKNOWN; |
316 |
FakedTrackingVariable currentTracker = this; |
317 |
// loop as to consider wrappers (per white list) encapsulating an inner resource. |
318 |
while (currentTracker != null) { |
319 |
LocalVariableBinding currentVar = currentTracker.binding; |
320 |
int currentStatus = getNullStatusAggressively(currentVar, flowInfo); |
321 |
if (locationScope != null) // only check at method exit points |
322 |
currentStatus = mergeCloseStatus(locationScope, currentStatus, currentVar, currentScope); |
323 |
if (currentStatus == FlowInfo.NON_NULL) { |
324 |
status = currentStatus; |
325 |
break; // closed -> stop searching |
326 |
} else if (status == FlowInfo.NULL || status == FlowInfo.UNKNOWN) { |
327 |
status = currentStatus; // improved although not yet safe -> keep searching for better |
328 |
} |
329 |
currentTracker = currentTracker.innerTracker; |
330 |
} |
331 |
return status; |
332 |
} |
333 |
|
334 |
/** |
335 |
* Get the null status looking even into unreachable flows |
336 |
* @param local |
337 |
* @param flowInfo |
338 |
* @return one of the constants FlowInfo.{NULL,POTENTIALLY_NULL,POTENTIALLY_NON_NULL,NON_NULL}. |
339 |
*/ |
340 |
private int getNullStatusAggressively(LocalVariableBinding local, FlowInfo flowInfo) { |
341 |
int reachMode = flowInfo.reachMode(); |
342 |
int status = 0; |
343 |
try { |
344 |
// unreachable flowInfo is too shy in reporting null-issues, temporarily forget reachability: |
345 |
if (reachMode != FlowInfo.REACHABLE) |
346 |
flowInfo.tagBits &= ~FlowInfo.UNREACHABLE; |
347 |
status = flowInfo.nullStatus(local); |
348 |
} finally { |
349 |
// reset |
350 |
flowInfo.tagBits |= reachMode; |
351 |
} |
352 |
// at this point some combinations are not useful so flatten to a single bit: |
353 |
if ((status & FlowInfo.NULL) != 0) { |
354 |
if ((status & (FlowInfo.NON_NULL | FlowInfo.POTENTIALLY_NON_NULL)) != 0) |
355 |
return FlowInfo.POTENTIALLY_NULL; // null + doubt = pot null |
356 |
return FlowInfo.NULL; |
357 |
} else if ((status & FlowInfo.NON_NULL) != 0) { |
358 |
if ((status & FlowInfo.POTENTIALLY_NULL) != 0) |
359 |
return FlowInfo.POTENTIALLY_NULL; // non-null + doubt = pot null |
360 |
return FlowInfo.NON_NULL; |
361 |
} else if ((status & FlowInfo.POTENTIALLY_NULL) != 0) |
362 |
return FlowInfo.POTENTIALLY_NULL; |
363 |
return status; |
364 |
} |
365 |
|
366 |
public int mergeCloseStatus(BlockScope currentScope, int status, LocalVariableBinding local, BlockScope outerScope) { |
367 |
// get the most suitable null status representing whether resource 'binding' has been closed |
368 |
// start at 'currentScope' and potentially travel out until 'outerScope' |
369 |
// at each scope consult any recorded 'finallyInfo'. |
370 |
if (status != FlowInfo.NON_NULL) { |
371 |
if (currentScope.finallyInfo != null) { |
372 |
int finallyStatus = currentScope.finallyInfo.nullStatus(local); |
373 |
if (finallyStatus == FlowInfo.NON_NULL) |
374 |
return finallyStatus; |
375 |
if (finallyStatus != FlowInfo.NULL) // neither is NON_NULL, but not both are NULL => call it POTENTIALLY_NULL |
376 |
status = FlowInfo.POTENTIALLY_NULL; |
377 |
} |
378 |
if (currentScope != outerScope && currentScope.parent instanceof BlockScope) |
379 |
return mergeCloseStatus(((BlockScope) currentScope.parent), status, local, outerScope); |
380 |
} |
381 |
return status; |
162 |
} |
382 |
} |
163 |
|
383 |
|
164 |
/** Mark that this resource is closed locally. */ |
384 |
/** Mark that this resource is closed locally. */ |
Lines 180-186
Link Here
|
180 |
* (as argument to a method/ctor call or as a return value from the current method), |
400 |
* (as argument to a method/ctor call or as a return value from the current method), |
181 |
* and thus should be considered as potentially closed. |
401 |
* and thus should be considered as potentially closed. |
182 |
*/ |
402 |
*/ |
183 |
public static FlowInfo markPassedToOutside(BlockScope scope, Expression expression, FlowInfo flowInfo) { |
403 |
public static FlowInfo markPassedToOutside(BlockScope scope, Expression expression, FlowInfo flowInfo, TypeBinding allocatedType) { |
|
|
404 |
if ((allocatedType instanceof ReferenceBinding) |
405 |
&& ((ReferenceBinding) allocatedType).hasTypeBit(TypeIds.BitWrapperCloseable)) |
406 |
return flowInfo; // wrapped closeables are analyzed separately: |
407 |
|
184 |
FakedTrackingVariable trackVar = getCloseTrackingVariable(expression); |
408 |
FakedTrackingVariable trackVar = getCloseTrackingVariable(expression); |
185 |
if (trackVar != null) { |
409 |
if (trackVar != null) { |
186 |
trackVar.globalClosingState |= PASSED_TO_OUTSIDE; |
410 |
trackVar.globalClosingState |= PASSED_TO_OUTSIDE; |
Lines 201-209
Link Here
|
201 |
} |
425 |
} |
202 |
|
426 |
|
203 |
public boolean reportRecordedErrors(Scope scope) { |
427 |
public boolean reportRecordedErrors(Scope scope) { |
204 |
if (this.globalClosingState == 0) { |
428 |
FakedTrackingVariable current = this; |
205 |
reportError(scope.problemReporter(), null, FlowInfo.NULL); |
429 |
while (current.globalClosingState == 0) { |
206 |
return true; |
430 |
current = current.innerTracker; |
|
|
431 |
if (current == null) { |
432 |
// no relevant state found -> report: |
433 |
reportError(scope.problemReporter(), null, FlowInfo.NULL); |
434 |
return true; |
435 |
} |
207 |
} |
436 |
} |
208 |
boolean hasReported = false; |
437 |
boolean hasReported = false; |
209 |
if (this.recordedLocations != null) { |
438 |
if (this.recordedLocations != null) { |
Lines 218-223
Link Here
|
218 |
} |
447 |
} |
219 |
|
448 |
|
220 |
public void reportError(ProblemReporter problemReporter, ASTNode location, int nullStatus) { |
449 |
public void reportError(ProblemReporter problemReporter, ASTNode location, int nullStatus) { |
|
|
450 |
if ((this.globalClosingState & WRAPPED) != 0) |
451 |
return; |
221 |
if (nullStatus == FlowInfo.NULL) { |
452 |
if (nullStatus == FlowInfo.NULL) { |
222 |
if ((this.globalClosingState & CLOSED_IN_NESTED_METHOD) != 0) |
453 |
if ((this.globalClosingState & CLOSED_IN_NESTED_METHOD) != 0) |
223 |
problemReporter.potentiallyUnclosedCloseable(this, location); |
454 |
problemReporter.potentiallyUnclosedCloseable(this, location); |
Lines 225-231
Link Here
|
225 |
problemReporter.unclosedCloseable(this, location); |
456 |
problemReporter.unclosedCloseable(this, location); |
226 |
} else if (nullStatus == FlowInfo.POTENTIALLY_NULL) { |
457 |
} else if (nullStatus == FlowInfo.POTENTIALLY_NULL) { |
227 |
problemReporter.potentiallyUnclosedCloseable(this, location); |
458 |
problemReporter.potentiallyUnclosedCloseable(this, location); |
228 |
} |
459 |
} |
229 |
} |
460 |
} |
230 |
|
461 |
|
231 |
public void reportExplicitClosing(ProblemReporter problemReporter) { |
462 |
public void reportExplicitClosing(ProblemReporter problemReporter) { |