| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.jetbrains.python.codeInsight.controlflow; |
| |
| import com.intellij.codeInsight.controlflow.ControlFlow; |
| import com.intellij.codeInsight.controlflow.ControlFlowBuilder; |
| import com.intellij.codeInsight.controlflow.Instruction; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiNamedElement; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.jetbrains.python.PyNames; |
| import com.jetbrains.python.psi.*; |
| import com.jetbrains.python.psi.impl.ParamHelper; |
| import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator; |
| import com.jetbrains.python.psi.impl.PyConstantExpressionEvaluator; |
| import com.jetbrains.python.psi.impl.PyImportStatementNavigator; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * @author oleg |
| */ |
| public class PyControlFlowBuilder extends PyRecursiveElementVisitor { |
| private final ControlFlowBuilder myBuilder = new ControlFlowBuilder(); |
| |
| public ControlFlow buildControlFlow(@NotNull final ScopeOwner owner) { |
| return myBuilder.build(this, owner); |
| } |
| |
| @Override |
| public void visitPyFunction(final PyFunction node) { |
| // Create node and stop here |
| myBuilder.startNode(node); |
| visitParameterListExpressions(node.getParameterList()); |
| visitDecorators(node.getDecoratorList()); |
| final PyAnnotation annotation = node.getAnnotation(); |
| if (annotation != null) { |
| annotation.accept(this); |
| } |
| |
| final ReadWriteInstruction instruction = ReadWriteInstruction.write(myBuilder, node, node.getName()); |
| myBuilder.addNode(instruction); |
| myBuilder.checkPending(instruction); |
| } |
| |
| private void visitDecorators(PyDecoratorList list) { |
| if (list != null) { |
| for (PyDecorator decorator : list.getDecorators()) { |
| decorator.accept(this); |
| } |
| } |
| } |
| |
| private void visitParameterListExpressions(PyParameterList parameterList) { |
| ParamHelper.walkDownParamArray(parameterList.getParameters(), new ParamHelper.ParamVisitor() { |
| @Override |
| public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) { |
| final PyExpression defaultValue = param.getDefaultValue(); |
| if (defaultValue != null) { |
| defaultValue.accept(PyControlFlowBuilder.this); |
| } |
| final PyAnnotation annotation = param.getAnnotation(); |
| if (annotation != null) { |
| annotation.accept(PyControlFlowBuilder.this); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void visitPyClass(final PyClass node) { |
| // Create node and stop here |
| myBuilder.startNode(node); |
| for (PsiElement element : node.getSuperClassExpressions()) { |
| element.accept(this); |
| } |
| visitDecorators(node.getDecoratorList()); |
| final ReadWriteInstruction instruction = ReadWriteInstruction.write(myBuilder, node, node.getName()); |
| myBuilder.addNode(instruction); |
| myBuilder.checkPending(instruction); |
| } |
| |
| @Override |
| public void visitPyStatement(final PyStatement node) { |
| myBuilder.startNode(node); |
| super.visitPyStatement(node); |
| } |
| |
| @Override |
| public void visitPyElement(PyElement node) { |
| if (node instanceof PsiNamedElement && !(node instanceof PyKeywordArgument)) { |
| myBuilder.startNode(node); |
| myBuilder.addNode(ReadWriteInstruction.newInstruction(myBuilder, node, node.getName(), ReadWriteInstruction.ACCESS.WRITE)); |
| } |
| super.visitPyElement(node); |
| } |
| |
| @Override |
| public void visitPyCallExpression(final PyCallExpression node) { |
| final PyExpression callee = node.getCallee(); |
| // Flow abrupted |
| final String repr = PyUtil.getReadableRepr(callee, true); |
| if (callee != null && ("sys.exit".equals(repr) || |
| "self.fail".equals(repr))) { |
| callee.accept(this); |
| for (PyExpression expression : node.getArguments()) { |
| expression.accept(this); |
| } |
| abruptFlow(node); |
| } |
| else { |
| super.visitPyCallExpression(node); |
| } |
| if (node.isCalleeText(PyNames.ASSERT_IS_INSTANCE)) { |
| final PyTypeAssertionEvaluator assertionEvaluator = new PyTypeAssertionEvaluator(); |
| node.accept(assertionEvaluator); |
| InstructionBuilder.addAssertInstructions(myBuilder, assertionEvaluator); |
| } |
| } |
| |
| @Override |
| public void visitPySubscriptionExpression(PySubscriptionExpression node) { |
| myBuilder.startNode(node); |
| node.getOperand().accept(this); |
| final PyExpression expression = node.getIndexExpression(); |
| if (expression != null) { |
| expression.accept(this); |
| } |
| } |
| |
| @Override |
| public void visitPyReferenceExpression(final PyReferenceExpression node) { |
| final PyExpression qualifier = node.getQualifier(); |
| if (qualifier != null) { |
| qualifier.accept(this); |
| return; |
| } |
| if (PyImportStatementNavigator.getImportStatementByElement(node) != null) { |
| return; |
| } |
| |
| final ReadWriteInstruction.ACCESS access = PyAugAssignmentStatementNavigator.getStatementByTarget(node) != null |
| ? ReadWriteInstruction.ACCESS.READWRITE |
| : ReadWriteInstruction.ACCESS.READ; |
| final ReadWriteInstruction readWriteInstruction = ReadWriteInstruction.newInstruction(myBuilder, node, node.getName(), access); |
| myBuilder.addNode(readWriteInstruction); |
| myBuilder.checkPending(readWriteInstruction); |
| } |
| |
| @Override |
| public void visitPyAssignmentStatement(final PyAssignmentStatement node) { |
| myBuilder.startNode(node); |
| final PyExpression value = node.getAssignedValue(); |
| if (value != null) { |
| value.accept(this); |
| } |
| for (PyExpression expression : node.getRawTargets()) { |
| expression.accept(this); |
| } |
| } |
| |
| @Override |
| public void visitPyAugAssignmentStatement(final PyAugAssignmentStatement node) { |
| myBuilder.startNode(node); |
| final PyExpression value = node.getValue(); |
| if (value != null) { |
| value.accept(this); |
| } |
| node.getTarget().accept(this); |
| } |
| |
| @Override |
| public void visitPyTargetExpression(final PyTargetExpression node) { |
| final PsiElement[] children = node.getChildren(); |
| // Case of non qualified reference |
| if (children.length == 0) { |
| final ReadWriteInstruction.ACCESS access = node.getParent() instanceof PySliceExpression |
| ? ReadWriteInstruction.ACCESS.READ : ReadWriteInstruction.ACCESS.WRITE; |
| final ReadWriteInstruction instruction = ReadWriteInstruction.newInstruction(myBuilder, node, node.getName(), access); |
| myBuilder.addNode(instruction); |
| myBuilder.checkPending(instruction); |
| } |
| else { |
| for (PsiElement child : children) { |
| child.accept(this); |
| } |
| } |
| } |
| |
| @Override |
| public void visitPyNamedParameter(final PyNamedParameter node) { |
| final PyExpression defaultValue = node.getDefaultValue(); |
| if (defaultValue != null) { |
| defaultValue.accept(this); |
| } |
| final ReadWriteInstruction instruction = ReadWriteInstruction.write(myBuilder, node, node.getName()); |
| myBuilder.addNode(instruction); |
| myBuilder.checkPending(instruction); |
| } |
| |
| @Override |
| public void visitPyImportStatement(final PyImportStatement node) { |
| visitPyImportStatementBase(node); |
| } |
| |
| @Override |
| public void visitPyFromImportStatement(PyFromImportStatement node) { |
| visitPyImportStatementBase(node); |
| } |
| |
| private void visitPyImportStatementBase(PyImportStatementBase node) { |
| myBuilder.startNode(node); |
| for (PyImportElement importElement : node.getImportElements()) { |
| final ReadWriteInstruction instruction = ReadWriteInstruction.write(myBuilder, importElement, importElement.getVisibleName()); |
| if (instruction != null) { |
| myBuilder.addNode(instruction); |
| myBuilder.checkPending(instruction); |
| } |
| } |
| } |
| |
| private Instruction getPrevInstruction(final PyElement condition) { |
| final Ref<Instruction> head = new Ref<Instruction>(myBuilder.prevInstruction); |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| public void process(final PsiElement pendingScope, final Instruction instruction) { |
| if (pendingScope != null && PsiTreeUtil.isAncestor(condition, pendingScope, false)) { |
| head.set(instruction); |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| }); |
| return head.get(); |
| } |
| |
| @Override |
| public void visitPyConditionalExpression(PyConditionalExpression node) { |
| myBuilder.startNode(node); |
| final PyExpression condition = node.getCondition(); |
| final PyTypeAssertionEvaluator assertionEvaluator = new PyTypeAssertionEvaluator(); |
| if (condition != null) { |
| condition.accept(this); |
| condition.accept(assertionEvaluator); |
| } |
| final Instruction branchingPoint = myBuilder.prevInstruction; |
| final PyExpression truePart = node.getTruePart(); |
| final PyExpression falsePart = node.getFalsePart(); |
| if (truePart != null) { |
| InstructionBuilder.addAssertInstructions(myBuilder, assertionEvaluator); |
| truePart.accept(this); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| if (falsePart != null) { |
| myBuilder.prevInstruction = branchingPoint; |
| falsePart.accept(this); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| } |
| |
| @Override |
| public void visitPyIfStatement(final PyIfStatement node) { |
| myBuilder.startNode(node); |
| final PyIfPart ifPart = node.getIfPart(); |
| PyExpression condition = ifPart.getCondition(); |
| PyTypeAssertionEvaluator assertionEvaluator = new PyTypeAssertionEvaluator(); |
| if (condition != null) { |
| condition.accept(this); |
| condition.accept(assertionEvaluator); |
| } |
| // Set the head as the last instruction of condition |
| PyElement lastCondition = condition; |
| Instruction lastBranchingPoint = getPrevInstruction(condition); |
| myBuilder.prevInstruction = lastBranchingPoint; |
| final PyStatementList thenStatements = ifPart.getStatementList(); |
| if (thenStatements != null) { |
| myBuilder.startConditionalNode(thenStatements, condition, true); |
| InstructionBuilder.addAssertInstructions(myBuilder, assertionEvaluator); |
| thenStatements.accept(this); |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| public void process(final PsiElement pendingScope, final Instruction instruction) { |
| if (pendingScope != null && PsiTreeUtil.isAncestor(thenStatements, pendingScope, false)) { |
| myBuilder.addPendingEdge(node, instruction); |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| }); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| for (final PyIfPart part : node.getElifParts()) { |
| // Set the head as the false branch |
| myBuilder.prevInstruction = lastBranchingPoint; |
| myBuilder.startConditionalNode(part, lastCondition, false); |
| condition = part.getCondition(); |
| assertionEvaluator = new PyTypeAssertionEvaluator(); |
| if (condition != null) { |
| lastCondition = condition; |
| lastBranchingPoint = getPrevInstruction(lastCondition); |
| condition.accept(this); |
| condition.accept(assertionEvaluator); |
| } |
| // Set the head as the last instruction of condition |
| myBuilder.prevInstruction = getPrevInstruction(lastCondition); |
| myBuilder.startConditionalNode(part, lastCondition, true); |
| final PyStatementList statementList = part.getStatementList(); |
| if (statementList != null) { |
| InstructionBuilder.addAssertInstructions(myBuilder, assertionEvaluator); |
| statementList.accept(this); |
| } |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| public void process(final PsiElement pendingScope, final Instruction instruction) { |
| if (pendingScope != null && PsiTreeUtil.isAncestor(part, pendingScope, false)) { |
| myBuilder.addPendingEdge(node, instruction); |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| }); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| boolean noPendingInScopeEdges = false; |
| if (!assertionEvaluator.getDefinitions().isEmpty()) { |
| final Ref<Boolean> pendingInScopeEdges = Ref.create(false); |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| @Override |
| public void process(PsiElement pendingScope, Instruction instruction) { |
| if (pendingScope != null && PsiTreeUtil.isAncestor(node, pendingScope, false)) { |
| pendingInScopeEdges.set(true); |
| } |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| }); |
| noPendingInScopeEdges = !pendingInScopeEdges.get(); |
| } |
| final PyTypeAssertionEvaluator negativeAssertionEvaluator = new PyTypeAssertionEvaluator(false); |
| final PyExpression ifCondition = ifPart.getCondition(); |
| // TODO: Add support for 'elif' |
| if (ifCondition != null) { |
| ifCondition.accept(negativeAssertionEvaluator); |
| } |
| final PyElsePart elseBranch = node.getElsePart(); |
| if (elseBranch != null) { |
| // Set the head as the false branch |
| myBuilder.prevInstruction = lastBranchingPoint; |
| myBuilder.startConditionalNode(elseBranch, lastCondition, false); |
| InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator); |
| elseBranch.accept(this); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } else { |
| if (noPendingInScopeEdges) { |
| myBuilder.prevInstruction = lastBranchingPoint; |
| InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| else { |
| myBuilder.addPendingEdge(node, lastBranchingPoint); |
| } |
| } |
| } |
| |
| @Override |
| public void visitPyWhileStatement(final PyWhileStatement node) { |
| final Instruction instruction = myBuilder.startNode(node); |
| final PyWhilePart whilePart = node.getWhilePart(); |
| final PyExpression condition = whilePart.getCondition(); |
| if (condition != null) { |
| condition.accept(this); |
| } |
| final Instruction head = myBuilder.prevInstruction; |
| final PyElsePart elsePart = node.getElsePart(); |
| if (elsePart == null) { |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| final PyStatementList list = whilePart.getStatementList(); |
| if (list != null) { |
| myBuilder.startConditionalNode(list, condition, true); |
| list.accept(this); |
| // Loop edges |
| if (myBuilder.prevInstruction != null) { |
| myBuilder.addEdge(myBuilder.prevInstruction, instruction); |
| } |
| myBuilder.checkPending(instruction); |
| } |
| myBuilder.prevInstruction = head; |
| if (elsePart != null) { |
| elsePart.accept(this); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| myBuilder.flowAbrupted(); |
| } |
| |
| @Override |
| public void visitPyForStatement(final PyForStatement node) { |
| myBuilder.startNode(node); |
| final PyForPart forPart = node.getForPart(); |
| final PyExpression source = forPart.getSource(); |
| if (source != null) { |
| source.accept(this); |
| } |
| final Instruction head = myBuilder.prevInstruction; |
| final PyElsePart elsePart = node.getElsePart(); |
| if (elsePart == null) { |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| final PyStatementList list = forPart.getStatementList(); |
| if (list != null) { |
| final Instruction body; |
| final PyExpression target = forPart.getTarget(); |
| if (target != null) { |
| body = myBuilder.startNode(target); |
| target.accept(this); |
| } |
| else { |
| body = myBuilder.startNode(list); |
| } |
| list.accept(this); |
| if (myBuilder.prevInstruction != null) { |
| myBuilder.addEdge(myBuilder.prevInstruction, body); //loop |
| myBuilder.addPendingEdge(list, myBuilder.prevInstruction); // exit |
| } |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| public void process(final PsiElement pendingScope, final Instruction instruction) { |
| if (pendingScope != null && PsiTreeUtil.isAncestor(list, pendingScope, false)) { |
| myBuilder.addEdge(instruction, body); //loop |
| myBuilder.addPendingEdge(list, instruction); // exit |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| }); |
| } |
| myBuilder.prevInstruction = head; |
| if (elsePart != null) { |
| elsePart.accept(this); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| myBuilder.flowAbrupted(); |
| } |
| |
| @Override |
| public void visitPyBreakStatement(final PyBreakStatement node) { |
| myBuilder.startNode(node); |
| final PyLoopStatement loop = node.getLoopStatement(); |
| if (loop != null) { |
| myBuilder.addPendingEdge(loop, myBuilder.prevInstruction); |
| } |
| else { |
| myBuilder.addPendingEdge(null, myBuilder.prevInstruction); |
| } |
| myBuilder.flowAbrupted(); |
| } |
| |
| @Override |
| public void visitPyContinueStatement(final PyContinueStatement node) { |
| myBuilder.startNode(node); |
| final PyLoopStatement loop = node.getLoopStatement(); |
| if (loop != null) { |
| final Instruction instruction = myBuilder.findInstructionByElement(loop); |
| if (instruction != null) { |
| myBuilder.addEdge(myBuilder.prevInstruction, instruction); |
| } |
| else { |
| myBuilder.addPendingEdge(null, instruction); |
| } |
| } |
| myBuilder.flowAbrupted(); |
| } |
| |
| @Override |
| public void visitPyYieldExpression(PyYieldExpression node) { |
| myBuilder.startNode(node); |
| final PyExpression expression = node.getExpression(); |
| if (expression != null) { |
| expression.accept(this); |
| } |
| } |
| |
| @Override |
| public void visitPyRaiseStatement(final PyRaiseStatement node) { |
| myBuilder.startNode(node); |
| final PyExpression[] expressions = node.getExpressions(); |
| for (PyExpression expression : expressions) { |
| expression.accept(this); |
| } |
| |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| public void process(final PsiElement pendingScope, final Instruction instruction) { |
| final PsiElement pendingElement = instruction.getElement(); |
| if (pendingElement != null && PsiTreeUtil.isAncestor(node, pendingElement, false)) { |
| myBuilder.addEdge(null, instruction); |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| }); |
| myBuilder.addPendingEdge(null, myBuilder.prevInstruction); |
| myBuilder.flowAbrupted(); |
| } |
| |
| @Override |
| public void visitPyReturnStatement(final PyReturnStatement node) { |
| myBuilder.startNode(node); |
| final PyExpression expression = node.getExpression(); |
| if (expression != null) { |
| expression.accept(this); |
| } |
| abruptFlow(node); |
| } |
| |
| @Override |
| public void visitPyTryExceptStatement(final PyTryExceptStatement node) { |
| myBuilder.startNode(node); |
| |
| // Process try part |
| final PyTryPart tryPart = node.getTryPart(); |
| myBuilder.startNode(tryPart); |
| tryPart.accept(this); |
| |
| // Goto else part after execution, or exit |
| final PyElsePart elsePart = node.getElsePart(); |
| if (elsePart != null) { |
| myBuilder.startNode(elsePart); |
| elsePart.accept(this); |
| } |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| |
| // Process except parts |
| final List<Instruction> exceptInstructions = emptyMutableList(); |
| List<Pair<PsiElement, Instruction>> pendingBackup = emptyMutableList(); |
| for (PyExceptPart exceptPart : node.getExceptParts()) { |
| pendingBackup.addAll(myBuilder.pending); |
| myBuilder.pending = emptyMutableList(); |
| myBuilder.flowAbrupted(); |
| final Instruction exceptInstruction = myBuilder.startNode(exceptPart); |
| exceptPart.accept(this); |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| exceptInstructions.add(exceptInstruction); |
| } |
| for (Pair<PsiElement, Instruction> pair : pendingBackup) { |
| myBuilder.addPendingEdge(pair.first, pair.second); |
| } |
| |
| final List<Instruction> normalExits = new ArrayList<Instruction>(); |
| final PyFinallyPart finallyPart = node.getFinallyPart(); |
| final Instruction finallyFailInstruction; |
| |
| // Store pending normal exit instructions from try-except-else parts |
| if (finallyPart != null) { |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| public void process(final PsiElement pendingScope, final Instruction instruction) { |
| final PsiElement pendingElement = instruction.getElement(); |
| if (pendingElement != null) { |
| final boolean isPending = PsiTreeUtil.isAncestor(node, pendingElement, false) && |
| !PsiTreeUtil.isAncestor(finallyPart, pendingElement, false); |
| if (isPending && pendingScope != null) { |
| normalExits.add(instruction); |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| } |
| }); |
| } |
| |
| // Finally-fail part handling |
| if (finallyPart != null) { |
| myBuilder.flowAbrupted(); |
| finallyFailInstruction = myBuilder.startNode(finallyPart); |
| finallyPart.accept(this); |
| myBuilder.addPendingEdge(null, myBuilder.prevInstruction); |
| myBuilder.flowAbrupted(); |
| } else { |
| finallyFailInstruction = null; |
| } |
| |
| // Create exception edges |
| for (Instruction instruction : myBuilder.instructions) { |
| final PsiElement e = instruction.getElement(); |
| if (e == null || !canRaiseExceptions(instruction)) { |
| continue; |
| } |
| // All instructions inside the try part have edges to except and finally parts |
| if (PsiTreeUtil.getParentOfType(e, PyTryPart.class, false) == tryPart) { |
| for (Instruction inst : exceptInstructions) { |
| myBuilder.addEdge(instruction, inst); |
| } |
| if (finallyPart != null) { |
| myBuilder.addEdge(instruction, finallyFailInstruction); |
| } |
| } |
| if (finallyPart != null) { |
| // All instructions inside except parts have edges to the finally part |
| for (PyExceptPart exceptPart : node.getExceptParts()) { |
| if (PsiTreeUtil.isAncestor(exceptPart, e, false)) { |
| myBuilder.addEdge(instruction, finallyFailInstruction); |
| } |
| } |
| // All instructions inside the else part have edges to the finally part |
| if (PsiTreeUtil.isAncestor(elsePart, e, false)) { |
| myBuilder.addEdge(instruction, finallyFailInstruction); |
| } |
| } |
| } |
| |
| if (finallyPart != null) { |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| @Override |
| public void process(PsiElement pendingScope, Instruction instruction) { |
| final PsiElement e = instruction.getElement(); |
| if (e != null) { |
| // Change the scope of pending edges from finally-fail part to point to the last instruction |
| if (PsiTreeUtil.isAncestor(finallyPart, e, false)) { |
| myBuilder.addPendingEdge(null, instruction); |
| } |
| // Connect pending fail edges to the finally-fail part |
| else if (pendingScope == null && PsiTreeUtil.isAncestor(node, e, false)) { |
| myBuilder.addEdge(instruction, finallyFailInstruction); |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| } |
| }); |
| |
| // Duplicate CFG for finally (-fail and -success) only if there are some successfull exits from the |
| // try part. Otherwise a single CFG for finally provides the correct control flow |
| final Instruction finallyInstruction; |
| if (!normalExits.isEmpty()) { |
| // Finally-success part handling |
| pendingBackup = emptyMutableList(); |
| pendingBackup.addAll(myBuilder.pending); |
| myBuilder.pending = emptyMutableList(); |
| myBuilder.flowAbrupted(); |
| Instruction finallySuccessInstruction = myBuilder.startNode(finallyPart); |
| finallyPart.accept(this); |
| for (Pair<PsiElement, Instruction> pair : pendingBackup) { |
| myBuilder.addPendingEdge(pair.first, pair.second); |
| } |
| finallyInstruction = finallySuccessInstruction; |
| } |
| else { |
| finallyInstruction = finallyFailInstruction; |
| } |
| |
| // Connect normal exits from try and else parts to the finally part |
| for (Instruction instr : normalExits) { |
| myBuilder.addEdge(instr, finallyInstruction); |
| } |
| } |
| } |
| |
| private static <T> List<T> emptyMutableList() { |
| return new ArrayList<T>(); |
| } |
| |
| @Override |
| public void visitPyComprehensionElement(final PyComprehensionElement node) { |
| PyExpression prevCondition = null; |
| myBuilder.startNode(node); |
| List<Instruction> iterators = new ArrayList<Instruction>(); |
| |
| for (ComprehensionComponent component : node.getComponents()) { |
| if (component instanceof ComprhForComponent) { |
| final ComprhForComponent c = (ComprhForComponent) component; |
| final PyExpression iteratedList = c.getIteratedList(); |
| final PyExpression iteratorVariable = c.getIteratorVariable(); |
| if (prevCondition != null) { |
| myBuilder.startConditionalNode(iteratedList, prevCondition, true); |
| prevCondition = null; |
| } |
| else { |
| myBuilder.startNode(iteratedList); |
| } |
| iteratedList.accept(this); |
| |
| // for-loop continue and exit |
| for (Instruction i : iterators) { |
| myBuilder.addEdge(myBuilder.prevInstruction, i); |
| } |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| |
| final Instruction iterator = myBuilder.startNode(iteratorVariable); |
| iteratorVariable.accept(this); |
| |
| // Inner "for" and "if" constructs will be linked to all outer iterators |
| iterators.add(iterator); |
| } |
| else if (component instanceof ComprhIfComponent) { |
| final ComprhIfComponent c = (ComprhIfComponent) component; |
| final PyExpression condition = c.getTest(); |
| if (condition == null) { |
| continue; |
| } |
| if (prevCondition != null) { |
| myBuilder.startConditionalNode(condition, prevCondition, true); |
| } |
| else { |
| myBuilder.startNode(condition); |
| } |
| condition.accept(this); |
| |
| // Condition is true for nested "for" and "if" constructs, next startNode() should create a conditional node |
| prevCondition = condition; |
| |
| // for-loop continue and exit |
| for (Instruction i : iterators) { |
| myBuilder.addEdge(myBuilder.prevInstruction, i); |
| } |
| myBuilder.addPendingEdge(node, myBuilder.prevInstruction); |
| } |
| } |
| |
| final PyExpression result = node.getResultExpression(); |
| if (result != null) { |
| if (prevCondition != null) { |
| myBuilder.startConditionalNode(result, prevCondition, true); |
| } |
| else { |
| myBuilder.startNode(result); |
| } |
| result.accept(this); |
| |
| // for-loop continue |
| for (Instruction i : iterators) { |
| myBuilder.addEdge(myBuilder.prevInstruction, i); |
| } |
| } |
| } |
| |
| public void visitPyAssertStatement(final PyAssertStatement node) { |
| super.visitPyAssertStatement(node); |
| final PyExpression[] args = node.getArguments(); |
| // assert False |
| if (args.length >= 1 && PyConstantExpressionEvaluator.evaluate(args[0]) == Boolean.FALSE) { |
| abruptFlow(node); |
| return; |
| } |
| PyTypeAssertionEvaluator evaluator = new PyTypeAssertionEvaluator(); |
| node.acceptChildren(evaluator); |
| InstructionBuilder.addAssertInstructions(myBuilder, evaluator); |
| } |
| |
| @Override |
| public void visitPyLambdaExpression(final PyLambdaExpression node) { |
| myBuilder.startNode(node); |
| visitParameterListExpressions(node.getParameterList()); |
| } |
| |
| @Override |
| public void visitPyWithStatement(final PyWithStatement node) { |
| super.visitPyWithStatement(node); |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| public void process(final PsiElement pendingScope, final Instruction instruction) { |
| final PsiElement element = instruction.getElement(); |
| if (element != null && PsiTreeUtil.isAncestor(node, element, true) && |
| PsiTreeUtil.getParentOfType(element, PyRaiseStatement.class) != null) { |
| myBuilder.addPendingEdge(node, instruction); |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| }); |
| } |
| |
| private void abruptFlow(final PsiElement node) { |
| // Here we process pending instructions!!! |
| myBuilder.processPending(new ControlFlowBuilder.PendingProcessor() { |
| public void process(final PsiElement pendingScope, final Instruction instruction) { |
| if (pendingScope != null && PsiTreeUtil.isAncestor(node, pendingScope, false)) { |
| myBuilder.addPendingEdge(null, instruction); |
| } |
| else { |
| myBuilder.addPendingEdge(pendingScope, instruction); |
| } |
| } |
| }); |
| myBuilder.addPendingEdge(null, myBuilder.prevInstruction); |
| myBuilder.flowAbrupted(); |
| } |
| |
| private static boolean canRaiseExceptions(final Instruction instruction) { |
| if (instruction instanceof ReadWriteInstruction) { |
| return true; |
| } |
| return !PsiTreeUtil.instanceOf(instruction.getElement(), |
| PyStatementList.class); |
| } |
| } |
| |