[java-dfa] IDEA-361818 Make LiveVariableAnalyzer descriptor-based, rather than variable-based

GitOrigin-RevId: c3a8a029fe4e6f4c3ef7bdd67726ec4993cb3c80
This commit is contained in:
Tagir Valeev
2024-11-05 16:07:12 +00:00
committed by intellij-monorepo-bot
parent 402acf04ff
commit 0ca49293fa
33 changed files with 623 additions and 208 deletions
@@ -14,10 +14,7 @@ import com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState;
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.types.DfTypes;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.impl.DebuggerUtilsEx;
@@ -150,7 +147,7 @@ public class DebuggerDfaRunner {
long modificationStamp = PsiModificationTracker.getInstance(project).getModificationCount();
int offset = flow.getStartOffset(anchor).getInstructionOffset();
if (offset < 0) return null;
Map<Value, List<DfaVariableValue>> jdiToDfa = createPreliminaryJdiMap(provider, anchor, factory, proxy);
Map<Value, List<DfaVariableValue>> jdiToDfa = createPreliminaryJdiMap(provider, anchor, flow, proxy);
if (jdiToDfa.isEmpty()) return null;
return new Larva(project, anchor, body, flow, factory, modificationStamp, provider, jdiToDfa, proxy, offset);
}
@@ -158,11 +155,18 @@ public class DebuggerDfaRunner {
@NotNull
private static Map<Value, List<DfaVariableValue>> createPreliminaryJdiMap(@NotNull DfaAssistProvider provider,
@NotNull PsiElement anchor,
@NotNull DfaValueFactory factory,
@NotNull ControlFlow flow,
@NotNull StackFrameProxyEx proxy) throws EvaluateException {
DfaValueFactory factory = flow.getFactory();
Set<VariableDescriptor> descriptors = StreamEx.of(flow.getInstructions()).flatCollection(inst -> inst.getRequiredDescriptors(factory))
.toSet();
Map<Value, List<DfaVariableValue>> myMap = new HashMap<>();
for (DfaValue dfaValue : factory.getValues().toArray(DfaValue.EMPTY_ARRAY)) {
if (dfaValue instanceof DfaVariableValue dfaVar) {
StreamEx<DfaVariableValue> stream = StreamEx.of(descriptors)
.map(desc -> desc.createValue(factory, dfaValue))
.append(dfaValue)
.select(DfaVariableValue.class);
for (DfaVariableValue dfaVar : stream) {
Value jdiValue = resolveJdiValue(provider, anchor, proxy, dfaVar);
if (jdiValue != null) {
myMap.computeIfAbsent(jdiValue, v -> new ArrayList<>()).add(dfaVar);
@@ -380,12 +380,10 @@ public final class DfaPsiUtil {
@Override
protected DfaInstructionState @NotNull [] acceptInstruction(@NotNull DfaInstructionState instructionState) {
Instruction instruction = instructionState.getInstruction();
if (instruction instanceof FinishElementInstruction) {
Set<DfaVariableValue> vars = ((FinishElementInstruction)instruction).getVarsToFlush();
vars.removeIf(v -> {
PsiElement variable = v.getPsiVariable();
return variable instanceof PsiField && ((PsiField)variable).getContainingClass() == containingClass;
});
if (instruction instanceof FinishElementInstruction finishInstruction) {
finishInstruction.removeFromFlushList(desc ->
desc instanceof PlainDescriptor plainDescriptor && plainDescriptor.getPsiElement() instanceof PsiField field &&
field.getContainingClass() == containingClass);
}
if ((isCallExposingNonInitializedFields(instruction) || instruction instanceof ReturnInstruction)) {
for (PsiField field : containingClass.getFields()) {
@@ -41,8 +41,8 @@ import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.impl.source.tree.java.PsiEmptyExpressionImpl;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
@@ -177,9 +177,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
myCurrentFlow.finishElement(element);
if (element instanceof PsiField || (element instanceof PsiStatement && !(element instanceof PsiReturnStatement) &&
!(element instanceof PsiSwitchLabeledRuleStatement))) {
List<DfaVariableValue> synthetics = myCurrentFlow.getSynthetics(element);
List<VariableDescriptor> synthetics = myCurrentFlow.getSynthetics(element);
FinishElementInstruction instruction = new FinishElementInstruction(element);
instruction.getVarsToFlush().addAll(synthetics);
instruction.flushVars(synthetics);
addInstruction(instruction);
}
}
@@ -231,8 +231,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (arrayStore != null) {
DfaControlTransferValue transfer = createTransfer("java.lang.ArrayIndexOutOfBoundsException");
var staticVariable = ObjectUtils.tryCast(JavaDfaValueFactory.getExpressionDfaValue(myFactory, arrayStore), DfaVariableValue.class);
addInstruction(new JavaArrayStoreInstruction(arrayStore, rExpr, transfer, staticVariable));
VariableDescriptor staticDescriptor = ArrayElementDescriptor.fromArrayAccess(arrayStore);
addInstruction(new JavaArrayStoreInstruction(arrayStore, rExpr, transfer, staticDescriptor));
} else {
addInstruction(new AssignInstruction(rExpr, JavaDfaValueFactory.getExpressionDfaValue(myFactory, lExpr)));
}
@@ -1681,10 +1681,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
private void readArrayElement(@NotNull PsiArrayAccessExpression expression) {
DfaControlTransferValue transfer = createTransfer("java.lang.ArrayIndexOutOfBoundsException");
DfaVariableValue staticValue =
ObjectUtils.tryCast(JavaDfaValueFactory.getExpressionDfaValue(myFactory, expression), DfaVariableValue.class);
addInstruction(new ArrayAccessInstruction(new JavaExpressionAnchor(expression), new ArrayIndexProblem(expression), transfer,
staticValue));
ArrayElementDescriptor.fromArrayAccess(expression)));
}
private @Nullable DfaVariableValue getTargetVariable(PsiExpression expression) {
@@ -2446,8 +2444,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
DfaValue dest = JavaDfaValueFactory.getExpressionDfaValue(myFactory, operand);
if (arrayStore != null) {
addInstruction(new JavaArrayStoreInstruction(arrayStore, null, null,
ObjectUtils.tryCast(dest, DfaVariableValue.class)));
VariableDescriptor staticDescriptor = ArrayElementDescriptor.fromArrayAccess(arrayStore);
addInstruction(new JavaArrayStoreInstruction(arrayStore, null, null, staticDescriptor));
} else {
addInstruction(new AssignInstruction(operand, null, dest));
}
@@ -4,6 +4,7 @@ package com.intellij.codeInspection.dataFlow.java.inst;
import com.intellij.codeInspection.dataFlow.TypeConstraint;
import com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter;
import com.intellij.codeInspection.dataFlow.java.JavaDfaHelpers;
import com.intellij.codeInspection.dataFlow.jvm.SpecialField;
import com.intellij.codeInspection.dataFlow.jvm.descriptors.ArrayElementDescriptor;
import com.intellij.codeInspection.dataFlow.jvm.problems.IndexOutOfBoundsProblem;
import com.intellij.codeInspection.dataFlow.lang.DfaAnchor;
@@ -15,7 +16,6 @@ import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfIntType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,12 +25,12 @@ import java.util.List;
public class ArrayAccessInstruction extends ExpressionPushingInstruction {
private final @Nullable DfaControlTransferValue myOutOfBoundsTransfer;
private final @NotNull IndexOutOfBoundsProblem myProblem;
private final @Nullable DfaVariableValue myStaticValue;
private final @Nullable VariableDescriptor myStaticValue;
public ArrayAccessInstruction(@Nullable DfaAnchor anchor,
@NotNull IndexOutOfBoundsProblem indexProblem,
@Nullable DfaControlTransferValue outOfBoundsTransfer,
@Nullable DfaVariableValue staticValue) {
@Nullable VariableDescriptor staticValue) {
super(anchor);
myOutOfBoundsTransfer = outOfBoundsTransfer;
myProblem = indexProblem;
@@ -40,8 +40,7 @@ public class ArrayAccessInstruction extends ExpressionPushingInstruction {
@Override
public @NotNull Instruction bindToFactory(@NotNull DfaValueFactory factory) {
DfaControlTransferValue newTransfer = myOutOfBoundsTransfer == null ? null : myOutOfBoundsTransfer.bindToFactory(factory);
DfaVariableValue newStaticValue = myStaticValue == null ? null : myStaticValue.bindToFactory(factory);
return new ArrayAccessInstruction(getDfaAnchor(), myProblem, newTransfer, newStaticValue);
return new ArrayAccessInstruction(getDfaAnchor(), myProblem, newTransfer, myStaticValue);
}
@Override
@@ -78,8 +77,9 @@ public class ArrayAccessInstruction extends ExpressionPushingInstruction {
}
@Override
public List<DfaVariableValue> getRequiredVariables(DfaValueFactory factory) {
return ContainerUtil.createMaybeSingletonList(myStaticValue);
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return myStaticValue == null ? List.of(SpecialField.ARRAY_LENGTH) :
List.of(myStaticValue, SpecialField.ARRAY_LENGTH);
}
@Override
@@ -3,6 +3,7 @@ package com.intellij.codeInspection.dataFlow.java.inst;
import com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter;
import com.intellij.codeInspection.dataFlow.java.JavaDfaHelpers;
import com.intellij.codeInspection.dataFlow.jvm.SpecialField;
import com.intellij.codeInspection.dataFlow.jvm.descriptors.ArrayElementDescriptor;
import com.intellij.codeInspection.dataFlow.jvm.problems.IndexOutOfBoundsProblem;
import com.intellij.codeInspection.dataFlow.lang.DfaAnchor;
@@ -12,11 +13,7 @@ import com.intellij.codeInspection.dataFlow.lang.ir.Instruction;
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.types.DfIntType;
import com.intellij.codeInspection.dataFlow.value.DfaControlTransferValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.codeInspection.dataFlow.value.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,12 +27,12 @@ import java.util.Set;
public class ArrayStoreInstruction extends ExpressionPushingInstruction {
protected final @Nullable DfaControlTransferValue myOutOfBoundsTransfer;
protected final @NotNull IndexOutOfBoundsProblem myIndexProblem;
protected final @Nullable DfaVariableValue myStaticVariable;
protected final @Nullable VariableDescriptor myStaticVariable;
public ArrayStoreInstruction(@Nullable DfaAnchor anchor,
@NotNull IndexOutOfBoundsProblem problem,
@Nullable DfaControlTransferValue outOfBoundsTransfer,
@Nullable DfaVariableValue variable) {
@Nullable VariableDescriptor variable) {
super(anchor);
myIndexProblem = problem;
myOutOfBoundsTransfer = outOfBoundsTransfer;
@@ -45,8 +42,7 @@ public class ArrayStoreInstruction extends ExpressionPushingInstruction {
@Override
public @NotNull Instruction bindToFactory(@NotNull DfaValueFactory factory) {
DfaControlTransferValue transfer = myOutOfBoundsTransfer == null ? null : myOutOfBoundsTransfer.bindToFactory(factory);
DfaVariableValue staticVariable = myStaticVariable == null ? null : myStaticVariable.bindToFactory(factory);
return new ArrayStoreInstruction(getDfaAnchor(), myIndexProblem, transfer, staticVariable);
return new ArrayStoreInstruction(getDfaAnchor(), myIndexProblem, transfer, myStaticVariable);
}
@Override
@@ -87,8 +83,8 @@ public class ArrayStoreInstruction extends ExpressionPushingInstruction {
}
@Override
public List<DfaVariableValue> getWrittenVariables(DfaValueFactory factory) {
return ContainerUtil.createMaybeSingletonList(myStaticVariable);
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return List.of(SpecialField.ARRAY_LENGTH);
}
@Override
@@ -19,13 +19,10 @@ 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.ContainerUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.intellij.util.ObjectUtils.tryCast;
public class AssignInstruction extends ExpressionPushingInstruction {
@@ -57,11 +54,6 @@ public class AssignInstruction extends ExpressionPushingInstruction {
return new AssignInstruction(myLExpression, myRExpression, myAssignedValue.bindToFactory(factory));
}
@Override
public List<DfaVariableValue> getWrittenVariables(DfaValueFactory factory) {
return ContainerUtil.createMaybeSingletonList(tryCast(myAssignedValue, DfaVariableValue.class));
}
@Override
public DfaInstructionState[] accept(@NotNull DataFlowInterpreter interpreter, @NotNull DfaMemoryState stateBefore) {
DfaValue dfaSource = stateBefore.pop();
@@ -9,7 +9,8 @@ import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
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.PsiMember;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.psi.util.PsiUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -26,9 +27,10 @@ public class EndOfInstanceInitializerInstruction extends Instruction {
}
@Override
public List<DfaVariableValue> getRequiredVariables(DfaValueFactory factory) {
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return StreamEx.of(factory.getValues()).select(DfaVariableValue.class)
.filter(var -> var.getPsiVariable() instanceof PsiMember).toList();
.map(DfaVariableValue::getDescriptor)
.filter(var -> !PsiUtil.isJvmLocalVariable(var.getPsiElement())).toList();
}
@Override
@@ -8,7 +8,9 @@ import com.intellij.codeInspection.dataFlow.lang.ir.Instruction;
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.util.containers.ContainerUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -32,8 +34,12 @@ public class EscapeInstruction extends Instruction {
}
@Override
public List<DfaVariableValue> getRequiredVariables(DfaValueFactory factory) {
return List.copyOf(myEscapedVars);
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return StreamEx.of(myEscapedVars)
.flatMap(v -> StreamEx.of(v.getDependentVariables()).append(v))
.map(DfaVariableValue::getDescriptor)
.distinct()
.toList();
}
@Override
@@ -4,21 +4,22 @@ package com.intellij.codeInspection.dataFlow.java.inst;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInspection.dataFlow.DfaNullability;
import com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter;
import com.intellij.codeInspection.dataFlow.jvm.descriptors.GetterDescriptor;
import com.intellij.codeInspection.dataFlow.lang.DfaAnchor;
import com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState;
import com.intellij.codeInspection.dataFlow.lang.ir.ExpressionPushingInstruction;
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.DfaCondition;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.PsiTypesUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static com.intellij.codeInspection.dataFlow.types.DfTypes.*;
@@ -83,6 +84,16 @@ public class InstanceofInstruction extends ExpressionPushingInstruction {
return states.toArray(DfaInstructionState.EMPTY_ARRAY);
}
@Override
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return StreamEx.of(factory.getValues())
.select(DfaVariableValue.class)
.map(DfaVariableValue::getDescriptor)
.filter(desc -> desc instanceof GetterDescriptor getterDescriptor &&
PsiTypesUtil.isGetClass(getterDescriptor.getPsiElement()))
.toList();
}
@Override
public String toString() {
return "INSTANCE_OF " + (myClassObjectCheck ? "(CLASS)" : "");
@@ -12,7 +12,7 @@ import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.DfaControlTransferValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiArrayAccessExpression;
import com.intellij.psi.PsiAssignmentExpression;
@@ -36,7 +36,7 @@ public class JavaArrayStoreInstruction extends ArrayStoreInstruction {
public JavaArrayStoreInstruction(@NotNull PsiArrayAccessExpression expression,
@Nullable PsiExpression valueExpression,
@Nullable DfaControlTransferValue outOfBoundsTransfer,
@Nullable DfaVariableValue staticVariable) {
@Nullable VariableDescriptor staticVariable) {
super(createAnchor(expression), new ArrayIndexProblem(expression), outOfBoundsTransfer, staticVariable);
myExpression = expression;
myValueExpression = valueExpression;
@@ -50,8 +50,7 @@ public class JavaArrayStoreInstruction extends ArrayStoreInstruction {
@Override
public @NotNull Instruction bindToFactory(@NotNull DfaValueFactory factory) {
DfaControlTransferValue transfer = myOutOfBoundsTransfer == null ? null : myOutOfBoundsTransfer.bindToFactory(factory);
DfaVariableValue staticVariable = myStaticVariable == null ? null : myStaticVariable.bindToFactory(factory);
return new JavaArrayStoreInstruction(myExpression, myValueExpression, transfer, staticVariable);
return new JavaArrayStoreInstruction(myExpression, myValueExpression, transfer, myStaticVariable);
}
@Override
@@ -26,7 +26,6 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.MethodUtils;
import org.jetbrains.annotations.NotNull;
@@ -538,7 +537,8 @@ public class MethodCallInstruction extends ExpressionPushingInstruction {
}
@Override
public List<DfaVariableValue> getRequiredVariables(DfaValueFactory factory) {
return ContainerUtil.createMaybeSingletonList(tryCast(myPrecalculatedReturnValue, DfaVariableValue.class));
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return myPrecalculatedReturnValue instanceof DfaVariableValue var ?
List.of(var.getDescriptor()) : List.of();
}
}
@@ -325,6 +325,11 @@ public enum SpecialField implements DerivedVariableDescriptor {
return myFinal;
}
@Override
public boolean isImplicitReadPossible() {
return true;
}
public abstract boolean isMyQualifierType(DfType type);
/**
@@ -22,6 +22,11 @@ import org.jetbrains.annotations.Nullable;
public final class ArrayElementDescriptor extends JvmVariableDescriptor {
private final int myIndex;
/**
* Creates a descriptor that represents an array element with a fixed index
*
* @param index index of an array element
*/
private ArrayElementDescriptor(int index) {
myIndex = index;
}
@@ -199,4 +204,15 @@ public final class ArrayElementDescriptor extends JvmVariableDescriptor {
}
return componentType;
}
/**
* @param arrayAccess expression to create a descriptor for
* @return an array element descriptor that describes a specified array access expression;
* null if it's not possible to describe a given array access expression with a single
* {@code ArrayElementDescriptor}
*/
public static @Nullable ArrayElementDescriptor fromArrayAccess(@NotNull PsiArrayAccessExpression arrayAccess) {
return ExpressionUtils.computeConstantExpression(arrayAccess.getIndexExpression()) instanceof Integer index ?
new ArrayElementDescriptor(index) : null;
}
}
@@ -27,7 +27,6 @@ import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.TypeUtils;
@@ -198,11 +197,14 @@ public final class SuspiciousInvocationHandlerImplementationInspection extends A
List<DfaInstructionState> result = new ArrayList<>();
Instruction instruction = flow.getInstruction(0);
for (Instruction inst : flow.getInstructions()) {
if (inst instanceof FinishElementInstruction) {
Set<DfaVariableValue> flush = ((FinishElementInstruction)inst).getVarsToFlush();
flush.remove(myDfaMethodDeclaringClass);
flush.remove(myDfaMethodName);
flush.remove(myDfaMethodName.getQualifier());
if (inst instanceof FinishElementInstruction finishInstruction) {
DfaVariableValue qualifier = myDfaMethodName.getQualifier();
finishInstruction.removeFromFlushList(
desc ->
desc.equals(myDfaMethodDeclaringClass.getDescriptor()) ||
desc.equals(myDfaMethodName.getDescriptor()) ||
(qualifier != null && desc.equals(qualifier.getDescriptor()))
);
}
}
for (DfaMemoryState state : memStates) {
@@ -149,9 +149,6 @@ final class OverwrittenFieldAnalyzer {
.filter(value -> value.getPsiVariable() instanceof PsiField field &&
field.hasModifierProperty(PsiModifier.STATIC));
}
else if (instruction instanceof FinishElementInstruction finishElementInstruction) {
readVariables = StreamEx.of(finishElementInstruction.getVarsToFlush());
}
else {
readVariables = getReadVariables(instruction);
}
@@ -5,6 +5,16 @@ class App {
System.out.println("Impossible");
}
}
System.out.println(a.field);
}
void test1(A a, A b) {
if(a.field != b.field) {
// Not supported anymore, as `field` is flushed :(
if(a == b) {
System.out.println("Impossible");
}
}
}
public static void main(String[] args) {
@@ -0,0 +1,384 @@
import java.util.Objects;
/**
* Should not take more than 1-2 seconds to analyze
*/
public class ManyObjectEquals2 {
private String test0;
private String test1;
private String test2;
private String test3;
private String test4;
private String test5;
private String test6;
private String test7;
private String test8;
private String test9;
private String test10;
private String test11;
private String test12;
private String test13;
private String test14;
private String test15;
private String test16;
private String test17;
private String test18;
private String test19;
private String test20;
private String test21;
private String test22;
private String test23;
private String test24;
private String test25;
private String test26;
private String test27;
private String test28;
private String test29;
private String test30;
private String test31;
private String test32;
private String test33;
private String test34;
private String test35;
private String test36;
private String test37;
private String test38;
private String test39;
private String test40;
private String test41;
private String test42;
private String test43;
private String test44;
private String test45;
private String test46;
private String test47;
private String test48;
private String test49;
private String test50;
private String test51;
private String test52;
private String test53;
private String test54;
private String test55;
private String test56;
private String test57;
private String test58;
private String test59;
private String test60;
private String test61;
private String test62;
private String test63;
private String test64;
private String test65;
private String test66;
private String test67;
private String test68;
private String test69;
private String test70;
private String test71;
private String test72;
private String test73;
private String test74;
private String test75;
private String test76;
private String test77;
private String test78;
private String test79;
private String test80;
private String test81;
private String test82;
private String test83;
private String test84;
private String test85;
private String test86;
private String test87;
private String test88;
private String test89;
private String test90;
private String test91;
private String test92;
private String test93;
private String test94;
private String test95;
private String test96;
private String test97;
private String test98;
private String test99;
private String test100;
private String test101;
private String test102;
private String test103;
private String test104;
private String test105;
private String test106;
private String test107;
private String test108;
private String test109;
private String test110;
private String test111;
private String test112;
private String test113;
private String test114;
private String test115;
private String test116;
private String test117;
private String test118;
private String test119;
private String test120;
private String test121;
private String test122;
private String test123;
private String test124;
private String test125;
private String test126;
private String test127;
private String test128;
private String test129;
private String test130;
private String test131;
private String test132;
private String test133;
private String test134;
private String test135;
private String test136;
private String test137;
private String test138;
private String test139;
private String test140;
private String test141;
private String test142;
private String test143;
private String test144;
private String test145;
private String test146;
private String test147;
private String test148;
private String test149;
private String test150;
private String test151;
private String test152;
private String test153;
private String test154;
private String test155;
private String test156;
private String test157;
private String test158;
private String test159;
private String test160;
private String test161;
private String test162;
private String test163;
private String test164;
private String test165;
private String test166;
private String test167;
private String test168;
private String test169;
private String test170;
private String test171;
private String test172;
private String test173;
private String test174;
private String test175;
private String test176;
private String test177;
private String test178;
private String test179;
private String test180;
private String test181;
private String test182;
private String test183;
@Override
public boolean <weak_warning descr="Method 'equals' is complex: data flow results could be imprecise">equals</weak_warning>(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ManyObjectEquals2 test = (ManyObjectEquals2) o;
return Objects.equals(this.test0, test.test0) &&
Objects.equals(this.test1, test.test1) &&
Objects.equals(this.test2, test.test2) &&
Objects.equals(this.test3, test.test3) &&
Objects.equals(this.test4, test.test4) &&
Objects.equals(this.test5, test.test5) &&
Objects.equals(this.test6, test.test6) &&
Objects.equals(this.test7, test.test7) &&
Objects.equals(this.test8, test.test8) &&
Objects.equals(this.test9, test.test9) &&
Objects.equals(this.test10, test.test10) &&
Objects.equals(this.test11, test.test11) &&
Objects.equals(this.test12, test.test12) &&
Objects.equals(this.test13, test.test13) &&
Objects.equals(this.test14, test.test14) &&
Objects.equals(this.test15, test.test15) &&
Objects.equals(this.test16, test.test16) &&
Objects.equals(this.test17, test.test17) &&
Objects.equals(this.test18, test.test18) &&
Objects.equals(this.test19, test.test19) &&
Objects.equals(this.test20, test.test20) &&
Objects.equals(this.test21, test.test21) &&
Objects.equals(this.test22, test.test22) &&
Objects.equals(this.test23, test.test23) &&
Objects.equals(this.test24, test.test24) &&
Objects.equals(this.test25, test.test25) &&
Objects.equals(this.test26, test.test26) &&
Objects.equals(this.test27, test.test27) &&
Objects.equals(this.test28, test.test28) &&
Objects.equals(this.test29, test.test29) &&
Objects.equals(this.test30, test.test30) &&
Objects.equals(this.test31, test.test31) &&
Objects.equals(this.test32, test.test32) &&
Objects.equals(this.test33, test.test33) &&
Objects.equals(this.test34, test.test34) &&
Objects.equals(this.test35, test.test35) &&
Objects.equals(this.test36, test.test36) &&
Objects.equals(this.test37, test.test37) &&
Objects.equals(this.test38, test.test38) &&
Objects.equals(this.test39, test.test39) &&
Objects.equals(this.test40, test.test40) &&
Objects.equals(this.test41, test.test41) &&
Objects.equals(this.test42, test.test42) &&
Objects.equals(this.test43, test.test43) &&
Objects.equals(this.test44, test.test44) &&
Objects.equals(this.test45, test.test45) &&
Objects.equals(this.test46, test.test46) &&
Objects.equals(this.test47, test.test47) &&
Objects.equals(this.test48, test.test48) &&
Objects.equals(this.test49, test.test49) &&
Objects.equals(this.test50, test.test50) &&
Objects.equals(this.test51, test.test51) &&
Objects.equals(this.test52, test.test52) &&
Objects.equals(this.test53, test.test53) &&
Objects.equals(this.test54, test.test54) &&
Objects.equals(this.test55, test.test55) &&
Objects.equals(this.test56, test.test56) &&
Objects.equals(this.test57, test.test57) &&
Objects.equals(this.test58, test.test58) &&
Objects.equals(this.test59, test.test59) &&
Objects.equals(this.test60, test.test60) &&
Objects.equals(this.test61, test.test61) &&
Objects.equals(this.test62, test.test62) &&
Objects.equals(this.test63, test.test63) &&
Objects.equals(this.test64, test.test64) &&
Objects.equals(this.test65, test.test65) &&
Objects.equals(this.test66, test.test66) &&
Objects.equals(this.test67, test.test67) &&
Objects.equals(this.test68, test.test68) &&
Objects.equals(this.test69, test.test69) &&
Objects.equals(this.test70, test.test70) &&
Objects.equals(this.test71, test.test71) &&
Objects.equals(this.test72, test.test72) &&
Objects.equals(this.test73, test.test73) &&
Objects.equals(this.test74, test.test74) &&
Objects.equals(this.test75, test.test75) &&
Objects.equals(this.test76, test.test76) &&
Objects.equals(this.test77, test.test77) &&
Objects.equals(this.test78, test.test78) &&
Objects.equals(this.test79, test.test79) &&
Objects.equals(this.test80, test.test80) &&
Objects.equals(this.test81, test.test81) &&
Objects.equals(this.test82, test.test82) &&
Objects.equals(this.test83, test.test83) &&
Objects.equals(this.test84, test.test84) &&
Objects.equals(this.test85, test.test85) &&
Objects.equals(this.test86, test.test86) &&
Objects.equals(this.test87, test.test87) &&
Objects.equals(this.test88, test.test88) &&
Objects.equals(this.test89, test.test89) &&
Objects.equals(this.test90, test.test90) &&
Objects.equals(this.test91, test.test91) &&
Objects.equals(this.test92, test.test92) &&
Objects.equals(this.test93, test.test93) &&
Objects.equals(this.test94, test.test94) &&
Objects.equals(this.test95, test.test95) &&
Objects.equals(this.test96, test.test96) &&
Objects.equals(this.test97, test.test97) &&
Objects.equals(this.test98, test.test98) &&
Objects.equals(this.test99, test.test99) &&
Objects.equals(this.test100, test.test100) &&
Objects.equals(this.test101, test.test101) &&
Objects.equals(this.test102, test.test102) &&
Objects.equals(this.test103, test.test103) &&
Objects.equals(this.test104, test.test104) &&
Objects.equals(this.test105, test.test105) &&
Objects.equals(this.test106, test.test106) &&
Objects.equals(this.test107, test.test107) &&
Objects.equals(this.test108, test.test108) &&
Objects.equals(this.test109, test.test109) &&
Objects.equals(this.test101, test.test101) &&
Objects.equals(this.test111, test.test111) &&
Objects.equals(this.test112, test.test112) &&
Objects.equals(this.test113, test.test113) &&
Objects.equals(this.test114, test.test114) &&
Objects.equals(this.test115, test.test115) &&
Objects.equals(this.test116, test.test116) &&
Objects.equals(this.test117, test.test117) &&
Objects.equals(this.test118, test.test118) &&
Objects.equals(this.test119, test.test119) &&
Objects.equals(this.test120, test.test120) &&
Objects.equals(this.test121, test.test121) &&
Objects.equals(this.test122, test.test122) &&
Objects.equals(this.test123, test.test123) &&
Objects.equals(this.test124, test.test124) &&
Objects.equals(this.test125, test.test125) &&
Objects.equals(this.test126, test.test126) &&
Objects.equals(this.test127, test.test127) &&
Objects.equals(this.test128, test.test128) &&
Objects.equals(this.test129, test.test129) &&
Objects.equals(this.test130, test.test130) &&
Objects.equals(this.test131, test.test131) &&
Objects.equals(this.test132, test.test132) &&
Objects.equals(this.test133, test.test133) &&
Objects.equals(this.test134, test.test134) &&
Objects.equals(this.test135, test.test135) &&
Objects.equals(this.test136, test.test136) &&
Objects.equals(this.test137, test.test137) &&
Objects.equals(this.test138, test.test138) &&
Objects.equals(this.test139, test.test139) &&
Objects.equals(this.test140, test.test140) &&
Objects.equals(this.test141, test.test141) &&
Objects.equals(this.test142, test.test142) &&
Objects.equals(this.test143, test.test143) &&
Objects.equals(this.test144, test.test144) &&
Objects.equals(this.test145, test.test145) &&
Objects.equals(this.test146, test.test146) &&
Objects.equals(this.test147, test.test147) &&
Objects.equals(this.test148, test.test148) &&
Objects.equals(this.test149, test.test149) &&
Objects.equals(this.test150, test.test150) &&
Objects.equals(this.test151, test.test151) &&
Objects.equals(this.test152, test.test152) &&
Objects.equals(this.test153, test.test153) &&
Objects.equals(this.test154, test.test154) &&
Objects.equals(this.test155, test.test155) &&
Objects.equals(this.test156, test.test156) &&
Objects.equals(this.test157, test.test157) &&
Objects.equals(this.test158, test.test158) &&
Objects.equals(this.test159, test.test159) &&
Objects.equals(this.test160, test.test160) &&
Objects.equals(this.test161, test.test161) &&
Objects.equals(this.test162, test.test162) &&
Objects.equals(this.test163, test.test163) &&
Objects.equals(this.test164, test.test164) &&
Objects.equals(this.test165, test.test165) &&
Objects.equals(this.test166, test.test166) &&
Objects.equals(this.test167, test.test167) &&
Objects.equals(this.test168, test.test168) &&
Objects.equals(this.test169, test.test169) &&
Objects.equals(this.test170, test.test170) &&
Objects.equals(this.test171, test.test171) &&
Objects.equals(this.test172, test.test172) &&
Objects.equals(this.test173, test.test173) &&
Objects.equals(this.test174, test.test174) &&
Objects.equals(this.test175, test.test175) &&
Objects.equals(this.test176, test.test176) &&
Objects.equals(this.test177, test.test177) &&
Objects.equals(this.test178, test.test178) &&
Objects.equals(this.test179, test.test179) &&
Objects.equals(this.test180, test.test180) &&
Objects.equals(this.test181, test.test181) &&
Objects.equals(this.test182, test.test182) &&
Objects.equals(this.test183, test.test183);
}
}
@@ -5,7 +5,8 @@ import org.jetbrains.annotations.NotNull;
class NullableReturn {
@NotNull Object test(Object o, Object o2, Object o3) {
Object x = o == null ? o3 : o2;
return x == null ? o3 : x;
// probably ephemeral (e.g. o3 could be never null; o2 only), but it's ok to warn in the absence of nullity annotations
return x == null ? <warning descr="Expression 'o3' might evaluate to null but is returned by the method declared as @NotNull">o3</warning> : x;
}
interface Context {}
@@ -1,7 +1,9 @@
/*
Value is always false (x == null; line#11)
'x' was assigned (=; line#10)
Primitive value was boxed ("0".equals(key) ? -1 : m.get(key); line#10)
Value is always false (x == null; line#13)
'x' was assigned (=; line#12)
One of the following happens:
Primitive value was boxed ("0".equals(key) ? -1 : m.get(key); line#12)
or expression cannot be null as it's a value of primitive type 'int' (-1; line#12)
*/
import java.util.*;
@@ -253,6 +253,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
}
public void testObjectsEquals() { doTest(); }
public void testManyObjectEquals() { doTest(); }
public void testManyObjectEquals2() { doTest(); }
public void testLambdaAfterNullCheck() { doTest(); }
public void testFlatMapSideEffect() { doTest(); }
public void testOptionalValueTracking() { doTest(); }
@@ -1921,8 +1921,9 @@ c:com.intellij.codeInspection.dataFlow.lang.ir.FinishElementInstruction
- <init>(com.intellij.psi.PsiElement):V
- accept(com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter,com.intellij.codeInspection.dataFlow.memory.DfaMemoryState):com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState[]
- bindToFactory(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- getVarsToFlush():java.util.Set
- getWrittenVariables(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
- flushVars(java.util.Collection):V
- mayFlushSomething():Z
- removeFromFlushList(java.util.function.Predicate):V
c:com.intellij.codeInspection.dataFlow.lang.ir.FlushFieldsInstruction
- com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- <init>():V
@@ -1933,7 +1934,6 @@ c:com.intellij.codeInspection.dataFlow.lang.ir.FlushVariableInstruction
- accept(com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter,com.intellij.codeInspection.dataFlow.memory.DfaMemoryState):com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState[]
- bindToFactory(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- getVariable():com.intellij.codeInspection.dataFlow.value.DfaVariableValue
- getWrittenVariables(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
c:com.intellij.codeInspection.dataFlow.lang.ir.GotoInstruction
- com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- <init>(com.intellij.codeInspection.dataFlow.lang.ir.ControlFlow$ControlFlowOffset):V
@@ -1948,9 +1948,8 @@ a:com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- a:accept(com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter,com.intellij.codeInspection.dataFlow.memory.DfaMemoryState):com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState[]
- bindToFactory(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- getIndex():I
- getRequiredVariables(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
- getRequiredDescriptors(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
- getSuccessorIndexes():I[]
- getWrittenVariables(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
- isLinear():Z
- pf:nextState(com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter,com.intellij.codeInspection.dataFlow.memory.DfaMemoryState):com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState
- pf:nextStates(com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter,com.intellij.codeInspection.dataFlow.memory.DfaMemoryState):com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState[]
@@ -1964,7 +1963,7 @@ c:com.intellij.codeInspection.dataFlow.lang.ir.PushInstruction
- <init>(com.intellij.codeInspection.dataFlow.value.DfaValue,com.intellij.codeInspection.dataFlow.lang.DfaAnchor):V
- bindToFactory(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- eval(com.intellij.codeInspection.dataFlow.value.DfaValueFactory,com.intellij.codeInspection.dataFlow.memory.DfaMemoryState,com.intellij.codeInspection.dataFlow.value.DfaValue[]):com.intellij.codeInspection.dataFlow.value.DfaValue
- getRequiredVariables(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
- getRequiredDescriptors(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
- getValue():com.intellij.codeInspection.dataFlow.value.DfaValue
c:com.intellij.codeInspection.dataFlow.lang.ir.PushValueInstruction
- com.intellij.codeInspection.dataFlow.lang.ir.EvalInstruction
@@ -1988,7 +1987,6 @@ c:com.intellij.codeInspection.dataFlow.lang.ir.SimpleAssignmentInstruction
- accept(com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter,com.intellij.codeInspection.dataFlow.memory.DfaMemoryState):com.intellij.codeInspection.dataFlow.lang.ir.DfaInstructionState[]
- bindToFactory(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- getDestination():com.intellij.codeInspection.dataFlow.value.DfaVariableValue
- getWrittenVariables(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
c:com.intellij.codeInspection.dataFlow.lang.ir.SpliceInstruction
- com.intellij.codeInspection.dataFlow.lang.ir.Instruction
- <init>(I,I[]):V
@@ -2001,6 +1999,7 @@ c:com.intellij.codeInspection.dataFlow.lang.ir.UnwrapDerivedVariableInstruction
- com.intellij.codeInspection.dataFlow.lang.ir.EvalInstruction
- <init>(com.intellij.codeInspection.dataFlow.value.DerivedVariableDescriptor):V
- eval(com.intellij.codeInspection.dataFlow.value.DfaValueFactory,com.intellij.codeInspection.dataFlow.memory.DfaMemoryState,com.intellij.codeInspection.dataFlow.value.DfaValue[]):com.intellij.codeInspection.dataFlow.value.DfaValue
- getRequiredDescriptors(com.intellij.codeInspection.dataFlow.value.DfaValueFactory):java.util.List
c:com.intellij.codeInspection.dataFlow.lang.ir.WrapDerivedVariableInstruction
- com.intellij.codeInspection.dataFlow.lang.ir.EvalInstruction
- <init>(com.intellij.codeInspection.dataFlow.types.DfType,com.intellij.codeInspection.dataFlow.value.DerivedVariableDescriptor):V
@@ -2019,7 +2018,8 @@ com.intellij.codeInspection.dataFlow.memory.DfaMemoryState
- a:flushFieldsQualifiedBy(java.util.Set):V
- a:flushVariable(com.intellij.codeInspection.dataFlow.value.DfaVariableValue):V
- a:flushVariable(com.intellij.codeInspection.dataFlow.value.DfaVariableValue,Z):V
- a:flushVariables(java.util.function.Predicate):V
- flushVariables(java.util.function.Predicate):V
- a:flushVariables(java.util.function.Predicate,Z):V
- a:getDfType(com.intellij.codeInspection.dataFlow.value.DfaValue):com.intellij.codeInspection.dataFlow.types.DfType
- a:getDfTypeIncludingDerived(com.intellij.codeInspection.dataFlow.value.DfaValue):com.intellij.codeInspection.dataFlow.types.DfType
- a:getMergeabilityKey():java.lang.Object
@@ -2061,7 +2061,7 @@ c:com.intellij.codeInspection.dataFlow.memory.DfaMemoryStateImpl
- flushFieldsQualifiedBy(java.util.Set):V
- flushVariable(com.intellij.codeInspection.dataFlow.value.DfaVariableValue):V
- flushVariable(com.intellij.codeInspection.dataFlow.value.DfaVariableValue,Z):V
- flushVariables(java.util.function.Predicate):V
- flushVariables(java.util.function.Predicate,Z):V
- forRecordedVariableTypes(java.util.function.BiConsumer):V
- getBinOpRange(com.intellij.codeInspection.dataFlow.value.DfaBinOpValue):com.intellij.codeInspection.dataFlow.types.DfType
- getDfType(com.intellij.codeInspection.dataFlow.value.DfaValue):com.intellij.codeInspection.dataFlow.types.DfType
@@ -205,7 +205,7 @@ public class StandardDataFlowInterpreter implements DataFlowInterpreter {
.into(joinInstructions);
for (int index = 0; index < myInstructions.length; index++) {
Instruction instruction = myInstructions[index];
if (instruction instanceof FinishElementInstruction && !((FinishElementInstruction)instruction).getVarsToFlush().isEmpty()) {
if (instruction instanceof FinishElementInstruction finishInstruction && !finishInstruction.mayFlushSomething()) {
// Good chances to squash something after some vars are flushed
joinInstructions.add(myInstructions[index + 1]);
}
@@ -2,8 +2,8 @@
package com.intellij.codeInspection.dataFlow.lang.ir;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.MultiMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
@@ -67,7 +67,7 @@ public abstract class BaseVariableAnalyzer {
/**
* @return true if completed, false if "too complex"
*/
protected boolean runDfa(boolean forward, BiFunction<? super Instruction, ? super BitSet, ? extends BitSet> handleState) {
protected boolean runDfa(boolean forward, BiFunction<? super Instruction, ? super Set<VariableDescriptor>, ? extends Set<VariableDescriptor>> handleState) {
List<Instruction> entryPoints;
if (forward) {
entryPoints = List.of(myInstructions[0]);
@@ -80,11 +80,11 @@ public abstract class BaseVariableAnalyzer {
Deque<InstructionState> queue = new ArrayDeque<>(10);
for (Instruction i : entryPoints) {
queue.addLast(new InstructionState(i, new BitSet()));
queue.addLast(new InstructionState(i, new HashSet<>()));
}
int limit = myForwardMap.size() * 100;
Map<BitSet, IntSet> processed = new HashMap<>();
Map<Set<VariableDescriptor>, IntSet> processed = new HashMap<>();
int steps = 0;
while (!queue.isEmpty()) {
if (steps > limit) {
@@ -94,9 +94,9 @@ public abstract class BaseVariableAnalyzer {
ProgressManager.checkCanceled();
}
InstructionState state = queue.removeFirst();
Instruction instruction = state.first;
Instruction instruction = state.instruction;
Collection<Instruction> nextInstructions = forward ? myForwardMap.get(instruction) : myBackwardMap.get(instruction);
BitSet nextVars = handleState.apply(instruction, state.second);
Set<VariableDescriptor> nextVars = handleState.apply(instruction, state.nextVars);
for (Instruction next : nextInstructions) {
IntSet instructionSet = processed.computeIfAbsent(nextVars, k -> new IntOpenHashSet());
int index = next.getIndex() + 1;
@@ -110,9 +110,6 @@ public abstract class BaseVariableAnalyzer {
return true;
}
private static class InstructionState extends Pair<Instruction, BitSet> {
InstructionState(Instruction first, BitSet second) {
super(first, second);
}
private record InstructionState(Instruction instruction, Set<VariableDescriptor> nextVars) {
}
}
@@ -186,15 +186,14 @@ public final class ControlFlow {
return getFactory().getVarFactory().createVariableValue(new Synthetic(getInstructionCount(), dfType));
}
public @NotNull List<DfaVariableValue> getSynthetics(PsiElement element) {
public @NotNull List<VariableDescriptor> getSynthetics(PsiElement element) {
int startOffset = getStartOffset(element).getInstructionOffset();
List<DfaVariableValue> synthetics = new ArrayList<>();
List<VariableDescriptor> synthetics = new ArrayList<>();
for (DfaValue value : myFactory.getValues()) {
if (value instanceof DfaVariableValue var) {
VariableDescriptor descriptor = var.getDescriptor();
if (descriptor instanceof Synthetic && ((Synthetic)descriptor).myLocation >= startOffset) {
synthetics.add(var);
}
if (value instanceof DfaVariableValue var &&
var.getDescriptor() instanceof Synthetic synthetic &&
synthetic.myLocation >= startOffset) {
synthetics.add(synthetic);
}
}
return synthetics;
@@ -4,16 +4,17 @@ package com.intellij.codeInspection.dataFlow.lang.ir;
import com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter;
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
public class FinishElementInstruction extends Instruction {
private final Set<DfaVariableValue> myVarsToFlush = new HashSet<>();
private final Set<VariableDescriptor> myVarsToFlush = new HashSet<>();
private final PsiElement myElement;
public FinishElementInstruction(PsiElement element) {
@@ -23,9 +24,7 @@ public class FinishElementInstruction extends Instruction {
@Override
public DfaInstructionState[] accept(@NotNull DataFlowInterpreter interpreter, @NotNull DfaMemoryState state) {
if (!myVarsToFlush.isEmpty()) {
for (DfaVariableValue value : myVarsToFlush) {
state.flushVariable(value, false);
}
state.flushVariables(var -> myVarsToFlush.contains(var.getDescriptor()), false);
}
return nextStates(interpreter, state);
}
@@ -33,24 +32,42 @@ public class FinishElementInstruction extends Instruction {
@Override
public @NotNull Instruction bindToFactory(@NotNull DfaValueFactory factory) {
if (myVarsToFlush.isEmpty()) return this;
// Derived analysis may change myVarsToFlush
// E.g. see com.intellij.codeInspection.dataFlow.DfaPsiUtil#getBlockNotNullFields
// (bad idea, but this is how it's done now)
// So we still need to copy instruction to detach myVarsToFlush list
var instruction = new FinishElementInstruction(myElement);
for (DfaVariableValue var : myVarsToFlush) {
instruction.myVarsToFlush.add(var.bindToFactory(factory));
}
instruction.flushVars(myVarsToFlush);
return instruction;
}
@Override
public List<DfaVariableValue> getWrittenVariables(DfaValueFactory factory) {
return List.copyOf(myVarsToFlush);
/**
* Add variables with given descriptors to the flush list
*
* @param vars vars to flush
*/
public void flushVars(@NotNull Collection<@NotNull VariableDescriptor> vars) {
myVarsToFlush.addAll(vars);
}
/**
* Removes variable descriptors from the flush list that match the given predicate.
*
* @param predicate the predicate used to determine which variable descriptors to remove
*/
public void removeFromFlushList(@NotNull Predicate<? super VariableDescriptor> predicate) {
myVarsToFlush.removeIf(predicate);
}
/**
* @return true if this instruction may flush some variables
*/
public boolean mayFlushSomething() {
return !myVarsToFlush.isEmpty();
}
@Override
public String toString() {
return "FINISH " + (myElement == null ? "" : myElement) + (myVarsToFlush.isEmpty() ? "" : "; flushing " + myVarsToFlush);
}
public Set<DfaVariableValue> getVarsToFlush() {
return myVarsToFlush;
}
}
@@ -8,8 +8,6 @@ import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Flush single variable
*/
@@ -32,11 +30,6 @@ public class FlushVariableInstruction extends Instruction {
return myVariable;
}
@Override
public List<DfaVariableValue> getWrittenVariables(DfaValueFactory factory) {
return List.of(myVariable);
}
@Override
public DfaInstructionState[] accept(@NotNull DataFlowInterpreter interpreter, @NotNull DfaMemoryState stateBefore) {
stateBefore.flushVariable(getVariable());
@@ -5,7 +5,7 @@ package com.intellij.codeInspection.dataFlow.lang.ir;
import com.intellij.codeInspection.dataFlow.interpreter.DataFlowInterpreter;
import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
@@ -57,19 +57,11 @@ public abstract class Instruction {
}
/**
* @return list of variables that are known to be always written by this instruction
* @return list of variable descriptors that must stay reachable at this instruction.
* The variables that are not required to be reachable by any instructions
* until the end of the interpretation could be flushed automatically.
*/
public List<DfaVariableValue> getWrittenVariables(DfaValueFactory factory) {
return Collections.emptyList();
}
/**
* @return list of variables that must stay reachable at this instruction.
* Variables that are not required to be reachable by any instructions
* until the next write or the interpretation end,
* could be flushed automatically.
*/
public List<DfaVariableValue> getRequiredVariables(DfaValueFactory factory) {
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return Collections.emptyList();
}
@@ -1,9 +1,8 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.dataFlow.lang.ir;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.util.containers.MultiMap;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -17,79 +16,62 @@ final class LiveVariablesAnalyzer extends BaseVariableAnalyzer {
@Override
protected boolean isInterestingInstruction(Instruction instruction) {
if (instruction == myInstructions[0]) return true;
if (!instruction.getRequiredVariables(myFactory).isEmpty() || !instruction.getWrittenVariables(myFactory).isEmpty()) return true;
if (!instruction.getRequiredDescriptors(myFactory).isEmpty()) return true;
return !instruction.isLinear() || instruction instanceof FinishElementInstruction;
}
private @Nullable Map<FinishElementInstruction, BitSet> findLiveVars() {
final Map<FinishElementInstruction, BitSet> result = new HashMap<>();
private @Nullable Map<FinishElementInstruction, Set<VariableDescriptor>> findLiveVars() {
final Map<FinishElementInstruction, Set<VariableDescriptor>> result = new HashMap<>();
boolean ok = runDfa(false, (instruction, liveVars) -> {
if (instruction instanceof FinishElementInstruction) {
BitSet set = result.get(instruction);
if (instruction instanceof FinishElementInstruction finishInstruction) {
Set<VariableDescriptor> set = result.get(instruction);
if (set != null) {
set.or(liveVars);
return (BitSet)set.clone();
set.addAll(liveVars);
return new HashSet<>(set);
}
else if (!liveVars.isEmpty()) {
result.put((FinishElementInstruction)instruction, (BitSet)liveVars.clone());
result.put(finishInstruction, new HashSet<>(liveVars));
}
}
List<DfaVariableValue> writtenVariables = instruction.getWrittenVariables(myFactory);
if (!writtenVariables.isEmpty()) {
BitSet newVars = (BitSet)liveVars.clone();
for (DfaVariableValue written : writtenVariables) {
newVars.clear(written.getID());
for (DfaVariableValue var : written.getDependentVariables()) {
newVars.clear(var.getID());
}
}
return newVars;
} else {
var processor = new Consumer<DfaVariableValue>() {
boolean cloned = false;
BitSet newVars = liveVars;
var processor = new Consumer<VariableDescriptor>() {
boolean cloned = false;
Set<VariableDescriptor> newVars = liveVars;
@Override
public void accept(DfaVariableValue value) {
if (!newVars.get(value.getID())) {
if (!cloned) {
newVars = (BitSet)newVars.clone();
cloned = true;
}
newVars.set(value.getID());
@Override
public void accept(VariableDescriptor value) {
if (!newVars.contains(value)) {
if (!cloned) {
newVars = new HashSet<>(newVars);
cloned = true;
}
newVars.add(value);
}
};
StreamEx.of(instruction.getRequiredVariables(myFactory))
.flatMap(v -> StreamEx.of(v.getDependentVariables()).prepend(v)).distinct().forEach(processor);
return processor.newVars;
}
}
};
instruction.getRequiredDescriptors(myFactory).forEach(processor);
return processor.newVars;
});
return ok ? result : null;
}
void flushDeadVariablesOnStatementFinish() {
final Map<FinishElementInstruction, BitSet> liveVars = findLiveVars();
final Map<FinishElementInstruction, Set<VariableDescriptor>> liveVars = findLiveVars();
if (liveVars == null) return;
final MultiMap<FinishElementInstruction, DfaVariableValue> toFlush = MultiMap.createSet();
final MultiMap<FinishElementInstruction, VariableDescriptor> toFlush = MultiMap.createSet();
boolean ok = runDfa(true, (instruction, prevLiveVars) -> {
if (instruction instanceof FinishElementInstruction) {
BitSet currentlyLive = liveVars.get(instruction);
if (instruction instanceof FinishElementInstruction finishInstruction) {
Set<VariableDescriptor> currentlyLive = liveVars.get(instruction);
if (currentlyLive == null) {
currentlyLive = new BitSet();
currentlyLive = new HashSet<>();
}
int index = 0;
while (true) {
int setBit = prevLiveVars.nextSetBit(index);
if (setBit < 0) break;
if (!currentlyLive.get(setBit)) {
toFlush.putValue((FinishElementInstruction)instruction, (DfaVariableValue)myFactory.getValue(setBit));
for (VariableDescriptor var : prevLiveVars) {
if (!currentlyLive.contains(var)) {
toFlush.putValue(finishInstruction, var);
}
index = setBit + 1;
}
return currentlyLive;
}
@@ -99,9 +81,9 @@ final class LiveVariablesAnalyzer extends BaseVariableAnalyzer {
if (ok) {
for (FinishElementInstruction instruction : toFlush.keySet()) {
Collection<DfaVariableValue> values = toFlush.get(instruction);
values.removeIf(var -> var.getDescriptor().isImplicitReadPossible());
instruction.getVarsToFlush().addAll(values);
Collection<VariableDescriptor> values = toFlush.get(instruction);
values.removeIf(var -> var.isImplicitReadPossible());
instruction.flushVars(values);
}
}
}
@@ -6,13 +6,11 @@ import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* An instruction that pushes given value to the stack
*/
@@ -30,8 +28,8 @@ public class PushInstruction extends EvalInstruction {
}
@Override
public List<DfaVariableValue> getRequiredVariables(DfaValueFactory factory) {
return ContainerUtil.createMaybeSingletonList(tryCast(myValue, DfaVariableValue.class));
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return getValue() instanceof DfaVariableValue var ? List.of(var.getDescriptor()) : List.of();
}
public @NotNull DfaValue getValue() {
@@ -10,8 +10,6 @@ import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Assign the value from the stack to a specified static destination
*/
@@ -42,11 +40,6 @@ public class SimpleAssignmentInstruction extends ExpressionPushingInstruction {
return nextStates(interpreter, stateBefore);
}
@Override
public List<DfaVariableValue> getWrittenVariables(DfaValueFactory factory) {
return List.of(myDestination);
}
@Override
public String toString() {
return "ASSIGN_TO " + myDestination;
@@ -5,8 +5,11 @@ import com.intellij.codeInspection.dataFlow.memory.DfaMemoryState;
import com.intellij.codeInspection.dataFlow.value.DerivedVariableDescriptor;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Instruction to push a field qualified by the value on the stack
*/
@@ -23,6 +26,11 @@ public class UnwrapDerivedVariableInstruction extends EvalInstruction {
return myDerivedVariableDescriptor.createValue(factory, arguments[0]);
}
@Override
public List<VariableDescriptor> getRequiredDescriptors(@NotNull DfaValueFactory factory) {
return List.of(myDerivedVariableDescriptor);
}
@Override
public String toString() {
return "UNWRAP " + myDerivedVariableDescriptor;
@@ -200,7 +200,19 @@ public interface DfaMemoryState {
*
* @param filter filter to check whether the variable should be flushed
*/
void flushVariables(@NotNull Predicate<? super @NotNull DfaVariableValue> filter);
default void flushVariables(@NotNull Predicate<? super @NotNull DfaVariableValue> filter) {
flushVariables(filter, true);
}
/**
* Flush all the variables for which filter returns true
*
* @param filter filter to check whether the variable should be flushed
* @param canonicalize whether to canonicalize the variable before flushing. Flushing canonical variable allows to forget
* about all known aliases as well. Flushing without canonicalization could be necessary only
* to simplify memory state, if it's known that given variable is never used anymore.
*/
void flushVariables(@NotNull Predicate<? super @NotNull DfaVariableValue> filter, boolean canonicalize);
/**
* Mark this state as ephemeral. See {@link #isEphemeral()} for details.
@@ -1416,7 +1416,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
@Override
public void flushVariables(@NotNull Predicate<? super @NotNull DfaVariableValue> filter) {
public void flushVariables(@NotNull Predicate<? super @NotNull DfaVariableValue> filter, boolean canonicalize) {
Set<DfaVariableValue> vars = new HashSet<>();
for (EqClass aClass : myEqClasses) {
if (aClass != null) {
@@ -1427,7 +1427,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
vars.addAll(myVariableTypes.keySet());
vars.removeIf(filter.negate());
vars.forEach(this::flushVariable);
vars.forEach(variable -> flushVariable(variable, canonicalize));
}
@Override