Skip to content
Open
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
24 changes: 4 additions & 20 deletions src/debugger.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use dap::events::Event;
use dap::prelude::ResponseBody;
use anyhow::Result;

use crate::connection::Connection;

Expand All @@ -9,30 +8,15 @@ pub struct CairoDebugger {
connection: Connection,
}

enum ServerResponse {
Success(ResponseBody),
Error(String),
Event(Event),
SuccessThenEvent(ResponseBody, Event),
}

impl CairoDebugger {
pub fn connect() -> anyhow::Result<Self> {
pub fn connect() -> Result<Self> {
let connection = Connection::new()?;
Ok(Self { connection })
}

pub fn run(&mut self) -> anyhow::Result<()> {
pub fn run(&self) -> Result<()> {
while let Ok(req) = self.connection.next_request() {
match handler::handle_request(&req) {
ServerResponse::Success(body) => self.connection.send_success(req, body)?,
ServerResponse::Error(msg) => self.connection.send_error(req, &msg)?,
ServerResponse::Event(event) => self.connection.send_event(event)?,
ServerResponse::SuccessThenEvent(body, event) => {
self.connection.send_success(req, body)?;
self.connection.send_event(event)?;
}
}
self.handle_request(req)?;
}

Ok(())
Expand Down
330 changes: 191 additions & 139 deletions src/debugger/handler.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::{Result, bail};
use dap::events::{Event, StoppedEventBody};
use dap::prelude::{Command, Request, ResponseBody};
use dap::responses::{
Expand All @@ -7,154 +8,205 @@ use dap::responses::{
use dap::types::{Breakpoint, Capabilities, Source, StackFrame, StoppedEventReason, Thread};
use tracing::trace;

use crate::debugger::ServerResponse;
use crate::CairoDebugger;

pub fn handle_request(request: &Request) -> ServerResponse {
match &request.command {
// We have not yet decided if we want to support these.
Command::BreakpointLocations(_)
| Command::Cancel(_)
| Command::Completions(_)
| Command::DataBreakpointInfo(_)
| Command::Disassemble(_)
| Command::Disconnect(_)
| Command::Goto(_)
| Command::ExceptionInfo(_)
| Command::GotoTargets(_)
| Command::LoadedSources
| Command::Modules(_)
| Command::ReadMemory(_)
| Command::RestartFrame(_)
| Command::SetDataBreakpoints(_)
| Command::Restart(_)
| Command::SetExceptionBreakpoints(_)
| Command::TerminateThreads(_)
| Command::Terminate(_)
| Command::StepInTargets(_)
| Command::SetVariable(_)
| Command::SetInstructionBreakpoints(_)
| Command::SetExpression(_)
| Command::WriteMemory(_) => {
// If we receive these with current capabilities, it is the client's fault.
let msg = format!("Received an unsupported request: {request:?}");
ServerResponse::Error(msg)
}
pub enum HandleResult {
Handled,
}
Comment on lines +13 to +15
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we could somehow return sth from handle_request that is an info for the server about what it should do next (send request/event/halt and wait for continue etc.), like it was before. This way we give the control back to the server event loop, which results in clearer code. And we don't need server state in this function anymore


// These may be supported after the MVP.
// Nonetheless, if we receive these with current capabilities,
// it is the client's fault.
Command::ReverseContinue(_) => {
ServerResponse::Error("Step back is not yet supported".into())
}
Command::StepBack(_) => ServerResponse::Error("Step back is not yet supported".into()),
Command::SetFunctionBreakpoints(_) => {
ServerResponse::Error("Set function breakpoints is not yet supported".into())
}
impl CairoDebugger {
pub(crate) fn handle_request(&self, request: Request) -> Result<HandleResult> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub(crate) fn handle_request(&self, request: Request) -> Result<HandleResult> {
pub fn handle_request(&self, request: Request) -> Result<HandleResult> {

This module is not visible from lib.rs anyways

match &request.command {
// We have not yet decided if we want to support these.
Command::BreakpointLocations(_)
| Command::Cancel(_)
| Command::Completions(_)
| Command::DataBreakpointInfo(_)
| Command::Disassemble(_)
| Command::Disconnect(_)
| Command::Goto(_)
| Command::ExceptionInfo(_)
| Command::GotoTargets(_)
| Command::LoadedSources
| Command::Modules(_)
| Command::ReadMemory(_)
| Command::RestartFrame(_)
| Command::SetDataBreakpoints(_)
| Command::Restart(_)
| Command::SetExceptionBreakpoints(_)
| Command::TerminateThreads(_)
| Command::Terminate(_)
| Command::StepInTargets(_)
| Command::SetVariable(_)
| Command::SetInstructionBreakpoints(_)
| Command::SetExpression(_)
| Command::WriteMemory(_) => {
// If we receive these with current capabilities, it is the client's fault.
let msg = format!("Received an unsupported request: {request:?}");
self.connection.send_error(request, &msg)?;
bail!("Unsupported request");
}

// It makes no sense to send `attach` in the current architecture.
Command::Attach(_) => ServerResponse::Error("Attach is not supported".into()),
// These may be supported after the MVP.
// Nonetheless, if we receive these with current capabilities,
// it is the client's fault.
Command::ReverseContinue(_) => {
self.connection.send_error(request, "Reverse continue is not yet supported")?;
bail!("Reverse continue is not yet supported");
}
Command::StepBack(_) => {
self.connection.send_error(request, "Step back is not yet supported")?;
bail!("Step back is not yet supported");
}
Command::SetFunctionBreakpoints(_) => {
self.connection
.send_error(request, "Set function breakpoints is not yet supported")?;
bail!("Set function breakpoints is not yet supported");
}

// Supported requests.
Command::Initialize(args) => {
trace!("Initialized a client: {:?}", args.client_name);
// It makes no sense to send `attach` in the current architecture.
Command::Attach(_) => {
self.connection.send_error(request, "Attach is not supported")?;
bail!("Unsupported request");
}

ServerResponse::Success(ResponseBody::Initialize(Capabilities {
supports_configuration_done_request: Some(true),
..Default::default()
}))
}
Command::ConfigurationDone => {
trace!("Configuration done");
ServerResponse::Success(ResponseBody::ConfigurationDone)
}
Command::Continue(_) => {
todo!()
}
Command::Launch(_) => {
// Start running the Cairo program here.
ServerResponse::SuccessThenEvent(ResponseBody::Launch, Event::Initialized)
}
Command::Next(_) => {
todo!()
}
Command::Pause(_) => ServerResponse::Event(Event::Stopped(StoppedEventBody {
reason: StoppedEventReason::Pause,
thread_id: Some(0),
description: None,
preserve_focus_hint: None,
text: None,
all_threads_stopped: Some(true),
hit_breakpoint_ids: None,
})),
Command::SetBreakpoints(args) => {
let mut response_bps = Vec::new();
if let Some(requested_bps) = &args.breakpoints {
for bp in requested_bps {
// For now accept every breakpoint as valid
response_bps.push(Breakpoint {
verified: true,
source: Some(args.source.clone()),
line: Some(bp.line),
// Supported requests.
Command::Initialize(args) => {
trace!("Initialized a client: {:?}", args.client_name);
self.connection.send_success(
request,
ResponseBody::Initialize(Capabilities {
supports_configuration_done_request: Some(true),
..Default::default()
});
}),
)?;
self.connection.send_event(Event::Initialized)?;
Ok(HandleResult::Handled)
}
Command::ConfigurationDone => {
trace!("Configuration done");
self.connection.send_success(request, ResponseBody::ConfigurationDone)?;
Ok(HandleResult::Handled)
}
Command::Continue(_) => {
todo!()
}
Command::Launch(_) => {
// Start running the Cairo program here.
self.connection.send_success(request, ResponseBody::Launch)?;
Ok(HandleResult::Handled)
}
Command::Next(_) => {
todo!()
}
Command::Pause(_) => {
self.connection.send_event(Event::Stopped(StoppedEventBody {
reason: StoppedEventReason::Pause,
thread_id: Some(0),
description: None,
preserve_focus_hint: None,
text: None,
all_threads_stopped: Some(true),
hit_breakpoint_ids: None,
}))?;
self.connection.send_success(request, ResponseBody::Pause)?;
Ok(HandleResult::Handled)
}
Command::SetBreakpoints(args) => {
let mut response_bps = Vec::new();
if let Some(requested_bps) = &args.breakpoints {
for bp in requested_bps {
// For now accept every breakpoint as valid
response_bps.push(Breakpoint {
verified: true,
source: Some(args.source.clone()),
line: Some(bp.line),
..Default::default()
});
}
}
self.connection.send_success(
request,
ResponseBody::SetBreakpoints(SetBreakpointsResponse {
breakpoints: response_bps,
}),
)?;
Ok(HandleResult::Handled)
}
Command::Source(_) => {
todo!()
}
Command::StackTrace(_) => {
self.connection.send_success(
request,
ResponseBody::StackTrace(StackTraceResponse {
stack_frames: vec![StackFrame {
id: 1,
name: "test".to_string(),
// Replace it with the actual source path.
// Otherwise, the debugger will crush after returning this response.
source: Some(Source { name: None, path: None, ..Default::default() }),
line: 1,
column: 1,
..Default::default()
}],
total_frames: Some(1),
}),
)?;
Ok(HandleResult::Handled)
}
Command::StepIn(_) => {
todo!()
}
Command::StepOut(_) => {
todo!()
}
ServerResponse::Success(ResponseBody::SetBreakpoints(SetBreakpointsResponse {
breakpoints: response_bps,
}))
}
Command::Source(_) => {
todo!()
}
Command::StackTrace(_) => {
ServerResponse::Success(ResponseBody::StackTrace(StackTraceResponse {
stack_frames: vec![StackFrame {
id: 1,
name: "test".to_string(),
// Replace it with the actual source path.
// Otherwise, the debugger will crush after returning this response.
source: Some(Source { name: None, path: None, ..Default::default() }),
line: 1,
column: 1,
..Default::default()
}],
total_frames: Some(1),
}))
}
Command::StepIn(_) => {
todo!()
}
Command::StepOut(_) => {
todo!()
}

Command::Evaluate(_) => {
ServerResponse::Success(ResponseBody::Evaluate(EvaluateResponse {
// Return whatever since we cannot opt out of supporting this request.
result: "".to_string(),
type_field: None,
presentation_hint: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
}))
}
Command::Threads => {
ServerResponse::Success(ResponseBody::Threads(ThreadsResponse {
// Return a single thread.
threads: vec![Thread { id: 0, name: "".to_string() }],
}))
}
Command::Variables(_) => {
ServerResponse::Success(ResponseBody::Variables(VariablesResponse {
// Return no variables.
variables: vec![],
}))
}
Command::Scopes(_) => {
// Return no scopes.
ServerResponse::Success(ResponseBody::Scopes(ScopesResponse { scopes: vec![] }))
Command::Evaluate(_) => {
self.connection.send_success(
request,
ResponseBody::Evaluate(EvaluateResponse {
// Return whatever since we cannot opt out of supporting this request.
result: "".to_string(),
type_field: None,
presentation_hint: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
memory_reference: None,
}),
)?;
Ok(HandleResult::Handled)
}
Command::Threads => {
self.connection.send_success(
request,
ResponseBody::Threads(ThreadsResponse {
// Return a single thread.
threads: vec![Thread { id: 0, name: "".to_string() }],
}),
)?;
Ok(HandleResult::Handled)
}
Command::Variables(_) => {
self.connection.send_success(
request,
ResponseBody::Variables(VariablesResponse {
// Return no variables.
variables: vec![],
}),
)?;
Ok(HandleResult::Handled)
}
Command::Scopes(_) => {
// Return no scopes.
// Return no scopes.
self.connection.send_success(
request,
ResponseBody::Scopes(ScopesResponse { scopes: vec![] }),
)?;
Ok(HandleResult::Handled)
}
}
}
}
Loading