View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules.design;
5   
6   import java.util.ArrayList;
7   import java.util.List;
8   import java.util.Map;
9   
10  import net.sourceforge.pmd.AbstractJavaRule;
11  import net.sourceforge.pmd.RuleContext;
12  import net.sourceforge.pmd.ast.ASTArgumentList;
13  import net.sourceforge.pmd.ast.ASTCastExpression;
14  import net.sourceforge.pmd.ast.ASTCatchStatement;
15  import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
16  import net.sourceforge.pmd.ast.ASTName;
17  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
18  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
19  import net.sourceforge.pmd.ast.ASTThrowStatement;
20  import net.sourceforge.pmd.ast.Node;
21  import net.sourceforge.pmd.ast.SimpleNode;
22  import net.sourceforge.pmd.symboltable.NameOccurrence;
23  import net.sourceforge.pmd.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 PreserveStackTrace extends AbstractJavaRule {
34  
35      private List<ASTName> nameNodes = new ArrayList<ASTName>();
36  
37      // FUTURE: This is dectection is name based, it should probably used Type Resolution, to become type "based"
38      private static final String FIND_THROWABLE_INSTANCE = "//VariableDeclaratorId[" +
39  						    "(../descendant::VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType" +
40  						    "[" +
41  						    			"contains(@Image,'Exception')" + // Assuming the Exception class does contains 'Exception' in its name
42  						    			"and" +
43  						    			"(not (../Arguments/ArgumentList))" +
44  						    "]" +
45  						    ")]";
46  
47      private static final String ILLEGAL_STATE_EXCEPTION = "IllegalStateException";
48      private static final String FILL_IN_STACKTRACE = ".fillInStackTrace";
49  
50      public Object visit(ASTCatchStatement catchStmt, Object data) {
51          String target = ((SimpleNode)catchStmt.jjtGetChild(0).jjtGetChild(1)).getImage();
52          // Gather every variable used to store exception instance created without any argument, inside the catch
53          gatherVariableWithExceptionRef(catchStmt,data);
54          // Inspect all the throw stmt inside the catch stmt
55          List<ASTThrowStatement> lstThrowStatements = catchStmt.findChildrenOfType(ASTThrowStatement.class);
56          for (ASTThrowStatement throwStatement : lstThrowStatements) {
57              Node n = throwStatement.jjtGetChild(0).jjtGetChild(0);
58              if (n.getClass().equals(ASTCastExpression.class)) {
59                  ASTPrimaryExpression expr = (ASTPrimaryExpression) n.jjtGetChild(1);
60                  if (expr.jjtGetNumChildren() > 1 && expr.jjtGetChild(1).getClass().equals(ASTPrimaryPrefix.class)) {
61                      RuleContext ctx = (RuleContext) data;
62                      addViolation(ctx, throwStatement);
63                  }
64                  continue;
65              }
66              // If the thrown exception is IllegalStateException, no way to preserve the exception (the constructor has no args)
67              if ( ! isThrownExceptionOfType(throwStatement,ILLEGAL_STATE_EXCEPTION) ) {
68  	            // Retrieve all argument for the throw exception (to see if the original exception is preserved)
69  	            ASTArgumentList args = throwStatement.getFirstChildOfType(ASTArgumentList.class);
70  
71  	            if (args != null) {
72  	                ck(data, target, throwStatement, args);
73  	            }
74  	            else {
75  	        	Node child = throwStatement.jjtGetChild(0);
76  	                while (child != null && child.jjtGetNumChildren() > 0
77  	                        && !child.getClass().equals(ASTName.class)) {
78  	                    child = child.jjtGetChild(0);
79  	                }
80  	                if (child != null){
81  	                    if( child.getClass().equals(ASTName.class) && (!target.equals(((SimpleNode)child).getImage()) && !((SimpleNode)child).hasImageEqualTo(target + FILL_IN_STACKTRACE))) {
82  	                        Map<VariableNameDeclaration, List<NameOccurrence>> vars = ((ASTName) child).getScope().getVariableDeclarations();
83  		                    for (VariableNameDeclaration decl: vars.keySet()) {
84  		                        args = ((SimpleNode)decl.getNode().jjtGetParent())
85  		                                .getFirstChildOfType(ASTArgumentList.class);
86  		                        if (args != null) {
87  		                            ck(data, target, throwStatement, args);
88  		                        }
89  		                    }
90  	                    } else if(child.getClass().equals(ASTClassOrInterfaceType.class)){
91  	                       addViolation(data, throwStatement);
92  	                    }
93  	                }
94  	            }
95              }
96  
97          }
98          return super.visit(catchStmt, data);
99      }
100 
101     /*
102      * Search Catch stmt nodes for variable used to store unproperly created throwable or exception
103      */
104     private void gatherVariableWithExceptionRef(ASTCatchStatement catchStmt, Object data) {
105     	try {
106 			List<Node> nodes = catchStmt.findChildNodesWithXPath(FIND_THROWABLE_INSTANCE);
107 			for ( Node node : nodes ) {
108 				List <Node> violations = catchStmt.findChildNodesWithXPath("//Expression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + ((SimpleNode)node).getImage() + "']");
109 				if ( violations != null && violations.size() > 0 ) {
110 					// If, after this allocation, the 'initCause' method is called, and the ex passed
111 					// this is not a violation
112 					if ( ! useInitCause((Node)violations.get(0),catchStmt) ) //FIXME: iterate, better than get(0) ?
113 						super.addViolation(data,(SimpleNode) node);
114 				}
115 			}
116 		} catch (JaxenException e) {
117 			// XPath is valid, this should never happens...
118 			e.printStackTrace();
119 		}
120 
121 	}
122 
123 	private boolean useInitCause(Node node, ASTCatchStatement catchStmt) throws JaxenException {
124 		// In case of NPE...
125 		if ( node != null && ((SimpleNode)node).getImage() != null )
126 		{
127 			List <Node> nodes = catchStmt.findChildNodesWithXPath("descendant::StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + ((SimpleNode)node).getImage() + ".initCause']");
128 			if ( nodes != null && nodes.size() > 0 )
129 			{
130 				return true;
131 			}
132 		}
133 		return false;
134 	}
135 
136 	private boolean isThrownExceptionOfType(ASTThrowStatement throwStatement,String type) {
137     	boolean status = false;
138     	try {
139 			List<Node> results = throwStatement.findChildNodesWithXPath("Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[@Image = '" + type + "']");
140 			// If we have a match, return true
141 			if ( results != null && results.size() == 1 ) {
142 				status = true;
143 			}
144 		} catch (JaxenException e) {
145 			// XPath is valid, this should never happens !
146 			e.printStackTrace();
147 		}
148     	return status;
149 	}
150 
151 	private void ck(Object data, String target, ASTThrowStatement throwStatement,
152                     ASTArgumentList args) {
153         boolean match = false;
154         nameNodes.clear();
155         args.findChildrenOfType(ASTName.class, nameNodes);
156         for (ASTName nameNode : nameNodes) {
157             if (target.equals(nameNode.getImage())) {
158                 match = true;
159                 break;
160             }
161         }
162         if ( ! match) {
163             RuleContext ctx = (RuleContext) data;
164             addViolation(ctx, throwStatement);
165         }
166     }
167 }