From ae943e2442d37541978a0377527365a04b7d302a Mon Sep 17 00:00:00 2001 From: piotmag769 Date: Mon, 16 Feb 2026 19:15:35 +0100 Subject: [PATCH] Fix depth length and object references --- src/debugger.rs | 11 +++- src/debugger/call_stack.rs | 127 ++++++++++++++++++++++++------------- src/debugger/handler.rs | 8 ++- 3 files changed, 96 insertions(+), 50 deletions(-) diff --git a/src/debugger.rs b/src/debugger.rs index 3021b32..6a4172e 100644 --- a/src/debugger.rs +++ b/src/debugger.rs @@ -105,11 +105,18 @@ impl CairoDebugger { let stop = match &self.state.step_action { Some(StepAction::StepIn { prev_line }) if *prev_line != current_line => true, Some(StepAction::Next { prev_line, depth }) - if *depth >= self.state.call_stack.depth() && *prev_line != current_line => + if *depth + >= self.state.call_stack.depth(self.state.current_statement_idx, &self.ctx) + && *prev_line != current_line => + { + true + } + Some(StepAction::StepOut { depth }) + if *depth + > self.state.call_stack.depth(self.state.current_statement_idx, &self.ctx) => { true } - Some(StepAction::StepOut { depth }) if *depth > self.state.call_stack.depth() => true, _ => false, }; diff --git a/src/debugger/call_stack.rs b/src/debugger/call_stack.rs index 19664ab..e5930ec 100644 --- a/src/debugger/call_stack.rs +++ b/src/debugger/call_stack.rs @@ -1,4 +1,4 @@ -use std::iter::once; +use std::iter; use std::path::Path; use cairo_annotations::annotations::coverage::{CodeLocation, SourceFileFullPath}; @@ -12,13 +12,27 @@ use crate::debugger::context::Context; #[derive(Default)] pub struct CallStack { - /// Stack of indexes of sierra statements that are function calls and values of variables in frames corresponding to these functions. - /// Does ***not*** contain a current function frame. + /// Stack of Cairo function frames and values of variables in frames corresponding + /// to these functions. + /// + /// 1. Each function frame corresponds to a Cairo function that is currently on the Cairo call + /// stack. + /// This includes inlined functions, even though they are not present on the call stack + /// if one looks from Sierra/CASM POV. + /// + /// 2. The stack is divided into substacks. + /// Each substack corresponds to a Sierra function call statement that is currently + /// on the Sierra call stack. + /// The substack contains zero to many frames of inlined Cairo functions together + /// with exactly one non-inlined Cairo function frame at the end of the substack. + /// + /// 3. The stack does ***not*** contain a substack corresponding to the current statement. /// /// [Object references](https://microsoft.github.io/debug-adapter-protocol/overview#lifetime-of-objects-references): - /// object reference for each stack frame is equal to its `1 + 2 * index` where `index` is its - /// position in this vector. For variables, it is `2 + 2 * index`. - call_ids: Vec<(StatementIdx, FunctionVariables)>, + /// object reference for each stack frame is equal to its `1 + 2 * flat_index` + /// where `flat_index` is its position in the flattened vector (vector of tuples). + /// For the variables' scope, the object reference is equal to `2 + 2 * flat_index`. + call_frames_and_vars: Vec, /// Modification that should be applied to the stack when a new sierra statement is reached. /// @@ -28,15 +42,18 @@ pub struct CallStack { action_on_new_statement: Option, } +type SubStack = Vec<(StackFrame, FunctionVariables)>; + enum Action { - Push(StatementIdx), + Push(SubStack), Pop, } impl CallStack { - pub fn depth(&self) -> usize { - self.call_ids.len() + pub fn depth(&self, statement_idx: StatementIdx, ctx: &Context) -> usize { + self.flat_length() + self.build_stack_frames(ctx, statement_idx).count() } + pub fn update(&mut self, statement_idx: StatementIdx, ctx: &Context) { // We can be sure that the `statement_idx` is different from the one which was the arg when // `action_on_new_statement` was set. @@ -44,32 +61,37 @@ impl CallStack { // https://github.com/starkware-libs/cairo/blob/20eca60c88a35f7da13f573b2fc68818506703a9/crates/cairo-lang-sierra-to-casm/src/invocations/function_call.rs#L46 // https://github.com/starkware-libs/cairo/blob/d52acf845fc234f1746f814de7c64b535563d479/crates/cairo-lang-sierra-to-casm/src/compiler.rs#L533 match self.action_on_new_statement.take() { - Some(Action::Push(statement)) => { + Some(Action::Push(frames_and_variables)) => { // TODO(#16) - self.call_ids.push((statement, FunctionVariables {})); + self.call_frames_and_vars.push(frames_and_variables); } Some(Action::Pop) => { - self.call_ids.pop(); + self.call_frames_and_vars.pop(); } None => {} } if ctx.is_function_call_statement(statement_idx) { - self.action_on_new_statement = Some(Action::Push(statement_idx)); + self.action_on_new_statement = Some(Action::Push( + self.build_stack_frames(ctx, statement_idx) + // TODO(#16) + .zip(iter::repeat_with(|| FunctionVariables {})) + .collect(), + )); } else if ctx.is_return_statement(statement_idx) { self.action_on_new_statement = Some(Action::Pop); } } pub fn get_frames(&self, statement_idx: StatementIdx, ctx: &Context) -> Vec { - // DAP expects frames to start from the most nested element. - self.call_ids + self.call_frames_and_vars .iter() - .map(|(call_statement_idx, _)| call_statement_idx) + .flatten() + .map(|(frame, _)| frame) .cloned() - .chain(once(statement_idx)) + .chain(self.build_stack_frames(ctx, statement_idx)) + // DAP expects frames to start from the most nested element. .rev() - .flat_map(|statement_idx| self.build_stack_frames(ctx, statement_idx)) .collect() } @@ -84,35 +106,43 @@ impl CallStack { } pub fn get_variables(&self, variables_reference: i64) -> Vec { - let index = variables_reference / 2 - 1; - let &FunctionVariables {} = if index == self.call_ids.len() as i64 { + let flat_index = (variables_reference / 2 - 1) as usize; + let &FunctionVariables {} = if flat_index >= self.flat_length() { // TODO(#16) // Build them on demand. &FunctionVariables {} } else { - &self.call_ids[index as usize].1 + self.call_frames_and_vars + .iter() + .flatten() + .map(|(_, vars)| vars) + .nth(flat_index) + .unwrap() }; vec![] } - /// Builds a vector of stack frames, ordered from the most nested (innermost) to the least nested (outermost) element. - fn build_stack_frames(&self, ctx: &Context, statement_idx: StatementIdx) -> Vec { + /// Builds a vector of stack frames, ordered from the least nested to the most nested element. + fn build_stack_frames<'a>( + &'a self, + ctx: &'a Context, + statement_idx: StatementIdx, + ) -> Box + 'a> { let Some(code_locations) = ctx.code_locations_for_statement_idx(statement_idx) else { - return vec![unknown_frame()]; + return Box::new(vec![self.unknown_frame()].into_iter()); }; - let default_function_names = vec![FunctionName("test".to_string())]; - let function_names = - ctx.function_names_for_statement_idx(statement_idx).unwrap_or(&default_function_names); + let function_names = ctx + .function_names_for_statement_idx(statement_idx) + .cloned() + .unwrap_or_else(|| vec![FunctionName("test".to_string())]); - code_locations - .iter() - .zip(function_names) - .map(|(code_location, function_name)| { - self.build_stack_frame(code_location, function_name, ctx) - }) - .collect() + Box::new(code_locations.clone().into_iter().rev().zip(function_names).map( + |(code_location, function_name)| { + self.build_stack_frame(&code_location, &function_name, ctx) + }, + )) } fn build_stack_frame( @@ -121,7 +151,6 @@ impl CallStack { FunctionName(function_name): &FunctionName, ctx: &Context, ) -> StackFrame { - let id = MIN_OBJECT_REFERENCE + 2 * self.call_ids.len() as i64; let file_path = Path::new(&source_file); let name = function_name.clone(); @@ -138,7 +167,7 @@ impl CallStack { let column = (code_span.start.col.0 + 1) as i64; StackFrame { - id, + id: self.next_frame_id(), name, source: Some(Source { name: None, @@ -151,16 +180,24 @@ impl CallStack { ..Default::default() } } -} -fn unknown_frame() -> StackFrame { - StackFrame { - id: 1, - name: "Unknown".to_string(), - line: 1, - column: 1, - presentation_hint: Some(StackFramePresentationhint::Subtle), - ..Default::default() + fn unknown_frame(&self) -> StackFrame { + StackFrame { + id: self.next_frame_id(), + name: "Unknown".to_string(), + line: 1, + column: 1, + presentation_hint: Some(StackFramePresentationhint::Subtle), + ..Default::default() + } + } + + fn next_frame_id(&self) -> i64 { + MIN_OBJECT_REFERENCE + 2 * self.flat_length() as i64 + } + + fn flat_length(&self) -> usize { + self.call_frames_and_vars.iter().map(|frames| frames.len()).sum() } } diff --git a/src/debugger/handler.rs b/src/debugger/handler.rs index 70cc77b..3feb789 100644 --- a/src/debugger/handler.rs +++ b/src/debugger/handler.rs @@ -185,8 +185,10 @@ pub fn handle_request( // This effectively "steps over" any function calls. let line = Line::create_from_statement_idx(state.current_statement_idx, ctx); - state.step_action = - Some(StepAction::Next { depth: state.call_stack.depth(), prev_line: line }); + state.step_action = Some(StepAction::Next { + depth: state.call_stack.depth(state.current_statement_idx, ctx), + prev_line: line, + }); state.resume_execution(); Ok(ResponseBody::Next.into()) @@ -208,7 +210,7 @@ pub fn handle_request( // We record the current call stack depth. The debugger will resume execution // and only stop when it reaches a line in a shallower call stack depth, which // happens when the current function returns. - let depth = state.call_stack.depth(); + let depth = state.call_stack.depth(state.current_statement_idx, ctx); if depth > 0 { state.step_action = Some(StepAction::StepOut { depth }); }