Create pending edge from continue when loop has at least one iteration (PY-29767)

There is no edge between loop statement and next after loop instruction
when loop has at least one iteration
so `continue` is marked as one more last instruction in the loop
This commit is contained in:
Semyon Proshev
2018-10-26 19:26:04 +03:00
parent d705ea92ca
commit 63b4650c38
6 changed files with 63 additions and 3 deletions

View File

@@ -474,7 +474,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
boolean isStaticallyTrue = false;
if (condition != null) {
condition.accept(this);
isStaticallyTrue = PyEvaluator.evaluateAsBooleanNoResolve(condition, false);
isStaticallyTrue = loopHasAtLeastOneIteration(node);
}
final Instruction head = myBuilder.prevInstruction;
final PyElsePart elsePart = node.getElsePart();
@@ -509,7 +509,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
}
final Instruction head = myBuilder.prevInstruction;
final PyElsePart elsePart = node.getElsePart();
if (elsePart == null && !PyEvaluator.evaluateAsBooleanNoResolve(source, false)) {
if (elsePart == null && !loopHasAtLeastOneIteration(node)) {
myBuilder.addPendingEdge(node, myBuilder.prevInstruction);
}
final PyStatementList list = forPart.getStatementList();
@@ -544,6 +544,16 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
myBuilder.flowAbrupted();
}
private static boolean loopHasAtLeastOneIteration(@NotNull PyLoopStatement loopStatement) {
final PyExpression expression = loopStatement instanceof PyForStatement
? ((PyForStatement)loopStatement).getForPart().getSource()
: loopStatement instanceof PyWhileStatement
? ((PyWhileStatement)loopStatement).getWhilePart().getCondition()
: null;
return PyEvaluator.evaluateAsBooleanNoResolve(expression, false);
}
@Override
public void visitPyBreakStatement(final PyBreakStatement node) {
myBuilder.startNode(node);
@@ -569,6 +579,15 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
else {
myBuilder.addPendingEdge(null, null);
}
// There is no edge between loop statement and next after loop instruction
// when loop has at least one iteration
// so `continue` is marked as one more last instruction in the loop
// see visitPyWhileStatement
// see visitPyForStatement
if (loopHasAtLeastOneIteration(loop)) {
myBuilder.addPendingEdge(loop, myBuilder.prevInstruction);
}
}
myBuilder.flowAbrupted();
}

View File

@@ -0,0 +1,7 @@
import sys
for s in "abc":
if len(s) == 1:
continue
sys.exit(0)
raise Exception("the end")

View File

@@ -0,0 +1,16 @@
0(1) element: null
1(2) element: PyImportStatement
2(3) WRITE ACCESS: sys
3(4) element: PyForStatement
4(5) element: PyTargetExpression: s
5(6) WRITE ACCESS: s
6(7) element: PyIfStatement
7(8) READ ACCESS: len
8(9,11) READ ACCESS: s
9(10) element: PyStatementList. Condition: len(s) == 1:true
10(3,13) element: PyContinueStatement
11(12) element: PyExpressionStatement
12(15) READ ACCESS: sys
13(14) element: PyRaiseStatement
14(15) READ ACCESS: Exception
15() element: null

View File

@@ -23,7 +23,7 @@
22(23,37) element: PyIfPartElif. Condition: x == 0:false
23(24,26,37) READ ACCESS: x
24(25) element: PyStatementList. Condition: x == 1:true
25(7,37) element: PyContinueStatement
25(7,37,40) element: PyContinueStatement
26(27,37) element: PyIfPartElif. Condition: x == 1:false
27(28,31,37) READ ACCESS: x
28(29) element: PyStatementList. Condition: x == 2:true

View File

@@ -284,6 +284,11 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase {
doTest();
}
// PY-29767
public void testContinueInPositiveIteration() {
doTest();
}
private void doTestFirstStatement() {
final String testName = getTestName(false).toLowerCase();
configureByFile(testName + ".py");

View File

@@ -127,6 +127,19 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase {
);
}
// PY-29767
public void testContinueInPositiveIterationWithExitPoint() {
doTestByText(
"import sys\n" +
"\n" +
"for s in \"abc\":\n" +
" if len(s) == 1:\n" +
" continue\n" +
" sys.exit(0)\n" +
"raise Exception(\"the end\")"
);
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {