Continue statement

This commit is contained in:
Clément Fournier committed 2020-06-27 18:21:38 +02:00
1 parent e771242597
commit 75b5fbf62b
2 files changed
+174 -63

No files matched your search

@@ -16,6 +16,7 @@ import java.util.Set;
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
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.ASTForInit;
@@ -98,6 +99,9 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
private static class LivenessVisitor extends JavaParserVisitorAdapter {
private final Deque<ScopeData> unnamedBreakTargets = new ArrayDeque<>();
// continue jumps to the condition check, while break jumps to after the loop
private final Deque<ScopeData> unnamedContinueTargets = new ArrayDeque<>();
private final Map<String, ScopeData> namedBreakTargets = new HashMap<>();
// following deals with control flow
@@ -136,7 +140,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
} else if (child instanceof ASTSwitchLabeledRule) {
// 'current' stays == 'before' so that the final join does nothing
current = acceptOpt(child.getChild(1), before.fork());
current = doBreak(current); // process this as if it was followed by a break
current = doBreak(current, unnamedBreakTargets); // process this as if it was followed by a break
} else {
// statement in a regular fallthrough switch block
current = acceptOpt(child, current);
@@ -162,36 +166,12 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
@Override
public Object visit(ASTWhileStatement node, Object data) {
// perform a few "iterations", to make sure that assignments in
// the body can affect themselves in the next iteration, and
// that they affect the condition
ScopeData before = acceptOpt(node.getCondition(), (ScopeData) data);
unnamedBreakTargets.push(before);
ScopeData iter = acceptOpt(node.getBody(), before.fork());
iter = acceptOpt(node.getCondition(), iter);
iter = acceptOpt(node.getBody(), iter);
unnamedBreakTargets.pop();
return before.join(iter);
return handleLoop((ScopeData) data, null, node.getCondition(), null, node.getBody(), true);
}
@Override
public Object visit(ASTDoStatement node, Object data) {
// same as while but don't check the condition first
ScopeData before = (ScopeData) data;
unnamedBreakTargets.push(before);
ScopeData iter = acceptOpt(node.getBody(), before.fork());
iter = acceptOpt(node.getCondition(), iter);
iter = acceptOpt(node.getBody(), iter);
unnamedBreakTargets.pop();
return before.join(iter);
return handleLoop((ScopeData) data, null, node.getCondition(), null, node.getBody(), false);
}
@Override
@@ -199,57 +179,98 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
ASTStatement body = node.getBody();
if (node.isForeach()) {
// the iterable expression
ScopeData before = (ScopeData) node.getChild(1).jjtAccept(this, data);
unnamedBreakTargets.push(before);
ScopeData iter = acceptOpt(body, before.fork());
iter = acceptOpt(body, iter); // the body must be able to affect itself
unnamedBreakTargets.pop();
return before.join(iter);
JavaNode init = node.getChild(1);
return handleLoop((ScopeData) data, init, null, null, body, true);
} else {
ASTForInit init = node.getFirstChildOfType(ASTForInit.class);
ASTExpression cond = node.getCondition();
ASTForUpdate update = node.getFirstChildOfType(ASTForUpdate.class);
ScopeData before = (ScopeData) data;
before = acceptOpt(init, before);
before = acceptOpt(cond, before);
unnamedBreakTargets.push(before);
ScopeData iter = acceptOpt(body, before.fork());
iter = acceptOpt(update, iter);
iter = acceptOpt(cond, iter);
iter = acceptOpt(body, iter); // the body must be able to affect itself
unnamedBreakTargets.pop();
return before.join(iter);
return handleLoop((ScopeData) data, init, cond, update, body, true);
}
}
private ScopeData handleLoop(ScopeData before,
JavaNode init,
JavaNode cond,
JavaNode update,
JavaNode body,
boolean checkFirstIter) {
// perform a few "iterations", to make sure that assignments in
// the body can affect themselves in the next iteration, and
// that they affect the condition, etc
before = acceptOpt(init, before);
if (checkFirstIter) {
before = acceptOpt(cond, before);
}
ScopeData breakTarget = before.forkEmpty();
ScopeData continueTarget = before.forkEmpty();
unnamedBreakTargets.push(breakTarget);
unnamedContinueTargets.push(continueTarget);
ScopeData iter = acceptOpt(body, before.fork());
// make the body live in the other parts of the loop,
// including itself
iter = acceptOpt(update, iter);
iter = acceptOpt(cond, iter);
iter = acceptOpt(body, iter);
breakTarget = unnamedBreakTargets.pop();
continueTarget = unnamedContinueTargets.pop();
if (!continueTarget.liveAssignments.isEmpty()) {
// make assignments that are only live before a continue
// live inside the other parts of the loop
continueTarget = acceptOpt(cond, continueTarget);
continueTarget = acceptOpt(body, continueTarget);
continueTarget = acceptOpt(update, continueTarget);
}
ScopeData result = breakTarget.join(continueTarget).join(iter);
if (checkFirstIter) {
result = result.join(before);
}
return result;
}
private ScopeData acceptOpt(JavaNode node, ScopeData before) {
return node == null ? before : (ScopeData) node.jjtAccept(this, before);
}
@Override
public Object visit(ASTBreakStatement node, Object data) {
public Object visit(ASTContinueStatement node, Object data) {
if (node.getImage() == null) {
return doBreak((ScopeData) data);
return doBreak((ScopeData) data, unnamedContinueTargets);
} else {
// TODO
return ((ScopeData) data).abruptCompletion();
}
}
public ScopeData doBreak(ScopeData data) {
unnamedBreakTargets.getFirst().join(data);
@Override
public Object visit(ASTBreakStatement node, Object data) {
if (node.getImage() == null) {
return doBreak((ScopeData) data, unnamedBreakTargets);
} else {
// TODO
return ((ScopeData) data).abruptCompletion();
}
}
public ScopeData doBreak(ScopeData data, Deque<ScopeData> targets) {
// basically, assignments that are live at the point of the break
// are also live after the break (wherever it lands)
targets.push(targets.getFirst().join(data));
return data.abruptCompletion();
}
// both of those exit the scope of the method/ctor, so their assignments go dead
@Override
public Object visit(ASTThrowStatement node, Object data) {
super.visit(node, data);
@@ -420,11 +441,16 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
return new ScopeData(this.allAssignments, this.usedAssignments, new HashMap<>(this.liveAssignments));
}
ScopeData forkEmpty() {
return new ScopeData(this.allAssignments, this.usedAssignments, new HashMap<>());
}
ScopeData abruptCompletion() {
this.liveAssignments.clear();
return this;
}
ScopeData join(ScopeData sub) {
// Merge live assignments of forked scopes
if (sub == this || sub.liveAssignments.isEmpty()) {
@@ -324,13 +324,9 @@ class Test{
]]></code>
</test-code>
<test-code regressionTest="false">
<description>#1905 [java] DataflowAnomalyAnalysis Rule in right order : Case 3. DU-Anomaly(a)</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>5</expected-linenumbers>
<expected-messages>
<message>Found 'DU'-anomaly for variable 'a' (lines '5'-'7').</message>
</expected-messages>
<test-code>
<description>Foreach</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Test{
public static void main(String[] args){
@@ -459,6 +455,72 @@ class Test{
]]></code>
</test-code>
<test-code>
<description>While loop with continue</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Test{
public static void main(String[] args){
int a = 0;
int i = 0;
while (true) {
if (a >= 30) {
i = a + 1; // used by below
continue;
}
a = a + 3;
i = i + 1; // used by itself
}
}
}
]]></code>
</test-code>
<test-code>
<description>While loop with continue 2</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Test{
public static void main(String[] args){
int a = 0;
int i = 0;
while (a < 50) {
if (i >= 30) {
a = i + 1; // used by loop condition
continue;
}
i++; // used by itself
}
}
}
]]></code>
</test-code>
<test-code>
<description>While loop with break (control for continue test above)</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>7</expected-linenumbers>
<expected-messages>
<message>The value assigned to variable 'a' is never used</message>
</expected-messages>
<code><![CDATA[
class Test{
public static void main(String[] args){
int a = 0;
int i = 0;
while (a < 50) {
if (i >= 30) {
a = i + 1; // unused
break;
}
i++; // used by itself
}
}
}
]]></code>
</test-code>
<test-code>
<description>Do while 0</description>
<expected-problems>0</expected-problems>
@@ -525,6 +587,29 @@ class Test{
]]></code>
</test-code>
<test-code>
<description>Do while with continue</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Test{
public static void main(String[] args){
int a = 0;
int i = 0;
do {
if (a >= 20) {
i = 4; // used by condition
a *= 5;
continue;
}
a = i + 3;
i += 3;
} while (i < 30);
}
}
]]></code>
</test-code>
<test-code>
<description>Switch statement 0</description>
<expected-problems>4</expected-problems>