From 023bbcde39c82ec77c061a347fb12e0cf7ea9b33 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 10 Nov 2025 16:12:46 +0100 Subject: [PATCH] IDEA-381915 [java-dfa] False positive nullability warning on Nullable private field previously checked for null GitOrigin-RevId: 7c9af4821b2fd7cb867dc98633e63e1eef9e8aac --- .../fixture/PrivateFieldPureMethod.java | 19 +++++++++++++++++++ .../DataFlowInspectionTest.java | 1 + .../dataFlow/memory/DfaMemoryState.java | 3 ++- .../dataFlow/memory/DfaMemoryStateImpl.java | 17 +++++++++-------- 4 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/PrivateFieldPureMethod.java diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/PrivateFieldPureMethod.java b/java/java-tests/testData/inspection/dataFlow/fixture/PrivateFieldPureMethod.java new file mode 100644 index 000000000000..8c197fdaae2f --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/PrivateFieldPureMethod.java @@ -0,0 +1,19 @@ +package org.example; + +import org.jetbrains.annotations.*; + +class PrivateFieldPureMethod { + private @Nullable String myField; + + void test() { + boolean b = myField != null; + if (isValid() && b) { + System.out.println(myField.trim()); + } + } + + @Contract(pure = true) + public boolean isValid() { + return Math.random() > 0.5; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index 23e167dea0b0..7c2c21359fb7 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -764,4 +764,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { public void testClosureInConstructor() { doTest(); } public void testHugeMethodFlow() { doTest(); } + public void testPrivateFieldPureMethod() { doTest(); } } 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 e63f0016b9b3..8ddbee5d49f3 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 @@ -187,7 +187,8 @@ public interface DfaMemoryState { void flushVariable(@NotNull DfaVariableValue variable); /** - * Flush all the variables for which filter returns true + * Flush all the variables for which filter returns true. The type of unstable variables will be corrected, + * like {@link #flushFields()} does (see {@link DfType#correctTypeOnFlush(DfType)}). * * @param filter filter to check whether the variable should be flushed */ 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 323ef5950d94..0d7253856482 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 @@ -203,7 +203,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { if (var == value) return; value = handleStackValueOnVariableFlush(value, var, null); - flushVariable(var, var.getDfType().isMergeable(var.getInherentType()), true); + flushVariable(var, var.getDfType().isMergeable(var.getInherentType()), true, false); flushQualifiedMethods(var); DfType dfType = filterDfTypeOnAssignment(var, getDfType(value)).meet(var.getDfType()); @@ -1410,20 +1410,20 @@ public class DfaMemoryStateImpl implements DfaMemoryState { @Override public void flushVariable(@NotNull DfaVariableValue variable) { - flushVariable(variable, true, true); + flushVariable(variable, true, true, false); } @Override public void flushVariables(@NotNull Predicate filter) { - flushVariables(filter, false); + flushVariables(filter, false, true); } @Override public void forgetVariables(@NotNull Predicate filter) { - flushVariables(filter, true); + flushVariables(filter, true, false); } - private void flushVariables(@NotNull Predicate filter, boolean onlyThis) { + private void flushVariables(@NotNull Predicate filter, boolean onlyThis, boolean markFlushed) { BitSet vars = new BitSet(); for (EqClassImpl aClass : myEqClasses) { if (aClass != null) { @@ -1436,7 +1436,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { for (int id = vars.nextSetBit(0); id >= 0; id = vars.nextSetBit(id + 1)) { DfaVariableValue var = (DfaVariableValue)myFactory.getValue(id); if (filter.test(var)) { - flushVariable(var, !onlyThis, !onlyThis); + flushVariable(var, !onlyThis, !onlyThis, markFlushed && var.isFlushableByCalls()); } } } @@ -1449,8 +1449,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState { * 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. * @param flushDeps whether to flush dependencies + * @param markFlushed whether to mark variable as flushed */ - private void flushVariable(@NotNull DfaVariableValue variable, boolean canonicalize, boolean flushDeps) { + private void flushVariable(@NotNull DfaVariableValue variable, boolean canonicalize, boolean flushDeps, boolean markFlushed) { DfaVariableValue canonical = canonicalize ? canonicalize(variable) : variable; EqClass eqClass = canonical.getDependentVariables().isEmpty() ? null : getEqClass(canonical); DfaVariableValue newCanonical = @@ -1459,7 +1460,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { .orElse(null); myStack.replaceAll(value -> handleStackValueOnVariableFlush(value, canonical, newCanonical)); - doFlush(canonical, false); + doFlush(canonical, markFlushed); if (flushDeps) { flushDependencies(canonical); }