Fix FP with side-effect in toplevel condition

This commit is contained in:
Clément Fournier
2020-06-22 18:00:17 +02:00
parent c837e244e9
commit 1d022d3d91
2 changed files with 56 additions and 26 deletions

View File

@ -343,7 +343,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
AlgoState thenState = before.fork(); AlgoState thenState = before.fork();
AlgoState elseState = elseBranch != null ? before.fork() : before; AlgoState elseState = elseBranch != null ? before.fork() : before;
linkConditional(before, condition, thenState, elseState); linkConditional(before, condition, thenState, elseState, true);
thenState = acceptOpt(thenBranch, thenState); thenState = acceptOpt(thenBranch, thenState);
elseState = acceptOpt(elseBranch, elseState); elseState = acceptOpt(elseBranch, elseState);
@ -351,17 +351,22 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
return elseState.absorb(thenState); return elseState.absorb(thenState);
} }
private AlgoState linkConditional(AlgoState prev, JavaNode condition, AlgoState thenState, AlgoState elseState) { private AlgoState linkConditional(AlgoState before, JavaNode condition, AlgoState thenState, AlgoState elseState, boolean isTopLevel) {
if (condition instanceof ASTConditionalOrExpression) { if (condition instanceof ASTConditionalOrExpression) {
return visitShortcutOrExpr(condition, prev, thenState, elseState); return visitShortcutOrExpr(condition, before, thenState, elseState);
} else if (condition instanceof ASTConditionalAndExpression) { } else if (condition instanceof ASTConditionalAndExpression) {
// To mimic a shortcut AND expr, swap the thenState and the elseState // To mimic a shortcut AND expr, swap the thenState and the elseState
// See explanations in method // See explanations in method
return visitShortcutOrExpr(condition, prev, elseState, thenState); return visitShortcutOrExpr(condition, before, elseState, thenState);
} else if (condition instanceof ASTExpression && condition.getNumChildren() == 1) { } else if (condition instanceof ASTExpression && condition.getNumChildren() == 1) {
return linkConditional(prev, condition.getChild(0), thenState, elseState); return linkConditional(before, condition.getChild(0), thenState, elseState, isTopLevel);
} else { } else {
return acceptOpt(condition, prev); AlgoState state = acceptOpt(condition, before);
if (isTopLevel) {
thenState.absorb(state);
elseState.absorb(state);
}
return state;
} }
// TODO parenthesized expression // TODO parenthesized expression
} }
@ -391,7 +396,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
AlgoState cur = before; AlgoState cur = before;
do { do {
JavaNode cond = iterator.next(); JavaNode cond = iterator.next();
cur = linkConditional(cur, cond, thenState, elseState); cur = linkConditional(cur, cond, thenState, elseState, false);
thenState.absorb(cur); thenState.absorb(cur);
} while (iterator.hasNext()); } while (iterator.hasNext());
@ -674,7 +679,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
JavaNode rhs = node.getChild(2); JavaNode rhs = node.getChild(2);
result = acceptOpt(rhs, result); result = acceptOpt(rhs, result);
VariableNameDeclaration lhsVar = getLhsVar(node.getChild(0), true); VariableNameDeclaration lhsVar = getVarFromExpression(node.getChild(0), true);
if (lhsVar != null) { if (lhsVar != null) {
// in that case lhs is a normal variable (array access not supported) // in that case lhs is a normal variable (array access not supported)
@ -709,7 +714,7 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
} }
private AlgoState checkIncOrDecrement(JavaNode unary, AlgoState data) { private AlgoState checkIncOrDecrement(JavaNode unary, AlgoState data) {
VariableNameDeclaration var = getLhsVar(unary.getChild(0), true); VariableNameDeclaration var = getVarFromExpression(unary.getChild(0), true);
if (var != null) { if (var != null) {
data.use(var); data.use(var);
data.assign(var, unary); data.assign(var, unary);
@ -721,19 +726,19 @@ public class UnusedAssignmentRule extends AbstractJavaRule {
@Override @Override
public Object visit(ASTPrimaryExpression node, Object data) { public Object visit(ASTPrimaryExpression node, Object data) {
super.visit(node, data); // visit subexpressions AlgoState state = (AlgoState) visit((JavaNode) node, data); // visit subexpressions
VariableNameDeclaration var = getLhsVar(node, false); VariableNameDeclaration var = getVarFromExpression(node, false);
if (var != null) { if (var != null) {
((AlgoState) data).use(var); state.use(var);
} }
return data; return state;
} }
/** /**
* Get the variable accessed from a primary. * Get the variable accessed from a primary.
*/ */
private VariableNameDeclaration getLhsVar(JavaNode primary, boolean inLhs) { private VariableNameDeclaration getVarFromExpression(JavaNode primary, boolean inLhs) {
if (primary instanceof ASTPrimaryExpression) { if (primary instanceof ASTPrimaryExpression) {
ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0); ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.getChild(0);

View File

@ -2124,25 +2124,50 @@ class Foo {
<message>The initializer for variable 'i' is never used (overwritten on line 7)</message> <message>The initializer for variable 'i' is never used (overwritten on line 7)</message>
</expected-messages> </expected-messages>
<code><![CDATA[ <code><![CDATA[
class Foo { class Foo {
void main(int[] bufline, int start, int bufsize) { void main(int[] bufline, int start, int bufsize) {
int i = 0, j, k = 0; int i = 0, j, k = 0;
if ( (i = 2) < (j = i) if ( (i = 2) < (j = i)
&& (j = k) == i ) { && (j = k) == i ) {
// reaching: i = 2, j = k (not j = i) // reaching: i = 2, j = k (not j = i)
} else { } else {
// reaching: i = 2, j = k, j = i // reaching: i = 2, j = k, j = i
log(j); log(j);
} }
} }
} }
]]></code> ]]></code>
</test-code> </test-code>
<test-code>
<description>FP with argument</description>
<expected-problems>1</expected-problems>
<expected-messages>
<message>The initializer for variable 'i' is never used (overwritten on line 7)</message>
</expected-messages>
<code><![CDATA[
class Foo {
static String replaceBackslash(String str) {
int i = 0, len = str.length();
char c;
StringBuffer b = new StringBuffer();
for (i = 0; i < len; i++)
if ((c = str.charAt(i)) == '\\')
b.append("\\\\");
else
b.append(c);
return b.toString();
}
}
]]></code>
</test-code>
</test-data> </test-data>