| /* |
| * Copyright 2007-2008 Dave Griffith |
| * |
| * 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 org.jetbrains.plugins.groovy.codeInspection.utils; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.CachedValueProvider; |
| import com.intellij.psi.util.CachedValuesManager; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.HashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; |
| import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; |
| import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrCondition; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.*; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.*; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseSection; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrUnaryExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.AfterCallInstruction; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ReadWriteVariableInstruction; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ControlFlowBuilder; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.IfEndInstruction; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.MaybeReturnInstruction; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ThrowingInstruction; |
| import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAEngine; |
| import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DfaInstance; |
| import org.jetbrains.plugins.groovy.lang.psi.dataFlow.Semilattice; |
| import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; |
| |
| import java.util.*; |
| |
| @SuppressWarnings({"OverlyComplexClass"}) |
| public class ControlFlowUtils { |
| private static final Logger LOG = Logger.getInstance(ControlFlowUtils.class); |
| |
| public static boolean statementMayCompleteNormally(@Nullable GrStatement statement) { |
| if (statement == null) { |
| return true; |
| } |
| if (statement instanceof GrBreakStatement || |
| statement instanceof GrContinueStatement || |
| statement instanceof GrReturnStatement || |
| statement instanceof GrThrowStatement) { |
| return false; |
| } |
| else if (statement instanceof GrForStatement) { |
| return forStatementMayReturnNormally((GrForStatement)statement); |
| } |
| else if (statement instanceof GrWhileStatement) { |
| return whileStatementMayReturnNormally((GrWhileStatement)statement); |
| } |
| else if (statement instanceof GrBlockStatement) { |
| return blockMayCompleteNormally((GrBlockStatement)statement); |
| } |
| else if (statement instanceof GrSynchronizedStatement) { |
| final GrSynchronizedStatement syncStatement = (GrSynchronizedStatement)statement; |
| return openBlockMayCompleteNormally(syncStatement.getBody()); |
| } |
| else if (statement instanceof GrLabeledStatement) { |
| return labeledStatementMayCompleteNormally((GrLabeledStatement)statement); |
| } |
| else if (statement instanceof GrIfStatement) { |
| return ifStatementMayReturnNormally((GrIfStatement)statement); |
| } |
| else if (statement instanceof GrTryCatchStatement) { |
| return tryStatementMayReturnNormally((GrTryCatchStatement)statement); |
| } |
| else if (statement instanceof GrSwitchStatement) { |
| return switchStatementMayReturnNormally((GrSwitchStatement)statement); |
| } |
| // other statement type |
| else { |
| return true; |
| } |
| } |
| |
| private static boolean whileStatementMayReturnNormally(@NotNull GrWhileStatement loopStatement) { |
| final GrCondition test = loopStatement.getCondition(); |
| return !BoolUtils.isTrue(test) || statementIsBreakTarget(loopStatement); |
| } |
| |
| private static boolean forStatementMayReturnNormally(@NotNull GrForStatement loopStatement) { |
| return true; |
| } |
| |
| private static boolean switchStatementMayReturnNormally(@NotNull GrSwitchStatement switchStatement) { |
| if (statementIsBreakTarget(switchStatement)) { |
| return true; |
| } |
| |
| final GrCaseSection[] caseClauses = switchStatement.getCaseSections(); |
| |
| if (ContainerUtil.find(caseClauses, new Condition<GrCaseSection>() { |
| @Override |
| public boolean value(GrCaseSection section) { |
| return section.isDefault(); |
| } |
| }) == null) { |
| return true; |
| } |
| |
| final GrCaseSection lastClause = caseClauses[caseClauses.length - 1]; |
| final GrStatement[] statements = lastClause.getStatements(); |
| if (statements.length == 0) { |
| return true; |
| } |
| return statementMayCompleteNormally(statements[statements.length - 1]); |
| } |
| |
| private static boolean tryStatementMayReturnNormally(@NotNull GrTryCatchStatement tryStatement) { |
| final GrFinallyClause finallyBlock = tryStatement.getFinallyClause(); |
| if (finallyBlock != null) { |
| if (!openBlockMayCompleteNormally(finallyBlock.getBody())) { |
| return false; |
| } |
| } |
| final GrOpenBlock tryBlock = tryStatement.getTryBlock(); |
| if (openBlockMayCompleteNormally(tryBlock)) { |
| return true; |
| } |
| |
| for (GrCatchClause catchClause : tryStatement.getCatchClauses()) { |
| if (openBlockMayCompleteNormally(catchClause.getBody())) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private static boolean ifStatementMayReturnNormally(@NotNull GrIfStatement ifStatement) { |
| final GrStatement thenBranch = ifStatement.getThenBranch(); |
| if (statementMayCompleteNormally(thenBranch)) { |
| return true; |
| } |
| final GrStatement elseBranch = ifStatement.getElseBranch(); |
| return elseBranch == null || statementMayCompleteNormally(elseBranch); |
| } |
| |
| private static boolean labeledStatementMayCompleteNormally(@NotNull GrLabeledStatement labeledStatement) { |
| final GrStatement statement = labeledStatement.getStatement(); |
| return statementMayCompleteNormally(statement) || statementIsBreakTarget(statement); |
| } |
| |
| public static boolean blockMayCompleteNormally(@Nullable GrBlockStatement block) { |
| if (block == null) { |
| return true; |
| } |
| final GrStatement[] statements = block.getBlock().getStatements(); |
| for (final GrStatement statement : statements) { |
| if (!statementMayCompleteNormally(statement)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public static boolean openBlockMayCompleteNormally(@Nullable GrOpenBlock block) { |
| if (block == null) { |
| return true; |
| } |
| final GrStatement[] statements = block.getStatements(); |
| for (final GrStatement statement : statements) { |
| if (!statementMayCompleteNormally(statement)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean statementIsBreakTarget(@NotNull GrStatement statement) { |
| final BreakFinder breakFinder = new BreakFinder(statement); |
| statement.accept(breakFinder); |
| return breakFinder.breakFound(); |
| } |
| |
| public static boolean statementContainsReturn(@NotNull GrStatement statement) { |
| final ReturnFinder returnFinder = new ReturnFinder(); |
| statement.accept(returnFinder); |
| return returnFinder.returnFound(); |
| } |
| |
| public static boolean statementIsContinueTarget(@NotNull GrStatement statement) { |
| final ContinueFinder continueFinder = new ContinueFinder(statement); |
| statement.accept(continueFinder); |
| return continueFinder.continueFound(); |
| } |
| |
| public static boolean isInLoop(@NotNull GroovyPsiElement element) { |
| return isInForStatementBody(element) || |
| isInWhileStatementBody(element); |
| } |
| |
| public static boolean isInFinallyBlock(@NotNull GroovyPsiElement element) { |
| final GrFinallyClause containingClause = PsiTreeUtil.getParentOfType(element, GrFinallyClause.class); |
| if (containingClause == null) { |
| return false; |
| } |
| final GrOpenBlock body = containingClause.getBody(); |
| return PsiTreeUtil.isAncestor(body, element, true); |
| } |
| |
| private static boolean isInWhileStatementBody(@NotNull GroovyPsiElement element) { |
| final GrWhileStatement whileStatement = PsiTreeUtil.getParentOfType(element, GrWhileStatement.class); |
| if (whileStatement == null) { |
| return false; |
| } |
| final GrStatement body = whileStatement.getBody(); |
| return PsiTreeUtil.isAncestor(body, element, true); |
| } |
| |
| private static boolean isInForStatementBody(@NotNull GroovyPsiElement element) { |
| final GrForStatement forStatement = PsiTreeUtil.getParentOfType(element, GrForStatement.class); |
| if (forStatement == null) { |
| return false; |
| } |
| final GrStatement body = forStatement.getBody(); |
| return PsiTreeUtil.isAncestor(body, element, true); |
| } |
| |
| |
| public static GrStatement stripBraces(@NotNull GrStatement branch) { |
| if (branch instanceof GrBlockStatement) { |
| final GrBlockStatement block = (GrBlockStatement)branch; |
| final GrStatement[] statements = block.getBlock().getStatements(); |
| if (statements.length == 1) { |
| return statements[0]; |
| } |
| else { |
| return block; |
| } |
| } |
| else { |
| return branch; |
| } |
| } |
| |
| public static boolean statementCompletesWithStatement(@NotNull GrStatement containingStatement,@NotNull GrStatement statement) { |
| GroovyPsiElement statementToCheck = statement; |
| while (true) { |
| if (statementToCheck.equals(containingStatement)) { |
| return true; |
| } |
| final GroovyPsiElement container = getContainingStatement(statementToCheck); |
| if (container == null) { |
| return false; |
| } |
| if (container instanceof GrBlockStatement) { |
| if (!statementIsLastInBlock((GrBlockStatement)container, (GrStatement)statementToCheck)) { |
| return false; |
| } |
| } |
| if (isLoop(container)) { |
| return false; |
| } |
| statementToCheck = container; |
| } |
| } |
| |
| public static boolean blockCompletesWithStatement(@NotNull GrBlockStatement body, @NotNull GrStatement statement) { |
| GrStatement statementToCheck = statement; |
| while (true) { |
| if (statementToCheck == null) { |
| return false; |
| } |
| |
| final PsiElement container = statementToCheck.getParent(); |
| if (container == null) { |
| return false; |
| } |
| |
| if (container instanceof GrLoopStatement) { |
| return false; |
| } |
| else if (container instanceof GrCaseSection) { |
| final GrCaseSection caseSection = (GrCaseSection)container; |
| |
| if (!statementIsLastInBlock(caseSection, statementToCheck)) return false; |
| |
| final PsiElement parent = container.getParent(); |
| assert parent instanceof GrSwitchStatement; |
| final GrSwitchStatement switchStatement = (GrSwitchStatement)parent; |
| |
| final GrCaseSection[] sections = switchStatement.getCaseSections(); |
| if (ArrayUtil.getLastElement(sections) != caseSection) { |
| return false; |
| } |
| } |
| else if (container instanceof GrOpenBlock) { |
| final GrOpenBlock block = (GrOpenBlock)container; |
| |
| if (!statementIsLastInBlock(block, statementToCheck)) return false; |
| final PsiElement parent = block.getParent(); |
| if (parent instanceof GrBlockStatement) { |
| final GrBlockStatement blockStatement = (GrBlockStatement)parent; |
| if (blockStatement == body) return true; |
| } |
| } |
| else if (container instanceof GrClosableBlock) { |
| return false; |
| } |
| |
| statementToCheck = getContainingStatement(statementToCheck); |
| } |
| } |
| |
| public static boolean openBlockCompletesWithStatement(@NotNull GrCodeBlock body, @NotNull GrStatement statement) { |
| GroovyPsiElement elementToCheck = statement; |
| while (true) { |
| if (elementToCheck == null) return false; |
| |
| final GroovyPsiElement container = |
| PsiTreeUtil.getParentOfType(elementToCheck, GrStatement.class, GrCodeBlock.class, GrCaseSection.class); |
| if (container == null) return false; |
| |
| if (isLoop(container)) return false; |
| |
| if (container instanceof GrCaseSection) { |
| final GrSwitchStatement switchStatement = (GrSwitchStatement)container.getParent(); |
| final GrCaseSection[] sections = switchStatement.getCaseSections(); |
| if (container == sections[sections.length - 1]) return false; |
| } |
| |
| if (container instanceof GrCodeBlock) { |
| if (elementToCheck instanceof GrStatement) { |
| final GrCodeBlock codeBlock = (GrCodeBlock)container; |
| if (!statementIsLastInBlock(codeBlock, (GrStatement)elementToCheck)) { |
| return false; |
| } |
| } |
| if (container instanceof GrOpenBlock || container instanceof GrClosableBlock) { |
| if (container.equals(body)) { |
| return true; |
| } |
| elementToCheck = PsiTreeUtil.getParentOfType(container, GrStatement.class); |
| } |
| else { |
| elementToCheck = container; |
| } |
| } |
| else { |
| elementToCheck = container; |
| } |
| } |
| } |
| |
| public static boolean closureCompletesWithStatement(@NotNull GrClosableBlock body,@NotNull GrStatement statement) { |
| GroovyPsiElement statementToCheck = statement; |
| while (true) { |
| if (!(statementToCheck instanceof GrExpression || statementToCheck instanceof GrReturnStatement)) { |
| return false; |
| } |
| final GroovyPsiElement container = getContainingStatementOrBlock(statementToCheck); |
| if (container == null) { |
| return false; |
| } |
| if (isLoop(container)) { |
| return false; |
| } |
| if (container instanceof GrCodeBlock) { |
| if (!statementIsLastInBlock((GrCodeBlock)container, (GrStatement)statementToCheck)) { |
| return false; |
| } |
| if (container.equals(body)) { |
| return true; |
| } |
| statementToCheck = PsiTreeUtil.getParentOfType(container, GrStatement.class); |
| } |
| else { |
| statementToCheck = container; |
| } |
| } |
| } |
| |
| private static boolean isLoop(@NotNull PsiElement element) { |
| return element instanceof GrLoopStatement; |
| } |
| |
| @Nullable |
| private static GrStatement getContainingStatement(@NotNull GroovyPsiElement statement) { |
| return PsiTreeUtil.getParentOfType(statement, GrStatement.class); |
| } |
| |
| @Nullable |
| private static GroovyPsiElement getContainingStatementOrBlock(@NotNull GroovyPsiElement statement) { |
| return PsiTreeUtil.getParentOfType(statement, GrStatement.class, GrCodeBlock.class); |
| } |
| |
| private static boolean statementIsLastInBlock(@NotNull GrBlockStatement block, @NotNull GrStatement statement) { |
| return statementIsLastInBlock(block.getBlock(), statement); |
| } |
| |
| private static boolean statementIsLastInBlock(@NotNull GrStatementOwner block, @NotNull GrStatement statement) { |
| final GrStatement[] statements = block.getStatements(); |
| for (int i = statements.length - 1; i >= 0; i--) { |
| final GrStatement childStatement = statements[i]; |
| if (statement.equals(childStatement)) { |
| return true; |
| } |
| if (!(childStatement instanceof GrReturnStatement)) { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| public static List<GrStatement> collectReturns(@Nullable PsiElement element) { |
| return collectReturns(element, element instanceof GrCodeBlock || element instanceof GroovyFile); |
| } |
| public static List<GrStatement> collectReturns(@Nullable PsiElement element, final boolean allExitPoints) { |
| if (element == null) return Collections.emptyList(); |
| |
| final Instruction[] flow; |
| if (element instanceof GrControlFlowOwner) { |
| flow = ((GrControlFlowOwner)element).getControlFlow(); |
| } |
| else { |
| flow = new ControlFlowBuilder(element.getProject()).buildControlFlow((GroovyPsiElement)element); |
| } |
| return collectReturns(flow, allExitPoints); |
| } |
| |
| public static List<GrStatement> collectReturns(@NotNull Instruction[] flow, final boolean allExitPoints) { |
| boolean[] visited = new boolean[flow.length]; |
| final List<GrStatement> res = new ArrayList<GrStatement>(); |
| visitAllExitPointsInner(flow[flow.length - 1], flow[0], visited, new ExitPointVisitor() { |
| @Override |
| public boolean visitExitPoint(Instruction instruction, @Nullable GrExpression returnValue) { |
| final PsiElement element = instruction.getElement(); |
| if (element instanceof GrReturnStatement || (allExitPoints && instruction instanceof MaybeReturnInstruction)) { |
| res.add((GrStatement)element); |
| } |
| return true; |
| } |
| }); |
| return res; |
| } |
| |
| @Nullable |
| public static GrExpression extractReturnExpression(GrStatement returnStatement) { |
| if (returnStatement instanceof GrReturnStatement) return ((GrReturnStatement)returnStatement).getReturnValue(); |
| if (returnStatement instanceof GrExpression) return (GrExpression)returnStatement; |
| return null; |
| } |
| |
| public static boolean isIncOrDecOperand(GrReferenceExpression referenceExpression) { |
| final PsiElement parent = referenceExpression.getParent(); |
| if (parent instanceof GrUnaryExpression) { |
| final IElementType opType = ((GrUnaryExpression)parent).getOperationTokenType(); |
| return opType == GroovyTokenTypes.mDEC || opType == GroovyTokenTypes.mINC; |
| } |
| |
| return false; |
| } |
| |
| public static String dumpControlFlow(Instruction[] instructions) { |
| StringBuilder builder = new StringBuilder(); |
| for (Instruction instruction : instructions) { |
| builder.append(instruction.toString()).append("\n"); |
| } |
| |
| return builder.toString(); |
| } |
| |
| @Nullable |
| public static ReadWriteVariableInstruction findRWInstruction(final GrReferenceExpression refExpr, final Instruction[] flow) { |
| for (Instruction instruction : flow) { |
| if (instruction instanceof ReadWriteVariableInstruction && instruction.getElement() == refExpr) { |
| return (ReadWriteVariableInstruction)instruction; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static Instruction findNearestInstruction(PsiElement place, Instruction[] flow) { |
| List<Instruction> applicable = new ArrayList<Instruction>(); |
| for (Instruction instruction : flow) { |
| final PsiElement element = instruction.getElement(); |
| if (element == null) continue; |
| |
| if (element == place) return instruction; |
| |
| if (PsiTreeUtil.isAncestor(element, place, true)) { |
| applicable.add(instruction); |
| } |
| } |
| if (applicable.isEmpty()) return null; |
| |
| Collections.sort(applicable, new Comparator<Instruction>() { |
| @Override |
| public int compare(Instruction o1, Instruction o2) { |
| PsiElement e1 = o1.getElement(); |
| PsiElement e2 = o2.getElement(); |
| LOG.assertTrue(e1 != null); |
| LOG.assertTrue(e2 != null); |
| final TextRange t1 = e1.getTextRange(); |
| final TextRange t2 = e2.getTextRange(); |
| final int s1 = t1.getStartOffset(); |
| final int s2 = t2.getStartOffset(); |
| |
| if (s1 == s2) { |
| return t1.getEndOffset() - t2.getEndOffset(); |
| } |
| return s2 - s1; |
| } |
| }); |
| |
| return applicable.get(0); |
| } |
| |
| private static class ReturnFinder extends GroovyRecursiveElementVisitor { |
| private boolean m_found = false; |
| |
| public boolean returnFound() { |
| return m_found; |
| } |
| |
| @Override |
| public void visitReturnStatement( |
| @NotNull GrReturnStatement returnStatement) { |
| if (m_found) { |
| return; |
| } |
| super.visitReturnStatement(returnStatement); |
| m_found = true; |
| } |
| } |
| |
| private static class BreakFinder extends GroovyRecursiveElementVisitor { |
| private boolean m_found = false; |
| private final GrStatement m_target; |
| |
| private BreakFinder(@NotNull GrStatement target) { |
| super(); |
| m_target = target; |
| } |
| |
| public boolean breakFound() { |
| return m_found; |
| } |
| |
| @Override |
| public void visitBreakStatement( |
| @NotNull GrBreakStatement breakStatement) { |
| if (m_found) { |
| return; |
| } |
| super.visitBreakStatement(breakStatement); |
| final GrStatement exitedStatement = breakStatement.findTargetStatement(); |
| if (exitedStatement == null) { |
| return; |
| } |
| if (PsiTreeUtil.isAncestor(exitedStatement, m_target, false)) { |
| m_found = true; |
| } |
| } |
| } |
| |
| private static class ContinueFinder extends GroovyRecursiveElementVisitor { |
| private boolean m_found = false; |
| private final GrStatement m_target; |
| |
| private ContinueFinder(@NotNull GrStatement target) { |
| super(); |
| m_target = target; |
| } |
| |
| public boolean continueFound() { |
| return m_found; |
| } |
| |
| @Override |
| public void visitContinueStatement( |
| @NotNull GrContinueStatement continueStatement) { |
| if (m_found) { |
| return; |
| } |
| super.visitContinueStatement(continueStatement); |
| final GrStatement exitedStatement = |
| continueStatement.findTargetStatement(); |
| if (exitedStatement == null) { |
| return; |
| } |
| if (PsiTreeUtil.isAncestor(exitedStatement, m_target, false)) { |
| m_found = true; |
| } |
| } |
| } |
| |
| |
| public interface ExitPointVisitor { |
| boolean visitExitPoint(Instruction instruction, @Nullable GrExpression returnValue); |
| } |
| |
| public static Set<GrExpression> getAllReturnValues(@NotNull final GrControlFlowOwner block) { |
| return CachedValuesManager.getCachedValue(block, new CachedValueProvider<Set<GrExpression>>() { |
| @Override |
| public Result<Set<GrExpression>> compute() { |
| final Set<GrExpression> result = new HashSet<GrExpression>(); |
| visitAllExitPoints(block, new ExitPointVisitor() { |
| @Override |
| public boolean visitExitPoint(Instruction instruction, @Nullable GrExpression returnValue) { |
| ContainerUtil.addIfNotNull(result, returnValue); |
| return true; |
| } |
| }); |
| return Result.create(result, block); |
| } |
| }); |
| } |
| |
| |
| public static boolean isReturnValue(@NotNull GrExpression expression, @NotNull GrControlFlowOwner flowOwner) { |
| return getAllReturnValues(flowOwner).contains(expression); |
| } |
| |
| public static boolean visitAllExitPoints(@Nullable GrControlFlowOwner block, ExitPointVisitor visitor) { |
| if (block == null) return true; |
| final Instruction[] flow = block.getControlFlow(); |
| boolean[] visited = new boolean[flow.length]; |
| return visitAllExitPointsInner(flow[flow.length - 1], flow[0], visited, visitor); |
| } |
| |
| private static boolean visitAllExitPointsInner(Instruction last, Instruction first, boolean[] visited, ExitPointVisitor visitor) { |
| if (first == last) return true; |
| if (last instanceof AfterCallInstruction) { |
| visited[last.num()] = true; |
| return visitAllExitPointsInner(((AfterCallInstruction)last).myCall, first, visited, visitor); |
| } |
| |
| if (last instanceof MaybeReturnInstruction) { |
| return visitor.visitExitPoint(last, (GrExpression)last.getElement()); |
| } |
| else if (last instanceof IfEndInstruction) { |
| visited[last.num()] = true; |
| for (Instruction instruction : last.allPredecessors()) { |
| if (!visitAllExitPointsInner(instruction, first, visited, visitor)) return false; |
| } |
| return true; |
| } |
| else if (last instanceof ThrowingInstruction) { |
| PsiElement element = last.getElement(); |
| if (!(element instanceof GrThrowStatement || element instanceof GrAssertStatement)) return true; |
| } |
| |
| PsiElement element = last.getElement(); |
| if (element != null) { |
| final GrExpression returnValue; |
| if (element instanceof GrReturnStatement) { |
| returnValue = ((GrReturnStatement)element).getReturnValue(); |
| } |
| else if (element instanceof GrExpression && PsiUtil.isExpressionStatement(element)) { |
| returnValue = (GrExpression)element; |
| } |
| else { |
| returnValue = null; |
| } |
| |
| return visitor.visitExitPoint(last, returnValue); |
| } |
| visited[last.num()] = true; |
| for (Instruction pred : last.allPredecessors()) { |
| if (!visited[pred.num()]) { |
| if (!visitAllExitPointsInner(pred, first, visited, visitor)) return false; |
| } |
| } |
| return true; |
| } |
| |
| @Nullable |
| public static GrControlFlowOwner findControlFlowOwner(PsiElement place) { |
| place = place.getContext(); |
| while (place != null) { |
| if (place instanceof GrControlFlowOwner && ((GrControlFlowOwner)place).isTopControlFlowOwner()) return (GrControlFlowOwner)place; |
| if (place instanceof GrMethod) return ((GrMethod)place).getBlock(); |
| if (place instanceof GrClassInitializer) return ((GrClassInitializer)place).getBlock(); |
| |
| place = place.getContext(); |
| } |
| return null; |
| } |
| |
| /** |
| * searches for next or previous write access to local variable |
| * @param local variable to analyze |
| * @param place place to start searching |
| * @param ahead if true search for next write. if false searches for previous write |
| * @return all write instructions leading to (or preceding) the place |
| */ |
| public static List<ReadWriteVariableInstruction> findAccess(GrVariable local, final PsiElement place, boolean ahead, boolean writeAccessOnly) { |
| LOG.assertTrue(!(local instanceof GrField), local.getClass()); |
| |
| final GrControlFlowOwner owner = findControlFlowOwner(place); |
| assert owner != null; |
| |
| final Instruction cur = findInstruction(place, owner.getControlFlow()); |
| |
| if (cur == null) { |
| throw new IllegalArgumentException("place is not in the flow"); |
| } |
| |
| return findAccess(local, ahead, writeAccessOnly, cur); |
| } |
| |
| public static List<ReadWriteVariableInstruction> findAccess(GrVariable local, boolean ahead, boolean writeAccessOnly, Instruction cur) { |
| String name = local.getName(); |
| |
| final ArrayList<ReadWriteVariableInstruction> result = new ArrayList<ReadWriteVariableInstruction>(); |
| final HashSet<Instruction> visited = new HashSet<Instruction>(); |
| |
| visited.add(cur); |
| |
| Queue<Instruction> queue = new ArrayDeque<Instruction>(); |
| |
| for (Instruction i : ahead ? cur.allSuccessors() : cur.allPredecessors()) { |
| if (visited.add(i)) { |
| queue.add(i); |
| } |
| } |
| |
| while (true) { |
| Instruction instruction = queue.poll(); |
| if (instruction == null) break; |
| |
| if (instruction instanceof ReadWriteVariableInstruction) { |
| ReadWriteVariableInstruction rw = (ReadWriteVariableInstruction)instruction; |
| if (name.equals(rw.getVariableName())) { |
| if (rw.isWrite()) { |
| result.add(rw); |
| continue; |
| } |
| |
| if (!writeAccessOnly) { |
| result.add(rw); |
| } |
| } |
| } |
| |
| for (Instruction i : ahead ? instruction.allSuccessors() : instruction.allPredecessors()) { |
| if (visited.add(i)) { |
| queue.add(i); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| @Nullable |
| public static Instruction findInstruction(final PsiElement place, Instruction[] controlFlow) { |
| return ContainerUtil.find(controlFlow, new Condition<Instruction>() { |
| @Override |
| public boolean value(Instruction instruction) { |
| return instruction.getElement() == place; |
| } |
| }); |
| } |
| |
| public static List<Instruction> findAllInstructions(final PsiElement place, Instruction[] controlFlow) { |
| return ContainerUtil.findAll(controlFlow, new Condition<Instruction>() { |
| @Override |
| public boolean value(Instruction instruction) { |
| return instruction.getElement() == place; |
| } |
| }); |
| } |
| |
| @NotNull |
| public static ArrayList<BitSet> inferWriteAccessMap(final Instruction[] flow, final GrVariable var) { |
| |
| final Semilattice<BitSet> sem = new Semilattice<BitSet>() { |
| @Override |
| public BitSet join(ArrayList<BitSet> ins) { |
| BitSet result = new BitSet(flow.length); |
| for (BitSet set : ins) { |
| result.or(set); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean eq(BitSet e1, BitSet e2) { |
| return e1.equals(e2); |
| } |
| }; |
| |
| DfaInstance<BitSet> dfa = new DfaInstance<BitSet>() { |
| @Override |
| public void fun(BitSet bitSet, Instruction instruction) { |
| if (!(instruction instanceof ReadWriteVariableInstruction)) return; |
| if (!((ReadWriteVariableInstruction)instruction).isWrite()) return; |
| |
| final PsiElement element = instruction.getElement(); |
| if (element instanceof GrVariable && element != var) return; |
| if (element instanceof GrReferenceExpression) { |
| final GrReferenceExpression ref = (GrReferenceExpression)element; |
| if (ref.isQualified() || ref.resolve() != var) { |
| return; |
| } |
| } |
| if (!((ReadWriteVariableInstruction)instruction).getVariableName().equals(var.getName())) { |
| return; |
| } |
| |
| bitSet.clear(); |
| bitSet.set(instruction.num()); |
| } |
| |
| @NotNull |
| @Override |
| public BitSet initial() { |
| return new BitSet(flow.length); |
| } |
| |
| @Override |
| public boolean isForward() { |
| return true; |
| } |
| }; |
| |
| return new DFAEngine<BitSet>(flow, dfa, sem).performForceDFA(); |
| } |
| |
| } |