forc_debug/server/
mod.rs

1mod handlers;
2mod state;
3mod util;
4
5use crate::{
6    error::{self, AdapterError, Error},
7    server::{state::ServerState, util::IdGenerator},
8    types::{ExitCode, Instruction},
9};
10use dap::{
11    events::{ExitedEventBody, OutputEventBody, StoppedEventBody},
12    prelude::*,
13    types::StartDebuggingRequestKind,
14};
15use forc_pkg::{
16    manifest::GenericManifestFile,
17    source::IPFSNode,
18    {self, BuildProfile, Built, BuiltPackage, PackageManifestFile},
19};
20use forc_test::{
21    execute::{DebugResult, TestExecutor},
22    setup::TestSetup,
23    BuiltTests,
24};
25use serde::{Deserialize, Serialize};
26use std::{
27    io::{BufReader, BufWriter, Read, Write},
28    process,
29    sync::Arc,
30};
31use sway_core::BuildTarget;
32
33pub const THREAD_ID: i64 = 0;
34pub const REGISTERS_VARIABLE_REF: i64 = 1;
35pub const INSTRUCTIONS_VARIABLE_REF: i64 = 2;
36
37#[derive(Debug, Clone, Default, Serialize, Deserialize)]
38pub struct AdditionalData {
39    pub program: String,
40}
41
42/// This struct is a stateful representation of a Debug Adapter Protocol (DAP) server. It holds everything
43/// needed to implement (DAP)[https://microsoft.github.io/debug-adapter-protocol/].
44///
45/// It is responsible for handling requests and sending responses and events to the client. It manages
46/// the state of the server and the underlying VM instances used for debugging sway tests. It builds sway code
47/// and generates source maps for debugging. It also manages the test setup and reports results back to the client.
48pub struct DapServer {
49    /// The DAP server transport.
50    server: Server<Box<dyn Read>, Box<dyn Write>>,
51    /// Used to generate unique breakpoint IDs.
52    breakpoint_id_gen: IdGenerator,
53    /// The server state.
54    pub state: ServerState,
55}
56
57impl Default for DapServer {
58    fn default() -> Self {
59        Self::new(Box::new(std::io::stdin()), Box::new(std::io::stdout()))
60    }
61}
62
63impl DapServer {
64    /// Creates a new DAP server with custom input and output streams.
65    ///
66    /// # Arguments
67    /// * `input` - Source of DAP protocol messages (usually stdin)
68    /// * `output` - Destination for DAP protocol messages (usually stdout)
69    pub fn new(input: Box<dyn Read>, output: Box<dyn Write>) -> Self {
70        let server = Server::new(BufReader::new(input), BufWriter::new(output));
71        DapServer {
72            server,
73            state: ServerState::default(),
74            breakpoint_id_gen: IdGenerator::default(),
75        }
76    }
77
78    /// Runs the debug server event loop, handling client requests until completion or error.
79    pub fn start(&mut self) -> error::Result<()> {
80        loop {
81            let req = match self.server.poll_request()? {
82                Some(req) => req,
83                None => return Err(Error::AdapterError(AdapterError::MissingCommand)),
84            };
85
86            // Handle the request and send response
87            let response = self.handle_request(req)?;
88            self.server.respond(response)?;
89
90            // Handle one-time initialization
91            if !self.state.initialized_event_sent {
92                let _ = self.server.send_event(Event::Initialized);
93                self.state.initialized_event_sent = true;
94            }
95
96            // Handle launch after configuration is complete
97            if self.should_launch() {
98                self.state.started_debugging = true;
99                match self.launch() {
100                    Ok(true) => continue,
101                    Ok(false) => self.exit(0), // The tests finished executing
102                    Err(e) => {
103                        self.error(format!("Launch error: {e:?}"));
104                        self.exit(1);
105                    }
106                }
107            }
108        }
109    }
110
111    /// Processes a debug adapter request and generates appropriate response.
112    fn handle_request(&mut self, req: Request) -> error::Result<Response> {
113        let (result, exit_code) = self.handle_command(&req.command).into_tuple();
114        let response = match result {
115            Ok(rsp) => Ok(req.success(rsp)),
116            Err(e) => {
117                self.error(format!("{e:?}"));
118                Ok(req.error(&format!("{e:?}")))
119            }
120        };
121        if let Some(exit_code) = exit_code {
122            self.exit(exit_code);
123        }
124        response
125    }
126
127    /// Handles a command and returns the result and exit code, if any.
128    pub fn handle_command(&mut self, command: &Command) -> HandlerResult {
129        match command {
130            Command::Attach(_) => self.handle_attach(),
131            Command::BreakpointLocations(ref args) => {
132                self.handle_breakpoint_locations_command(args)
133            }
134            Command::ConfigurationDone => self.handle_configuration_done(),
135            Command::Continue(_) => self.handle_continue(),
136            Command::Disconnect(_) => HandlerResult::ok_with_exit(ResponseBody::Disconnect, 0),
137            Command::Evaluate(args) => self.handle_evaluate(args),
138            Command::Initialize(_) => self.handle_initialize(),
139            Command::Launch(ref args) => self.handle_launch(args),
140            Command::Next(_) => self.handle_next(),
141            Command::Pause(_) => self.handle_pause(),
142            Command::Restart(_) => self.handle_restart(),
143            Command::Scopes(_) => self.handle_scopes(),
144            Command::SetBreakpoints(ref args) => self.handle_set_breakpoints_command(args),
145            Command::StackTrace(_) => self.handle_stack_trace_command(),
146            Command::StepIn(_) => {
147                self.error("This feature is not currently supported.".into());
148                HandlerResult::ok(ResponseBody::StepIn)
149            }
150            Command::StepOut(_) => {
151                self.error("This feature is not currently supported.".into());
152                HandlerResult::ok(ResponseBody::StepOut)
153            }
154            Command::Terminate(_) => HandlerResult::ok_with_exit(ResponseBody::Terminate, 0),
155            Command::TerminateThreads(_) => {
156                HandlerResult::ok_with_exit(ResponseBody::TerminateThreads, 0)
157            }
158            Command::Threads => self.handle_threads(),
159            Command::Variables(ref args) => self.handle_variables_command(args),
160            _ => HandlerResult::err(AdapterError::UnhandledCommand {
161                command: command.clone(),
162            }),
163        }
164    }
165
166    /// Checks whether debug session is ready to begin launching tests.
167    fn should_launch(&self) -> bool {
168        self.state.configuration_done
169            && !self.state.started_debugging
170            && matches!(self.state.mode, Some(StartDebuggingRequestKind::Launch))
171    }
172
173    /// Logs a message to the client's debugger console output.
174    fn log(&mut self, output: String) {
175        let _ = self.server.send_event(Event::Output(OutputEventBody {
176            output,
177            ..Default::default()
178        }));
179    }
180
181    /// Logs an error message to the client's debugger console output.
182    fn error(&mut self, output: String) {
183        let _ = self.server.send_event(Event::Output(OutputEventBody {
184            output,
185            category: Some(types::OutputEventCategory::Stderr),
186            ..Default::default()
187        }));
188    }
189
190    /// Logs test execution results in a cargo-test-like format, showing duration and gas usage for each test.
191    fn log_test_results(&mut self) {
192        if !self.state.executors.is_empty() {
193            return;
194        }
195        let test_results = &self.state.test_results;
196        let test_lines = test_results
197            .iter()
198            .map(|r| {
199                let outcome = if r.passed() { "ok" } else { "failed" };
200                format!(
201                    "test {} ... {} ({}ms, {} gas)",
202                    r.name,
203                    outcome,
204                    r.duration.as_millis(),
205                    r.gas_used
206                )
207            })
208            .collect::<Vec<_>>()
209            .join("\n");
210
211        let passed = test_results.iter().filter(|r| r.passed()).count();
212        let final_outcome = if passed == test_results.len() {
213            "OK"
214        } else {
215            "FAILED"
216        };
217
218        self.log(format!(
219            "{test_lines}\nResult: {final_outcome}. {passed} passed. {} failed.\n",
220            test_results.len() - passed
221        ));
222    }
223
224    /// Handles a `launch` request. Returns true if the server should continue running.
225    pub fn launch(&mut self) -> Result<bool, AdapterError> {
226        // Build tests for the given path.
227        let (pkg_to_debug, test_setup) = self.build_tests()?;
228        let entries = pkg_to_debug.bytecode.entries.iter().filter_map(|entry| {
229            if let Some(test_entry) = entry.kind.test() {
230                return Some((entry, test_entry));
231            }
232            None
233        });
234
235        // Construct a TestExecutor for each test and store it
236        let executors: Vec<TestExecutor> = entries
237            .filter_map(|(entry, test_entry)| {
238                let offset = u32::try_from(entry.finalized.imm)
239                    .expect("test instruction offset out of range");
240                let name = entry.finalized.fn_name.clone();
241                if test_entry.file_path.as_path() != self.state.program_path.as_path() {
242                    return None;
243                }
244
245                TestExecutor::build(
246                    &pkg_to_debug.bytecode.bytes,
247                    offset,
248                    test_setup.clone(),
249                    test_entry,
250                    name.clone(),
251                )
252                .ok()
253            })
254            .collect();
255        self.state.init_executors(executors);
256
257        // Start debugging
258        self.start_debugging_tests(false)
259    }
260
261    /// Builds the tests at the given [PathBuf] and stores the source maps.
262    pub fn build_tests(&mut self) -> Result<(BuiltPackage, TestSetup), AdapterError> {
263        if let Some(pkg) = &self.state.built_package {
264            if let Some(setup) = &self.state.test_setup {
265                return Ok((pkg.clone(), setup.clone()));
266            }
267        }
268
269        // 1. Build the packages
270        let manifest_file = forc_pkg::manifest::ManifestFile::from_dir(&self.state.program_path)
271            .map_err(|err| AdapterError::BuildFailed {
272                reason: format!("read manifest file: {err:?}"),
273            })?;
274        let pkg_manifest: PackageManifestFile =
275            manifest_file
276                .clone()
277                .try_into()
278                .map_err(|err: anyhow::Error| AdapterError::BuildFailed {
279                    reason: format!("package manifest: {err:?}"),
280                })?;
281        let member_manifests =
282            manifest_file
283                .member_manifests()
284                .map_err(|err| AdapterError::BuildFailed {
285                    reason: format!("member manifests: {err:?}"),
286                })?;
287        let lock_path = manifest_file
288            .lock_path()
289            .map_err(|err| AdapterError::BuildFailed {
290                reason: format!("lock path: {err:?}"),
291            })?;
292        let build_plan = forc_pkg::BuildPlan::from_lock_and_manifests(
293            &lock_path,
294            &member_manifests,
295            false,
296            false,
297            &IPFSNode::default(),
298        )
299        .map_err(|err| AdapterError::BuildFailed {
300            reason: format!("build plan: {err:?}"),
301        })?;
302
303        let project_name = pkg_manifest.project_name();
304
305        let outputs = std::iter::once(build_plan.find_member_index(project_name).ok_or(
306            AdapterError::BuildFailed {
307                reason: format!("find built project: {project_name}"),
308            },
309        )?)
310        .collect();
311
312        let built_packages = forc_pkg::build(
313            &build_plan,
314            BuildTarget::default(),
315            &BuildProfile {
316                optimization_level: sway_core::OptLevel::Opt0,
317                include_tests: true,
318                ..Default::default()
319            },
320            &outputs,
321            &[],
322            &[],
323            None,
324        )
325        .map_err(|err| AdapterError::BuildFailed {
326            reason: format!("build packages: {err:?}"),
327        })?;
328
329        // 2. Store the source maps and find debug package
330        let pkg_to_debug = built_packages
331            .iter()
332            .find(|(_, pkg)| pkg.descriptor.manifest_file == pkg_manifest)
333            .map(|(_, pkg)| pkg)
334            .ok_or(AdapterError::BuildFailed {
335                reason: format!("find package: {project_name}"),
336            })?;
337
338        self.state.source_map = pkg_to_debug.source_map.clone();
339
340        // 3. Build the tests
341        let built = Built::Package(Arc::from(pkg_to_debug.clone()));
342
343        let built_tests = BuiltTests::from_built(built, &build_plan).map_err(|err| {
344            AdapterError::BuildFailed {
345                reason: format!("build tests: {err:?}"),
346            }
347        })?;
348
349        let pkg_tests = match built_tests {
350            BuiltTests::Package(pkg_tests) => pkg_tests,
351            BuiltTests::Workspace(_) => {
352                return Err(AdapterError::BuildFailed {
353                    reason: "package tests: workspace tests not supported".into(),
354                })
355            }
356        };
357        let test_setup = pkg_tests.setup().map_err(|err| AdapterError::BuildFailed {
358            reason: format!("test setup: {err:?}"),
359        })?;
360        self.state.built_package = Some(pkg_to_debug.clone());
361        self.state.test_setup = Some(test_setup.clone());
362        Ok((pkg_to_debug.clone(), test_setup))
363    }
364
365    /// Sends the 'exited' event to the client and kills the server process.
366    fn exit(&mut self, exit_code: i64) {
367        let _ = self
368            .server
369            .send_event(Event::Exited(ExitedEventBody { exit_code }));
370        process::exit(exit_code as i32);
371    }
372
373    fn stop(&mut self, pc: Instruction) -> Result<bool, AdapterError> {
374        let (hit_breakpoint_ids, reason) =
375            if let Ok(breakpoint_id) = self.state.vm_pc_to_breakpoint_id(pc) {
376                self.state.stopped_on_breakpoint_id = Some(breakpoint_id);
377                (
378                    Some(vec![breakpoint_id]),
379                    types::StoppedEventReason::Breakpoint,
380                )
381            } else {
382                self.state.stopped_on_breakpoint_id = None;
383                (None, types::StoppedEventReason::Step)
384            };
385
386        let _ = self.server.send_event(Event::Stopped(StoppedEventBody {
387            reason,
388            hit_breakpoint_ids,
389            description: None,
390            thread_id: Some(THREAD_ID),
391            preserve_focus_hint: None,
392            text: None,
393            all_threads_stopped: None,
394        }));
395        Ok(true)
396    }
397
398    /// Starts debugging all tests.
399    /// `single_stepping` indicates whether the VM should break after one instruction.
400    ///
401    /// Returns true if it has stopped on a breakpoint or false if all tests have finished.
402    fn start_debugging_tests(&mut self, single_stepping: bool) -> Result<bool, AdapterError> {
403        self.state.update_vm_breakpoints();
404
405        while let Some(executor) = self.state.executors.first_mut() {
406            executor.interpreter.set_single_stepping(single_stepping);
407            match executor.start_debugging()? {
408                DebugResult::TestComplete(result) => {
409                    self.state.test_complete(result);
410                }
411                DebugResult::Breakpoint(pc) => {
412                    executor.interpreter.set_single_stepping(false);
413                    return self.stop(pc);
414                }
415            };
416        }
417        self.log_test_results();
418        Ok(false)
419    }
420
421    /// Continues debugging the current test and starts the next one if no breakpoint is hit.
422    /// `single_stepping` indicates whether the VM should break after one instruction.
423    ///
424    /// Returns true if it has stopped on a breakpoint or false if all tests have finished.
425    fn continue_debugging_tests(&mut self, single_stepping: bool) -> Result<bool, AdapterError> {
426        self.state.update_vm_breakpoints();
427
428        if let Some(executor) = self.state.executors.first_mut() {
429            executor.interpreter.set_single_stepping(single_stepping);
430            match executor.continue_debugging()? {
431                DebugResult::TestComplete(result) => {
432                    self.state.test_complete(result);
433                    // The current test has finished, but there could be more tests to run. Start debugging the
434                    // remaining tests.
435                    return self.start_debugging_tests(single_stepping);
436                }
437                DebugResult::Breakpoint(pc) => {
438                    executor.interpreter.set_single_stepping(false);
439                    return self.stop(pc);
440                }
441            }
442        }
443        self.log_test_results();
444        Ok(false)
445    }
446}
447
448/// Represents the result of a DAP handler operation, combining the response/error and an optional exit code
449#[derive(Debug)]
450pub struct HandlerResult {
451    response: Result<ResponseBody, AdapterError>,
452    exit_code: Option<ExitCode>,
453}
454
455impl HandlerResult {
456    /// Creates a new successful result with no exit code
457    pub fn ok(response: ResponseBody) -> Self {
458        Self {
459            response: Ok(response),
460            exit_code: None,
461        }
462    }
463
464    /// Creates a new successful result with an exit code
465    pub fn ok_with_exit(response: ResponseBody, code: ExitCode) -> Self {
466        Self {
467            response: Ok(response),
468            exit_code: Some(code),
469        }
470    }
471
472    /// Creates a new error result with an exit code
473    pub fn err_with_exit(error: AdapterError, code: ExitCode) -> Self {
474        Self {
475            response: Err(error),
476            exit_code: Some(code),
477        }
478    }
479
480    /// Creates a new error result with no exit code
481    pub fn err(error: AdapterError) -> Self {
482        Self {
483            response: Err(error),
484            exit_code: None,
485        }
486    }
487
488    /// Deconstructs the result into its original tuple form
489    pub fn into_tuple(self) -> (Result<ResponseBody, AdapterError>, Option<ExitCode>) {
490        (self.response, self.exit_code)
491    }
492}