java dfa: rewrite "try "treatment to visit finally on break/continue control transfers (IDEA-55394, IDEA-156394)

This commit is contained in:
peter
2016-10-07 12:32:30 +02:00
parent 2c3307e2b9
commit 6b3e2fc6db
8 changed files with 267 additions and 305 deletions
@@ -28,8 +28,7 @@ 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.FactoryMap;
import com.intellij.util.containers.Stack;
import com.intellij.util.containers.FList;
import com.siyeh.ig.numeric.UnnecessaryExplicitNumericCastInspection;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -50,18 +49,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
private final DfaValueFactory myFactory;
private ControlFlow myCurrentFlow;
private Stack<CatchDescriptor> myCatchStack;
private final DfaValue myRuntimeException;
private final DfaValue myError;
private final DfaValue myString;
private FList<Trap> myTrapStack = FList.emptyList();
private final ExceptionTransfer myRuntimeException;
private final ExceptionTransfer myError;
private final PsiType myNpe;
private final PsiType myAssertionError;
private final Stack<PsiElement> myElementStack = new Stack<>();
/**
* Variables for try-related control transfers. Contain exceptions or an (Throwable-inconvertible) string to indicate return inside finally
*/
private FactoryMap<PsiTryStatement, DfaVariableValue> myExceptionHolders;
ControlFlowAnalyzer(final DfaValueFactory valueFactory, @NotNull PsiElement codeFragment, boolean ignoreAssertions) {
myFactory = valueFactory;
@@ -69,26 +61,14 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
myProject = codeFragment.getProject();
myIgnoreAssertions = ignoreAssertions;
GlobalSearchScope scope = codeFragment.getResolveScope();
myRuntimeException = myFactory.createTypeValue(createClassType(scope, JAVA_LANG_RUNTIME_EXCEPTION), Nullness.NOT_NULL);
myError = myFactory.createTypeValue(createClassType(scope, JAVA_LANG_ERROR), Nullness.NOT_NULL);
myRuntimeException = new ExceptionTransfer(myFactory.createTypeValue(createClassType(scope, JAVA_LANG_RUNTIME_EXCEPTION), Nullness.NOT_NULL));
myError = new ExceptionTransfer(myFactory.createTypeValue(createClassType(scope, JAVA_LANG_ERROR), Nullness.NOT_NULL));
myNpe = createClassType(scope, JAVA_LANG_NULL_POINTER_EXCEPTION);
myAssertionError = createClassType(scope, JAVA_LANG_ASSERTION_ERROR);
myString = myFactory.createTypeValue(createClassType(scope, JAVA_LANG_STRING), Nullness.NOT_NULL);
myExceptionHolders = new FactoryMap<PsiTryStatement, DfaVariableValue>() {
@Nullable
@Override
protected DfaVariableValue create(PsiTryStatement key) {
String text = "java.lang.Object $exception" + myExceptionHolders.size() + "$";
PsiParameter mockVar = JavaPsiFacade.getElementFactory(myProject).createParameterFromText(text, null);
return myFactory.getVarFactory().createVariableValue(mockVar, false);
}
};
}
@Nullable
public ControlFlow buildControlFlow() {
myCatchStack = new Stack<>();
myCurrentFlow = new ControlFlow(myFactory);
try {
myCodeFragment.accept(this);
@@ -104,7 +84,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new CheckReturnValueInstruction(myCodeFragment));
}
addInstruction(new ReturnInstruction(false, null));
addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, FList.emptyList()), null));
if (Registry.is("idea.dfa.live.variables.analysis")) {
new LiveVariablesAnalyzer(myCurrentFlow, myFactory).flushDeadVariablesOnStatementFinish();
@@ -135,15 +115,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
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);
}
if (element instanceof PsiStatement && !(element instanceof PsiReturnStatement)) {
addInstruction(new FinishElementInstruction(element));
}
@@ -250,9 +225,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
description.accept(this);
}
CatchDescriptor cd = findNextCatch(false);
initException(myAssertionError, cd);
addThrowCode(cd, statement);
throwException(myAssertionError, statement);
}
finishElement(statement);
}
@@ -369,20 +342,36 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiStatement exitedStatement = statement.findExitedStatement();
if (exitedStatement != null) {
flushVariablesOnControlTransfer(exitedStatement);
addInstruction(new GotoInstruction(getEndOffset(exitedStatement)));
controlTransfer(new InstructionTransfer(getEndOffset(exitedStatement), getVariablesInside(exitedStatement)),
getTrapsInsideStatement(exitedStatement));
}
finishElement(statement);
}
private void controlTransfer(InstructionTransfer target, FList<Trap> traps) {
addInstruction(new ControlTransferInstruction(myFactory.controlTransfer(target, traps)));
}
@NotNull
private FList<Trap> getTrapsInsideStatement(PsiStatement statement) {
return FList.createFromReversed(ContainerUtil.reverse(
ContainerUtil.findAll(myTrapStack, cd -> PsiTreeUtil.isAncestor(statement, cd.getAnchor(), true))));
}
@NotNull
private List<DfaVariableValue> getVariablesInside(PsiElement exitedStatement) {
return ContainerUtil.map(PsiTreeUtil.findChildrenOfType(exitedStatement, PsiVariable.class),
var -> myFactory.getVarFactory().createVariableValue(var, false));
}
@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)));
controlTransfer(new InstructionTransfer(getEndOffset(body), getVariablesInside(body)), getTrapsInsideStatement(body));
} else {
addInstruction(new EmptyInstruction(null));
}
@@ -590,24 +579,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new CheckReturnValueInstruction(returnValue));
}
returnCheckingFinally(false, statement);
addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, myTrapStack), statement));
finishElement(statement);
}
private void returnCheckingFinally(boolean viaException, @NotNull PsiElement anchor) {
CatchDescriptor finallyDescriptor = findFinally();
if (finallyDescriptor != null) {
addInstruction(new PushInstruction(getExceptionHolder(finallyDescriptor), null));
addInstruction(new PushInstruction(myString, null));
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
addInstruction(new GotoInstruction(finallyDescriptor.getJumpOffset(this)));
} else {
addInstruction(new ReturnInstruction(viaException, anchor));
}
}
@Override public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) {
startElement(statement);
finishElement(statement);
@@ -737,13 +712,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (exception != null) {
exception.accept(this);
CatchDescriptor cd = findNextCatch(false);
if (cd == null) {
addInstruction(new FieldReferenceInstruction(exception, "thrown exception"));
addInstruction(new ReturnInstruction(true, statement));
finishElement(statement);
return;
}
addConditionalRuntimeThrow();
addInstruction(new DupInstruction());
@@ -753,105 +721,32 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(gotoInstruction);
addInstruction(new FieldReferenceInstruction(exception, "thrown exception"));
initException(myNpe, cd);
addThrowCode(cd, statement);
throwException(myNpe, statement);
gotoInstruction.setOffset(myCurrentFlow.getInstructionCount());
addInstruction(new PushInstruction(getExceptionHolder(cd), null));
addInstruction(new SwapInstruction());
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
addThrowCode(cd, statement);
throwException(exception.getType(), statement);
}
finishElement(statement);
}
private void addConditionalRuntimeThrow() {
CatchDescriptor cd = findNextCatch(false);
if (cd == null) {
if (myTrapStack.isEmpty()) {
return;
}
pushUnknown();
final ConditionalGotoInstruction ifNoException = addInstruction(new ConditionalGotoInstruction(null, false, null));
addInstruction(new EmptyStackInstruction());
addInstruction(new PushInstruction(getExceptionHolder(cd), null));
pushUnknown();
final ConditionalGotoInstruction ifError = addInstruction(new ConditionalGotoInstruction(null, false, null));
addInstruction(new PushInstruction(myRuntimeException, null));
GotoInstruction ifRuntime = addInstruction(new GotoInstruction(null));
throwException(myRuntimeException, null);
ifError.setOffset(myCurrentFlow.getInstructionCount());
addInstruction(new PushInstruction(myError, null));
ifRuntime.setOffset(myCurrentFlow.getInstructionCount());
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
addThrowCode(cd, null);
throwException(myError, 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(@Nullable CatchDescriptor cd, @Nullable PsiElement explicitThrower) {
if (cd == null) {
addInstruction(new ReturnInstruction(true, explicitThrower));
return;
}
flushVariablesOnControlTransfer(cd.getBlock());
addInstruction(new GotoInstruction(cd.getJumpOffset(this)));
}
@Nullable
private CatchDescriptor findNextCatch(boolean catchRethrow) {
if (myCatchStack.isEmpty()) {
return null;
}
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) {
return null;
}
cd = myCatchStack.get(i);
}
return cd;
}
@Nullable
private CatchDescriptor findFinally() {
for (int i = myCatchStack.size() - 1; i >= 0; i--) {
CatchDescriptor cd = myCatchStack.get(i);
if (cd.isFinally()) return cd;
}
return null;
}
private static class ApplyNotNullInstruction extends Instruction {
private final PsiMethodCallExpression myCall;
@@ -874,50 +769,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
}
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);
@@ -926,27 +777,23 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiCodeBlock tryBlock = statement.getTryBlock();
PsiCodeBlock finallyBlock = statement.getFinallyBlock();
if (finallyBlock != null) {
myCatchStack.push(new CatchDescriptor(finallyBlock));
Trap.TryFinally finallyDescriptor = finallyBlock != null ? new Trap.TryFinally(finallyBlock, getStartOffset(finallyBlock)) : null;
if (finallyDescriptor != null) {
myTrapStack = myTrapStack.prepend(finallyDescriptor);
}
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;
if (sections.length > 0) {
LinkedHashMap<PsiCatchSection, ControlFlow.ControlFlowOffset> clauses = new LinkedHashMap<>();
for (PsiCatchSection section : sections) {
PsiCodeBlock catchBlock = section.getCatchBlock();
if (catchBlock != null) {
clauses.put(section, getStartOffset(catchBlock));
}
}
throw new CannotAnalyzeException();
myTrapStack = myTrapStack.prepend(new Trap.TryCatch(statement, clauses));
}
ControlFlow.ControlFlowOffset endOffset = finallyBlock == null ? getEndOffset(statement) : getStartOffset(finallyBlock);
if (resourceList != null) {
resourceList.accept(this);
}
@@ -955,79 +802,37 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
tryBlock.accept(this);
}
addInstruction(new GotoInstruction(endOffset));
InstructionTransfer gotoEnd = new InstructionTransfer(getEndOffset(statement), getVariablesInside(tryBlock));
FList<Trap> singleFinally = FList.createFromReversed(ContainerUtil.createMaybeSingletonList(finallyDescriptor));
controlTransfer(gotoEnd, singleFinally);
if (sections.length > 0) {
assert myTrapStack.getHead() instanceof Trap.TryCatch;
myTrapStack = myTrapStack.getTail();
}
for (PsiCatchSection section : sections) {
section.accept(this);
addInstruction(new GotoInstruction(endOffset));
myCatchStack.pop();
PsiCodeBlock catchBlock = section.getCatchBlock();
if (catchBlock != null) {
visitCodeBlock(catchBlock);
}
controlTransfer(gotoEnd, singleFinally);
}
if (finallyBlock != null) {
CatchDescriptor finallyDescriptor = myCatchStack.pop();
assert myTrapStack.getHead() instanceof Trap.TryFinally;
myTrapStack = myTrapStack.getTail().prepend(new Trap.InsideFinally(finallyBlock));
finallyBlock.accept(this);
//if $exception$==null => continue normal execution
addInstruction(new PushInstruction(getExceptionHolder(finallyDescriptor), null));
addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null));
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, myProject));
addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), false, null));
// else throw $exception$
rethrowException(finallyDescriptor, false);
addInstruction(new ControlTransferInstruction(null)); // DfaControlTransferValue is on stack
assert myTrapStack.getHead() instanceof Trap.InsideFinally;
myTrapStack = myTrapStack.getTail();
}
finishElement(statement);
}
@Override
public void visitCatchSection(PsiCatchSection section) {
startElement(section);
PsiCodeBlock catchBlock = section.getCatchBlock();
if (catchBlock != null) {
CatchDescriptor currentDescriptor = new CatchDescriptor(section.getParameter(), catchBlock);
DfaVariableValue exceptionHolder = getExceptionHolder(currentDescriptor);
// exception is in exceptionHolder 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(exceptionHolder, null));
addInstruction(new PushInstruction(myFactory.createTypeValue(catchType, Nullness.UNKNOWN), null));
addInstruction(new BinopInstruction(JavaTokenType.INSTANCEOF_KEYWORD, null, myProject));
addInstruction(new ConditionalGotoInstruction(ControlFlow.deltaOffset(getStartOffset(catchBlock), -5), false, null));
}
// not assignable => rethrow
rethrowException(currentDescriptor, true);
// e = $exception$
addInstruction(new PushInstruction(myFactory.getVarFactory().createVariableValue(section.getParameter(), false), null));
addInstruction(new PushInstruction(exceptionHolder, null));
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
addInstruction(new FlushVariableInstruction(exceptionHolder));
catchBlock.accept(this);
}
finishElement(section);
}
private void rethrowException(CatchDescriptor currentDescriptor, boolean catchRethrow) {
CatchDescriptor nextCatch = findNextCatch(catchRethrow);
if (nextCatch != null) {
addInstruction(new PushInstruction(getExceptionHolder(nextCatch), null, false));
addInstruction(new PushInstruction(getExceptionHolder(currentDescriptor), null, true));
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
}
addThrowCode(nextCatch, null);
}
@Override
public void visitResourceList(PsiResourceList resourceList) {
for (PsiResourceListElement resource : resourceList) {
@@ -1044,7 +849,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
final List<PsiClassType> closerExceptions = ExceptionUtil.getCloserExceptions(resource);
if (!closerExceptions.isEmpty()) {
addThrows(null, findNextCatch(false), closerExceptions.toArray(new PsiClassType[closerExceptions.size()]));
addThrows(null, closerExceptions.toArray(new PsiClassType[closerExceptions.size()]));
}
}
}
@@ -1409,35 +1214,28 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
private void addMethodThrows(PsiMethod method, @Nullable PsiElement explicitCall) {
CatchDescriptor cd = findNextCatch(false);
if (method != null) {
PsiClassType[] refs = method.getThrowsList().getReferencedTypes();
addThrows(explicitCall, cd, refs);
addThrows(explicitCall, method.getThrowsList().getReferencedTypes());
}
}
private void addThrows(@Nullable PsiElement explicitCall, CatchDescriptor cd, PsiClassType[] refs) {
private void addThrows(@Nullable PsiElement explicitCall, PsiClassType[] refs) {
for (PsiClassType ref : refs) {
pushUnknown();
ConditionalGotoInstruction cond = new ConditionalGotoInstruction(null, false, null);
addInstruction(cond);
addInstruction(new EmptyStackInstruction());
initException(ref, cd);
addThrowCode(cd, explicitCall);
throwException(ref, explicitCall);
cond.setOffset(myCurrentFlow.getInstructionCount());
}
}
private void initException(PsiType ref, @Nullable CatchDescriptor cd) {
if (cd == null) return;
addInstruction(new PushInstruction(getExceptionHolder(cd), null));
addInstruction(new PushInstruction(myFactory.createTypeValue(ref, Nullness.NOT_NULL), null));
addInstruction(new AssignInstruction(null, null));
addInstruction(new PopInstruction());
private void throwException(PsiType ref, @Nullable PsiElement anchor) {
throwException(new ExceptionTransfer(myFactory.createTypeValue(ref, Nullness.NOT_NULL)), anchor);
}
private DfaVariableValue getExceptionHolder(CatchDescriptor cd) {
return myExceptionHolders.get(cd.getTryStatement());
private void throwException(ExceptionTransfer kind, @Nullable PsiElement anchor) {
addInstruction(new EmptyStackInstruction());
addInstruction(new ReturnInstruction(myFactory.controlTransfer(kind, myTrapStack), anchor));
}
@Override public void visitMethodCallExpression(PsiMethodCallExpression expression) {
@@ -1488,11 +1286,12 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, myProject));
ConditionalGotoInstruction ifNotFail = new ConditionalGotoInstruction(null, true, null);
addInstruction(ifNotFail);
returnCheckingFinally(true, expression);
addInstruction(new ReturnInstruction(myFactory.controlTransfer(new ExceptionTransfer(DfaUnknownValue.getInstance()), myTrapStack), expression));
ifNotFail.setOffset(myCurrentFlow.getInstructionCount());
}
if (!myCatchStack.isEmpty()) {
if (!myTrapStack.isEmpty()) {
addMethodThrows(expression.resolveMethod(), expression);
}
@@ -1588,7 +1387,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addConditionalRuntimeThrow();
addInstruction(new MethodCallInstruction(expression, null, constructor == null ? Collections.emptyList() : getMethodContracts(constructor)));
if (!myCatchStack.isEmpty()) {
if (!myTrapStack.isEmpty()) {
addMethodThrows(constructor, expression);
}
@@ -237,7 +237,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
@Override
public void emptyStack() {
myCachedHash = null;
myStack.clear();
while (!myStack.isEmpty() && !(myStack.peek() instanceof DfaControlTransferValue)) {
myStack.pop();
}
}
@Override
@@ -677,7 +679,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
setVariableState(dfaVar, newState);
return true;
}
return applyRelation(dfaVar, myFactory.getConstFactory().getNull(), false);
return !getVariableState(dfaVar).isNotNull() && applyRelation(dfaVar, myFactory.getConstFactory().getNull(), false);
}
if (applyRelation(dfaVar, myFactory.getConstFactory().getNull(), true)) {
DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue((DfaTypeValue)dfaRight);
@@ -0,0 +1,125 @@
/*
* Copyright 2000-2016 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.codeInspection.dataFlow.instructions.Instruction
import com.intellij.codeInspection.dataFlow.value.DfaTypeValue
import com.intellij.codeInspection.dataFlow.value.DfaValue
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue
import com.intellij.psi.*
import com.intellij.util.containers.FList
import java.util.*
/**
* @author peter
*/
class DfaControlTransferValue(factory: DfaValueFactory,
val target: TransferTarget,
val traps: FList<Trap>) : DfaValue(factory) {
override fun toString() = target.toString() + " " + traps.toString()
}
interface TransferTarget
data class ExceptionTransfer(val throwable: DfaValue) : TransferTarget
data class InstructionTransfer(val offset: ControlFlow.ControlFlowOffset, val toFlush: List<DfaVariableValue>) : TransferTarget
object ReturnTransfer : TransferTarget
open class ControlTransferInstruction(val transfer: DfaControlTransferValue?) : Instruction() {
override fun accept(runner: DataFlowRunner, state: DfaMemoryState, visitor: InstructionVisitor): Array<out DfaInstructionState> {
val transferValue = transfer ?: state.pop() as DfaControlTransferValue
return ControlTransferHandler(state, runner, transferValue.target).iteration(transferValue.traps).toTypedArray()
}
override fun toString() = transfer.toString()
}
sealed class Trap(val anchor: PsiElement) {
class TryCatch(tryStatement : PsiTryStatement, val clauses: LinkedHashMap<PsiCatchSection, ControlFlow.ControlFlowOffset>): Trap(tryStatement)
class TryFinally(val finallyBlock: PsiCodeBlock, val jumpOffset: ControlFlow.ControlFlowOffset): Trap(finallyBlock)
class InsideFinally(val finallyBlock: PsiCodeBlock): Trap(finallyBlock)
}
private class ControlTransferHandler(val state: DfaMemoryState, val runner: DataFlowRunner, val target: TransferTarget) {
var throwableState: DfaVariableState? = null
fun iteration(traps: FList<Trap>): List<DfaInstructionState> {
val (head, tail) = traps.head to traps.tail
return when (head) {
null -> transferToTarget()
is Trap.TryCatch -> if (target is ExceptionTransfer) processCatches(head, target.throwable, tail) else iteration(tail)
is Trap.TryFinally -> goToFinally(head.jumpOffset.instructionOffset, tail)
is Trap.InsideFinally -> leaveFinally(tail)
}
}
private fun transferToTarget(): List<DfaInstructionState> {
return when (target) {
is InstructionTransfer -> {
target.toFlush.forEach { state.flushVariable(it) }
listOf(DfaInstructionState(runner.getInstruction(target.offset.instructionOffset), state))
}
else -> emptyList()
}
}
private fun goToFinally(offset: Int, traps: FList<Trap>): List<DfaInstructionState> {
state.push(runner.factory.controlTransfer(target, traps))
return listOf(DfaInstructionState(runner.getInstruction(offset), state))
}
private fun leaveFinally(traps: FList<Trap>): List<DfaInstructionState> {
state.pop() as DfaControlTransferValue
return iteration(traps)
}
private fun processCatches(tryCatch: Trap.TryCatch, thrownValue: DfaValue, traps: FList<Trap>): List<DfaInstructionState> {
val result = arrayListOf<DfaInstructionState>()
for ((catchSection, jumpOffset) in tryCatch.clauses) {
val param = catchSection.parameter ?: continue
if (throwableState == null) throwableState = initVariableState(param, thrownValue)
for (caughtType in allCaughtTypes(param)) {
throwableState?.withInstanceofValue(caughtType)?.let { varState ->
result.add(DfaInstructionState(runner.getInstruction(jumpOffset.instructionOffset), stateForCatchClause(param, varState)))
}
throwableState = throwableState?.withNotInstanceofValue(caughtType) ?: return result
}
}
return result + iteration(traps)
}
private fun allCaughtTypes(param: PsiParameter): List<DfaTypeValue> {
val psiTypes = param.type.let { if (it is PsiDisjunctionType) it.disjunctions else listOfNotNull(it) }
return psiTypes.map { runner.factory.createTypeValue(it, Nullness.NOT_NULL) }.filterIsInstance<DfaTypeValue>()
}
private fun stateForCatchClause(param: PsiParameter, varState: DfaVariableState): DfaMemoryState {
val catchingCopy = state.createCopy() as DfaMemoryStateImpl
catchingCopy.setVariableState(catchingCopy.factory.varFactory.createVariableValue(param, false), varState)
return catchingCopy
}
private fun initVariableState(param: PsiParameter, throwable: DfaValue): DfaVariableState {
val sampleVar = (state as DfaMemoryStateImpl).factory.varFactory.createVariableValue(param, false)
val varState = state.createVariableState(sampleVar).withNullability(Nullness.NOT_NULL)
return if (throwable is DfaTypeValue) varState.withInstanceofValue(throwable)!! else varState
}
}
@@ -26,14 +26,14 @@ package com.intellij.codeInspection.dataFlow.instructions;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ReturnInstruction extends Instruction {
private final boolean isViaException;
public class ReturnInstruction extends ControlTransferInstruction {
private final PsiElement myAnchor;
public ReturnInstruction(boolean isViaException, @Nullable PsiElement anchor) {
this.isViaException = isViaException;
public ReturnInstruction(@NotNull DfaControlTransferValue transfer, @Nullable PsiElement anchor) {
super(transfer);
myAnchor = anchor;
}
@@ -43,15 +43,8 @@ public class ReturnInstruction extends Instruction {
}
public boolean isViaException() {
return isViaException;
DfaControlTransferValue transfer = getTransfer();
return transfer != null && transfer.getTarget() instanceof ExceptionTransfer;
}
@Override
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
return DfaInstructionState.EMPTY_ARRAY;
}
public String toString() {
return "RETURN";
}
}
@@ -24,12 +24,17 @@
*/
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInspection.dataFlow.DfaControlTransferValue;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.codeInspection.dataFlow.TransferTarget;
import com.intellij.codeInspection.dataFlow.Trap;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FList;
import com.intellij.util.containers.FactoryMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -101,18 +106,6 @@ public class DfaValueFactory {
return getConstFactory().create(literal);
}
@Nullable
public static PsiVariable resolveUnqualifiedVariable(PsiReferenceExpression refExpression) {
if (isEffectivelyUnqualified(refExpression)) {
PsiElement resolved = refExpression.resolve();
if (resolved instanceof PsiVariable) {
return (PsiVariable)resolved;
}
}
return null;
}
public static boolean isEffectivelyUnqualified(PsiReferenceExpression refExpression) {
PsiExpression qualifier = refExpression.getQualifierExpression();
if (qualifier == null) {
@@ -129,6 +122,13 @@ public class DfaValueFactory {
return false;
}
public DfaControlTransferValue controlTransfer(TransferTarget kind, FList<Trap> traps) {
return myControlTransfers.get(Pair.create(kind, traps));
}
private final Map<Pair<TransferTarget, FList<Trap>>, DfaControlTransferValue> myControlTransfers =
FactoryMap.createMap(p -> new DfaControlTransferValue(this, p.first, p.second));
private final DfaVariableValue.Factory myVarFactory;
private final DfaConstValue.Factory myConstFactory;
private final DfaBoxedValue.Factory myBoxedFactory;
@@ -0,0 +1,31 @@
class Test {
public void testContinue() {
Object o = null;
for (int i = 0; i < 4; i++) {
try {
if (o == null) {
System.out.println("hello");
continue;
}
System.out.println("fred");
} finally {
o = "";
}
}
}
public void testBreak() {
Object o = null;
while (true) {
try {
System.out.println("hello");
break;
} finally {
o = "";
}
}
if (<warning descr="Condition 'o != null' is always 'true'">o != null</warning>) {
System.out.println("fred");
}
}
}
@@ -60,6 +60,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testExceptionFromFinallyNesting() throws Throwable { doTest(); }
public void testNestedFinally() { doTest(); }
public void testTryFinallyInsideFinally() { doTest(); }
public void testBreakContinueViaFinally() { doTest(); }
public void testFieldChangedBetweenSynchronizedBlocks() throws Throwable { doTest(); }
public void testGeneratedEquals() throws Throwable { doTest(); }
@@ -153,4 +153,15 @@ public class FList<E> extends AbstractList<E> {
//noinspection unchecked
return (FList<E>)EMPTY_LIST;
}
/**
* Creates an FList object with the elements of the given sequence in the reversed order, i.e. the last element of <code>from</code> will be the result's {@link #getHead()}
*/
public static <E> FList<E> createFromReversed(Iterable<E> from) {
FList<E> result = emptyList();
for (E e : from) {
result = result.prepend(e);
}
return result;
}
}