1
2
3
4 package net.sourceforge.pmd.lang.java.rule.design;
5
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Set;
11
12 import net.sourceforge.pmd.lang.ast.Node;
13 import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
14 import net.sourceforge.pmd.lang.java.ast.ASTBlock;
15 import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
16 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
17 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
18 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
19 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
20 import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
21 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
22 import net.sourceforge.pmd.lang.java.ast.ASTName;
23 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
24 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
25 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
26 import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
27 import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
28 import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
29 import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
30 import net.sourceforge.pmd.lang.java.ast.ASTType;
31 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
32 import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
33 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
34 import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
35 import net.sourceforge.pmd.lang.rule.properties.StringMultiProperty;
36
37 import org.jaxen.JaxenException;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class CloseResourceRule extends AbstractJavaRule {
56
57 private Set<String> types = new HashSet<String>();
58 private Set<String> simpleTypes = new HashSet<String>();
59
60 private Set<String> closeTargets = new HashSet<String>();
61 private static final StringMultiProperty CLOSE_TARGETS_DESCRIPTOR = new StringMultiProperty("closeTargets",
62 "Methods which may close this resource", new String[] {}, 1.0f, ',');
63
64 private static final StringMultiProperty TYPES_DESCRIPTOR = new StringMultiProperty("types", "Affected types",
65 new String[] { "java.sql.Connection", "java.sql.Statement", "java.sql.ResultSet" }, 2.0f, ',');
66
67 private static final BooleanProperty USE_CLOSE_AS_DEFAULT_TARGET = new BooleanProperty("closeAsDefaultTarget",
68 "Consider 'close' as a target by default", true, 3.0f);
69
70 public CloseResourceRule() {
71 definePropertyDescriptor(CLOSE_TARGETS_DESCRIPTOR);
72 definePropertyDescriptor(TYPES_DESCRIPTOR);
73 definePropertyDescriptor(USE_CLOSE_AS_DEFAULT_TARGET);
74 }
75
76 @Override
77 public Object visit(ASTCompilationUnit node, Object data) {
78 if (closeTargets.isEmpty() && getProperty(CLOSE_TARGETS_DESCRIPTOR) != null) {
79 closeTargets.addAll(Arrays.asList(getProperty(CLOSE_TARGETS_DESCRIPTOR)));
80 }
81 if (getProperty(USE_CLOSE_AS_DEFAULT_TARGET) && !closeTargets.contains("close")) {
82 closeTargets.add("close");
83 }
84 if (types.isEmpty() && getProperty(TYPES_DESCRIPTOR) != null) {
85 types.addAll(Arrays.asList(getProperty(TYPES_DESCRIPTOR)));
86 }
87 if (simpleTypes.isEmpty() && getProperty(TYPES_DESCRIPTOR) != null) {
88 for (String type : getProperty(TYPES_DESCRIPTOR)) {
89 simpleTypes.add(toSimpleType(type));
90 }
91 }
92 return super.visit(node, data);
93 }
94
95 private static String toSimpleType(String fullyQualifiedClassName) {
96 int lastIndexOf = fullyQualifiedClassName.lastIndexOf('.');
97 if (lastIndexOf > -1) {
98 return fullyQualifiedClassName.substring(lastIndexOf + 1);
99 } else {
100 return fullyQualifiedClassName;
101 }
102 }
103
104 @Override
105 public Object visit(ASTConstructorDeclaration node, Object data) {
106 checkForResources(node, data);
107 return data;
108 }
109
110 @Override
111 public Object visit(ASTMethodDeclaration node, Object data) {
112 checkForResources(node, data);
113 return data;
114 }
115
116 private void checkForResources(Node node, Object data) {
117 List<ASTLocalVariableDeclaration> vars = node.findDescendantsOfType(ASTLocalVariableDeclaration.class);
118 List<ASTVariableDeclaratorId> ids = new ArrayList<ASTVariableDeclaratorId>();
119
120
121 for (ASTLocalVariableDeclaration var : vars) {
122 ASTType type = var.getTypeNode();
123
124 if (type.jjtGetChild(0) instanceof ASTReferenceType) {
125 ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
126 if (ref.jjtGetChild(0) instanceof ASTClassOrInterfaceType) {
127 ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);
128
129 if (clazz.getType() != null && types.contains(clazz.getType().getName()) || clazz.getType() == null
130 && simpleTypes.contains(toSimpleType(clazz.getImage())) || types.contains(clazz.getImage())) {
131
132 ASTVariableDeclaratorId id = var.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
133 ids.add(id);
134 }
135 }
136 }
137 }
138
139
140 for (ASTVariableDeclaratorId x : ids) {
141 ensureClosed((ASTLocalVariableDeclaration) x.jjtGetParent().jjtGetParent(), x, data);
142 }
143 }
144
145 private boolean hasNullInitializer(ASTLocalVariableDeclaration var) {
146 ASTVariableInitializer init = var.getFirstDescendantOfType(ASTVariableInitializer.class);
147 if (init != null) {
148 try {
149 List<?> nulls = init
150 .findChildNodesWithXPath("Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral");
151 return !nulls.isEmpty();
152 } catch (JaxenException e) {
153 return false;
154 }
155 }
156 return false;
157 }
158
159 private void ensureClosed(ASTLocalVariableDeclaration var, ASTVariableDeclaratorId id, Object data) {
160
161
162 String variableToClose = id.getImage();
163 Node n = var;
164
165 while (!(n instanceof ASTBlock) && !(n instanceof ASTConstructorDeclaration)) {
166 n = n.jjtGetParent();
167 }
168
169 Node top = n;
170
171 List<ASTTryStatement> tryblocks = top.findDescendantsOfType(ASTTryStatement.class);
172
173 boolean closed = false;
174
175 ASTBlockStatement parentBlock = id.getFirstParentOfType(ASTBlockStatement.class);
176
177
178
179
180 for (ASTTryStatement t : tryblocks) {
181
182
183
184
185 ASTBlockStatement tryBlock = t.getFirstParentOfType(ASTBlockStatement.class);
186 if (!hasNullInitializer(var)
187
188 && parentBlock.jjtGetParent() == tryBlock.jjtGetParent()) {
189
190 List<ASTBlockStatement> blocks = parentBlock.jjtGetParent().findChildrenOfType(ASTBlockStatement.class);
191 int parentBlockIndex = blocks.indexOf(parentBlock);
192 int tryBlockIndex = blocks.indexOf(tryBlock);
193 boolean criticalStatements = false;
194
195 for (int i = parentBlockIndex + 1; i < tryBlockIndex; i++) {
196
197 ASTLocalVariableDeclaration varDecl = blocks.get(i).getFirstDescendantOfType(
198 ASTLocalVariableDeclaration.class);
199 if (varDecl == null) {
200 criticalStatements = true;
201 break;
202 }
203 }
204 if (criticalStatements) {
205 break;
206 }
207 }
208
209 if (t.getBeginLine() > id.getBeginLine() && t.hasFinally()) {
210 ASTBlock f = (ASTBlock) t.getFinally().jjtGetChild(0);
211 List<ASTName> names = f.findDescendantsOfType(ASTName.class);
212 for (ASTName oName : names) {
213 String name = oName.getImage();
214 if (name != null && name.contains(".")) {
215 String[] parts = name.split("\\.");
216 if (parts.length == 2) {
217 String methodName = parts[1];
218 String varName = parts[0];
219 if (varName.equals(variableToClose) && closeTargets.contains(methodName)
220 && nullCheckIfCondition(f, oName, varName)) {
221 closed = true;
222 break;
223 }
224
225 }
226 }
227 }
228 if (closed) {
229 break;
230 }
231
232 List<ASTStatementExpression> exprs = new ArrayList<ASTStatementExpression>();
233 f.findDescendantsOfType(ASTStatementExpression.class, exprs, true);
234 for (ASTStatementExpression stmt : exprs) {
235 ASTPrimaryExpression expr = stmt.getFirstChildOfType(ASTPrimaryExpression.class);
236 if (expr != null) {
237 ASTPrimaryPrefix prefix = expr.getFirstChildOfType(ASTPrimaryPrefix.class);
238 ASTPrimarySuffix suffix = expr.getFirstChildOfType(ASTPrimarySuffix.class);
239 if (prefix != null && suffix != null) {
240 if (prefix.getImage() == null) {
241 ASTName prefixName = prefix.getFirstChildOfType(ASTName.class);
242 if (prefixName != null && closeTargets.contains(prefixName.getImage())) {
243
244
245
246
247 closed = variableIsPassedToMethod(expr, variableToClose);
248 if (closed) {
249 break;
250 }
251 }
252 } else if (suffix.getImage() != null) {
253 String prefixPlusSuffix = prefix.getImage() + "." + suffix.getImage();
254 if (closeTargets.contains(prefixPlusSuffix)) {
255
256
257
258 closed = variableIsPassedToMethod(expr, variableToClose);
259 if (closed) {
260 break;
261 }
262 }
263 }
264
265
266
267
268
269
270
271
272
273
274 if (!closed) {
275 List<ASTPrimarySuffix> suffixes = new ArrayList<ASTPrimarySuffix>();
276 expr.findDescendantsOfType(ASTPrimarySuffix.class, suffixes, true);
277 for (ASTPrimarySuffix oSuffix : suffixes) {
278 String suff = oSuffix.getImage();
279 if (closeTargets.contains(suff)) {
280 closed = variableIsPassedToMethod(expr, variableToClose);
281 if (closed) {
282 break;
283 }
284 }
285
286 }
287 }
288 }
289 }
290 }
291 if (closed) {
292 break;
293 }
294 }
295 }
296
297 if (!closed) {
298
299
300
301 List<ASTReturnStatement> returns = new ArrayList<ASTReturnStatement>();
302 top.findDescendantsOfType(ASTReturnStatement.class, returns, true);
303 for (ASTReturnStatement returnStatement : returns) {
304 ASTName name = returnStatement.getFirstDescendantOfType(ASTName.class);
305 if (name != null && name.getImage().equals(variableToClose)) {
306 closed = true;
307 break;
308 }
309 }
310 }
311
312
313 if (!closed) {
314 ASTType type = var.getFirstChildOfType(ASTType.class);
315 ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
316 ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);
317 addViolation(data, id, clazz.getImage());
318 }
319 }
320
321 private boolean variableIsPassedToMethod(ASTPrimaryExpression expr, String variable) {
322 List<ASTName> methodParams = new ArrayList<ASTName>();
323 expr.findDescendantsOfType(ASTName.class, methodParams, true);
324 for (ASTName pName : methodParams) {
325 String paramName = pName.getImage();
326
327
328 ASTArgumentList parentParam = pName.getFirstParentOfType(ASTArgumentList.class);
329 if (paramName.equals(variable) && parentParam != null) {
330 return true;
331 }
332 }
333 return false;
334 }
335
336 private ASTIfStatement findIfStatement(ASTBlock enclosingBlock, Node node) {
337 ASTIfStatement ifStatement = node.getFirstParentOfType(ASTIfStatement.class);
338 List<ASTIfStatement> allIfStatements = enclosingBlock.findDescendantsOfType(ASTIfStatement.class);
339 if (ifStatement != null && allIfStatements.contains(ifStatement)) {
340 return ifStatement;
341 }
342 return null;
343 }
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358 private boolean nullCheckIfCondition(ASTBlock enclosingBlock, Node node, String varName) {
359 ASTIfStatement ifStatement = findIfStatement(enclosingBlock, node);
360 if (ifStatement != null) {
361 try {
362
363 List<?> nodes = ifStatement.findChildNodesWithXPath("Expression/EqualityExpression[@Image='!=']"
364 + " [PrimaryExpression/PrimaryPrefix/Name[@Image='" + varName + "']]"
365 + " [PrimaryExpression/PrimaryPrefix/Literal/NullLiteral]");
366 return !nodes.isEmpty();
367 } catch (JaxenException e) {
368
369 }
370 }
371 return true;
372 }
373 }