DoubleCheckedLockingRule.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.java.rule.basic;
005 
006 import java.util.ArrayList;
007 import java.util.List;
008 
009 import net.sourceforge.pmd.lang.ast.Node;
010 import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
011 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
012 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
013 import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
014 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
015 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
016 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
017 import net.sourceforge.pmd.lang.java.ast.ASTName;
018 import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
019 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
020 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
021 import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
022 import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
023 import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
024 import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
025 import net.sourceforge.pmd.lang.java.ast.ASTType;
026 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
027 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
028 
029 /**
030  * void method() {
031  * if(x == null) {
032  * synchronized(this){
033  * if(x == null) {
034  * x = new | method();
035  * }
036  * }
037  * }
038  * 1.  The error is when one uses the value assigned within a synchronized
039  * section, outside of a synchronized section.
040  * if(x == null) is outside of synchronized section
041  * x = new | method();
042  <p/>
043  <p/>
044  * Very very specific check for double checked locking.
045  *
046  @author CL Gilbert (dnoyeb@users.sourceforge.net)
047  */
048 public class DoubleCheckedLockingRule extends AbstractJavaRule {
049 
050     private List<String> volatileFields;
051 
052     @Override
053     public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
054         if (node.isInterface()) {
055             return data;
056         }
057         return super.visit(node, data);
058     }
059  
060     @Override
061     public Object visit(ASTCompilationUnit compilationUnit, Object data) {
062         if this.volatileFields == null ) {
063             this.volatileFields = new ArrayList<String>(0);
064   else {
065       this.volatileFields.clear();
066   }
067   return super.visit(compilationUnit,data);
068     }
069 
070 
071     @Override
072     public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) {
073         if fieldDeclaration.isVolatile() ) {
074           for (ASTVariableDeclaratorId declarator : fieldDeclaration.findDescendantsOfType(ASTVariableDeclaratorId.class) ) {
075                 this.volatileFields.add(declarator.getImage());
076           }
077         }
078         return super.visit(fieldDeclaration, data);
079     }
080 
081     @Override
082     public Object visit(ASTMethodDeclaration node, Object data) {
083         if (node.getResultType().isVoid()) {
084             return super.visit(node, data);
085         }
086 
087         ASTType typeNode = (ASTTypenode.getResultType().jjtGetChild(0);
088         if (typeNode.jjtGetNumChildren() == || !(typeNode.jjtGetChild(0instanceof ASTReferenceType)) {
089             return super.visit(node, data);
090         }
091 
092         List<ASTReturnStatement> rsl = node.findDescendantsOfType(ASTReturnStatement.class);
093         if (rsl.size() != 1) {
094             return super.visit(node, data);
095         }
096         ASTReturnStatement rs = rsl.get(0);
097 
098         List<ASTPrimaryExpression> pel = rs.findDescendantsOfType(ASTPrimaryExpression.class);
099         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((ASTPrimaryPrefixlastChild);
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(0instanceof ASTPrimaryExpression) {
126                                         ASTPrimaryExpression pe = (ASTPrimaryExpressionse.jjtGetChild(0);
127                                         if (matchName(pe, returnVariableName)) {
128                                             if (se.jjtGetChild(1instanceof 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(0instanceof ASTPrimaryPrefix)) {
149                     ASTPrimaryPrefix pp2 = (ASTPrimaryPrefixnullStmt.jjtGetChild(0);
150                     if ((pp2.jjtGetNumChildren() == 1&& (pp2.jjtGetChild(0instanceof ASTLiteral)) {
151                         ASTLiteral lit = (ASTLiteralpp2.jjtGetChild(0);
152                         if ((lit.jjtGetNumChildren() == 1&& (lit.jjtGetChild(0instanceof 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(0instanceof ASTPrimaryPrefix)) {
183             ASTPrimaryPrefix pp = (ASTPrimaryPrefixape.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(0instanceof ASTName)) {
194             return ((ASTNamepp.jjtGetChild(0)).getImage();
195         }
196         return null;
197     }
198 }