| /* |
| * 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.codeInsight.ConditionCheckManager; |
| import com.intellij.codeInsight.ConditionChecker; |
| import com.intellij.codeInsight.ExceptionUtil; |
| 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.openapi.util.text.StringUtil; |
| 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.SmartList; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.Stack; |
| import org.jetbrains.annotations.Contract; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| import static com.intellij.codeInsight.ConditionChecker.Type.*; |
| import static com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint; |
| import static com.intellij.psi.CommonClassNames.*; |
| |
| class ControlFlowAnalyzer extends JavaElementVisitor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer"); |
| private static final ControlFlow.ControlFlowOffset NOT_FOUND = new ControlFlow.ControlFlowOffset() { |
| @Override |
| public int getInstructionOffset() { |
| throw new UnsupportedOperationException("Not implemented"); |
| } |
| }; |
| 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 Set<DfaVariableValue> myFields; |
| private Stack<CatchDescriptor> myCatchStack; |
| private DfaValue myRuntimeException; |
| private DfaValue myError; |
| private PsiType myNpe; |
| private Stack<PsiElement> myElementStack = new Stack<PsiElement>(); |
| |
| 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); |
| myFields = new HashSet<DfaVariableValue>(); |
| myCatchStack = new Stack<CatchDescriptor>(); |
| myCurrentFlow = new ControlFlow(myFactory); |
| |
| try { |
| codeFragment.accept(this); |
| } |
| catch (CannotAnalyzeException e) { |
| return null; |
| } |
| |
| myCurrentFlow.setFields(myFields.toArray(new DfaVariableValue[myFields.size()])); |
| |
| addInstruction(new ReturnInstruction()); |
| |
| 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; |
| } |
| |
| lExpr.accept(this); |
| |
| IElementType op = expression.getOperationTokenType(); |
| PsiType type = expression.getType(); |
| boolean isBoolean = PsiType.BOOLEAN.equals(type); |
| if (op == JavaTokenType.EQ) { |
| rExpr.accept(this); |
| generateBoxingUnboxingInstructionFor(rExpr, type); |
| } |
| else if (op == JavaTokenType.ANDEQ) { |
| if (isBoolean) { |
| generateNonLazyExpression(true, lExpr, rExpr, type); |
| } |
| else { |
| generateDefaultBinOp(lExpr, rExpr, type); |
| } |
| } |
| else if (op == JavaTokenType.OREQ) { |
| if (isBoolean) { |
| generateNonLazyExpression(false, lExpr, rExpr, type); |
| } |
| else { |
| generateDefaultBinOp(lExpr, rExpr, type); |
| } |
| } |
| else if (op == JavaTokenType.XOREQ) { |
| if (isBoolean) { |
| generateXorExpression(expression, new PsiExpression[]{lExpr, rExpr}, type); |
| } |
| else { |
| generateDefaultBinOp(lExpr, rExpr, type); |
| } |
| } |
| else if (op == JavaTokenType.PLUSEQ && type != null && type.equalsToText(JAVA_LANG_STRING)) { |
| lExpr.accept(this); |
| rExpr.accept(this); |
| addInstruction(new BinopInstruction(JavaTokenType.PLUS, null, lExpr.getProject())); |
| } |
| else { |
| generateDefaultBinOp(lExpr, rExpr, type); |
| } |
| |
| addInstruction(new AssignInstruction(rExpr)); |
| finishElement(expression); |
| } |
| |
| private void generateDefaultBinOp(PsiExpression lExpr, PsiExpression rExpr, final PsiType exprType) { |
| lExpr.accept(this); |
| 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); |
| } |
| addInstruction(new ReturnInstruction()); |
| } |
| 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); |
| } |
| } |
| } |
| } |
| } |
| |
| @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 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(statement)); |
| } |
| |
| returnCheckingFinally(); |
| finishElement(statement); |
| } |
| |
| private void returnCheckingFinally() { |
| ControlFlow.ControlFlowOffset finallyOffset = getFinallyOffset(); |
| if (finallyOffset != NOT_FOUND) { |
| addInstruction(new GosubInstruction(finallyOffset)); |
| } |
| addInstruction(new ReturnInstruction()); |
| } |
| |
| @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 && psiClass.isEnum()) { |
| addInstruction(new FieldReferenceInstruction(caseExpression, "switch statement expression")); |
| 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(getExpressionDfaValue((PsiReferenceExpression)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); |
| 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 PushInstruction(myFactory.createTypeValue(myNpe, Nullness.NOT_NULL), null)); |
| addThrowCode(myNpe); |
| gotoInstruction.setOffset(myCurrentFlow.getInstructionCount()); |
| addThrowCode(exception.getType()); |
| } |
| |
| finishElement(statement); |
| } |
| |
| private void addConditionalRuntimeThrow() { |
| for (int i = myCatchStack.size() - 1; i >= 0; i--) { |
| CatchDescriptor cd = myCatchStack.get(i); |
| if (cd.isFinally()) { |
| addConditionalRuntimeThrow(cd, false); |
| continue; |
| } |
| |
| PsiType type = cd.getLubType(); |
| if (type instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)type)) { |
| addConditionalRuntimeThrow(cd, true); |
| } |
| } |
| } |
| |
| private void addConditionalRuntimeThrow(CatchDescriptor cd, boolean forCatch) { |
| pushUnknown(); |
| final ConditionalGotoInstruction branch = new ConditionalGotoInstruction(null, false, null); |
| addInstruction(branch); |
| addInstruction(new EmptyStackInstruction()); |
| flushVariablesInsideTry(cd); |
| |
| if (forCatch) { |
| PsiType type = cd.getLubType(); |
| boolean isRuntime = InheritanceUtil.isInheritor(type, JAVA_LANG_RUNTIME_EXCEPTION) || ExceptionUtil.isGeneralExceptionType(type); |
| boolean isError = InheritanceUtil.isInheritor(type, JAVA_LANG_ERROR) || type.equalsToText(JAVA_LANG_THROWABLE); |
| if (isRuntime != isError) { |
| addInstruction(new PushInstruction(isRuntime ? myRuntimeException : myError, null)); |
| addGotoCatch(cd); |
| } else { |
| pushUnknown(); |
| final ConditionalGotoInstruction branch2 = new ConditionalGotoInstruction(null, false, null); |
| addInstruction(branch2); |
| addInstruction(new PushInstruction(myError, null)); |
| addGotoCatch(cd); |
| branch2.setOffset(myCurrentFlow.getInstructionCount()); |
| addInstruction(new PushInstruction(myRuntimeException, null)); |
| addGotoCatch(cd); |
| } |
| } |
| else { |
| addInstruction(new GosubInstruction(cd.getJumpOffset(this))); |
| addInstruction(new ReturnInstruction()); |
| } |
| branch.setOffset(myCurrentFlow.getInstructionCount()); |
| } |
| |
| private void flushVariablesInsideTry(CatchDescriptor cd) { |
| flushVariablesOnControlTransfer(cd.getBlock()); |
| } |
| |
| 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); |
| if (scope.getParent() instanceof PsiTryStatement && scope == ((PsiTryStatement)scope.getParent()).getFinallyBlock()) { |
| addInstruction(new PopOffsetInstruction()); |
| } |
| } |
| } |
| } |
| |
| private void addThrowCode(PsiType exceptionClass) { |
| if (exceptionClass == null) return; |
| for (int i = myCatchStack.size() - 1; i >= 0; i--) { |
| CatchDescriptor cd = myCatchStack.get(i); |
| if (cd.isFinally()) { |
| flushVariablesInsideTry(cd); |
| addInstruction(new GosubInstruction(cd.getJumpOffset(this))); |
| break; |
| } |
| else if (cd.getType().isAssignableFrom(exceptionClass)) { // Definite catch. |
| flushVariablesInsideTry(cd); |
| addGotoCatch(cd); |
| return; |
| } |
| else if (cd.getType().isConvertibleFrom(exceptionClass)) { // Probable catch |
| addInstruction(new DupInstruction()); |
| pushUnknown(); |
| final ConditionalGotoInstruction branch = new ConditionalGotoInstruction(null, false, null); |
| addInstruction(branch); |
| flushVariablesInsideTry(cd); |
| addGotoCatch(cd); |
| branch.setOffset(myCurrentFlow.getInstructionCount()); |
| } |
| } |
| |
| addInstruction(new ReturnInstruction()); |
| } |
| |
| /** |
| * Exception is expected on the stack. |
| */ |
| private void addGotoCatch(CatchDescriptor cd) { |
| addInstruction(new PushInstruction(myFactory.getVarFactory().createVariableValue(cd.getParameter(), false), null)); |
| addInstruction(new SwapInstruction()); |
| myCurrentFlow.addInstruction(new AssignInstruction(null)); |
| addInstruction(new PopInstruction()); |
| addInstruction(new GotoInstruction(cd.getJumpOffset(this))); |
| } |
| |
| 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 NOT_FOUND; |
| } |
| |
| 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 PsiType getType() { |
| return myType; |
| } |
| |
| PsiType getLubType() { |
| PsiType type = myType; |
| if (type instanceof PsiDisjunctionType) { |
| return ((PsiDisjunctionType)type).getLeastUpperBound(); |
| } |
| return type; |
| } |
| |
| public boolean isFinally() { |
| return myIsFinally; |
| } |
| |
| public ControlFlow.ControlFlowOffset getJumpOffset(ControlFlowAnalyzer analyzer) { |
| return analyzer.getStartOffset(myBlock); |
| } |
| |
| 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)); |
| } |
| |
| int catchesPushCount = 0; |
| 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)); |
| catchesPushCount++; |
| continue; |
| } |
| } |
| throw new CannotAnalyzeException(); |
| } |
| |
| ControlFlow.ControlFlowOffset endOffset = finallyBlock == null ? getEndOffset(statement) : ControlFlow.deltaOffset(getStartOffset(finallyBlock), -2); |
| |
| if (resourceList != null) { |
| resourceList.accept(this); |
| } |
| |
| if (tryBlock != null) { |
| tryBlock.accept(this); |
| } |
| |
| for (int i = 0; i < catchesPushCount; i++) { |
| myCatchStack.pop(); |
| } |
| |
| addInstruction(new GotoInstruction(endOffset)); |
| |
| for (PsiCatchSection section : sections) { |
| section.accept(this); |
| addInstruction(new GotoInstruction(endOffset)); |
| } |
| |
| if (finallyBlock != null) { |
| myCatchStack.pop(); |
| addInstruction(new GosubInstruction(getStartOffset(finallyBlock))); |
| addInstruction(new GotoInstruction(getEndOffset(statement))); |
| finallyBlock.accept(this); |
| addInstruction(new ReturnFromSubInstruction()); |
| } |
| |
| finishElement(statement); |
| } |
| |
| @Override |
| public void visitCatchSection(PsiCatchSection section) { |
| PsiCodeBlock catchBlock = section.getCatchBlock(); |
| if (catchBlock != null) { |
| catchBlock.accept(this); |
| } |
| } |
| |
| @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); |
| } |
| } |
| } |
| |
| @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()); |
| } |
| |
| pushTypeOrUnknown(arrayExpression); |
| 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); |
| } |
| 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) { |
| PsiExpression operand = operands[0]; |
| operand.accept(this); |
| 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 generateNonLazyExpression(boolean and, PsiExpression lExpression, PsiExpression rExpression, PsiType exprType) { |
| rExpression.accept(this); |
| generateBoxingUnboxingInstructionFor(rExpression, exprType); |
| |
| lExpression.accept(this); |
| generateBoxingUnboxingInstructionFor(lExpression, exprType); |
| |
| 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) { |
| 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()); |
| addInstruction(new PushInstruction(myFactory.createTypeValue(ref, Nullness.NOT_NULL), null)); |
| addThrowCode(ref); |
| cond.setOffset(myCurrentFlow.getInstructionCount()); |
| } |
| } |
| } |
| |
| @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { |
| startElement(expression); |
| |
| if (handleContracts(expression, getCallContracts(expression))) { |
| finishElement(expression); |
| return; |
| } |
| |
| 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; |
| 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()); |
| } |
| } |
| |
| addConditionalRuntimeThrow(); |
| addInstruction(new MethodCallInstruction(expression, createChainedVariableValue(expression))); |
| |
| if (!myCatchStack.isEmpty()) { |
| addMethodThrows(expression.resolveMethod()); |
| } |
| |
| if (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())) { |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getFalse(), null)); |
| addInstruction(new SwapInstruction()); |
| addInstruction(new ConditionalGotoInstruction(getEndOffset(expression), true, null)); |
| |
| addInstruction(new PopInstruction()); |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null)); |
| |
| expressions[0].accept(this); |
| addInstruction(new ApplyNotNullInstruction(expression)); |
| } |
| finishElement(expression); |
| } |
| |
| private boolean handleContracts(PsiMethodCallExpression expression, List<MethodContract> _contracts) { |
| if (_contracts.isEmpty()) { |
| return false; |
| } |
| |
| final PsiExpression[] args = expression.getArgumentList().getExpressions(); |
| List<MethodContract> contracts = ContainerUtil.findAll(_contracts, new Condition<MethodContract>() { |
| @Override |
| public boolean value(MethodContract contract) { |
| return args.length == contract.arguments.length; |
| } |
| }); |
| if (contracts.isEmpty()) { |
| return false; |
| } |
| |
| for (PsiExpression arg : args) { |
| arg.accept(this); |
| } |
| |
| if (contracts.size() > 1) { |
| addInstruction(new DupInstruction(args.length, contracts.size() - 1)); |
| } |
| for (MethodContract contract : contracts) { |
| handleContract(expression, contract); |
| } |
| pushUnknown(); // goto here if all contracts are false |
| return true; |
| } |
| |
| private void handleContract(PsiMethodCallExpression expression, MethodContract contract) { |
| PsiExpression[] args = expression.getArgumentList().getExpressions(); |
| |
| final ControlFlow.ControlFlowOffset exitPoint = getEndOffset(expression); |
| |
| List<GotoInstruction> gotoContractFalse = new SmartList<GotoInstruction>(); |
| for (int i = args.length - 1; i >= 0; i--) { |
| ValueConstraint arg = contract.arguments[i]; |
| if (arg == ValueConstraint.NULL_VALUE || arg == ValueConstraint.NOT_NULL_VALUE) { |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); |
| addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, expression.getProject())); |
| } |
| else if (arg != ValueConstraint.TRUE_VALUE && arg != ValueConstraint.FALSE_VALUE) { |
| addInstruction(new PopInstruction()); |
| continue; |
| } |
| |
| boolean expectingTrueOnStack = arg == ValueConstraint.NULL_VALUE || arg == ValueConstraint.TRUE_VALUE; |
| ConditionalGotoInstruction continueCheckingContract = addInstruction(new ConditionalGotoInstruction(null, !expectingTrueOnStack, null)); |
| |
| for (int j = 0; j < i; j++) { |
| addInstruction(new PopInstruction()); |
| } |
| gotoContractFalse.add(addInstruction(new GotoInstruction(null))); |
| continueCheckingContract.setOffset(myCurrentFlow.getInstructionCount()); |
| } |
| |
| // if contract is true |
| switch (contract.returnValue) { |
| case ANY_VALUE: |
| pushUnknown(); |
| addInstruction(new GotoInstruction(exitPoint)); |
| break; |
| case NULL_VALUE: |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null)); |
| addInstruction(new GotoInstruction(exitPoint)); |
| break; |
| case NOT_NULL_VALUE: |
| PsiType type = expression.getType(); |
| addInstruction(new PushInstruction(myFactory.createTypeValue(type, Nullness.NOT_NULL), null)); |
| addInstruction(new GotoInstruction(exitPoint)); |
| break; |
| case TRUE_VALUE: |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getTrue(), null)); |
| addInstruction(new GotoInstruction(exitPoint)); |
| break; |
| case FALSE_VALUE: |
| addInstruction(new PushInstruction(myFactory.getConstFactory().getFalse(), null)); |
| addInstruction(new GotoInstruction(exitPoint)); |
| break; |
| case THROW_EXCEPTION: |
| ControlFlow.ControlFlowOffset finallyOffset = getFinallyOffset(); |
| if (finallyOffset != NOT_FOUND) { |
| addInstruction(new GosubInstruction(finallyOffset)); |
| } |
| addInstruction(new ReturnInstruction()); |
| break; |
| case SYSTEM_EXIT: |
| addInstruction(new ReturnInstruction()); |
| break; |
| } |
| |
| // if contract is false |
| for (GotoInstruction instruction : gotoContractFalse) { |
| instruction.setOffset(myCurrentFlow.getInstructionCount()); |
| } |
| } |
| |
| private static List<MethodContract> getCallContracts(PsiMethodCallExpression expression) { |
| PsiMethod resolved = expression.resolveMethod(); |
| if (resolved != null) { |
| final PsiAnnotation contractAnno = findContractAnnotation(resolved); |
| if (contractAnno != null) { |
| 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 { |
| return Result.create(parseContract(text), contractAnno); |
| } |
| catch (Exception ignored) { |
| } |
| } |
| return Result.create(Collections.<MethodContract>emptyList(), contractAnno); |
| } |
| }); |
| } |
| |
| @NonNls String methodName = resolved.getName(); |
| |
| PsiExpression[] params = expression.getArgumentList().getExpressions(); |
| PsiClass owner = resolved.getContainingClass(); |
| if (owner != null) { |
| final String className = owner.getQualifiedName(); |
| if ("java.lang.System".equals(className)) { |
| if ("exit".equals(methodName)) { |
| return Collections.singletonList(new MethodContract(getAnyArgConstraints(params), ValueConstraint.SYSTEM_EXIT)); |
| } |
| } |
| else if ("junit.framework.Assert".equals(className) || "org.junit.Assert".equals(className) || |
| "junit.framework.TestCase".equals(className) || "org.testng.Assert".equals(className)) { |
| boolean testng = "org.testng.Assert".equals(className); |
| if ("fail".equals(methodName)) { |
| return Collections.singletonList(new MethodContract(getAnyArgConstraints(params), ValueConstraint.THROW_EXCEPTION)); |
| } |
| |
| int checkedParam = testng ? 0 : params.length - 1; |
| ValueConstraint[] constraints = getAnyArgConstraints(params); |
| if ("assertTrue".equals(methodName)) { |
| constraints[checkedParam] = ValueConstraint.FALSE_VALUE; |
| return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION)); |
| } |
| if ("assertFalse".equals(methodName)) { |
| constraints[checkedParam] = ValueConstraint.TRUE_VALUE; |
| return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION)); |
| } |
| if ("assertNull".equals(methodName)) { |
| constraints[checkedParam] = ValueConstraint.NOT_NULL_VALUE; |
| return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION)); |
| } |
| if ("assertNotNull".equals(methodName)) { |
| constraints[checkedParam] = ValueConstraint.NULL_VALUE; |
| return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION)); |
| } |
| return Collections.emptyList(); |
| } |
| } |
| |
| ConditionChecker checker = ConditionCheckManager.findConditionChecker(resolved); |
| if (checker != null) { |
| ValueConstraint[] constraints = getAnyArgConstraints(params); |
| int checkedParam = checker.getCheckedParameterIndex(); |
| if (checkedParam >= constraints.length) { |
| return Collections.emptyList(); |
| } |
| |
| ConditionChecker.Type type = checker.getConditionCheckType(); |
| if (type == ASSERT_IS_NULL_METHOD || type == ASSERT_IS_NOT_NULL_METHOD) { |
| constraints[checkedParam] = type == ASSERT_IS_NOT_NULL_METHOD ? ValueConstraint.NULL_VALUE : ValueConstraint.NOT_NULL_VALUE; |
| return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION)); |
| } else if (type == IS_NOT_NULL_METHOD || type == IS_NULL_METHOD) { |
| constraints[checkedParam] = ValueConstraint.NULL_VALUE; |
| return Collections.singletonList(new MethodContract(constraints, type == IS_NULL_METHOD ? ValueConstraint.TRUE_VALUE : ValueConstraint.FALSE_VALUE)); |
| } else { //assertTrue or assertFalse |
| constraints[checkedParam] = type == ASSERT_FALSE_METHOD ? ValueConstraint.TRUE_VALUE : ValueConstraint.FALSE_VALUE; |
| return Collections.singletonList(new MethodContract(constraints, ValueConstraint.THROW_EXCEPTION)); |
| } |
| } |
| } |
| |
| return Collections.emptyList(); |
| } |
| |
| public static PsiAnnotation findContractAnnotation(PsiMethod method) { |
| return AnnotationUtil.findAnnotation(method, ORG_JETBRAINS_ANNOTATIONS_CONTRACT); |
| } |
| |
| public static List<MethodContract> parseContract(String text) throws ParseException { |
| List<MethodContract> result = ContainerUtil.newArrayList(); |
| for (String clause : StringUtil.replace(text, " ", "").split(";")) { |
| String arrow = "->"; |
| int arrowIndex = clause.indexOf(arrow); |
| if (arrowIndex < 0) { |
| throw new ParseException("A contract clause must be in form arg1, ..., argN -> return-value"); |
| } |
| |
| String[] argStrings = clause.substring(0, arrowIndex).split(","); |
| ValueConstraint[] args = new ValueConstraint[argStrings.length]; |
| for (int i = 0; i < args.length; i++) { |
| args[i] = parseConstraint(argStrings[i]); |
| } |
| result.add(new MethodContract(args, parseConstraint(clause.substring(arrowIndex + arrow.length())))); |
| } |
| return result; |
| } |
| |
| private static ValueConstraint parseConstraint(String name) throws ParseException { |
| if (StringUtil.isEmpty(name)) throw new ParseException("Constraint should not be empty"); |
| if ("null".equals(name)) return ValueConstraint.NULL_VALUE; |
| if ("!null".equals(name)) return ValueConstraint.NOT_NULL_VALUE; |
| if ("true".equals(name)) return ValueConstraint.TRUE_VALUE; |
| if ("false".equals(name)) return ValueConstraint.FALSE_VALUE; |
| if ("exit".equals(name)) return ValueConstraint.SYSTEM_EXIT; |
| if ("fail".equals(name)) return ValueConstraint.THROW_EXCEPTION; |
| if ("_".equals(name)) return ValueConstraint.ANY_VALUE; |
| throw new ParseException("Constraint should be one of: null, !null, true, false, exit, fail, _. Found: " + name); |
| } |
| |
| public static class ParseException extends Exception { |
| private ParseException(String message) { |
| super(message); |
| } |
| } |
| |
| private static ValueConstraint[] getAnyArgConstraints(PsiExpression[] params) { |
| ValueConstraint[] args = new ValueConstraint[params.length]; |
| for (int i = 0; i < args.length; i++) { |
| args[i] = ValueConstraint.ANY_VALUE; |
| } |
| return args; |
| } |
| |
| private void pushTypeOrUnknown(PsiExpression expr) { |
| PsiType type = expr.getType(); |
| |
| final DfaValue dfaValue; |
| if (type instanceof PsiClassType) { |
| dfaValue = myFactory.createTypeValue(type, Nullness.UNKNOWN); |
| } |
| else { |
| dfaValue = null; |
| } |
| |
| addInstruction(new PushInstruction(dfaValue, null)); |
| } |
| |
| @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)); |
| } |
| 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)); |
| |
| if (!myCatchStack.isEmpty()) { |
| addMethodThrows(ctr); |
| } |
| |
| } |
| |
| 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)); |
| } |
| } |
| |
| 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)); |
| } |
| } |
| } |
| } |
| } |
| 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(getExpressionDfaValue(expression), expression, referenceRead)); |
| |
| finishElement(expression); |
| } |
| |
| @Nullable |
| private DfaValue getExpressionDfaValue(PsiReferenceExpression expression) { |
| DfaValue dfaValue = myFactory.createReferenceValue(expression); |
| if (dfaValue instanceof DfaVariableValue) { |
| DfaVariableValue dfaVariable = (DfaVariableValue)dfaValue; |
| if (dfaVariable.getPsiVariable() instanceof PsiField) { |
| myFields.add(dfaVariable); |
| } |
| } |
| if (dfaValue == null) { |
| PsiElement resolved = expression.resolve(); |
| if (resolved instanceof PsiField) { |
| dfaValue = createDfaValueForAnotherInstanceMemberAccess(expression, (PsiField)resolved); |
| } |
| } |
| return dfaValue; |
| } |
| |
| @NotNull |
| private DfaValue createDfaValueForAnotherInstanceMemberAccess(PsiReferenceExpression expression, PsiField field) { |
| DfaValue dfaValue = null; |
| if (expression.getQualifierExpression() != null) { |
| dfaValue = createChainedVariableValue(expression); |
| } |
| if (dfaValue == null) { |
| PsiType type = expression.getType(); |
| return myFactory.createTypeValue(type, DfaPsiUtil.getElementNullability(type, field)); |
| } |
| return dfaValue; |
| } |
| |
| @Nullable |
| private DfaVariableValue createChainedVariableValue(@Nullable PsiExpression expression) { |
| if (expression instanceof PsiParenthesizedExpression) { |
| return createChainedVariableValue(((PsiParenthesizedExpression)expression).getExpression()); |
| } |
| |
| PsiReferenceExpression refExpr; |
| if (expression instanceof PsiMethodCallExpression) { |
| refExpr = ((PsiMethodCallExpression)expression).getMethodExpression(); |
| } |
| else if (expression instanceof PsiReferenceExpression) { |
| refExpr = (PsiReferenceExpression)expression; |
| } |
| else { |
| return null; |
| } |
| |
| PsiElement target = refExpr.resolve(); |
| PsiModifierListOwner var = getAccessedVariable(target); |
| if (var == null) { |
| return null; |
| } |
| |
| PsiExpression qualifier = refExpr.getQualifierExpression(); |
| if (qualifier == null) { |
| DfaVariableValue result = myFactory.getVarFactory().createVariableValue(var, refExpr.getType(), false, null); |
| if (var instanceof PsiField) { |
| myFields.add(result); |
| } |
| return result; |
| } |
| |
| if (!(var instanceof PsiField) || !var.hasModifierProperty(PsiModifier.TRANSIENT) && !var.hasModifierProperty(PsiModifier.VOLATILE)) { |
| DfaVariableValue qualifierValue = createChainedVariableValue(qualifier); |
| if (qualifierValue != null) { |
| return myFactory.getVarFactory().createVariableValue(var, refExpr.getType(), false, qualifierValue); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static PsiModifierListOwner getAccessedVariable(final PsiElement target) { |
| if (target instanceof PsiVariable) { |
| return (PsiVariable)target; |
| } |
| if (target instanceof PsiMethod) { |
| if (PropertyUtil.isSimplePropertyGetter((PsiMethod)target)) { |
| return (PsiMethod)target; |
| } |
| } |
| return null; |
| } |
| |
| @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 { |
| pushTypeOrUnknown(castExpression); |
| } |
| |
| final PsiTypeElement typeElement = castExpression.getCastType(); |
| if (typeElement != null && operand != null) { |
| addInstruction(new TypeCastInstruction(castExpression, operand, typeElement.getType())); |
| } |
| finishElement(castExpression); |
| } |
| |
| @Override public void visitClass(PsiClass aClass) { |
| } |
| |
| } |
| |
| class MethodContract { |
| public final ValueConstraint[] arguments; |
| public final ValueConstraint returnValue; |
| |
| public MethodContract(ValueConstraint[] arguments, ValueConstraint returnValue) { |
| this.arguments = arguments; |
| this.returnValue = returnValue; |
| } |
| |
| public enum ValueConstraint { |
| ANY_VALUE, NULL_VALUE, NOT_NULL_VALUE, TRUE_VALUE, FALSE_VALUE, THROW_EXCEPTION, SYSTEM_EXIT |
| } |
| } |
| |