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