CloseResourceRule.java
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(0instanceof ASTReferenceType) {
083                 ASTReferenceType ref = (ASTReferenceTypetype.jjtGetChild(0);
084                 if (ref.jjtGetChild(0instanceof ASTClassOrInterfaceType) {
085                     ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceTyperef.jjtGetChild(0);
086                     if (types.contains(clazz.getImage())) {
087                         ASTVariableDeclaratorId id = (ASTVariableDeclaratorIdvar.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((ASTLocalVariableDeclarationx.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 (!(instanceof ASTBlock)) {
110             n = n.jjtGetParent();
111         }
112 
113         ASTBlock top = (ASTBlockn;
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 = (ASTBlockt.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 = (ASTTypevar.jjtGetChild(0);
197             ASTReferenceType ref = (ASTReferenceTypetype.jjtGetChild(0);
198             ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceTyperef.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 }