-
Notifications
You must be signed in to change notification settings - Fork 23
Description
With a help of Claude and insight from @topologoanatom and @gerau, here what I have found and tested:
When Value::prune creates a Left value by calling Value::left(), the resulting value can have corrupted tag bits. This causes ReachedPrunedBranch errors during execution when the BitMachine reads the wrong branch selector.
In src/value.rs, the right_shift_1 function has an optimization bug. When prepending a 0 bit (for Left values) and bit_offset > 0, it simply decrements the bit_offset without clearing the existing bit:
} else {
// ...and if we are inserting a 0 we don't even need to allocate a new [u8]
(Arc::clone(inner), bit_offset - 1)
}The bug is triggered when:
- A witness value like Left(Right(data)) is pruned to a smaller type
- The pruning creates nested Left/Right wrappers via Value::left() and Value::right()
- The inner value's bit representation has a 1 at the position where the outer 0 tag should be inserted
This happened in the #333, where the program was with complex sum types that have multiple execution paths, where pruning converts Case nodes to AssertL/AssertR.
Possible fix
Check if the bit at new_bit_offset is already 0 before skipping allocation. If it's 1, allocate and clear it:
} else {
let new_bit_offset = bit_offset - 1;
let byte_idx = new_bit_offset / 8;
let bit_mask = 1 << (7 - new_bit_offset % 8);
if (inner[byte_idx] & bit_mask) == 0 {
// The bit is already 0, no need to allocate
(Arc::clone(inner), new_bit_offset)
} else {
// The bit is 1, we need to clear it to insert a 0
let mut bx: Box<[u8]> = inner.as_ref().into();
bx[byte_idx] &= !bit_mask;
(bx.into(), new_bit_offset)
}
}With the fix above the bug described in the #333 does not happen