PreserveStackTraceRule.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.java.rule.design;
005 
006 import java.util.List;
007 import java.util.Map;
008 
009 import net.sourceforge.pmd.RuleContext;
010 import net.sourceforge.pmd.lang.ast.Node;
011 import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
012 import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
013 import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
014 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
015 import net.sourceforge.pmd.lang.java.ast.ASTName;
016 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
017 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
018 import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
019 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
020 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
021 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
022 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
023 
024 import org.jaxen.JaxenException;
025 
026 /**
027  *
028  @author Unknown,
029  @author Romain PELISSE, belaran@gmail.com, fix for bug 1808110
030  *
031  */
032 public class PreserveStackTraceRule extends AbstractJavaRule {
033 
034     // FUTURE: This detection is name based, it should probably use Type Resolution, to become type "based"
035     // it assumes the exception class contains 'Exception' in its name
036     private static final String FIND_THROWABLE_INSTANCE =
037   "./VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression" +
038   "[ClassOrInterfaceType[contains(@Image,'Exception')] and Arguments[count(*)=0]]";
039 
040     private static final String ILLEGAL_STATE_EXCEPTION = "IllegalStateException";
041     private static final String FILL_IN_STACKTRACE = ".fillInStackTrace";
042 
043     @Override
044     public Object visit(ASTCatchStatement catchStmt, Object data) {
045         String target = catchStmt.jjtGetChild(0).jjtGetChild(1).getImage();
046         // Inspect all the throw stmt inside the catch stmt
047         List<ASTThrowStatement> lstThrowStatements = catchStmt.findDescendantsOfType(ASTThrowStatement.class);
048         for (ASTThrowStatement throwStatement : lstThrowStatements) {
049             Node n = throwStatement.jjtGetChild(0).jjtGetChild(0);
050             if (instanceof ASTCastExpression) {
051                 ASTPrimaryExpression expr = (ASTPrimaryExpressionn.jjtGetChild(1);
052                 if (expr.jjtGetNumChildren() && expr.jjtGetChild(1instanceof ASTPrimaryPrefix) {
053                     RuleContext ctx = (RuleContextdata;
054                     addViolation(ctx, throwStatement);
055                 }
056                 continue;
057             }
058             // If the thrown exception is IllegalStateException, no way to preserve the exception (the constructor has no args)
059             if ! isThrownExceptionOfType(throwStatement,ILLEGAL_STATE_EXCEPTION) ) {
060               // Retrieve all argument for the throw exception (to see if the original exception is preserved)
061               ASTArgumentList args = throwStatement.getFirstDescendantOfType(ASTArgumentList.class);
062 
063               if (args != null) {
064                   ck(data, target, throwStatement, args);
065               }
066               else {
067             Node child = throwStatement.jjtGetChild(0);
068                   while (child != null && child.jjtGetNumChildren() 0
069                           && !(child instanceof ASTName)) {
070                       child = child.jjtGetChild(0);
071                   }
072                   if (child != null){
073                       if ((child instanceof ASTName&& !target.equals(child.getImage()) && !child.hasImageEqualTo(target + FILL_IN_STACKTRACE)) {
074                           Map<VariableNameDeclaration, List<NameOccurrence>> vars = ((ASTNamechild).getScope().getVariableDeclarations();
075                         for (VariableNameDeclaration decl: vars.keySet()) {
076                             args = decl.getNode().jjtGetParent()
077                                     .getFirstDescendantOfType(ASTArgumentList.class);
078                             if (args != null) {
079                                 ck(data, target, throwStatement, args);
080                             }
081                         }
082                       else if (child instanceof ASTClassOrInterfaceType){
083                          addViolation(data, throwStatement);
084                       }
085                   }
086               }
087             }
088 
089         }
090         return super.visit(catchStmt, data);
091     }
092 
093     @Override
094     public Object visit(ASTVariableDeclarator node, Object data) {
095   // Search Catch stmt nodes for variable used to store improperly created throwable or exception
096   try {
097       if (node.hasDescendantMatchingXPath(FIND_THROWABLE_INSTANCE)) {
098     String variableName = node.jjtGetChild(0).getImage()// VariableDeclatorId
099     ASTCatchStatement catchStmt = node.getFirstParentOfType(ASTCatchStatement.class);
100 
101     while (catchStmt != null) {
102         List<Node> violations = catchStmt.findChildNodesWithXPath("//Expression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + variableName + "']");
103         if (!violations.isEmpty()) {
104       // If, after this allocation, the 'initCause' method is called, and the ex passed
105       // this is not a violation
106       if (!useInitCause(violations.get(0), catchStmt)) {
107           addViolation(data, node);
108       }
109         }
110 
111         // check ASTCatchStatement higher up
112         catchStmt = catchStmt.getFirstParentOfType(ASTCatchStatement.class);
113     }
114       }
115       return super.visit(node, data);
116   catch (JaxenException e) {
117       // XPath is valid, this should never happens...
118       throw new IllegalStateException(e);
119   }
120     }
121 
122   private boolean useInitCause(Node node, ASTCatchStatement catchStmt) {
123     // In case of NPE...
124     if node != null && node.getImage() != null )
125     {
126       return catchStmt.hasDescendantMatchingXPath("descendant::StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + node.getImage() ".initCause']");
127     }
128     return false;
129   }
130 
131     private boolean isThrownExceptionOfType(ASTThrowStatement throwStatement,String type) {
132         return throwStatement.hasDescendantMatchingXPath("Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[@Image = '" + type + "']");
133     }
134 
135   private void ck(Object data, String target, ASTThrowStatement throwStatement,
136                     ASTArgumentList args) {
137         boolean match = false;
138         List<ASTName> nameNodes = args.findDescendantsOfType(ASTName.class);
139         for (ASTName nameNode : nameNodes) {
140             if (target.equals(nameNode.getImage())) {
141                 match = true;
142                 break;
143             }
144         }
145         if ! match) {
146             RuleContext ctx = (RuleContextdata;
147             addViolation(ctx, throwStatement);
148         }
149     }
150 }