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