diff --git a/src/debugger.rs b/src/debugger.rs index 611ce77..e75d9d3 100644 --- a/src/debugger.rs +++ b/src/debugger.rs @@ -9,7 +9,8 @@ use dap::types::StoppedEventReason; use tracing::error; use crate::connection::Connection; -use crate::debugger::context::{CasmDebugInfo, Context}; +use crate::debugger::context::{CasmDebugInfo, Context, Line}; +use crate::debugger::handler::StepAction; use crate::debugger::state::State; mod call_stack; @@ -56,7 +57,9 @@ impl CairoDebugger { fn sync_with_vm(&mut self, vm: &VirtualMachine) -> Result<()> { self.state.update_state(vm, &self.ctx); + self.maybe_handle_breakpoint_hit()?; + self.maybe_handle_step_action()?; while let Some(request) = self.connection.try_next_request()? { self.process_request(request)?; @@ -95,25 +98,51 @@ impl CairoDebugger { Ok(()) } + fn maybe_handle_step_action(&mut self) -> Result<()> { + let current_line = + Line::create_from_statement_idx(self.state.current_statement_idx, &self.ctx); + + 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 => + { + true + } + _ => false, + }; + + if stop { + self.state.step_action = None; + self.pause_and_process_requests(StoppedEventReason::Step)?; + } + + Ok(()) + } + fn maybe_handle_breakpoint_hit(&mut self) -> Result<()> { if self.state.was_breakpoint_hit(&self.ctx) { - self.state.stop_execution(); - self.connection.send_event(Event::Stopped(StoppedEventBody { - reason: StoppedEventReason::Breakpoint, - thread_id: Some(MAX_OBJECT_REFERENCE), - all_threads_stopped: Some(true), - // Breakpoint IDs are not set in `SetBreakpointsResponse`, hence we set them to `None` also here. - // This would matter if we supported multiple breakpoints per line, but currently we don't. - hit_breakpoint_ids: None, - description: None, - preserve_focus_hint: None, - text: None, - }))?; - self.process_until_resume()?; + self.pause_and_process_requests(StoppedEventReason::Breakpoint)?; } Ok(()) } + + fn pause_and_process_requests(&mut self, reason: StoppedEventReason) -> Result<()> { + self.state.stop_execution(); + self.connection.send_event(Event::Stopped(StoppedEventBody { + reason, + thread_id: Some(MAX_OBJECT_REFERENCE), + all_threads_stopped: Some(true), + // Breakpoint IDs are not set in `SetBreakpointsResponse`, hence we set them to `None` also here. + // This would matter if we supported multiple breakpoints per line, but currently we don't. + hit_breakpoint_ids: None, + description: None, + preserve_focus_hint: None, + text: None, + }))?; + self.process_until_resume() + } } impl Drop for CairoDebugger { diff --git a/src/debugger/call_stack.rs b/src/debugger/call_stack.rs index 4e9d475..19664ab 100644 --- a/src/debugger/call_stack.rs +++ b/src/debugger/call_stack.rs @@ -34,6 +34,9 @@ enum Action { } impl CallStack { + pub fn depth(&self) -> usize { + self.call_ids.len() + } 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. diff --git a/src/debugger/context.rs b/src/debugger/context.rs index ba3d9bd..6ad1c66 100644 --- a/src/debugger/context.rs +++ b/src/debugger/context.rs @@ -46,6 +46,13 @@ impl Line { pub fn new(line: usize) -> Self { Self(line) } + + pub fn create_from_statement_idx(statement_idx: StatementIdx, ctx: &Context) -> Self { + let CodeLocation(_, code_span, _) = ctx + .code_location_for_statement_idx(statement_idx) + .expect("statement was expected to have corresponding code location"); + Self(code_span.start.line.0) + } } impl Context { diff --git a/src/debugger/handler.rs b/src/debugger/handler.rs index 7bf4cf2..510af0d 100644 --- a/src/debugger/handler.rs +++ b/src/debugger/handler.rs @@ -1,6 +1,7 @@ use anyhow::{Result, anyhow, bail}; use dap::events::{Event, StoppedEventBody}; use dap::prelude::{Command, Request, ResponseBody}; +use dap::requests::{NextArguments, StepInArguments}; use dap::requests::{ScopesArguments, VariablesArguments}; use dap::responses::{ ContinueResponse, EvaluateResponse, ScopesResponse, SetBreakpointsResponse, @@ -24,6 +25,11 @@ impl From for HandlerResponse { } } +pub enum StepAction { + StepIn { prev_line: Line }, + Next { depth: usize, prev_line: Line }, +} + impl HandlerResponse { #[must_use] pub fn with_event(mut self, event: Event) -> Self { @@ -169,11 +175,32 @@ pub fn handle_request( Ok(ResponseBody::Variables(VariablesResponse { variables }).into()) } - Command::Next(_) => { - todo!() - } - Command::StepIn(_) => { - todo!() + // `vs-code` currently doesn't support choosing granularity, see: https://github.com/microsoft/vscode/issues/102236. + // We assume granularity of line. + Command::Next(NextArguments { .. }) => { + // To handle a "step over" action, we set the step action to `Next`. + // We record the current call stack depth. The debugger will resume execution + // and only stop when it reaches a new line at the same or a shallower call stack depth. + // 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.resume_execution(); + Ok(ResponseBody::Next.into()) + } + // `vs-code` currently doesn't support choosing granularity, see: https://github.com/microsoft/vscode/issues/102236. + // We assume granularity of line. + Command::StepIn(StepInArguments { .. }) => { + // To handle a "step in" action, we set the step action to `StepIn`. + // The debugger will resume execution and stop at the very next executable line, + // which might be inside a function call. + let line = Line::create_from_statement_idx(state.current_statement_idx, ctx); + + state.step_action = Some(StepAction::StepIn { prev_line: line }); + state.resume_execution(); + Ok(ResponseBody::StepIn.into()) } Command::StepOut(_) => { todo!() diff --git a/src/debugger/state.rs b/src/debugger/state.rs index 4550b5c..c72346a 100644 --- a/src/debugger/state.rs +++ b/src/debugger/state.rs @@ -10,6 +10,7 @@ use tracing::{debug, trace}; use crate::debugger::call_stack::CallStack; use crate::debugger::context::{Context, Line}; +use crate::debugger::handler::StepAction; type SourcePath = String; @@ -20,6 +21,7 @@ pub struct State { pub current_statement_idx: StatementIdx, pub call_stack: CallStack, last_breakpoint_hit: Option, + pub step_action: Option, } impl State { @@ -31,6 +33,7 @@ impl State { current_statement_idx: StatementIdx(0), call_stack: CallStack::default(), last_breakpoint_hit: None, + step_action: None, } }