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.ASTArgumentList;
12  import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
13  import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
15  import net.sourceforge.pmd.lang.java.ast.ASTName;
16  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
17  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
18  import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
19  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
20  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
21  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
22  import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
23  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
24  
25  import org.jaxen.JaxenException;
26  
27  /**
28   *
29   * @author Unknown,
30   * @author Romain PELISSE, belaran@gmail.com, fix for bug 1808110
31   *
32   */
33  public class PreserveStackTraceRule extends AbstractJavaRule {
34  
35      // FUTURE: This detection is name based, it should probably use Type Resolution, to become type "based"
36      // it assumes the exception class contains 'Exception' in its name
37      private static final String FIND_THROWABLE_INSTANCE =
38  	"./VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression" +
39  	"[ClassOrInterfaceType[contains(@Image,'Exception')] and Arguments[count(*)=0]]";
40  
41      private static final String ILLEGAL_STATE_EXCEPTION = "IllegalStateException";
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              // If the thrown exception is IllegalStateException, no way to preserve the exception (the constructor has no args)
60              if ( ! isThrownExceptionOfType(throwStatement,ILLEGAL_STATE_EXCEPTION) ) {
61  	            // Retrieve all argument for the throw exception (to see if the original exception is preserved)
62  	            ASTArgumentList args = throwStatement.getFirstDescendantOfType(ASTArgumentList.class);
63  
64  	            if (args != null) {
65  	                ck(data, target, throwStatement, args);
66  	            }
67  	            else {
68  	        	Node child = throwStatement.jjtGetChild(0);
69  	                while (child != null && child.jjtGetNumChildren() > 0
70  	                        && !(child instanceof ASTName)) {
71  	                    child = child.jjtGetChild(0);
72  	                }
73  	                if (child != null){
74  	                    if ((child instanceof ASTName) && !target.equals(child.getImage()) && !child.hasImageEqualTo(target + FILL_IN_STACKTRACE)) {
75  	                        Map<VariableNameDeclaration, List<NameOccurrence>> vars = ((ASTName) child).getScope().getVariableDeclarations();
76  		                    for (VariableNameDeclaration decl: vars.keySet()) {
77  		                        args = decl.getNode().jjtGetParent()
78  		                                .getFirstDescendantOfType(ASTArgumentList.class);
79  		                        if (args != null) {
80  		                            ck(data, target, throwStatement, args);
81  		                        }
82  		                    }
83  	                    } else if (child instanceof ASTClassOrInterfaceType){
84  	                       addViolation(data, throwStatement);
85  	                    }
86  	                }
87  	            }
88              }
89  
90          }
91          return super.visit(catchStmt, data);
92      }
93  
94      @Override
95      public Object visit(ASTVariableDeclarator node, Object data) {
96  	// Search Catch stmt nodes for variable used to store improperly created throwable or exception
97  	try {
98  	    if (node.hasDescendantMatchingXPath(FIND_THROWABLE_INSTANCE)) {
99  		String variableName = node.jjtGetChild(0).getImage(); // VariableDeclatorId
100 		ASTCatchStatement catchStmt = node.getFirstParentOfType(ASTCatchStatement.class);
101 
102 		while (catchStmt != null) {
103 		    List<Node> violations = catchStmt.findChildNodesWithXPath("//Expression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + variableName + "']");
104 		    if (!violations.isEmpty()) {
105 			// If, after this allocation, the 'initCause' method is called, and the ex passed
106 			// this is not a violation
107 			if (!useInitCause(violations.get(0), catchStmt)) {
108 			    addViolation(data, node);
109 			}
110 		    }
111 
112 		    // check ASTCatchStatement higher up
113 		    catchStmt = catchStmt.getFirstParentOfType(ASTCatchStatement.class);
114 		}
115 	    }
116 	    return super.visit(node, data);
117 	} catch (JaxenException e) {
118 	    // XPath is valid, this should never happens...
119 	    throw new IllegalStateException(e);
120 	}
121     }
122 
123 	private boolean useInitCause(Node node, ASTCatchStatement catchStmt) {
124 		// In case of NPE...
125 		if ( node != null && node.getImage() != null )
126 		{
127 			return catchStmt.hasDescendantMatchingXPath("./Block/BlockStatement/Statement/StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + node.getImage() + ".initCause']");
128 		}
129 		return false;
130 	}
131 
132     private boolean isThrownExceptionOfType(ASTThrowStatement throwStatement,String type) {
133         return throwStatement.hasDescendantMatchingXPath("Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[@Image = '" + type + "']");
134     }
135 
136 	private void ck(Object data, String target, ASTThrowStatement throwStatement,
137                     ASTArgumentList args) {
138         boolean match = false;
139         List<ASTName> nameNodes = args.findDescendantsOfType(ASTName.class);
140         for (ASTName nameNode : nameNodes) {
141             if (target.equals(nameNode.getImage())) {
142                 match = true;
143                 break;
144             }
145         }
146         if ( ! match) {
147             RuleContext ctx = (RuleContext) data;
148             addViolation(ctx, throwStatement);
149         }
150     }
151 }