| /* |
| * 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.codeInsight.ExceptionUtil; |
| import com.intellij.codeInsight.daemon.JavaErrorMessages; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProgressIndicatorProvider; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.psi.*; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.util.containers.Stack; |
| import gnu.trove.THashMap; |
| import gnu.trove.TIntArrayList; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| class ControlFlowAnalyzer extends JavaElementVisitor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.controlFlow.ControlFlowAnalyzer"); |
| |
| private final PsiElement myCodeFragment; |
| private final ControlFlowPolicy myPolicy; |
| |
| private ControlFlowImpl myCurrentFlow; |
| private final ControlFlowStack myStack = new ControlFlowStack(); |
| private final Stack<PsiParameter> myCatchParameters = new Stack<PsiParameter>();// stack of PsiParameter for catch |
| private final Stack<PsiElement> myCatchBlocks = new Stack<PsiElement>(); |
| |
| private final Stack<PsiElement> myFinallyBlocks = new Stack<PsiElement>(); |
| private final Stack<PsiElement> myUnhandledExceptionCatchBlocks = new Stack<PsiElement>(); |
| |
| // element to jump to from inner (sub)expression in "jump to begin" situation. |
| // E.g. we should jump to "then" branch if condition expression evaluated to true inside if statement |
| private final StatementStack myStartStatementStack = new StatementStack(); |
| // element to jump to from inner (sub)expression in "jump to end" situation. |
| // E.g. we should jump to "else" branch if condition expression evaluated to false inside if statement |
| private final StatementStack myEndStatementStack = new StatementStack(); |
| |
| private final Stack<BranchingInstruction.Role> myStartJumpRoles = new Stack<BranchingInstruction.Role>(); |
| private final Stack<BranchingInstruction.Role> myEndJumpRoles = new Stack<BranchingInstruction.Role>(); |
| |
| // true if generate direct jumps for short-circuited operations, |
| // e.g. jump to else branch of if statement after each calculation of '&&' operand in condition |
| private final boolean myEnabledShortCircuit; |
| // true if evaluate constant expression inside 'if' statement condition and alter control flow accordingly |
| // in case of unreachable statement analysis must be false |
| private final boolean myEvaluateConstantIfCondition; |
| private final boolean myAssignmentTargetsAreElements; |
| |
| private final Stack<TIntArrayList> intArrayPool = new Stack<TIntArrayList>(); |
| // map: PsiElement element -> TIntArrayList instructionOffsetsToPatch with getStartOffset(element) |
| private final Map<PsiElement,TIntArrayList> offsetsAddElementStart = new THashMap<PsiElement, TIntArrayList>(); |
| // map: PsiElement element -> TIntArrayList instructionOffsetsToPatch with getEndOffset(element) |
| private final Map<PsiElement,TIntArrayList> offsetsAddElementEnd = new THashMap<PsiElement, TIntArrayList>(); |
| private final ControlFlowFactory myControlFlowFactory; |
| private final Map<PsiElement, ControlFlowSubRange> mySubRanges = new THashMap<PsiElement, ControlFlowSubRange>(); |
| private final PsiConstantEvaluationHelper myConstantEvaluationHelper; |
| |
| ControlFlowAnalyzer(@NotNull PsiElement codeFragment, |
| @NotNull ControlFlowPolicy policy, |
| boolean enabledShortCircuit, |
| boolean evaluateConstantIfCondition) { |
| this(codeFragment, policy, enabledShortCircuit, evaluateConstantIfCondition, false); |
| } |
| |
| private ControlFlowAnalyzer(@NotNull PsiElement codeFragment, |
| @NotNull ControlFlowPolicy policy, |
| boolean enabledShortCircuit, |
| boolean evaluateConstantIfCondition, |
| boolean assignmentTargetsAreElements) { |
| myCodeFragment = codeFragment; |
| myPolicy = policy; |
| myEnabledShortCircuit = enabledShortCircuit; |
| myEvaluateConstantIfCondition = evaluateConstantIfCondition; |
| myAssignmentTargetsAreElements = assignmentTargetsAreElements; |
| Project project = codeFragment.getProject(); |
| myControlFlowFactory = ControlFlowFactory.getInstance(project); |
| myConstantEvaluationHelper = JavaPsiFacade.getInstance(project).getConstantEvaluationHelper(); |
| } |
| |
| @NotNull |
| ControlFlow buildControlFlow() throws AnalysisCanceledException { |
| // push guard outer statement offsets in case when nested expression is incorrect |
| myStartJumpRoles.push(BranchingInstruction.Role.END); |
| myEndJumpRoles.push(BranchingInstruction.Role.END); |
| |
| myCurrentFlow = new ControlFlowImpl(); |
| |
| // guard elements |
| myStartStatementStack.pushStatement(myCodeFragment, false); |
| myEndStatementStack.pushStatement(myCodeFragment, false); |
| |
| try { |
| myCodeFragment.accept(this); |
| cleanup(); |
| } |
| catch (AnalysisCanceledSoftException e) { |
| throw new AnalysisCanceledException(e.getErrorElement()); |
| } |
| |
| return myCurrentFlow; |
| } |
| |
| private static class StatementStack { |
| private final Stack<PsiElement> myStatements = new Stack<PsiElement>(); |
| private final TIntArrayList myAtStart = new TIntArrayList(); |
| |
| private void popStatement() { |
| myAtStart.remove(myAtStart.size() - 1); |
| myStatements.pop(); |
| } |
| |
| private PsiElement peekElement() { |
| return myStatements.peek(); |
| } |
| |
| private boolean peekAtStart() { |
| return myAtStart.get(myAtStart.size() - 1) == 1; |
| } |
| |
| private void pushStatement(PsiElement statement, boolean atStart) { |
| myStatements.push(statement); |
| myAtStart.add(atStart ? 1 : 0); |
| } |
| } |
| |
| private TIntArrayList getEmptyIntArray() { |
| if (intArrayPool.isEmpty()) { |
| return new TIntArrayList(1); |
| } |
| TIntArrayList list = intArrayPool.pop(); |
| list.clear(); |
| return list; |
| } |
| |
| private void poolIntArray(TIntArrayList list) { |
| intArrayPool.add(list); |
| } |
| |
| // patch instruction currently added to control flow so that its jump offset corrected on getStartOffset(element) or getEndOffset(element) |
| // when corresponding element offset become available |
| private void addElementOffsetLater(PsiElement element, boolean atStart) { |
| Map<PsiElement,TIntArrayList> offsetsAddElement = atStart ? offsetsAddElementStart : offsetsAddElementEnd; |
| TIntArrayList offsets = offsetsAddElement.get(element); |
| if (offsets == null) { |
| offsets = getEmptyIntArray(); |
| offsetsAddElement.put(element, offsets); |
| } |
| int offset = myCurrentFlow.getSize() - 1; |
| offsets.add(offset); |
| if (myCurrentFlow.getEndOffset(element) != -1) { |
| patchInstructionOffsets(element); |
| } |
| } |
| |
| |
| private void patchInstructionOffsets(PsiElement element) { |
| patchInstructionOffsets(offsetsAddElementStart.get(element), myCurrentFlow.getStartOffset(element)); |
| offsetsAddElementStart.put(element, null); |
| patchInstructionOffsets(offsetsAddElementEnd.get(element), myCurrentFlow.getEndOffset(element)); |
| offsetsAddElementEnd.put(element, null); |
| } |
| |
| private void patchInstructionOffsets(TIntArrayList offsets, int add) { |
| if (offsets == null) return; |
| for (int i = 0; i < offsets.size(); i++) { |
| int offset = offsets.get(i); |
| BranchingInstruction instruction = (BranchingInstruction)myCurrentFlow.getInstructions().get(offset); |
| instruction.offset += add; |
| LOG.assertTrue(instruction.offset >= 0); |
| } |
| poolIntArray(offsets); |
| } |
| |
| private void cleanup() { |
| // make all non patched goto instructions jump to the end of control flow |
| for (TIntArrayList offsets : offsetsAddElementStart.values()) { |
| patchInstructionOffsets(offsets, myCurrentFlow.getEndOffset(myCodeFragment)); |
| } |
| for (TIntArrayList offsets : offsetsAddElementEnd.values()) { |
| patchInstructionOffsets(offsets, myCurrentFlow.getEndOffset(myCodeFragment)); |
| } |
| |
| // register all sub ranges |
| for (Map.Entry<PsiElement, ControlFlowSubRange> entry : mySubRanges.entrySet()) { |
| ProgressIndicatorProvider.checkCanceled(); |
| ControlFlowSubRange subRange = entry.getValue(); |
| PsiElement element = entry.getKey(); |
| myControlFlowFactory.registerSubRange(element, subRange, myEvaluateConstantIfCondition, myEnabledShortCircuit, myPolicy); |
| } |
| } |
| |
| private void startElement(PsiElement element) { |
| for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { |
| ProgressIndicatorProvider.checkCanceled(); |
| if (child instanceof PsiErrorElement && !Comparing.strEqual(((PsiErrorElement)child).getErrorDescription(), JavaErrorMessages.message("expected.semicolon"))) { |
| // do not perform control flow analysis for incomplete code |
| throw new AnalysisCanceledSoftException(element); |
| } |
| } |
| ProgressIndicatorProvider.checkCanceled(); |
| myCurrentFlow.startElement(element); |
| |
| generateUncheckedExceptionJumpsIfNeeded(element, true); |
| } |
| |
| private void generateUncheckedExceptionJumpsIfNeeded(PsiElement element, boolean atStart) { |
| // optimization: reduce number of instructions |
| boolean isGeneratingStatement = element instanceof PsiStatement && !(element instanceof PsiSwitchLabelStatement); |
| boolean isGeneratingCodeBlock = element instanceof PsiCodeBlock && !(element.getParent() instanceof PsiSwitchStatement); |
| if (isGeneratingStatement || isGeneratingCodeBlock) { |
| generateUncheckedExceptionJumps(element, atStart); |
| } |
| } |
| |
| private void finishElement(PsiElement element) { |
| generateUncheckedExceptionJumpsIfNeeded(element, false); |
| |
| myCurrentFlow.finishElement(element); |
| patchInstructionOffsets(element); |
| } |
| |
| |
| private void generateUncheckedExceptionJumps(PsiElement element, boolean atStart) { |
| // optimization: if we just generated all necessary jumps, do not generate it once again |
| if (atStart |
| && element instanceof PsiStatement |
| && element.getParent() instanceof PsiCodeBlock && element.getPrevSibling() != null) { |
| return; |
| } |
| |
| for (int i = myUnhandledExceptionCatchBlocks.size() - 1; i >= 0; i--) { |
| ProgressIndicatorProvider.checkCanceled(); |
| PsiElement block = myUnhandledExceptionCatchBlocks.get(i); |
| // cannot jump to outer catch blocks (belonging to outer try stmt) if current try{} has finally block |
| if (block == null) { |
| if (!myFinallyBlocks.isEmpty()) { |
| break; |
| } |
| else { |
| continue; |
| } |
| } |
| ConditionalThrowToInstruction throwToInstruction = new ConditionalThrowToInstruction(-1); // -1 for init parameter |
| myCurrentFlow.addInstruction(throwToInstruction); |
| if (!patchUncheckedThrowInstructionIfInsideFinally(throwToInstruction, element, block)) { |
| addElementOffsetLater(block, true); |
| } |
| } |
| |
| // generate jump to the top finally block |
| if (!myFinallyBlocks.isEmpty()) { |
| final PsiElement finallyBlock = myFinallyBlocks.peek(); |
| ConditionalThrowToInstruction throwToInstruction = new ConditionalThrowToInstruction(-2); |
| myCurrentFlow.addInstruction(throwToInstruction); |
| if (!patchUncheckedThrowInstructionIfInsideFinally(throwToInstruction, element, finallyBlock)) { |
| addElementOffsetLater(finallyBlock, true); |
| } |
| } |
| } |
| |
| private void generateCheckedExceptionJumps(PsiElement element) { |
| //generate jumps to all handled exception handlers |
| Collection<PsiClassType> unhandledExceptions = ExceptionUtil.collectUnhandledExceptions(element, element.getParent()); |
| for (PsiClassType unhandledException : unhandledExceptions) { |
| ProgressIndicatorProvider.checkCanceled(); |
| generateThrow(unhandledException, element); |
| } |
| } |
| |
| private void generateThrow(PsiClassType unhandledException, PsiElement throwingElement) { |
| final List<PsiElement> catchBlocks = findThrowToBlocks(unhandledException); |
| for (PsiElement block : catchBlocks) { |
| ProgressIndicatorProvider.checkCanceled(); |
| ConditionalThrowToInstruction instruction = new ConditionalThrowToInstruction(0); |
| myCurrentFlow.addInstruction(instruction); |
| if (!patchCheckedThrowInstructionIfInsideFinally(instruction, throwingElement, block)) { |
| if (block == null) { |
| addElementOffsetLater(myCodeFragment, false); |
| } |
| else { |
| instruction.offset--; // -1 for catch block param init |
| addElementOffsetLater(block, true); |
| } |
| } |
| } |
| } |
| |
| private final Map<PsiElement, List<PsiElement>> finallyBlockToUnhandledExceptions = new HashMap<PsiElement, List<PsiElement>>(); |
| |
| private boolean patchCheckedThrowInstructionIfInsideFinally(@NotNull ConditionalThrowToInstruction instruction, |
| PsiElement throwingElement, |
| PsiElement elementToJumpTo) { |
| final PsiElement finallyBlock = findEnclosingFinallyBlockElement(throwingElement, elementToJumpTo); |
| if (finallyBlock == null) return false; |
| |
| List<PsiElement> unhandledExceptionCatchBlocks = finallyBlockToUnhandledExceptions.get(finallyBlock); |
| if (unhandledExceptionCatchBlocks == null) { |
| unhandledExceptionCatchBlocks = new ArrayList<PsiElement>(); |
| finallyBlockToUnhandledExceptions.put(finallyBlock, unhandledExceptionCatchBlocks); |
| } |
| int index = unhandledExceptionCatchBlocks.indexOf(elementToJumpTo); |
| if (index == -1) { |
| index = unhandledExceptionCatchBlocks.size(); |
| unhandledExceptionCatchBlocks.add(elementToJumpTo); |
| } |
| // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. |
| instruction.offset = 3 + index; |
| addElementOffsetLater(finallyBlock, false); |
| |
| return true; |
| } |
| |
| private boolean patchUncheckedThrowInstructionIfInsideFinally(@NotNull ConditionalThrowToInstruction instruction, |
| PsiElement throwingElement, |
| PsiElement elementToJumpTo) { |
| final PsiElement finallyBlock = findEnclosingFinallyBlockElement(throwingElement, elementToJumpTo); |
| if (finallyBlock == null) return false; |
| |
| // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. |
| instruction.offset = 2; |
| addElementOffsetLater(finallyBlock, false); |
| |
| return true; |
| } |
| |
| @Override public void visitCodeFragment(JavaCodeFragment codeFragment) { |
| startElement(codeFragment); |
| int prevOffset = myCurrentFlow.getSize(); |
| PsiElement[] children = codeFragment.getChildren(); |
| for (PsiElement child : children) { |
| ProgressIndicatorProvider.checkCanceled(); |
| child.accept(this); |
| } |
| |
| finishElement(codeFragment); |
| registerSubRange(codeFragment, prevOffset); |
| } |
| |
| private void registerSubRange(final PsiElement codeFragment, final int startOffset) { |
| // cache child code block in hope it will be needed |
| ControlFlowSubRange flow = new ControlFlowSubRange(myCurrentFlow, startOffset, myCurrentFlow.getSize()); |
| // register it later since offset may not have been patched yet |
| mySubRanges.put(codeFragment, flow); |
| } |
| |
| @Override public void visitCodeBlock(PsiCodeBlock block) { |
| startElement(block); |
| int prevOffset = myCurrentFlow.getSize(); |
| PsiStatement[] statements = block.getStatements(); |
| for (PsiStatement statement : statements) { |
| ProgressIndicatorProvider.checkCanceled(); |
| statement.accept(this); |
| } |
| |
| //each statement should contain at least one instruction in order to getElement(offset) work |
| int nextOffset = myCurrentFlow.getSize(); |
| if (!(block.getParent() instanceof PsiSwitchStatement) && prevOffset == nextOffset) { |
| emitEmptyInstruction(); |
| } |
| |
| finishElement(block); |
| if (prevOffset != 0) { |
| registerSubRange(block, prevOffset); |
| } |
| } |
| |
| private void emitEmptyInstruction() { |
| myCurrentFlow.addInstruction(EmptyInstruction.INSTANCE); |
| } |
| |
| @Override |
| public void visitFile(PsiFile file) { |
| visitChildren(file); |
| } |
| |
| @Override public void visitBlockStatement(PsiBlockStatement statement) { |
| startElement(statement); |
| final PsiCodeBlock codeBlock = statement.getCodeBlock(); |
| codeBlock.accept(this); |
| finishElement(statement); |
| } |
| |
| @Override public void visitBreakStatement(PsiBreakStatement statement) { |
| startElement(statement); |
| PsiStatement exitedStatement = statement.findExitedStatement(); |
| if (exitedStatement != null) { |
| final Instruction instruction; |
| final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, exitedStatement); |
| final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); |
| if (finallyBlock != null && finallyStartOffset != -1) { |
| // go out of finally, use return |
| CallInstruction callInstruction = (CallInstruction)myCurrentFlow.getInstructions().get(finallyStartOffset - 2); |
| instruction = new ReturnInstruction(0, myStack, callInstruction); |
| } |
| else { |
| instruction = new GoToInstruction(0); |
| } |
| myCurrentFlow.addInstruction(instruction); |
| // exited statement might be out of control flow analyzed |
| addElementOffsetLater(exitedStatement, false); |
| } |
| finishElement(statement); |
| } |
| |
| private PsiElement findEnclosingFinallyBlockElement(PsiElement sourceElement, PsiElement jumpElement) { |
| PsiElement element = sourceElement; |
| while (element != null && !(element instanceof PsiFile)) { |
| if (element instanceof PsiCodeBlock |
| && element.getParent() instanceof PsiTryStatement |
| && ((PsiTryStatement)element.getParent()).getFinallyBlock() == element) { |
| // element maybe out of scope to be analyzed |
| if (myCurrentFlow.getStartOffset(element.getParent()) == -1) return null; |
| if (jumpElement == null || !PsiTreeUtil.isAncestor(element, jumpElement, false)) return element; |
| } |
| element = element.getParent(); |
| } |
| return null; |
| } |
| |
| @Override public void visitContinueStatement(PsiContinueStatement statement) { |
| startElement(statement); |
| PsiStatement continuedStatement = statement.findContinuedStatement(); |
| if (continuedStatement != null) { |
| PsiElement body = null; |
| if (continuedStatement instanceof PsiForStatement) { |
| body = ((PsiForStatement)continuedStatement).getBody(); |
| } |
| else if (continuedStatement instanceof PsiWhileStatement) { |
| body = ((PsiWhileStatement)continuedStatement).getBody(); |
| } |
| else if (continuedStatement instanceof PsiDoWhileStatement) { |
| body = ((PsiDoWhileStatement)continuedStatement).getBody(); |
| } |
| else if (continuedStatement instanceof PsiForeachStatement) { |
| body = ((PsiForeachStatement)continuedStatement).getBody(); |
| } |
| if (body == null) { |
| body = myCodeFragment; |
| } |
| final Instruction instruction; |
| final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, continuedStatement); |
| final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); |
| if (finallyBlock != null && finallyStartOffset != -1) { |
| // go out of finally, use return |
| CallInstruction callInstruction = (CallInstruction)myCurrentFlow.getInstructions().get(finallyStartOffset - 2); |
| instruction = new ReturnInstruction(0, myStack, callInstruction); |
| } |
| else { |
| instruction = new GoToInstruction(0); |
| } |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(body, false); |
| } |
| finishElement(statement); |
| } |
| |
| @Override |
| public void visitDeclarationStatement(PsiDeclarationStatement statement) { |
| startElement(statement); |
| int pc = myCurrentFlow.getSize(); |
| PsiElement[] elements = statement.getDeclaredElements(); |
| for (PsiElement element : elements) { |
| ProgressIndicatorProvider.checkCanceled(); |
| if (element instanceof PsiClass) { |
| element.accept(this); |
| } |
| else if (element instanceof PsiVariable) { |
| processVariable((PsiVariable)element); |
| } |
| } |
| if (pc == myCurrentFlow.getSize()) { |
| // generate at least one instruction for declaration |
| emitEmptyInstruction(); |
| } |
| finishElement(statement); |
| } |
| |
| private void processVariable(final PsiVariable element) { |
| final PsiExpression initializer = element.getInitializer(); |
| if (initializer != null) { |
| myStartStatementStack.pushStatement(initializer, false); |
| myEndStatementStack.pushStatement(initializer, false); |
| initializer.accept(this); |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| } |
| if (element instanceof PsiLocalVariable && initializer != null || |
| element instanceof PsiField) { |
| if (element instanceof PsiLocalVariable && !myPolicy.isLocalVariableAccepted((PsiLocalVariable)element)) return; |
| |
| if (myAssignmentTargetsAreElements) { |
| startElement(element); |
| } |
| |
| generateWriteInstruction(element); |
| |
| if (myAssignmentTargetsAreElements) { |
| finishElement(element); |
| } |
| } |
| } |
| |
| @Override public void visitDoWhileStatement(PsiDoWhileStatement statement) { |
| startElement(statement); |
| myStartStatementStack.pushStatement(statement.getBody() == null ? statement : statement.getBody(), true); |
| myEndStatementStack.pushStatement(statement, false); |
| |
| PsiStatement body = statement.getBody(); |
| if (body != null) { |
| body.accept(this); |
| } |
| |
| PsiExpression condition = statement.getCondition(); |
| if (condition != null) { |
| condition.accept(this); |
| } |
| |
| int offset = myCurrentFlow.getStartOffset(statement); |
| |
| Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(statement.getCondition()); |
| if (loopCondition instanceof Boolean) { |
| if (((Boolean)loopCondition).booleanValue()) { |
| myCurrentFlow.addInstruction(new GoToInstruction(offset)); |
| } |
| else { |
| emitEmptyInstruction(); |
| } |
| |
| } |
| else { |
| Instruction instruction = new ConditionalGoToInstruction(offset, statement.getCondition()); |
| myCurrentFlow.addInstruction(instruction); |
| } |
| |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| finishElement(statement); |
| } |
| |
| @Override public void visitEmptyStatement(PsiEmptyStatement statement) { |
| startElement(statement); |
| emitEmptyInstruction(); |
| |
| finishElement(statement); |
| } |
| |
| @Override public void visitExpressionStatement(PsiExpressionStatement statement) { |
| startElement(statement); |
| final PsiExpression expression = statement.getExpression(); |
| expression.accept(this); |
| |
| for (PsiParameter catchParameter : myCatchParameters) { |
| ProgressIndicatorProvider.checkCanceled(); |
| PsiType type = catchParameter.getType(); |
| if (type instanceof PsiClassType) { |
| generateThrow((PsiClassType)type, statement); |
| } |
| } |
| finishElement(statement); |
| } |
| |
| @Override public void visitExpressionListStatement(PsiExpressionListStatement statement) { |
| startElement(statement); |
| PsiExpression[] expressions = statement.getExpressionList().getExpressions(); |
| for (PsiExpression expr : expressions) { |
| ProgressIndicatorProvider.checkCanceled(); |
| expr.accept(this); |
| } |
| finishElement(statement); |
| } |
| |
| @Override public void visitField(PsiField field) { |
| final PsiExpression initializer = field.getInitializer(); |
| if (initializer != null) { |
| startElement(field); |
| initializer.accept(this); |
| finishElement(field); |
| } |
| } |
| |
| @Override public void visitForStatement(PsiForStatement statement) { |
| startElement(statement); |
| myStartStatementStack.pushStatement(statement.getBody() == null ? statement : statement.getBody(), false); |
| myEndStatementStack.pushStatement(statement, false); |
| |
| PsiStatement initialization = statement.getInitialization(); |
| if (initialization != null) { |
| initialization.accept(this); |
| } |
| |
| PsiExpression condition = statement.getCondition(); |
| if (condition != null) { |
| condition.accept(this); |
| } |
| |
| |
| Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(condition); |
| if (loopCondition instanceof Boolean || condition == null) { |
| boolean value = condition == null || ((Boolean)loopCondition).booleanValue(); |
| if (value) { |
| emitEmptyInstruction(); |
| } |
| else { |
| myCurrentFlow.addInstruction(new GoToInstruction(0)); |
| addElementOffsetLater(statement, false); |
| } |
| } |
| else { |
| Instruction instruction = new ConditionalGoToInstruction(0, statement.getCondition()); |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(statement, false); |
| } |
| |
| PsiStatement body = statement.getBody(); |
| if (body != null) { |
| body.accept(this); |
| } |
| |
| PsiStatement update = statement.getUpdate(); |
| if (update != null) { |
| update.accept(this); |
| } |
| |
| int offset = initialization != null |
| ? myCurrentFlow.getEndOffset(initialization) |
| : myCurrentFlow.getStartOffset(statement); |
| Instruction instruction = new GoToInstruction(offset); |
| myCurrentFlow.addInstruction(instruction); |
| |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| finishElement(statement); |
| } |
| |
| @Override public void visitForeachStatement(PsiForeachStatement statement) { |
| startElement(statement); |
| final PsiStatement body = statement.getBody(); |
| myStartStatementStack.pushStatement(body == null ? statement : body, false); |
| myEndStatementStack.pushStatement(statement, false); |
| final PsiExpression iteratedValue = statement.getIteratedValue(); |
| if (iteratedValue != null) { |
| iteratedValue.accept(this); |
| } |
| |
| final int gotoTarget = myCurrentFlow.getSize(); |
| Instruction instruction = new ConditionalGoToInstruction(0, statement.getIteratedValue()); |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(statement, false); |
| |
| final PsiParameter iterationParameter = statement.getIterationParameter(); |
| if (myPolicy.isParameterAccepted(iterationParameter)) { |
| generateWriteInstruction(iterationParameter); |
| } |
| if (body != null) { |
| body.accept(this); |
| } |
| |
| final GoToInstruction gotoInstruction = new GoToInstruction(gotoTarget); |
| myCurrentFlow.addInstruction(gotoInstruction); |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| finishElement(statement); |
| } |
| |
| @Override public void visitIfStatement(PsiIfStatement statement) { |
| startElement(statement); |
| |
| final PsiStatement elseBranch = statement.getElseBranch(); |
| final PsiStatement thenBranch = statement.getThenBranch(); |
| PsiExpression conditionExpression = statement.getCondition(); |
| |
| generateConditionalStatementInstructions(statement, conditionExpression, thenBranch, elseBranch); |
| |
| finishElement(statement); |
| } |
| |
| private void generateConditionalStatementInstructions(PsiElement statement, |
| PsiExpression conditionExpression, |
| final PsiElement thenBranch, |
| final PsiElement elseBranch) { |
| if (thenBranch == null) { |
| myStartStatementStack.pushStatement(statement, false); |
| } |
| else { |
| myStartStatementStack.pushStatement(thenBranch, true); |
| } |
| if (elseBranch == null) { |
| myEndStatementStack.pushStatement(statement, false); |
| } |
| else { |
| myEndStatementStack.pushStatement(elseBranch, true); |
| } |
| |
| myEndJumpRoles.push(elseBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.ELSE); |
| myStartJumpRoles.push(thenBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.THEN); |
| |
| if (conditionExpression != null) { |
| conditionExpression.accept(this); |
| } |
| |
| boolean generateElseFlow = true; |
| boolean generateThenFlow = true; |
| boolean generateConditionalJump = true; |
| /** |
| * if() statement generated instructions outline: |
| * 'if (C) { A } [ else { B } ]' : |
| * generate (C) |
| * cond_goto else |
| * generate (A) |
| * [ goto end ] |
| * :else |
| * [ generate (B) ] |
| * :end |
| */ |
| if (myEvaluateConstantIfCondition) { |
| final Object value = myConstantEvaluationHelper.computeConstantExpression(conditionExpression); |
| if (value instanceof Boolean) { |
| boolean condition = ((Boolean)value).booleanValue(); |
| generateThenFlow = condition; |
| generateElseFlow = !condition; |
| generateConditionalJump = false; |
| myCurrentFlow.setConstantConditionOccurred(true); |
| } |
| } |
| if (generateConditionalJump) { |
| BranchingInstruction.Role role = elseBranch == null ? BranchingInstruction.Role.END : BranchingInstruction.Role.ELSE; |
| Instruction instruction = new ConditionalGoToInstruction(0, role, conditionExpression); |
| myCurrentFlow.addInstruction(instruction); |
| if (elseBranch == null) { |
| addElementOffsetLater(statement, false); |
| } |
| else { |
| addElementOffsetLater(elseBranch, true); |
| } |
| } |
| if (thenBranch != null && generateThenFlow) { |
| thenBranch.accept(this); |
| } |
| if (elseBranch != null && generateElseFlow) { |
| if (generateThenFlow) { |
| // make jump to end after then branch (only if it has been generated) |
| Instruction instruction = new GoToInstruction(0); |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(statement, false); |
| } |
| elseBranch.accept(this); |
| } |
| |
| myStartJumpRoles.pop(); |
| myEndJumpRoles.pop(); |
| |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| } |
| |
| @Override public void visitLabeledStatement(PsiLabeledStatement statement) { |
| startElement(statement); |
| final PsiStatement innerStatement = statement.getStatement(); |
| if (innerStatement != null) { |
| innerStatement.accept(this); |
| } |
| finishElement(statement); |
| } |
| |
| @Override public void visitReturnStatement(PsiReturnStatement statement) { |
| startElement(statement); |
| PsiExpression returnValue = statement.getReturnValue(); |
| |
| myStartStatementStack.pushStatement(returnValue, false); |
| myEndStatementStack.pushStatement(returnValue, false); |
| |
| if (returnValue != null) { |
| returnValue.accept(this); |
| } |
| addReturnInstruction(statement); |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| |
| finishElement(statement); |
| } |
| |
| private void addReturnInstruction(PsiElement statement) { |
| BranchingInstruction instruction; |
| final PsiElement finallyBlock = findEnclosingFinallyBlockElement(statement, null); |
| final int finallyStartOffset = finallyBlock == null ? -1 : myCurrentFlow.getStartOffset(finallyBlock); |
| if (finallyBlock != null && finallyStartOffset != -1) { |
| // go out of finally, go to 2nd return after finally block |
| // second return is for return statement called completion |
| instruction = new GoToInstruction(1, BranchingInstruction.Role.END, true); |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(finallyBlock, false); |
| } |
| else { |
| instruction = new GoToInstruction(0, BranchingInstruction.Role.END, true); |
| myCurrentFlow.addInstruction(instruction); |
| if (myFinallyBlocks.isEmpty()) { |
| addElementOffsetLater(myCodeFragment, false); |
| } |
| else { |
| instruction.offset = -4; // -4 for return |
| addElementOffsetLater(myFinallyBlocks.peek(), true); |
| } |
| } |
| } |
| |
| @Override public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) { |
| startElement(statement); |
| PsiExpression caseValue = statement.getCaseValue(); |
| |
| myStartStatementStack.pushStatement(caseValue, false); |
| myEndStatementStack.pushStatement(caseValue, false); |
| |
| if (caseValue != null) caseValue.accept(this); |
| |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| |
| finishElement(statement); |
| } |
| |
| @Override public void visitSwitchStatement(PsiSwitchStatement statement) { |
| startElement(statement); |
| |
| PsiExpression expr = statement.getExpression(); |
| if (expr != null) { |
| expr.accept(this); |
| } |
| |
| PsiCodeBlock body = statement.getBody(); |
| if (body != null) { |
| PsiStatement[] statements = body.getStatements(); |
| PsiSwitchLabelStatement defaultLabel = null; |
| for (PsiStatement aStatement : statements) { |
| ProgressIndicatorProvider.checkCanceled(); |
| if (aStatement instanceof PsiSwitchLabelStatement) { |
| if (((PsiSwitchLabelStatement)aStatement).isDefaultCase()) { |
| defaultLabel = (PsiSwitchLabelStatement)aStatement; |
| } |
| Instruction instruction = new ConditionalGoToInstruction(0, statement.getExpression()); |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(aStatement, true); |
| } |
| } |
| if (defaultLabel == null) { |
| Instruction instruction = new GoToInstruction(0); |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(body, false); |
| } |
| |
| body.accept(this); |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override public void visitSynchronizedStatement(PsiSynchronizedStatement statement) { |
| startElement(statement); |
| |
| PsiExpression lock = statement.getLockExpression(); |
| if (lock != null) { |
| lock.accept(this); |
| } |
| |
| PsiCodeBlock body = statement.getBody(); |
| if (body != null) { |
| body.accept(this); |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override public void visitThrowStatement(PsiThrowStatement statement) { |
| startElement(statement); |
| |
| PsiExpression exception = statement.getException(); |
| if (exception != null) { |
| exception.accept(this); |
| } |
| final List<PsiElement> blocks = findThrowToBlocks(statement); |
| PsiElement element; |
| if (blocks.isEmpty() || blocks.get(0) == null) { |
| ThrowToInstruction instruction = new ThrowToInstruction(0); |
| myCurrentFlow.addInstruction(instruction); |
| if (myFinallyBlocks.isEmpty()) { |
| element = myCodeFragment; |
| addElementOffsetLater(element, false); |
| } |
| else { |
| instruction.offset = -2; // -2 to rethrow exception |
| element = myFinallyBlocks.peek(); |
| addElementOffsetLater(element, true); |
| } |
| } |
| else { |
| for (int i = 0; i < blocks.size(); i++) { |
| ProgressIndicatorProvider.checkCanceled(); |
| element = blocks.get(i); |
| BranchingInstruction instruction = i == blocks.size() - 1 |
| ? new ThrowToInstruction(0) |
| : new ConditionalThrowToInstruction(0); |
| myCurrentFlow.addInstruction(instruction); |
| instruction.offset = -1; // -1 to init catch param |
| addElementOffsetLater(element, true); |
| } |
| } |
| |
| |
| finishElement(statement); |
| } |
| |
| /** |
| * find offsets of catch(es) corresponding to this throw statement |
| * myCatchParameters and myCatchBlocks arrays should be sorted in ascending scope order (from outermost to innermost) |
| * |
| * @return offset or -1 if not found |
| */ |
| private List<PsiElement> findThrowToBlocks(PsiThrowStatement statement) { |
| final PsiExpression exceptionExpr = statement.getException(); |
| if (exceptionExpr == null) return Collections.emptyList(); |
| final PsiType throwType = exceptionExpr.getType(); |
| if (!(throwType instanceof PsiClassType)) return Collections.emptyList(); |
| return findThrowToBlocks((PsiClassType)throwType); |
| } |
| |
| private List<PsiElement> findThrowToBlocks(PsiClassType throwType) { |
| List<PsiElement> blocks = new ArrayList<PsiElement>(); |
| for (int i = myCatchParameters.size() - 1; i >= 0; i--) { |
| ProgressIndicatorProvider.checkCanceled(); |
| PsiParameter parameter = myCatchParameters.get(i); |
| PsiType catchType = parameter.getType(); |
| if (catchType.isAssignableFrom(throwType) || throwType.isAssignableFrom(catchType)) { |
| blocks.add(myCatchBlocks.get(i)); |
| } |
| } |
| if (blocks.isEmpty()) { |
| // consider it as throw at the end of the control flow |
| blocks.add(null); |
| } |
| return blocks; |
| } |
| |
| @Override public void visitAssertStatement(PsiAssertStatement statement) { |
| startElement(statement); |
| |
| // should not try to compute constant expression within assert |
| // since assertions can be disabled/enabled at any moment via JVM flags |
| |
| final PsiExpression condition = statement.getAssertCondition(); |
| if (condition != null) { |
| myStartStatementStack.pushStatement(statement, false); |
| myEndStatementStack.pushStatement(statement, false); |
| |
| myEndJumpRoles.push(BranchingInstruction.Role.END); |
| myStartJumpRoles.push(BranchingInstruction.Role.END); |
| |
| condition.accept(this); |
| |
| myStartJumpRoles.pop(); |
| myEndJumpRoles.pop(); |
| |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| } |
| PsiExpression description = statement.getAssertDescription(); |
| if (description != null) { |
| description.accept(this); |
| } |
| |
| Instruction instruction = new ConditionalThrowToInstruction(0, statement.getAssertCondition()); |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(myCodeFragment, false); |
| |
| finishElement(statement); |
| } |
| |
| @Override |
| public void visitTryStatement(PsiTryStatement statement) { |
| startElement(statement); |
| |
| PsiCodeBlock[] catchBlocks = statement.getCatchBlocks(); |
| PsiParameter[] catchBlockParameters = statement.getCatchBlockParameters(); |
| int catchNum = Math.min(catchBlocks.length, catchBlockParameters.length); |
| myUnhandledExceptionCatchBlocks.push(null); |
| for (int i = catchNum - 1; i >= 0; i--) { |
| ProgressIndicatorProvider.checkCanceled(); |
| myCatchParameters.push(catchBlockParameters[i]); |
| myCatchBlocks.push(catchBlocks[i]); |
| |
| final PsiType type = catchBlockParameters[i].getType(); |
| // todo cast param |
| if (type instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)type)) { |
| myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); |
| } |
| else if (type instanceof PsiDisjunctionType) { |
| final PsiType lub = ((PsiDisjunctionType)type).getLeastUpperBound(); |
| if (lub instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)lub)) { |
| myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); |
| } |
| else if (lub instanceof PsiIntersectionType) { |
| for (PsiType conjunct : ((PsiIntersectionType)lub).getConjuncts()) { |
| if (conjunct instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)conjunct)) { |
| myUnhandledExceptionCatchBlocks.push(catchBlocks[i]); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| PsiCodeBlock finallyBlock = statement.getFinallyBlock(); |
| |
| if (finallyBlock != null) { |
| myFinallyBlocks.push(finallyBlock); |
| } |
| |
| PsiResourceList resourceList = statement.getResourceList(); |
| if (resourceList != null) { |
| generateCheckedExceptionJumps(resourceList); |
| resourceList.accept(this); |
| } |
| PsiCodeBlock tryBlock = statement.getTryBlock(); |
| if (tryBlock != null) { |
| // javac works as if all checked exceptions can occur at the top of the block |
| generateCheckedExceptionJumps(tryBlock); |
| tryBlock.accept(this); |
| } |
| |
| //noinspection StatementWithEmptyBody |
| while (myUnhandledExceptionCatchBlocks.pop() != null) ; |
| |
| myCurrentFlow.addInstruction(new GoToInstruction(finallyBlock == null ? 0 : -6)); |
| if (finallyBlock == null) { |
| addElementOffsetLater(statement, false); |
| } |
| else { |
| addElementOffsetLater(finallyBlock, true); |
| } |
| |
| for (int i = 0; i < catchNum; i++) { |
| myCatchParameters.pop(); |
| myCatchBlocks.pop(); |
| } |
| |
| for (int i = catchNum - 1; i >= 0; i--) { |
| ProgressIndicatorProvider.checkCanceled(); |
| if (myPolicy.isParameterAccepted(catchBlockParameters[i])) { |
| generateWriteInstruction(catchBlockParameters[i]); |
| } |
| PsiCodeBlock catchBlock = catchBlocks[i]; |
| assert catchBlock != null : i+statement.getText(); |
| catchBlock.accept(this); |
| |
| myCurrentFlow.addInstruction(new GoToInstruction(finallyBlock == null ? 0 : -6)); |
| if (finallyBlock == null) { |
| addElementOffsetLater(statement, false); |
| } |
| else { |
| addElementOffsetLater(finallyBlock, true); |
| } |
| } |
| |
| if (finallyBlock != null) { |
| myFinallyBlocks.pop(); |
| } |
| |
| if (finallyBlock != null) { |
| // normal completion, call finally block and proceed |
| myCurrentFlow.addInstruction(new CallInstruction(0, 0, myStack)); |
| addElementOffsetLater(finallyBlock, true); |
| myCurrentFlow.addInstruction(new GoToInstruction(0)); |
| addElementOffsetLater(statement, false); |
| // return completion, call finally block and return |
| myCurrentFlow.addInstruction(new CallInstruction(0, 0, myStack)); |
| addElementOffsetLater(finallyBlock, true); |
| addReturnInstruction(statement); |
| // throw exception completion, call finally block and rethrow |
| myCurrentFlow.addInstruction(new CallInstruction(0, 0, myStack)); |
| addElementOffsetLater(finallyBlock, true); |
| final GoToInstruction gotoUncheckedRethrow = new GoToInstruction(0); |
| myCurrentFlow.addInstruction(gotoUncheckedRethrow); |
| addElementOffsetLater(finallyBlock, false); |
| |
| finallyBlock.accept(this); |
| final int procStart = myCurrentFlow.getStartOffset(finallyBlock); |
| final int procEnd = myCurrentFlow.getEndOffset(finallyBlock); |
| int offset = procStart - 6; |
| final List<Instruction> instructions = myCurrentFlow.getInstructions(); |
| CallInstruction callInstruction = (CallInstruction)instructions.get(offset); |
| callInstruction.procBegin = procStart; |
| callInstruction.procEnd = procEnd; |
| offset += 2; |
| callInstruction = (CallInstruction)instructions.get(offset); |
| callInstruction.procBegin = procStart; |
| callInstruction.procEnd = procEnd; |
| offset += 2; |
| callInstruction = (CallInstruction)instructions.get(offset); |
| callInstruction.procBegin = procStart; |
| callInstruction.procEnd = procEnd; |
| |
| // generate return instructions |
| // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp. |
| |
| // normal completion |
| myCurrentFlow.addInstruction(new ReturnInstruction(0, myStack, callInstruction)); |
| |
| // return statement call completion |
| myCurrentFlow.addInstruction(new ReturnInstruction(procStart - 3, myStack, callInstruction)); |
| |
| // unchecked exception throwing completion |
| myCurrentFlow.addInstruction(new ReturnInstruction(procStart - 1, myStack, callInstruction)); |
| |
| // checked exception throwing completion. need to dispatch to the correct catch clause |
| final List<PsiElement> unhandledExceptionCatchBlocks = finallyBlockToUnhandledExceptions.remove(finallyBlock); |
| for (int i = 0; unhandledExceptionCatchBlocks != null && i < unhandledExceptionCatchBlocks.size(); i++) { |
| ProgressIndicatorProvider.checkCanceled(); |
| PsiElement catchBlock = unhandledExceptionCatchBlocks.get(i); |
| |
| final ReturnInstruction returnInstruction = new ReturnInstruction(0, myStack, callInstruction); |
| returnInstruction.setRethrowFromFinally(); |
| myCurrentFlow.addInstruction(returnInstruction); |
| if (catchBlock == null) { |
| // dispatch to rethrowing exception code |
| returnInstruction.offset = procStart - 1; |
| } |
| else { |
| // dispatch to catch clause |
| returnInstruction.offset--; // -1 for catch block init parameter instruction |
| addElementOffsetLater(catchBlock, true); |
| } |
| } |
| |
| // here generated rethrowing code for unchecked exceptions |
| gotoUncheckedRethrow.offset = myCurrentFlow.getSize(); |
| generateUncheckedExceptionJumps(statement, false); |
| // just in case |
| myCurrentFlow.addInstruction(new ThrowToInstruction(0)); |
| addElementOffsetLater(myCodeFragment, false); |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override |
| public void visitResourceList(final PsiResourceList resourceList) { |
| startElement(resourceList); |
| |
| final List<PsiResourceVariable> resources = resourceList.getResourceVariables(); |
| for (PsiResourceVariable resource : resources) { |
| ProgressIndicatorProvider.checkCanceled(); |
| processVariable(resource); |
| } |
| |
| finishElement(resourceList); |
| } |
| |
| @Override |
| public void visitWhileStatement(PsiWhileStatement statement) { |
| startElement(statement); |
| if (statement.getBody() == null) { |
| myStartStatementStack.pushStatement(statement, false); |
| } |
| else { |
| myStartStatementStack.pushStatement(statement.getBody(), true); |
| } |
| myEndStatementStack.pushStatement(statement, false); |
| |
| PsiExpression condition = statement.getCondition(); |
| if (condition != null) { |
| condition.accept(this); |
| } |
| |
| |
| Object loopCondition = myConstantEvaluationHelper.computeConstantExpression(statement.getCondition()); |
| if (loopCondition instanceof Boolean) { |
| boolean value = ((Boolean)loopCondition).booleanValue(); |
| if (value) { |
| emitEmptyInstruction(); |
| } |
| else { |
| myCurrentFlow.addInstruction(new GoToInstruction(0)); |
| addElementOffsetLater(statement, false); |
| } |
| } |
| else { |
| Instruction instruction = new ConditionalGoToInstruction(0, statement.getCondition()); |
| myCurrentFlow.addInstruction(instruction); |
| addElementOffsetLater(statement, false); |
| } |
| |
| PsiStatement body = statement.getBody(); |
| if (body != null) { |
| body.accept(this); |
| } |
| int offset = myCurrentFlow.getStartOffset(statement); |
| Instruction instruction = new GoToInstruction(offset); |
| myCurrentFlow.addInstruction(instruction); |
| |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| finishElement(statement); |
| } |
| |
| @Override public void visitExpressionList(PsiExpressionList list) { |
| PsiExpression[] expressions = list.getExpressions(); |
| for (final PsiExpression expression : expressions) { |
| ProgressIndicatorProvider.checkCanceled(); |
| myStartStatementStack.pushStatement(expression, false); |
| myEndStatementStack.pushStatement(expression, false); |
| |
| expression.accept(this); |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| } |
| } |
| |
| @Override public void visitArrayAccessExpression(PsiArrayAccessExpression expression) { |
| startElement(expression); |
| |
| expression.getArrayExpression().accept(this); |
| final PsiExpression indexExpression = expression.getIndexExpression(); |
| if (indexExpression != null) { |
| indexExpression.accept(this); |
| } |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) { |
| startElement(expression); |
| |
| PsiExpression[] initializers = expression.getInitializers(); |
| for (PsiExpression initializer : initializers) { |
| ProgressIndicatorProvider.checkCanceled(); |
| initializer.accept(this); |
| } |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitAssignmentExpression(PsiAssignmentExpression expression) { |
| startElement(expression); |
| |
| myStartStatementStack.pushStatement(expression.getRExpression() == null ? expression : expression.getRExpression(), false); |
| myEndStatementStack.pushStatement(expression.getRExpression() == null ? expression : expression.getRExpression(), false); |
| |
| PsiExpression rExpr = expression.getRExpression(); |
| if (rExpr != null) { |
| rExpr.accept(this); |
| } |
| |
| PsiExpression lExpr = PsiUtil.skipParenthesizedExprDown(expression.getLExpression()); |
| if (lExpr instanceof PsiReferenceExpression) { |
| final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)lExpr; |
| PsiExpression qualifierExpression = referenceExpression.getQualifierExpression(); |
| PsiVariable variable = getUsedVariable(referenceExpression); |
| if (qualifierExpression == null || |
| qualifierExpression instanceof PsiThisExpression || |
| variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC)) { |
| if (variable != null) { |
| if (myAssignmentTargetsAreElements) |
| startElement(lExpr); |
| |
| if (expression.getOperationTokenType() != JavaTokenType.EQ) { |
| generateReadInstruction(variable); |
| } |
| generateWriteInstruction(variable); |
| |
| if (myAssignmentTargetsAreElements) finishElement(lExpr); |
| } |
| } |
| else { |
| lExpr.accept(this); //? |
| } |
| } |
| else if (lExpr != null) { |
| lExpr.accept(this); |
| } |
| |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| |
| finishElement(expression); |
| } |
| |
| private static enum Shortcut { |
| NO_SHORTCUT, // a || b |
| SKIP_CURRENT_OPERAND, // false || a |
| STOP_EXPRESSION // true || a |
| } |
| |
| @Override |
| public void visitPolyadicExpression(PsiPolyadicExpression expression) { |
| startElement(expression); |
| IElementType signTokenType = expression.getOperationTokenType(); |
| |
| boolean isAndAnd = signTokenType == JavaTokenType.ANDAND; |
| boolean isOrOr = signTokenType == JavaTokenType.OROR; |
| |
| PsiExpression[] operands = expression.getOperands(); |
| Boolean lValue = isAndAnd; |
| PsiExpression lOperand = null; |
| Boolean rValue = null; |
| for (int i = 0; i < operands.length; i++) { |
| PsiExpression rOperand = operands[i]; |
| if ((isAndAnd || isOrOr) && myEnabledShortCircuit) { |
| Object exprValue = myConstantEvaluationHelper.computeConstantExpression(rOperand); |
| if (exprValue instanceof Boolean) { |
| myCurrentFlow.setConstantConditionOccurred(true); |
| rValue = shouldCalculateConstantExpression(expression) ? (Boolean)exprValue : null; |
| } |
| |
| BranchingInstruction.Role role = isAndAnd ? myEndJumpRoles.peek() : myStartJumpRoles.peek(); |
| PsiElement gotoElement = isAndAnd ? myEndStatementStack.peekElement() : myStartStatementStack.peekElement(); |
| boolean gotoIsAtStart = isAndAnd ? myEndStatementStack.peekAtStart() : myStartStatementStack.peekAtStart(); |
| |
| Shortcut shortcut; |
| if (lValue != null) { |
| shortcut = lValue.booleanValue() == isOrOr ? Shortcut.STOP_EXPRESSION : Shortcut.SKIP_CURRENT_OPERAND; |
| } |
| else if (rValue != null && rValue.booleanValue() == isOrOr) { |
| shortcut = Shortcut.STOP_EXPRESSION; |
| } |
| else { |
| shortcut = Shortcut.NO_SHORTCUT; |
| } |
| |
| switch (shortcut) { |
| case NO_SHORTCUT: |
| assert lOperand != null; |
| myCurrentFlow.addInstruction(new ConditionalGoToInstruction(0, role, lOperand)); |
| addElementOffsetLater(gotoElement, gotoIsAtStart); |
| |
| break; |
| case STOP_EXPRESSION: |
| if (lOperand != null) { |
| myCurrentFlow.addInstruction(new GoToInstruction(0, role)); |
| addElementOffsetLater(gotoElement, gotoIsAtStart); |
| } |
| break; |
| case SKIP_CURRENT_OPERAND: |
| break; |
| } |
| |
| if (shortcut == Shortcut.STOP_EXPRESSION) break; |
| } |
| generateLOperand(rOperand, i == operands.length-1 ? null : operands[i+1],signTokenType); |
| |
| lOperand = rOperand; |
| lValue = rValue; |
| } |
| |
| finishElement(expression); |
| } |
| |
| private void generateLOperand(@NotNull PsiExpression lOperand, PsiExpression rOperand, IElementType signTokenType) { |
| if (rOperand != null) { |
| myStartJumpRoles.push(BranchingInstruction.Role.END); |
| myEndJumpRoles.push(BranchingInstruction.Role.END); |
| PsiElement then = signTokenType == JavaTokenType.OROR ? myStartStatementStack.peekElement() : rOperand; |
| boolean thenAtStart = signTokenType != JavaTokenType.OROR || myStartStatementStack.peekAtStart(); |
| myStartStatementStack.pushStatement(then, thenAtStart); |
| PsiElement elseS = signTokenType == JavaTokenType.ANDAND ? myEndStatementStack.peekElement() : rOperand; |
| boolean elseAtStart = signTokenType != JavaTokenType.ANDAND || myEndStatementStack.peekAtStart(); |
| myEndStatementStack.pushStatement(elseS, elseAtStart); |
| } |
| lOperand.accept(this); |
| if (rOperand != null) { |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| myStartJumpRoles.pop(); |
| myEndJumpRoles.pop(); |
| } |
| } |
| |
| private static boolean isInsideIfCondition(PsiExpression expression) { |
| PsiElement element = expression; |
| while (element instanceof PsiExpression) { |
| final PsiElement parent = element.getParent(); |
| if (parent instanceof PsiIfStatement && element == ((PsiIfStatement)parent).getCondition()) return true; |
| element = parent; |
| } |
| return false; |
| } |
| |
| private boolean shouldCalculateConstantExpression(PsiExpression expression) { |
| return myEvaluateConstantIfCondition || !isInsideIfCondition(expression); |
| } |
| |
| @Override public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { |
| visitChildren(expression); |
| |
| } |
| |
| private void visitChildren(PsiElement element) { |
| startElement(element); |
| |
| PsiElement[] children = element.getChildren(); |
| for (PsiElement child : children) { |
| ProgressIndicatorProvider.checkCanceled(); |
| child.accept(this); |
| } |
| |
| finishElement(element); |
| } |
| |
| @Override public void visitConditionalExpression(PsiConditionalExpression expression) { |
| startElement(expression); |
| |
| final PsiExpression condition = expression.getCondition(); |
| final PsiExpression thenExpression = expression.getThenExpression(); |
| final PsiExpression elseExpression = expression.getElseExpression(); |
| generateConditionalStatementInstructions(expression, condition, thenExpression, elseExpression); |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { |
| startElement(expression); |
| |
| final PsiExpression operand = expression.getOperand(); |
| operand.accept(this); |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitLiteralExpression(PsiLiteralExpression expression) { |
| startElement(expression); |
| finishElement(expression); |
| } |
| |
| @Override |
| public void visitLambdaExpression(PsiLambdaExpression expression) { |
| startElement(expression); |
| final PsiElement body = expression.getBody(); |
| if (body != null) { |
| List<PsiVariable> array = new ArrayList<PsiVariable>(); |
| addUsedVariables(array, body); |
| for (PsiVariable var : array) { |
| ProgressIndicatorProvider.checkCanceled(); |
| generateReadInstruction(var); |
| } |
| } |
| finishElement(expression); |
| } |
| |
| @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { |
| startElement(expression); |
| |
| final PsiReferenceExpression methodExpression = expression.getMethodExpression(); |
| methodExpression.accept(this); |
| final PsiExpressionList argumentList = expression.getArgumentList(); |
| argumentList.accept(this); |
| // just to increase counter - there is some executable code here |
| emitEmptyInstruction(); |
| |
| generateCheckedExceptionJumps(expression); |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitNewExpression(PsiNewExpression expression) { |
| startElement(expression); |
| |
| int pc = myCurrentFlow.getSize(); |
| PsiElement[] children = expression.getChildren(); |
| for (PsiElement child : children) { |
| ProgressIndicatorProvider.checkCanceled(); |
| child.accept(this); |
| } |
| generateCheckedExceptionJumps(expression); |
| |
| if (pc == myCurrentFlow.getSize()) { |
| // generate at least one instruction for constructor call |
| emitEmptyInstruction(); |
| } |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitParenthesizedExpression(PsiParenthesizedExpression expression) { |
| visitChildren(expression); |
| } |
| |
| @Override public void visitPostfixExpression(PsiPostfixExpression expression) { |
| startElement(expression); |
| |
| IElementType op = expression.getOperationTokenType(); |
| PsiExpression operand = PsiUtil.skipParenthesizedExprDown(expression.getOperand()); |
| if (operand != null) { |
| operand.accept(this); |
| if (op == JavaTokenType.PLUSPLUS || op == JavaTokenType.MINUSMINUS) { |
| if (operand instanceof PsiReferenceExpression) { |
| PsiVariable variable = getUsedVariable((PsiReferenceExpression)operand); |
| if (variable != null) { |
| generateWriteInstruction(variable); |
| } |
| } |
| } |
| } |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitPrefixExpression(PsiPrefixExpression expression) { |
| startElement(expression); |
| |
| PsiExpression operand = PsiUtil.skipParenthesizedExprDown(expression.getOperand()); |
| if (operand != null) { |
| IElementType operationSign = expression.getOperationTokenType(); |
| if (operationSign == JavaTokenType.EXCL) { |
| // negation inverts jump targets |
| PsiElement topStartStatement = myStartStatementStack.peekElement(); |
| boolean topAtStart = myStartStatementStack.peekAtStart(); |
| myStartStatementStack.pushStatement(myEndStatementStack.peekElement(), myEndStatementStack.peekAtStart()); |
| myEndStatementStack.pushStatement(topStartStatement, topAtStart); |
| } |
| |
| operand.accept(this); |
| |
| if (operationSign == JavaTokenType.EXCL) { |
| // negation inverts jump targets |
| myStartStatementStack.popStatement(); |
| myEndStatementStack.popStatement(); |
| } |
| |
| if (operand instanceof PsiReferenceExpression && |
| (operationSign == JavaTokenType.PLUSPLUS || operationSign == JavaTokenType.MINUSMINUS)) { |
| PsiVariable variable = getUsedVariable((PsiReferenceExpression)operand); |
| if (variable != null) { |
| generateWriteInstruction(variable); |
| } |
| } |
| } |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitReferenceExpression(PsiReferenceExpression expression) { |
| startElement(expression); |
| |
| PsiExpression qualifier = expression.getQualifierExpression(); |
| if (qualifier != null) { |
| qualifier.accept(this); |
| } |
| |
| PsiVariable variable = getUsedVariable(expression); |
| if (variable != null) { |
| generateReadInstruction(variable); |
| } |
| |
| finishElement(expression); |
| } |
| |
| |
| @Override public void visitSuperExpression(PsiSuperExpression expression) { |
| startElement(expression); |
| finishElement(expression); |
| } |
| |
| @Override public void visitThisExpression(PsiThisExpression expression) { |
| startElement(expression); |
| finishElement(expression); |
| } |
| |
| @Override public void visitTypeCastExpression(PsiTypeCastExpression expression) { |
| startElement(expression); |
| PsiExpression operand = expression.getOperand(); |
| if (operand != null) { |
| operand.accept(this); |
| |
| } |
| finishElement(expression); |
| } |
| |
| @Override public void visitClass(PsiClass aClass) { |
| startElement(aClass); |
| // anonymous or local class |
| if (aClass instanceof PsiAnonymousClass) { |
| final PsiElement arguments = PsiTreeUtil.getChildOfType(aClass, PsiExpressionList.class); |
| if (arguments != null) arguments.accept(this); |
| } |
| List<PsiVariable> array = new ArrayList<PsiVariable>(); |
| addUsedVariables(array, aClass); |
| for (PsiVariable var : array) { |
| ProgressIndicatorProvider.checkCanceled(); |
| generateReadInstruction(var); |
| } |
| finishElement(aClass); |
| } |
| |
| private void addUsedVariables(List<PsiVariable> array, PsiElement scope) { |
| if (scope instanceof PsiReferenceExpression) { |
| PsiVariable variable = getUsedVariable((PsiReferenceExpression)scope); |
| if (variable != null) { |
| if (!array.contains(variable)) { |
| array.add(variable); |
| } |
| } |
| } |
| |
| PsiElement[] children = scope.getChildren(); |
| for (PsiElement child : children) { |
| ProgressIndicatorProvider.checkCanceled(); |
| addUsedVariables(array, child); |
| } |
| } |
| |
| private void generateReadInstruction(PsiVariable variable) { |
| Instruction instruction = new ReadVariableInstruction(variable); |
| myCurrentFlow.addInstruction(instruction); |
| } |
| |
| private void generateWriteInstruction(PsiVariable variable) { |
| Instruction instruction = new WriteVariableInstruction(variable); |
| myCurrentFlow.addInstruction(instruction); |
| } |
| |
| @Nullable |
| private PsiVariable getUsedVariable(PsiReferenceExpression refExpr) { |
| if (refExpr.getParent() instanceof PsiMethodCallExpression) return null; |
| return myPolicy.getUsedVariable(refExpr); |
| } |
| } |