Optimizations and false positive fixes in PreserveStackTrace

git-svn-id: https://pmd.svn.sourceforge.net/svnroot/pmd/branches/pmd/4.2.x@6232 51baf565-9d33-0410-a72c-fc3788e3496d
This commit is contained in:
Xavier Le Vourch 2008-06-19 03:50:50 +00:00
parent d951795634
commit 9ce5a5be43
4 changed files with 125 additions and 37 deletions

View File

@ -6,6 +6,7 @@ Upgrading UselessOperationOnImmutable to detect more use cases, especially on St
Fixed bug 1988829 - Violation reported without source file name (actually a fix to ConsecutiveLiteralAppends)
Fixed bug 1989814 - false +: ConsecutiveLiteralAppends
Fixed bug 1977230 - false positive: UselessOverridingMethod
Optimizations and false positive fixes in PreserveStackTrace
New rule:
Basic ruleset: EmptyInitializer

View File

@ -302,4 +302,87 @@ public class Foo {
]]></code>
</test-code>
<test-code>
<description><![CDATA[
18, side effects on rules
]]></description>
<expected-problems>1</expected-problems>
<code><![CDATA[
public class Foo {
public void bar1() {
try {
;
} catch (Exception notUsed) {
RuntimeException ex = new RuntimeException();
throw ex;
}
}
public void bar2() {
try {
;
} catch (Exception e) {
IllegalStateException ex = new IllegalStateException();
ex.initCause(e);
throw ex;
}
}
}
]]></code>
</test-code>
<test-code>
<description><![CDATA[
19, False positive
]]></description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
private static boolean isInstanceOf(final MBeanServer mbs,
final ObjectName name,
final String className) {
PrivilegedExceptionAction<Boolean> act =
new PrivilegedExceptionAction<Boolean>() {
public Boolean run() throws InstanceNotFoundException {
return mbs.isInstanceOf(name, className);
}
};
try {
return AccessController.doPrivileged(act);
} catch (Exception e) {
logger.fine("isInstanceOf", "failed: " + e);
logger.debug("isInstanceOf", e);
return false;
}
}
}
]]></code>
</test-code>
<test-code>
<description><![CDATA[
20, False positive
]]></description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
private CodeException[] getCodeExceptions() {
int size = exception_vec.size();
CodeException[] c_exc = new CodeException[size];
try {
for(int i=0; i < size; i++) {
CodeExceptionGen c = (CodeExceptionGen)exception_vec.get(i);
c_exc[i] = c.getCodeException(cp);
}
} catch(ArrayIndexOutOfBoundsException e) {}
return c_exc;
}
}
]]></code>
</test-code>
</test-data>

View File

@ -1329,12 +1329,12 @@ public class Foo {
<rule name="PreserveStackTrace"
since="3.7"
message="Caught exception is rethrown, original stack trace may be lost"
message="New exception is thrown in catch block, original stack trace may be lost"
class="net.sourceforge.pmd.rules.design.PreserveStackTrace"
externalInfoUrl="http://pmd.sourceforge.net/rules/design.html#PreserveStackTrace">
<description>
Throwing a new exception from a catch block without passing the original exception into the
new Exception will cause the true stack trace to be lost, and can make it difficult to
new exception will cause the true stack trace to be lost, and can make it difficult to
debug effectively.
</description>
<priority>3</priority>

View File

@ -17,6 +17,7 @@ import net.sourceforge.pmd.ast.ASTName;
import net.sourceforge.pmd.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.ast.ASTThrowStatement;
import net.sourceforge.pmd.ast.ASTVariableDeclarator;
import net.sourceforge.pmd.ast.Node;
import net.sourceforge.pmd.ast.SimpleNode;
import net.sourceforge.pmd.symboltable.NameOccurrence;
@ -34,23 +35,18 @@ public class PreserveStackTrace extends AbstractJavaRule {
private List<ASTName> nameNodes = new ArrayList<ASTName>();
// FUTURE: This is dectection is name based, it should probably used Type Resolution, to become type "based"
private static final String FIND_THROWABLE_INSTANCE = "//VariableDeclaratorId[" +
"(../descendant::VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType" +
"[" +
"contains(@Image,'Exception')" + // Assuming the Exception class does contains 'Exception' in its name
"and" +
"(not (../Arguments/ArgumentList))" +
"]" +
")]";
// FUTURE: This detection is name based, it should probably use Type Resolution, to become type "based"
// it assumes the exception class contains 'Exception' in its name
private static final String FIND_THROWABLE_INSTANCE =
"./VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression" +
"[ClassOrInterfaceType[contains(@Image,'Exception')] and Arguments[count(*)=0]]";
private static final String ILLEGAL_STATE_EXCEPTION = "IllegalStateException";
private static final String FILL_IN_STACKTRACE = ".fillInStackTrace";
@Override
public Object visit(ASTCatchStatement catchStmt, Object data) {
String target = ((SimpleNode)catchStmt.jjtGetChild(0).jjtGetChild(1)).getImage();
// Gather every variable used to store exception instance created without any argument, inside the catch
gatherVariableWithExceptionRef(catchStmt,data);
// Inspect all the throw stmt inside the catch stmt
List<ASTThrowStatement> lstThrowStatements = catchStmt.findChildrenOfType(ASTThrowStatement.class);
for (ASTThrowStatement throwStatement : lstThrowStatements) {
@ -72,13 +68,13 @@ public class PreserveStackTrace extends AbstractJavaRule {
ck(data, target, throwStatement, args);
}
else {
Node child = throwStatement.jjtGetChild(0);
SimpleNode child = (SimpleNode)throwStatement.jjtGetChild(0);
while (child != null && child.jjtGetNumChildren() > 0
&& !child.getClass().equals(ASTName.class)) {
child = child.jjtGetChild(0);
child = (SimpleNode)child.jjtGetChild(0);
}
if (child != null){
if( child.getClass().equals(ASTName.class) && (!target.equals(((SimpleNode)child).getImage()) && !((SimpleNode)child).hasImageEqualTo(target + FILL_IN_STACKTRACE))) {
if( child.getClass().equals(ASTName.class) && !target.equals(child.getImage()) && !child.hasImageEqualTo(target + FILL_IN_STACKTRACE)) {
Map<VariableNameDeclaration, List<NameOccurrence>> vars = ((ASTName) child).getScope().getVariableDeclarations();
for (VariableNameDeclaration decl: vars.keySet()) {
args = ((SimpleNode)decl.getNode().jjtGetParent())
@ -98,33 +94,41 @@ public class PreserveStackTrace extends AbstractJavaRule {
return super.visit(catchStmt, data);
}
/*
* Search Catch stmt nodes for variable used to store unproperly created throwable or exception
*/
private void gatherVariableWithExceptionRef(ASTCatchStatement catchStmt, Object data) {
try {
List<Node> nodes = catchStmt.findChildNodesWithXPath(FIND_THROWABLE_INSTANCE);
for ( Node node : nodes ) {
List <Node> violations = catchStmt.findChildNodesWithXPath("//Expression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + ((SimpleNode)node).getImage() + "']");
if ( violations != null && violations.size() > 0 ) {
// If, after this allocation, the 'initCause' method is called, and the ex passed
// this is not a violation
if ( ! useInitCause((Node)violations.get(0),catchStmt) ) //FIXME: iterate, better than get(0) ?
super.addViolation(data,(SimpleNode) node);
}
@Override
public Object visit(ASTVariableDeclarator node, Object data) {
// Search Catch stmt nodes for variable used to store improperly created throwable or exception
try {
List<Node> nodes = node.findChildNodesWithXPath(FIND_THROWABLE_INSTANCE);
if (nodes.size() > 0) {
String variableName = ((SimpleNode)node.jjtGetChild(0)).getImage(); // VariableDeclatorId
ASTCatchStatement catchStmt = node.getFirstParentOfType(ASTCatchStatement.class);
while (catchStmt != null) {
List<SimpleNode> violations = catchStmt.findChildNodesWithXPath("//Expression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + variableName + "']");
if (violations != null && violations.size() > 0) {
// If, after this allocation, the 'initCause' method is called, and the ex passed
// this is not a violation
if (!useInitCause(violations.get(0), catchStmt)) {
addViolation(data, node);
}
} catch (JaxenException e) {
// XPath is valid, this should never happens...
e.printStackTrace();
}
// check ASTCatchStatement higher up
catchStmt = catchStmt.getFirstParentOfType(ASTCatchStatement.class);
}
}
return super.visit(node, data);
} catch (JaxenException e) {
// XPath is valid, this should never happens...
throw new IllegalStateException(e);
}
}
private boolean useInitCause(Node node, ASTCatchStatement catchStmt) throws JaxenException {
private boolean useInitCause(SimpleNode node, ASTCatchStatement catchStmt) throws JaxenException {
// In case of NPE...
if ( node != null && ((SimpleNode)node).getImage() != null )
if ( node != null && node.getImage() != null )
{
List <Node> nodes = catchStmt.findChildNodesWithXPath("descendant::StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + ((SimpleNode)node).getImage() + ".initCause']");
List <Node> nodes = catchStmt.findChildNodesWithXPath("descendant::StatementExpression/PrimaryExpression/PrimaryPrefix/Name[@Image = '" + node.getImage() + ".initCause']");
if ( nodes != null && nodes.size() > 0 )
{
return true;