Skip to content

Commit d4a067f

Browse files
committed
fix(bit_machine): use cursor position instead of frame start for tracker read iterator
The tracker was receiving a bit iterator anchored at the frame's start rather than the current cursor position. When a `case` node executes inside another `case` or `drop`, the read frame cursor has already advanced past the start, so the tracker read stale bits and recorded the wrong branch. This caused `assertl`/`assertr` pruning to remove the wrong branch after execution. Adds a regression test covering the specific `comp (pair (injl (injr unit)) unit) (case (case unit unit) unit)` pattern that triggered the bug.
1 parent fe1f888 commit d4a067f

2 files changed

Lines changed: 42 additions & 10 deletions

File tree

src/bit_machine/frame.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,12 @@ impl Frame {
115115
}
116116
}
117117

118-
/// Extend the present frame with a read-only reference the the data
119-
/// and return the resulting struct.
120-
pub(super) fn as_bit_iter<'a>(
118+
/// Return an iterator over the frame's remaining bits, starting at the current cursor position.
119+
pub(super) fn as_bit_iter_from_cursor<'a>(
121120
&self,
122121
data: &'a [u8],
123122
) -> BitIter<core::iter::Copied<core::slice::Iter<'a, u8>>> {
124-
BitIter::byte_slice_window(data, self.start, self.start + self.len)
123+
BitIter::byte_slice_window(data, self.cursor, self.start + self.len)
125124
}
126125
}
127126

src/bit_machine/mod.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,11 @@ impl BitMachine {
277277
}
278278

279279
'main_loop: loop {
280-
// Make a copy of the input frame to give to the tracker.
280+
// Capture read and write frames before the node action, to give to the tracker.
281+
// The read frame cursor is where the node's input begins.
282+
// The write frame cursor is where the node's output begins.
281283
let input_frame = self.read.last().map(Frame::shallow_copy);
284+
let output_frame = self.write.last().map(Frame::shallow_copy);
282285
let mut jet_result = Ok(());
283286

284287
match ip.inner() {
@@ -391,16 +394,15 @@ impl BitMachine {
391394
// describes the Bit Machine "input" to the current node,
392395
// no matter the node.
393396
let read_iter = input_frame
394-
.map(|frame| frame.as_bit_iter(&self.data))
397+
.map(|frame| frame.as_bit_iter_from_cursor(&self.data))
395398
.unwrap_or(crate::BitIter::from([].iter().copied()));
396399
// See the docs on `tracker::NodeOutput` for more information about
397400
// this match.
398401
let output = match (ip.inner(), &jet_result) {
399402
(node::Inner::Unit | node::Inner::Iden | node::Inner::Witness(_), _)
400403
| (node::Inner::Jet(_), Ok(_)) => NodeOutput::Success(
401-
self.write
402-
.last()
403-
.map(|r| r.as_bit_iter(&self.data))
404+
output_frame
405+
.map(|frame| frame.as_bit_iter_from_cursor(&self.data))
404406
.unwrap_or(crate::BitIter::from([].iter().copied())),
405407
),
406408
(node::Inner::Jet(_), Err(_)) => NodeOutput::JetFailed,
@@ -430,7 +432,7 @@ impl BitMachine {
430432
let out_frame = self.write.last_mut().unwrap();
431433
out_frame.reset_cursor();
432434
let value = Value::from_padded_bits(
433-
&mut out_frame.as_bit_iter(&self.data),
435+
&mut out_frame.as_bit_iter_from_cursor(&self.data),
434436
&program.arrow().target,
435437
)
436438
.expect("Decode value of output frame");
@@ -669,6 +671,37 @@ mod tests {
669671
.exec(&prog, &env)
670672
}
671673

674+
#[test]
675+
#[cfg(feature = "human_encoding")]
676+
fn set_tracker_cursor_regression() {
677+
// When a `case` node executes inside another `case` or `drop`, the read frame cursor
678+
// is no longer at the frame's start. Before this fix, `exec_with_tracker` passed an
679+
// iterator starting at frame.start to the tracker, so `SetTracker` read the wrong bit
680+
// and recorded the wrong branch, leading to an assertl/assertr mismatch after pruning.
681+
//
682+
// Program: comp (pair (injl (injr unit)) unit) (case (case unit unit) unit)
683+
// Intermediate frame bits: [0=L-tag of outer, 1=R-tag of inner]
684+
// Outer case: peek bit 0 = 0 (LEFT), fwd(1), cursor moves to bit 1.
685+
// Inner case: should read bit 1 = 1 (RIGHT).
686+
// Bug: tracker received iterator from bit 0, read 0 (LEFT) → pruned wrong branch.
687+
use crate::human_encoding::Forest;
688+
use crate::types;
689+
use std::collections::HashMap;
690+
691+
types::Context::with_context(|ctx| {
692+
let s = "main := comp (pair (injl (injr unit)) unit) (case (case unit unit) unit)";
693+
let program = Forest::parse::<crate::jet::Core>(s)
694+
.expect("parse")
695+
.to_witness_node(&ctx, &HashMap::new())
696+
.expect("main root")
697+
.finalize_pruned(&CoreEnv::new())
698+
.expect("finalize and prune");
699+
let mut mac = BitMachine::for_program(&program).expect("for_program");
700+
mac.exec(&program, &CoreEnv::new())
701+
.expect("pruned execution should succeed");
702+
});
703+
}
704+
672705
#[test]
673706
#[cfg(feature = "elements")]
674707
fn crash_regression1() {

0 commit comments

Comments
 (0)