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 = (ASTType) node.getResultType().jjtGetChild(0);
088 if (typeNode.jjtGetNumChildren() == 0 || !(typeNode.jjtGetChild(0) instanceof 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((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 }
|