| /* |
| * Copyright 2000-2014 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.ReflectionUtil; |
| 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>(); |
| if (start < 0) return array; |
| 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 (ReflectionUtil.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 = |
| {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); |
| } |
| |
| @NotNull |
| public static PsiElement findCodeFragment(@NotNull 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 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 final 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); |
| } |
| } |