Lines 41-67
Link Here
|
41 |
|
41 |
|
42 |
// a call to close() was seen at least on one path: |
42 |
// a call to close() was seen at least on one path: |
43 |
private static final int CLOSE_SEEN = 1; |
43 |
private static final int CLOSE_SEEN = 1; |
44 |
// the resource was passed to outside code (arg in method/ctor call or as a return value from this method): |
44 |
// the resource is shared with outside code either by |
45 |
private static final int PASSED_TO_OUTSIDE = 2; |
45 |
// - passing as an arg in a method call or |
46 |
// the resource was obtained from the outside (argument of the current method, or via a field read) |
46 |
// - obtaining this from a method call or array reference |
47 |
private static final int OBTAINED_FROM_OUTSIDE = 4; |
47 |
// Interpret that we may or may not be responsible for closing |
|
|
48 |
private static final int SHARED_WITH_OUTSIDE = 2; |
49 |
// the resource is likely owned by outside code (owner has responsibility to close): |
50 |
// - obtained as argument of the current method, or via a field read |
51 |
// - returned as the result of this method |
52 |
private static final int OWNED_BY_OUTSIDE = 4; |
48 |
// If close() is invoked from a nested method (inside a local type) report remaining problems only as potential: |
53 |
// If close() is invoked from a nested method (inside a local type) report remaining problems only as potential: |
49 |
private static final int CLOSED_IN_NESTED_METHOD = 8; |
54 |
private static final int CLOSED_IN_NESTED_METHOD = 8; |
50 |
// a location independent issue has been reported already against this resource: |
55 |
// explicit closing has been reported already against this resource: |
51 |
private static final int REPORTED = 16; |
56 |
private static final int REPORTED_EXPLICIT_CLOSE = 16; |
52 |
// a resource is wrapped in another resource: |
57 |
// a location independent potential problem has been reported against this resource: |
53 |
private static final int WRAPPED = 32; |
58 |
private static final int REPORTED_POTENTIAL_LEAK = 32; |
54 |
|
59 |
// a location independent definitive problem has been reported against this resource: |
55 |
private static final int DOUBT_MASK = CLOSE_SEEN | PASSED_TO_OUTSIDE | OBTAINED_FROM_OUTSIDE | CLOSED_IN_NESTED_METHOD | REPORTED; // not WRAPPED |
60 |
private static final int REPORTED_DEFINITIVE_LEAK = 64; |
56 |
|
61 |
|
57 |
/** |
62 |
/** |
58 |
* Bitset of {@link #CLOSE_SEEN}, {@link #PASSED_TO_OUTSIDE}, {@link #OBTAINED_FROM_OUTSIDE}, {@link #CLOSED_IN_NESTED_METHOD}, {@link #REPORTED} and {@link #WRAPPED}. |
63 |
* Bitset of {@link #CLOSE_SEEN}, {@link #SHARED_WITH_OUTSIDE}, {@link #OWNED_BY_OUTSIDE}, {@link #CLOSED_IN_NESTED_METHOD}, {@link #REPORTED_EXPLICIT_CLOSE}, {@link #REPORTED_POTENTIAL_LEAK} and {@link #REPORTED_DEFINITIVE_LEAK}. |
59 |
*/ |
64 |
*/ |
60 |
private int globalClosingState = 0; |
65 |
private int globalClosingState = 0; |
61 |
|
66 |
|
62 |
public LocalVariableBinding originalBinding; // the real local being tracked, can be null for preliminary track vars for allocation expressions |
67 |
public LocalVariableBinding originalBinding; // the real local being tracked, can be null for preliminary track vars for allocation expressions |
63 |
|
68 |
|
64 |
public FakedTrackingVariable innerTracker; // chained tracking variable of a chained (wrapped) resource |
69 |
public FakedTrackingVariable innerTracker; // chained tracking variable of a chained (wrapped) resource |
|
|
70 |
public FakedTrackingVariable outerTracker; // inverse of 'innerTracker' |
65 |
|
71 |
|
66 |
MethodScope methodScope; // designates the method declaring this variable |
72 |
MethodScope methodScope; // designates the method declaring this variable |
67 |
|
73 |
|
Lines 139-145
Link Here
|
139 |
Statement location = local.declaration; |
145 |
Statement location = local.declaration; |
140 |
local.closeTracker = new FakedTrackingVariable(local, location); |
146 |
local.closeTracker = new FakedTrackingVariable(local, location); |
141 |
if (local.isParameter()) { |
147 |
if (local.isParameter()) { |
142 |
local.closeTracker.globalClosingState |= OBTAINED_FROM_OUTSIDE; |
148 |
local.closeTracker.globalClosingState |= OWNED_BY_OUTSIDE; |
143 |
// status of this tracker is now UNKNOWN |
149 |
// status of this tracker is now UNKNOWN |
144 |
} |
150 |
} |
145 |
return local.closeTracker; |
151 |
return local.closeTracker; |
Lines 169-174
Link Here
|
169 |
if (closeTracker == null) { |
175 |
if (closeTracker == null) { |
170 |
if (rhs.resolvedType != TypeBinding.NULL) { // not NULL means valid closeable as per method precondition |
176 |
if (rhs.resolvedType != TypeBinding.NULL) { // not NULL means valid closeable as per method precondition |
171 |
closeTracker = new FakedTrackingVariable(local, location); |
177 |
closeTracker = new FakedTrackingVariable(local, location); |
|
|
178 |
if (local.isParameter()) { |
179 |
closeTracker.globalClosingState |= OWNED_BY_OUTSIDE; |
180 |
} |
172 |
} |
181 |
} |
173 |
} |
182 |
} |
174 |
if (closeTracker != null) { |
183 |
if (closeTracker != null) { |
Lines 197-208
Link Here
|
197 |
if (innerTracker != null) { |
206 |
if (innerTracker != null) { |
198 |
if (innerTracker == allocation.closeTracker) |
207 |
if (innerTracker == allocation.closeTracker) |
199 |
return; // self wrap (res = new Res(res)) -> neither change (here) nor remove (below) |
208 |
return; // self wrap (res = new Res(res)) -> neither change (here) nor remove (below) |
|
|
209 |
int newStatus = FlowInfo.NULL; |
200 |
if (allocation.closeTracker == null) { |
210 |
if (allocation.closeTracker == null) { |
201 |
allocation.closeTracker = new FakedTrackingVariable(scope, allocation); // no local available, closeable is unassigned |
211 |
allocation.closeTracker = new FakedTrackingVariable(scope, allocation); // no local available, closeable is unassigned |
|
|
212 |
} else { |
213 |
if (scope.finallyInfo != null) { |
214 |
// inject results from analysing a finally block onto the newly connected wrapper |
215 |
int finallyStatus = scope.finallyInfo.nullStatus(allocation.closeTracker.binding); |
216 |
if (finallyStatus != FlowInfo.UNKNOWN) |
217 |
newStatus = finallyStatus; |
218 |
} |
202 |
} |
219 |
} |
203 |
allocation.closeTracker.innerTracker = innerTracker; |
220 |
allocation.closeTracker.innerTracker = innerTracker; |
204 |
innerTracker.globalClosingState |= WRAPPED; |
221 |
innerTracker.outerTracker = allocation.closeTracker; |
205 |
flowInfo.markAsDefinitelyNull(allocation.closeTracker.binding); |
222 |
flowInfo.markNullStatus(allocation.closeTracker.binding, newStatus); |
|
|
223 |
if (newStatus != FlowInfo.NULL) { |
224 |
// propagate results from a finally block also into nested resources: |
225 |
FakedTrackingVariable currentTracker = innerTracker; |
226 |
while (currentTracker != null) { |
227 |
flowInfo.markNullStatus(currentTracker.binding, newStatus); |
228 |
currentTracker = currentTracker.innerTracker; |
229 |
} |
230 |
} |
206 |
return; // keep chaining wrapper |
231 |
return; // keep chaining wrapper |
207 |
} |
232 |
} |
208 |
} |
233 |
} |
Lines 216-221
Link Here
|
216 |
if (presetTracker != null && presetTracker.originalBinding != null) { |
241 |
if (presetTracker != null && presetTracker.originalBinding != null) { |
217 |
int closeStatus = flowInfo.nullStatus(presetTracker.binding); |
242 |
int closeStatus = flowInfo.nullStatus(presetTracker.binding); |
218 |
if (closeStatus != FlowInfo.NON_NULL |
243 |
if (closeStatus != FlowInfo.NON_NULL |
|
|
244 |
&& closeStatus != FlowInfo.UNKNOWN |
219 |
&& !flowInfo.isDefinitelyNull(presetTracker.originalBinding) |
245 |
&& !flowInfo.isDefinitelyNull(presetTracker.originalBinding) |
220 |
&& !(presetTracker.currentAssignment instanceof LocalDeclaration)) |
246 |
&& !(presetTracker.currentAssignment instanceof LocalDeclaration)) |
221 |
allocation.closeTracker.recordErrorLocation(presetTracker.currentAssignment, closeStatus); |
247 |
allocation.closeTracker.recordErrorLocation(presetTracker.currentAssignment, closeStatus); |
Lines 291-297
Link Here
|
291 |
// keep close-status of RHS unchanged across this assignment |
317 |
// keep close-status of RHS unchanged across this assignment |
292 |
} else if (previousTracker != null) { // 2. re-use tracking variable from the LHS? |
318 |
} else if (previousTracker != null) { // 2. re-use tracking variable from the LHS? |
293 |
// re-assigning from a fresh value, mark as not-closed again: |
319 |
// re-assigning from a fresh value, mark as not-closed again: |
294 |
if ((previousTracker.globalClosingState & (PASSED_TO_OUTSIDE|OBTAINED_FROM_OUTSIDE)) == 0) |
320 |
if ((previousTracker.globalClosingState & (SHARED_WITH_OUTSIDE|OWNED_BY_OUTSIDE)) == 0) |
295 |
flowInfo.markAsDefinitelyNull(previousTracker.binding); |
321 |
flowInfo.markAsDefinitelyNull(previousTracker.binding); |
296 |
local.closeTracker = analyseCloseableExpression(flowInfo, local, location, rhs, previousTracker); |
322 |
local.closeTracker = analyseCloseableExpression(flowInfo, local, location, rhs, previousTracker); |
297 |
} else { // 3. no re-use, create a fresh tracking variable: |
323 |
} else { // 3. no re-use, create a fresh tracking variable: |
Lines 299-305
Link Here
|
299 |
if (rhsTrackVar != null) { |
325 |
if (rhsTrackVar != null) { |
300 |
local.closeTracker = rhsTrackVar; |
326 |
local.closeTracker = rhsTrackVar; |
301 |
// a fresh resource, mark as not-closed: |
327 |
// a fresh resource, mark as not-closed: |
302 |
if ((rhsTrackVar.globalClosingState & (PASSED_TO_OUTSIDE|OBTAINED_FROM_OUTSIDE)) == 0) |
328 |
if ((rhsTrackVar.globalClosingState & (SHARED_WITH_OUTSIDE|OWNED_BY_OUTSIDE)) == 0) |
303 |
flowInfo.markAsDefinitelyNull(rhsTrackVar.binding); |
329 |
flowInfo.markAsDefinitelyNull(rhsTrackVar.binding); |
304 |
// TODO(stephan): this might be useful, but I could not find a test case for it: |
330 |
// TODO(stephan): this might be useful, but I could not find a test case for it: |
305 |
// if (flowContext.initsOnFinally != null) |
331 |
// if (flowContext.initsOnFinally != null) |
Lines 308-315
Link Here
|
308 |
} |
334 |
} |
309 |
} |
335 |
} |
310 |
|
336 |
|
311 |
if (disconnectedTracker != null) |
337 |
if (disconnectedTracker != null) { |
312 |
disconnectedTracker.recordErrorLocation(location, upstreamInfo.nullStatus(disconnectedTracker.binding)); |
338 |
int upstreamStatus = upstreamInfo.nullStatus(disconnectedTracker.binding); |
|
|
339 |
if (upstreamStatus != FlowInfo.NON_NULL) |
340 |
disconnectedTracker.recordErrorLocation(location, upstreamStatus); |
341 |
} |
313 |
} |
342 |
} |
314 |
/** |
343 |
/** |
315 |
* Analyze structure of a closeable expression, matching (chained) resources against our white lists. |
344 |
* Analyze structure of a closeable expression, matching (chained) resources against our white lists. |
Lines 348-361
Link Here
|
348 |
{ |
377 |
{ |
349 |
// we *might* be responsible for the resource obtained |
378 |
// we *might* be responsible for the resource obtained |
350 |
FakedTrackingVariable tracker = new FakedTrackingVariable(local, location); |
379 |
FakedTrackingVariable tracker = new FakedTrackingVariable(local, location); |
351 |
tracker.globalClosingState |= PASSED_TO_OUTSIDE; |
380 |
tracker.globalClosingState |= SHARED_WITH_OUTSIDE; |
352 |
flowInfo.markPotentiallyNullBit(tracker.binding); // shed some doubt |
381 |
flowInfo.markPotentiallyNullBit(tracker.binding); // shed some doubt |
353 |
return tracker; |
382 |
return tracker; |
354 |
} else if ((expression.bits & RestrictiveFlagMASK) == Binding.FIELD) |
383 |
} else if ((expression.bits & RestrictiveFlagMASK) == Binding.FIELD) |
355 |
{ |
384 |
{ |
356 |
// responsibility for this resource probably lies at a higher level |
385 |
// responsibility for this resource probably lies at a higher level |
357 |
FakedTrackingVariable tracker = new FakedTrackingVariable(local, location); |
386 |
FakedTrackingVariable tracker = new FakedTrackingVariable(local, location); |
358 |
tracker.globalClosingState |= OBTAINED_FROM_OUTSIDE; |
387 |
tracker.globalClosingState |= OWNED_BY_OUTSIDE; |
359 |
// leave state as UNKNOWN, the bit OBTAINED_FROM_OUTSIDE will prevent spurious warnings |
388 |
// leave state as UNKNOWN, the bit OBTAINED_FROM_OUTSIDE will prevent spurious warnings |
360 |
return tracker; |
389 |
return tracker; |
361 |
} |
390 |
} |
Lines 477-487
Link Here
|
477 |
|
506 |
|
478 |
/** Mark that this resource is closed locally. */ |
507 |
/** Mark that this resource is closed locally. */ |
479 |
public void markClose(FlowInfo flowInfo, FlowContext flowContext) { |
508 |
public void markClose(FlowInfo flowInfo, FlowContext flowContext) { |
480 |
flowInfo.markAsDefinitelyNonNull(this.binding); |
509 |
FakedTrackingVariable current = this; |
481 |
this.globalClosingState |= CLOSE_SEEN; |
510 |
while (current != null) { |
|
|
511 |
flowInfo.markAsDefinitelyNonNull(current.binding); |
512 |
current.globalClosingState |= CLOSE_SEEN; |
482 |
//TODO(stephan): this might be useful, but I could not find a test case for it: |
513 |
//TODO(stephan): this might be useful, but I could not find a test case for it: |
483 |
// if (flowContext.initsOnFinally != null) |
514 |
// if (flowContext.initsOnFinally != null) |
484 |
// flowContext.initsOnFinally.markAsDefinitelyNonNull(this.binding); |
515 |
// flowContext.initsOnFinally.markAsDefinitelyNonNull(this.binding); |
|
|
516 |
current = current.innerTracker; |
517 |
} |
485 |
} |
518 |
} |
486 |
|
519 |
|
487 |
/** Mark that this resource is closed from a nested method (inside a local class). */ |
520 |
/** Mark that this resource is closed from a nested method (inside a local class). */ |
Lines 503-509
Link Here
|
503 |
scope.removeTrackingVar(trackVar); |
536 |
scope.removeTrackingVar(trackVar); |
504 |
return flowInfo; |
537 |
return flowInfo; |
505 |
} |
538 |
} |
506 |
trackVar.globalClosingState |= PASSED_TO_OUTSIDE; |
539 |
trackVar.globalClosingState |= SHARED_WITH_OUTSIDE; |
507 |
if (scope.methodScope() != trackVar.methodScope) |
540 |
if (scope.methodScope() != trackVar.methodScope) |
508 |
trackVar.globalClosingState |= CLOSED_IN_NESTED_METHOD; |
541 |
trackVar.globalClosingState |= CLOSED_IN_NESTED_METHOD; |
509 |
// insert info that the tracked resource *may* be closed (by the target method, i.e.) |
542 |
// insert info that the tracked resource *may* be closed (by the target method, i.e.) |
Lines 522-528
Link Here
|
522 |
|
555 |
|
523 |
public boolean reportRecordedErrors(Scope scope) { |
556 |
public boolean reportRecordedErrors(Scope scope) { |
524 |
FakedTrackingVariable current = this; |
557 |
FakedTrackingVariable current = this; |
525 |
while ((current.globalClosingState & DOUBT_MASK) == 0) { |
558 |
while (current.globalClosingState == 0) { |
526 |
current = current.innerTracker; |
559 |
current = current.innerTracker; |
527 |
if (current == null) { |
560 |
if (current == null) { |
528 |
// no relevant state found -> report: |
561 |
// no relevant state found -> report: |
Lines 543-563
Link Here
|
543 |
} |
576 |
} |
544 |
|
577 |
|
545 |
public void reportError(ProblemReporter problemReporter, ASTNode location, int nullStatus) { |
578 |
public void reportError(ProblemReporter problemReporter, ASTNode location, int nullStatus) { |
546 |
if ((this.globalClosingState & WRAPPED) != 0) |
579 |
// which degree of problem? |
547 |
return; |
580 |
boolean isPotentialProblem = false; |
548 |
if (nullStatus == FlowInfo.NULL) { |
581 |
if (nullStatus == FlowInfo.NULL) { |
549 |
if ((this.globalClosingState & CLOSED_IN_NESTED_METHOD) != 0) |
582 |
if ((this.globalClosingState & CLOSED_IN_NESTED_METHOD) != 0) |
550 |
problemReporter.potentiallyUnclosedCloseable(this, location); |
583 |
isPotentialProblem = true; |
551 |
else |
|
|
552 |
problemReporter.unclosedCloseable(this, location); |
553 |
} else if (nullStatus == FlowInfo.POTENTIALLY_NULL) { |
584 |
} else if (nullStatus == FlowInfo.POTENTIALLY_NULL) { |
554 |
problemReporter.potentiallyUnclosedCloseable(this, location); |
585 |
isPotentialProblem = true; |
|
|
586 |
} |
587 |
// report: |
588 |
if (isPotentialProblem) { |
589 |
if ((this.globalClosingState & (REPORTED_POTENTIAL_LEAK|REPORTED_DEFINITIVE_LEAK)) != 0) |
590 |
return; |
591 |
problemReporter.potentiallyUnclosedCloseable(this, location); |
592 |
} else { |
593 |
if ((this.globalClosingState & (REPORTED_DEFINITIVE_LEAK)) != 0) |
594 |
return; |
595 |
problemReporter.unclosedCloseable(this, location); |
596 |
} |
597 |
// propagate flag to inners: |
598 |
if (location == null) { |
599 |
int reportFlag = isPotentialProblem ? REPORTED_POTENTIAL_LEAK : REPORTED_DEFINITIVE_LEAK; |
600 |
FakedTrackingVariable current = this; |
601 |
while (current != null) { |
602 |
current.globalClosingState |= reportFlag; |
603 |
current = current.innerTracker; |
604 |
} |
555 |
} |
605 |
} |
556 |
} |
606 |
} |
557 |
|
607 |
|
558 |
public void reportExplicitClosing(ProblemReporter problemReporter) { |
608 |
public void reportExplicitClosing(ProblemReporter problemReporter) { |
559 |
if ((this.globalClosingState & (OBTAINED_FROM_OUTSIDE|REPORTED)) == 0) { // can't use t-w-r for OBTAINED_FROM_OUTSIDE |
609 |
if ((this.globalClosingState & (OWNED_BY_OUTSIDE|REPORTED_EXPLICIT_CLOSE)) == 0) { // can't use t-w-r for OWNED_BY_OUTSIDE |
560 |
this.globalClosingState |= REPORTED; |
610 |
this.globalClosingState |= REPORTED_EXPLICIT_CLOSE; |
561 |
problemReporter.explicitlyClosedAutoCloseable(this); |
611 |
problemReporter.explicitlyClosedAutoCloseable(this); |
562 |
} |
612 |
} |
563 |
} |
613 |
} |