BrokenNullCheckRule.java
001 package net.sourceforge.pmd.lang.java.rule.basic;
002 
003 import java.util.ArrayList;
004 import java.util.List;
005 
006 import net.sourceforge.pmd.lang.ast.Node;
007 import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
008 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
009 import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
010 import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
011 import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
012 import net.sourceforge.pmd.lang.java.ast.ASTExpression;
013 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
014 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
015 import net.sourceforge.pmd.lang.java.ast.ASTName;
016 import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
017 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
018 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
019 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
020 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
021 
022 public class BrokenNullCheckRule extends AbstractJavaRule {
023 
024     @Override
025     public Object visit(ASTIfStatement node, Object data) {
026         ASTExpression expression = (ASTExpression)node.jjtGetChild(0);
027 
028         ASTConditionalAndExpression conditionalAndExpression = expression.getFirstDescendantOfType(ASTConditionalAndExpression.class);
029         if (conditionalAndExpression != null) {
030             checkForViolations(node, data, conditionalAndExpression);
031         }
032 
033         ASTConditionalOrExpression conditionalOrExpression = expression.getFirstDescendantOfType(ASTConditionalOrExpression.class);
034         if (conditionalOrExpression != null) {
035             checkForViolations(node, data, conditionalOrExpression);
036         }
037 
038         return super.visit(node, data);
039     }
040 
041 
042     private void checkForViolations(ASTIfStatement node, Object data, Node conditionalExpression) {
043         ASTEqualityExpression equalityExpression = conditionalExpression.getFirstChildOfType(ASTEqualityExpression.class);
044         if (equalityExpression == null) {
045             return;
046         }
047         if (conditionalExpression instanceof ASTConditionalAndExpression &&
048                 !"==".equals(equalityExpression.getImage())) {
049             return;
050         }
051         if (conditionalExpression instanceof ASTConditionalOrExpression &&
052                 !"!=".equals(equalityExpression.getImage())) {
053             return;
054         }
055         ASTNullLiteral nullLiteral = equalityExpression.getFirstDescendantOfType(ASTNullLiteral.class);
056         if (nullLiteral == null) {
057             return;     //No null check
058         }
059         //If there is an assignment in the equalityExpression we give up, because things get too complex
060         if (conditionalExpression.hasDescendantOfType(ASTAssignmentOperator.class)) {
061             return;
062         }
063 
064         //Find the expression used in the null compare
065         ASTPrimaryExpression nullCompareExpression = findNullCompareExpression(equalityExpression);
066         if (nullCompareExpression == null) {
067             return;     //No good null check
068         }
069 
070         //Now we find the expression to compare to and do the comparison
071         for (int i = 0; i < conditionalExpression.jjtGetNumChildren(); i++) {
072             Node conditionalSubnode = conditionalExpression.jjtGetChild(i);
073 
074             //We skip the null compare branch
075             ASTEqualityExpression nullEqualityExpression = nullLiteral.getFirstParentOfType(ASTEqualityExpression.class);
076             if (conditionalSubnode.equals(nullEqualityExpression)) {
077                 continue;
078             }
079             ASTPrimaryExpression conditionalPrimaryExpression;
080             if (conditionalSubnode instanceof ASTPrimaryExpression) {
081                 conditionalPrimaryExpression = (ASTPrimaryExpression)conditionalSubnode;
082             else {
083                 //The ASTPrimaryExpression is hidden (in a negation, braces or EqualityExpression)
084                 conditionalPrimaryExpression = conditionalSubnode
085                     .getFirstDescendantOfType(ASTPrimaryExpression.class);
086             }
087 
088             if (primaryExpressionsAreEqual(nullCompareExpression, conditionalPrimaryExpression)) {
089                 addViolation(data, node);   //We have a match
090             }
091 
092         }
093     }
094 
095     private boolean primaryExpressionsAreEqual(ASTPrimaryExpression nullCompareVariable, ASTPrimaryExpression expressionUsage) {
096         List<String> nullCompareNames = new ArrayList<String>();
097         findExpressionNames(nullCompareVariable, nullCompareNames);
098 
099         List<String> expressionUsageNames = new ArrayList<String>();
100         findExpressionNames(expressionUsage, expressionUsageNames);
101 
102         for (int i = 0; i < nullCompareNames.size(); i++) {
103             if (expressionUsageNames.size() == i) {
104                 return false;   //The used expression is shorter than the null compare expression (and we don't want to crash below)
105             }
106 
107             String nullCompareExpressionName = nullCompareNames.get(i);
108             String expressionUsageName       = expressionUsageNames.get(i);
109 
110             //Variablenames should match or the expressionUsage should have the variable with a method call (ie. var.equals())
111             if (!nullCompareExpressionName.equals(expressionUsageName&&
112                     !expressionUsageName.startsWith(nullCompareExpressionName + ".")) {
113                 return false;   //Some other expression is being used after the null compare
114             }
115         }
116 
117         return true;
118     }
119 
120 
121     /**
122      * Find the names of variables, methods and array arguments in a PrimaryExpression.
123      */
124     private void findExpressionNames(Node nullCompareVariable, List<String> results) {
125         for (int i = 0; i < nullCompareVariable.jjtGetNumChildren(); i++) {
126             Node child = nullCompareVariable.jjtGetChild(i);
127 
128             if (child instanceof ASTName) {                   //Variable names and some method calls
129                 results.add( ((ASTName)child).getImage() );
130             else if (child instanceof ASTLiteral) {         //Array arguments
131                 String literalImage = ((ASTLiteral)child).getImage();
132                 //Skip other null checks
133                 if (literalImage != null) {
134                     results.addliteralImage );
135                 }
136             else if (child instanceof ASTPrimarySuffix) {   //More method calls
137                 String name = ((ASTPrimarySuffix)child).getImage();
138                 if (name != null && !name.equals("")) {
139                     results.add(name);
140                 }
141             else if (child instanceof ASTClassOrInterfaceType) {    //A class can be an argument too
142                 String name = ((ASTClassOrInterfaceType)child).getImage();
143                 results.add(name);
144             }
145 
146             if (child.jjtGetNumChildren() 0) {
147                 findExpressionNames(child, results);
148             }
149         }
150     }
151 
152     private ASTPrimaryExpression findNullCompareExpression(ASTEqualityExpression equalityExpression) {
153         List<ASTPrimaryExpression> primaryExpressions = equalityExpression.findDescendantsOfType(ASTPrimaryExpression.class);
154         for (ASTPrimaryExpression primaryExpression: primaryExpressions) {
155             List<ASTPrimaryPrefix> primaryPrefixes = primaryExpression.findDescendantsOfType(ASTPrimaryPrefix.class);
156             for (ASTPrimaryPrefix primaryPrefix: primaryPrefixes) {
157                 if (primaryPrefix.hasDescendantOfType(ASTName.class)) {
158                     //We found the variable that is compared to null
159                     return primaryExpression;
160                 }
161             }
162         }
163         return null;  //Nothing found
164     }
165 
166 }