diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f4dccda3a..2f09d9ff5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ #### Bug Fixes - Fixed debug-only underflow in memory range-check trace generation when the first memory access is at `clk = 0` ([#2976](https://github.com/0xMiden/miden-vm/pull/2976)). +- Aligned replay stack word access bounds with `StackInterface`, allowing the maximum valid start index for word reads and writes ([#3014](https://github.com/0xMiden/miden-vm/pull/3014)). - Replaced unsound `ptr::read` with safe unbox in panic recovery, removing UB from potential double-drop ([#2934](https://github.com/0xMiden/miden-vm/pull/2934)). - Reverted `InvokeKind::ProcRef` back to `InvokeKind::Exec` in `visit_mut_procref` and added an explanatory comment (#2893). - Fixed the release dry-run publish cycle between `miden-air` and `miden-ace-codegen`, and preserved leaf-only DAG imports with explicit snapshots ([#2931](https://github.com/0xMiden/miden-vm/pull/2931)). diff --git a/processor/src/trace/parallel/processor.rs b/processor/src/trace/parallel/processor.rs index 4007e7b88c..89c92291af 100644 --- a/processor/src/trace/parallel/processor.rs +++ b/processor/src/trace/parallel/processor.rs @@ -255,7 +255,7 @@ impl StackInterface for ReplayProcessor { } fn get_word(&self, start_idx: usize) -> Word { - debug_assert!(start_idx < MIN_STACK_DEPTH - 4); + debug_assert!(start_idx <= MIN_STACK_DEPTH - WORD_SIZE); let word_start_idx = MIN_STACK_DEPTH - start_idx - 4; let mut result: [Felt; WORD_SIZE] = @@ -274,7 +274,7 @@ impl StackInterface for ReplayProcessor { } fn set_word(&mut self, start_idx: usize, word: &Word) { - debug_assert!(start_idx < MIN_STACK_DEPTH - 4); + debug_assert!(start_idx <= MIN_STACK_DEPTH - WORD_SIZE); let word_start_idx = MIN_STACK_DEPTH - start_idx - 4; // Reverse so word[0] ends up at the top of stack (highest internal index) @@ -506,3 +506,45 @@ impl Stopper for ReplayStopper { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::trace::trace_state::{ + AdviceReplay, ExecutionContextReplay, HasherResponseReplay, MastForestResolutionReplay, + MemoryReadsReplay, StackOverflowReplay, StackState, SystemState, + }; + + fn build_replay_processor() -> ReplayProcessor { + let system = SystemState { + clk: 0_u32.into(), + ctx: ContextId::root(), + fn_hash: Word::default(), + pc_transcript_state: Word::default(), + }; + let stack = StackState::new([ZERO; MIN_STACK_DEPTH], MIN_STACK_DEPTH, ZERO); + + ReplayProcessor::new( + system, + stack, + StackOverflowReplay::default(), + ExecutionContextReplay::default(), + AdviceReplay::default(), + MemoryReadsReplay::default(), + HasherResponseReplay::default(), + MastForestResolutionReplay::default(), + 1_u32.into(), + ) + } + + #[test] + fn stack_set_word_allows_max_start_idx() { + let mut processor = build_replay_processor(); + let start_idx = MIN_STACK_DEPTH - WORD_SIZE; + let word = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)].into(); + + processor.set_word(start_idx, &word); + + assert_eq!(processor.get_word(start_idx), word); + } +}