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.basic;
    5
    6   import java.util.ArrayList;
    7   import java.util.List;
    8
    9   import net.sourceforge.pmd.lang.ast.Node;
    10  import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
    11  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
    12  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
    13  import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
    14  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
    15  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
    16  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
    17  import net.sourceforge.pmd.lang.java.ast.ASTName;
    18  import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
    19  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
    20  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
    21  import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
    22  import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
    23  import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
    24  import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
    25  import net.sourceforge.pmd.lang.java.ast.ASTType;
    26  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
    27  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    28
    29  /**
    30   * void method() {
    31   * if(x == null) {
    32   * synchronized(this){
    33   * if(x == null) {
    34   * x = new | method();
    35   * }
    36   * }
    37   * }
    38   * 1.  The error is when one uses the value assigned within a synchronized
    39   * section, outside of a synchronized section.
    40   * if(x == null) is outside of synchronized section
    41   * x = new | method();
    42   * <p/>
    43   * <p/>
    44   * Very very specific check for double checked locking.
    45   *
    46   * @author CL Gilbert (dnoyeb@users.sourceforge.net)
    47   */
    48  public class DoubleCheckedLockingRule extends AbstractJavaRule {
    49
    50      private List<String> volatileFields;
    51
    52      @Override
    53      public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
    54          if (node.isInterface()) {
    55              return data;
    56          }
    57          return super.visit(node, data);
    58      }
    59
    60      @Override
    61      public Object visit(ASTCompilationUnit compilationUnit, Object data) {
    62          if ( this.volatileFields == null ) {
    63              this.volatileFields = new ArrayList<String>(0);
    64  	} else {
    65  	    this.volatileFields.clear();
    66  	}
    67  	return super.visit(compilationUnit,data);
    68      }
    69
    70
    71      @Override
    72      public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) {
    73          if ( fieldDeclaration.isVolatile() ) {
    74          	for (ASTVariableDeclaratorId declarator : fieldDeclaration.findDescendantsOfType(ASTVariableDeclaratorId.class) ) {
    75                  this.volatileFields.add(declarator.getImage());
    76          	}
    77          }
    78          return super.visit(fieldDeclaration, data);
    79      }
    80
    81      @Override
    82      public Object visit(ASTMethodDeclaration node, Object data) {
    83          if (node.getResultType().isVoid()) {
    84              return super.visit(node, data);
    85          }
    86
    87          ASTType typeNode = (ASTType) node.getResultType().jjtGetChild(0);
    88          if (typeNode.jjtGetNumChildren() == 0 || !(typeNode.jjtGetChild(0) instanceof ASTReferenceType)) {
    89              return super.visit(node, data);
    90          }
    91
    92          List<ASTReturnStatement> rsl = node.findDescendantsOfType(ASTReturnStatement.class);
    93          if (rsl.size() != 1) {
    94              return super.visit(node, data);
    95          }
    96          ASTReturnStatement rs = rsl.get(0);
    97
    98          List<ASTPrimaryExpression> pel = rs.findDescendantsOfType(ASTPrimaryExpression.class);
    99          ASTPrimaryExpression ape = pel.get(0);
    100         Node lastChild = ape.jjtGetChild(ape.jjtGetNumChildren() - 1);
    101         String returnVariableName = null;
    102         if (lastChild instanceof ASTPrimaryPrefix) {
    103             returnVariableName = getNameFromPrimaryPrefix((ASTPrimaryPrefix) lastChild);
    104         }
    105         // With Java5 and volatile keyword, DCL is no longer an issue
    106         if (returnVariableName == null || this.volatileFields.contains(returnVariableName)) {
    107             return super.visit(node, data);
    108         }
    109         List<ASTIfStatement> isl = node.findDescendantsOfType(ASTIfStatement.class);
    110         if (isl.size() == 2) {
    111             ASTIfStatement is = isl.get(0);
    112             if (ifVerify(is, returnVariableName)) {
    113                 //find synchronized
    114                 List<ASTSynchronizedStatement> ssl = is.findDescendantsOfType(ASTSynchronizedStatement.class);
    115                 if (ssl.size() == 1) {
    116                     ASTSynchronizedStatement ss = ssl.get(0);
    117                     isl = ss.findDescendantsOfType(ASTIfStatement.class);
    118                     if (isl.size() == 1) {
    119                         ASTIfStatement is2 = isl.get(0);
    120                         if (ifVerify(is2, returnVariableName)) {
    121                             List<ASTStatementExpression> sel = is2.findDescendantsOfType(ASTStatementExpression.class);
    122                             if (sel.size() == 1) {
    123                                 ASTStatementExpression se = sel.get(0);
    124                                 if (se.jjtGetNumChildren() == 3) { //primaryExpression, AssignmentOperator, Expression
    125                                     if (se.jjtGetChild(0) instanceof ASTPrimaryExpression) {
    126                                         ASTPrimaryExpression pe = (ASTPrimaryExpression) se.jjtGetChild(0);
    127                                         if (matchName(pe, returnVariableName)) {
    128                                             if (se.jjtGetChild(1) instanceof ASTAssignmentOperator) {
    129                                                 addViolation(data, node);
    130                                             }
    131                                         }
    132                                     }
    133                                 }
    134                             }
    135                         }
    136                     }
    137                 }
    138             }
    139         }
    140         return super.visit(node, data);
    141     }
    142
    143     private boolean ifVerify(ASTIfStatement is, String varname) {
    144         List<ASTPrimaryExpression> finder = is.findDescendantsOfType(ASTPrimaryExpression.class);
    145         if (finder.size() > 1) {
    146             ASTPrimaryExpression nullStmt = findNonVariableStmt(varname,finder.get(0),finder.get(1));
    147             if ( nullStmt != null ) {
    148                 if ((nullStmt.jjtGetNumChildren() == 1) && (nullStmt.jjtGetChild(0) instanceof ASTPrimaryPrefix)) {
    149                     ASTPrimaryPrefix pp2 = (ASTPrimaryPrefix) nullStmt.jjtGetChild(0);
    150                     if ((pp2.jjtGetNumChildren() == 1) && (pp2.jjtGetChild(0) instanceof ASTLiteral)) {
    151                         ASTLiteral lit = (ASTLiteral) pp2.jjtGetChild(0);
    152                         if ((lit.jjtGetNumChildren() == 1) && (lit.jjtGetChild(0) instanceof ASTNullLiteral)) {
    153                             return true;
    154                         }
    155                     }
    156                 }
    157             }
    158         }
    159         return false;
    160     }
    161
    162     /**
    163      * <p>Sort out if apeLeft or apeRight are variable with the provided 'variableName'.</p>
    164      *
    165      * @param variableName
    166      * @param apeLeft
    167      * @param apeRight
    168      * @return reference from either apeLeft or apeRight, if one of them match, or 'null', if none match.
    169      */
    170 	private ASTPrimaryExpression findNonVariableStmt(String variableName,
    171 			ASTPrimaryExpression apeLeft, ASTPrimaryExpression apeRight) {
    172     	if (matchName(apeLeft, variableName) ) {
    173     		return apeRight;
    174     	}
    175     	else if (matchName(apeRight, variableName) ) {
    176     		return apeLeft;
    177     	}
    178 		return null;
    179 	}
    180
    181 	private boolean matchName(ASTPrimaryExpression ape, String name) {
    182         if ((ape.jjtGetNumChildren() == 1) && (ape.jjtGetChild(0) instanceof ASTPrimaryPrefix)) {
    183             ASTPrimaryPrefix pp = (ASTPrimaryPrefix) ape.jjtGetChild(0);
    184             String name2 = getNameFromPrimaryPrefix(pp);
    185             if (name2 != null && name2.equals(name)) {
    186                 return true;
    187             }
    188         }
    189         return false;
    190     }
    191
    192     private String getNameFromPrimaryPrefix(ASTPrimaryPrefix pp) {
    193         if ((pp.jjtGetNumChildren() == 1) && (pp.jjtGetChild(0) instanceof ASTName)) {
    194             return ((ASTName) pp.jjtGetChild(0)).getImage();
    195         }
    196         return null;
    197     }
    198 }