| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.codeInspection.dataFlow; |
| |
| import com.intellij.codeInsight.AnnotationUtil; |
| import com.intellij.codeInspection.dataFlow.instructions.*; |
| import com.intellij.codeInspection.dataFlow.value.*; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.*; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.Stack; |
| import com.siyeh.ig.numeric.UnnecessaryExplicitNumericCastInspection; |
| import org.jetbrains.annotations.Contract; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| import static com.intellij.psi.CommonClassNames.*; |
| |
| public class ControlFlowAnalyzer extends JavaElementVisitor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer"); |
| public static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName(); |
| private boolean myIgnoreAssertions; |
| |
| private static class CannotAnalyzeException extends RuntimeException { } |
| |
| private final DfaValueFactory myFactory; |
| private ControlFlow myCurrentFlow; |
| private Stack<CatchDescriptor> myCatchStack; |
| private DfaValue myRuntimeException; |
| private DfaValue myError; |
| private DfaValue myString; |
| private PsiType myNpe; |
| private PsiType myAssertionError; |
| private Stack<PsiElement> myElementStack = new Stack<PsiElement>(); |
| |
| /** |
| * A mock variable for try-related control transfers. Contains exceptions or an (Throwable-inconvertible) string to indicate return inside finally |
| */ |
| private DfaVariableValue myExceptionHolder; |
| |
| ControlFlowAnalyzer(final DfaValueFactory valueFactory) { |
| myFactory = valueFactory; |
| } |
| |
| public ControlFlow buildControlFlow(@NotNull PsiElement codeFragment, boolean ignoreAssertions) { |
| myIgnoreAssertions = ignoreAssertions; |
| PsiManager manager = codeFragment.getManager(); |
| GlobalSearchScope scope = codeFragment.getResolveScope(); |
| myRuntimeException = myFactory.createTypeValue(createClassType(manager, scope, JAVA_LANG_RUNTIME_EXCEPTION), Nullness.NOT_NULL); |
| myError = myFactory.createTypeValue(createClassType(manager, scope, JAVA_LANG_ERROR), Nullness.NOT_NULL); |
| myNpe = createClassType(manager, scope, JAVA_LANG_NULL_POINTER_EXCEPTION); |
| myAssertionError = createClassType(manager, scope, JAVA_LANG_ASSERTION_ERROR); |
| myString = myFactory.createTypeValue(createClassType(manager, scope, JAVA_LANG_STRING), Nullness.NOT_NULL); |
| |
| PsiParameter mockVar = JavaPsiFacade.getElementFactory(manager.getProject()).createParameterFromText("java.lang.Object $exception$", null); |
| myExceptionHolder = myFactory.getVarFactory().createVariableValue(mockVar, false); |
| |
| myCatchStack = new Stack<CatchDescriptor>(); |
| myCurrentFlow = new ControlFlow(myFactory); |
| |
| try { |
| codeFragment.accept(this); |
| } |
| catch (CannotAnalyzeException e) { |
| return null; |
| } |
| |
| PsiElement parent = codeFragment.getParent(); |
| if (parent instanceof PsiLambdaExpression && codeFragment instanceof PsiExpression) { |
| addInstruction(new CheckReturnValueInstruction(codeFragment)); |
| } |
| |
| addInstruction(new ReturnInstruction(false, null)); |
| |
| return myCurrentFlow; |
| } |
| |
| private static PsiClassType createClassType(PsiManager manager, GlobalSearchScope scope, String fqn) { |
| PsiClass aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(fqn, scope); |
| if (aClass != null) return JavaPsiFacade.getElementFactory(manager.getProject()).createType(aClass); |
| return JavaPsiFacade.getElementFactory(manager.getProject()).createTypeByFQClassName(fqn, scope); |
| } |
| |
| private <T extends Instruction> T addInstruction(T i) { |
| myCurrentFlow.addInstruction(i); |
| return i; |
| } |
| |
| private ControlFlow.ControlFlowOffset getEndOffset(PsiElement element) { |
| return myCurrentFlow.getEndOffset(element); |
| } |
| |
| private ControlFlow.ControlFlowOffset getStartOffset(PsiElement element) { |
| return myCurrentFlow.getStartOffset(element); |
| } |
| |
| private void startElement(PsiElement element) { |
| myCurrentFlow.startElement(element); |
| myElementStack.push(element); |
| } |
| |
| private void finishElement(PsiElement element) { |
| myCurrentFlow.finishElement(element); |
| PsiElement popped = myElementStack.pop(); |
| if (element != popped) { |
| throw new AssertionError("Expected " + element + ", popped " + popped); |
| } |
| } |
| |
| @Override |
| public void visitErrorElement(PsiErrorElement element) { |
| throw new CannotAnalyzeException(); |
| } |
| |
| @Override public void visitAssignmentExpression(PsiAssignmentExpression expression) { |
| PsiExpression lExpr = expression.getLExpression(); |
| PsiExpression rExpr = expression.getRExpression(); |
| |
| startElement(expression); |
| if (rExpr == null) { |
| pushUnknown(); |
| finishElement(expression); |
| return; |
| } |
| |
| IElementType op = expression.getOperationTokenType(); |
| PsiType type = expression.getType(); |
| boolean isBoolean = PsiType.BOOLEAN.equals(type); |
| if (op == JavaTokenType.EQ) { |
| lExpr.accept(this); |
| rExpr.accept(this); |
| generateBoxingUnboxingInstructionFor(rExpr, type); |
| } |
| else if (op == JavaTokenType.ANDEQ) { |
| if (isBoolean) { |
| generateBooleanAssignmentExpression(true, lExpr, rExpr, type); |
| } |
| else { |
| generateDefaultAssignmentBinOp(lExpr, rExpr, type); |
| } |
| } |
| else if (op == JavaTokenType.OREQ) { |
| if (isBoolean) { |
| generateBooleanAssignmentExpression(false, lExpr, rExpr, type); |
| } |
| else { |
| generateDefaultAssignmentBinOp(lExpr, rExpr, type); |
| } |
| } |
| else if (op == JavaTokenType.XOREQ) { |
| if (isBoolean) { |
| generateXorExpression(expression, new PsiExpression[]{lExpr, rExpr}, type, true); |
| } |
| else { |
| generateDefaultAssignmentBinOp(lExpr, rExpr, type); |
| } |
| } |
| else if (op == JavaTokenType.PLUSEQ && type != null && type.equalsToText(JAVA_LANG_STRING)) { |
| lExpr.accept(this); |
| addInstruction(new DupInstruction()); |
| rExpr.accept(this); |
| addInstruction(new BinopInstruction(JavaTokenType.PLUS, null, lExpr.getProject())); |
| } |
| else { |
| generateDefaultAssignmentBinOp(lExpr, rExpr, type); |
| } |
| |
| addInstruction(new AssignInstruction(rExpr)); |
| |
| flushArrayElementsOnUnknownIndexAssignment(lExpr); |
| |
| finishElement(expression); |
| } |
| |
| private void flushArrayElementsOnUnknownIndexAssignment(PsiExpression lExpr) { |
| if (lExpr instanceof PsiArrayAccessExpression && |
| myFactory.createValue(lExpr) == null // check for unknown index, otherwise AssignInstruction will flush only that element |
| ) { |
| DfaValue arrayVar = myFactory.createValue(((PsiArrayAccessExpression)lExpr).getArrayExpression()); |
| if (arrayVar instanceof DfaVariableValue) { |
| addInstruction(new FlushVariableInstruction((DfaVariableValue)arrayVar)); |
| } |
| } |
| } |
| |
| private void generateDefaultAssignmentBinOp(PsiExpression lExpr, PsiExpression rExpr, final PsiType exprType) { |
| lExpr.accept(this); |
| addInstruction(new DupInstruction()); |
| generateBoxingUnboxingInstructionFor(lExpr,exprType); |
| rExpr.accept(this); |
| generateBoxingUnboxingInstructionFor(rExpr, exprType); |
| addInstruction(new BinopInstruction(null, null, lExpr.getProject())); |
| } |
| |
| @Override public void visitAssertStatement(PsiAssertStatement statement) { |
| if (myIgnoreAssertions) { |
| return; |
| } |
| |
| startElement(statement); |
| final PsiExpression condition = statement.getAssertCondition(); |
| final PsiExpression description = statement.getAssertDescription(); |
| if (condition != null) { |
| condition.accept(this); |
| generateBoxingUnboxingInstructionFor(condition, PsiType.BOOLEAN); |
| addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), false, condition)); |
| if (description != null) { |
| description.accept(this); |
| } |
| |
| initException(myAssertionError); |
| addThrowCode(false, statement); |
| } |
| finishElement(statement); |
| } |
| |
| @Override public void visitDeclarationStatement(PsiDeclarationStatement statement) { |
| startElement(statement); |
| |
| PsiElement[] elements = statement.getDeclaredElements(); |
| for (PsiElement element : elements) { |
| if (element instanceof PsiClass) { |
| addInstruction(new EmptyInstruction(element)); |
| } |
| else if (element instanceof PsiVariable) { |
| PsiVariable variable = (PsiVariable)element; |
| PsiExpression initializer = variable.getInitializer(); |
| if (initializer != null) { |
| initializeVariable(variable, initializer); |
| } |
| } |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override |
| public void visitField(PsiField field) { |
| PsiExpression initializer = field.getInitializer(); |
| if (initializer != null) { |
| initializeVariable(field, initializer); |
| } |
| } |
| |
| private void initializeVariable(PsiVariable variable, PsiExpression initializer) { |
| DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(variable, false); |
| addInstruction(new PushInstruction(dfaVariable, initializer)); |
| initializer.accept(this); |
| generateBoxingUnboxingInstructionFor(initializer, variable.getType()); |
| addInstruction(new AssignInstruction(initializer)); |
| addInstruction(new PopInstruction()); |
| } |
| |
| @Override |
| public void visitCodeFragment(JavaCodeFragment codeFragment) { |
| startElement(codeFragment); |
| if (codeFragment instanceof PsiExpressionCodeFragment) { |
| PsiExpression expression = ((PsiExpressionCodeFragment)codeFragment).getExpression(); |
| if (expression != null) { |
| expression.accept(this); |
| } |
| } |
| finishElement(codeFragment); |
| } |
| |
| @Override public void visitCodeBlock(PsiCodeBlock block) { |
| startElement(block); |
| |
| for (PsiStatement statement : block.getStatements()) { |
| statement.accept(this); |
| } |
| |
| flushCodeBlockVariables(block); |
| |
| finishElement(block); |
| } |
| |
| private void flushCodeBlockVariables(PsiCodeBlock block) { |
| for (PsiStatement statement : block.getStatements()) { |
| if (statement instanceof PsiDeclarationStatement) { |
| for (PsiElement declaration : ((PsiDeclarationStatement)statement).getDeclaredElements()) { |
| if (declaration instanceof PsiVariable) { |
| myCurrentFlow.removeVariable((PsiVariable)declaration); |
| } |
| } |
| } |
| } |
| PsiElement parent = block.getParent(); |
| if (parent instanceof PsiCatchSection) { |
| myCurrentFlow.removeVariable(((PsiCatchSection)parent).getParameter()); |
| } |
| else if (parent instanceof PsiForeachStatement) { |
| myCurrentFlow.removeVariable(((PsiForeachStatement)parent).getIterationParameter()); |
| } |
| else if (parent instanceof PsiForStatement) { |
| PsiStatement statement = ((PsiForStatement)parent).getInitialization(); |
| if (statement instanceof PsiDeclarationStatement) { |
| for (PsiElement declaration : ((PsiDeclarationStatement)statement).getDeclaredElements()) { |
| if (declaration instanceof PsiVariable) { |
| myCurrentFlow.removeVariable((PsiVariable)declaration); |
| } |
| } |
| } |
| } |
| else if (parent instanceof PsiTryStatement) { |
| PsiResourceList list = ((PsiTryStatement)parent).getResourceList(); |
| if (list != null) { |
| for (PsiResourceVariable variable : list.getResourceVariables()) { |
| myCurrentFlow.removeVariable(variable); |
| } |
| } |
| } |
| } |
| |
| @Override public void visitBlockStatement(PsiBlockStatement statement) { |
| startElement(statement); |
| statement.getCodeBlock().accept(this); |
| finishElement(statement); |
| } |
| |
| @Override public void visitBreakStatement(PsiBreakStatement statement) { |
| startElement(statement); |
| |
| PsiStatement exitedStatement = statement.findExitedStatement(); |
| |
| if (exitedStatement != null) { |
| flushVariablesOnControlTransfer(exitedStatement); |
| addInstruction(new GotoInstruction(getEndOffset(exitedStatement))); |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override public void visitContinueStatement(PsiContinueStatement statement) { |
| startElement(statement); |
| PsiStatement continuedStatement = statement.findContinuedStatement(); |
| if (continuedStatement instanceof PsiLoopStatement) { |
| PsiStatement body = ((PsiLoopStatement)continuedStatement).getBody(); |
| flushVariablesOnControlTransfer(body); |
| addInstruction(new GotoInstruction(getEndOffset(body))); |
| } else { |
| addInstruction(new EmptyInstruction(null)); |
| } |
| finishElement(statement); |
| } |
| |
| @Override public void visitDoWhileStatement(PsiDoWhileStatement statement) { |
| startElement(statement); |
| |
| PsiStatement body = statement.getBody(); |
| if (body != null) { |
| body.accept(this); |
| PsiExpression condition = statement.getCondition(); |
| if (condition != null) { |
| condition.accept(this); |
| generateBoxingUnboxingInstructionFor(condition, PsiType.BOOLEAN); |
| addInstruction(new ConditionalGotoInstruction(getStartOffset(statement), false, condition)); |
| } |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override public void visitEmptyStatement(PsiEmptyStatement statement) { |
| startElement(statement); |
| finishElement(statement); |
| } |
| |
| @Override public void visitExpressionStatement(PsiExpressionStatement statement) { |
| startElement(statement); |
| final PsiExpression expr = statement.getExpression(); |
| expr.accept(this); |
| addInstruction(new PopInstruction()); |
| finishElement(statement); |
| } |
| |
| @Override public void visitExpressionListStatement(PsiExpressionListStatement statement) { |
| startElement(statement); |
| PsiExpression[] expressions = statement.getExpressionList().getExpressions(); |
| for (PsiExpression expr : expressions) { |
| expr.accept(this); |
| addInstruction(new PopInstruction()); |
| } |
| finishElement(statement); |
| } |
| |
| @Override public void visitForeachStatement(PsiForeachStatement statement) { |
| startElement(statement); |
| final PsiParameter parameter = statement.getIterationParameter(); |
| final PsiExpression iteratedValue = statement.getIteratedValue(); |
| |
| if (iteratedValue != null) { |
| iteratedValue.accept(this); |
| addInstruction(new FieldReferenceInstruction(iteratedValue, "Collection iterator or array.length")); |
| } |
| |
| ControlFlow.ControlFlowOffset offset = myCurrentFlow.getNextOffset(); |
| DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(parameter, false); |
| addInstruction(new FlushVariableInstruction(dfaVariable)); |
| |
| pushUnknown(); |
| addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), true, null)); |
| |
| final PsiStatement body = statement.getBody(); |
| if (body != null) { |
| body.accept(this); |
| } |
| |
| addInstruction(new GotoInstruction(offset)); |
| |
| finishElement(statement); |
| myCurrentFlow.removeVariable(parameter); |
| } |
| |
| @Override public void visitForStatement(PsiForStatement statement) { |
| startElement(statement); |
| final ArrayList<PsiElement> declaredVariables = new ArrayList<PsiElement>(); |
| |
| PsiStatement initialization = statement.getInitialization(); |
| if (initialization != null) { |
| initialization.accept(this); |
| initialization.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitReferenceExpression(PsiReferenceExpression expression) { |
| visitElement(expression); |
| } |
| |
| @Override |
| public void visitDeclarationStatement(PsiDeclarationStatement statement) { |
| PsiElement[] declaredElements = statement.getDeclaredElements(); |
| for (PsiElement element : declaredElements) { |
| if (element instanceof PsiVariable) { |
| declaredVariables.add(element); |
| } |
| } |
| } |
| }); |
| } |
| |
| PsiExpression condition = statement.getCondition(); |
| if (condition != null) { |
| condition.accept(this); |
| generateBoxingUnboxingInstructionFor(condition, PsiType.BOOLEAN); |
| } |
| else { |
| addInstruction(new PushInstruction(statement.getRParenth() == null ? null : myFactory.getConstFactory().getTrue(), null)); |
| } |
| addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), true, condition)); |
| |
| PsiStatement body = statement.getBody(); |
| if (body != null) { |
| body.accept(this); |
| } |
| |
| PsiStatement update = statement.getUpdate(); |
| if (update != null) { |
| update.accept(this); |
| } |
| |
| ControlFlow.ControlFlowOffset offset = initialization != null |
| ? getEndOffset(initialization) |
| : getStartOffset(statement); |
| |
| addInstruction(new GotoInstruction(offset)); |
| finishElement(statement); |
| |
| for (PsiElement declaredVariable : declaredVariables) { |
| PsiVariable psiVariable = (PsiVariable)declaredVariable; |
| myCurrentFlow.removeVariable(psiVariable); |
| } |
| } |
| |
| @Override public void visitIfStatement(PsiIfStatement statement) { |
| startElement(statement); |
| |
| PsiExpression condition = statement.getCondition(); |
| |
| PsiStatement thenStatement = statement.getThenBranch(); |
| PsiStatement elseStatement = statement.getElseBranch(); |
| |
| ControlFlow.ControlFlowOffset offset = elseStatement != null |
| ? getStartOffset(elseStatement) |
| : getEndOffset(statement); |
| |
| if (condition != null) { |
| condition.accept(this); |
| generateBoxingUnboxingInstructionFor(condition, PsiType.BOOLEAN); |
| addInstruction(new ConditionalGotoInstruction(offset, true, condition)); |
| } |
| |
| if (thenStatement != null) { |
| thenStatement.accept(this); |
| } |
| |
| if (elseStatement != null) { |
| offset = getEndOffset(statement); |
| Instruction instruction = new GotoInstruction(offset); |
| addInstruction(instruction); |
| elseStatement.accept(this); |
| } |
| |
| finishElement(statement); |
| } |
| |
| // in case of JspTemplateStatement |
| @Override public void visitStatement(PsiStatement statement) { |
| startElement(statement); |
| finishElement(statement); |
| } |
| |
| @Override public void visitLabeledStatement(PsiLabeledStatement statement) { |
| startElement(statement); |
| PsiStatement childStatement = statement.getStatement(); |
| if (childStatement != null) { |
| childStatement.accept(this); |
| } |
| finishElement(statement); |
| } |
| |
| @Override |
| public void visitLambdaExpression(PsiLambdaExpression expression) { |
| startElement(expression); |
| DfaValue dfaValue = myFactory.createValue(expression); |
| addInstruction(new PushInstruction(dfaValue, expression)); |
| addInstruction(new LambdaInstruction(expression)); |
| finishElement(expression); |
| } |
| |
| @Override public void visitReturnStatement(PsiReturnStatement statement) { |
| startElement(statement); |
| |
| PsiExpression returnValue = statement.getReturnValue(); |
| if (returnValue != null) { |
| returnValue.accept(this); |
| PsiMethod method = PsiTreeUtil.getParentOfType(statement, PsiMethod.class, true, PsiMember.class); |
| if (method != null) { |
| generateBoxingUnboxingInstructionFor(returnValue, method.getReturnType()); |
| } |
| addInstruction(new CheckReturnValueInstruction(returnValue)); |
| } |
| |
| returnCheckingFinally(false, statement); |
| finishElement(statement); |
| } |
| |
| private void returnCheckingFinally(boolean viaException, @NotNull PsiElement anchor) { |
| ControlFlow.ControlFlowOffset finallyOffset = getFinallyOffset(); |
| if (finallyOffset != null) { |
| addInstruction(new PushInstruction(myExceptionHolder, null)); |
| addInstruction(new PushInstruction(myString, null)); |
| addInstruction(new AssignInstruction(null)); |
| addInstruction(new PopInstruction()); |
| |
| addInstruction(new GotoInstruction(finallyOffset)); |
| } else { |
| addInstruction(new ReturnInstruction(viaException, anchor)); |
| } |
| } |
| |
| @Override public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) { |
| startElement(statement); |
| finishElement(statement); |
| } |
| |
| @Override public void visitSwitchStatement(PsiSwitchStatement switchStmt) { |
| startElement(switchStmt); |
| PsiExpression caseExpression = switchStmt.getExpression(); |
| Set<PsiEnumConstant> enumValues = null; |
| if (caseExpression != null /*&& !(caseExpression instanceof PsiReferenceExpression)*/) { |
| caseExpression.accept(this); |
| |
| generateBoxingUnboxingInstructionFor(caseExpression, PsiType.INT); |
| final PsiClass psiClass = PsiUtil.resolveClassInType(caseExpression.getType()); |
| if (psiClass != null) { |
| addInstruction(new FieldReferenceInstruction(caseExpression, "switch statement expression")); |
| if (psiClass.isEnum()) { |
| enumValues = new HashSet<PsiEnumConstant>(); |
| for (PsiField f : psiClass.getFields()) { |
| if (f instanceof PsiEnumConstant) { |
| enumValues.add((PsiEnumConstant)f); |
| } |
| } |
| } |
| } else { |
| addInstruction(new PopInstruction()); |
| } |
| |
| } |
| |
| PsiCodeBlock body = switchStmt.getBody(); |
| |
| if (body != null) { |
| PsiStatement[] statements = body.getStatements(); |
| PsiSwitchLabelStatement defaultLabel = null; |
| for (PsiStatement statement : statements) { |
| if (statement instanceof PsiSwitchLabelStatement) { |
| PsiSwitchLabelStatement psiLabelStatement = (PsiSwitchLabelStatement)statement; |
| if (psiLabelStatement.isDefaultCase()) { |
| defaultLabel = psiLabelStatement; |
| } |
| else { |
| try { |
| ControlFlow.ControlFlowOffset offset = getStartOffset(statement); |
| PsiExpression caseValue = psiLabelStatement.getCaseValue(); |
| |
| if (caseValue != null && |
| caseExpression instanceof PsiReferenceExpression && |
| ((PsiReferenceExpression)caseExpression).getQualifierExpression() == null && |
| JavaPsiFacade.getInstance(body.getProject()).getConstantEvaluationHelper().computeConstantExpression(caseValue) != null) { |
| |
| addInstruction(new PushInstruction(myFactory.createValue(caseExpression), caseExpression)); |
| caseValue.accept(this); |
| addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, caseExpression.getProject())); |
| } |
| else { |
| pushUnknown(); |
| } |
| |
| addInstruction(new ConditionalGotoInstruction(offset, false, statement)); |
| |
| if (enumValues != null) { |
| if (caseValue instanceof PsiReferenceExpression) { |
| //noinspection SuspiciousMethodCalls |
| enumValues.remove(((PsiReferenceExpression)caseValue).resolve()); |
| } |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| } |
| } |
| |
| if (enumValues == null || !enumValues.isEmpty()) { |
| ControlFlow.ControlFlowOffset offset = defaultLabel != null ? getStartOffset(defaultLabel) : getEndOffset(body); |
| addInstruction(new GotoInstruction(offset)); |
| } |
| |
| body.accept(this); |
| } |
| |
| finishElement(switchStmt); |
| } |
| |
| @Override public void visitSynchronizedStatement(PsiSynchronizedStatement statement) { |
| startElement(statement); |
| |
| PsiExpression lock = statement.getLockExpression(); |
| if (lock != null) { |
| lock.accept(this); |
| addInstruction(new FieldReferenceInstruction(lock, "Synchronized value")); |
| } |
| |
| addInstruction(new FlushVariableInstruction(null)); |
| |
| 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); |
| if (myCatchStack.isEmpty()) { |
| addInstruction(new ReturnInstruction(true, statement)); |
| finishElement(statement); |
| return; |
| } |
| |
| addConditionalRuntimeThrow(); |
| addInstruction(new DupInstruction()); |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); |
| addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, statement.getProject())); |
| ConditionalGotoInstruction gotoInstruction = new ConditionalGotoInstruction(null, true, null); |
| addInstruction(gotoInstruction); |
| |
| addInstruction(new PopInstruction()); |
| initException(myNpe); |
| addThrowCode(false, statement); |
| |
| gotoInstruction.setOffset(myCurrentFlow.getInstructionCount()); |
| addInstruction(new PushInstruction(myExceptionHolder, null)); |
| addInstruction(new SwapInstruction()); |
| addInstruction(new AssignInstruction(null)); |
| addInstruction(new PopInstruction()); |
| addThrowCode(false, statement); |
| } |
| |
| finishElement(statement); |
| } |
| |
| private void addConditionalRuntimeThrow() { |
| if (myCatchStack.isEmpty()) { |
| return; |
| } |
| |
| pushUnknown(); |
| final ConditionalGotoInstruction ifNoException = addInstruction(new ConditionalGotoInstruction(null, false, null)); |
| addInstruction(new EmptyStackInstruction()); |
| |
| addInstruction(new PushInstruction(myExceptionHolder, null)); |
| |
| pushUnknown(); |
| final ConditionalGotoInstruction ifError = addInstruction(new ConditionalGotoInstruction(null, false, null)); |
| addInstruction(new PushInstruction(myRuntimeException, null)); |
| GotoInstruction ifRuntime = addInstruction(new GotoInstruction(null)); |
| ifError.setOffset(myCurrentFlow.getInstructionCount()); |
| addInstruction(new PushInstruction(myError, null)); |
| ifRuntime.setOffset(myCurrentFlow.getInstructionCount()); |
| |
| addInstruction(new AssignInstruction(null)); |
| addInstruction(new PopInstruction()); |
| |
| addThrowCode(false, null); |
| |
| ifNoException.setOffset(myCurrentFlow.getInstructionCount()); |
| } |
| |
| private void flushVariablesOnControlTransfer(PsiElement stopWhenAncestorOf) { |
| for (int i = myElementStack.size() - 1; i >= 0; i--) { |
| PsiElement scope = myElementStack.get(i); |
| if (PsiTreeUtil.isAncestor(scope, stopWhenAncestorOf, true)) { |
| break; |
| } |
| if (scope instanceof PsiCodeBlock) { |
| flushCodeBlockVariables((PsiCodeBlock)scope); |
| } |
| } |
| } |
| |
| // the exception object should be in $exception$ variable |
| private void addThrowCode(boolean catchRethrow, @Nullable PsiElement explicitThrower) { |
| if (myCatchStack.isEmpty()) { |
| addInstruction(new ReturnInstruction(true, explicitThrower)); |
| return; |
| } |
| |
| PsiElement currentElement = myElementStack.peek(); |
| |
| CatchDescriptor cd = myCatchStack.get(myCatchStack.size() - 1); |
| if (!cd.isFinally() && PsiTreeUtil.isAncestor(cd.getBlock().getParent(), currentElement, false)) { |
| int i = myCatchStack.size() - 2; |
| while (!catchRethrow && i >= 0 && !myCatchStack.get(i).isFinally() && myCatchStack.get(i).getTryStatement() == cd.getTryStatement()) { |
| i--; |
| } |
| if (i < 0) { |
| addInstruction(new ReturnInstruction(true, explicitThrower)); |
| return; |
| } |
| cd = myCatchStack.get(i); |
| } |
| |
| flushVariablesOnControlTransfer(cd.getBlock()); |
| addInstruction(new GotoInstruction(cd.getJumpOffset(this))); |
| } |
| |
| @Nullable |
| private ControlFlow.ControlFlowOffset getFinallyOffset() { |
| for (int i = myCatchStack.size() - 1; i >= 0; i--) { |
| CatchDescriptor cd = myCatchStack.get(i); |
| if (cd.isFinally()) return cd.getJumpOffset(this); |
| } |
| |
| return null; |
| } |
| |
| private static class ApplyNotNullInstruction extends Instruction { |
| private PsiMethodCallExpression myCall; |
| |
| private ApplyNotNullInstruction(PsiMethodCallExpression call) { |
| myCall = call; |
| } |
| |
| @Override |
| public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState state, InstructionVisitor visitor) { |
| DfaValue value = state.pop(); |
| DfaValueFactory factory = runner.getFactory(); |
| if (state.applyCondition( |
| factory.getRelationFactory().createRelation(value, factory.getConstFactory().getNull(), JavaTokenType.EQEQ, true))) { |
| return nextInstruction(runner, state); |
| } |
| if (visitor instanceof StandardInstructionVisitor) { |
| ((StandardInstructionVisitor)visitor).skipConstantConditionReporting(myCall); |
| } |
| return DfaInstructionState.EMPTY_ARRAY; |
| } |
| } |
| |
| private static class CatchDescriptor { |
| private final PsiType myType; |
| private final PsiParameter myParameter; |
| private final PsiCodeBlock myBlock; |
| private final boolean myIsFinally; |
| |
| public CatchDescriptor(PsiCodeBlock finallyBlock) { |
| myType = null; |
| myParameter = null; |
| myBlock = finallyBlock; |
| myIsFinally = true; |
| } |
| |
| public CatchDescriptor(PsiParameter parameter, PsiCodeBlock catchBlock) { |
| myType = parameter.getType(); |
| myParameter = parameter; |
| myBlock = catchBlock; |
| myIsFinally = false; |
| } |
| |
| public PsiCodeBlock getBlock() { |
| return myBlock; |
| } |
| public PsiTryStatement getTryStatement() { |
| return (PsiTryStatement) (isFinally() ? myBlock.getParent() : myBlock.getParent().getParent()); |
| } |
| |
| public PsiType getType() { |
| return myType; |
| } |
| |
| public boolean isFinally() { |
| return myIsFinally; |
| } |
| |
| public ControlFlow.ControlFlowOffset getJumpOffset(ControlFlowAnalyzer analyzer) { |
| return analyzer.getStartOffset(isFinally() ? myBlock : myBlock.getParent()); |
| } |
| |
| public PsiParameter getParameter() { |
| return myParameter; |
| } |
| } |
| |
| @Override |
| public void visitTryStatement(PsiTryStatement statement) { |
| startElement(statement); |
| |
| PsiResourceList resourceList = statement.getResourceList(); |
| PsiCodeBlock tryBlock = statement.getTryBlock(); |
| PsiCodeBlock finallyBlock = statement.getFinallyBlock(); |
| |
| if (finallyBlock != null) { |
| myCatchStack.push(new CatchDescriptor(finallyBlock)); |
| } |
| |
| PsiCatchSection[] sections = statement.getCatchSections(); |
| for (int i = sections.length - 1; i >= 0; i--) { |
| PsiCatchSection section = sections[i]; |
| PsiCodeBlock catchBlock = section.getCatchBlock(); |
| PsiParameter parameter = section.getParameter(); |
| if (parameter != null && catchBlock != null) { |
| PsiType type = parameter.getType(); |
| if (type instanceof PsiClassType || type instanceof PsiDisjunctionType) { |
| myCatchStack.push(new CatchDescriptor(parameter, catchBlock)); |
| continue; |
| } |
| } |
| throw new CannotAnalyzeException(); |
| } |
| |
| ControlFlow.ControlFlowOffset endOffset = finallyBlock == null ? getEndOffset(statement) : getStartOffset(finallyBlock); |
| |
| if (resourceList != null) { |
| resourceList.accept(this); |
| } |
| |
| if (tryBlock != null) { |
| tryBlock.accept(this); |
| } |
| |
| addInstruction(new GotoInstruction(endOffset)); |
| |
| for (PsiCatchSection section : sections) { |
| section.accept(this); |
| addInstruction(new GotoInstruction(endOffset)); |
| myCatchStack.pop(); |
| } |
| |
| if (finallyBlock != null) { |
| myCatchStack.pop(); |
| finallyBlock.accept(this); |
| |
| //if $exception$==null => continue normal execution |
| addInstruction(new PushInstruction(myExceptionHolder, null)); |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); |
| addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, statement.getProject())); |
| addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), false, null)); |
| |
| // else throw $exception$ |
| addThrowCode(false, null); |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override |
| public void visitCatchSection(PsiCatchSection section) { |
| startElement(section); |
| PsiCodeBlock catchBlock = section.getCatchBlock(); |
| if (catchBlock != null) { |
| // exception is in myExceptionHolder mock variable |
| // check if it's assignable to catch parameter type |
| PsiType declaredType = section.getCatchType(); |
| List<PsiType> flattened = declaredType instanceof PsiDisjunctionType ? |
| ((PsiDisjunctionType)declaredType).getDisjunctions() : |
| ContainerUtil.createMaybeSingletonList(declaredType); |
| for (PsiType catchType : flattened) { |
| addInstruction(new PushInstruction(myExceptionHolder, null)); |
| addInstruction(new PushInstruction(myFactory.createTypeValue(catchType, Nullness.UNKNOWN), null)); |
| addInstruction(new BinopInstruction(JavaTokenType.INSTANCEOF_KEYWORD, null, section.getProject())); |
| addInstruction(new ConditionalGotoInstruction(ControlFlow.deltaOffset(getStartOffset(catchBlock), -5), false, null)); |
| } |
| |
| // not assignable => rethrow |
| addThrowCode(true, null); |
| |
| // e = $exception$ |
| addInstruction(new PushInstruction(myFactory.getVarFactory().createVariableValue(section.getParameter(), false), null)); |
| addInstruction(new PushInstruction(myExceptionHolder, null)); |
| addInstruction(new AssignInstruction(null)); |
| addInstruction(new PopInstruction()); |
| |
| addInstruction(new FlushVariableInstruction(myExceptionHolder)); |
| |
| catchBlock.accept(this); |
| } |
| finishElement(section); |
| } |
| |
| @Override |
| public void visitResourceList(PsiResourceList resourceList) { |
| for (PsiResourceVariable variable : resourceList.getResourceVariables()) { |
| PsiExpression initializer = variable.getInitializer(); |
| if (initializer != null) { |
| initializeVariable(variable, initializer); |
| } |
| PsiMethod closer = PsiUtil.getResourceCloserMethod(variable); |
| if (closer != null) { |
| addMethodThrows(closer, null); |
| } |
| } |
| } |
| |
| @Override public void visitWhileStatement(PsiWhileStatement statement) { |
| startElement(statement); |
| |
| PsiExpression condition = statement.getCondition(); |
| |
| if (condition != null) { |
| condition.accept(this); |
| generateBoxingUnboxingInstructionFor(condition, PsiType.BOOLEAN); |
| addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), true, condition)); |
| } |
| |
| PsiStatement body = statement.getBody(); |
| if (body != null) { |
| body.accept(this); |
| } |
| |
| if (condition != null) { |
| addInstruction(new GotoInstruction(getStartOffset(statement))); |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override public void visitExpressionList(PsiExpressionList list) { |
| startElement(list); |
| |
| PsiExpression[] expressions = list.getExpressions(); |
| for (PsiExpression expression : expressions) { |
| expression.accept(this); |
| } |
| |
| finishElement(list); |
| } |
| |
| @Override public void visitExpression(PsiExpression expression) { |
| startElement(expression); |
| DfaValue dfaValue = myFactory.createValue(expression); |
| addInstruction(new PushInstruction(dfaValue, expression)); |
| finishElement(expression); |
| } |
| |
| @Override |
| public void visitArrayAccessExpression(PsiArrayAccessExpression expression) { |
| startElement(expression); |
| PsiExpression arrayExpression = expression.getArrayExpression(); |
| arrayExpression.accept(this); |
| addInstruction(new FieldReferenceInstruction(expression, null)); |
| |
| PsiExpression indexExpression = expression.getIndexExpression(); |
| if (indexExpression != null) { |
| indexExpression.accept(this); |
| generateBoxingUnboxingInstructionFor(indexExpression, PsiType.INT); |
| addInstruction(new PopInstruction()); |
| } |
| |
| DfaValue toPush = myFactory.createValue(expression); |
| addInstruction(new PushInstruction(toPush != null ? toPush : myFactory.createTypeValue(expression.getType(), Nullness.UNKNOWN), null)); |
| finishElement(expression); |
| } |
| |
| @Override |
| public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) { |
| startElement(expression); |
| PsiType type = expression.getType(); |
| PsiExpression[] initializers = expression.getInitializers(); |
| for (PsiExpression initializer : initializers) { |
| initializer.accept(this); |
| if (type instanceof PsiArrayType) { |
| generateBoxingUnboxingInstructionFor(initializer, ((PsiArrayType)type).getComponentType()); |
| } |
| addInstruction(new PopInstruction()); |
| } |
| pushUnknown(); |
| finishElement(expression); |
| } |
| |
| @Override |
| public void visitPolyadicExpression(PsiPolyadicExpression expression) { |
| startElement(expression); |
| |
| DfaValue dfaValue = myFactory.createValue(expression); |
| if (dfaValue != null) { |
| addInstruction(new PushInstruction(dfaValue, expression)); |
| finishElement(expression); |
| return; |
| } |
| IElementType op = expression.getOperationTokenType(); |
| |
| PsiExpression[] operands = expression.getOperands(); |
| if (operands.length <= 1) { |
| pushUnknown(); |
| finishElement(expression); |
| return; |
| } |
| PsiType type = expression.getType(); |
| if (op == JavaTokenType.ANDAND) { |
| generateAndExpression(operands, type, true); |
| } |
| else if (op == JavaTokenType.OROR) { |
| generateOrExpression(operands, type, true); |
| } |
| else if (op == JavaTokenType.XOR && PsiType.BOOLEAN.equals(type)) { |
| generateXorExpression(expression, operands, type, false); |
| } |
| else if (op == JavaTokenType.AND && PsiType.BOOLEAN.equals(type)) { |
| generateAndExpression(operands, type, false); |
| } |
| else if (op == JavaTokenType.OR && PsiType.BOOLEAN.equals(type)) { |
| generateOrExpression(operands, type, false); |
| } |
| else { |
| generateOther(expression, op, operands, type); |
| } |
| finishElement(expression); |
| } |
| |
| private void generateOther(PsiPolyadicExpression expression, IElementType op, PsiExpression[] operands, PsiType type) { |
| op = substituteBinaryOperation(op, type); |
| |
| PsiExpression lExpr = operands[0]; |
| lExpr.accept(this); |
| PsiType lType = lExpr.getType(); |
| |
| for (int i = 1; i < operands.length; i++) { |
| PsiExpression rExpr = operands[i]; |
| PsiType rType = rExpr.getType(); |
| |
| acceptBinaryRightOperand(op, type, lExpr, lType, rExpr, rType); |
| addInstruction(new BinopInstruction(op, expression.isPhysical() ? expression : null, expression.getProject())); |
| |
| lExpr = rExpr; |
| lType = rType; |
| } |
| } |
| |
| @Nullable |
| private static IElementType substituteBinaryOperation(IElementType op, PsiType type) { |
| if (JavaTokenType.PLUS == op && (type == null || !type.equalsToText(JAVA_LANG_STRING))) { |
| return null; |
| } |
| return op; |
| } |
| |
| private void acceptBinaryRightOperand(@Nullable IElementType op, PsiType type, |
| PsiExpression lExpr, PsiType lType, |
| PsiExpression rExpr, PsiType rType) { |
| boolean comparing = op == JavaTokenType.EQEQ || op == JavaTokenType.NE; |
| boolean comparingRef = comparing |
| && !TypeConversionUtil.isPrimitiveAndNotNull(lType) |
| && !TypeConversionUtil.isPrimitiveAndNotNull(rType); |
| |
| boolean comparingPrimitiveNumeric = comparing && |
| TypeConversionUtil.isPrimitiveAndNotNull(lType) && |
| TypeConversionUtil.isPrimitiveAndNotNull(rType) && |
| TypeConversionUtil.isNumericType(lType) && |
| TypeConversionUtil.isNumericType(rType); |
| |
| PsiType castType = comparingPrimitiveNumeric ? TypeConversionUtil.isFloatOrDoubleType(lType) ? PsiType.DOUBLE : PsiType.LONG : type; |
| |
| if (!comparingRef) { |
| generateBoxingUnboxingInstructionFor(lExpr,castType); |
| } |
| |
| rExpr.accept(this); |
| if (!comparingRef) { |
| generateBoxingUnboxingInstructionFor(rExpr, castType); |
| } |
| } |
| |
| private void generateBoxingUnboxingInstructionFor(PsiExpression expression, PsiType expectedType) { |
| PsiType exprType = expression.getType(); |
| |
| if (TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && TypeConversionUtil.isPrimitiveWrapper(exprType)) { |
| addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.UNBOXING, expectedType)); |
| } |
| else if (TypeConversionUtil.isAssignableFromPrimitiveWrapper(expectedType) && TypeConversionUtil.isPrimitiveAndNotNull(exprType)) { |
| addConditionalRuntimeThrow(); |
| addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.BOXING, expectedType)); |
| } |
| else if (exprType != expectedType && |
| TypeConversionUtil.isPrimitiveAndNotNull(exprType) && |
| TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && |
| TypeConversionUtil.isNumericType(exprType) && |
| TypeConversionUtil.isNumericType(expectedType)) { |
| addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.CAST, expectedType) { |
| @Override |
| public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) { |
| return visitor.visitCast(this, runner, stateBefore); |
| } |
| }); |
| } |
| } |
| |
| private void generateXorExpression(PsiExpression expression, PsiExpression[] operands, final PsiType exprType, boolean forAssignment) { |
| PsiExpression operand = operands[0]; |
| operand.accept(this); |
| if (forAssignment) { |
| addInstruction(new DupInstruction()); |
| } |
| generateBoxingUnboxingInstructionFor(operand, exprType); |
| for (int i = 1; i < operands.length; i++) { |
| operand = operands[i]; |
| operand.accept(this); |
| generateBoxingUnboxingInstructionFor(operand, exprType); |
| PsiElement psiAnchor = expression.isPhysical() ? expression : null; |
| addInstruction(new BinopInstruction(JavaTokenType.NE, psiAnchor, expression.getProject())); |
| } |
| } |
| |
| private void generateOrExpression(PsiExpression[] operands, final PsiType exprType, boolean shortCircuit) { |
| for (int i = 0; i < operands.length; i++) { |
| PsiExpression operand = operands[i]; |
| operand.accept(this); |
| generateBoxingUnboxingInstructionFor(operand, exprType); |
| if (!shortCircuit) { |
| if (i > 0) { |
| combineStackBooleans(false, operand); |
| } |
| continue; |
| } |
| |
| PsiExpression nextOperand = i == operands.length - 1 ? null : operands[i + 1]; |
| if (nextOperand != null) { |
| addInstruction(new ConditionalGotoInstruction(getStartOffset(nextOperand), true, operand)); |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null)); |
| addInstruction(new GotoInstruction(getEndOffset(operands[operands.length - 1]))); |
| } |
| } |
| } |
| |
| private void generateBooleanAssignmentExpression(boolean and, PsiExpression lExpression, PsiExpression rExpression, PsiType exprType) { |
| lExpression.accept(this); |
| generateBoxingUnboxingInstructionFor(lExpression, exprType); |
| addInstruction(new DupInstruction()); |
| |
| rExpression.accept(this); |
| generateBoxingUnboxingInstructionFor(rExpression, exprType); |
| addInstruction(new SwapInstruction()); |
| |
| combineStackBooleans(and, lExpression); |
| } |
| |
| private void combineStackBooleans(boolean and, PsiExpression anchor) { |
| ConditionalGotoInstruction toPopAndPushSuccess = new ConditionalGotoInstruction(null, and, anchor); |
| addInstruction(toPopAndPushSuccess); |
| GotoInstruction overPushSuccess = new GotoInstruction(null); |
| addInstruction(overPushSuccess); |
| |
| PopInstruction pop = new PopInstruction(); |
| addInstruction(pop); |
| DfaConstValue constValue = and ? myFactory.getConstFactory().getFalse() : myFactory.getConstFactory().getTrue(); |
| PushInstruction pushSuccess = new PushInstruction(constValue, null); |
| addInstruction(pushSuccess); |
| |
| toPopAndPushSuccess.setOffset(pop.getIndex()); |
| overPushSuccess.setOffset(pushSuccess.getIndex() + 1); |
| } |
| |
| private void generateAndExpression(PsiExpression[] operands, final PsiType exprType, boolean shortCircuit) { |
| List<ConditionalGotoInstruction> branchToFail = new ArrayList<ConditionalGotoInstruction>(); |
| for (int i = 0; i < operands.length; i++) { |
| PsiExpression operand = operands[i]; |
| operand.accept(this); |
| generateBoxingUnboxingInstructionFor(operand, exprType); |
| |
| if (!shortCircuit) { |
| if (i > 0) { |
| combineStackBooleans(true, operand); |
| } |
| continue; |
| } |
| |
| ConditionalGotoInstruction onFail = new ConditionalGotoInstruction(null, true, operand); |
| branchToFail.add(onFail); |
| addInstruction(onFail); |
| } |
| |
| if (!shortCircuit) { |
| return; |
| } |
| |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null)); |
| GotoInstruction toSuccess = new GotoInstruction(null); |
| addInstruction(toSuccess); |
| PushInstruction pushFalse = new PushInstruction(myFactory.getConstFactory().getFalse(), null); |
| addInstruction(pushFalse); |
| for (ConditionalGotoInstruction toFail : branchToFail) { |
| toFail.setOffset(pushFalse.getIndex()); |
| } |
| toSuccess.setOffset(pushFalse.getIndex()+1); |
| |
| } |
| |
| @Override public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { |
| startElement(expression); |
| PsiElement[] children = expression.getChildren(); |
| for (PsiElement child : children) { |
| child.accept(this); |
| } |
| pushUnknown(); |
| finishElement(expression); |
| } |
| |
| @Override public void visitConditionalExpression(PsiConditionalExpression expression) { |
| startElement(expression); |
| |
| PsiExpression condition = expression.getCondition(); |
| |
| PsiExpression thenExpression = expression.getThenExpression(); |
| PsiExpression elseExpression = expression.getElseExpression(); |
| |
| final ControlFlow.ControlFlowOffset elseOffset = elseExpression == null ? ControlFlow.deltaOffset(getEndOffset(expression), -1) : getStartOffset(elseExpression); |
| if (thenExpression != null) { |
| condition.accept(this); |
| generateBoxingUnboxingInstructionFor(condition, PsiType.BOOLEAN); |
| PsiType type = expression.getType(); |
| addInstruction(new ConditionalGotoInstruction(elseOffset, true, condition)); |
| thenExpression.accept(this); |
| generateBoxingUnboxingInstructionFor(thenExpression,type); |
| |
| addInstruction(new GotoInstruction(getEndOffset(expression))); |
| |
| if (elseExpression != null) { |
| elseExpression.accept(this); |
| generateBoxingUnboxingInstructionFor(elseExpression,type); |
| } |
| else { |
| pushUnknown(); |
| } |
| } |
| else { |
| pushUnknown(); |
| } |
| |
| finishElement(expression); |
| } |
| |
| private void pushUnknown() { |
| addInstruction(new PushInstruction(DfaUnknownValue.getInstance(), null)); |
| } |
| |
| @Override public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { |
| startElement(expression); |
| PsiExpression operand = expression.getOperand(); |
| PsiTypeElement checkType = expression.getCheckType(); |
| if (checkType != null) { |
| operand.accept(this); |
| PsiType type = checkType.getType(); |
| if (type instanceof PsiClassType) { |
| type = ((PsiClassType)type).rawType(); |
| } |
| addInstruction(new PushInstruction(myFactory.createTypeValue(type, Nullness.UNKNOWN), null)); |
| addInstruction(new InstanceofInstruction(expression, expression.getProject(), operand, type)); |
| } |
| else { |
| pushUnknown(); |
| } |
| |
| finishElement(expression); |
| } |
| |
| private void addMethodThrows(PsiMethod method, @Nullable PsiElement explicitCall) { |
| if (method != null) { |
| PsiClassType[] refs = method.getThrowsList().getReferencedTypes(); |
| for (PsiClassType ref : refs) { |
| pushUnknown(); |
| ConditionalGotoInstruction cond = new ConditionalGotoInstruction(null, false, null); |
| addInstruction(cond); |
| addInstruction(new EmptyStackInstruction()); |
| initException(ref); |
| addThrowCode(false, explicitCall); |
| cond.setOffset(myCurrentFlow.getInstructionCount()); |
| } |
| } |
| } |
| |
| private void initException(PsiType ref) { |
| addInstruction(new PushInstruction(myExceptionHolder, null)); |
| addInstruction(new PushInstruction(myFactory.createTypeValue(ref, Nullness.NOT_NULL), null)); |
| addInstruction(new AssignInstruction(null)); |
| addInstruction(new PopInstruction()); |
| } |
| |
| @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { |
| startElement(expression); |
| |
| PsiReferenceExpression methodExpression = expression.getMethodExpression(); |
| PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); |
| |
| if (qualifierExpression != null) { |
| qualifierExpression.accept(this); |
| } |
| else { |
| pushUnknown(); |
| } |
| |
| PsiExpression[] expressions = expression.getArgumentList().getExpressions(); |
| PsiElement method = methodExpression.resolve(); |
| PsiParameter[] parameters = method instanceof PsiMethod ? ((PsiMethod)method).getParameterList().getParameters() : null; |
| boolean isEqualsCall = expressions.length == 1 && method instanceof PsiMethod && |
| "equals".equals(((PsiMethod)method).getName()) && parameters.length == 1 && |
| parameters[0].getType().equalsToText(JAVA_LANG_OBJECT) && |
| PsiType.BOOLEAN.equals(((PsiMethod)method).getReturnType()); |
| |
| for (int i = 0; i < expressions.length; i++) { |
| PsiExpression paramExpr = expressions[i]; |
| paramExpr.accept(this); |
| if (parameters != null && i < parameters.length) { |
| generateBoxingUnboxingInstructionFor(paramExpr, parameters[i].getType()); |
| } |
| if (i == 0 && isEqualsCall) { |
| // stack: .., qualifier, arg1 |
| addInstruction(new SwapInstruction()); |
| // stack: .., arg1, qualifier |
| addInstruction(new DupInstruction(2, 1)); |
| // stack: .., arg1, qualifier, arg1, qualifier |
| addInstruction(new PopInstruction()); |
| // stack: .., arg1, qualifier, arg1 |
| } |
| } |
| |
| addConditionalRuntimeThrow(); |
| List<MethodContract> contracts = method instanceof PsiMethod ? getMethodCallContracts((PsiMethod)method, expression) : Collections.<MethodContract>emptyList(); |
| addInstruction(new MethodCallInstruction(expression, myFactory.createValue(expression), contracts)); |
| if (!contracts.isEmpty()) { |
| // if a contract resulted in 'fail', handle it |
| addInstruction(new DupInstruction()); |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getContractFail(), null)); |
| addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, expression.getProject())); |
| ConditionalGotoInstruction ifNotFail = new ConditionalGotoInstruction(null, true, null); |
| addInstruction(ifNotFail); |
| returnCheckingFinally(true, expression); |
| ifNotFail.setOffset(myCurrentFlow.getInstructionCount()); |
| } |
| |
| if (!myCatchStack.isEmpty()) { |
| addMethodThrows(expression.resolveMethod(), expression); |
| } |
| |
| if (isEqualsCall) { |
| // assume equals argument must be not-null if the result is true |
| // don't assume the call result to be false if arg1==null |
| |
| // stack: .., arg1, call-result |
| ConditionalGotoInstruction ifFalse = addInstruction(new ConditionalGotoInstruction(null, true, null)); |
| |
| addInstruction(new ApplyNotNullInstruction(expression)); |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null)); |
| addInstruction(new GotoInstruction(getEndOffset(expression))); |
| |
| ifFalse.setOffset(myCurrentFlow.getInstructionCount()); |
| addInstruction(new PopInstruction()); |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getFalse(), null)); |
| } |
| finishElement(expression); |
| } |
| |
| private static List<MethodContract> getMethodCallContracts(@NotNull final PsiMethod method, @NotNull PsiMethodCallExpression call) { |
| List<MethodContract> contracts = HardcodedContracts.getHardcodedContracts(method, call); |
| return !contracts.isEmpty() ? contracts : getMethodContracts(method); |
| } |
| |
| static List<MethodContract> getMethodContracts(@NotNull final PsiMethod method) { |
| final PsiAnnotation contractAnno = findContractAnnotation(method); |
| final int paramCount = method.getParameterList().getParametersCount(); |
| if (contractAnno != null) { |
| if (AnnotationUtil.isInferredAnnotation(contractAnno) && PsiUtil.canBeOverriden(method)) { |
| return Collections.emptyList(); |
| } |
| |
| return CachedValuesManager.getCachedValue(contractAnno, new CachedValueProvider<List<MethodContract>>() { |
| @Nullable |
| @Override |
| public Result<List<MethodContract>> compute() { |
| String text = AnnotationUtil.getStringAttributeValue(contractAnno, null); |
| if (text != null) { |
| try { |
| List<MethodContract> applicable = ContainerUtil.filter(MethodContract.parseContract(text), new Condition<MethodContract>() { |
| @Override |
| public boolean value(MethodContract contract) { |
| return contract.arguments.length == paramCount; |
| } |
| }); |
| return Result.create(applicable, contractAnno); |
| } |
| catch (Exception ignored) { |
| } |
| } |
| return Result.create(Collections.<MethodContract>emptyList(), contractAnno); |
| } |
| }); |
| } |
| |
| return Collections.emptyList(); |
| } |
| |
| @Nullable |
| public static PsiAnnotation findContractAnnotation(PsiMethod method) { |
| return AnnotationUtil.findAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT); |
| } |
| |
| @Override public void visitNewExpression(PsiNewExpression expression) { |
| startElement(expression); |
| |
| pushUnknown(); |
| |
| if (expression.getType() instanceof PsiArrayType) { |
| final PsiExpression[] dimensions = expression.getArrayDimensions(); |
| for (final PsiExpression dimension : dimensions) { |
| dimension.accept(this); |
| } |
| for (PsiExpression ignored : dimensions) { |
| addInstruction(new PopInstruction()); |
| } |
| final PsiArrayInitializerExpression arrayInitializer = expression.getArrayInitializer(); |
| if (arrayInitializer != null) { |
| for (final PsiExpression initializer : arrayInitializer.getInitializers()) { |
| initializer.accept(this); |
| addInstruction(new PopInstruction()); |
| } |
| } |
| addConditionalRuntimeThrow(); |
| addInstruction(new MethodCallInstruction(expression, null, Collections.<MethodContract>emptyList())); |
| } |
| else { |
| final PsiExpressionList args = expression.getArgumentList(); |
| PsiMethod ctr = expression.resolveConstructor(); |
| if (args != null) { |
| PsiExpression[] params = args.getExpressions(); |
| PsiParameter[] parameters = ctr == null ? null : ctr.getParameterList().getParameters(); |
| for (int i = 0; i < params.length; i++) { |
| PsiExpression param = params[i]; |
| param.accept(this); |
| if (parameters != null && i < parameters.length) { |
| generateBoxingUnboxingInstructionFor(param, parameters[i].getType()); |
| } |
| } |
| } |
| |
| addConditionalRuntimeThrow(); |
| addInstruction(new MethodCallInstruction(expression, null, Collections.<MethodContract>emptyList())); |
| |
| if (!myCatchStack.isEmpty()) { |
| addMethodThrows(ctr, expression); |
| } |
| |
| } |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitParenthesizedExpression(PsiParenthesizedExpression expression) { |
| startElement(expression); |
| PsiExpression inner = expression.getExpression(); |
| if (inner != null) { |
| inner.accept(this); |
| } |
| else { |
| pushUnknown(); |
| } |
| finishElement(expression); |
| } |
| |
| @Override public void visitPostfixExpression(PsiPostfixExpression expression) { |
| startElement(expression); |
| |
| PsiExpression operand = expression.getOperand(); |
| operand.accept(this); |
| generateBoxingUnboxingInstructionFor(operand, PsiType.INT); |
| |
| addInstruction(new PopInstruction()); |
| pushUnknown(); |
| |
| if (operand instanceof PsiReferenceExpression) { |
| PsiVariable psiVariable = DfaValueFactory.resolveUnqualifiedVariable((PsiReferenceExpression)expression.getOperand()); |
| if (psiVariable != null) { |
| DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(psiVariable, false); |
| addInstruction(new FlushVariableInstruction(dfaVariable)); |
| if (psiVariable instanceof PsiField) { |
| addInstruction(new FlushVariableInstruction(null)); |
| } |
| } |
| } |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitPrefixExpression(PsiPrefixExpression expression) { |
| startElement(expression); |
| |
| DfaValue dfaValue = myFactory.createValue(expression); |
| if (dfaValue == null) { |
| PsiExpression operand = expression.getOperand(); |
| |
| if (operand == null) { |
| pushUnknown(); |
| } |
| else { |
| operand.accept(this); |
| PsiType type = expression.getType(); |
| PsiPrimitiveType unboxed = PsiPrimitiveType.getUnboxedType(type); |
| generateBoxingUnboxingInstructionFor(operand, unboxed == null ? type : unboxed); |
| if (expression.getOperationTokenType() == JavaTokenType.EXCL) { |
| addInstruction(new NotInstruction()); |
| } |
| else { |
| addInstruction(new PopInstruction()); |
| pushUnknown(); |
| |
| if (operand instanceof PsiReferenceExpression) { |
| PsiVariable psiVariable = DfaValueFactory.resolveUnqualifiedVariable((PsiReferenceExpression)operand); |
| if (psiVariable != null) { |
| DfaVariableValue dfaVariable = myFactory.getVarFactory().createVariableValue(psiVariable, false); |
| addInstruction(new FlushVariableInstruction(dfaVariable)); |
| if (psiVariable instanceof PsiField) { |
| addInstruction(new FlushVariableInstruction(null)); |
| } |
| } |
| } |
| } |
| } |
| } |
| else { |
| addInstruction(new PushInstruction(dfaValue, expression)); |
| } |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitReferenceExpression(PsiReferenceExpression expression) { |
| startElement(expression); |
| |
| final PsiExpression qualifierExpression = expression.getQualifierExpression(); |
| if (qualifierExpression != null) { |
| qualifierExpression.accept(this); |
| addInstruction(expression.resolve() instanceof PsiField ? new FieldReferenceInstruction(expression, null) : new PopInstruction()); |
| } |
| |
| boolean referenceRead = PsiUtil.isAccessedForReading(expression) && !PsiUtil.isAccessedForWriting(expression); |
| addInstruction(new PushInstruction(myFactory.createValue(expression), expression, referenceRead)); |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitSuperExpression(PsiSuperExpression expression) { |
| startElement(expression); |
| addInstruction(new PushInstruction(myFactory.createTypeValue(expression.getType(), Nullness.NOT_NULL), null)); |
| finishElement(expression); |
| } |
| |
| @Override public void visitThisExpression(PsiThisExpression expression) { |
| startElement(expression); |
| addInstruction(new PushInstruction(myFactory.createTypeValue(expression.getType(), Nullness.NOT_NULL), null)); |
| finishElement(expression); |
| } |
| |
| @Override public void visitLiteralExpression(PsiLiteralExpression expression) { |
| startElement(expression); |
| |
| DfaValue dfaValue = myFactory.createLiteralValue(expression); |
| addInstruction(new PushInstruction(dfaValue, expression)); |
| |
| finishElement(expression); |
| } |
| |
| @Override public void visitTypeCastExpression(PsiTypeCastExpression castExpression) { |
| startElement(castExpression); |
| PsiExpression operand = castExpression.getOperand(); |
| |
| if (operand != null) { |
| operand.accept(this); |
| generateBoxingUnboxingInstructionFor(operand, castExpression.getType()); |
| } |
| else { |
| addInstruction(new PushInstruction(myFactory.createTypeValue(castExpression.getType(), Nullness.UNKNOWN), null)); |
| } |
| |
| final PsiTypeElement typeElement = castExpression.getCastType(); |
| if (typeElement != null && operand != null && operand.getType() != null) { |
| if (typeElement.getType() instanceof PsiPrimitiveType && |
| UnnecessaryExplicitNumericCastInspection.isPrimitiveNumericCastNecessary(castExpression)) { |
| addInstruction(new PopInstruction()); |
| pushUnknown(); |
| } else { |
| addInstruction(new TypeCastInstruction(castExpression, operand, typeElement.getType())); |
| } |
| } |
| finishElement(castExpression); |
| } |
| |
| @Override public void visitClass(PsiClass aClass) { |
| } |
| |
| } |
| |