/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.design;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.RuleContext;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
/**
*
* @author Unknown,
* @author Romain PELISSE, belaran@gmail.com, fix for bug 1808110
*
*/
public class PreserveStackTraceRule extends AbstractJavaRule {
private static final String FILL_IN_STACKTRACE = ".fillInStackTrace";
@Override
public Object visit(ASTCatchStatement catchStmt, Object data) {
String target = catchStmt.jjtGetChild(0).findChildrenOfType(ASTVariableDeclaratorId.class).get(0).getImage();
// Inspect all the throw stmt inside the catch stmt
List<ASTThrowStatement> lstThrowStatements = catchStmt.findDescendantsOfType(ASTThrowStatement.class);
for (ASTThrowStatement throwStatement : lstThrowStatements) {
Node n = throwStatement.jjtGetChild(0).jjtGetChild(0);
if (n instanceof ASTCastExpression) {
ASTPrimaryExpression expr = (ASTPrimaryExpression) n.jjtGetChild(1);
if (expr.jjtGetNumChildren() > 1 && expr.jjtGetChild(1) instanceof ASTPrimaryPrefix) {
RuleContext ctx = (RuleContext) data;
addViolation(ctx, throwStatement);
}
continue;
}
// Retrieve all argument for the throw exception (to see if the
// original exception is preserved)
ASTArgumentList args = throwStatement.getFirstDescendantOfType(ASTArgumentList.class);
if (args != null) {
Node parent = args.jjtGetParent().jjtGetParent();
if (parent instanceof ASTAllocationExpression) {
// maybe it is used inside a anonymous class
ck(data, target, throwStatement, parent);
} else {
ck(data, target, throwStatement, args);
}
} else {
Node child = throwStatement.jjtGetChild(0);
while (child != null && child.jjtGetNumChildren() > 0 && !(child instanceof ASTName)) {
child = child.jjtGetChild(0);
}
if (child != null) {
if (child instanceof ASTName && !target.equals(child.getImage())
&& !child.hasImageEqualTo(target + FILL_IN_STACKTRACE)) {
Map<VariableNameDeclaration, List<NameOccurrence>> vars = ((ASTName) child).getScope()
.getDeclarations(VariableNameDeclaration.class);
for (Map.Entry<VariableNameDeclaration, List<NameOccurrence>> entry : vars.entrySet()) {
VariableNameDeclaration decl = entry.getKey();
List<NameOccurrence> occurrences = entry.getValue();
if (decl.getImage().equals(child.getImage())) {
if (!isInitCauseCalled(target, occurrences)) {
// Check how the variable is initialized
ASTVariableInitializer initializer = decl.getNode().jjtGetParent()
.getFirstDescendantOfType(ASTVariableInitializer.class);
if (initializer != null) {
args = initializer.getFirstDescendantOfType(ASTArgumentList.class);
if (args != null) {
// constructor with args?
ck(data, target, throwStatement, args);
} else if (!isFillInStackTraceCalled(target, initializer)) {
addViolation(data, throwStatement);
}
}
}
}
}
} else if (child instanceof ASTClassOrInterfaceType) {
addViolation(data, throwStatement);
}
}
}
}
return super.visit(catchStmt, data);
}
private boolean isFillInStackTraceCalled(final String target, final ASTVariableInitializer initializer) {
final ASTName astName = initializer.getFirstDescendantOfType(ASTName.class);
return astName != null && astName.hasImageEqualTo(target + FILL_IN_STACKTRACE);
}
private boolean isInitCauseCalled(String target, List<NameOccurrence> occurrences) {
boolean initCauseCalled = false;
for (NameOccurrence occurrence : occurrences) {
String image = null;
if (occurrence.getLocation() != null) {
image = occurrence.getLocation().getImage();
}
if (image != null && image.endsWith("initCause")) {
ASTPrimaryExpression primaryExpression = occurrence.getLocation()
.getFirstParentOfType(ASTPrimaryExpression.class);
if (primaryExpression != null) {
ASTArgumentList args2 = primaryExpression.getFirstDescendantOfType(ASTArgumentList.class);
if (checkForTargetUsage(target, args2)) {
initCauseCalled = true;
break;
}
}
}
}
return initCauseCalled;
}
/**
* Checks whether the given target is in the argument list. If this is the
* case, then the target (root exception) is used as the cause.
*
* @param target
* @param baseNode
*/
private boolean checkForTargetUsage(String target, Node baseNode) {
boolean match = false;
if (target != null && baseNode != null) {
List<ASTName> nameNodes = baseNode.findDescendantsOfType(ASTName.class);
for (ASTName nameNode : nameNodes) {
if (target.equals(nameNode.getImage())) {
match = true;
break;
}
}
}
return match;
}
private void ck(Object data, String target, ASTThrowStatement throwStatement, Node baseNode) {
if (!checkForTargetUsage(target, baseNode)) {
RuleContext ctx = (RuleContext) data;
addViolation(ctx, throwStatement);
}
}
}