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.ASTVariableDeclarator;
21  import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
22  import net.sourceforge.pmd.ast.Node;
23  import net.sourceforge.pmd.ast.SimpleNode;
24  import net.sourceforge.pmd.symboltable.NameOccurrence;
25  import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
26  
27  import org.jaxen.JaxenException;
28  
29  /**
30   *
31   * @author Unknown,
32   * @author Romain PELISSE, belaran@gmail.com, fix for bug 1808110
33   *
34   */
35  public class PreserveStackTrace extends AbstractJavaRule {
36  
37      private List<ASTName> nameNodes = new ArrayList<ASTName>();
38  
39      // FUTURE: This detection is name based, it should probably use Type Resolution, to become type "based"
40      // it assumes the exception class contains 'Exception' in its name
41      private static final String FIND_THROWABLE_INSTANCE =
42  	"./VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression" +
43  	"[ClassOrInterfaceType[contains(@Image,'Exception')] and Arguments[count(*)=0]]";
44  
45      private static final String ILLEGAL_STATE_EXCEPTION = "IllegalStateException";
46      private static final String FILL_IN_STACKTRACE = ".fillInStackTrace";
47  
48      @Override
49      public Object visit(ASTCatchStatement catchStmt, Object data) {
50          String target = catchStmt.findChildrenOfType(ASTVariableDeclaratorId.class).get(0).getImage();
51          // Inspect all the throw stmt inside the catch stmt
52          List<ASTThrowStatement> lstThrowStatements = catchStmt.findChildrenOfType(ASTThrowStatement.class);
53          for (ASTThrowStatement throwStatement : lstThrowStatements) {
54              Node n = throwStatement.jjtGetChild(0).jjtGetChild(0);
55              if (n.getClass().equals(ASTCastExpression.class)) {
56                  ASTPrimaryExpression expr = (ASTPrimaryExpression) n.jjtGetChild(1);
57                  if (expr.jjtGetNumChildren() > 1 && expr.jjtGetChild(1).getClass().equals(ASTPrimaryPrefix.class)) {
58                      RuleContext ctx = (RuleContext) data;
59                      addViolation(ctx, throwStatement);
60                  }
61                  continue;
62              }
63              // If the thrown exception is IllegalStateException, no way to preserve the exception (the constructor has no args)
64              if ( ! isThrownExceptionOfType(throwStatement,ILLEGAL_STATE_EXCEPTION) ) {
65  	            // Retrieve all argument for the throw exception (to see if the original exception is preserved)
66  	            ASTArgumentList args = throwStatement.getFirstChildOfType(ASTArgumentList.class);
67  
68  	            if (args != null) {
69  	                ck(data, target, throwStatement, args);
70  	            }
71  	            else {
72  	        	SimpleNode child = (SimpleNode)throwStatement.jjtGetChild(0);
73  	                while (child != null && child.jjtGetNumChildren() > 0
74  	                        && !child.getClass().equals(ASTName.class)) {
75  	                    child = (SimpleNode)child.jjtGetChild(0);
76  	                }
77  	                if (child != null){
78  	                    if( child.getClass().equals(ASTName.class) && !target.equals(child.getImage()) && !child.hasImageEqualTo(target + FILL_IN_STACKTRACE)) {
79  	                        Map<VariableNameDeclaration, List<NameOccurrence>> vars = ((ASTName) child).getScope().getVariableDeclarations();
80  		                    for (VariableNameDeclaration decl: vars.keySet()) {
81  		                        args = ((SimpleNode)decl.getNode().jjtGetParent())
82  		                                .getFirstChildOfType(ASTArgumentList.class);
83  		                        if (args != null) {
84  		                            ck(data, target, throwStatement, args);
85  		                        }
86  		                    }
87  	                    } else if(child.getClass().equals(ASTClassOrInterfaceType.class)){
88  	                       addViolation(data, throwStatement);
89  	                    }
90  	                }
91  	            }
92              }
93  
94          }
95          return super.visit(catchStmt, data);
96      }
97  
98      @Override
99      public Object visit(ASTVariableDeclarator node, Object data) {
100 	// Search Catch stmt nodes for variable used to store improperly created throwable or exception
101 	try {
102 	    List<Node> nodes = node.findChildNodesWithXPath(FIND_THROWABLE_INSTANCE);
103 	    if (nodes.size() > 0) {
104 		String variableName = ((SimpleNode)node.jjtGetChild(0)).getImage(); // VariableDeclatorId
105 		ASTCatchStatement catchStmt = node.getFirstParentOfType(ASTCatchStatement.class);
106 
107 		while (catchStmt != null) {
108 		    List<SimpleNode> violations = catchStmt.findChildNodesWithXPath("//Expression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + variableName + "']");
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(violations.get(0), catchStmt)) {
113 			    addViolation(data, node);
114 			}
115 		    }
116 
117 		    // check ASTCatchStatement higher up
118 		    catchStmt = catchStmt.getFirstParentOfType(ASTCatchStatement.class);
119 		}
120 	    }
121 	    return super.visit(node, data);
122 	} catch (JaxenException e) {
123 	    // XPath is valid, this should never happens...
124 	    throw new IllegalStateException(e);
125 	}
126     }
127 
128 	private boolean useInitCause(SimpleNode node, ASTCatchStatement catchStmt) throws JaxenException {
129 		// In case of NPE...
130 		if ( node != null && node.getImage() != null )
131 		{
132 			List <Node> nodes = catchStmt.findChildNodesWithXPath("./Block/BlockStatement/Statement/StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + node.getImage() + ".initCause']");
133 			if ( nodes != null && nodes.size() > 0 )
134 			{
135 				return true;
136 			}
137 		}
138 		return false;
139 	}
140 
141 	private boolean isThrownExceptionOfType(ASTThrowStatement throwStatement,String type) {
142     	boolean status = false;
143     	try {
144 			List<Node> results = throwStatement.findChildNodesWithXPath("Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[@Image = '" + type + "']");
145 			// If we have a match, return true
146 			if ( results != null && results.size() == 1 ) {
147 				status = true;
148 			}
149 		} catch (JaxenException e) {
150 			// XPath is valid, this should never happens !
151 			e.printStackTrace();
152 		}
153     	return status;
154 	}
155 
156 	private void ck(Object data, String target, ASTThrowStatement throwStatement,
157                     ASTArgumentList args) {
158         boolean match = false;
159         nameNodes.clear();
160         args.findChildrenOfType(ASTName.class, nameNodes);
161         for (ASTName nameNode : nameNodes) {
162             if (target.equals(nameNode.getImage())) {
163                 match = true;
164                 break;
165             }
166         }
167         if ( ! match) {
168             RuleContext ctx = (RuleContext) data;
169             addViolation(ctx, throwStatement);
170         }
171     }
172 }