View | Details | Raw Unified | Return to bug 358903 | Differences between
and this patch

Collapse All | Expand All

(-)a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakTests.java (-2 / +37 lines)
Lines 26-32 Link Here
26
public class ResourceLeakTests extends AbstractRegressionTest {
26
public class ResourceLeakTests extends AbstractRegressionTest {
27
27
28
static {
28
static {
29
//	TESTS_NAMES = new String[] { "test063e"};
29
//	TESTS_NAMES = new String[] { "test056q"};
30
//	TESTS_NUMBERS = new int[] { 50 };
30
//	TESTS_NUMBERS = new int[] { 50 };
31
//	TESTS_RANGE = new int[] { 11, -1 };
31
//	TESTS_RANGE = new int[] { 11, -1 };
32
}
32
}
Lines 2392-2398 Link Here
2392
		options);
2392
		options);
2393
}
2393
}
2394
// Bug 358903 - Filter practically unimportant resource leak warnings
2394
// Bug 358903 - Filter practically unimportant resource leak warnings
2395
// a resource wrapper does not wrap a provided resource
2395
// a resource wrapper does not wrap any provided resource
2396
public void test061n() {
2396
public void test061n() {
2397
	Map options = getCompilerOptions();
2397
	Map options = getCompilerOptions();
2398
	options.put(CompilerOptions.OPTION_ReportUnclosedCloseable, CompilerOptions.ERROR);
2398
	options.put(CompilerOptions.OPTION_ReportUnclosedCloseable, CompilerOptions.ERROR);
Lines 2419-2424 Link Here
2419
		true,
2419
		true,
2420
		options);
2420
		options);
2421
}
2421
}
2422
// Bug 358903 - Filter practically unimportant resource leak warnings
2423
// a resource wrapper is closed only in its local block, underlying resource may leak
2424
public void test061o() {
2425
	Map options = getCompilerOptions();
2426
	options.put(CompilerOptions.OPTION_ReportUnclosedCloseable, CompilerOptions.ERROR);
2427
	options.put(CompilerOptions.OPTION_ReportPotentiallyUnclosedCloseable, CompilerOptions.ERROR);
2428
	this.runNegativeTest(
2429
		new String[] {
2430
			"X.java",
2431
			"import java.io.File;\n" +
2432
			"import java.io.FileInputStream;\n" +
2433
			"import java.io.BufferedInputStream;\n" +
2434
			"import java.io.IOException;\n" +
2435
			"public class X {\n" +
2436
			"    void foo(boolean bar) throws IOException {\n" + 
2437
			"        File file = new File(\"somefil\");\n" + 
2438
			"        FileInputStream fileStream  = new FileInputStream(file);\n" + 
2439
			"        BufferedInputStream bis = new BufferedInputStream(fileStream);   \n" + 
2440
			"        if (bar) {\n" + 
2441
			"            BufferedInputStream doubleWrap = new BufferedInputStream(bis);\n" + 
2442
			"            doubleWrap.close();\n" + 
2443
			"        }\n" + 
2444
			"    }\n" + 
2445
			"}\n"
2446
		},
2447
		"----------\n" +
2448
		"1. ERROR in X.java (at line 9)\n" +
2449
		"	BufferedInputStream bis = new BufferedInputStream(fileStream);   \n" +
2450
		"	                    ^^^\n" +
2451
		"Potential resource leak: \'bis\' may not be closed\n" +
2452
		"----------\n",
2453
		null,
2454
		true,
2455
		options);
2456
}
2422
// Bug 362331 - Resource leak not detected when closeable not assigned to variable
2457
// Bug 362331 - Resource leak not detected when closeable not assigned to variable
2423
// a resource is never assigned
2458
// a resource is never assigned
2424
public void test062a() throws IOException {
2459
public void test062a() throws IOException {
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java (-34 / +84 lines)
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
	}
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BlockScope.java (-3 / +10 lines)
Lines 15-21 Link Here
15
package org.eclipse.jdt.internal.compiler.lookup;
15
package org.eclipse.jdt.internal.compiler.lookup;
16
16
17
import java.util.ArrayList;
17
import java.util.ArrayList;
18
import java.util.HashSet;
18
import java.util.List;
19
import java.util.List;
20
import java.util.Set;
19
21
20
import org.eclipse.jdt.core.compiler.CharOperation;
22
import org.eclipse.jdt.core.compiler.CharOperation;
21
import org.eclipse.jdt.internal.compiler.ast.*;
23
import org.eclipse.jdt.internal.compiler.ast.*;
Lines 1003-1011 Link Here
1003
		return;
1005
		return;
1004
	}
1006
	}
1005
	if (location != null && flowInfo.reachMode() != 0) return;
1007
	if (location != null && flowInfo.reachMode() != 0) return;
1006
	int trackVarsLen = this.trackingVariables.size();
1008
	Set varSet = new HashSet(this.trackingVariables);
1007
	for (int i=0; i<trackVarsLen; i++) {
1009
	while (!varSet.isEmpty()) {
1008
		FakedTrackingVariable trackingVar = (FakedTrackingVariable) this.trackingVariables.get(i);
1010
		// pick one outer-most variable from the set
1011
		FakedTrackingVariable trackingVar = (FakedTrackingVariable) varSet.iterator().next();
1012
		while (trackingVar.outerTracker != null && varSet.contains(trackingVar.outerTracker)) {
1013
			trackingVar = trackingVar.outerTracker;
1014
		}
1015
		varSet.remove(trackingVar);
1009
		if (location != null && trackingVar.originalBinding != null && flowInfo.isDefinitelyNull(trackingVar.originalBinding))
1016
		if (location != null && trackingVar.originalBinding != null && flowInfo.isDefinitelyNull(trackingVar.originalBinding))
1010
			continue; // reporting against a specific location, resource is null at this flow, don't complain
1017
			continue; // reporting against a specific location, resource is null at this flow, don't complain
1011
		
1018
		

Return to bug 358903