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 (n instanceof ASTCastExpression) {
051 ASTPrimaryExpression expr = (ASTPrimaryExpression) n.jjtGetChild(1);
052 if (expr.jjtGetNumChildren() > 1 && expr.jjtGetChild(1) instanceof ASTPrimaryPrefix) {
053 RuleContext ctx = (RuleContext) data;
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 = ((ASTName) child).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 = (RuleContext) data;
147 addViolation(ctx, throwStatement);
148 }
149 }
150 }
|