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