001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.java.rule.design;
005
006 import java.util.ArrayList;
007 import java.util.Arrays;
008 import java.util.HashSet;
009 import java.util.List;
010 import java.util.Set;
011
012 import net.sourceforge.pmd.lang.ast.Node;
013 import net.sourceforge.pmd.lang.java.ast.ASTBlock;
014 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
015 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
016 import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
017 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
018 import net.sourceforge.pmd.lang.java.ast.ASTName;
019 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
020 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
021 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
022 import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
023 import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
024 import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
025 import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
026 import net.sourceforge.pmd.lang.java.ast.ASTType;
027 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
028 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
029 import net.sourceforge.pmd.lang.rule.properties.StringMultiProperty;
030
031 /**
032 * Makes sure you close your database connections. It does this by
033 * looking for code patterned like this:
034 * <pre>
035 * Connection c = X;
036 * try {
037 * // do stuff, and maybe catch something
038 * } finally {
039 * c.close();
040 * }
041 *
042 * @author original author unknown
043 * @author Contribution from Pierre Mathien
044 * </pre>
045 */
046 public class CloseResourceRule extends AbstractJavaRule {
047
048 private Set<String> types = new HashSet<String>();
049
050 private Set<String> closeTargets = new HashSet<String>();
051 private static final StringMultiProperty CLOSE_TARGETS_DESCRIPTOR = new StringMultiProperty("closeTargets",
052 "Methods which may close this resource", new String[]{}, 1.0f, ',');
053
054 private static final StringMultiProperty TYPES_DESCRIPTOR = new StringMultiProperty("types",
055 "Affected types", new String[]{"Connection","Statement","ResultSet"}, 2.0f, ',');
056
057 public CloseResourceRule() {
058 definePropertyDescriptor(CLOSE_TARGETS_DESCRIPTOR);
059 definePropertyDescriptor(TYPES_DESCRIPTOR);
060 }
061
062 @Override
063 public Object visit(ASTCompilationUnit node, Object data) {
064 if (closeTargets.isEmpty() && getProperty(CLOSE_TARGETS_DESCRIPTOR) != null) {
065 closeTargets.addAll(Arrays.asList(getProperty(CLOSE_TARGETS_DESCRIPTOR)));
066 }
067 if (types.isEmpty() && getProperty(TYPES_DESCRIPTOR) != null) {
068 types.addAll(Arrays.asList(getProperty(TYPES_DESCRIPTOR)));
069 }
070 return super.visit(node, data);
071 }
072
073 @Override
074 public Object visit(ASTMethodDeclaration node, Object data) {
075 List<ASTLocalVariableDeclaration> vars = node.findDescendantsOfType(ASTLocalVariableDeclaration.class);
076 List<ASTVariableDeclaratorId> ids = new ArrayList<ASTVariableDeclaratorId>();
077
078 // find all variable references to Connection objects
079 for (ASTLocalVariableDeclaration var: vars) {
080 ASTType type = var.getTypeNode();
081
082 if (type.jjtGetChild(0) instanceof ASTReferenceType) {
083 ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
084 if (ref.jjtGetChild(0) instanceof ASTClassOrInterfaceType) {
085 ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);
086 if (types.contains(clazz.getImage())) {
087 ASTVariableDeclaratorId id = (ASTVariableDeclaratorId) var.jjtGetChild(1).jjtGetChild(0);
088 ids.add(id);
089 }
090 }
091 }
092 }
093
094 // if there are connections, ensure each is closed.
095 for (ASTVariableDeclaratorId x : ids) {
096 ensureClosed((ASTLocalVariableDeclaration) x.jjtGetParent().jjtGetParent(), x, data);
097 }
098 return data;
099 }
100
101 private void ensureClosed(ASTLocalVariableDeclaration var,
102 ASTVariableDeclaratorId id, Object data) {
103 // What are the chances of a Connection being instantiated in a
104 // for-loop init block? Anyway, I'm lazy!
105 String variableToClose = id.getImage();
106 String target = variableToClose + ".close";
107 Node n = var;
108
109 while (!(n instanceof ASTBlock)) {
110 n = n.jjtGetParent();
111 }
112
113 ASTBlock top = (ASTBlock) n;
114
115 List<ASTTryStatement> tryblocks = top.findDescendantsOfType(ASTTryStatement.class);
116
117 boolean closed = false;
118
119 // look for try blocks below the line the variable was
120 // introduced and make sure there is a .close call in a finally
121 // block.
122 for (ASTTryStatement t : tryblocks) {
123 if (t.getBeginLine() > id.getBeginLine() && t.hasFinally()) {
124 ASTBlock f = (ASTBlock) t.getFinally().jjtGetChild(0);
125 List<ASTName> names = f.findDescendantsOfType(ASTName.class);
126 for (ASTName oName : names) {
127 String name = oName.getImage();
128 if (name.equals(target)) {
129 closed = true;
130 break;
131 }
132 }
133 if (closed) {
134 break;
135 }
136
137 List<ASTStatementExpression> exprs = new ArrayList<ASTStatementExpression>();
138 f.findDescendantsOfType(ASTStatementExpression.class, exprs, true);
139 for (ASTStatementExpression stmt : exprs) {
140 ASTPrimaryExpression expr =
141 stmt.getFirstChildOfType(ASTPrimaryExpression.class);
142 if (expr != null) {
143 ASTPrimaryPrefix prefix = expr.getFirstChildOfType(ASTPrimaryPrefix.class);
144 ASTPrimarySuffix suffix = expr.getFirstChildOfType(ASTPrimarySuffix.class);
145 if ((prefix != null) && (suffix != null)) {
146 if (prefix.getImage() == null) {
147 ASTName prefixName = prefix.getFirstChildOfType(ASTName.class);
148 if ((prefixName != null)
149 && closeTargets.contains(prefixName.getImage()))
150 {
151 // Found a call to a "close target" that is a direct
152 // method call without a "ClassName." prefix.
153 closed = variableIsPassedToMethod(expr, variableToClose);
154 if (closed) {
155 break;
156 }
157 }
158 } else if (suffix.getImage() != null) {
159 String prefixPlusSuffix =
160 prefix.getImage()+ "." + suffix.getImage();
161 if (closeTargets.contains(prefixPlusSuffix)) {
162 // Found a call to a "close target" that is a method call
163 // in the form "ClassName.methodName".
164 closed = variableIsPassedToMethod(expr, variableToClose);
165 if (closed) {
166 break;
167 }
168 }
169 }
170 }
171 }
172 }
173 if (closed) {
174 break;
175 }
176 }
177 }
178
179 if (!closed) {
180 // See if the variable is returned by the method, which means the
181 // method is a utility for creating the db resource, which means of
182 // course it can't be closed by the method, so it isn't an error.
183 List<ASTReturnStatement> returns = new ArrayList<ASTReturnStatement>();
184 top.findDescendantsOfType(ASTReturnStatement.class, returns, true);
185 for (ASTReturnStatement returnStatement : returns) {
186 ASTName name = returnStatement.getFirstChildOfType(ASTName.class);
187 if ((name != null) && name.getImage().equals(variableToClose)) {
188 closed = true;
189 break;
190 }
191 }
192 }
193
194 // if all is not well, complain
195 if (!closed) {
196 ASTType type = (ASTType) var.jjtGetChild(0);
197 ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
198 ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);
199 addViolation(data, id, clazz.getImage());
200 }
201 }
202
203 private boolean variableIsPassedToMethod(ASTPrimaryExpression expr, String variable) {
204 List<ASTName> methodParams = new ArrayList<ASTName>();
205 expr.findDescendantsOfType(ASTName.class, methodParams, true);
206 for (ASTName pName : methodParams) {
207 String paramName = pName.getImage();
208 if (paramName.equals(variable)) {
209 return true;
210 }
211 }
212 return false;
213 }
214 }
|