diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index c58265df1fff..ea0eb5f2edcb 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -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 exceptInstructions = emptyMutableList(); - List> pendingBackup = emptyMutableList(); + final List exceptInstructions = new ArrayList<>(); + List> 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 normalExits = new ArrayList<>(); + final List> 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 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 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 List 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) { diff --git a/python/testData/codeInsight/controlflow/trytry.txt b/python/testData/codeInsight/controlflow/trytry.txt index 0509f5935ea2..0ce67198ecc6 100644 --- a/python/testData/codeInsight/controlflow/trytry.txt +++ b/python/testData/codeInsight/controlflow/trytry.txt @@ -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 diff --git a/python/testData/inspections/PyUnreachableCodeInspection/WhileTrueTryBreakFinally.py b/python/testData/inspections/PyUnreachableCodeInspection/WhileTrueTryBreakFinally.py new file mode 100644 index 000000000000..3a80cabede26 --- /dev/null +++ b/python/testData/inspections/PyUnreachableCodeInspection/WhileTrueTryBreakFinally.py @@ -0,0 +1,7 @@ +def foo(): + while True: + try: + break + finally: + pass + print('b') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java index add825bfbf6b..04aea5c965b1 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnreachableCodeInspectionTest.java @@ -38,6 +38,11 @@ public class PyUnreachableCodeInspectionTest extends PyInspectionTestCase { doTest(); } + // PY-22184 + public void testWhileTrueTryBreakFinally() { + doTest(); + } + @NotNull @Override protected Class getInspectionClass() {