/** * 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.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression; import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression; import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; 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.ASTPrimarySuffix; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.util.StringUtil; public class BrokenNullCheckRule extends AbstractJavaRule { @Override public Object visit(ASTIfStatement node, Object data) { ASTExpression expression = (ASTExpression) node.jjtGetChild(0); ASTConditionalAndExpression conditionalAndExpression = expression .getFirstDescendantOfType(ASTConditionalAndExpression.class); if (conditionalAndExpression != null) { checkForViolations(node, data, conditionalAndExpression); } ASTConditionalOrExpression conditionalOrExpression = expression .getFirstDescendantOfType(ASTConditionalOrExpression.class); if (conditionalOrExpression != null) { checkForViolations(node, data, conditionalOrExpression); } return super.visit(node, data); } private void checkForViolations(ASTIfStatement node, Object data, Node conditionalExpression) { ASTEqualityExpression equalityExpression = conditionalExpression .getFirstChildOfType(ASTEqualityExpression.class); if (equalityExpression == null) { return; } if (conditionalExpression instanceof ASTConditionalAndExpression && !"==".equals(equalityExpression.getImage())) { return; } if (conditionalExpression instanceof ASTConditionalOrExpression && !"!=".equals(equalityExpression.getImage())) { return; } ASTNullLiteral nullLiteral = equalityExpression.getFirstDescendantOfType(ASTNullLiteral.class); if (nullLiteral == null) { return; // No null check } // If there is an assignment in the equalityExpression we give up, // because things get too complex if (conditionalExpression.hasDescendantOfType(ASTAssignmentOperator.class)) { return; } // Find the expression used in the null compare ASTPrimaryExpression nullCompareExpression = findNullCompareExpression(equalityExpression); if (nullCompareExpression == null) { return; // No good null check } // Now we find the expression to compare to and do the comparison for (int i = 0; i < conditionalExpression.jjtGetNumChildren(); i++) { Node conditionalSubnode = conditionalExpression.jjtGetChild(i); // We skip the null compare branch ASTEqualityExpression nullEqualityExpression = nullLiteral .getFirstParentOfType(ASTEqualityExpression.class); if (conditionalSubnode.equals(nullEqualityExpression)) { continue; } ASTPrimaryExpression conditionalPrimaryExpression; if (conditionalSubnode instanceof ASTPrimaryExpression) { conditionalPrimaryExpression = (ASTPrimaryExpression) conditionalSubnode; } else { // The ASTPrimaryExpression is hidden (in a negation, braces or // EqualityExpression) conditionalPrimaryExpression = conditionalSubnode.getFirstDescendantOfType(ASTPrimaryExpression.class); } if (primaryExpressionsAreEqual(nullCompareExpression, conditionalPrimaryExpression)) { addViolation(data, node); // We have a match } } } private boolean primaryExpressionsAreEqual(ASTPrimaryExpression nullCompareVariable, ASTPrimaryExpression expressionUsage) { List<String> nullCompareNames = new ArrayList<>(); findExpressionNames(nullCompareVariable, nullCompareNames); List<String> expressionUsageNames = new ArrayList<>(); findExpressionNames(expressionUsage, expressionUsageNames); for (int i = 0; i < nullCompareNames.size(); i++) { if (expressionUsageNames.size() == i) { // The used expression is shorter than the null // compare expression (and we don't want to crash // below) return false; } String nullCompareExpressionName = nullCompareNames.get(i); String expressionUsageName = expressionUsageNames.get(i); // Variablenames should match or the expressionUsage should have the // variable with a method call (ie. var.equals()) if (!nullCompareExpressionName.equals(expressionUsageName) && !expressionUsageName.startsWith(nullCompareExpressionName + ".")) { // Some other expression is being used after the // null compare return false; } } return true; } /** * Find the names of variables, methods and array arguments in a * PrimaryExpression. */ private void findExpressionNames(Node nullCompareVariable, List<String> results) { for (int i = 0; i < nullCompareVariable.jjtGetNumChildren(); i++) { Node child = nullCompareVariable.jjtGetChild(i); if (child instanceof ASTName) { // Variable names and some method calls results.add(((ASTName) child).getImage()); } else if (child instanceof ASTLiteral) { // Array arguments String literalImage = ((ASTLiteral) child).getImage(); // Skip other null checks if (literalImage != null) { results.add(literalImage); } } else if (child instanceof ASTPrimarySuffix) { // More method calls String name = ((ASTPrimarySuffix) child).getImage(); if (StringUtil.isNotEmpty(name)) { results.add(name); } } else if (child instanceof ASTClassOrInterfaceType) { // A class can be an argument too String name = ((ASTClassOrInterfaceType) child).getImage(); results.add(name); } if (child.jjtGetNumChildren() > 0) { findExpressionNames(child, results); } } } private ASTPrimaryExpression findNullCompareExpression(ASTEqualityExpression equalityExpression) { List<ASTPrimaryExpression> primaryExpressions = equalityExpression .findDescendantsOfType(ASTPrimaryExpression.class); for (ASTPrimaryExpression primaryExpression : primaryExpressions) { List<ASTPrimaryPrefix> primaryPrefixes = primaryExpression.findDescendantsOfType(ASTPrimaryPrefix.class); for (ASTPrimaryPrefix primaryPrefix : primaryPrefixes) { if (primaryPrefix.hasDescendantOfType(ASTName.class)) { // We found the variable that is compared to null return primaryExpression; } } } return null; // Nothing found } }