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 }