View Javadoc

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