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<>(0);
64          } else {
65              this.volatileFields.clear();
66          }
67          return super.visit(compilationUnit, data);
68      }
69  
70      @Override
71      public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) {
72          if (fieldDeclaration.isVolatile()) {
73              for (ASTVariableDeclaratorId declarator : fieldDeclaration
74                      .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,
125                                                                    // AssignmentOperator,
126                                                                    // Expression
127                                     if (se.jjtGetChild(0) instanceof ASTPrimaryExpression) {
128                                         ASTPrimaryExpression pe = (ASTPrimaryExpression) se.jjtGetChild(0);
129                                         if (matchName(pe, returnVariableName)) {
130                                             if (se.jjtGetChild(1) instanceof ASTAssignmentOperator) {
131                                                 addViolation(data, node);
132                                             }
133                                         }
134                                     }
135                                 }
136                             }
137                         }
138                     }
139                 }
140             }
141         }
142         return super.visit(node, data);
143     }
144 
145     private boolean ifVerify(ASTIfStatement is, String varname) {
146         List<ASTPrimaryExpression> finder = is.findDescendantsOfType(ASTPrimaryExpression.class);
147         if (finder.size() > 1) {
148             ASTPrimaryExpression nullStmt = findNonVariableStmt(varname, finder.get(0), finder.get(1));
149             if (nullStmt != null) {
150                 if (nullStmt.jjtGetNumChildren() == 1 && nullStmt.jjtGetChild(0) instanceof ASTPrimaryPrefix) {
151                     ASTPrimaryPrefix pp2 = (ASTPrimaryPrefix) nullStmt.jjtGetChild(0);
152                     if (pp2.jjtGetNumChildren() == 1 && pp2.jjtGetChild(0) instanceof ASTLiteral) {
153                         ASTLiteral lit = (ASTLiteral) pp2.jjtGetChild(0);
154                         if (lit.jjtGetNumChildren() == 1 && lit.jjtGetChild(0) instanceof ASTNullLiteral) {
155                             return true;
156                         }
157                     }
158                 }
159             }
160         }
161         return false;
162     }
163 
164     /**
165      * <p>
166      * Sort out if apeLeft or apeRight are variable with the provided
167      * 'variableName'.
168      * </p>
169      * 
170      * @param variableName
171      * @param apeLeft
172      * @param apeRight
173      * @return reference from either apeLeft or apeRight, if one of them match,
174      *         or 'null', if none match.
175      */
176     private ASTPrimaryExpression findNonVariableStmt(String variableName, ASTPrimaryExpression apeLeft,
177             ASTPrimaryExpression apeRight) {
178         if (matchName(apeLeft, variableName)) {
179             return apeRight;
180         } else if (matchName(apeRight, variableName)) {
181             return apeLeft;
182         }
183         return null;
184     }
185 
186     private boolean matchName(ASTPrimaryExpression ape, String name) {
187         if (ape.jjtGetNumChildren() == 1 && ape.jjtGetChild(0) instanceof ASTPrimaryPrefix) {
188             ASTPrimaryPrefix pp = (ASTPrimaryPrefix) ape.jjtGetChild(0);
189             String name2 = getNameFromPrimaryPrefix(pp);
190             if (name2 != null && name2.equals(name)) {
191                 return true;
192             }
193         }
194         return false;
195     }
196 
197     private String getNameFromPrimaryPrefix(ASTPrimaryPrefix pp) {
198         if (pp.jjtGetNumChildren() == 1 && pp.jjtGetChild(0) instanceof ASTName) {
199             return ((ASTName) pp.jjtGetChild(0)).getImage();
200         }
201         return null;
202     }
203 }