| /* |
| * 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.inspections; |
| |
| import com.intellij.codeInsight.controlflow.ControlFlow; |
| import com.intellij.codeInsight.controlflow.ControlFlowUtil; |
| import com.intellij.codeInsight.controlflow.Instruction; |
| import com.intellij.codeInsight.dataflow.DFALimitExceededException; |
| import com.intellij.codeInspection.LocalInspectionToolSession; |
| import com.intellij.codeInspection.ProblemHighlightType; |
| import com.intellij.codeInspection.ProblemsHolder; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiElementVisitor; |
| import com.intellij.psi.PsiPolyVariantReference; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.containers.HashSet; |
| import com.jetbrains.python.PyBundle; |
| import com.jetbrains.python.inspections.quickfix.AddGlobalQuickFix; |
| import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; |
| import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction; |
| import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; |
| import com.jetbrains.python.codeInsight.dataflow.scope.Scope; |
| import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; |
| import com.jetbrains.python.codeInsight.dataflow.scope.ScopeVariable; |
| import com.jetbrains.python.psi.*; |
| import com.jetbrains.python.psi.impl.PyBuiltinCache; |
| import com.jetbrains.python.psi.impl.PyGlobalStatementNavigator; |
| import org.jetbrains.annotations.Nls; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.Set; |
| |
| /** |
| * @author oleg |
| */ |
| public class PyUnboundLocalVariableInspection extends PyInspection { |
| private static Key<Set<ScopeOwner>> LARGE_FUNCTIONS_KEY = Key.create("PyUnboundLocalVariableInspection.LargeFunctions"); |
| |
| @NotNull |
| @Nls |
| public String getDisplayName() { |
| return PyBundle.message("INSP.NAME.unbound"); |
| } |
| |
| @NotNull |
| public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull final LocalInspectionToolSession session) { |
| session.putUserData(LARGE_FUNCTIONS_KEY, new HashSet<ScopeOwner>()); |
| return new Visitor(holder, session); |
| } |
| |
| public static class Visitor extends PyInspectionVisitor { |
| |
| public Visitor(final ProblemsHolder holder, LocalInspectionToolSession session) { |
| super(holder, session); |
| } |
| @Override |
| public void visitPyReferenceExpression(final PyReferenceExpression node) { |
| if (node.getContainingFile() instanceof PyExpressionCodeFragment) { |
| return; |
| } |
| // Ignore global statements arguments |
| if (PyGlobalStatementNavigator.getByArgument(node) != null) { |
| return; |
| } |
| // Ignore qualifier inspections |
| if (node.isQualified()) { |
| return; |
| } |
| // Ignore import subelements |
| if (PsiTreeUtil.getParentOfType(node, PyImportStatementBase.class) != null) { |
| return; |
| } |
| final String name = node.getReferencedName(); |
| if (name == null) { |
| return; |
| } |
| final ScopeOwner owner = ScopeUtil.getDeclarationScopeOwner(node, name); |
| final Set<ScopeOwner> largeFunctions = getSession().getUserData(LARGE_FUNCTIONS_KEY); |
| assert largeFunctions != null; |
| if (owner == null || largeFunctions.contains(owner)) { |
| return; |
| } |
| // Ignore references declared in outer scopes |
| if (owner != ScopeUtil.getScopeOwner(node)) { |
| return; |
| } |
| final Scope scope = ControlFlowCache.getScope(owner); |
| // Ignore globals and if scope even doesn't contain such a declaration |
| if (scope.isGlobal(name) || (!scope.containsDeclaration(name))){ |
| return; |
| } |
| // Start DFA from the assignment statement in case of augmented assignments |
| final PsiElement anchor; |
| final PyAugAssignmentStatement augAssignment = PsiTreeUtil.getParentOfType(node, PyAugAssignmentStatement.class); |
| if (augAssignment != null && name.equals(augAssignment.getTarget().getName())) { |
| anchor = augAssignment; |
| } |
| else { |
| anchor = node; |
| } |
| final ScopeVariable variable; |
| try { |
| variable = scope.getDeclaredVariable(anchor, name); |
| } |
| catch (DFALimitExceededException e) { |
| largeFunctions.add(owner); |
| registerLargeFunction(owner); |
| return; |
| } |
| if (variable == null) { |
| if (!isFirstUnboundRead(node, owner)) { |
| return; |
| } |
| final PsiPolyVariantReference ref = node.getReference(getResolveContext()); |
| if (ref == null) { |
| return; |
| } |
| final PsiElement resolved = ref.resolve(); |
| final boolean isBuiltin = PyBuiltinCache.getInstance(node).isBuiltin(resolved); |
| if (owner instanceof PyClass) { |
| if (isBuiltin || ScopeUtil.getDeclarationScopeOwner(owner, name) != null) { |
| return; |
| } |
| } |
| if (PyUnreachableCodeInspection.hasAnyInterruptedControlFlowPaths(node)) { |
| return; |
| } |
| if (owner instanceof PyFile) { |
| if (isBuiltin) { |
| return; |
| } |
| if (resolved != null && !PyUtil.inSameFile(node, resolved)) { |
| return; |
| } |
| registerProblem(node, PyBundle.message("INSP.unbound.name.not.defined", name)); |
| } |
| else { |
| registerProblem(node, PyBundle.message("INSP.unbound.local.variable", node.getName()), |
| ProblemHighlightType.GENERIC_ERROR_OR_WARNING, |
| null, |
| new AddGlobalQuickFix()); |
| } |
| } |
| } |
| |
| private static boolean isFirstUnboundRead(@NotNull PyReferenceExpression node, @NotNull ScopeOwner owner) { |
| final String nodeName = node.getReferencedName(); |
| final Scope scope = ControlFlowCache.getScope(owner); |
| final ControlFlow flow = ControlFlowCache.getControlFlow(owner); |
| final Instruction[] instructions = flow.getInstructions(); |
| final int num = ControlFlowUtil.findInstructionNumberByElement(instructions, node); |
| if (num < 0) { |
| return true; |
| } |
| final Ref<Boolean> first = Ref.create(true); |
| ControlFlowUtil.iteratePrev(num, instructions, new Function<Instruction, ControlFlowUtil.Operation>() { |
| @Override |
| public ControlFlowUtil.Operation fun(Instruction instruction) { |
| if (instruction instanceof ReadWriteInstruction) { |
| final ReadWriteInstruction rwInstruction = (ReadWriteInstruction)instruction; |
| final String name = rwInstruction.getName(); |
| final PsiElement element = rwInstruction.getElement(); |
| if (element != null && name != null && name.equals(nodeName) && instruction.num() != num) { |
| try { |
| if (scope.getDeclaredVariable(element, name) == null) { |
| final ReadWriteInstruction.ACCESS access = rwInstruction.getAccess(); |
| if (access.isReadAccess()) { |
| first.set(false); |
| return ControlFlowUtil.Operation.BREAK; |
| } |
| } |
| } |
| catch (DFALimitExceededException e) { |
| first.set(false); |
| } |
| return ControlFlowUtil.Operation.CONTINUE; |
| } |
| } |
| return ControlFlowUtil.Operation.NEXT; |
| } |
| }); |
| return first.get(); |
| } |
| |
| @Override |
| public void visitPyNonlocalStatement(final PyNonlocalStatement node) { |
| for (PyTargetExpression var : node.getVariables()) { |
| final String name = var.getName(); |
| final ScopeOwner owner = ScopeUtil.getDeclarationScopeOwner(var, name); |
| if (owner == null || owner instanceof PyFile) { |
| registerProblem(var, PyBundle.message("INSP.unbound.nonlocal.variable", name), |
| ProblemHighlightType.GENERIC_ERROR_OR_WARNING); |
| } |
| } |
| } |
| |
| private void registerLargeFunction(ScopeOwner owner) { |
| registerProblem((owner instanceof PyFunction) ? ((PyFunction)owner).getNameIdentifier() : owner, |
| PyBundle.message("INSP.unbound.function.too.large", owner.getName()), |
| ProblemHighlightType.WEAK_WARNING); |
| } |
| } |
| } |