diff --git a/checker/tests/nullness/Issue771.java b/checker/tests/nullness/Issue771.java new file mode 100644 index 00000000000..5bf1f3f3727 --- /dev/null +++ b/checker/tests/nullness/Issue771.java @@ -0,0 +1,50 @@ +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; + +public class Issue771 { + private @MonotonicNonNull String value = null; + + @RequiresNonNull("value") + void print() { + System.out.println(value); + } + + void func() { + if (value == null) { + return; + } + + Runnable r = () -> print(); + } + + private final @Nullable String finalValue; + + Issue771(@Nullable String finalValue) { + this.finalValue = finalValue; + } + + void finalField() { + if (finalValue == null) { + return; + } + + Runnable r = () -> finalValue.toString(); + } + + private @Nullable String nullableValue; + + void nullableField() { + if (nullableValue == null) { + return; + } + + Runnable r = + () -> { + // :: error: (dereference.of.nullable) + nullableValue.toString(); + }; + nullableValue = null; + r.run(); + } +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2e0e8f0991e..facb4ced71d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -231,7 +231,7 @@ median of four warm-daemon reps per side). **Closed issues:** -eisop#433, eisop#792, eisop#863, eisop#1801. +eisop#433, eisop#771, eisop#792, eisop#863, eisop#1801. Version 3.49.5-eisop1 (April 26, 2026) diff --git a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java index d4f087c0fce..c785766484f 100644 --- a/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java +++ b/framework/src/main/java/org/checkerframework/framework/flow/CFAbstractTransfer.java @@ -418,10 +418,17 @@ public S initialStore(UnderlyingAST underlyingAST, List param addFinalLocalValues(store, enclosingElement); } - // We want the initialization stuff, but need to throw out any refinements. + // We want the initialization stuff, but need to throw out refinements that might not + // still hold when the lambda is invoked later. // Update values in place; keys are unchanged. store.fieldValues.replaceAll( (fieldAccess, currentValue) -> { + V laterValue = + store.newFieldValueAfterMethodCall( + fieldAccess, analysis.atypeFactory, currentValue); + if (laterValue != null) { + return laterValue; + } AnnotatedTypeMirror declaredType = atypeFactory.getAnnotatedType(fieldAccess.getField()); return analysis.createAbstractValue(declaredType)