View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.basic;
5   
6   import java.util.ArrayList;
7   import java.util.List;
8   
9   import net.sourceforge.pmd.lang.ast.Node;
10  import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
11  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
12  import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression;
13  import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression;
14  import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
15  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
16  import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
17  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
18  import net.sourceforge.pmd.lang.java.ast.ASTName;
19  import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
20  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
21  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
22  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
23  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
24  import net.sourceforge.pmd.util.StringUtil;
25  
26  public class BrokenNullCheckRule extends AbstractJavaRule {
27  
28      @Override
29      public Object visit(ASTIfStatement node, Object data) {
30          ASTExpression expression = (ASTExpression)node.jjtGetChild(0);
31  
32          ASTConditionalAndExpression conditionalAndExpression = expression.getFirstDescendantOfType(ASTConditionalAndExpression.class);
33          if (conditionalAndExpression != null) {
34              checkForViolations(node, data, conditionalAndExpression);
35          }
36  
37          ASTConditionalOrExpression conditionalOrExpression = expression.getFirstDescendantOfType(ASTConditionalOrExpression.class);
38          if (conditionalOrExpression != null) {
39              checkForViolations(node, data, conditionalOrExpression);
40          }
41  
42          return super.visit(node, data);
43      }
44  
45  
46      private void checkForViolations(ASTIfStatement node, Object data, Node conditionalExpression) {
47          ASTEqualityExpression equalityExpression = conditionalExpression.getFirstChildOfType(ASTEqualityExpression.class);
48          if (equalityExpression == null) {
49              return;
50          }
51          if (conditionalExpression instanceof ASTConditionalAndExpression &&
52                  !"==".equals(equalityExpression.getImage())) {
53              return;
54          }
55          if (conditionalExpression instanceof ASTConditionalOrExpression &&
56                  !"!=".equals(equalityExpression.getImage())) {
57              return;
58          }
59          ASTNullLiteral nullLiteral = equalityExpression.getFirstDescendantOfType(ASTNullLiteral.class);
60          if (nullLiteral == null) {
61              return;     //No null check
62          }
63          //If there is an assignment in the equalityExpression we give up, because things get too complex
64          if (conditionalExpression.hasDescendantOfType(ASTAssignmentOperator.class)) {
65              return;
66          }
67  
68          //Find the expression used in the null compare
69          ASTPrimaryExpression nullCompareExpression = findNullCompareExpression(equalityExpression);
70          if (nullCompareExpression == null) {
71              return;     //No good null check
72          }
73  
74          //Now we find the expression to compare to and do the comparison
75          for (int i = 0; i < conditionalExpression.jjtGetNumChildren(); i++) {
76              Node conditionalSubnode = conditionalExpression.jjtGetChild(i);
77  
78              //We skip the null compare branch
79              ASTEqualityExpression nullEqualityExpression = nullLiteral.getFirstParentOfType(ASTEqualityExpression.class);
80              if (conditionalSubnode.equals(nullEqualityExpression)) {
81                  continue;
82              }
83              ASTPrimaryExpression conditionalPrimaryExpression;
84              if (conditionalSubnode instanceof ASTPrimaryExpression) {
85                  conditionalPrimaryExpression = (ASTPrimaryExpression)conditionalSubnode;
86              } else {
87                  //The ASTPrimaryExpression is hidden (in a negation, braces or EqualityExpression)
88                  conditionalPrimaryExpression = conditionalSubnode
89                      .getFirstDescendantOfType(ASTPrimaryExpression.class);
90              }
91  
92              if (primaryExpressionsAreEqual(nullCompareExpression, conditionalPrimaryExpression)) {
93                  addViolation(data, node);   //We have a match
94              }
95  
96          }
97      }
98  
99      private boolean primaryExpressionsAreEqual(ASTPrimaryExpression nullCompareVariable, ASTPrimaryExpression expressionUsage) {
100         List<String> nullCompareNames = new ArrayList<>();
101         findExpressionNames(nullCompareVariable, nullCompareNames);
102 
103         List<String> expressionUsageNames = new ArrayList<>();
104         findExpressionNames(expressionUsage, expressionUsageNames);
105 
106         for (int i = 0; i < nullCompareNames.size(); i++) {
107             if (expressionUsageNames.size() == i) {
108                 return false;   //The used expression is shorter than the null compare expression (and we don't want to crash below)
109             }
110 
111             String nullCompareExpressionName = nullCompareNames.get(i);
112             String expressionUsageName       = expressionUsageNames.get(i);
113 
114             //Variablenames should match or the expressionUsage should have the variable with a method call (ie. var.equals())
115             if (!nullCompareExpressionName.equals(expressionUsageName) &&
116                     !expressionUsageName.startsWith(nullCompareExpressionName + ".")) {
117                 return false;   //Some other expression is being used after the null compare
118             }
119         }
120 
121         return true;
122     }
123 
124 
125     /**
126      * Find the names of variables, methods and array arguments in a PrimaryExpression.
127      */
128     private void findExpressionNames(Node nullCompareVariable, List<String> results) {
129         for (int i = 0; i < nullCompareVariable.jjtGetNumChildren(); i++) {
130             Node child = nullCompareVariable.jjtGetChild(i);
131 
132             if (child instanceof ASTName) {                   //Variable names and some method calls
133                 results.add( ((ASTName)child).getImage() );
134             } else if (child instanceof ASTLiteral) {         //Array arguments
135                 String literalImage = ((ASTLiteral)child).getImage();
136                 //Skip other null checks
137                 if (literalImage != null) {
138                     results.add( literalImage );
139                 }
140             } else if (child instanceof ASTPrimarySuffix) {   //More method calls
141                 String name = ((ASTPrimarySuffix)child).getImage();
142                 if (StringUtil.isNotEmpty(name)) {
143                     results.add(name);
144                 }
145             } else if (child instanceof ASTClassOrInterfaceType) {    //A class can be an argument too
146                 String name = ((ASTClassOrInterfaceType)child).getImage();
147                 results.add(name);
148             }
149 
150             if (child.jjtGetNumChildren() > 0) {
151                 findExpressionNames(child, results);
152             }
153         }
154     }
155 
156     private ASTPrimaryExpression findNullCompareExpression(ASTEqualityExpression equalityExpression) {
157         List<ASTPrimaryExpression> primaryExpressions = equalityExpression.findDescendantsOfType(ASTPrimaryExpression.class);
158         for (ASTPrimaryExpression primaryExpression: primaryExpressions) {
159             List<ASTPrimaryPrefix> primaryPrefixes = primaryExpression.findDescendantsOfType(ASTPrimaryPrefix.class);
160             for (ASTPrimaryPrefix primaryPrefix: primaryPrefixes) {
161                 if (primaryPrefix.hasDescendantOfType(ASTName.class)) {
162                     //We found the variable that is compared to null
163                     return primaryExpression;
164                 }
165             }
166         }
167         return null;  //Nothing found
168     }
169 
170 }