Skip to content

Precision loss after a branch merges with dead code #2129

Description

@wmdietl

Split off from #627 / #2128.

-AignoreDeadCode (#2128) stops errors from being reported on code that is physically
inside a dead branch (a literal-condition if, or code dataflow proves is CFG-unreachable). It
does not fix a related but distinct problem: precision loss on code after a branch, caused
by merging with a dead alternative. Example:

@Nullable Object x;
if (true) {
    x = new Object();
} else {
    x = null;
}
x.toString();  // false positive today, even though the else branch is dead

CFAbstractStore.leastUpperBound runs at the join point during GenericAnnotatedTypeFactory .analyze(), a dataflow pass that is entirely separate from, and runs before, whatever the
type-checking visitor later chooses to check. Skipping visitation of the dead branch (what
#2128 does) has no effect on that pass, so x still gets widened to @Nullable at the
join and x.toString() is still flagged.

#101 ("Introduce "bottom store" to reduce false positive in dead branch") fixes exactly
this, by making the untaken branch of a literal-condition if/while/for/ternary produce a
lattice-bottom store instead of a normal one, so leastUpperBound with it is the identity and
the live branch's precision is preserved across the merge. That PR is not mergeable as-is
(unconditional — no option gate, changes default behavior for everyone; touches dataflow/'s
Store.FlowRule and CFGTranslationPhaseOne for ternary handling; requires new constructors on
every checker's Store/Analysis pair using names that no longer match current code, e.g.
InitializationStore no longer exists under that name), but the core technique — an isBottom
flag on CFAbstractStore, a bottom-store singleton on CFAbstractAnalysis, and a
CFAbstractTransfer.visitBooleanLiteral hook for if/while/for conditions only (dropping
#101's ternary/FlowRule changes as the riskiest, least-reviewed part) — is worth
reviving, gated by -AignoreDeadCode, with a safe createEmptyStore() fallback for any
checker's Store that doesn't override the new bottom-store method (so nothing else needs to
change to keep compiling).

Neither #101 nor #633's own tests actually exercised this join-point case (both only
tested errors inside a dead branch), so this was scoped out of #2128 rather than bundled
in.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions