mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
dfa: exception handling reworked
no more gosub-return causing equal states differing by offset stack only don't visit outer finally without visiting inner one
This commit is contained in:
@@ -31,6 +31,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import gnu.trove.TObjectIntHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -77,9 +78,9 @@ public class ControlFlow {
|
||||
myInstructions.add(instruction);
|
||||
}
|
||||
|
||||
public void removeVariable(PsiVariable variable) {
|
||||
DfaVariableValue var = myFactory.getVarFactory().createVariableValue(variable, false);
|
||||
addInstruction(new FlushVariableInstruction(var));
|
||||
public void removeVariable(@Nullable PsiVariable variable) {
|
||||
if (variable == null) return;
|
||||
addInstruction(new FlushVariableInstruction(myFactory.getVarFactory().createVariableValue(variable, false)));
|
||||
}
|
||||
|
||||
public ControlFlowOffset getStartOffset(final PsiElement element) {
|
||||
|
||||
+145
-118
@@ -18,7 +18,6 @@ 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;
|
||||
@@ -45,12 +44,6 @@ 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;
|
||||
|
||||
@@ -62,9 +55,15 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
private Stack<CatchDescriptor> myCatchStack;
|
||||
private DfaValue myRuntimeException;
|
||||
private DfaValue myError;
|
||||
private DfaValue myString;
|
||||
private PsiType myNpe;
|
||||
private Stack<PsiElement> myElementStack = new Stack<PsiElement>();
|
||||
|
||||
/**
|
||||
* A mock variable for try-related control transfers. Contains exceptions or an (Throwable-inconvertible) string to indicate return inside finally
|
||||
*/
|
||||
private DfaVariableValue myExceptionHolder;
|
||||
|
||||
ControlFlowAnalyzer(final DfaValueFactory valueFactory) {
|
||||
myFactory = valueFactory;
|
||||
}
|
||||
@@ -76,6 +75,11 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
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);
|
||||
myString = myFactory.createTypeValue(createClassType(manager, scope, JAVA_LANG_STRING), Nullness.NOT_NULL);
|
||||
|
||||
PsiParameter mockVar = JavaPsiFacade.getElementFactory(manager.getProject()).createParameter("$exception$", createClassType(manager, scope, JAVA_LANG_OBJECT));
|
||||
myExceptionHolder = myFactory.getVarFactory().createVariableValue(mockVar, false);
|
||||
|
||||
myFields = new HashSet<DfaVariableValue>();
|
||||
myCatchStack = new Stack<CatchDescriptor>();
|
||||
myCurrentFlow = new ControlFlow(myFactory);
|
||||
@@ -192,7 +196,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
lExpr.accept(this);
|
||||
generateBoxingUnboxingInstructionFor(lExpr,exprType);
|
||||
rExpr.accept(this);
|
||||
generateBoxingUnboxingInstructionFor(rExpr,exprType);
|
||||
generateBoxingUnboxingInstructionFor(rExpr, exprType);
|
||||
addInstruction(new BinopInstruction(null, null, lExpr.getProject()));
|
||||
}
|
||||
|
||||
@@ -287,6 +291,23 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
PsiElement parent = block.getParent();
|
||||
if (parent instanceof PsiCatchSection) {
|
||||
myCurrentFlow.removeVariable(((PsiCatchSection)parent).getParameter());
|
||||
}
|
||||
else if (parent instanceof PsiForeachStatement) {
|
||||
myCurrentFlow.removeVariable(((PsiForeachStatement)parent).getIterationParameter());
|
||||
}
|
||||
else if (parent instanceof PsiForStatement) {
|
||||
PsiStatement statement = ((PsiForStatement)parent).getInitialization();
|
||||
if (statement instanceof PsiDeclarationStatement) {
|
||||
for (PsiElement declaration : ((PsiDeclarationStatement)statement).getDeclaredElements()) {
|
||||
if (declaration instanceof PsiVariable) {
|
||||
myCurrentFlow.removeVariable((PsiVariable)declaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void visitBlockStatement(PsiBlockStatement statement) {
|
||||
@@ -397,11 +418,13 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
if (initialization != null) {
|
||||
initialization.accept(this);
|
||||
initialization.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
visitElement(expression);
|
||||
}
|
||||
|
||||
@Override public void visitDeclarationStatement(PsiDeclarationStatement statement) {
|
||||
@Override
|
||||
public void visitDeclarationStatement(PsiDeclarationStatement statement) {
|
||||
PsiElement[] declaredElements = statement.getDeclaredElements();
|
||||
for (PsiElement element : declaredElements) {
|
||||
if (element instanceof PsiVariable) {
|
||||
@@ -511,10 +534,16 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
private void returnCheckingFinally() {
|
||||
ControlFlow.ControlFlowOffset finallyOffset = getFinallyOffset();
|
||||
if (finallyOffset != NOT_FOUND) {
|
||||
addInstruction(new GosubInstruction(finallyOffset));
|
||||
if (finallyOffset != null) {
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
addInstruction(new PushInstruction(myString, null));
|
||||
addInstruction(new AssignInstruction(null));
|
||||
addInstruction(new PopInstruction());
|
||||
|
||||
addInstruction(new GotoInstruction(finallyOffset));
|
||||
} else {
|
||||
addInstruction(new ReturnInstruction());
|
||||
}
|
||||
addInstruction(new ReturnInstruction());
|
||||
}
|
||||
|
||||
@Override public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) {
|
||||
@@ -627,70 +656,62 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
if (exception != null) {
|
||||
exception.accept(this);
|
||||
if (myCatchStack.isEmpty()) {
|
||||
addInstruction(new ReturnInstruction());
|
||||
finishElement(statement);
|
||||
return;
|
||||
}
|
||||
|
||||
addConditionalRuntimeThrow();
|
||||
addInstruction(new DupInstruction());
|
||||
addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null));
|
||||
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, statement.getProject()));
|
||||
ConditionalGotoInstruction gotoInstruction = new ConditionalGotoInstruction(null, true, null);
|
||||
addInstruction(gotoInstruction);
|
||||
|
||||
addInstruction(new PopInstruction());
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
addInstruction(new PushInstruction(myFactory.createTypeValue(myNpe, Nullness.NOT_NULL), null));
|
||||
addThrowCode(myNpe);
|
||||
addInstruction(new AssignInstruction(null));
|
||||
addInstruction(new PopInstruction());
|
||||
addThrowCode(false);
|
||||
|
||||
gotoInstruction.setOffset(myCurrentFlow.getInstructionCount());
|
||||
addThrowCode(exception.getType());
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
addInstruction(new SwapInstruction());
|
||||
addInstruction(new AssignInstruction(null));
|
||||
addInstruction(new PopInstruction());
|
||||
addThrowCode(false);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
if (myCatchStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void addConditionalRuntimeThrow(CatchDescriptor cd, boolean forCatch) {
|
||||
|
||||
pushUnknown();
|
||||
final ConditionalGotoInstruction branch = new ConditionalGotoInstruction(null, false, null);
|
||||
addInstruction(branch);
|
||||
final ConditionalGotoInstruction ifNoException = addInstruction(new ConditionalGotoInstruction(null, false, null));
|
||||
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());
|
||||
}
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
|
||||
private void flushVariablesInsideTry(CatchDescriptor cd) {
|
||||
flushVariablesOnControlTransfer(cd.getBlock());
|
||||
pushUnknown();
|
||||
final ConditionalGotoInstruction ifError = addInstruction(new ConditionalGotoInstruction(null, false, null));
|
||||
addInstruction(new PushInstruction(myRuntimeException, null));
|
||||
GotoInstruction ifRuntime = addInstruction(new GotoInstruction(null));
|
||||
ifError.setOffset(myCurrentFlow.getInstructionCount());
|
||||
addInstruction(new PushInstruction(myError, null));
|
||||
ifRuntime.setOffset(myCurrentFlow.getInstructionCount());
|
||||
|
||||
addInstruction(new AssignInstruction(null));
|
||||
addInstruction(new PopInstruction());
|
||||
|
||||
addThrowCode(false);
|
||||
|
||||
ifNoException.setOffset(myCurrentFlow.getInstructionCount());
|
||||
}
|
||||
|
||||
private void flushVariablesOnControlTransfer(PsiElement stopWhenAncestorOf) {
|
||||
@@ -701,59 +722,44 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
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;
|
||||
// the exception object should be in $exception$ variable
|
||||
private void addThrowCode(boolean catchRethrow) {
|
||||
if (myCatchStack.isEmpty()) {
|
||||
addInstruction(new ReturnInstruction());
|
||||
return;
|
||||
}
|
||||
|
||||
PsiElement currentElement = myElementStack.peek();
|
||||
|
||||
CatchDescriptor cd = myCatchStack.get(myCatchStack.size() - 1);
|
||||
if (!cd.isFinally() && PsiTreeUtil.isAncestor(cd.getBlock().getParent(), currentElement, false)) {
|
||||
int i = myCatchStack.size() - 2;
|
||||
while (!catchRethrow && i >= 0 && !myCatchStack.get(i).isFinally() && myCatchStack.get(i).getTryStatement() == cd.getTryStatement()) {
|
||||
i--;
|
||||
}
|
||||
else if (cd.getType().isAssignableFrom(exceptionClass)) { // Definite catch.
|
||||
flushVariablesInsideTry(cd);
|
||||
addGotoCatch(cd);
|
||||
if (i < 0) {
|
||||
addInstruction(new ReturnInstruction());
|
||||
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());
|
||||
}
|
||||
cd = myCatchStack.get(i);
|
||||
}
|
||||
|
||||
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());
|
||||
flushVariablesOnControlTransfer(cd.getBlock());
|
||||
addInstruction(new GotoInstruction(cd.getJumpOffset(this)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ControlFlow.ControlFlowOffset getFinallyOffset() {
|
||||
for (int i = myCatchStack.size() - 1; i >= 0; i--) {
|
||||
CatchDescriptor cd = myCatchStack.get(i);
|
||||
if (cd.isFinally()) return cd.getJumpOffset(this);
|
||||
}
|
||||
|
||||
return NOT_FOUND;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class ApplyNotNullInstruction extends Instruction {
|
||||
@@ -801,25 +807,20 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
public PsiCodeBlock getBlock() {
|
||||
return myBlock;
|
||||
}
|
||||
public PsiTryStatement getTryStatement() {
|
||||
return (PsiTryStatement) (isFinally() ? myBlock.getParent() : myBlock.getParent().getParent());
|
||||
}
|
||||
|
||||
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);
|
||||
return analyzer.getStartOffset(isFinally() ? myBlock : myBlock.getParent());
|
||||
}
|
||||
|
||||
public PsiParameter getParameter() {
|
||||
@@ -839,7 +840,6 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
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];
|
||||
@@ -849,14 +849,13 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
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);
|
||||
ControlFlow.ControlFlowOffset endOffset = finallyBlock == null ? getEndOffset(statement) : getStartOffset(finallyBlock);
|
||||
|
||||
if (resourceList != null) {
|
||||
resourceList.accept(this);
|
||||
@@ -866,23 +865,26 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
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));
|
||||
myCatchStack.pop();
|
||||
}
|
||||
|
||||
if (finallyBlock != null) {
|
||||
myCatchStack.pop();
|
||||
addInstruction(new GosubInstruction(getStartOffset(finallyBlock)));
|
||||
addInstruction(new GotoInstruction(getEndOffset(statement)));
|
||||
finallyBlock.accept(this);
|
||||
addInstruction(new ReturnFromSubInstruction());
|
||||
|
||||
//if $exception$==null => continue normal exectuion
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
addInstruction(new PushInstruction(myFactory.getConstFactory().getNull(), null));
|
||||
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, statement.getProject()));
|
||||
addInstruction(new ConditionalGotoInstruction(getEndOffset(statement), false, null));
|
||||
|
||||
// else throw $exception$
|
||||
addThrowCode(false);
|
||||
}
|
||||
|
||||
finishElement(statement);
|
||||
@@ -890,10 +892,36 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
|
||||
@Override
|
||||
public void visitCatchSection(PsiCatchSection section) {
|
||||
startElement(section);
|
||||
PsiCodeBlock catchBlock = section.getCatchBlock();
|
||||
if (catchBlock != null) {
|
||||
// exception is in myExceptionHolder mock variable
|
||||
// check if it's assignable to catch parameter type
|
||||
PsiType declaredType = section.getCatchType();
|
||||
List<PsiType> flattened = declaredType instanceof PsiDisjunctionType ?
|
||||
((PsiDisjunctionType)declaredType).getDisjunctions() :
|
||||
ContainerUtil.createMaybeSingletonList(declaredType);
|
||||
for (PsiType catchType : flattened) {
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
addInstruction(new PushInstruction(myFactory.createTypeValue(catchType, Nullness.UNKNOWN), null));
|
||||
addInstruction(new BinopInstruction(JavaTokenType.INSTANCEOF_KEYWORD, null, section.getProject()));
|
||||
addInstruction(new ConditionalGotoInstruction(ControlFlow.deltaOffset(getStartOffset(catchBlock), -5), false, null));
|
||||
}
|
||||
|
||||
// not assignable => rethrow
|
||||
addThrowCode(true);
|
||||
|
||||
// e = $exception$
|
||||
addInstruction(new PushInstruction(myFactory.getVarFactory().createVariableValue(section.getParameter(), false), null));
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
addInstruction(new AssignInstruction(null));
|
||||
addInstruction(new PopInstruction());
|
||||
|
||||
addInstruction(new FlushVariableInstruction(myExceptionHolder));
|
||||
|
||||
catchBlock.accept(this);
|
||||
}
|
||||
finishElement(section);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1273,8 +1301,11 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
ConditionalGotoInstruction cond = new ConditionalGotoInstruction(null, false, null);
|
||||
addInstruction(cond);
|
||||
addInstruction(new EmptyStackInstruction());
|
||||
addInstruction(new PushInstruction(myExceptionHolder, null));
|
||||
addInstruction(new PushInstruction(myFactory.createTypeValue(ref, Nullness.NOT_NULL), null));
|
||||
addThrowCode(ref);
|
||||
addInstruction(new AssignInstruction(null));
|
||||
addInstruction(new PopInstruction());
|
||||
addThrowCode(false);
|
||||
cond.setOffset(myCurrentFlow.getInstructionCount());
|
||||
}
|
||||
}
|
||||
@@ -1414,11 +1445,7 @@ class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(new GotoInstruction(exitPoint));
|
||||
break;
|
||||
case THROW_EXCEPTION:
|
||||
ControlFlow.ControlFlowOffset finallyOffset = getFinallyOffset();
|
||||
if (finallyOffset != NOT_FOUND) {
|
||||
addInstruction(new GosubInstruction(finallyOffset));
|
||||
}
|
||||
addInstruction(new ReturnInstruction());
|
||||
returnCheckingFinally();
|
||||
break;
|
||||
case SYSTEM_EXIT:
|
||||
addInstruction(new ReturnInstruction());
|
||||
|
||||
+5
-9
@@ -114,19 +114,17 @@ public class DataFlowRunner {
|
||||
joinInstructions.add(myInstructions[((GotoInstruction)instruction).getOffset()]);
|
||||
} else if (instruction instanceof ConditionalGotoInstruction) {
|
||||
joinInstructions.add(myInstructions[((ConditionalGotoInstruction)instruction).getOffset()]);
|
||||
} else if (instruction instanceof GosubInstruction) {
|
||||
joinInstructions.add(myInstructions[((GosubInstruction)instruction).getSubprogramOffset()]);
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Analyzing code block: " + psiBlock.getText());
|
||||
for (int i = 0; i < myInstructions.length; i++) {
|
||||
Instruction instruction = myInstructions[i];
|
||||
LOG.debug(i + ": " + instruction.toString());
|
||||
LOG.debug(i + ": " + myInstructions[i].toString());
|
||||
}
|
||||
}
|
||||
|
||||
//for (int i = 0; i < myInstructions.length; i++) System.out.println(i + ": " + myInstructions[i].toString());
|
||||
|
||||
Integer tooExpensiveHash = psiBlock.getUserData(TOO_EXPENSIVE_HASH);
|
||||
if (tooExpensiveHash != null && tooExpensiveHash == psiBlock.getText().hashCode()) {
|
||||
LOG.debug("Too complex because hasn't changed since being too complex already");
|
||||
@@ -196,13 +194,11 @@ public class DataFlowRunner {
|
||||
return RunnerResult.OK;
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException e) {
|
||||
LOG.error(psiBlock.getText(), e); // TODO fix in better times
|
||||
LOG.error(psiBlock.getText(), e);
|
||||
return RunnerResult.ABORTED;
|
||||
}
|
||||
catch (EmptyStackException e) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.error(e); // TODO fix in better times
|
||||
}
|
||||
LOG.error(psiBlock.getText(), e);
|
||||
return RunnerResult.ABORTED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,6 @@ public interface DfaMemoryState {
|
||||
DfaValue peek();
|
||||
void push(@NotNull DfaValue value);
|
||||
|
||||
int popOffset();
|
||||
void pushOffset(int offset);
|
||||
|
||||
void emptyStack();
|
||||
|
||||
void setVarValue(DfaVariableValue var, DfaValue value);
|
||||
|
||||
+8
-18
@@ -28,11 +28,16 @@ import com.intellij.codeInspection.dataFlow.value.*;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.UnorderedPair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.PsiPrimitiveType;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.Stack;
|
||||
import gnu.trove.*;
|
||||
import gnu.trove.THashMap;
|
||||
import gnu.trove.THashSet;
|
||||
import gnu.trove.TLongArrayList;
|
||||
import gnu.trove.TLongHashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -44,7 +49,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
|
||||
private final List<EqClass> myEqClasses;
|
||||
private final Stack<DfaValue> myStack;
|
||||
private TIntStack myOffsetStack;
|
||||
private final TLongHashSet myDistinctClasses;
|
||||
private final Map<DfaVariableValue,DfaVariableState> myVariableStates;
|
||||
private final Map<DfaVariableValue,DfaVariableState> myDefaultVariableStates;
|
||||
@@ -58,7 +62,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
myUnknownVariables = ContainerUtil.newTroveSet();
|
||||
myVariableStates = ContainerUtil.newTroveMap();
|
||||
myDistinctClasses = new TLongHashSet();
|
||||
myOffsetStack = new TIntStack();
|
||||
myStack = new Stack<DfaValue>();
|
||||
}
|
||||
|
||||
@@ -70,7 +73,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
myStack = new Stack<DfaValue>(toCopy.myStack);
|
||||
myDistinctClasses = new TLongHashSet(toCopy.myDistinctClasses.toArray());
|
||||
myUnknownVariables = new THashSet<DfaVariableValue>(toCopy.myUnknownVariables);
|
||||
myOffsetStack = toCopy.myOffsetStack;
|
||||
|
||||
myEqClasses = ContainerUtil.newArrayList(toCopy.myEqClasses);
|
||||
myVariableStates = new THashMap<DfaVariableValue, DfaVariableState>(toCopy.myVariableStates);
|
||||
@@ -100,7 +102,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
}
|
||||
|
||||
boolean equalsSuperficially(DfaMemoryStateImpl other) {
|
||||
return myEphemeral == other.myEphemeral && myStack.equals(other.myStack) && myOffsetStack.equals(other.myOffsetStack);
|
||||
return myEphemeral == other.myEphemeral && myStack.equals(other.myStack);
|
||||
}
|
||||
|
||||
boolean equalsByRelations(DfaMemoryStateImpl that) {
|
||||
@@ -205,18 +207,6 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
myStack.push(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int popOffset() {
|
||||
myOffsetStack = new TIntStack(myOffsetStack);
|
||||
return myOffsetStack.pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushOffset(int offset) {
|
||||
myOffsetStack = new TIntStack(myOffsetStack);
|
||||
myOffsetStack.push(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emptyStack() {
|
||||
myStack.clear();
|
||||
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2009 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.instructions;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.*;
|
||||
|
||||
/**
|
||||
* @author max
|
||||
*/
|
||||
public class GosubInstruction extends Instruction {
|
||||
private final ControlFlow.ControlFlowOffset mySubprogramOffset;
|
||||
|
||||
public GosubInstruction(ControlFlow.ControlFlowOffset subprogramOffset) {
|
||||
mySubprogramOffset = subprogramOffset;
|
||||
}
|
||||
|
||||
public int getSubprogramOffset() {
|
||||
return mySubprogramOffset.getInstructionOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) {
|
||||
final int returnIndex = getIndex() + 1;
|
||||
stateBefore.pushOffset(returnIndex);
|
||||
Instruction nextInstruction = runner.getInstruction(getSubprogramOffset());
|
||||
return new DfaInstructionState[] {new DfaInstructionState(nextInstruction, stateBefore)};
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "GOSUB: " + getSubprogramOffset();
|
||||
}
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.instructions;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowRunner;
|
||||
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
|
||||
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class PopOffsetInstruction extends Instruction{
|
||||
|
||||
@Override
|
||||
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState memState, InstructionVisitor visitor) {
|
||||
memState.popOffset();
|
||||
return nextInstruction(runner, memState);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "POP_OFFSET";
|
||||
}
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2009 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.instructions;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowRunner;
|
||||
import com.intellij.codeInspection.dataFlow.DfaInstructionState;
|
||||
import com.intellij.codeInspection.dataFlow.DfaMemoryState;
|
||||
import com.intellij.codeInspection.dataFlow.InstructionVisitor;
|
||||
|
||||
/**
|
||||
* @author max
|
||||
*/
|
||||
public class ReturnFromSubInstruction extends Instruction{
|
||||
|
||||
@Override
|
||||
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState memState, InstructionVisitor visitor) {
|
||||
int offset = memState.popOffset();
|
||||
return new DfaInstructionState[] {new DfaInstructionState(runner.getInstruction(offset), memState)};
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "RETURN_FROM_SUB";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
public class Foo {
|
||||
Foo getFoo() { return <warning descr="'null' is returned by the method which isn't declared as @Nullable">null</warning>; }
|
||||
void foo() {
|
||||
try {
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo().getFoo();
|
||||
} finally {
|
||||
if (hashCode() == 2) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.Throwable;
|
||||
import java.net.Socket;
|
||||
|
||||
public class Foo {
|
||||
void unchecked() {
|
||||
boolean b = true;
|
||||
try {
|
||||
try {
|
||||
System.out.println();
|
||||
} finally {
|
||||
b = false;
|
||||
}
|
||||
} finally {
|
||||
if (<warning descr="Condition 'b' is always 'false'">b</warning>) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checked(boolean flag) throws Throwable {
|
||||
Throwable throwable = new Throwable();
|
||||
boolean b = true;
|
||||
try {
|
||||
if (flag) {
|
||||
try {
|
||||
throw throwable;
|
||||
} finally {
|
||||
b = false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (b) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void justReturn(boolean flag) throws Throwable {
|
||||
boolean b = true;
|
||||
try {
|
||||
try {
|
||||
return;
|
||||
} finally {
|
||||
b = false;
|
||||
}
|
||||
} finally {
|
||||
if (<warning descr="Condition 'b' is always 'false'">b</warning>) {
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,7 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase {
|
||||
public void testNestedTryInWhileNotComplex() throws Throwable { doTest(); }
|
||||
public void testExceptionFromFinally() throws Throwable { doTest(); }
|
||||
public void testExceptionFromFinallyNesting() throws Throwable { doTest(); }
|
||||
public void testNestedFinally() { doTest(); }
|
||||
public void testFieldChangedBetweenSynchronizedBlocks() throws Throwable { doTest(); }
|
||||
|
||||
public void testGeneratedEquals() throws Throwable { doTest(); }
|
||||
@@ -291,6 +292,7 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase {
|
||||
public void testWhileNotComplex() { doTest(); }
|
||||
public void testManyDisjunctiveFieldAssignmentsInLoopNotComplex() { doTest(); }
|
||||
public void testManyContinuesNotComplex() { doTest(); }
|
||||
public void testFinallyNotComplex() { doTest(); }
|
||||
|
||||
public void testVariablesDiverge() { doTest(); }
|
||||
public void testMergeByNullability() { doTest(); }
|
||||
|
||||
Reference in New Issue
Block a user