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

Collapse All | Expand All

(-)a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TryWithResourcesStatementTest.java (-1 / +195 lines)
Lines 23-29 Link Here
23
public class TryWithResourcesStatementTest extends AbstractRegressionTest {
23
public class TryWithResourcesStatementTest extends AbstractRegressionTest {
24
24
25
static {
25
static {
26
//	TESTS_NAMES = new String[] { "test056zz"};
26
//	TESTS_NAMES = new String[] { "test056throw"};
27
//	TESTS_NUMBERS = new int[] { 50 };
27
//	TESTS_NUMBERS = new int[] { 50 };
28
//	TESTS_RANGE = new int[] { 11, -1 };
28
//	TESTS_RANGE = new int[] { 11, -1 };
29
}
29
}
Lines 4944-4949 Link Here
4944
		options);
4944
		options);
4945
}
4945
}
4946
// Bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points
4946
// Bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points
4947
// explicit throw is a true method exit here
4947
public void test056throw1() {
4948
public void test056throw1() {
4948
	Map options = getCompilerOptions();
4949
	Map options = getCompilerOptions();
4949
	options.put(JavaCore.COMPILER_PB_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
4950
	options.put(JavaCore.COMPILER_PB_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
Lines 4982-4987 Link Here
4982
		true,
4983
		true,
4983
		options);	
4984
		options);	
4984
}
4985
}
4986
// Bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points
4987
// close() within finally provides protection for throw
4988
public void test056throw2() {
4989
	Map options = getCompilerOptions();
4990
	options.put(JavaCore.COMPILER_PB_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
4991
	options.put(JavaCore.COMPILER_PB_POTENTIALLY_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
4992
	options.put(JavaCore.COMPILER_PB_EXPLICITLY_CLOSED_AUTOCLOSEABLE, CompilerOptions.ERROR);
4993
	options.put(JavaCore.COMPILER_PB_DEAD_CODE, CompilerOptions.ERROR);
4994
	this.runNegativeTest(
4995
		new String[] {
4996
			"X.java",
4997
			"import java.io.FileReader;\n" +
4998
			"public class X {\n" +
4999
			"    void foo1() throws Exception {\n" + 
5000
			"        FileReader reader = new FileReader(\"file\"); // propose t-w-r\n" + 
5001
			"        try {\n" + 
5002
			"            reader.read();\n" + 
5003
			"            return;\n" + 
5004
			"        } catch (Exception e) {\n" + 
5005
			"            throw new Exception();\n" + 
5006
			"        } finally {\n" + 
5007
			"            reader.close();\n" + 
5008
			"        }\n" + 
5009
			"    }\n" + 
5010
			"\n" + 
5011
			"    void foo2() throws Exception {\n" + 
5012
			"        FileReader reader = new FileReader(\"file\"); // propose t-w-r\n" + 
5013
			"        try {\n" + 
5014
			"            reader.read();\n" + 
5015
			"            throw new Exception(); // should not warn here\n" + 
5016
			"        } catch (Exception e) {\n" + 
5017
			"            throw new Exception();\n" + 
5018
			"        } finally {\n" + 
5019
			"            reader.close();\n" + 
5020
			"        }\n" + 
5021
			"    }\n" + 
5022
			"\n" + 
5023
			"    void foo3() throws Exception {\n" + 
5024
			"        FileReader reader = new FileReader(\"file\"); // propose t-w-r\n" + 
5025
			"        try {\n" + 
5026
			"            reader.read();\n" + 
5027
			"            throw new Exception();\n" + 
5028
			"        } finally {\n" + 
5029
			"            reader.close();\n" + 
5030
			"        }\n" + 
5031
			"    }\n" + 
5032
			"}\n"
5033
		},
5034
		"----------\n" +
5035
		"1. ERROR in X.java (at line 4)\n" +
5036
		"	FileReader reader = new FileReader(\"file\"); // propose t-w-r\n" +
5037
		"	           ^^^^^^\n" +
5038
		"Resource \'reader\' should be managed by try-with-resource\n" +
5039
		"----------\n" +
5040
		"2. ERROR in X.java (at line 16)\n" +
5041
		"	FileReader reader = new FileReader(\"file\"); // propose t-w-r\n" +
5042
		"	           ^^^^^^\n" +
5043
		"Resource \'reader\' should be managed by try-with-resource\n" +
5044
		"----------\n" +
5045
		"3. ERROR in X.java (at line 28)\n" +
5046
		"	FileReader reader = new FileReader(\"file\"); // propose t-w-r\n" +
5047
		"	           ^^^^^^\n" +
5048
		"Resource \'reader\' should be managed by try-with-resource\n" +
5049
		"----------\n",
5050
		null,
5051
		true,
5052
		options);	
5053
}
5054
// Bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points
5055
// close() nested within finally provides protection for throw
5056
public void test056throw3() {
5057
	Map options = getCompilerOptions();
5058
	options.put(JavaCore.COMPILER_PB_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
5059
	options.put(JavaCore.COMPILER_PB_POTENTIALLY_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
5060
	options.put(JavaCore.COMPILER_PB_EXPLICITLY_CLOSED_AUTOCLOSEABLE, CompilerOptions.ERROR);
5061
	options.put(JavaCore.COMPILER_PB_DEAD_CODE, CompilerOptions.ERROR);
5062
	this.runNegativeTest(
5063
		new String[] {
5064
			"X.java",
5065
			"import java.io.FileReader;\n" +
5066
			"public class X {\n" +
5067
			"    void foo2x() throws Exception {\n" + 
5068
			"        FileReader reader = new FileReader(\"file\"); // propose t-w-r\n" + 
5069
			"        try {\n" + 
5070
			"            reader.read();\n" + 
5071
			"            throw new Exception(); // should not warn here\n" + 
5072
			"        } catch (Exception e) {\n" + 
5073
			"            throw new Exception();\n" + 
5074
			"        } finally {\n" +
5075
			"            if (reader != null)\n" +
5076
			"                 try {\n" + 
5077
			"                     reader.close();\n" +
5078
			"                 } catch (java.io.IOException io) {}\n" + 
5079
			"        }\n" + 
5080
			"    }\n" + 
5081
			"}\n"
5082
		},
5083
		"----------\n" + 
5084
		"1. ERROR in X.java (at line 4)\n" + 
5085
		"	FileReader reader = new FileReader(\"file\"); // propose t-w-r\n" + 
5086
		"	           ^^^^^^\n" + 
5087
		"Resource \'reader\' should be managed by try-with-resource\n" + 
5088
		"----------\n",
5089
		null,
5090
		true,
5091
		options);	
5092
}
5093
// Bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points
5094
// additional boolean should shed doubt on whether we reach the close() call
5095
public void test056throw4() {
5096
	Map options = getCompilerOptions();
5097
	options.put(JavaCore.COMPILER_PB_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
5098
	options.put(JavaCore.COMPILER_PB_POTENTIALLY_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
5099
	options.put(JavaCore.COMPILER_PB_EXPLICITLY_CLOSED_AUTOCLOSEABLE, CompilerOptions.ERROR);
5100
	options.put(JavaCore.COMPILER_PB_DEAD_CODE, CompilerOptions.ERROR);
5101
	this.runNegativeTest(
5102
		new String[] {
5103
			"X.java",
5104
			"import java.io.FileReader;\n" +
5105
			"public class X {\n" +
5106
			"    void foo2x(boolean b) throws Exception {\n" + 
5107
			"        FileReader reader = new FileReader(\"file\");\n" + 
5108
			"        try {\n" + 
5109
			"            reader.read();\n" + 
5110
			"            throw new Exception(); // should warn here\n" + 
5111
			"        } catch (Exception e) {\n" + 
5112
			"            throw new Exception(); // should warn here\n" + 
5113
			"        } finally {\n" +
5114
			"            if (reader != null && b)\n" + // this condition is too strong to protect reader
5115
			"                 try {\n" + 
5116
			"                     reader.close();\n" +
5117
			"                 } catch (java.io.IOException io) {}\n" + 
5118
			"        }\n" + 
5119
			"    }\n" + 
5120
			"}\n"
5121
		},
5122
		"----------\n" +
5123
		"1. ERROR in X.java (at line 7)\n" +
5124
		"	throw new Exception(); // should warn here\n" +
5125
		"	^^^^^^^^^^^^^^^^^^^^^^\n" +
5126
		"Potential resource leak: \'reader\' may not be closed at this location\n" +
5127
		"----------\n" +
5128
		"2. ERROR in X.java (at line 9)\n" +
5129
		"	throw new Exception(); // should warn here\n" +
5130
		"	^^^^^^^^^^^^^^^^^^^^^^\n" +
5131
		"Potential resource leak: \'reader\' may not be closed at this location\n" +
5132
		"----------\n",
5133
		null,
5134
		true,
5135
		options);	
5136
}
5137
// Bug 359334 - Analysis for resource leak warnings does not consider exceptions as method exit points
5138
// similar to test056throw3() but indirectly calling close(), so doubts remain.
5139
public void test056throw5() {
5140
	Map options = getCompilerOptions();
5141
	options.put(JavaCore.COMPILER_PB_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
5142
	options.put(JavaCore.COMPILER_PB_POTENTIALLY_UNCLOSED_CLOSEABLE, CompilerOptions.ERROR);
5143
	options.put(JavaCore.COMPILER_PB_EXPLICITLY_CLOSED_AUTOCLOSEABLE, CompilerOptions.ERROR);
5144
	options.put(JavaCore.COMPILER_PB_DEAD_CODE, CompilerOptions.ERROR);
5145
	this.runNegativeTest(
5146
		new String[] {
5147
			"X.java",
5148
			"import java.io.FileReader;\n" +
5149
			"public class X {\n" +
5150
			"    void foo2x() throws Exception {\n" +
5151
			"        FileReader reader = new FileReader(\"file\");\n" +
5152
			"        try {\n" +
5153
			"            reader.read();\n" +
5154
			"            throw new Exception(); // should warn 'may not' here\n" +
5155
			"        } catch (Exception e) {\n" +
5156
			"            throw new Exception(); // should warn 'may not' here\n" +
5157
			"        } finally {\n" +
5158
			"            doClose(reader);\n" +
5159
			"        }\n" +
5160
			"    }\n" +
5161
			"    void doClose(FileReader r) { try { r.close(); } catch (java.io.IOException ex) {}}\n" + 
5162
			"}\n"
5163
		},
5164
		"----------\n" +
5165
		"1. ERROR in X.java (at line 7)\n" +
5166
		"	throw new Exception(); // should warn \'may not\' here\n" +
5167
		"	^^^^^^^^^^^^^^^^^^^^^^\n" +
5168
		"Potential resource leak: \'reader\' may not be closed at this location\n" +
5169
		"----------\n" +
5170
		"2. ERROR in X.java (at line 9)\n" +
5171
		"	throw new Exception(); // should warn \'may not\' here\n" +
5172
		"	^^^^^^^^^^^^^^^^^^^^^^\n" +
5173
		"Potential resource leak: \'reader\' may not be closed at this location\n" +
5174
		"----------\n",
5175
		null,
5176
		true,
5177
		options);	
5178
}
4985
public static Class testClass() {
5179
public static Class testClass() {
4986
	return TryWithResourcesStatementTest.class;
5180
	return TryWithResourcesStatementTest.class;
4987
}
5181
}
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Block.java (-1 / +1 lines)
Lines 38-44 Link Here
38
		}
38
		}
39
	}
39
	}
40
	if (this.explicitDeclarations > 0) // if block has its own scope analyze tracking vars now:
40
	if (this.explicitDeclarations > 0) // if block has its own scope analyze tracking vars now:
41
		this.scope.checkUnclosedCloseables(flowInfo, null);
41
		this.scope.checkUnclosedCloseables(flowInfo, null, null);
42
	return flowInfo;
42
	return flowInfo;
43
}
43
}
44
/**
44
/**
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java (-1 / +1 lines)
Lines 167-173 Link Here
167
		this.globalClosingState |= CLOSE_SEEN;
167
		this.globalClosingState |= CLOSE_SEEN;
168
//TODO(stephan): this might be useful, but I could not find a test case for it: 
168
//TODO(stephan): this might be useful, but I could not find a test case for it: 
169
//		if (flowContext.initsOnFinally != null)
169
//		if (flowContext.initsOnFinally != null)
170
//			flowContext.initsOnFinally.markAsDefinitelyNonNull(this.binding);		
170
//			flowContext.initsOnFinally.markAsDefinitelyNonNull(this.binding);
171
	}
171
	}
172
172
173
	/** Mark that this resource is closed from a nested method (inside a local class). */
173
	/** Mark that this resource is closed from a nested method (inside a local class). */
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java (-1 / +1 lines)
Lines 135-141 Link Here
135
				}
135
				}
136
					
136
					
137
			}
137
			}
138
			this.scope.checkUnclosedCloseables(flowInfo, null/*don't report against a specific location*/);
138
			this.scope.checkUnclosedCloseables(flowInfo, null/*don't report against a specific location*/, null);
139
		} catch (AbortMethod e) {
139
		} catch (AbortMethod e) {
140
			this.ignoreFurtherInvestigation = true;
140
			this.ignoreFurtherInvestigation = true;
141
		}
141
		}
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java (-1 / +1 lines)
Lines 114-120 Link Here
114
			this.expression.bits |= ASTNode.IsReturnedValue;
114
			this.expression.bits |= ASTNode.IsReturnedValue;
115
		}
115
		}
116
	}
116
	}
117
	currentScope.checkUnclosedCloseables(flowInfo, this);
117
	currentScope.checkUnclosedCloseables(flowInfo, this, currentScope);
118
	return FlowInfo.DEAD_END;
118
	return FlowInfo.DEAD_END;
119
}
119
}
120
120
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Statement.java (-2 / +2 lines)
Lines 80-93 Link Here
80
			if (previousComplaintLevel < COMPLAINED_UNREACHABLE) {
80
			if (previousComplaintLevel < COMPLAINED_UNREACHABLE) {
81
				scope.problemReporter().unreachableCode(this);
81
				scope.problemReporter().unreachableCode(this);
82
				if (endOfBlock)
82
				if (endOfBlock)
83
					scope.checkUnclosedCloseables(flowInfo, null);
83
					scope.checkUnclosedCloseables(flowInfo, null, null);
84
			}
84
			}
85
			return COMPLAINED_UNREACHABLE;
85
			return COMPLAINED_UNREACHABLE;
86
		} else {
86
		} else {
87
			if (previousComplaintLevel < COMPLAINED_FAKE_REACHABLE) {
87
			if (previousComplaintLevel < COMPLAINED_FAKE_REACHABLE) {
88
				scope.problemReporter().fakeReachable(this);
88
				scope.problemReporter().fakeReachable(this);
89
				if (endOfBlock)
89
				if (endOfBlock)
90
					scope.checkUnclosedCloseables(flowInfo, null);
90
					scope.checkUnclosedCloseables(flowInfo, null, null);
91
			}
91
			}
92
			return COMPLAINED_FAKE_REACHABLE;
92
			return COMPLAINED_FAKE_REACHABLE;
93
		}
93
		}
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ThrowStatement.java (-1 / +1 lines)
Lines 36-42 Link Here
36
	this.exception.checkNPE(currentScope, flowContext, flowInfo);
36
	this.exception.checkNPE(currentScope, flowContext, flowInfo);
37
	// need to check that exception thrown is actually caught somewhere
37
	// need to check that exception thrown is actually caught somewhere
38
	flowContext.checkExceptionHandlers(this.exceptionType, this, flowInfo, currentScope);
38
	flowContext.checkExceptionHandlers(this.exceptionType, this, flowInfo, currentScope);
39
	currentScope.checkUnclosedCloseables(flowInfo, this);
39
	currentScope.checkUnclosedCloseables(flowInfo, this, currentScope);
40
	return FlowInfo.DEAD_END;
40
	return FlowInfo.DEAD_END;
41
}
41
}
42
42
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java (+8 lines)
Lines 238-243 Link Here
238
		if (subInfo == FlowInfo.DEAD_END) {
238
		if (subInfo == FlowInfo.DEAD_END) {
239
			this.bits |= ASTNode.IsSubRoutineEscaping;
239
			this.bits |= ASTNode.IsSubRoutineEscaping;
240
			this.scope.problemReporter().finallyMustCompleteNormally(this.finallyBlock);
240
			this.scope.problemReporter().finallyMustCompleteNormally(this.finallyBlock);
241
		} else {
242
			// for resource analysis we need the finallyInfo in these nested scopes:
243
			FlowInfo finallyInfo = subInfo.copy();
244
			this.tryBlock.scope.finallyInfo = finallyInfo;
245
			if (this.catchBlocks != null) {
246
				for (int i = 0; i < this.catchBlocks.length; i++)
247
					this.catchBlocks[i].scope.finallyInfo = finallyInfo;
248
			}
241
		}
249
		}
242
		this.subRoutineInits = subInfo;
250
		this.subRoutineInits = subInfo;
243
		// process the try block in a context handling the local exceptions.
251
		// process the try block in a context handling the local exceptions.
(-)a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BlockScope.java (-8 / +33 lines)
Lines 964-969 Link Here
964
}
964
}
965
965
966
private List trackingVariables; // can be null if no resources are tracked
966
private List trackingVariables; // can be null if no resources are tracked
967
/** Used only during analyseCode and only for checking if a resource was closed in a finallyBlock. */
968
public FlowInfo finallyInfo;
967
/**
969
/**
968
 * Register a tracking variable and compute its id.
970
 * Register a tracking variable and compute its id.
969
 */
971
 */
Lines 975-981 Link Here
975
	return outerMethodScope.analysisIndex + (outerMethodScope.trackVarCount++);
977
	return outerMethodScope.analysisIndex + (outerMethodScope.trackVarCount++);
976
	
978
	
977
}
979
}
978
/** When no longer interested in this tracking variable remove it. */
980
/** When are no longer interested in this tracking variable - remove it. */
979
public void removeTrackingVar(FakedTrackingVariable trackingVariable) {
981
public void removeTrackingVar(FakedTrackingVariable trackingVariable) {
980
	if (this.trackingVariables != null)
982
	if (this.trackingVariables != null)
981
		if (this.trackingVariables.remove(trackingVariable))
983
		if (this.trackingVariables.remove(trackingVariable))
Lines 987-997 Link Here
987
 * At the end of a block check the closing-status of all tracked closeables that are declared in this block.
989
 * At the end of a block check the closing-status of all tracked closeables that are declared in this block.
988
 * Also invoked when entering unreachable code.
990
 * Also invoked when entering unreachable code.
989
 */
991
 */
990
public void checkUnclosedCloseables(FlowInfo flowInfo, ASTNode location) {
992
public void checkUnclosedCloseables(FlowInfo flowInfo, ASTNode location, BlockScope locationScope) {
991
	if (this.trackingVariables == null) {
993
	if (this.trackingVariables == null) {
992
		// at a method return we also consider enclosing scopes
994
		// at a method return we also consider enclosing scopes
993
		if (location != null && this.parent instanceof BlockScope)
995
		if (location != null && this.parent instanceof BlockScope)
994
			((BlockScope) this.parent).checkUnclosedCloseables(flowInfo, location);
996
			((BlockScope) this.parent).checkUnclosedCloseables(flowInfo, location, locationScope);
995
		return;
997
		return;
996
	}
998
	}
997
	if (location != null && flowInfo.reachMode() != 0) return;
999
	if (location != null && flowInfo.reachMode() != 0) return;
Lines 1000-1013 Link Here
1000
		if (location != null && trackingVar.originalBinding != null && flowInfo.isDefinitelyNull(trackingVar.originalBinding))
1002
		if (location != null && trackingVar.originalBinding != null && flowInfo.isDefinitelyNull(trackingVar.originalBinding))
1001
			continue; // reporting against a specific location, resource is null at this flow, don't complain
1003
			continue; // reporting against a specific location, resource is null at this flow, don't complain
1002
		int status = getNullStatusAggressively(trackingVar.binding, flowInfo);
1004
		int status = getNullStatusAggressively(trackingVar.binding, flowInfo);
1005
		// try to improve info if a close() inside finally was observed:
1006
		if (locationScope != null) // only check at method exit points
1007
			status = locationScope.mergeCloseStatus(status, trackingVar.binding, this);
1003
		if (status == FlowInfo.NULL) {
1008
		if (status == FlowInfo.NULL) {
1004
			// definitely unclosed: highest priority
1009
			// definitely unclosed: highest priority
1005
			reportResourceLeak(trackingVar, location, status);
1010
			reportResourceLeak(trackingVar, location, status);
1006
			if (location == null) {
1007
				// definitely done with this trackingVar, remove it
1008
				this.trackingVariables.remove(trackingVar);
1009
				i--; // ... but don't disturb the enclosing loop.
1010
			}
1011
			continue;
1011
			continue;
1012
		}
1012
		}
1013
		if (location == null) // at end of block and not definitely unclosed
1013
		if (location == null) // at end of block and not definitely unclosed
Lines 1025-1031 Link Here
1025
				trackingVar.reportExplicitClosing(problemReporter());
1025
				trackingVar.reportExplicitClosing(problemReporter());
1026
		}
1026
		}
1027
	}
1027
	}
1028
	if (location == null) {
1029
		// when leaving this block dispose off all tracking variables:
1030
		for (int i=0; i<this.localIndex; i++)
1031
			this.locals[i].closeTracker = null;		
1032
		this.trackingVariables = null;
1033
	}
1028
}
1034
}
1035
1036
private int mergeCloseStatus(int status, LocalVariableBinding binding, BlockScope outerScope) {
1037
	// get the most suitable null status representing whether resource 'binding' has been closed
1038
	// start at this scope and potentially travel out until 'outerScope'
1039
	// at each scope consult any recorded 'finallyInfo'.
1040
	if (status != FlowInfo.NON_NULL) {
1041
		if (this.finallyInfo != null) {
1042
			int finallyStatus = this.finallyInfo.nullStatus(binding);
1043
			if (finallyStatus == FlowInfo.NON_NULL)
1044
				return finallyStatus;
1045
			if (finallyStatus != FlowInfo.NULL) // neither is NON_NULL, but not both are NULL => call it POTENTIALLY_NULL
1046
				status = FlowInfo.POTENTIALLY_NULL;
1047
		}
1048
		if (this != outerScope && this.parent instanceof BlockScope)
1049
			return ((BlockScope) this.parent).mergeCloseStatus(status, binding, outerScope);
1050
	}
1051
	return status;
1052
}
1053
1029
private void reportResourceLeak(FakedTrackingVariable trackingVar, ASTNode location, int nullStatus) {
1054
private void reportResourceLeak(FakedTrackingVariable trackingVar, ASTNode location, int nullStatus) {
1030
	if (location != null)
1055
	if (location != null)
1031
		trackingVar.recordErrorLocation(location, nullStatus);
1056
		trackingVar.recordErrorLocation(location, nullStatus);

Return to bug 359334