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