GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — See state changes through aliases and forget mutable field initializers across methods by CatarinaGamboa · Pull Request #393 · liquid-java/liquidjava

Conversation

@CatarinaGamboa

@CatarinaGamboa CatarinaGamboa commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator

Fixes #337, fixes #340.

Two false negatives found in the real-code examples, one commit each.

#337: state changes through an alias. Res b = a; records b == a, but b.close() gave b a new closed version that was no longer equal to a, so a.read() passed. After a state change, every other variable of a related type that the solver proves refers to the same object now points to the new version (AuxStateHandler.updateAliases, relatedTypes). Reassigning b = new Res() breaks the alias, so closing b afterwards leaves a alone.

Fixtures: ErrorStateChangeThroughAlias (plain alias, alias of an alias, alias created by a later assignment, close inside an if) and CorrectStateChangeThroughAlias (separate objects, reassignment, state-preserving calls, closing through either name in different branches).

#340: a mutable field read as its initializer. A field initializer's value stayed visible in every method. When a method (not a constructor) starts, each non-final field now keeps only its declared refinement (RefinementTypeChecker.forgetMutableFieldValues). Final fields keep their initializer, and assignments within the method are still tracked.

Fixtures: ErrorMutableFieldInitializer (the reproducer via q and this.q, a static non-final field, a non-final field holding an object with a state) and CorrectMutableFieldInitializer (final field, field refinement that guarantees the bound, assign-then-read, read in the constructor).

Existing fixtures changed: three field cases in CorrectDeferredCallbackState / ErrorDeferredCallbackState relied on a non-final field's initializer state inside a method. Their calls moved into the constructors, where that state really holds, so they still test that creating a callback does not run it.

Not covered:

  • Aliasing through method arguments: parameter state is unknown and callee state changes are not propagated to the caller.
  • Variables that may or may not alias (e.g. two parameters) still keep separate states.
  • updateGhostField (state-tracked int field writes) does not update aliases.
  • A final field holding an object with a state still assumes its initializer state, although another method can change it. Treating it like a non-final field breaks classes/state_field_initializer_correct (Fix Field Initialization #281), so it needs a design decision.
  • Within one method, a call that may write a field (this.setQ(x)) does not invalidate the value assigned earlier in the method (pre-existing).

Validation: mvn test passes all 414 tests (253 TestExamples cases).

🤖 Generated with Claude Code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
CatarinaGamboa and others added 2 commits October 13, 2026 09:19
@CatarinaGamboa @claude
Propagate typestate changes to must-aliases (#337)
a415821
A typestate transition creates a new instance of the variable it was made
through (`b#2` with `closed(b#2)`), but other variables bound to the same
object (`Res b = a;` gives `b#1 == a#1`) kept their old instance, so `a`
was still considered open after `b.close()`.

After a transition, every other reference variable of a related type whose
current instance is provably equal to the target's previous instance gets a
new instance equal to the transitioned one, so the new state is seen through
all must-aliases (including aliases of aliases and aliases created by a later
assignment). Reassigning the alias (`b = new Res()`) breaks the proved
equality, so later changes through it no longer affect the original.
Variables that may, but need not, alias the target keep their state.

Fixes #337

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa @claude
Forget non-final field initializers inside methods (#340)
456ae10
A field initializer's instance stayed the field's last instance for the
whole class, so every method read a non-final field as if it still held
its initializer, although any other method (e.g. a setter) may have
reassigned it. Passing such a field to a refined parameter was accepted.

On entry to a method (constructors are unchanged), each non-final field
with a recorded value gets a fresh instance carrying only its declared
refinement. Final fields keep their initializer. Assignments made inside
the method are still tracked as before.

The field cases of the deferred-callback fixtures relied on the
initializer state of a non-final field in a method; they now check that
state in the constructor, where it does hold, so they keep testing that
creating a callback does not run it.

Fixes #340

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@CatarinaGamboa
CatarinaGamboa marked this pull request as draft October 20, 2026 17:36
@CatarinaGamboa CatarinaGamboa mentioned this pull request Nov 10, 2026
Origin note: say where the wrong state of a receiver comes from #528
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Soundness: a field read in another method is assumed to still hold its initializer Soundness: a state change made through an alias is not seen through the original variable

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@CatarinaGamboa
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.