View Javadoc

1   package net.sourceforge.pmd.rules.basic;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   
6   import net.sourceforge.pmd.AbstractRule;
7   import net.sourceforge.pmd.ast.ASTAssignmentOperator;
8   import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
9   import net.sourceforge.pmd.ast.ASTConditionalAndExpression;
10  import net.sourceforge.pmd.ast.ASTConditionalOrExpression;
11  import net.sourceforge.pmd.ast.ASTEqualityExpression;
12  import net.sourceforge.pmd.ast.ASTExpression;
13  import net.sourceforge.pmd.ast.ASTIfStatement;
14  import net.sourceforge.pmd.ast.ASTLiteral;
15  import net.sourceforge.pmd.ast.ASTName;
16  import net.sourceforge.pmd.ast.ASTNullLiteral;
17  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
18  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
19  import net.sourceforge.pmd.ast.ASTPrimarySuffix;
20  import net.sourceforge.pmd.ast.Node;
21  import net.sourceforge.pmd.ast.SimpleJavaNode;
22  
23  public class BrokenNullCheck extends AbstractRule {
24  
25      public Object visit(ASTIfStatement node, Object data) {
26          ASTExpression expression = (ASTExpression)node.jjtGetChild(0);
27          
28          ASTConditionalAndExpression conditionalAndExpression = expression.getFirstChildOfType(ASTConditionalAndExpression.class);
29          if (conditionalAndExpression != null) {
30              checkForViolations(node, data, conditionalAndExpression);
31          }
32          
33          ASTConditionalOrExpression conditionalOrExpression = expression.getFirstChildOfType(ASTConditionalOrExpression.class);
34          if (conditionalOrExpression != null) {
35              checkForViolations(node, data, conditionalOrExpression);
36          }
37  
38          return super.visit(node, data);
39      }
40  
41  
42      private void checkForViolations(ASTIfStatement node, Object data, SimpleJavaNode conditionalExpression) {
43          ASTEqualityExpression equalityExpression = getFirstDirectChildOfType(ASTEqualityExpression.class, conditionalExpression);
44          if (equalityExpression == null) {
45              return;
46          }
47          if (conditionalExpression instanceof ASTConditionalAndExpression && 
48                  !"==".equals(equalityExpression.getImage())) {
49              return;
50          }
51          if (conditionalExpression instanceof ASTConditionalOrExpression && 
52                  !"!=".equals(equalityExpression.getImage())) {
53              return;
54          }
55          ASTNullLiteral nullLiteral = equalityExpression.getFirstChildOfType(ASTNullLiteral.class);
56          if (nullLiteral == null) {
57              return;     //No null check
58          }
59          //If there is an assignment in the equalityExpression we give up, because things get too complex
60          if (conditionalExpression.getFirstChildOfType(ASTAssignmentOperator.class) != null) {
61              return;
62          }
63  
64          //Find the expression used in the null compare
65          ASTPrimaryExpression nullCompareExpression = findNullCompareExpression(equalityExpression);
66          if (nullCompareExpression == null) {
67              return;     //No good null check
68          }
69          
70          //Now we find the expression to compare to and do the comparison
71          for (int i = 0; i < conditionalExpression.jjtGetNumChildren(); i++) {
72              SimpleJavaNode conditionalSubnode = (SimpleJavaNode)conditionalExpression.jjtGetChild(i);
73              
74              //We skip the null compare branch
75              ASTEqualityExpression nullEqualityExpression = nullLiteral.getFirstParentOfType(ASTEqualityExpression.class);
76              if (conditionalSubnode.equals(nullEqualityExpression)) {
77                  continue;
78              }
79              ASTPrimaryExpression conditionalPrimaryExpression;
80              if (conditionalSubnode instanceof ASTPrimaryExpression) {
81                  conditionalPrimaryExpression = (ASTPrimaryExpression)conditionalSubnode;
82              } else {
83                  //The ASTPrimaryExpression is hidden (in a negation, braces or EqualityExpression)
84                  conditionalPrimaryExpression = conditionalSubnode
85                      .getFirstChildOfType(ASTPrimaryExpression.class);
86              }
87  
88              if (primaryExpressionsAreEqual(nullCompareExpression, conditionalPrimaryExpression)) {
89                  addViolation(data, node);   //We have a match
90              }
91  
92          }
93      }
94  
95      private boolean primaryExpressionsAreEqual(ASTPrimaryExpression nullCompareVariable, ASTPrimaryExpression expressionUsage) {
96          List<String> nullCompareNames = new ArrayList<String>();
97          findExpressionNames(nullCompareVariable, nullCompareNames);
98          
99          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.getClass().equals(ASTName.class)) {                   //Variable names and some method calls
129                 results.add( ((ASTName)child).getImage() );
130             } else if (child.getClass().equals(ASTLiteral.class)) {         //Array arguments
131                 String literalImage = ((ASTLiteral)child).getImage();
132                 //Skip other null checks
133                 if (literalImage != null) {
134                     results.add( literalImage );
135                 }
136             } else if (child.getClass().equals(ASTPrimarySuffix.class)) {   //More method calls
137                 String name = ((ASTPrimarySuffix)child).getImage();
138                 if (name != null && !name.equals("")) {
139                     results.add(name);
140                 }
141             } else if (child.getClass().equals(ASTClassOrInterfaceType.class)) {    //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.findChildrenOfType(ASTPrimaryExpression.class);
154         for (ASTPrimaryExpression primaryExpression: primaryExpressions) {
155             List<ASTPrimaryPrefix> primaryPrefixes = primaryExpression.findChildrenOfType(ASTPrimaryPrefix.class);
156             for (ASTPrimaryPrefix primaryPrefix: primaryPrefixes) {
157                 ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class);
158                 if (name != null) {
159                     //We found the variable that is compared to null
160                     return primaryExpression;
161                 }
162             }
163         }
164         return null;  //Nothing found
165     }
166 
167     private <T> T getFirstDirectChildOfType(Class<T> childType, Node node) {
168         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
169             SimpleJavaNode simpleNode = (SimpleJavaNode) node.jjtGetChild(i);
170             if (simpleNode.getClass().equals(childType))
171                 return (T)simpleNode;
172         }
173         return null;
174     }
175 }