Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 43 additions & 14 deletions src/debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions src/debugger/call_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions src/debugger/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 32 additions & 5 deletions src/debugger/handler.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -24,6 +25,11 @@ impl From<ResponseBody> 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 {
Expand Down Expand Up @@ -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!()
Expand Down
3 changes: 3 additions & 0 deletions src/debugger/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,6 +21,7 @@ pub struct State {
pub current_statement_idx: StatementIdx,
pub call_stack: CallStack,
last_breakpoint_hit: Option<BreakpointHit>,
pub step_action: Option<StepAction>,
}

impl State {
Expand All @@ -31,6 +33,7 @@ impl State {
current_statement_idx: StatementIdx(0),
call_stack: CallStack::default(),
last_breakpoint_hit: None,
step_action: None,
}
}

Expand Down