From 0ca49293fad9fb17a5afaac5b692d254c3fc0a56 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Sun, 3 Nov 2024 11:30:40 +0100 Subject: [PATCH] [java-dfa] IDEA-361818 Make LiveVariableAnalyzer descriptor-based, rather than variable-based GitOrigin-RevId: c3a8a029fe4e6f4c3ef7bdd67726ec4993cb3c80 --- .../engine/dfaassist/DebuggerDfaRunner.java | 18 +- .../codeInspection/dataFlow/DfaPsiUtil.java | 10 +- .../dataFlow/java/ControlFlowAnalyzer.java | 18 +- .../java/inst/ArrayAccessInstruction.java | 14 +- .../java/inst/ArrayStoreInstruction.java | 18 +- .../dataFlow/java/inst/AssignInstruction.java | 8 - .../EndOfInstanceInitializerInstruction.java | 8 +- .../dataFlow/java/inst/EscapeInstruction.java | 10 +- .../java/inst/InstanceofInstruction.java | 19 +- .../java/inst/JavaArrayStoreInstruction.java | 7 +- .../java/inst/MethodCallInstruction.java | 6 +- .../dataFlow/jvm/SpecialField.java | 5 + .../descriptors/ArrayElementDescriptor.java | 16 + ...cationHandlerImplementationInspection.java | 14 +- .../defUse/OverwrittenFieldAnalyzer.java | 3 - .../fixture/AssignmentFieldAliasing.java | 10 + .../dataFlow/fixture/ManyObjectEquals2.java | 384 ++++++++++++++++++ .../dataFlow/fixture/NullableReturn.java | 3 +- .../inspection/dataFlow/tracker/Reboxing.java | 8 +- .../DataFlowInspection8Test.java | 1 + .../analysis-impl/api-dump-unreviewed.txt | 18 +- .../StandardDataFlowInterpreter.java | 2 +- .../lang/ir/BaseVariableAnalyzer.java | 17 +- .../dataFlow/lang/ir/ControlFlow.java | 13 +- .../lang/ir/FinishElementInstruction.java | 49 ++- .../lang/ir/FlushVariableInstruction.java | 7 - .../dataFlow/lang/ir/Instruction.java | 18 +- .../lang/ir/LiveVariablesAnalyzer.java | 86 ++-- .../dataFlow/lang/ir/PushInstruction.java | 8 +- .../lang/ir/SimpleAssignmentInstruction.java | 7 - .../ir/UnwrapDerivedVariableInstruction.java | 8 + .../dataFlow/memory/DfaMemoryState.java | 14 +- .../dataFlow/memory/DfaMemoryStateImpl.java | 4 +- 33 files changed, 623 insertions(+), 208 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/ManyObjectEquals2.java diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java index 85aa89ebe270..4067cfe2b942 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/dfaassist/DebuggerDfaRunner.java @@ -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> jdiToDfa = createPreliminaryJdiMap(provider, anchor, factory, proxy); + Map> 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> createPreliminaryJdiMap(@NotNull DfaAssistProvider provider, @NotNull PsiElement anchor, - @NotNull DfaValueFactory factory, + @NotNull ControlFlow flow, @NotNull StackFrameProxyEx proxy) throws EvaluateException { + DfaValueFactory factory = flow.getFactory(); + Set descriptors = StreamEx.of(flow.getInstructions()).flatCollection(inst -> inst.getRequiredDescriptors(factory)) + .toSet(); Map> myMap = new HashMap<>(); for (DfaValue dfaValue : factory.getValues().toArray(DfaValue.EMPTY_ARRAY)) { - if (dfaValue instanceof DfaVariableValue dfaVar) { + StreamEx 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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java index 1eafc6d50b64..cc2b06d04fef 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java @@ -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 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()) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java index 9686f53526c8..e2407543e900 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java @@ -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 synthetics = myCurrentFlow.getSynthetics(element); + List 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)); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/ArrayAccessInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/ArrayAccessInstruction.java index 50a159ada658..c84075f44b1f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/ArrayAccessInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/ArrayAccessInstruction.java @@ -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 getRequiredVariables(DfaValueFactory factory) { - return ContainerUtil.createMaybeSingletonList(myStaticValue); + public List getRequiredDescriptors(@NotNull DfaValueFactory factory) { + return myStaticValue == null ? List.of(SpecialField.ARRAY_LENGTH) : + List.of(myStaticValue, SpecialField.ARRAY_LENGTH); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/ArrayStoreInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/ArrayStoreInstruction.java index 1c4f0d175019..0ed8da8a84f1 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/ArrayStoreInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/ArrayStoreInstruction.java @@ -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 getWrittenVariables(DfaValueFactory factory) { - return ContainerUtil.createMaybeSingletonList(myStaticVariable); + public List getRequiredDescriptors(@NotNull DfaValueFactory factory) { + return List.of(SpecialField.ARRAY_LENGTH); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/AssignInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/AssignInstruction.java index 549e193dada7..468276a4849c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/AssignInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/AssignInstruction.java @@ -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 getWrittenVariables(DfaValueFactory factory) { - return ContainerUtil.createMaybeSingletonList(tryCast(myAssignedValue, DfaVariableValue.class)); - } - @Override public DfaInstructionState[] accept(@NotNull DataFlowInterpreter interpreter, @NotNull DfaMemoryState stateBefore) { DfaValue dfaSource = stateBefore.pop(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/EndOfInstanceInitializerInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/EndOfInstanceInitializerInstruction.java index 173d611b280f..67055992f75f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/EndOfInstanceInitializerInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/EndOfInstanceInitializerInstruction.java @@ -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 getRequiredVariables(DfaValueFactory factory) { + public List 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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/EscapeInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/EscapeInstruction.java index 4ba218198f41..5cae491256c7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/EscapeInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/EscapeInstruction.java @@ -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 getRequiredVariables(DfaValueFactory factory) { - return List.copyOf(myEscapedVars); + public List getRequiredDescriptors(@NotNull DfaValueFactory factory) { + return StreamEx.of(myEscapedVars) + .flatMap(v -> StreamEx.of(v.getDependentVariables()).append(v)) + .map(DfaVariableValue::getDescriptor) + .distinct() + .toList(); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/InstanceofInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/InstanceofInstruction.java index a092d8953164..85d80f33b1d3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/InstanceofInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/InstanceofInstruction.java @@ -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 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)" : ""); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/JavaArrayStoreInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/JavaArrayStoreInstruction.java index 18043b192095..cf886a05cd7a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/JavaArrayStoreInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/JavaArrayStoreInstruction.java @@ -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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/MethodCallInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/MethodCallInstruction.java index 94e3d892d66e..8a18ab03d8c6 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/MethodCallInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/inst/MethodCallInstruction.java @@ -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 getRequiredVariables(DfaValueFactory factory) { - return ContainerUtil.createMaybeSingletonList(tryCast(myPrecalculatedReturnValue, DfaVariableValue.class)); + public List getRequiredDescriptors(@NotNull DfaValueFactory factory) { + return myPrecalculatedReturnValue instanceof DfaVariableValue var ? + List.of(var.getDescriptor()) : List.of(); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/SpecialField.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/SpecialField.java index 5d4ae98ee7a8..bd5381eb8187 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/SpecialField.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/SpecialField.java @@ -325,6 +325,11 @@ public enum SpecialField implements DerivedVariableDescriptor { return myFinal; } + @Override + public boolean isImplicitReadPossible() { + return true; + } + public abstract boolean isMyQualifierType(DfType type); /** diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/ArrayElementDescriptor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/ArrayElementDescriptor.java index bc27d2ab7164..b02384ca90b7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/ArrayElementDescriptor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/jvm/descriptors/ArrayElementDescriptor.java @@ -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; + } } diff --git a/java/java-analysis-impl/src/com/siyeh/ig/bugs/SuspiciousInvocationHandlerImplementationInspection.java b/java/java-analysis-impl/src/com/siyeh/ig/bugs/SuspiciousInvocationHandlerImplementationInspection.java index 5da63c4d93d8..2293dbd3a18b 100644 --- a/java/java-analysis-impl/src/com/siyeh/ig/bugs/SuspiciousInvocationHandlerImplementationInspection.java +++ b/java/java-analysis-impl/src/com/siyeh/ig/bugs/SuspiciousInvocationHandlerImplementationInspection.java @@ -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 result = new ArrayList<>(); Instruction instruction = flow.getInstruction(0); for (Instruction inst : flow.getInstructions()) { - if (inst instanceof FinishElementInstruction) { - Set 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) { diff --git a/java/java-impl-inspections/src/com/intellij/codeInspection/defUse/OverwrittenFieldAnalyzer.java b/java/java-impl-inspections/src/com/intellij/codeInspection/defUse/OverwrittenFieldAnalyzer.java index f117b0a68e10..c95286f500c6 100644 --- a/java/java-impl-inspections/src/com/intellij/codeInspection/defUse/OverwrittenFieldAnalyzer.java +++ b/java/java-impl-inspections/src/com/intellij/codeInspection/defUse/OverwrittenFieldAnalyzer.java @@ -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); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AssignmentFieldAliasing.java b/java/java-tests/testData/inspection/dataFlow/fixture/AssignmentFieldAliasing.java index cf7c2ebc8d62..44560f0b5579 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/AssignmentFieldAliasing.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AssignmentFieldAliasing.java @@ -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) { diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ManyObjectEquals2.java b/java/java-tests/testData/inspection/dataFlow/fixture/ManyObjectEquals2.java new file mode 100644 index 000000000000..303b919a8fee --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ManyObjectEquals2.java @@ -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 equals(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); + } + +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NullableReturn.java b/java/java-tests/testData/inspection/dataFlow/fixture/NullableReturn.java index faf555da9e0c..b2d62263211b 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/NullableReturn.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NullableReturn.java @@ -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 ? o3 : x; } interface Context {} diff --git a/java/java-tests/testData/inspection/dataFlow/tracker/Reboxing.java b/java/java-tests/testData/inspection/dataFlow/tracker/Reboxing.java index 3fef33bb66c5..8b6223ef4ead 100644 --- a/java/java-tests/testData/inspection/dataFlow/tracker/Reboxing.java +++ b/java/java-tests/testData/inspection/dataFlow/tracker/Reboxing.java @@ -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.*; diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index 0b26f7ceaaa5..e20a236e7783 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -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(); } diff --git a/platform/analysis-impl/api-dump-unreviewed.txt b/platform/analysis-impl/api-dump-unreviewed.txt index e55a7395d3a6..442065924e23 100644 --- a/platform/analysis-impl/api-dump-unreviewed.txt +++ b/platform/analysis-impl/api-dump-unreviewed.txt @@ -1921,8 +1921,9 @@ c:com.intellij.codeInspection.dataFlow.lang.ir.FinishElementInstruction - (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 - ():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 - (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 - (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 - (I,I[]):V @@ -2001,6 +1999,7 @@ c:com.intellij.codeInspection.dataFlow.lang.ir.UnwrapDerivedVariableInstruction - com.intellij.codeInspection.dataFlow.lang.ir.EvalInstruction - (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 - (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 diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/interpreter/StandardDataFlowInterpreter.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/interpreter/StandardDataFlowInterpreter.java index 624796c4b349..12c1026c2157 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/interpreter/StandardDataFlowInterpreter.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/interpreter/StandardDataFlowInterpreter.java @@ -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]); } diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/BaseVariableAnalyzer.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/BaseVariableAnalyzer.java index 8e68b59bd9c2..38b575192a14 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/BaseVariableAnalyzer.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/BaseVariableAnalyzer.java @@ -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 handleState) { + protected boolean runDfa(boolean forward, BiFunction, ? extends Set> handleState) { List entryPoints; if (forward) { entryPoints = List.of(myInstructions[0]); @@ -80,11 +80,11 @@ public abstract class BaseVariableAnalyzer { Deque 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 processed = new HashMap<>(); + Map, 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 nextInstructions = forward ? myForwardMap.get(instruction) : myBackwardMap.get(instruction); - BitSet nextVars = handleState.apply(instruction, state.second); + Set 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 { - InstructionState(Instruction first, BitSet second) { - super(first, second); - } + private record InstructionState(Instruction instruction, Set nextVars) { } } diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/ControlFlow.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/ControlFlow.java index e8bd86d43ad3..28589a5a147d 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/ControlFlow.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/ControlFlow.java @@ -186,15 +186,14 @@ public final class ControlFlow { return getFactory().getVarFactory().createVariableValue(new Synthetic(getInstructionCount(), dfType)); } - public @NotNull List getSynthetics(PsiElement element) { + public @NotNull List getSynthetics(PsiElement element) { int startOffset = getStartOffset(element).getInstructionOffset(); - List synthetics = new ArrayList<>(); + List 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; diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/FinishElementInstruction.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/FinishElementInstruction.java index dcc4d8e27c0c..4683c929c4b8 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/FinishElementInstruction.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/FinishElementInstruction.java @@ -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 myVarsToFlush = new HashSet<>(); + private final Set 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 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 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 getVarsToFlush() { - return myVarsToFlush; - } } diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/FlushVariableInstruction.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/FlushVariableInstruction.java index ab174bfcaf89..38b7055f5fdc 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/FlushVariableInstruction.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/FlushVariableInstruction.java @@ -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 getWrittenVariables(DfaValueFactory factory) { - return List.of(myVariable); - } - @Override public DfaInstructionState[] accept(@NotNull DataFlowInterpreter interpreter, @NotNull DfaMemoryState stateBefore) { stateBefore.flushVariable(getVariable()); diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/Instruction.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/Instruction.java index 5dbaeca93aad..bb64e8df7d65 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/Instruction.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/Instruction.java @@ -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 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 getRequiredVariables(DfaValueFactory factory) { + public List getRequiredDescriptors(@NotNull DfaValueFactory factory) { return Collections.emptyList(); } diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/LiveVariablesAnalyzer.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/LiveVariablesAnalyzer.java index 472b012dcba7..15c9d3ed2403 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/LiveVariablesAnalyzer.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/LiveVariablesAnalyzer.java @@ -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 findLiveVars() { - final Map result = new HashMap<>(); + private @Nullable Map> findLiveVars() { + final Map> result = new HashMap<>(); boolean ok = runDfa(false, (instruction, liveVars) -> { - if (instruction instanceof FinishElementInstruction) { - BitSet set = result.get(instruction); + if (instruction instanceof FinishElementInstruction finishInstruction) { + Set 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 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() { - boolean cloned = false; - BitSet newVars = liveVars; + var processor = new Consumer() { + boolean cloned = false; + Set 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 liveVars = findLiveVars(); + final Map> liveVars = findLiveVars(); if (liveVars == null) return; - final MultiMap toFlush = MultiMap.createSet(); + final MultiMap toFlush = MultiMap.createSet(); boolean ok = runDfa(true, (instruction, prevLiveVars) -> { - if (instruction instanceof FinishElementInstruction) { - BitSet currentlyLive = liveVars.get(instruction); + if (instruction instanceof FinishElementInstruction finishInstruction) { + Set 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 values = toFlush.get(instruction); - values.removeIf(var -> var.getDescriptor().isImplicitReadPossible()); - instruction.getVarsToFlush().addAll(values); + Collection values = toFlush.get(instruction); + values.removeIf(var -> var.isImplicitReadPossible()); + instruction.flushVars(values); } } } diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/PushInstruction.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/PushInstruction.java index bb527877f6cd..fded2ff8b662 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/PushInstruction.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/PushInstruction.java @@ -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 getRequiredVariables(DfaValueFactory factory) { - return ContainerUtil.createMaybeSingletonList(tryCast(myValue, DfaVariableValue.class)); + public List getRequiredDescriptors(@NotNull DfaValueFactory factory) { + return getValue() instanceof DfaVariableValue var ? List.of(var.getDescriptor()) : List.of(); } public @NotNull DfaValue getValue() { diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/SimpleAssignmentInstruction.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/SimpleAssignmentInstruction.java index 943d1a206030..4c86b32dba80 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/SimpleAssignmentInstruction.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/SimpleAssignmentInstruction.java @@ -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 getWrittenVariables(DfaValueFactory factory) { - return List.of(myDestination); - } - @Override public String toString() { return "ASSIGN_TO " + myDestination; diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/UnwrapDerivedVariableInstruction.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/UnwrapDerivedVariableInstruction.java index 8566f7d0ef88..208c9e324f62 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/UnwrapDerivedVariableInstruction.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/lang/ir/UnwrapDerivedVariableInstruction.java @@ -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 getRequiredDescriptors(@NotNull DfaValueFactory factory) { + return List.of(myDerivedVariableDescriptor); + } + @Override public String toString() { return "UNWRAP " + myDerivedVariableDescriptor; diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/memory/DfaMemoryState.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/memory/DfaMemoryState.java index abefb8826df6..6f7242af12b3 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/memory/DfaMemoryState.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/memory/DfaMemoryState.java @@ -200,7 +200,19 @@ public interface DfaMemoryState { * * @param filter filter to check whether the variable should be flushed */ - void flushVariables(@NotNull Predicate filter); + default void flushVariables(@NotNull Predicate 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 filter, boolean canonicalize); /** * Mark this state as ephemeral. See {@link #isEphemeral()} for details. diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/memory/DfaMemoryStateImpl.java b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/memory/DfaMemoryStateImpl.java index f4c15ee811f7..2ccecb0818ce 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/memory/DfaMemoryStateImpl.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/dataFlow/memory/DfaMemoryStateImpl.java @@ -1416,7 +1416,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } @Override - public void flushVariables(@NotNull Predicate filter) { + public void flushVariables(@NotNull Predicate filter, boolean canonicalize) { Set 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