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.add( literalImage );
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 }
|