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.ASTAllocationExpression;
12  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
13  import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
14  import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
15  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
16  import net.sourceforge.pmd.lang.java.ast.ASTName;
17  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
18  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
19  import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
20  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
21  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
22  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
23  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
24  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
25  
26  import org.jaxen.JaxenException;
27  
28  /**
29   *
30   * @author Unknown,
31   * @author Romain PELISSE, belaran@gmail.com, fix for bug 1808110
32   *
33   */
34  public class PreserveStackTraceRule extends AbstractJavaRule {
35  
36      // FUTURE: This detection is name based, it should probably use Type
37      // Resolution, to become type "based"
38      // it assumes the exception class contains 'Exception' in its name
39      private static final String FIND_THROWABLE_INSTANCE = "./VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression"
40              + "[ClassOrInterfaceType[contains(@Image,'Exception')] and Arguments[count(*)=0]]";
41  
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              // Retrieve all argument for the throw exception (to see if the
60              // original exception is preserved)
61              ASTArgumentList args = throwStatement.getFirstDescendantOfType(ASTArgumentList.class);
62              if (args != null) {
63                  Node parent = args.jjtGetParent().jjtGetParent();
64                  if (parent instanceof ASTAllocationExpression) {
65                      // maybe it is used inside a anonymous class
66                      ck(data, target, throwStatement, parent);
67                  } else {
68                      ck(data, target, throwStatement, args);
69                  }
70              } else {
71                  Node child = throwStatement.jjtGetChild(0);
72                  while (child != null && child.jjtGetNumChildren() > 0 && !(child instanceof ASTName)) {
73                      child = child.jjtGetChild(0);
74                  }
75                  if (child != null) {
76                      if (child instanceof ASTName && !target.equals(child.getImage())
77                              && !child.hasImageEqualTo(target + FILL_IN_STACKTRACE)) {
78                          Map<VariableNameDeclaration, List<NameOccurrence>> vars = ((ASTName) child).getScope()
79                                  .getDeclarations(VariableNameDeclaration.class);
80                          for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry : vars.entrySet()) {
81                              VariableNameDeclaration decl = entry.getKey();
82                              List<NameOccurrence> occurrences = entry.getValue();
83                              if (decl.getImage().equals(child.getImage())) {
84                                  if (!isInitCauseCalled(target, occurrences)) {
85                                      args = decl.getNode().jjtGetParent()
86                                              .getFirstDescendantOfType(ASTArgumentList.class);
87                                      if (args != null) {
88                                          ck(data, target, throwStatement, args);
89                                      }
90                                  }
91                              }
92                          }
93                      } else if (child instanceof ASTClassOrInterfaceType) {
94                          addViolation(data, throwStatement);
95                      }
96                  }
97              }
98          }
99          return super.visit(catchStmt, data);
100     }
101 
102     private boolean isInitCauseCalled(String target, List<NameOccurrence> occurrences) {
103         boolean initCauseCalled = false;
104         for (NameOccurrence occurrence : occurrences) {
105             String image = null;
106             if (occurrence.getLocation() != null) {
107                 image = occurrence.getLocation().getImage();
108             }
109             if (image != null && image.endsWith("initCause")) {
110                 ASTPrimaryExpression primaryExpression = occurrence.getLocation().getFirstParentOfType(
111                         ASTPrimaryExpression.class);
112                 if (primaryExpression != null) {
113                     ASTArgumentList args2 = primaryExpression.getFirstDescendantOfType(ASTArgumentList.class);
114                     if (checkForTargetUsage(target, args2)) {
115                         initCauseCalled = true;
116                         break;
117                     }
118                 }
119             }
120         }
121         return initCauseCalled;
122     }
123 
124     @Override
125     public Object visit(ASTVariableDeclarator node, Object data) {
126         // Search Catch stmt nodes for variable used to store improperly created
127         // throwable or exception
128         try {
129             if (node.hasDescendantMatchingXPath(FIND_THROWABLE_INSTANCE)) {
130                 String variableName = node.jjtGetChild(0).getImage(); // VariableDeclatorId
131                 ASTCatchStatement catchStmt = node.getFirstParentOfType(ASTCatchStatement.class);
132 
133                 while (catchStmt != null) {
134                     List<Node> violations = catchStmt
135                             .findChildNodesWithXPath("//Expression/PrimaryExpression/PrimaryPrefix/Name[@Image = '"
136                                     + variableName + "']");
137                     if (!violations.isEmpty()) {
138                         // If, after this allocation, the 'initCause' method is
139                         // called, and the ex passed
140                         // this is not a violation
141                         if (!useInitCause(violations.get(0), catchStmt)) {
142                             addViolation(data, node);
143                         }
144                     }
145 
146                     // check ASTCatchStatement higher up
147                     catchStmt = catchStmt.getFirstParentOfType(ASTCatchStatement.class);
148                 }
149             }
150             return super.visit(node, data);
151         } catch (JaxenException e) {
152             // XPath is valid, this should never happens...
153             throw new IllegalStateException(e);
154         }
155     }
156 
157     private boolean useInitCause(Node node, ASTCatchStatement catchStmt) {
158         // In case of NPE...
159         if (node != null && node.getImage() != null) {
160             return catchStmt
161                     .hasDescendantMatchingXPath("./Block/BlockStatement/Statement/StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image = '"
162                             + node.getImage() + ".initCause']");
163         }
164         return false;
165     }
166 
167     /**
168      * Checks whether the given target is in the argument list. If this is the
169      * case, then the target (root exception) is used as the cause.
170      * 
171      * @param target
172      * @param baseNode
173      */
174     private boolean checkForTargetUsage(String target, Node baseNode) {
175         boolean match = false;
176         if (target != null && baseNode != null) {
177             List<ASTName> nameNodes = baseNode.findDescendantsOfType(ASTName.class);
178             for (ASTName nameNode : nameNodes) {
179                 if (target.equals(nameNode.getImage())) {
180                     match = true;
181                     break;
182                 }
183             }
184         }
185         return match;
186     }
187 
188     private void ck(Object data, String target, ASTThrowStatement throwStatement, Node baseNode) {
189         if (!checkForTargetUsage(target, baseNode)) {
190             RuleContext ctx = (RuleContext) data;
191             addViolation(ctx, throwStatement);
192         }
193     }
194 }