2828import org .sonar .java .se .ConstraintManager ;
2929import org .sonar .java .se .ObjectConstraint ;
3030import org .sonar .java .se .ProgramState ;
31+ import org .sonar .java .se .ProgramState .ConstrainedValue ;
3132import org .sonar .java .se .SymbolicValue ;
3233import org .sonar .java .se .SymbolicValueFactory ;
3334import org .sonar .plugins .java .api .JavaFileScanner ;
4344import org .sonar .plugins .java .api .tree .NewClassTree ;
4445import org .sonar .plugins .java .api .tree .ReturnStatementTree ;
4546import org .sonar .plugins .java .api .tree .Tree ;
47+ import org .sonar .plugins .java .api .tree .TryStatementTree ;
48+ import org .sonar .plugins .java .api .tree .VariableTree ;
4649import org .sonar .squidbridge .annotations .ActivatedByDefault ;
4750import org .sonar .squidbridge .annotations .SqaleConstantRemediation ;
4851import org .sonar .squidbridge .annotations .SqaleSubCharacteristic ;
4952
53+ import javax .annotation .Nullable ;
54+
5055import java .util .Iterator ;
5156import java .util .List ;
5257import java .util .Map ;
6166@ SqaleConstantRemediation ("5min" )
6267public class UnclosedResourcesCheck extends SECheck implements JavaFileScanner {
6368
64- private enum States {
69+ private enum Status {
6570 OPENED , CLOSED ;
6671 }
6772
73+ private static final String JAVA_IO_AUTO_CLOSEABLE = "java.lang.AutoCloseable" ;
6874 private static final String JAVA_IO_CLOSEABLE = "java.io.Closeable" ;
6975 private static final String [] IGNORED_CLOSEABLE_SUBTYPES = {
7076 "java.io.ByteArrayOutputStream" ,
7177 "java.io.ByteArrayInputStream" ,
72- "java.io.StringReader" ,
73- "java.io.StringWriter" ,
7478 "java.io.CharArrayReader" ,
7579 "java.io.CharArrayWriter" ,
80+ "java.io.StringReader" ,
81+ "java.io.StringWriter" ,
7682 "com.sun.org.apache.xml.internal.security.utils.UnsyncByteArrayOutputStream"
7783 };
84+ private static final String JAVA_SQL_STATEMENT = "java.sql.Statement" ;
85+ private static final String [] JDBC_RESOURCE_CREATIONS = {"java.sql.Connection" , JAVA_SQL_STATEMENT };
7886
7987 static boolean needsClosing (Type type ) {
8088 for (String ignoredTypes : IGNORED_CLOSEABLE_SUBTYPES ) {
@@ -86,7 +94,7 @@ static boolean needsClosing(Type type) {
8694 }
8795
8896 static boolean isCloseable (Type type ) {
89- return type .isSubtypeOf (JAVA_IO_CLOSEABLE );
97+ return type .isSubtypeOf (JAVA_IO_AUTO_CLOSEABLE ) || type . isSubtypeOf ( JAVA_IO_CLOSEABLE );
9098 }
9199
92100 static boolean isOpeningResource (NewClassTree syntaxNode ) {
@@ -97,18 +105,33 @@ static boolean isOpeningResource(NewClassTree syntaxNode) {
97105 return needsClosing (type );
98106 }
99107
100- static boolean isWithinTryHeader (NewClassTree syntaxNode ) {
108+ static boolean isWithinTryHeader (Tree syntaxNode ) {
101109 final Tree parent = syntaxNode .parent ();
102- if (parent != null ) {
103- final Tree grandParent = parent .parent ();
104- if (grandParent != null ) {
105- final Tree greatGrandParent = grandParent .parent ();
106- return greatGrandParent != null && greatGrandParent .is (Tree .Kind .TRY_STATEMENT );
107- }
110+ if (parent .is (Tree .Kind .VARIABLE )) {
111+ return isTryStatementResource ((VariableTree ) parent );
108112 }
109113 return false ;
110114 }
111115
116+ static boolean isTryStatementResource (VariableTree variable ) {
117+ final TryStatementTree tryStatement = getEnclosingTryStatement (variable );
118+ if (tryStatement != null ) {
119+ return tryStatement .resources ().contains (variable );
120+ }
121+ return false ;
122+ }
123+
124+ private static TryStatementTree getEnclosingTryStatement (Tree syntaxNode ) {
125+ Tree parent = syntaxNode .parent ();
126+ while (parent != null ) {
127+ if (parent .is (Tree .Kind .TRY_STATEMENT )) {
128+ return (TryStatementTree ) parent ;
129+ }
130+ parent = parent .parent ();
131+ }
132+ return null ;
133+ }
134+
112135 private static class PostStatementVisitor extends CheckerTreeNodeVisitor {
113136
114137 public PostStatementVisitor (CheckerContext context ) {
@@ -120,10 +143,30 @@ public void visitNewClass(NewClassTree syntaxNode) {
120143 if (isOpeningResource (syntaxNode )) {
121144 final SymbolicValue instanceValue = programState .peekValue ();
122145 if (!(instanceValue instanceof ResourceWrapperSymbolicValue )) {
123- programState = programState .addConstraint (instanceValue , new ObjectConstraint (false , false , syntaxNode , States .OPENED ));
146+ programState = programState .addConstraint (instanceValue , new ObjectConstraint (false , false , syntaxNode , Status .OPENED ));
124147 }
125148 }
126149 }
150+
151+ @ Override
152+ public void visitMethodInvocation (MethodInvocationTree syntaxNode ) {
153+ if (syntaxNode .methodSelect ().is (Tree .Kind .MEMBER_SELECT ) && needsClosing (syntaxNode .symbolType ())) {
154+ final ExpressionTree targetExpression = ((MemberSelectExpressionTree ) syntaxNode .methodSelect ()).expression ();
155+ if (targetExpression .is (Tree .Kind .IDENTIFIER ) && !isWithinTryHeader (syntaxNode )
156+ && (syntaxNode .symbol ().isStatic () || isJdbcResourceCreation (targetExpression ))) {
157+ programState = programState .addConstraint (programState .peekValue (), new ObjectConstraint (false , false , syntaxNode , Status .OPENED ));
158+ }
159+ }
160+ }
161+
162+ private static boolean isJdbcResourceCreation (ExpressionTree targetExpression ) {
163+ for (String creator : JDBC_RESOURCE_CREATIONS ) {
164+ if (targetExpression .symbolType ().is (creator )) {
165+ return true ;
166+ }
167+ }
168+ return false ;
169+ }
127170 }
128171
129172 public static class ResourceWrapperSymbolicValue extends SymbolicValue {
@@ -135,11 +178,6 @@ public ResourceWrapperSymbolicValue(int id, SymbolicValue dependent) {
135178 this .dependent = dependent ;
136179 }
137180
138- @ Override
139- public boolean references (SymbolicValue other ) {
140- return dependent .equals (other ) || dependent .references (other );
141- }
142-
143181 @ Override
144182 public SymbolicValue wrappedValue () {
145183 return dependent .wrappedValue ();
@@ -163,6 +201,8 @@ public SymbolicValue createSymbolicValue(int counter, Tree syntaxNode) {
163201 static class PreStatementVisitor extends CheckerTreeNodeVisitor {
164202
165203 private static final String CLOSE_METHOD_NAME = "close" ;
204+ private static final String GET_MORE_RESULTS_METHOD_NAME = "getMoreResults" ;
205+ private static final String GET_RESULT_SET = "getResultSet" ;
166206
167207 private final ConstraintManager constraintManager ;
168208
@@ -221,23 +261,54 @@ public void visitAssignmentExpression(AssignmentExpressionTree syntaxNode) {
221261
222262 @ Override
223263 public void visitMethodInvocation (MethodInvocationTree syntaxNode ) {
264+ final ExpressionTree methodSelect = syntaxNode .methodSelect ();
224265 if (isClosingResource (syntaxNode .symbol ())) {
225- ExpressionTree methodSelect = syntaxNode .methodSelect ();
226266 if (methodSelect .is (Tree .Kind .MEMBER_SELECT )) {
227267 final ExpressionTree targetExpression = ((MemberSelectExpressionTree ) methodSelect ).expression ();
228268 if (targetExpression .is (Tree .Kind .IDENTIFIER )) {
229269 final IdentifierTree identifier = (IdentifierTree ) targetExpression ;
230- final SymbolicValue target = programState .getValue (identifier .symbol ());
231- programState = closeResource (programState , target );
270+ programState = closeResource (programState , programState .getValue (identifier .symbol ()));
232271 } else {
233272 programState = closeResource (programState , programState .peekValue ());
234273 }
235274 }
275+ } else if (syntaxNode .methodSelect ().is (Tree .Kind .MEMBER_SELECT ) && isOpeningResultSet (syntaxNode .symbol ())) {
276+ final SymbolicValue value = getTargetValue (syntaxNode );
277+ constraintManager .setValueFactory (new WrappedValueFactory (value ));
278+ } else if (isClosingResultSets (syntaxNode .symbol ())) {
279+ if (methodSelect .is (Tree .Kind .MEMBER_SELECT )) {
280+ final SymbolicValue value = getTargetValue (syntaxNode );
281+ closeResultSetsRelatedTo (value );
282+ }
236283 } else {
237284 closeArguments (syntaxNode .arguments (), 1 );
238285 }
239286 }
240287
288+ private SymbolicValue getTargetValue (MethodInvocationTree syntaxNode ) {
289+ final ExpressionTree targetExpression = ((MemberSelectExpressionTree ) syntaxNode .methodSelect ()).expression ();
290+ final SymbolicValue value ;
291+ if (targetExpression .is (Tree .Kind .IDENTIFIER )) {
292+ final IdentifierTree identifier = (IdentifierTree ) targetExpression ;
293+ value = programState .getValue (identifier .symbol ());
294+ } else {
295+ value = programState .peekValue ();
296+ }
297+ return value ;
298+ }
299+
300+ private void closeResultSetsRelatedTo (SymbolicValue value ) {
301+ final List <ConstrainedValue > constrainedValues = programState .getValuesWithConstraints (Status .OPENED );
302+ for (ConstrainedValue constrainedValue : constrainedValues ) {
303+ if (constrainedValue .value instanceof ResourceWrapperSymbolicValue ) {
304+ ResourceWrapperSymbolicValue rValue = (ResourceWrapperSymbolicValue ) constrainedValue .value ;
305+ if (value .equals (rValue .dependent )) {
306+ programState = programState .addConstraint (rValue , constrainedValue .constraint .withStatus (Status .CLOSED ));
307+ }
308+ }
309+ }
310+ }
311+
241312 private void closeArguments (final Arguments arguments , int stackOffset ) {
242313 final List <SymbolicValue > values = programState .peekValues (arguments .size () + stackOffset );
243314 final List <SymbolicValue > argumentValues = values .subList (stackOffset , values .size ());
@@ -246,11 +317,11 @@ private void closeArguments(final Arguments arguments, int stackOffset) {
246317 }
247318 }
248319
249- private static ProgramState closeResource (ProgramState programState , final SymbolicValue target ) {
320+ private static ProgramState closeResource (ProgramState programState , @ Nullable final SymbolicValue target ) {
250321 if (target != null ) {
251322 ObjectConstraint oConstraint = openedConstraint (programState , target );
252323 if (oConstraint != null ) {
253- return programState .addConstraint (target .wrappedValue (), oConstraint .withStatus (States .CLOSED ));
324+ return programState .addConstraint (target .wrappedValue (), oConstraint .withStatus (Status .CLOSED ));
254325 }
255326 }
256327 return programState ;
@@ -260,7 +331,7 @@ private static ObjectConstraint openedConstraint(ProgramState programState, Symb
260331 final Object constraint = programState .getConstraint (value .wrappedValue ());
261332 if (constraint instanceof ObjectConstraint ) {
262333 ObjectConstraint oConstraint = (ObjectConstraint ) constraint ;
263- if (oConstraint .hasStatus (States .OPENED )) {
334+ if (oConstraint .hasStatus (Status .OPENED )) {
264335 return oConstraint ;
265336 }
266337 }
@@ -270,6 +341,14 @@ private static ObjectConstraint openedConstraint(ProgramState programState, Symb
270341 private static boolean isClosingResource (Symbol methodInvocationSymbol ) {
271342 return methodInvocationSymbol .isMethodSymbol () && CLOSE_METHOD_NAME .equals (methodInvocationSymbol .name ());
272343 }
344+
345+ private static boolean isClosingResultSets (Symbol methodInvocationSymbol ) {
346+ return methodInvocationSymbol .isMethodSymbol () && GET_MORE_RESULTS_METHOD_NAME .equals (methodInvocationSymbol .name ());
347+ }
348+
349+ private static boolean isOpeningResultSet (Symbol methodInvocationSymbol ) {
350+ return methodInvocationSymbol .isMethodSymbol () && GET_RESULT_SET .equals (methodInvocationSymbol .name ());
351+ }
273352 }
274353
275354 @ Override
@@ -296,12 +375,18 @@ public ProgramState checkPostStatement(CheckerContext context, Tree syntaxNode)
296375
297376 @ Override
298377 public void checkEndOfExecutionPath (CheckerContext context , ConstraintManager constraintManager ) {
299- final List <ObjectConstraint > constraints = context .getState ().getFieldConstraints (States .OPENED );
378+ final List <ObjectConstraint > constraints = context .getState ().getFieldConstraints (Status .OPENED );
300379 for (ObjectConstraint constraint : constraints ) {
301380 Tree syntaxNode = constraint .syntaxNode ();
302- if (syntaxNode instanceof NewClassTree ) {
381+ if (syntaxNode . is ( Tree . Kind . NEW_CLASS ) ) {
303382 context .reportIssue (syntaxNode , this , "Close this \" " + ((NewClassTree ) syntaxNode ).identifier () + "\" ." );
383+ } else if (syntaxNode .is (Tree .Kind .METHOD_INVOCATION )) {
384+ context .reportIssue (syntaxNode , this , "Close this \" " + toString ((MethodInvocationTree ) syntaxNode ) + "\" ." );
304385 }
305386 }
306387 }
388+
389+ private static String toString (MethodInvocationTree syntaxNode ) {
390+ return syntaxNode .symbolType ().name ();
391+ }
307392}
0 commit comments