Skip to content

Normalise before returning at the root in ascend_until and ascend_until_branch - #73

Open
imlvts wants to merge 1 commit into
Adam-Vandervorst:masterfrom
imlvts:bugfix/ascend-until-root-normalise
Open

Normalise before returning at the root in ascend_until and ascend_until_branch#73
imlvts wants to merge 1 commit into
Adam-Vandervorst:masterfrom
imlvts:bugfix/ascend-until-root-normalise

Conversation

@imlvts

@imlvts imlvts commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Both methods checked at_root() and returned before the step that closes out a spent node key:

ascended += self.ascend_within_node();
if self.at_root() { return ascended; }        // <- returned here
if self.key.node_key().len() == 0 { self.ascend_across_nodes(); }

ascend right above them does it the other way round, normalise first and then decide whether it is done, and does not have the bug. So the zipper could come back at its root still holding a node key it had already ascended past. At the root val() and set_val read root_val rather than the focus node, so the zipper denied its own root value and set_val unwrapped a None:

map.insert(&[2, 2, 0, 2], 52);
let mut wz = map.write_zipper_at_path(&[2, 1]);
wz.get_val_or_set_mut_with(|| 198);
wz.move_to_path(&[2, 2, 1, 1]);
wz.create_path();
wz.ascend_until();
// wz.val() was None; map.get_val_at(&[2,1]) is Some(198)

Swapping the two, so both match ascend, fixes it. On the bugfixes branch this also removed the root_val unwrap panics at the two set_val/remove_val sites entirely, which were downstream of it: the same field, unusable for the same reason.

Regression test: write_zipper_ascend_until_normalises_at_the_root, for both methods, reading and then writing at the root afterwards. Fails before this change.

lean/FINDINGS.md finding 9 on the lean-fuzzer-restage branch.

…il_branch

Both methods checked `at_root()` and returned *before* the step that
closes out a spent node key:

    ascended += self.ascend_within_node();
    if self.at_root() { return ascended; }        // <- returned here
    if self.key.node_key().len() == 0 { self.ascend_across_nodes(); }

`ascend` right above them does it the other way round, normalise first
and then decide whether it is done, and does not have the bug.  So the
zipper could come back at its root still holding a node key it had
already ascended past.  At the root `val()` and `set_val` read
`root_val` rather than the focus node, so the zipper denied its own root
value and `set_val` unwrapped a `None`:

    map.insert(&[2, 2, 0, 2], 52);
    let mut wz = map.write_zipper_at_path(&[2, 1]);
    wz.get_val_or_set_mut_with(|| 198);
    wz.move_to_path(&[2, 2, 1, 1]);
    wz.create_path();
    wz.ascend_until();
    // wz.val() was None; map.get_val_at(&[2,1]) is Some(198)

Swapping the two, so both match `ascend`, fixes it.  On the bugfixes
branch this also removed the `root_val` unwrap panics at the two
`set_val`/`remove_val` sites entirely, which were downstream of it: the
same field, unusable for the same reason.

Regression test: `write_zipper_ascend_until_normalises_at_the_root`, for
both methods, reading and then writing at the root afterwards.  Fails
before this change.

lean/FINDINGS.md finding 9 on the lean-fuzzer-restage branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZmoASqM5FUuzvJeJaYQjR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant