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 }