mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
java dfa: rewrite "try "treatment to visit finally on break/continue control transfers (IDEA-55394, IDEA-156394)
This commit is contained in:
+77
-278
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
+6
-13
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user