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