/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.basic;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
/**
* <pre>
* void method() {
* if (x == null) {
* synchronized(this){
* if (x == null) {
* x = new | method();
* }
* }
* }
* }
* </pre>
*
* <p>The error is when one uses the value assigned within a synchronized
* section, outside of a synchronized section.</p>
*
* <pre>
* if (x == null) // is outside of synchronized section
* x = new | method();
* </pre>
*
* <p>Very very specific check for double checked locking.</p>
*
* @author CL Gilbert (dnoyeb@users.sourceforge.net)
*/
public class DoubleCheckedLockingRule extends AbstractJavaRule {
private List<String> volatileFields;
@Override
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
if (node.isInterface()) {
return data;
}
return super.visit(node, data);
}
@Override
public Object visit(ASTCompilationUnit compilationUnit, Object data) {
if (this.volatileFields == null) {
this.volatileFields = new ArrayList<>(0);
} else {
this.volatileFields.clear();
}
return super.visit(compilationUnit, data);
}
@Override
public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) {
if (fieldDeclaration.isVolatile()) {
for (ASTVariableDeclaratorId declarator : fieldDeclaration
.findDescendantsOfType(ASTVariableDeclaratorId.class)) {
this.volatileFields.add(declarator.getImage());
}
}
return super.visit(fieldDeclaration, data);
}
@Override
public Object visit(ASTMethodDeclaration node, Object data) {
if (node.getResultType().isVoid()) {
return super.visit(node, data);
}
ASTType typeNode = (ASTType) node.getResultType().jjtGetChild(0);
if (typeNode.jjtGetNumChildren() == 0 || !(typeNode.jjtGetChild(0) instanceof ASTReferenceType)) {
return super.visit(node, data);
}
List<ASTReturnStatement> rsl = node.findDescendantsOfType(ASTReturnStatement.class);
if (rsl.size() != 1) {
return super.visit(node, data);
}
ASTReturnStatement rs = rsl.get(0);
List<ASTPrimaryExpression> pel = rs.findDescendantsOfType(ASTPrimaryExpression.class);
ASTPrimaryExpression ape = pel.get(0);
Node lastChild = ape.jjtGetChild(ape.jjtGetNumChildren() - 1);
String returnVariableName = null;
if (lastChild instanceof ASTPrimaryPrefix) {
returnVariableName = getNameFromPrimaryPrefix((ASTPrimaryPrefix) lastChild);
}
// With Java5 and volatile keyword, DCL is no longer an issue
if (returnVariableName == null || this.volatileFields.contains(returnVariableName)) {
return super.visit(node, data);
}
// if the return variable is local and only written with the volatile
// field, then it's ok, too
if (checkLocalVariableUsage(node, returnVariableName)) {
return super.visit(node, data);
}
List<ASTIfStatement> isl = node.findDescendantsOfType(ASTIfStatement.class);
if (isl.size() == 2) {
ASTIfStatement is = isl.get(0);
if (ifVerify(is, returnVariableName)) {
// find synchronized
List<ASTSynchronizedStatement> ssl = is.findDescendantsOfType(ASTSynchronizedStatement.class);
if (ssl.size() == 1) {
ASTSynchronizedStatement ss = ssl.get(0);
isl = ss.findDescendantsOfType(ASTIfStatement.class);
if (isl.size() == 1) {
ASTIfStatement is2 = isl.get(0);
if (ifVerify(is2, returnVariableName)) {
List<ASTStatementExpression> sel = is2.findDescendantsOfType(ASTStatementExpression.class);
if (sel.size() == 1) {
ASTStatementExpression se = sel.get(0);
if (se.jjtGetNumChildren() == 3) {
// primaryExpression, AssignmentOperator, Expression
if (se.jjtGetChild(0) instanceof ASTPrimaryExpression) {
ASTPrimaryExpression pe = (ASTPrimaryExpression) se.jjtGetChild(0);
if (matchName(pe, returnVariableName)) {
if (se.jjtGetChild(1) instanceof ASTAssignmentOperator) {
addViolation(data, node);
}
}
}
}
}
}
}
}
}
}
return super.visit(node, data);
}
private boolean checkLocalVariableUsage(ASTMethodDeclaration node, String returnVariableName) {
List<ASTLocalVariableDeclaration> locals = node.findDescendantsOfType(ASTLocalVariableDeclaration.class);
ASTVariableInitializer initializer = null;
for (ASTLocalVariableDeclaration l : locals) {
ASTVariableDeclaratorId id = l.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
if (id != null && id.hasImageEqualTo(returnVariableName)) {
initializer = l.getFirstDescendantOfType(ASTVariableInitializer.class);
break;
}
}
// the return variable name doesn't seem to be a local variable
if (initializer == null) {
return false;
}
// verify the value with which the local variable is initialized
if (initializer.jjtGetNumChildren() > 0 && initializer.jjtGetChild(0) instanceof ASTExpression
&& initializer.jjtGetChild(0).jjtGetNumChildren() > 0
&& initializer.jjtGetChild(0).jjtGetChild(0) instanceof ASTPrimaryExpression
&& initializer.jjtGetChild(0).jjtGetChild(0).jjtGetNumChildren() > 0
&& initializer.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0) instanceof ASTPrimaryPrefix
&& initializer.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0).jjtGetNumChildren() > 0
&& initializer.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0).jjtGetChild(0) instanceof ASTName) {
ASTName name = (ASTName) initializer.jjtGetChild(0).jjtGetChild(0).jjtGetChild(0).jjtGetChild(0);
if (name == null || !volatileFields.contains(name.getImage())) {
return false;
}
} else {
// not a simple assignment
return false;
}
// now check every usage/assignment of the variable
List<ASTName> names = node.findDescendantsOfType(ASTName.class);
for (ASTName n : names) {
if (!n.hasImageEqualTo(returnVariableName)) {
continue;
}
Node expression = n.getNthParent(3);
if (expression instanceof ASTEqualityExpression) {
continue;
}
if (expression instanceof ASTStatementExpression) {
if (expression.jjtGetNumChildren() > 2 && expression.jjtGetChild(1) instanceof ASTAssignmentOperator) {
ASTName value = expression.jjtGetChild(2).getFirstDescendantOfType(ASTName.class);
if (value == null || !volatileFields.contains(value.getImage())) {
return false;
}
}
}
}
return true;
}
private boolean ifVerify(ASTIfStatement is, String varname) {
List<ASTPrimaryExpression> finder = is.findDescendantsOfType(ASTPrimaryExpression.class);
if (finder.size() > 1) {
ASTPrimaryExpression nullStmt = findNonVariableStmt(varname, finder.get(0), finder.get(1));
if (nullStmt != null) {
if (nullStmt.jjtGetNumChildren() == 1 && nullStmt.jjtGetChild(0) instanceof ASTPrimaryPrefix) {
ASTPrimaryPrefix pp2 = (ASTPrimaryPrefix) nullStmt.jjtGetChild(0);
if (pp2.jjtGetNumChildren() == 1 && pp2.jjtGetChild(0) instanceof ASTLiteral) {
ASTLiteral lit = (ASTLiteral) pp2.jjtGetChild(0);
if (lit.jjtGetNumChildren() == 1 && lit.jjtGetChild(0) instanceof ASTNullLiteral) {
return true;
}
}
}
}
}
return false;
}
/**
* <p>
* Sort out if apeLeft or apeRight are variable with the provided
* 'variableName'.
* </p>
*
* @param variableName
* @param apeLeft
* @param apeRight
* @return reference from either apeLeft or apeRight, if one of them match,
* or 'null', if none match.
*/
private ASTPrimaryExpression findNonVariableStmt(String variableName, ASTPrimaryExpression apeLeft,
ASTPrimaryExpression apeRight) {
if (matchName(apeLeft, variableName)) {
return apeRight;
} else if (matchName(apeRight, variableName)) {
return apeLeft;
}
return null;
}
private boolean matchName(ASTPrimaryExpression ape, String name) {
if (ape.jjtGetNumChildren() == 1 && ape.jjtGetChild(0) instanceof ASTPrimaryPrefix) {
ASTPrimaryPrefix pp = (ASTPrimaryPrefix) ape.jjtGetChild(0);
String name2 = getNameFromPrimaryPrefix(pp);
if (name2 != null && name2.equals(name)) {
return true;
}
}
return false;
}
private String getNameFromPrimaryPrefix(ASTPrimaryPrefix pp) {
if (pp.jjtGetNumChildren() == 1 && pp.jjtGetChild(0) instanceof ASTName) {
return ((ASTName) pp.jjtGetChild(0)).getImage();
}
return null;
}
}