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 }