IDEA-381915 [java-dfa] False positive nullability warning on Nullable private field previously checked for null

GitOrigin-RevId: 7c9af4821b2fd7cb867dc98633e63e1eef9e8aac
This commit is contained in:
Tagir Valeev
2025-11-10 19:39:07 +00:00
committed by intellij-monorepo-bot
parent cd294aa27d
commit 023bbcde39
4 changed files with 31 additions and 9 deletions
@@ -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;
}
}
@@ -764,4 +764,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testClosureInConstructor() { doTest(); }
public void testHugeMethodFlow() { doTest(); }
public void testPrivateFieldPureMethod() { doTest(); }
}
@@ -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
*/
@@ -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<? super @NotNull DfaVariableValue> filter) {
flushVariables(filter, false);
flushVariables(filter, false, true);
}
@Override
public void forgetVariables(@NotNull Predicate<? super @NotNull DfaVariableValue> filter) {
flushVariables(filter, true);
flushVariables(filter, true, false);
}
private void flushVariables(@NotNull Predicate<? super @NotNull DfaVariableValue> filter, boolean onlyThis) {
private void flushVariables(@NotNull Predicate<? super @NotNull DfaVariableValue> 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);
}