/* * Copyright 2000-2009 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.intellij.psi.controlFlow; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.*; import com.intellij.psi.impl.source.DummyHolder; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ReflectionCache; import com.intellij.util.containers.IntArrayList; import gnu.trove.THashSet; import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; public class ControlFlowUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.controlFlow.ControlFlowUtil"); private static class SSAInstructionState implements Cloneable { private final int myWriteCount; private final int myInstructionIdx; public SSAInstructionState(int writeCount, int instructionIdx) { myWriteCount = writeCount; myInstructionIdx = instructionIdx; } public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SSAInstructionState)) return false; final SSAInstructionState ssaInstructionState = (SSAInstructionState)o; if (myInstructionIdx != ssaInstructionState.myInstructionIdx) return false; if (Math.min(2, myWriteCount) != Math.min(2, ssaInstructionState.myWriteCount)) return false; return true; } public int hashCode() { int result = Math.min(2, myWriteCount); result = 29 * result + myInstructionIdx; return result; } public int getWriteCount() { return myWriteCount; } public int getInstructionIdx() { return myInstructionIdx; } } public static List<PsiVariable> getSSAVariables(ControlFlow flow) { return getSSAVariables(flow, 0, flow.getSize(), false); } public static List<PsiVariable> getSSAVariables(ControlFlow flow, int from, int to, boolean reportVarsIfNonInitializingPathExists) { List<Instruction> instructions = flow.getInstructions(); Collection<PsiVariable> writtenVariables = getWrittenVariables(flow, from, to, false); ArrayList<PsiVariable> result = new ArrayList<PsiVariable>(1); variables: for (PsiVariable psiVariable : writtenVariables) { final List<SSAInstructionState> queue = new ArrayList<SSAInstructionState>(); queue.add(new SSAInstructionState(0, from)); Set<SSAInstructionState> processedStates = new THashSet<SSAInstructionState>(); while (!queue.isEmpty()) { final SSAInstructionState state = queue.remove(0); if (state.getWriteCount() > 1) continue variables; if (!processedStates.contains(state)) { processedStates.add(state); int i = state.getInstructionIdx(); if (i < to) { Instruction instruction = instructions.get(i); if (instruction instanceof ReturnInstruction) { int[] offsets = ((ReturnInstruction)instruction).getPossibleReturnOffsets(); for (int offset : offsets) { queue.add(new SSAInstructionState(state.getWriteCount(), Math.min(offset, to))); } } else if (instruction instanceof GoToInstruction) { int nextOffset = ((GoToInstruction)instruction).offset; nextOffset = Math.min(nextOffset, to); queue.add(new SSAInstructionState(state.getWriteCount(), nextOffset)); } else if (instruction instanceof ThrowToInstruction) { int nextOffset = ((ThrowToInstruction)instruction).offset; nextOffset = Math.min(nextOffset, to); queue.add(new SSAInstructionState(state.getWriteCount(), nextOffset)); } else if (instruction instanceof ConditionalGoToInstruction) { int nextOffset = ((ConditionalGoToInstruction)instruction).offset; nextOffset = Math.min(nextOffset, to); queue.add(new SSAInstructionState(state.getWriteCount(), nextOffset)); queue.add(new SSAInstructionState(state.getWriteCount(), i + 1)); } else if (instruction instanceof ConditionalThrowToInstruction) { int nextOffset = ((ConditionalThrowToInstruction)instruction).offset; nextOffset = Math.min(nextOffset, to); queue.add(new SSAInstructionState(state.getWriteCount(), nextOffset)); queue.add(new SSAInstructionState(state.getWriteCount(), i + 1)); } else if (instruction instanceof WriteVariableInstruction) { WriteVariableInstruction write = (WriteVariableInstruction)instruction; queue.add(new SSAInstructionState(state.getWriteCount() + (write.variable == psiVariable ? 1 : 0), i + 1)); } else if (instruction instanceof ReadVariableInstruction) { ReadVariableInstruction read = (ReadVariableInstruction)instruction; if (read.variable == psiVariable && state.getWriteCount() == 0) continue variables; queue.add(new SSAInstructionState(state.getWriteCount(), i + 1)); } else { queue.add(new SSAInstructionState(state.getWriteCount(), i + 1)); } } else if (!reportVarsIfNonInitializingPathExists && state.getWriteCount() == 0) continue variables; } } result.add(psiVariable); } return result; } private static boolean needVariableValueAt(final PsiVariable variable, final ControlFlow flow, final int offset) { InstructionClientVisitor<Boolean> visitor = new InstructionClientVisitor<Boolean>() { final boolean[] neededBelow = new boolean[flow.getSize() + 1]; @Override public void procedureEntered(int startOffset, int endOffset) { for (int i = startOffset; i < endOffset; i++) neededBelow[i] = false; } @Override public void visitReadVariableInstruction(ReadVariableInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean needed = neededBelow[nextOffset]; if (instruction.variable.equals(variable)) { needed = true; } neededBelow[offset] |= needed; } @Override public void visitWriteVariableInstruction(WriteVariableInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean needed = neededBelow[nextOffset]; if (instruction.variable.equals(variable)) { needed = false; } neededBelow[offset] = needed; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean needed = neededBelow[nextOffset]; neededBelow[offset] |= needed; } @Override public Boolean getResult() { return neededBelow[offset]; } }; depthFirstSearch(flow, visitor, offset, flow.getSize()); return visitor.getResult().booleanValue(); } public static Collection<PsiVariable> getWrittenVariables(ControlFlow flow, int start, int end, final boolean ignoreNotReachingWrites) { final HashSet<PsiVariable> set = new HashSet<PsiVariable>(); getWrittenVariables(flow, start, end, ignoreNotReachingWrites, set); return set; } public static void getWrittenVariables(ControlFlow flow, int start, int end, final boolean ignoreNotReachingWrites, final Collection<PsiVariable> set) { List<Instruction> instructions = flow.getInstructions(); for (int i = start; i < end; i++) { Instruction instruction = instructions.get(i); if (instruction instanceof WriteVariableInstruction && (!ignoreNotReachingWrites || isInstructionReachable(flow, end, i))) { set.add(((WriteVariableInstruction)instruction).variable); } } } public static List<PsiVariable> getUsedVariables(ControlFlow flow, int start, int end) { ArrayList<PsiVariable> array = new ArrayList<PsiVariable>(); List<Instruction> instructions = flow.getInstructions(); for (int i = start; i < end; i++) { Instruction instruction = instructions.get(i); if (instruction instanceof ReadVariableInstruction) { PsiVariable variable = ((ReadVariableInstruction)instruction).variable; if (!array.contains(variable)) { array.add(variable); } } else if (instruction instanceof WriteVariableInstruction) { PsiVariable variable = ((WriteVariableInstruction)instruction).variable; if (!array.contains(variable)) { array.add(variable); } } } return array; } public static List<PsiVariable> getInputVariables(ControlFlow flow, int start, int end) { List<PsiVariable> usedVariables = getUsedVariables(flow, start, end); ArrayList<PsiVariable> array = new ArrayList<PsiVariable>(usedVariables.size()); for (PsiVariable variable : usedVariables) { if (needVariableValueAt(variable, flow, start)) { array.add(variable); } } return array; } public static PsiVariable[] getOutputVariables(ControlFlow flow, int start, int end, int[] exitPoints) { Collection<PsiVariable> writtenVariables = getWrittenVariables(flow, start, end, false); ArrayList<PsiVariable> array = new ArrayList<PsiVariable>(); for (PsiVariable variable : writtenVariables) { for (int exitPoint : exitPoints) { if (needVariableValueAt(variable, flow, exitPoint)) { array.add(variable); } } } PsiVariable[] outputVariables = array.toArray(new PsiVariable[array.size()]); if (LOG.isDebugEnabled()) { LOG.debug("output variables:"); for (PsiVariable variable : outputVariables) { LOG.debug(" " + variable.toString()); } } return outputVariables; } public static Collection<PsiStatement> findExitPointsAndStatements(final ControlFlow flow, final int start, final int end, final IntArrayList exitPoints, final Class... classesFilter) { if (end == start) { exitPoints.add(end); return Collections.emptyList(); } final Collection<PsiStatement> exitStatements = new THashSet<PsiStatement>(); InstructionClientVisitor visitor = new InstructionClientVisitor() { @Override public void visitThrowToInstruction(ThrowToInstruction instruction, int offset, int nextOffset) { //[ven]This is a hack since Extract Method doesn't want to see throw's exit points processGotoStatement(classesFilter, exitStatements, findStatement(flow, offset)); } @Override public void visitBranchingInstruction(BranchingInstruction instruction, int offset, int nextOffset) { processGoto(flow, start, end, exitPoints, exitStatements, instruction, classesFilter, findStatement(flow, offset)); } // call/return do not incur exit points @Override public void visitReturnInstruction(ReturnInstruction instruction, int offset, int nextOffset) { } @Override public void visitCallInstruction(CallInstruction instruction, int offset, int nextOffset) { } @Override public void visitConditionalThrowToInstruction(ConditionalThrowToInstruction instruction, int offset, int nextOffset) { visitInstruction(instruction, offset, nextOffset); } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { if (offset >= end - 1) { int exitOffset = end; exitOffset = promoteThroughGotoChain(flow, exitOffset); if (!exitPoints.contains(exitOffset)) { exitPoints.add(exitOffset); } } } @Override public Object getResult() { return null; } }; depthFirstSearch(flow, visitor, start, end); return exitStatements; } private static void processGoto(ControlFlow flow, int start, int end, IntArrayList exitPoints, Collection<PsiStatement> exitStatements, BranchingInstruction instruction, Class[] classesFilter, final PsiStatement statement) { if (statement == null) return; int gotoOffset = instruction.offset; if (start > gotoOffset || gotoOffset >= end || isElementOfClass(statement, classesFilter)) { // process chain of goto's gotoOffset = promoteThroughGotoChain(flow, gotoOffset); if (!exitPoints.contains(gotoOffset) && (gotoOffset >= end || gotoOffset < start)) { exitPoints.add(gotoOffset); } if (gotoOffset >= end || gotoOffset < start) { processGotoStatement(classesFilter, exitStatements, statement); } else { boolean isReturn = instruction instanceof GoToInstruction && ((GoToInstruction)instruction).isReturn; final Instruction gotoInstruction = flow.getInstructions().get(gotoOffset); isReturn |= gotoInstruction instanceof GoToInstruction && ((GoToInstruction)gotoInstruction).isReturn; if (isReturn) { processGotoStatement(classesFilter, exitStatements, statement); } } } } private static void processGotoStatement(Class[] classesFilter, Collection<PsiStatement> exitStatements, PsiStatement statement) { if (statement != null && isElementOfClass(statement, classesFilter)) { exitStatements.add(statement); } } private static boolean isElementOfClass(PsiElement element, Class[] classesFilter) { if (classesFilter == null) return true; for (Class aClassesFilter : classesFilter) { if (ReflectionCache.isAssignable(aClassesFilter, element.getClass())) { return true; } } return false; } private static int promoteThroughGotoChain(ControlFlow flow, int offset) { List<Instruction> instructions = flow.getInstructions(); while (true) { if (offset >= instructions.size()) break; Instruction instruction = instructions.get(offset); if (!(instruction instanceof GoToInstruction) || ((GoToInstruction)instruction).isReturn) break; offset = ((BranchingInstruction)instruction).offset; } return offset; } public static final Class[] DEFAULT_EXIT_STATEMENTS_CLASSES = new Class[]{PsiReturnStatement.class, PsiBreakStatement.class, PsiContinueStatement.class}; private static PsiStatement findStatement(ControlFlow flow, int offset) { PsiElement element = flow.getElement(offset); return PsiTreeUtil.getParentOfType(element, PsiStatement.class, false); } public static PsiElement findCodeFragment(PsiElement element) { PsiElement codeFragment = element; PsiElement parent = codeFragment.getParent(); while (parent != null) { if (parent instanceof PsiDirectory || parent instanceof PsiMethod || parent instanceof PsiField || parent instanceof PsiClassInitializer || parent instanceof DummyHolder || parent instanceof PsiLambdaExpression) { break; } codeFragment = parent; parent = parent.getParent(); } return codeFragment; } private static boolean checkReferenceExpressionScope(final PsiReferenceExpression ref, @NotNull PsiElement targetClassMember) { final JavaResolveResult resolveResult = ref.advancedResolve(false); final PsiElement def = resolveResult.getElement(); if (def != null) { PsiElement parent = def.getParent(); PsiElement commonParent = parent == null ? null : PsiTreeUtil.findCommonParent(parent, targetClassMember); if (commonParent == null) { parent = resolveResult.getCurrentFileResolveScope(); } if (parent instanceof PsiClass) { final PsiClass clss = (PsiClass)parent; if (PsiTreeUtil.isAncestor(targetClassMember, clss, false)) return false; PsiClass containingClass = PsiTreeUtil.getParentOfType(ref, PsiClass.class); while (containingClass != null) { if (containingClass.isInheritor(clss, true) && PsiTreeUtil.isAncestor(targetClassMember, containingClass, false)) { return false; } containingClass = containingClass.getContainingClass(); } } } return true; } /** * Checks possibility of extracting code fragment outside containing anonymous (local) class. * Also collects variables to be passed as additional parameters. * * @param array Vector to collect variables to be passed as additional parameters * @param scope scope to be scanned (part of code fragement to be extracted) * @param member member containing the code to be extracted * @param targetClassMember member in target class containing code fragement * @return true if code fragement can be extracted outside */ public static boolean collectOuterLocals(List<PsiVariable> array, PsiElement scope, PsiElement member, PsiElement targetClassMember) { if (scope instanceof PsiMethodCallExpression) { final PsiMethodCallExpression call = (PsiMethodCallExpression)scope; if (!checkReferenceExpressionScope(call.getMethodExpression(), targetClassMember)) { return false; } } else if (scope instanceof PsiReferenceExpression) { if (!checkReferenceExpressionScope((PsiReferenceExpression)scope, targetClassMember)) { return false; } } if (scope instanceof PsiJavaCodeReferenceElement) { final PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)scope; final JavaResolveResult result = ref.advancedResolve(false); final PsiElement refElement = result.getElement(); if (refElement != null) { PsiElement parent = refElement.getParent(); parent = parent != null ? PsiTreeUtil.findCommonParent(parent, member) : null; if (parent == null) { parent = result.getCurrentFileResolveScope(); } if (parent != null && !member.equals(parent)) { // not local in member parent = PsiTreeUtil.findCommonParent(parent, targetClassMember); if (targetClassMember.equals(parent)) { //something in anonymous class if (refElement instanceof PsiVariable) { if (scope instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiReferenceExpression)scope)) { return false; } PsiVariable variable = (PsiVariable)refElement; if (!array.contains(variable)) { array.add(variable); } } else { return false; } } } } } else if (scope instanceof PsiThisExpression) { PsiJavaCodeReferenceElement qualifier = ((PsiThisExpression)scope).getQualifier(); if (qualifier == null) { return false; } } else if (scope instanceof PsiSuperExpression) { if (((PsiSuperExpression)scope).getQualifier() == null) { return false; } } for (PsiElement child = scope.getFirstChild(); child != null; child = child.getNextSibling()) { if (!collectOuterLocals(array, child, member, targetClassMember)) return false; } return true; } /** * @return true if each control flow path results in return statement or exception thrown */ public static boolean returnPresent(final ControlFlow flow) { InstructionClientVisitor<Boolean> visitor = new ReturnPresentClientVisitor(flow); depthFirstSearch(flow, visitor); return visitor.getResult().booleanValue(); } public static boolean processReturns(final ControlFlow flow, final ReturnStatementsVisitor afterVisitor) throws IncorrectOperationException { final ConvertReturnClientVisitor instructionsVisitor = new ConvertReturnClientVisitor(flow, afterVisitor); depthFirstSearch(flow, instructionsVisitor); instructionsVisitor.afterProcessing(); return instructionsVisitor.getResult().booleanValue(); } private static class ConvertReturnClientVisitor extends ReturnPresentClientVisitor { private final List<PsiReturnStatement> myAffectedReturns; private final ReturnStatementsVisitor myVisitor; ConvertReturnClientVisitor(final ControlFlow flow, final ReturnStatementsVisitor visitor) { super(flow); myAffectedReturns = new ArrayList<PsiReturnStatement>(); myVisitor = visitor; } @Override public void visitGoToInstruction(final GoToInstruction instruction, final int offset, final int nextOffset) { super.visitGoToInstruction(instruction, offset, nextOffset); if (instruction.isReturn) { final PsiElement element = myFlow.getElement(offset); if (element instanceof PsiReturnStatement) { final PsiReturnStatement returnStatement = (PsiReturnStatement)element; myAffectedReturns.add(returnStatement); } } } public void afterProcessing() throws IncorrectOperationException { myVisitor.visit(myAffectedReturns); } } private static class ReturnPresentClientVisitor extends InstructionClientVisitor<Boolean> { // false if control flow at this offset terminates either by return called or exception thrown private final boolean[] isNormalCompletion; protected final ControlFlow myFlow; public ReturnPresentClientVisitor(ControlFlow flow) { myFlow = flow; isNormalCompletion = new boolean[myFlow.getSize() + 1]; isNormalCompletion[myFlow.getSize()] = true; } @Override public void visitConditionalThrowToInstruction(ConditionalThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > myFlow.getSize()) nextOffset = myFlow.getSize(); boolean isNormal = instruction.offset == nextOffset && nextOffset != offset + 1 ? !isLeaf(nextOffset) && isNormalCompletion[nextOffset] : isLeaf(nextOffset) || isNormalCompletion[nextOffset]; isNormalCompletion[offset] |= isNormal; } @Override public void visitThrowToInstruction(ThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > myFlow.getSize()) nextOffset = myFlow.getSize(); isNormalCompletion[offset] |= !isLeaf(nextOffset) && isNormalCompletion[nextOffset]; } @Override public void visitGoToInstruction(GoToInstruction instruction, int offset, int nextOffset) { if (nextOffset > myFlow.getSize()) nextOffset = myFlow.getSize(); isNormalCompletion[offset] |= !instruction.isReturn && isNormalCompletion[nextOffset]; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { if (nextOffset > myFlow.getSize()) nextOffset = myFlow.getSize(); boolean isNormal = isLeaf(nextOffset) || isNormalCompletion[nextOffset]; isNormalCompletion[offset] |= isNormal; } @Override public Boolean getResult() { return !isNormalCompletion[0]; } } public static boolean returnPresentBetween(final ControlFlow flow, final int startOffset, final int endOffset) { class MyVisitor extends InstructionClientVisitor<Boolean> { // false if control flow at this offset terminates either by return called or exception thrown final boolean[] isNormalCompletion = new boolean[flow.getSize() + 1]; public MyVisitor() { int i; final int length = flow.getSize(); for (i = 0; i < startOffset; i++) { isNormalCompletion[i] = true; } for (i = endOffset; i <= length; i++) { isNormalCompletion[i] = true; } } @Override public void visitConditionalThrowToInstruction(ConditionalThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; int throwToOffset = instruction.offset; boolean isNormal; if (throwToOffset == nextOffset) { if (throwToOffset <= endOffset) { isNormal = !isLeaf(nextOffset) && isNormalCompletion[nextOffset]; } else { return; } } else { isNormal = isLeaf(nextOffset) || isNormalCompletion[nextOffset]; } isNormalCompletion[offset] |= isNormal; } @Override public void visitThrowToInstruction(ThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; if (nextOffset <= endOffset) { boolean isNormal = !isLeaf(nextOffset) && isNormalCompletion[nextOffset]; isNormalCompletion[offset] |= isNormal; } } @Override public void visitCallInstruction(CallInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; if (nextOffset > endOffset && nextOffset != offset + 1) { return; } boolean isNormal = isNormalCompletion[nextOffset]; isNormalCompletion[offset] |= isNormal; } @Override public void visitGoToInstruction(GoToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; boolean isRethrowFromFinally = instruction instanceof ReturnInstruction && ((ReturnInstruction)instruction).isRethrowFromFinally(); boolean isNormal = !instruction.isReturn && isNormalCompletion[nextOffset] && !isRethrowFromFinally; isNormalCompletion[offset] |= isNormal; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; final boolean isNormal = isLeaf(nextOffset) || isNormalCompletion[nextOffset]; isNormalCompletion[offset] |= isNormal; } @Override public Boolean getResult() { return !isNormalCompletion[startOffset]; } } final MyVisitor visitor = new MyVisitor(); depthFirstSearch(flow, visitor, startOffset, endOffset); return visitor.getResult().booleanValue(); } public static Object[] getAllWorldProblemsAtOnce(final ControlFlow flow) { InstructionClientVisitor[] visitors = new InstructionClientVisitor[]{ new ReturnPresentClientVisitor(flow), new UnreachableStatementClientVisitor(flow), new ReadBeforeWriteClientVisitor(flow, true), new InitializedTwiceClientVisitor(flow, 0), }; CompositeInstructionClientVisitor visitor = new CompositeInstructionClientVisitor(visitors); depthFirstSearch(flow, visitor); return visitor.getResult(); } /** * returns true iff exists controlflow path completing normally, i.e. not resulting in return,break,continue or exception thrown. * In other words, if we add instruction after controlflow specified, it should be reachable */ public static boolean canCompleteNormally(final ControlFlow flow, final int startOffset, final int endOffset) { class MyVisitor extends InstructionClientVisitor<Boolean> { // false if control flow at this offset terminates abruptly final boolean[] canCompleteNormally = new boolean[flow.getSize() + 1]; @Override public void visitConditionalGoToInstruction(ConditionalGoToInstruction instruction, int offset, int nextOffset) { checkInstruction(offset, nextOffset, false); } @Override public void visitGoToInstruction(GoToInstruction instruction, int offset, int nextOffset) { checkInstruction(offset, nextOffset, instruction.isReturn); } private void checkInstruction(int offset, int nextOffset, boolean isReturn) { if (offset > endOffset) return; if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean isNormal = nextOffset <= endOffset && !isReturn && (nextOffset == endOffset || canCompleteNormally[nextOffset]); if (isNormal && nextOffset == endOffset) { PsiElement element = flow.getElement(offset); if (element instanceof PsiBreakStatement || element instanceof PsiContinueStatement) { isNormal = false; } } canCompleteNormally[offset] |= isNormal; } @Override public void visitConditionalThrowToInstruction(ConditionalThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; int throwToOffset = instruction.offset; boolean isNormal; if (throwToOffset == nextOffset) { isNormal = throwToOffset <= endOffset && !isLeaf(nextOffset) && canCompleteNormally[nextOffset]; } else { isNormal = canCompleteNormally[nextOffset]; } canCompleteNormally[offset] |= isNormal; } @Override public void visitThrowToInstruction(ThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; if (nextOffset <= endOffset) { boolean isNormal = !isLeaf(nextOffset) && canCompleteNormally[nextOffset]; canCompleteNormally[offset] |= isNormal; } } @Override public void visitCallInstruction(CallInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; if (nextOffset > endOffset && nextOffset != offset + 1) { return; } boolean isNormal = canCompleteNormally[nextOffset]; canCompleteNormally[offset] |= isNormal; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { checkInstruction(offset, nextOffset, false); } @Override public Boolean getResult() { return canCompleteNormally[startOffset]; } } final MyVisitor visitor = new MyVisitor(); depthFirstSearch(flow, visitor, startOffset, endOffset); return visitor.getResult().booleanValue(); } /** * @return any unreachable statement or null */ public static PsiElement getUnreachableStatement(final ControlFlow flow) { final InstructionClientVisitor<PsiElement> visitor = new UnreachableStatementClientVisitor(flow); depthFirstSearch(flow, visitor); return visitor.getResult(); } private static class UnreachableStatementClientVisitor extends InstructionClientVisitor<PsiElement> { private final ControlFlow myFlow; public UnreachableStatementClientVisitor(ControlFlow flow) { myFlow = flow; } @Override public PsiElement getResult() { for (int i = 0; i < processedInstructions.length; i++) { if (!processedInstructions[i]) { PsiElement element = myFlow.getElement(i); if (element == null || !PsiUtil.isStatement(element)) continue; if (element.getParent() instanceof PsiExpression) continue; // ignore for(;;) statement unreachable update while (element instanceof PsiExpression) { element = element.getParent(); } if (element instanceof PsiStatement && element.getParent() instanceof PsiForStatement && element == ((PsiForStatement)element.getParent()).getUpdate()) { continue; } //filter out generated stmts final int endOffset = myFlow.getEndOffset(element); if (endOffset != i + 1) continue; final int startOffset = myFlow.getStartOffset(element); // this offset actually is a part of reachable statement if (0 <= startOffset && startOffset < processedInstructions.length && processedInstructions[startOffset]) continue; return element; } } return null; } } private static PsiReferenceExpression getEnclosingReferenceExpression(PsiElement element, PsiVariable variable) { final PsiReferenceExpression reference = findReferenceTo(element, variable); if (reference != null) return reference; while (element != null) { if (element instanceof PsiReferenceExpression) { return (PsiReferenceExpression)element; } else if (element instanceof PsiMethod || element instanceof PsiClass) { return null; } element = element.getParent(); } return null; } private static PsiReferenceExpression findReferenceTo(PsiElement element, PsiVariable variable) { if (element instanceof PsiReferenceExpression && ((PsiReferenceExpression)element).resolve() == variable) { return (PsiReferenceExpression)element; } final PsiElement[] children = element.getChildren(); for (PsiElement child : children) { final PsiReferenceExpression reference = findReferenceTo(child, variable); if (reference != null) return reference; } return null; } public static boolean isVariableDefinitelyAssigned(final PsiVariable variable, final ControlFlow flow) { class MyVisitor extends InstructionClientVisitor<Boolean> { // true if from this point below there may be branch with no variable assignment final boolean[] maybeUnassigned = new boolean[flow.getSize() + 1]; { maybeUnassigned[maybeUnassigned.length - 1] = true; } @Override public void visitWriteVariableInstruction(WriteVariableInstruction instruction, int offset, int nextOffset) { if (instruction.variable == variable) { maybeUnassigned[offset] = false; } else { visitInstruction(instruction, offset, nextOffset); } } @Override public void visitConditionalThrowToInstruction(ConditionalThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean unassigned = offset == flow.getSize() - 1 || !isLeaf(nextOffset) && maybeUnassigned[nextOffset]; maybeUnassigned[offset] |= unassigned; } @Override public void visitCallInstruction(CallInstruction instruction, int offset, int nextOffset) { visitInstruction(instruction, offset, nextOffset); // clear return statements after procedure as well for (int i = instruction.procBegin; i < instruction.procEnd + 3; i++) { maybeUnassigned[i] = false; } } @Override public void visitThrowToInstruction(ThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean unassigned = !isLeaf(nextOffset) && maybeUnassigned[nextOffset]; maybeUnassigned[offset] |= unassigned; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean unassigned = isLeaf(nextOffset) || maybeUnassigned[nextOffset]; maybeUnassigned[offset] |= unassigned; } @Override public Boolean getResult() { final int variableDeclarationOffset = flow.getStartOffset(variable.getParent()); return !maybeUnassigned[variableDeclarationOffset > -1 ? variableDeclarationOffset : 0]; } } if (flow.getSize() == 0) return false; MyVisitor visitor = new MyVisitor(); depthFirstSearch(flow, visitor); return visitor.getResult().booleanValue(); } public static boolean isVariableDefinitelyNotAssigned(final PsiVariable variable, final ControlFlow flow) { class MyVisitor extends InstructionClientVisitor<Boolean> { // true if from this point below there may be branch with variable assignment final boolean[] maybeAssigned = new boolean[flow.getSize() + 1]; @Override public void visitWriteVariableInstruction(WriteVariableInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean assigned = instruction.variable == variable || maybeAssigned[nextOffset]; maybeAssigned[offset] |= assigned; } @Override public void visitThrowToInstruction(ThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean assigned = !isLeaf(nextOffset) && maybeAssigned[nextOffset]; maybeAssigned[offset] |= assigned; } @Override public void visitConditionalThrowToInstruction(ConditionalThrowToInstruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); int throwToOffset = instruction.offset; boolean assigned = throwToOffset == nextOffset ? !isLeaf(nextOffset) && maybeAssigned[nextOffset] : maybeAssigned[nextOffset]; maybeAssigned[offset] |= assigned; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); boolean assigned = maybeAssigned[nextOffset]; maybeAssigned[offset] |= assigned; } @Override public Boolean getResult() { return !maybeAssigned[0]; } } MyVisitor visitor = new MyVisitor(); depthFirstSearch(flow, visitor); return visitor.getResult().booleanValue(); } /** * @return min offset after sourceOffset which is definitely reachable from all references */ public static int getMinDefinitelyReachedOffset(final ControlFlow flow, final int sourceOffset, final List references) { class MyVisitor extends InstructionClientVisitor<Integer> { // set of exit posint reached from this offset final TIntHashSet[] exitPoints = new TIntHashSet[flow.getSize()]; @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (exitPoints[offset] == null) { exitPoints[offset] = new TIntHashSet(); } if (isLeaf(nextOffset)) { exitPoints[offset].add(offset); } else { exitPoints[offset].addAll(exitPoints[nextOffset].toArray()); } } @Override public Integer getResult() { int minOffset = flow.getSize(); int maxExitPoints = 0; nextOffset: for (int i = sourceOffset; i < exitPoints.length; i++) { TIntHashSet exitPointSet = exitPoints[i]; final int size = exitPointSet == null ? 0 : exitPointSet.size(); if (size > maxExitPoints) { // this offset should be reachable from all other references for (Object reference : references) { PsiElement element = (PsiElement)reference; final PsiElement statement = PsiUtil.getEnclosingStatement(element); if (statement == null) continue; final int endOffset = flow.getEndOffset(statement); if (endOffset == -1) continue; if (i != endOffset && !isInstructionReachable(flow, i, endOffset)) continue nextOffset; } minOffset = i; maxExitPoints = size; } } return minOffset; } } MyVisitor visitor = new MyVisitor(); depthFirstSearch(flow, visitor); return visitor.getResult().intValue(); } private static void depthFirstSearch(ControlFlow flow, InstructionClientVisitor visitor) { depthFirstSearch(flow, visitor, 0, flow.getSize()); } private static void depthFirstSearch(ControlFlow flow, InstructionClientVisitor visitor, int startOffset, int endOffset) { visitor.processedInstructions = new boolean[endOffset]; internalDepthFirstSearch(flow.getInstructions(), visitor, startOffset, endOffset); } private static void internalDepthFirstSearch(final List<Instruction> instructions, final InstructionClientVisitor clientVisitor, int offset, int endOffset) { final IntArrayList oldOffsets = new IntArrayList(instructions.size() / 2); final IntArrayList newOffsets = new IntArrayList(instructions.size() / 2); oldOffsets.add(offset); newOffsets.add(-1); // we can change instruction internal state here (e.g. CallInstruction.stack) synchronized (instructions) { final IntArrayList currentProcedureReturnOffsets = new IntArrayList(); ControlFlowInstructionVisitor getNextOffsetVisitor = new ControlFlowInstructionVisitor() { @Override public void visitCallInstruction(CallInstruction instruction, int offset, int nextOffset) { instruction.execute(offset + 1); int newOffset = instruction.offset; // 'procedure' pointed by call instruction should be processed regardless of whether it was already visited or not // clear procedure text and return instructions aftewards int i; for (i = instruction.procBegin; i < instruction.procEnd || instructions.get(i) instanceof ReturnInstruction; i++) { clientVisitor.processedInstructions[i] = false; } clientVisitor.procedureEntered(instruction.procBegin, i); oldOffsets.add(offset); newOffsets.add(newOffset); oldOffsets.add(newOffset); newOffsets.add(-1); currentProcedureReturnOffsets.add(offset + 1); } @Override public void visitReturnInstruction(ReturnInstruction instruction, int offset, int nextOffset) { int newOffset = instruction.execute(false); if (newOffset != -1) { oldOffsets.add(offset); newOffsets.add(newOffset); oldOffsets.add(newOffset); newOffsets.add(-1); } } @Override public void visitBranchingInstruction(BranchingInstruction instruction, int offset, int nextOffset) { int newOffset = instruction.offset; oldOffsets.add(offset); newOffsets.add(newOffset); oldOffsets.add(newOffset); newOffsets.add(-1); } @Override public void visitConditionalBranchingInstruction(ConditionalBranchingInstruction instruction, int offset, int nextOffset) { int newOffset = instruction.offset; oldOffsets.add(offset); newOffsets.add(newOffset); oldOffsets.add(offset); newOffsets.add(offset + 1); oldOffsets.add(newOffset); newOffsets.add(-1); oldOffsets.add(offset + 1); newOffsets.add(-1); } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { int newOffset = offset + 1; oldOffsets.add(offset); newOffsets.add(newOffset); oldOffsets.add(newOffset); newOffsets.add(-1); } }; while (!oldOffsets.isEmpty()) { offset = oldOffsets.remove(oldOffsets.size() - 1); int newOffset = newOffsets.remove(newOffsets.size() - 1); if (offset >= endOffset) { continue; } Instruction instruction = instructions.get(offset); if (clientVisitor.processedInstructions[offset]) { if (newOffset != -1) { instruction.accept(clientVisitor, offset, newOffset); } // when traversing call instruction, we have traversed all procedure control flows, so pop return address if (!currentProcedureReturnOffsets.isEmpty() && currentProcedureReturnOffsets.get(currentProcedureReturnOffsets.size() - 1) - 1 == offset) { currentProcedureReturnOffsets.remove(currentProcedureReturnOffsets.size() - 1); } continue; } if (!currentProcedureReturnOffsets.isEmpty()) { int returnOffset = currentProcedureReturnOffsets.get(currentProcedureReturnOffsets.size() - 1); CallInstruction callInstruction = (CallInstruction)instructions.get(returnOffset - 1); // check if we inside procedure but 'return offset' stack is empty, so // we should push back to 'return offset' stack synchronized (callInstruction.stack) { if (callInstruction.procBegin <= offset && offset < callInstruction.procEnd + 2 && (callInstruction.stack.size() == 0 || callInstruction.stack.peekReturnOffset() != returnOffset)) { callInstruction.stack.push(returnOffset, callInstruction); } } } clientVisitor.processedInstructions[offset] = true; instruction.accept(getNextOffsetVisitor, offset, newOffset); } } } private static boolean isInsideReturnStatement(PsiElement element) { while (element instanceof PsiExpression) element = element.getParent(); return element instanceof PsiReturnStatement; } private static class CopyOnWriteList { private final List<VariableInfo> list; public CopyOnWriteList add(VariableInfo value) { CopyOnWriteList newList = new CopyOnWriteList(); List<VariableInfo> list = getList(); for (final VariableInfo variableInfo : list) { if (!value.equals(variableInfo)) { newList.list.add(variableInfo); } } newList.list.add(value); return newList; } public CopyOnWriteList remove(VariableInfo value) { CopyOnWriteList newList = new CopyOnWriteList(); List<VariableInfo> list = getList(); for (final VariableInfo variableInfo : list) { if (!value.equals(variableInfo)) { newList.list.add(variableInfo); } } return newList; } @NotNull public List<VariableInfo> getList() { return list; } public CopyOnWriteList() { this(Collections.<VariableInfo>emptyList()); } public CopyOnWriteList(VariableInfo... infos) { this(Arrays.asList(infos)); } public CopyOnWriteList(Collection<VariableInfo> infos) { list = new LinkedList<VariableInfo>(infos); } public CopyOnWriteList addAll(CopyOnWriteList addList) { CopyOnWriteList newList = new CopyOnWriteList(); List<VariableInfo> list = getList(); for (final VariableInfo variableInfo : list) { newList.list.add(variableInfo); } List<VariableInfo> toAdd = addList.getList(); for (final VariableInfo variableInfo : toAdd) { if (!newList.list.contains(variableInfo)) { // no copy newList.list.add(variableInfo); } } return newList; } public static CopyOnWriteList add(@Nullable CopyOnWriteList list, @NotNull VariableInfo value) { return list == null ? new CopyOnWriteList(value) : list.add(value); } } public static class VariableInfo { private final PsiVariable variable; public final PsiElement expression; public VariableInfo(PsiVariable variable, PsiElement expression) { this.variable = variable; this.expression = expression; } public boolean equals(Object o) { return this == o || o instanceof VariableInfo && variable.equals(((VariableInfo)o).variable); } public int hashCode() { return variable.hashCode(); } } private static void merge(int offset, CopyOnWriteList source, CopyOnWriteList[] target) { if (source != null) { CopyOnWriteList existing = target[offset]; target[offset] = existing == null ? source : existing.addAll(source); } } /** * @return list of PsiReferenceExpression of usages of non-initialized local variables */ public static List<PsiReferenceExpression> getReadBeforeWriteLocals(ControlFlow flow) { final InstructionClientVisitor<List<PsiReferenceExpression>> visitor = new ReadBeforeWriteClientVisitor(flow, true); depthFirstSearch(flow, visitor); return visitor.getResult(); } public static List<PsiReferenceExpression> getReadBeforeWrite(ControlFlow flow) { final InstructionClientVisitor<List<PsiReferenceExpression>> visitor = new ReadBeforeWriteClientVisitor(flow, false); depthFirstSearch(flow, visitor); return visitor.getResult(); } private static class ReadBeforeWriteClientVisitor extends InstructionClientVisitor<List<PsiReferenceExpression>> { // map of variable->PsiReferenceExpressions for all read before written variables for this point and below in control flow private final CopyOnWriteList[] readVariables; private final ControlFlow myFlow; private boolean localVariablesOnly; public ReadBeforeWriteClientVisitor(ControlFlow flow, boolean localVariablesOnly) { myFlow = flow; this.localVariablesOnly = localVariablesOnly; readVariables = new CopyOnWriteList[myFlow.getSize() + 1]; } @Override public void visitReadVariableInstruction(ReadVariableInstruction instruction, int offset, int nextOffset) { CopyOnWriteList readVars = readVariables[Math.min(nextOffset, myFlow.getSize())]; final PsiVariable variable = instruction.variable; if (!localVariablesOnly || !isMethodParameter(variable)) { final PsiReferenceExpression expression = getEnclosingReferenceExpression(myFlow.getElement(offset), variable); if (expression != null) { readVars = CopyOnWriteList.add(readVars, new VariableInfo(variable, expression)); } } merge(offset, readVars, readVariables); } @Override public void visitWriteVariableInstruction(WriteVariableInstruction instruction, int offset, int nextOffset) { CopyOnWriteList readVars = readVariables[Math.min(nextOffset, myFlow.getSize())]; if (readVars == null) return; final PsiVariable variable = instruction.variable; if (!localVariablesOnly || !isMethodParameter(variable)) { readVars = readVars.remove(new VariableInfo(variable, null)); } merge(offset, readVars, readVariables); } private static boolean isMethodParameter(@NotNull PsiVariable variable) { if (variable instanceof PsiParameter) { final PsiParameter parameter = (PsiParameter)variable; return !(parameter.getDeclarationScope() instanceof PsiForeachStatement); } return false; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { merge(offset, readVariables[Math.min(nextOffset, myFlow.getSize())], readVariables); } @Override public void visitCallInstruction(CallInstruction instruction, int offset, int nextOffset) { visitInstruction(instruction, offset, nextOffset); for (int i = instruction.procBegin; i <= instruction.procEnd; i++) { readVariables[i] = null; } } @Override public List<PsiReferenceExpression> getResult() { final CopyOnWriteList topReadVariables = readVariables[0]; if (topReadVariables == null) return Collections.emptyList(); final List<PsiReferenceExpression> result = new ArrayList<PsiReferenceExpression>(); List<VariableInfo> list = topReadVariables.getList(); for (final VariableInfo variableInfo : list) { result.add((PsiReferenceExpression)variableInfo.expression); } return result; } } public static final int NORMAL_COMPLETION_REASON = 1; public static final int RETURN_COMPLETION_REASON = 2; /** * return reasons.normalCompletion when block can complete normally * reasons.returnCalled when block can complete abruptly because of return statement executed */ public static int getCompletionReasons(final ControlFlow flow, final int offset, final int endOffset) { class MyVisitor extends InstructionClientVisitor<Integer> { final boolean[] normalCompletion = new boolean[endOffset]; final boolean[] returnCalled = new boolean[endOffset]; @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { boolean ret = nextOffset < endOffset && returnCalled[nextOffset]; boolean normal = nextOffset < endOffset && normalCompletion[nextOffset]; final PsiElement element = flow.getElement(offset); boolean goToReturn = instruction instanceof GoToInstruction && ((GoToInstruction)instruction).isReturn; if (goToReturn || isInsideReturnStatement(element)) { ret = true; } else if (instruction instanceof ConditionalThrowToInstruction) { final int throwOffset = ((ConditionalThrowToInstruction)instruction).offset; boolean normalWhenThrow = throwOffset < endOffset && normalCompletion[throwOffset]; boolean normalWhenNotThrow = offset == endOffset - 1 || normalCompletion[offset + 1]; normal = normalWhenThrow || normalWhenNotThrow; } else if (!(instruction instanceof ThrowToInstruction) && nextOffset >= endOffset) { normal = true; } returnCalled[offset] |= ret; normalCompletion[offset] |= normal; } @Override public Integer getResult() { return (returnCalled[offset] ? RETURN_COMPLETION_REASON : 0) | (normalCompletion[offset] ? NORMAL_COMPLETION_REASON : 0); } } MyVisitor visitor = new MyVisitor(); depthFirstSearch(flow, visitor, offset, endOffset); return visitor.getResult().intValue(); } @NotNull public static Collection<VariableInfo> getInitializedTwice(@NotNull ControlFlow flow) { return getInitializedTwice(flow, 0, flow.getSize()); } @NotNull public static Collection<VariableInfo> getInitializedTwice(@NotNull ControlFlow flow, int startOffset, int endOffset) { InitializedTwiceClientVisitor visitor = new InitializedTwiceClientVisitor(flow, startOffset); depthFirstSearch(flow, visitor, startOffset, endOffset); return visitor.getResult(); } private static class InitializedTwiceClientVisitor extends InstructionClientVisitor<Collection<VariableInfo>> { // map of variable->PsiReferenceExpressions for all read and not written variables for this point and below in control flow private final CopyOnWriteList[] writtenVariables; private final CopyOnWriteList[] writtenTwiceVariables; private final ControlFlow myFlow; private final int myStartOffset; public InitializedTwiceClientVisitor(@NotNull ControlFlow flow, final int startOffset) { myFlow = flow; myStartOffset = startOffset; writtenVariables = new CopyOnWriteList[myFlow.getSize() + 1]; writtenTwiceVariables = new CopyOnWriteList[myFlow.getSize() + 1]; } @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { final int safeNextOffset = Math.min(nextOffset, myFlow.getSize()); CopyOnWriteList writeVars = writtenVariables[safeNextOffset]; CopyOnWriteList writeTwiceVars = writtenTwiceVariables[safeNextOffset]; if (instruction instanceof WriteVariableInstruction) { final PsiVariable variable = ((WriteVariableInstruction)instruction).variable; final PsiElement latestWriteVarExpression = getLatestWriteVarExpression(writeVars, variable); if (latestWriteVarExpression == null) { final PsiElement expression = getExpression(myFlow.getElement(offset)); writeVars = CopyOnWriteList.add(writeVars, new VariableInfo(variable, expression)); } else { writeTwiceVars = CopyOnWriteList.add(writeTwiceVars, new VariableInfo(variable, latestWriteVarExpression)); } } merge(offset, writeVars, writtenVariables); merge(offset, writeTwiceVars, writtenTwiceVariables); } @Nullable private static PsiElement getExpression(@NotNull PsiElement element) { if (element instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)element).getLExpression() instanceof PsiReferenceExpression) { return ((PsiAssignmentExpression)element).getLExpression(); } else if (element instanceof PsiPostfixExpression) { return ((PsiPostfixExpression)element).getOperand(); } else if (element instanceof PsiPrefixExpression) { return ((PsiPrefixExpression)element).getOperand(); } else if (element instanceof PsiDeclarationStatement) { //should not happen return element; } return null; } @Nullable private static PsiElement getLatestWriteVarExpression(@Nullable CopyOnWriteList writeVars, @Nullable PsiVariable variable) { if (writeVars == null) return null; for (final VariableInfo variableInfo : writeVars.getList()) { if (variableInfo.variable == variable) { return variableInfo.expression; } } return null; } @Override @NotNull public Collection<VariableInfo> getResult() { final CopyOnWriteList writtenTwiceVariable = writtenTwiceVariables[myStartOffset]; if (writtenTwiceVariable == null) return Collections.emptyList(); return writtenTwiceVariable.getList(); } } /** * @return true if instruction at 'instructionOffset' is reachable from offset 'startOffset' */ public static boolean isInstructionReachable(final ControlFlow flow, final int instructionOffset, final int startOffset) { class MyVisitor extends InstructionClientVisitor<Boolean> { boolean reachable; @Override public void visitInstruction(Instruction instruction, int offset, int nextOffset) { if (nextOffset == instructionOffset) reachable = true; } @Override public Boolean getResult() { return reachable; } } MyVisitor visitor = new MyVisitor(); depthFirstSearch(flow, visitor, startOffset, flow.getSize()); return visitor.getResult().booleanValue(); } public static boolean isVariableAssignedInLoop(PsiReferenceExpression expression, PsiElement resolved) { if (!(expression.getParent() instanceof PsiAssignmentExpression) || ((PsiAssignmentExpression)expression.getParent()).getLExpression() != expression) { return false; } PsiExpression qualifier = expression.getQualifierExpression(); if (qualifier != null && !(qualifier instanceof PsiThisExpression)) return false; if (!(resolved instanceof PsiVariable)) return false; PsiVariable variable = (PsiVariable)resolved; final PsiElement codeBlock = PsiUtil.getVariableCodeBlock(variable, expression); if (codeBlock == null) return false; final ControlFlow flow; try { flow = ControlFlowFactory.getInstance(codeBlock.getProject()).getControlFlow(codeBlock, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); } catch (AnalysisCanceledException e) { return false; } final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expression.getParent(); int startOffset = flow.getStartOffset(assignmentExpression); return startOffset != -1 && isInstructionReachable(flow, startOffset, startOffset); } }