View Javadoc

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