Try/catch

This commit is contained in:
Clément Fournier
2020-06-20 10:39:32 +02:00
parent 9f54f2715e
commit 972610fe89
2 changed files with 169 additions and 15 deletions

View File

@ -21,9 +21,11 @@ import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement;
import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement;
import net.sourceforge.pmd.lang.java.ast.ASTForInit;
import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
import net.sourceforge.pmd.lang.java.ast.ASTForUpdate;
@ -38,6 +40,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTPreIncrementExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTResourceSpecification;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
@ -46,6 +49,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule;
import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
@ -125,6 +129,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
// continue jumps to the condition check, while break jumps to after the loop
private final TargetStack continueTargets = new TargetStack();
private final Deque<AlgoState> normalFinallies = new ArrayDeque<>();
// following deals with control flow
@Override
@ -209,6 +215,37 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
return thenState.join(elseState);
}
@Override
public Object visit(ASTTryStatement node, Object data) {
final AlgoState before = (AlgoState) data;
ASTFinallyStatement finallyClause = node.getFinallyClause();
if (finallyClause != null) {
before.myFinally = before.forkEmpty();
}
ASTResourceSpecification resources = node.getFirstChildOfType(ASTResourceSpecification.class);
AlgoState bodyState = acceptOpt(resources, before.fork());
bodyState = acceptOpt(node.getBody(), bodyState);
AlgoState exceptionalState = null;
for (ASTCatchStatement catchClause : node.getCatchClauses()) {
AlgoState current = acceptOpt(catchClause, before.fork());
exceptionalState = current.join(exceptionalState);
}
AlgoState finalState;
if (finallyClause != null) {
finalState = before.myFinally.join(bodyState).join(exceptionalState);
finalState = acceptOpt(finallyClause, finalState);
before.myFinally = null;
} else {
finalState = bodyState.join(exceptionalState);
}
return finalState;
}
@Override
public Object visit(ASTWhileStatement node, Object data) {
return handleLoop(node, (AlgoState) data, null, node.getCondition(), null, node.getBody(), true);
@ -339,13 +376,13 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
@Override
public Object visit(ASTThrowStatement node, Object data) {
super.visit(node, data);
return ((AlgoState) data).abruptCompletion();
return ((AlgoState) data).abruptCompletion(null);
}
@Override
public Object visit(ASTReturnStatement node, Object data) {
super.visit(node, data);
return ((AlgoState) data).abruptCompletion();
return ((AlgoState) data).abruptCompletion(null);
}
// following deals with assignment
@ -493,6 +530,8 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
private static class AlgoState {
final AlgoState parent;
// Those 3 are shared between all the forked states
final Set<AssignmentEntry> allAssignments;
final Set<AssignmentEntry> usedAssignments;
@ -501,18 +540,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
// Map of var -> reaching(var)
final Map<VariableNameDeclaration, Set<AssignmentEntry>> reachingDefs;
AlgoState myFinally = null;
private AlgoState() {
this(new HashSet<AssignmentEntry>(),
this(null,
new HashSet<AssignmentEntry>(),
new HashSet<AssignmentEntry>(),
new HashMap<AssignmentEntry, Set<AssignmentEntry>>(),
new HashMap<VariableNameDeclaration, Set<AssignmentEntry>>());
}
private AlgoState(Set<AssignmentEntry> allAssignments,
private AlgoState(AlgoState parent,
Set<AssignmentEntry> allAssignments,
Set<AssignmentEntry> usedAssignments,
Map<AssignmentEntry, Set<AssignmentEntry>> killRecord,
Map<VariableNameDeclaration, Set<AssignmentEntry>> reachingDefs) {
this.parent = parent;
this.allAssignments = allAssignments;
this.usedAssignments = usedAssignments;
this.killRecord = killRecord;
@ -550,14 +594,23 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
}
AlgoState fork() {
return new AlgoState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<>(this.reachingDefs));
return new AlgoState(this, this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<>(this.reachingDefs));
}
AlgoState forkEmpty() {
return new AlgoState(this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<VariableNameDeclaration, Set<AssignmentEntry>>());
return new AlgoState(this, this.allAssignments, this.usedAssignments, this.killRecord, new HashMap<VariableNameDeclaration, Set<AssignmentEntry>>());
}
AlgoState abruptCompletion() {
AlgoState abruptCompletion(AlgoState target) {
// if target == null then this will unwind all the parents
AlgoState parent = this;
while (parent != target && parent != null) {
if (parent.myFinally != null) {
parent.myFinally.join(this);
}
parent = parent.parent;
}
this.reachingDefs.clear();
return this;
}
@ -565,7 +618,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
AlgoState join(AlgoState sub) {
// Merge reaching des of the other scope into this
if (sub == this || sub.reachingDefs.isEmpty()) {
if (sub == this || sub == null || sub.reachingDefs.isEmpty()) {
return this;
}
@ -625,16 +678,17 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
AlgoState doBreak(AlgoState data,/* nullable */ String label) {
// basically, reaching defs at the point of the break
// also reach after the break (wherever it lands)
AlgoState target;
if (label == null) {
unnamedTargets.push(unnamedTargets.getFirst().join(data));
target = unnamedTargets.getFirst();
} else {
AlgoState target = namedTargets.get(label);
if (target != null) {
// otherwise CT error
target.join(data);
}
target = namedTargets.get(label);
}
return data.abruptCompletion();
if (target != null) { // otherwise CT error
target.join(data);
}
return data.abruptCompletion(target);
}
}

View File

@ -1073,4 +1073,104 @@ public class Foo {
} ]]></code>
</test-code>
<test-code>
<description>Try/catch</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<expected-messages>
<message>The value assigned to variable 'a' is never used (overwritten on lines 6 and 8)</message>
</expected-messages>
<code><![CDATA[
public class Foo {
public int foo() {
int a = 0;
try (Reader r = new StringReader("")) {
a = r.read();
} catch (IOException e) {
a = -1;
}
return a;
}
} ]]></code>
</test-code>
<test-code>
<description>Try/catch finally</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<expected-messages>
<message>The value assigned to variable 'a' is never used (overwritten on lines 6 and 8)</message>
</expected-messages>
<code><![CDATA[
public class Foo {
public int foo() {
int a = 0;
try (Reader r = new StringReader("")) {
a = r.read(); // used in finally
} catch (IOException e) {
a = -1; // used in finally
} finally {
print(a);
}
return 0;
}
} ]]></code>
</test-code>
<test-code>
<description>Try/catch finally 3</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<expected-messages>
<message>The value assigned to variable 'a' is never used (overwritten on lines 6 and 8)</message>
</expected-messages>
<code><![CDATA[
public class Foo {
public int foo() {
int a = 0;
try (Reader r = new StringReader("")) {
a = r.read(); // used in finally
} catch (IOException e) {
a = -1; // used in finally
} finally {
// don't use a
}
return a;
}
} ]]></code>
</test-code>
<test-code>
<description>Try/catch finally in loop</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
class Foo {
public int foo() {
int a = 0;
while (a > 10) {
try (Reader r = new StringReader("")) {
r.read();
} catch (IOException e) {
a = -1; // used in finally even if break
break;
} finally {
a++;
}
}
return a;
}
}
]]></code>
</test-code>
</test-data>