View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
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.ASTClassOrInterfaceType;
16  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
17  import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
18  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
19  import net.sourceforge.pmd.lang.java.ast.ASTName;
20  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
21  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
22  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
23  import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
24  import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
25  import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
26  import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
27  import net.sourceforge.pmd.lang.java.ast.ASTType;
28  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
29  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
30  import net.sourceforge.pmd.lang.rule.properties.StringMultiProperty;
31  
32  /**
33   * Makes sure you close your database connections. It does this by
34   * looking for code patterned like this:
35   * <pre>
36   *  Connection c = X;
37   *  try {
38   *   // do stuff, and maybe catch something
39   *  } finally {
40   *   c.close();
41   *  }
42   *
43   *  @author original author unknown
44   *  @author Contribution from Pierre Mathien
45   * </pre>
46   */
47  public class CloseResourceRule extends AbstractJavaRule {
48  
49      private Set<String> types = new HashSet<String>();
50  
51      private Set<String> closeTargets = new HashSet<String>();
52      private static final StringMultiProperty CLOSE_TARGETS_DESCRIPTOR = new StringMultiProperty("closeTargets",
53              "Methods which may close this resource", new String[]{}, 1.0f, ',');
54  
55      private static final StringMultiProperty TYPES_DESCRIPTOR = new StringMultiProperty("types",
56              "Affected types", new String[]{"Connection","Statement","ResultSet"}, 2.0f, ',');
57      
58      public CloseResourceRule() {
59  	definePropertyDescriptor(CLOSE_TARGETS_DESCRIPTOR);
60  	definePropertyDescriptor(TYPES_DESCRIPTOR);
61      }
62  
63      @Override
64      public Object visit(ASTCompilationUnit node, Object data) {
65          if (closeTargets.isEmpty() && getProperty(CLOSE_TARGETS_DESCRIPTOR) != null) {
66              closeTargets.addAll(Arrays.asList(getProperty(CLOSE_TARGETS_DESCRIPTOR)));
67          }
68          if (types.isEmpty() && getProperty(TYPES_DESCRIPTOR) != null) {
69              types.addAll(Arrays.asList(getProperty(TYPES_DESCRIPTOR)));
70          }
71          return super.visit(node, data);
72      }
73  
74      @Override
75      public Object visit(ASTMethodDeclaration node, Object data) {
76          List<ASTLocalVariableDeclaration> vars = node.findDescendantsOfType(ASTLocalVariableDeclaration.class);
77          List<ASTVariableDeclaratorId> ids = new ArrayList<ASTVariableDeclaratorId>();
78  
79          // find all variable references to Connection objects
80          for (ASTLocalVariableDeclaration var: vars) {
81              ASTType type = var.getTypeNode();
82  
83              if (type.jjtGetChild(0) instanceof ASTReferenceType) {
84                  ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
85                  if (ref.jjtGetChild(0) instanceof ASTClassOrInterfaceType) {
86                      ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);
87                      if (types.contains(clazz.getImage())) {
88                          ASTVariableDeclaratorId id = (ASTVariableDeclaratorId) var.jjtGetChild(1).jjtGetChild(0);
89                          ids.add(id);
90                      }
91                  }
92              }
93          }
94  
95          // if there are connections, ensure each is closed.
96          for (ASTVariableDeclaratorId x : ids) {
97              ensureClosed((ASTLocalVariableDeclaration) x.jjtGetParent().jjtGetParent(), x, data);
98          }
99          return data;
100     }
101 
102     private void ensureClosed(ASTLocalVariableDeclaration var,
103                               ASTVariableDeclaratorId id, Object data) {
104         // What are the chances of a Connection being instantiated in a
105         // for-loop init block? Anyway, I'm lazy!
106         String variableToClose = id.getImage();
107         String target = variableToClose + ".close";
108         Node n = var;
109 
110         while (!(n instanceof ASTBlock)) {
111             n = n.jjtGetParent();
112         }
113 
114         ASTBlock top = (ASTBlock) n;
115 
116         List<ASTTryStatement> tryblocks = top.findDescendantsOfType(ASTTryStatement.class);
117 
118         boolean closed = false;
119 
120         // look for try blocks below the line the variable was
121         // introduced and make sure there is a .close call in a finally
122         // block.
123         for (ASTTryStatement t : tryblocks) {
124             if (t.getBeginLine() > id.getBeginLine() && t.hasFinally()) {
125                 ASTBlock f = (ASTBlock) t.getFinally().jjtGetChild(0);
126                 List<ASTName> names = f.findDescendantsOfType(ASTName.class);
127                 for (ASTName oName : names) {
128                     String name = oName.getImage();
129                     if (name.equals(target)) {
130                         closed = true;
131                         break;
132                     }
133                 }
134                 if (closed) {
135                     break;
136                 }
137 
138                 List<ASTStatementExpression> exprs = new ArrayList<ASTStatementExpression>();
139                 f.findDescendantsOfType(ASTStatementExpression.class, exprs, true);
140                 for (ASTStatementExpression stmt : exprs) {
141                     ASTPrimaryExpression expr =
142                         stmt.getFirstChildOfType(ASTPrimaryExpression.class);
143                     if (expr != null) {
144                         ASTPrimaryPrefix prefix = expr.getFirstChildOfType(ASTPrimaryPrefix.class);
145                         ASTPrimarySuffix suffix = expr.getFirstChildOfType(ASTPrimarySuffix.class);
146                         if ((prefix != null) && (suffix != null)) {
147                             if (prefix.getImage() == null) {
148                                 ASTName prefixName = prefix.getFirstChildOfType(ASTName.class);
149                                 if ((prefixName != null)
150                                         && closeTargets.contains(prefixName.getImage()))
151                                 {
152                                     // Found a call to a "close target" that is a direct
153                                     // method call without a "ClassName." prefix.
154                                     closed = variableIsPassedToMethod(expr, variableToClose);
155                                     if (closed) {
156                                         break;
157                                     }
158                                 }
159                             } else if (suffix.getImage() != null) {
160                                 String prefixPlusSuffix =
161                                         prefix.getImage()+ "." + suffix.getImage();
162                                 if (closeTargets.contains(prefixPlusSuffix)) {
163                                     // Found a call to a "close target" that is a method call
164                                     // in the form "ClassName.methodName".
165                                     closed = variableIsPassedToMethod(expr, variableToClose);
166                                     if (closed) {
167                                         break;
168                                     }
169                                 }
170                             }
171                          // look for primary suffix containing the close Targets elements.
172                             // If the .close is executed in another class accessed by a method
173                             // this form : getProviderInstance().closeConnexion(connexion)
174                             // For this use case, we assume the variable is correctly closed
175                             // in the other class since there is no way to really check it.
176                             if (!closed)
177                             {
178                                 List<ASTPrimarySuffix> suffixes = new ArrayList<ASTPrimarySuffix>();
179                                 expr.findDescendantsOfType(ASTPrimarySuffix.class, suffixes, true);
180                                 for (ASTPrimarySuffix oSuffix : suffixes) {
181                                     String suff = oSuffix.getImage();
182                                     if (closeTargets.contains(suff)) 
183                                     {
184                                         closed = variableIsPassedToMethod(expr, variableToClose);
185                                         if(closed)
186                                         { 
187                                             break;                    
188                                         }                                                        
189                                     }
190 
191                                 }
192                             }
193                         }
194                     }
195                 }
196                 if (closed) {
197                     break;
198                 }
199             }
200         }
201 
202         if (!closed) {
203             // See if the variable is returned by the method, which means the
204             // method is a utility for creating the db resource, which means of
205             // course it can't be closed by the method, so it isn't an error.
206             List<ASTReturnStatement> returns = new ArrayList<ASTReturnStatement>();
207             top.findDescendantsOfType(ASTReturnStatement.class, returns, true);
208             for (ASTReturnStatement returnStatement : returns) {
209                 ASTName name = returnStatement.getFirstChildOfType(ASTName.class);
210                 if ((name != null) && name.getImage().equals(variableToClose)) {
211                     closed = true;
212                     break;
213                 }
214             }
215         }
216 
217         // if all is not well, complain
218         if (!closed) {
219             ASTType type = (ASTType) var.jjtGetChild(0);
220             ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
221             ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);
222             addViolation(data, id, clazz.getImage());
223         }
224     }
225 
226     private boolean variableIsPassedToMethod(ASTPrimaryExpression expr, String variable) {
227         List<ASTName> methodParams = new ArrayList<ASTName>();
228         expr.findDescendantsOfType(ASTName.class, methodParams, true);
229         for (ASTName pName : methodParams) {
230             String paramName = pName.getImage();
231             // also check if we've got the a parameter (i.e if it's an argument !) 
232             ASTArgumentList parentParam = pName.getFirstParentOfType(ASTArgumentList.class);
233             if (paramName.equals(variable) && parentParam != null) {
234                 return true;
235             }
236         }
237         return false;
238     }
239 }