Fix processing normal exits from try-except when they goes out of scope (PY-22184)

This commit is contained in:
Semyon Proshev
2018-02-20 15:21:44 +03:00
parent d38fa20a2a
commit fbee38877a
4 changed files with 42 additions and 24 deletions
@@ -396,7 +396,8 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
}
else {
myBuilder.prevInstruction = null;
final Instruction instruction = ContainerUtil.getFirstItem(InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator));
final Instruction instruction =
ContainerUtil.getFirstItem(InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator));
if (instruction != null) {
lastBranchingPoints.forEach(p -> myBuilder.addEdge(p.getSecond(), instruction));
}
@@ -597,11 +598,11 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
myBuilder.addPendingEdge(node, myBuilder.prevInstruction);
// Process except parts
final List<Instruction> exceptInstructions = emptyMutableList();
List<Pair<PsiElement, Instruction>> pendingBackup = emptyMutableList();
final List<Instruction> exceptInstructions = new ArrayList<>();
List<Pair<PsiElement, Instruction>> pendingBackup = new ArrayList<>();
for (PyExceptPart exceptPart : node.getExceptParts()) {
pendingBackup.addAll(myBuilder.pending);
myBuilder.pending = emptyMutableList();
myBuilder.pending = new ArrayList<>();
myBuilder.flowAbrupted();
final Instruction exceptInstruction = myBuilder.startNode(exceptPart);
exceptPart.accept(this);
@@ -612,7 +613,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
myBuilder.addPendingEdge(pair.first, pair.second);
}
final List<Instruction> normalExits = new ArrayList<>();
final List<Pair<PsiElement, Instruction>> pendingNormalExits = new ArrayList<>();
final PyFinallyPart finallyPart = node.getFinallyPart();
final Instruction finallyFailInstruction;
@@ -624,7 +625,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
final boolean isPending = PsiTreeUtil.isAncestor(node, pendingElement, false) &&
!PsiTreeUtil.isAncestor(finallyPart, pendingElement, false);
if (isPending && pendingScope != null) {
normalExits.add(instruction);
pendingNormalExits.add(Pair.createNonNull(pendingScope, instruction));
}
else {
myBuilder.addPendingEdge(pendingScope, instruction);
@@ -640,7 +641,8 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
finallyPart.accept(this);
myBuilder.addPendingEdge(null, myBuilder.prevInstruction);
myBuilder.flowAbrupted();
} else {
}
else {
finallyFailInstruction = null;
}
@@ -691,16 +693,15 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
}
});
// Duplicate CFG for finally (-fail and -success) only if there are some successfull exits from the
// Duplicate CFG for finally (-fail and -success) only if there are some successful exits from the
// try part. Otherwise a single CFG for finally provides the correct control flow
final Instruction finallyInstruction;
if (!normalExits.isEmpty()) {
if (!pendingNormalExits.isEmpty()) {
// Finally-success part handling
pendingBackup = emptyMutableList();
pendingBackup.addAll(myBuilder.pending);
myBuilder.pending = emptyMutableList();
pendingBackup = new ArrayList<>(myBuilder.pending);
myBuilder.pending = new ArrayList<>();
myBuilder.flowAbrupted();
Instruction finallySuccessInstruction = myBuilder.startNode(finallyPart);
final Instruction finallySuccessInstruction = myBuilder.startNode(finallyPart);
finallyPart.accept(this);
for (Pair<PsiElement, Instruction> pair : pendingBackup) {
myBuilder.addPendingEdge(pair.first, pair.second);
@@ -712,16 +713,21 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
}
// Connect normal exits from try and else parts to the finally part
for (Instruction instr : normalExits) {
myBuilder.addEdge(instr, finallyInstruction);
for (Pair<PsiElement, Instruction> pendingScopeAndInstruction : pendingNormalExits) {
final PsiElement pendingScope = pendingScopeAndInstruction.first;
final Instruction instruction = pendingScopeAndInstruction.second;
myBuilder.addEdge(instruction, finallyInstruction);
// When instruction continues outside of try-except statement scope
// the last instruction in finally-block is marked as pointing to that continuation
if (PsiTreeUtil.isAncestor(pendingScope, node, true)) {
myBuilder.addPendingEdge(pendingScope, myBuilder.prevInstruction);
}
}
}
}
private static <T> List<T> emptyMutableList() {
return new ArrayList<>();
}
@Override
public void visitPyComprehensionElement(final PyComprehensionElement node) {
PyExpression prevCondition = null;
@@ -730,7 +736,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
for (PyComprehensionComponent component : node.getComponents()) {
if (component instanceof PyComprehensionForComponent) {
final PyComprehensionForComponent c = (PyComprehensionForComponent) component;
final PyComprehensionForComponent c = (PyComprehensionForComponent)component;
final PyExpression iteratedList = c.getIteratedList();
final PyExpression iteratorVariable = c.getIteratorVariable();
if (prevCondition != null) {
@@ -755,7 +761,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
iterators.add(iterator);
}
else if (component instanceof PyComprehensionIfComponent) {
final PyComprehensionIfComponent c = (PyComprehensionIfComponent) component;
final PyComprehensionIfComponent c = (PyComprehensionIfComponent)component;
final PyExpression condition = c.getTest();
if (condition == null) {
continue;
@@ -832,7 +838,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
else {
myBuilder.addPendingEdge(pendingScope, instruction);
}
});
});
}
private void abruptFlow(final PsiElement node) {
@@ -40,7 +40,7 @@
39(45) WRITE ACCESS: f
40(41,45) element: PyFinallyPart
41(42,45) element: PyAssignmentStatement
42(43,45) WRITE ACCESS: f
42(43,45,48) WRITE ACCESS: f
43(44,45) element: PyAssignmentStatement
44(45,48) WRITE ACCESS: g
45(46,55) element: PyFinallyPart
@@ -48,7 +48,7 @@
47(55) WRITE ACCESS: h
48(49,55) element: PyFinallyPart
49(50,55) element: PyAssignmentStatement
50(51,55) WRITE ACCESS: h
50(51,53,55) WRITE ACCESS: h
51(52,55) element: PyAssignmentStatement
52(8,53,55) WRITE ACCESS: i
53(54,55) element: PyAssignmentStatement
@@ -0,0 +1,7 @@
def foo():
while True:
try:
break
finally:
pass
print('b')
@@ -38,6 +38,11 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-22184
public void testWhileTrueTryBreakFinally() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {