From e49a563f2243fb1f2e8ed4b9e4b8b76fd7482146 Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Tue, 5 Mar 2024 18:30:47 -0500 Subject: [PATCH] feat: dap wrapper --- toolchains/solidity/core/Cargo.lock | 1 + .../core/crates/foundry-debugger/Cargo.toml | 3 +- .../core/crates/foundry-debugger/src/dap.rs | 210 ++++++++++++++++++ .../core/crates/foundry-debugger/src/main.rs | 43 +++- 4 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 toolchains/solidity/core/crates/foundry-debugger/src/dap.rs diff --git a/toolchains/solidity/core/Cargo.lock b/toolchains/solidity/core/Cargo.lock index 3825e787..a9364d48 100644 --- a/toolchains/solidity/core/Cargo.lock +++ b/toolchains/solidity/core/Cargo.lock @@ -2078,6 +2078,7 @@ dependencies = [ "foundry-evm-core", "serde", "serde_json", + "thiserror", ] [[package]] diff --git a/toolchains/solidity/core/crates/foundry-debugger/Cargo.toml b/toolchains/solidity/core/crates/foundry-debugger/Cargo.toml index 165080a2..70403d47 100644 --- a/toolchains/solidity/core/crates/foundry-debugger/Cargo.toml +++ b/toolchains/solidity/core/crates/foundry-debugger/Cargo.toml @@ -12,4 +12,5 @@ exclude.workspace = true serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" dap = "0.4.1-alpha1" -foundry-evm-core = { git = "https://github.com/foundry-rs/foundry.git"} \ No newline at end of file +foundry-evm-core = { git = "https://github.com/foundry-rs/foundry.git"} +thiserror = "1.0.57" diff --git a/toolchains/solidity/core/crates/foundry-debugger/src/dap.rs b/toolchains/solidity/core/crates/foundry-debugger/src/dap.rs new file mode 100644 index 00000000..8418300d --- /dev/null +++ b/toolchains/solidity/core/crates/foundry-debugger/src/dap.rs @@ -0,0 +1,210 @@ +use std::io::{Read, Write}; + +use dap::errors::ServerError; +use dap::prelude::Event; +use dap::requests::{Command, Request}; +use dap::responses::{ + ContinueResponse, DisassembleResponse, ResponseBody, ScopesResponse, SetBreakpointsResponse, + SetExceptionBreakpointsResponse, SetInstructionBreakpointsResponse, StackTraceResponse, + ThreadsResponse, +}; +use dap::server::Server; +use dap::types::{ + SteppingGranularity, Thread, +}; + +pub struct DapSession { + server: Server, + running: bool, + next_breakpoint_id: i64, +} + +impl<'a, R: Read, W: Write> DapSession { + pub fn new( + server: Server, + ) -> Self { + Self { + server, + running: false, + next_breakpoint_id: 1, + } + } + + pub fn run_loop(&mut self) -> Result<(), ServerError> { + self.running = true; + + self.server.send_event(Event::Initialized)?; + + while self.running { + let req = match self.server.poll_request()? { + Some(req) => req, + None => break, + }; + match req.command { + Command::Disconnect(_) => { + eprintln!("INFO: ending debugging session"); + self.server.respond(req.ack()?)?; + break; + } + Command::SetBreakpoints(_) => { + self.handle_set_source_breakpoints(req)?; + } + Command::SetExceptionBreakpoints(_) => { + self.handle_set_exceptions_breakpoints(req)?; + } + Command::SetInstructionBreakpoints(_) => { + self.handle_set_instruction_breakpoints(req)?; + } + Command::Threads => { + self.server.respond(req.success(ResponseBody::Threads(ThreadsResponse { + threads: vec![Thread { id: 0, name: "main".to_string() }], + })))?; + } + Command::StackTrace(_) => { + self.handle_stack_trace(req)?; + } + Command::Disassemble(_) => { + self.handle_disassemble(req)?; + } + Command::StepIn(ref args) => { + let granularity = + args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + match granularity { + SteppingGranularity::Instruction => self.handle_step(req)?, + _ => self.handle_next(req)?, + } + } + Command::StepOut(ref args) => { + let granularity = + args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + match granularity { + SteppingGranularity::Instruction => self.handle_step(req)?, + _ => self.handle_next(req)?, + } + } + Command::Next(ref args) => { + let granularity = + args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + match granularity { + SteppingGranularity::Instruction => self.handle_step(req)?, + _ => self.handle_next(req)?, + } + } + Command::Continue(_) => { + self.handle_continue(req)?; + } + Command::Scopes(_) => { + let scopes = vec![]; + + self.server.respond( + req.success(ResponseBody::Scopes(ScopesResponse { scopes })), + )?; + } + _ => { + eprintln!("ERROR: unhandled command: {:?}", req.command); + } + } + } + Ok(()) + } + + fn handle_stack_trace(&mut self, req: Request) -> Result<(), ServerError> { + let stack_frames = vec![]; + let total_frames = Some(stack_frames.len() as i64); + self.server.respond(req.success(ResponseBody::StackTrace(StackTraceResponse { + stack_frames, + total_frames, + })))?; + Ok(()) + } + + fn handle_scopes(&mut self, req: Request) -> Result<(), ServerError> { + let scopes = vec![]; + self.server.respond( + req.success(ResponseBody::Scopes(ScopesResponse { scopes })), + )?; + Ok(()) + } + + fn handle_set_exceptions_breakpoints(&mut self, req: Request) -> Result<(), ServerError> { + self.server.respond(req.success(ResponseBody::SetExceptionBreakpoints( + SetExceptionBreakpointsResponse { breakpoints: None }, + )))?; + Ok(()) + } + + fn handle_disassemble(&mut self, req: Request) -> Result<(), ServerError> { + let Command::Disassemble(ref args) = req.command else { + unreachable!("handle_disassemble called on a non disassemble request"); + }; + + let instructions = vec![]; + + self.server.respond( + req.success(ResponseBody::Disassemble(DisassembleResponse { instructions })), + )?; + Ok(()) + } + + fn handle_step(&mut self, req: Request) -> Result<(), ServerError> { + eprintln!("INFO: stepped by instruction"); + self.server.respond(req.ack()?)?; + Ok(()) + } + + fn handle_next(&mut self, req: Request) -> Result<(), ServerError> { + eprintln!("INFO: stepped by statement"); + self.server.respond(req.ack()?)?; + Ok(()) + } + + fn handle_continue(&mut self, req: Request) -> Result<(), ServerError> { + eprintln!("INFO: continue"); + self.server.respond(req.success(ResponseBody::Continue(ContinueResponse { + all_threads_continued: Some(true), + })))?; + Ok(()) + } + + fn get_next_breakpoint_id(&mut self) -> i64 { + let id = self.next_breakpoint_id; + self.next_breakpoint_id += 1; + id + } + + fn handle_set_instruction_breakpoints(&mut self, req: Request) -> Result<(), ServerError> { + let Command::SetInstructionBreakpoints(ref args) = req.command else { + unreachable!("handle_set_instruction_breakpoints called on a different request"); + }; + + let breakpoints = vec![]; + + // response to request + self.server.respond(req.success(ResponseBody::SetInstructionBreakpoints( + SetInstructionBreakpointsResponse { breakpoints }, + )))?; + Ok(()) + } + + fn handle_set_source_breakpoints(&mut self, req: Request) -> Result<(), ServerError> { + let Command::SetBreakpoints(ref args) = req.command else { + unreachable!("handle_set_source_breakpoints called on a different request"); + }; + + let breakpoints = vec![]; + + self.server.respond( + req.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints })), + )?; + Ok(()) + } +} + +pub fn run_session( + server: Server, +) -> Result<(), ServerError> { + let mut session = + DapSession::new(server); + + session.run_loop() +} \ No newline at end of file diff --git a/toolchains/solidity/core/crates/foundry-debugger/src/main.rs b/toolchains/solidity/core/crates/foundry-debugger/src/main.rs index fbedd920..5dbab3a9 100644 --- a/toolchains/solidity/core/crates/foundry-debugger/src/main.rs +++ b/toolchains/solidity/core/crates/foundry-debugger/src/main.rs @@ -1,3 +1,40 @@ -fn main() { - println!("Hello, world!"); -} \ No newline at end of file +use std::io::{BufReader, BufWriter}; +use ::dap::server::Server; +use ::dap::requests::Command; +use ::dap::responses::ResponseBody; + +use thiserror::Error; + +mod dap; + +#[derive(Error, Debug)] +enum MyAdapterError { + #[error("Unhandled command")] + UnhandledCommandError, + + #[error("Missing command")] + MissingCommandError, +} + +type DynResult = std::result::Result>; + +fn main() -> DynResult<()> { + let output = BufWriter::new(std::io::stdout()); + let input = BufReader::new(std::io::stdin()); + let mut server = Server::new(input, output); + + // TODO handle launch command once we know what to send + let req = match server.poll_request()? { + Some(req) => req, + None => return Err(Box::new(MyAdapterError::MissingCommandError)), + }; + if let Command::Initialize(_) = req.command { + let rsp = req.success(ResponseBody::Initialize(Default::default())); + + server.respond(rsp)?; + let _ = dap::run_session(server); + Ok(()) + } else { + Err(Box::new(MyAdapterError::UnhandledCommandError)) + } +}