Update CFG to honour condition values in if statements (PY-24750)

This commit is contained in:
Semyon Proshev
2018-03-13 16:08:46 +03:00
parent 63e13170ef
commit 6933c9d9e0
18 changed files with 284 additions and 32 deletions
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
@@ -325,6 +326,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
myBuilder.startNode(node);
final PyIfPart ifPart = node.getIfPart();
PyExpression condition = ifPart.getCondition();
final List<Boolean> conditionResults = new SmartList<>(PyEvaluator.evaluateAsBooleanNoResolve(condition));
PyTypeAssertionEvaluator assertionEvaluator = new PyTypeAssertionEvaluator();
if (condition != null) {
condition.accept(this);
@@ -334,7 +336,9 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
PyElement lastCondition = condition;
List<Pair<PsiElement, Instruction>> lastBranchingPoints = getPrevInstructions(condition);
lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond()));
if (ContainerUtil.getLastItem(conditionResults) != Boolean.FALSE) {
lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond()));
}
myBuilder.prevInstruction = null;
final PyStatementList thenStatements = ifPart.getStatementList();
@@ -352,11 +356,14 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
myBuilder.addPendingEdge(node, myBuilder.prevInstruction);
for (final PyIfPart part : node.getElifParts()) {
// Set the head as the false branch
lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond()));
if (!ContainerUtil.exists(conditionResults, Boolean.TRUE::equals)) {
lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond()));
}
myBuilder.prevInstruction = null;
myBuilder.startConditionalNode(part, lastCondition, false);
condition = part.getCondition();
conditionResults.add(PyEvaluator.evaluateAsBooleanNoResolve(condition));
assertionEvaluator = new PyTypeAssertionEvaluator();
if (condition != null) {
lastCondition = condition;
@@ -365,11 +372,13 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
lastBranchingPoints = getPrevInstructions(lastCondition);
}
// Set the head as the last instruction of condition
getPrevInstructions(lastCondition).forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond()));
if (ContainerUtil.getLastItem(conditionResults) != Boolean.FALSE) {
getPrevInstructions(lastCondition).forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond()));
}
myBuilder.prevInstruction = null;
myBuilder.startConditionalNode(part, lastCondition, true);
final PyStatementList statementList = part.getStatementList();
myBuilder.startConditionalNode(statementList, lastCondition, true);
InstructionBuilder.addAssertInstructions(myBuilder, assertionEvaluator);
statementList.accept(this);
myBuilder.processPending((pendingScope, instruction) -> {
@@ -391,15 +400,18 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
final PyElsePart elseBranch = node.getElsePart();
if (elseBranch != null) {
// Set the head as the false branch
lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond()));
if (!ContainerUtil.exists(conditionResults, Boolean.TRUE::equals)) {
lastBranchingPoints.forEach(pair -> myBuilder.addPendingEdge(pair.getFirst(), pair.getSecond()));
}
myBuilder.prevInstruction = null;
myBuilder.startConditionalNode(elseBranch, lastCondition, false);
final PyStatementList statementList = elseBranch.getStatementList();
myBuilder.startConditionalNode(statementList, lastCondition, false);
InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator);
elseBranch.accept(this);
statementList.accept(this);
myBuilder.addPendingEdge(node, myBuilder.prevInstruction);
}
else {
else if (ContainerUtil.getLastItem(conditionResults) != Boolean.TRUE) {
myBuilder.prevInstruction = null;
final Instruction instruction =
ContainerUtil.getFirstItem(InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator));
@@ -1,19 +1,19 @@
if 0:
if 0j:
pass
if 1:
if 1j:
pass
else:
pass
if 2:
if 2j:
pass
elif 3:
elif 3j:
pass
if 4:
if 4j:
pass
elif 5:
elif 5j:
pass
else:
1
+10 -10
View File
@@ -1,17 +1,17 @@
0(1) element: null
1(2,3) element: PyIfStatement
2(3) element: PyStatementList. Condition: 0:true
2(3) element: PyStatementList. Condition: 0j:true
3(4,5) element: PyIfStatement
4(6) element: PyStatementList. Condition: 1:true
5(6) element: PyElsePart. Condition: 1:false
4(6) element: PyStatementList. Condition: 1j:true
5(6) element: PyStatementList. Condition: 1j:false
6(7,8) element: PyIfStatement
7(10) element: PyStatementList. Condition: 2:true
8(9,10) element: PyIfPartElif. Condition: 2:false
9(10) element: PyIfPartElif. Condition: 3:true
7(10) element: PyStatementList. Condition: 2j:true
8(9,10) element: PyIfPartElif. Condition: 2j:false
9(10) element: PyStatementList. Condition: 3j:true
10(11,12) element: PyIfStatement
11(16) element: PyStatementList. Condition: 4:true
12(13,14) element: PyIfPartElif. Condition: 4:false
13(16) element: PyIfPartElif. Condition: 5:true
14(15) element: PyElsePart. Condition: 5:false
11(16) element: PyStatementList. Condition: 4j:true
12(13,14) element: PyIfPartElif. Condition: 4j:false
13(16) element: PyStatementList. Condition: 5j:true
14(15) element: PyStatementList. Condition: 5j:false
15(16) element: PyExpressionStatement
16() element: null
@@ -0,0 +1,11 @@
if c:
pass
elif False:
a = 1
if d:
pass
elif False:
b = 1
else:
pass
@@ -0,0 +1,23 @@
0(1) element: null
1(2) element: PyIfStatement
2(3,5) READ ACCESS: c
3(4) element: PyStatementList. Condition: c:true
4(11) ASSERTTYPE ACCESS: c
5(6) element: PyIfPartElif. Condition: c:false
6(11) READ ACCESS: False
7(8) element: PyStatementList. Condition: False:true
8(9) ASSERTTYPE ACCESS: False
9(10) element: PyAssignmentStatement
10(11) WRITE ACCESS: a
11(12) element: PyIfStatement
12(13,15) READ ACCESS: d
13(14) element: PyStatementList. Condition: d:true
14(22) ASSERTTYPE ACCESS: d
15(16) element: PyIfPartElif. Condition: d:false
16(21) READ ACCESS: False
17(18) element: PyStatementList. Condition: False:true
18(19) ASSERTTYPE ACCESS: False
19(20) element: PyAssignmentStatement
20(22) WRITE ACCESS: b
21(22) element: PyStatementList. Condition: False:false
22() element: null
@@ -0,0 +1,11 @@
if c:
pass
elif True:
pass
if d:
pass
elif True:
pass
else:
e = 1
@@ -0,0 +1,21 @@
0(1) element: null
1(2) element: PyIfStatement
2(3,5) READ ACCESS: c
3(4) element: PyStatementList. Condition: c:true
4(9) ASSERTTYPE ACCESS: c
5(6) element: PyIfPartElif. Condition: c:false
6(7) READ ACCESS: True
7(8) element: PyStatementList. Condition: True:true
8(9) ASSERTTYPE ACCESS: True
9(10) element: PyIfStatement
10(11,13) READ ACCESS: d
11(12) element: PyStatementList. Condition: d:true
12(20) ASSERTTYPE ACCESS: d
13(14) element: PyIfPartElif. Condition: d:false
14(15) READ ACCESS: True
15(16) element: PyStatementList. Condition: True:true
16(20) ASSERTTYPE ACCESS: True
17(18) element: PyStatementList. Condition: True:false
18(19) element: PyAssignmentStatement
19(20) WRITE ACCESS: e
20() element: null
@@ -11,7 +11,7 @@
10(11) READ ACCESS: KeyError
11(12) element: PyRaiseStatement
12(16) READ ACCESS: Error
13(14) element: PyElsePart. Condition: markup:false
13(14) element: PyStatementList. Condition: markup:false
14(15) element: PyReturnStatement
15(16) READ ACCESS: body
16() element: null
@@ -0,0 +1,14 @@
if False:
a = 1
if False:
b = 1
else:
pass
if False:
c = 1
elif d:
pass
else:
pass
@@ -0,0 +1,26 @@
0(1) element: null
1(2) element: PyIfStatement
2(7) READ ACCESS: False
3(4) element: PyStatementList. Condition: False:true
4(5) ASSERTTYPE ACCESS: False
5(6) element: PyAssignmentStatement
6(7) WRITE ACCESS: a
7(8) element: PyIfStatement
8(13) READ ACCESS: False
9(10) element: PyStatementList. Condition: False:true
10(11) ASSERTTYPE ACCESS: False
11(12) element: PyAssignmentStatement
12(14) WRITE ACCESS: b
13(14) element: PyStatementList. Condition: False:false
14(15) element: PyIfStatement
15(20) READ ACCESS: False
16(17) element: PyStatementList. Condition: False:true
17(18) ASSERTTYPE ACCESS: False
18(19) element: PyAssignmentStatement
19(25) WRITE ACCESS: c
20(21) element: PyIfPartElif. Condition: False:false
21(22,24) READ ACCESS: d
22(23) element: PyStatementList. Condition: d:true
23(25) ASSERTTYPE ACCESS: d
24(25) element: PyStatementList. Condition: d:false
25() element: null
@@ -0,0 +1,14 @@
if True:
pass
if True:
pass
else:
b = 1
if True:
pass
elif c:
d = 1
else:
e = 1
@@ -0,0 +1,26 @@
0(1) element: null
1(2) element: PyIfStatement
2(3) READ ACCESS: True
3(4) element: PyStatementList. Condition: True:true
4(5) ASSERTTYPE ACCESS: True
5(6) element: PyIfStatement
6(7) READ ACCESS: True
7(8) element: PyStatementList. Condition: True:true
8(12) ASSERTTYPE ACCESS: True
9(10) element: PyStatementList. Condition: True:false
10(11) element: PyAssignmentStatement
11(12) WRITE ACCESS: b
12(13) element: PyIfStatement
13(14) READ ACCESS: True
14(15) element: PyStatementList. Condition: True:true
15(25) ASSERTTYPE ACCESS: True
16(17) element: PyIfPartElif. Condition: True:false
17(18) READ ACCESS: c
18(19) element: PyStatementList. Condition: c:true
19(20) ASSERTTYPE ACCESS: c
20(21) element: PyAssignmentStatement
21(25) WRITE ACCESS: d
22(23) element: PyStatementList. Condition: c:false
23(24) element: PyAssignmentStatement
24(25) WRITE ACCESS: e
25() element: null
@@ -10,7 +10,7 @@
9(10) element: PyAssignmentStatement
10(11) READ ACCESS: s
11(17) WRITE ACCESS: s2
12(13) element: PyElsePart. Condition: (isinstance(s, basestring)):false
12(13) element: PyStatementList. Condition: (isinstance(s, basestring)):false
13(14) ASSERTTYPE ACCESS: s
14(15) element: PyAssignmentStatement
15(16) READ ACCESS: s
@@ -10,7 +10,7 @@
9(10) element: PyIfPartElif. Condition: a == b:false
10(11) READ ACCESS: aa
11(12,22) READ ACCESS: bb
12(13) element: PyIfPartElif. Condition: aa == bb:true
12(13) element: PyStatementList. Condition: aa == bb:true
13(14) element: PyAssignmentStatement
14(15) READ ACCESS: same_changet_expression
15(16) WRITE ACCESS: bbb
@@ -20,7 +20,7 @@
19(20) ASSERTTYPE ACCESS: bbb
20(21) element: PyAssignmentStatement
21(25) WRITE ACCESS: var
22(23) element: PyElsePart. Condition: aa == bb:false
22(23) element: PyStatementList. Condition: aa == bb:false
23(24) element: PyAssignmentStatement
24(25) WRITE ACCESS: var
25(26) element: PyReturnStatement
@@ -22,16 +22,16 @@
21(37,40) element: PyBreakStatement
22(23,37) element: PyIfPartElif. Condition: x == 0:false
23(24,26,37) READ ACCESS: x
24(25,37) element: PyIfPartElif. Condition: x == 1:true
24(25) element: PyStatementList. Condition: x == 1:true
25(7,37) element: PyContinueStatement
26(27,37) element: PyIfPartElif. Condition: x == 1:false
27(28,31,37) READ ACCESS: x
28(29,37) element: PyIfPartElif. Condition: x == 2:true
28(29) element: PyStatementList. Condition: x == 2:true
29(30,37) element: PyRaiseStatement
30(37) READ ACCESS: Exception
31(32,37) element: PyIfPartElif. Condition: x == 2:false
32(33,35,37) READ ACCESS: x
33(34,37) element: PyIfPartElif. Condition: x == 3:true
33(34) element: PyStatementList. Condition: x == 3:true
34(37) element: PyReturnStatement
35(36,37) element: PyAssignmentStatement
36(37,40) WRITE ACCESS: e
@@ -4,7 +4,7 @@ def f():
def f():
if 0:
if b:
return
a = 1 # pass
@@ -254,6 +254,26 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase {
doTest();
}
// PY-24750
public void testIfFalse() {
doTest();
}
// PY-24750
public void testIfTrue() {
doTest();
}
// PY-24750
public void testIfElifTrue() {
doTest();
}
// PY-24750
public void testIfElifFalse() {
doTest();
}
private void doTestFirstStatement() {
final String testName = getTestName(false).toLowerCase();
configureByFile(testName + ".py");
@@ -43,6 +43,80 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-24750
public void testIfFalse() {
doTestByText(
"if False:\n" +
" <warning descr=\"This code is unreachable\">a = 1</warning>\n" +
"\n" +
"if False:\n" +
" <warning descr=\"This code is unreachable\">b = 1</warning>\n" +
"else:\n" +
" pass\n" +
"\n" +
"if False:\n" +
" <warning descr=\"This code is unreachable\">c = 1</warning>\n" +
"elif d:\n" +
" pass\n" +
"else:\n" +
" pass\n"
);
}
// PY-24750
public void testIfTrue() {
doTestByText(
"if True:\n" +
" pass\n" +
"\n" +
"if True:\n" +
" pass\n" +
"else:\n" +
" <warning descr=\"This code is unreachable\">b = 1</warning>\n" +
"\n" +
"if True:\n" +
" pass\n" +
"<warning descr=\"This code is unreachable\">elif c:\n" +
" d = 1</warning>\n" +
"else:\n" +
" <warning descr=\"This code is unreachable\">e = 1</warning>\n"
);
}
// PY-24750
public void testIfElifTrue() {
doTestByText(
"if c:\n" +
" pass\n" +
"elif True:\n" +
" pass\n" +
"\n" +
"if d:\n" +
" pass\n" +
"elif True:\n" +
" pass\n" +
"else:\n" +
" <warning descr=\"This code is unreachable\">e = 1</warning>\n"
);
}
// PY-24750
public void testIfElifFalse() {
doTestByText(
"if c:\n" +
" pass\n" +
"elif False:\n" +
" <warning descr=\"This code is unreachable\">a = 1</warning>\n" +
"\n" +
"if d:\n" +
" pass\n" +
"elif False:\n" +
" <warning descr=\"This code is unreachable\">b = 1</warning>\n" +
"else:\n" +
" pass"
);
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {