View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.lang.java.rule.design;
    5
    6   import java.util.List;
    7   import java.util.Map;
    8
    9   import net.sourceforge.pmd.RuleContext;
    10  import net.sourceforge.pmd.lang.ast.Node;
    11  import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
    12  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
    13  import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
    14  import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
    15  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
    16  import net.sourceforge.pmd.lang.java.ast.ASTName;
    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.ASTThrowStatement;
    20  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
    21  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
    22  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    23  import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
    24  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
    25
    26  import org.jaxen.JaxenException;
    27
    28  /**
    29   *
    30   * @author Unknown,
    31   * @author Romain PELISSE, belaran@gmail.com, fix for bug 1808110
    32   *
    33   */
    34  public class PreserveStackTraceRule extends AbstractJavaRule {
    35
    36      // FUTURE: This detection is name based, it should probably use Type Resolution, to become type "based"
    37      // it assumes the exception class contains 'Exception' in its name
    38      private static final String FIND_THROWABLE_INSTANCE =
    39  	"./VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression" +
    40  	"[ClassOrInterfaceType[contains(@Image,'Exception')] and Arguments[count(*)=0]]";
    41
    42      private static final String FILL_IN_STACKTRACE = ".fillInStackTrace";
    43
    44      @Override
    45      public Object visit(ASTCatchStatement catchStmt, Object data) {
    46          String target = catchStmt.jjtGetChild(0).findChildrenOfType(ASTVariableDeclaratorId.class).get(0).getImage();
    47          // Inspect all the throw stmt inside the catch stmt
    48          List<ASTThrowStatement> lstThrowStatements = catchStmt.findDescendantsOfType(ASTThrowStatement.class);
    49          for (ASTThrowStatement throwStatement : lstThrowStatements) {
    50              Node n = throwStatement.jjtGetChild(0).jjtGetChild(0);
    51              if (n instanceof ASTCastExpression) {
    52                  ASTPrimaryExpression expr = (ASTPrimaryExpression) n.jjtGetChild(1);
    53                  if (expr.jjtGetNumChildren() > 1 && expr.jjtGetChild(1) instanceof ASTPrimaryPrefix) {
    54                      RuleContext ctx = (RuleContext) data;
    55                      addViolation(ctx, throwStatement);
    56                  }
    57                  continue;
    58              }
    59              // Retrieve all argument for the throw exception (to see if the original exception is preserved)
    60              ASTArgumentList args = throwStatement.getFirstDescendantOfType(ASTArgumentList.class);
    61              if (args != null) {
    62                  Node parent = args.jjtGetParent().jjtGetParent();
    63                  if (parent instanceof ASTAllocationExpression) {
    64                      // maybe it is used inside a anonymous class
    65                      ck(data, target, throwStatement, parent);
    66                  } else {
    67                      ck(data, target, throwStatement, args);
    68                  }
    69              }
    70              else {
    71                  Node child = throwStatement.jjtGetChild(0);
    72                  while (child != null && child.jjtGetNumChildren() > 0
    73                          && !(child instanceof ASTName)) {
    74                      child = child.jjtGetChild(0);
    75                  }
    76                  if (child != null){
    77                      if ((child instanceof ASTName) && !target.equals(child.getImage()) && !child.hasImageEqualTo(target + FILL_IN_STACKTRACE)) {
    78                          Map<VariableNameDeclaration, List<NameOccurrence>> vars = ((ASTName) child).getScope().getVariableDeclarations();
    79                          for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry : vars.entrySet()) {
    80                              VariableNameDeclaration decl = entry.getKey();
    81                              List<NameOccurrence> occurrences = entry.getValue();
    82  	                        if (decl.getImage().equals(child.getImage())) {
    83  	                            if (!isInitCauseCalled(target, occurrences)) {
    84      		                        args = decl.getNode().jjtGetParent()
    85      		                                .getFirstDescendantOfType(ASTArgumentList.class);
    86      		                        if (args != null) {
    87      		                            ck(data, target, throwStatement, args);
    88      		                        }
    89  	                            }
    90  	                        }
    91  	                    }
    92                      } else if (child instanceof ASTClassOrInterfaceType){
    93                         addViolation(data, throwStatement);
    94                      }
    95                  }
    96              }
    97          }
    98          return super.visit(catchStmt, data);
    99      }
    100
    101     private boolean isInitCauseCalled(String target, List<NameOccurrence> occurrences) {
    102         boolean initCauseCalled = false;
    103         for (NameOccurrence occurrence : occurrences) {
    104             String image = null;
    105             if (occurrence.getLocation() != null) {
    106                 image = occurrence.getLocation().getImage();
    107             }
    108             if (image != null && image.endsWith("initCause")) {
    109                 ASTPrimaryExpression primaryExpression = occurrence.getLocation().getFirstParentOfType(ASTPrimaryExpression.class);
    110                 if (primaryExpression != null) {
    111                     ASTArgumentList args2 = primaryExpression.getFirstDescendantOfType(ASTArgumentList.class);
    112                     if (checkForTargetUsage(target, args2)) {
    113                         initCauseCalled = true;
    114                         break;
    115                     }
    116                 }
    117             }
    118         }
    119         return initCauseCalled;
    120     }
    121
    122     @Override
    123     public Object visit(ASTVariableDeclarator node, Object data) {
    124 	// Search Catch stmt nodes for variable used to store improperly created throwable or exception
    125 	try {
    126 	    if (node.hasDescendantMatchingXPath(FIND_THROWABLE_INSTANCE)) {
    127 		String variableName = node.jjtGetChild(0).getImage(); // VariableDeclatorId
    128 		ASTCatchStatement catchStmt = node.getFirstParentOfType(ASTCatchStatement.class);
    129
    130 		while (catchStmt != null) {
    131 		    List<Node> violations = catchStmt.findChildNodesWithXPath("//Expression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + variableName + "']");
    132 		    if (!violations.isEmpty()) {
    133 			// If, after this allocation, the 'initCause' method is called, and the ex passed
    134 			// this is not a violation
    135 			if (!useInitCause(violations.get(0), catchStmt)) {
    136 			    addViolation(data, node);
    137 			}
    138 		    }
    139
    140 		    // check ASTCatchStatement higher up
    141 		    catchStmt = catchStmt.getFirstParentOfType(ASTCatchStatement.class);
    142 		}
    143 	    }
    144 	    return super.visit(node, data);
    145 	} catch (JaxenException e) {
    146 	    // XPath is valid, this should never happens...
    147 	    throw new IllegalStateException(e);
    148 	}
    149     }
    150
    151 	private boolean useInitCause(Node node, ASTCatchStatement catchStmt) {
    152 		// In case of NPE...
    153 		if ( node != null && node.getImage() != null )
    154 		{
    155 			return catchStmt.hasDescendantMatchingXPath("./Block/BlockStatement/Statement/StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + node.getImage() + ".initCause']");
    156 		}
    157 		return false;
    158 	}
    159
    160     /**
    161      * Checks whether the given target is in the argument list.
    162      * If this is the case, then the target (root exception) is used as the cause.
    163      * @param target
    164      * @param baseNode
    165      */
    166 	private boolean checkForTargetUsage(String target, Node baseNode) {
    167 	    boolean match = false;
    168 	    if (target != null && baseNode != null) {
    169             List<ASTName> nameNodes = baseNode.findDescendantsOfType(ASTName.class);
    170             for (ASTName nameNode : nameNodes) {
    171                 if (target.equals(nameNode.getImage())) {
    172                     match = true;
    173                     break;
    174                 }
    175             }
    176 	    }
    177         return match;
    178 	}
    179
    180 	private void ck(Object data, String target, ASTThrowStatement throwStatement,
    181                     Node baseNode) {
    182         if (!checkForTargetUsage(target, baseNode)) {
    183             RuleContext ctx = (RuleContext) data;
    184             addViolation(ctx, throwStatement);
    185         }
    186     }
    187 }