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 }