mod handlers;
mod state;
mod util;
use crate::{
error::{self, AdapterError, Error},
server::{state::ServerState, util::IdGenerator},
types::{ExitCode, Instruction},
};
use dap::{
events::{ExitedEventBody, OutputEventBody, StoppedEventBody},
prelude::*,
types::StartDebuggingRequestKind,
};
use forc_pkg::{
manifest::GenericManifestFile,
source::IPFSNode,
{self, BuildProfile, Built, BuiltPackage, PackageManifestFile},
};
use forc_test::{
execute::{DebugResult, TestExecutor},
setup::TestSetup,
BuiltTests,
};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
io::{BufReader, BufWriter, Read, Write},
process,
sync::Arc,
};
use sway_core::BuildTarget;
use sway_types::LineCol;
pub const THREAD_ID: i64 = 0;
pub const REGISTERS_VARIABLE_REF: i64 = 1;
pub const INSTRUCTIONS_VARIABLE_REF: i64 = 2;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AdditionalData {
pub program: String,
}
pub struct DapServer {
server: Server<Box<dyn Read>, Box<dyn Write>>,
breakpoint_id_gen: IdGenerator,
state: ServerState,
}
impl Default for DapServer {
fn default() -> Self {
Self::new(Box::new(std::io::stdin()), Box::new(std::io::stdout()))
}
}
impl DapServer {
pub fn new(input: Box<dyn Read>, output: Box<dyn Write>) -> Self {
let server = Server::new(BufReader::new(input), BufWriter::new(output));
DapServer {
server,
state: ServerState::default(),
breakpoint_id_gen: IdGenerator::default(),
}
}
pub fn start(&mut self) -> error::Result<()> {
loop {
let req = match self.server.poll_request()? {
Some(req) => req,
None => return Err(Error::AdapterError(AdapterError::MissingCommand)),
};
let response = self.handle_request(req)?;
self.server.respond(response)?;
if !self.state.initialized_event_sent {
let _ = self.server.send_event(Event::Initialized);
self.state.initialized_event_sent = true;
}
if self.should_launch() {
self.state.started_debugging = true;
match self.launch() {
Ok(true) => continue,
Ok(false) => self.exit(0), Err(e) => {
self.error(format!("Launch error: {e:?}"));
self.exit(1);
}
}
}
}
}
fn handle_request(&mut self, req: Request) -> error::Result<Response> {
let (result, exit_code) = self.handle_command(&req.command).into_tuple();
let response = match result {
Ok(rsp) => Ok(req.success(rsp)),
Err(e) => {
self.error(format!("{e:?}"));
Ok(req.error(&format!("{e:?}")))
}
};
if let Some(exit_code) = exit_code {
self.exit(exit_code);
}
response
}
pub fn handle_command(&mut self, command: &Command) -> HandlerResult {
match command {
Command::Attach(_) => self.handle_attach(),
Command::BreakpointLocations(ref args) => {
self.handle_breakpoint_locations_command(args)
}
Command::ConfigurationDone => self.handle_configuration_done(),
Command::Continue(_) => self.handle_continue(),
Command::Disconnect(_) => HandlerResult::ok_with_exit(ResponseBody::Disconnect, 0),
Command::Evaluate(args) => self.handle_evaluate(args),
Command::Initialize(_) => self.handle_initialize(),
Command::Launch(ref args) => self.handle_launch(args),
Command::Next(_) => self.handle_next(),
Command::Pause(_) => self.handle_pause(),
Command::Restart(_) => self.handle_restart(),
Command::Scopes(_) => self.handle_scopes(),
Command::SetBreakpoints(ref args) => self.handle_set_breakpoints_command(args),
Command::StackTrace(_) => self.handle_stack_trace_command(),
Command::StepIn(_) => {
self.error("This feature is not currently supported.".into());
HandlerResult::ok(ResponseBody::StepIn)
}
Command::StepOut(_) => {
self.error("This feature is not currently supported.".into());
HandlerResult::ok(ResponseBody::StepOut)
}
Command::Terminate(_) => HandlerResult::ok_with_exit(ResponseBody::Terminate, 0),
Command::TerminateThreads(_) => {
HandlerResult::ok_with_exit(ResponseBody::TerminateThreads, 0)
}
Command::Threads => self.handle_threads(),
Command::Variables(ref args) => self.handle_variables_command(args),
_ => HandlerResult::err(AdapterError::UnhandledCommand {
command: command.clone(),
}),
}
}
fn should_launch(&self) -> bool {
self.state.configuration_done
&& !self.state.started_debugging
&& matches!(self.state.mode, Some(StartDebuggingRequestKind::Launch))
}
fn log(&mut self, output: String) {
let _ = self.server.send_event(Event::Output(OutputEventBody {
output,
..Default::default()
}));
}
fn error(&mut self, output: String) {
let _ = self.server.send_event(Event::Output(OutputEventBody {
output,
category: Some(types::OutputEventCategory::Stderr),
..Default::default()
}));
}
fn log_test_results(&mut self) {
if !self.state.executors.is_empty() {
return;
}
let test_results = &self.state.test_results;
let test_lines = test_results
.iter()
.map(|r| {
let outcome = if r.passed() { "ok" } else { "failed" };
format!(
"test {} ... {} ({}ms, {} gas)",
r.name,
outcome,
r.duration.as_millis(),
r.gas_used
)
})
.collect::<Vec<_>>()
.join("\n");
let passed = test_results.iter().filter(|r| r.passed()).count();
let final_outcome = if passed == test_results.len() {
"OK"
} else {
"FAILED"
};
self.log(format!(
"{test_lines}\nResult: {final_outcome}. {passed} passed. {} failed.\n",
test_results.len() - passed
));
}
pub fn launch(&mut self) -> Result<bool, AdapterError> {
let (pkg_to_debug, test_setup) = self.build_tests()?;
let entries = pkg_to_debug.bytecode.entries.iter().filter_map(|entry| {
if let Some(test_entry) = entry.kind.test() {
return Some((entry, test_entry));
}
None
});
let executors: Vec<TestExecutor> = entries
.filter_map(|(entry, test_entry)| {
let offset = u32::try_from(entry.finalized.imm)
.expect("test instruction offset out of range");
let name = entry.finalized.fn_name.clone();
if test_entry.file_path.as_path() != self.state.program_path.as_path() {
return None;
}
TestExecutor::build(
&pkg_to_debug.bytecode.bytes,
offset,
test_setup.clone(),
test_entry,
name.clone(),
)
.ok()
})
.collect();
self.state.init_executors(executors);
self.start_debugging_tests(false)
}
pub(crate) fn build_tests(&mut self) -> Result<(BuiltPackage, TestSetup), AdapterError> {
if let Some(pkg) = &self.state.built_package {
if let Some(setup) = &self.state.test_setup {
return Ok((pkg.clone(), setup.clone()));
}
}
let manifest_file = forc_pkg::manifest::ManifestFile::from_dir(&self.state.program_path)
.map_err(|err| AdapterError::BuildFailed {
reason: format!("read manifest file: {err:?}"),
})?;
let pkg_manifest: PackageManifestFile =
manifest_file
.clone()
.try_into()
.map_err(|err: anyhow::Error| AdapterError::BuildFailed {
reason: format!("package manifest: {err:?}"),
})?;
let member_manifests =
manifest_file
.member_manifests()
.map_err(|err| AdapterError::BuildFailed {
reason: format!("member manifests: {err:?}"),
})?;
let lock_path = manifest_file
.lock_path()
.map_err(|err| AdapterError::BuildFailed {
reason: format!("lock path: {err:?}"),
})?;
let build_plan = forc_pkg::BuildPlan::from_lock_and_manifests(
&lock_path,
&member_manifests,
false,
false,
&IPFSNode::default(),
)
.map_err(|err| AdapterError::BuildFailed {
reason: format!("build plan: {err:?}"),
})?;
let project_name = pkg_manifest.project_name();
let outputs = std::iter::once(build_plan.find_member_index(project_name).ok_or(
AdapterError::BuildFailed {
reason: format!("find built project: {project_name}"),
},
)?)
.collect();
let built_packages = forc_pkg::build(
&build_plan,
BuildTarget::default(),
&BuildProfile {
optimization_level: sway_core::OptLevel::Opt0,
include_tests: true,
..Default::default()
},
&outputs,
&[],
&[],
)
.map_err(|err| AdapterError::BuildFailed {
reason: format!("build packages: {err:?}"),
})?;
let mut pkg_to_debug: Option<&BuiltPackage> = None;
for (_, built_pkg) in &built_packages {
if built_pkg.descriptor.manifest_file == pkg_manifest {
pkg_to_debug = Some(built_pkg);
}
let source_map = &built_pkg.source_map;
let paths = &source_map.paths;
source_map.map.iter().for_each(|(instruction, sm_span)| {
if let Some(path_buf) = paths.get(sm_span.path.0) {
let LineCol { line, .. } = sm_span.range.start;
let (line, instruction) = (line as i64, *instruction as Instruction);
self.state
.source_map
.entry(path_buf.clone())
.and_modify(|new_map| {
new_map
.entry(line)
.and_modify(|val| {
match val.binary_search(&instruction) {
Ok(_) => {} Err(pos) => val.insert(pos, instruction),
}
})
.or_insert(vec![instruction]);
})
.or_insert(HashMap::from([(line, vec![instruction])]));
} else {
self.error(format!(
"Path missing from source map: {:?}",
sm_span.path.0
));
}
});
}
let built_package = pkg_to_debug.ok_or(AdapterError::BuildFailed {
reason: format!("find package: {project_name}"),
})?;
let built = Built::Package(Arc::from(built_package.clone()));
let built_tests = BuiltTests::from_built(built, &build_plan).map_err(|err| {
AdapterError::BuildFailed {
reason: format!("build tests: {err:?}"),
}
})?;
let pkg_tests = match built_tests {
BuiltTests::Package(pkg_tests) => pkg_tests,
BuiltTests::Workspace(_) => {
return Err(AdapterError::BuildFailed {
reason: "package tests: workspace tests not supported".into(),
})
}
};
let test_setup = pkg_tests.setup().map_err(|err| AdapterError::BuildFailed {
reason: format!("test setup: {err:?}"),
})?;
self.state.built_package = Some(built_package.clone());
self.state.test_setup = Some(test_setup.clone());
Ok((built_package.clone(), test_setup))
}
fn exit(&mut self, exit_code: i64) {
let _ = self
.server
.send_event(Event::Exited(ExitedEventBody { exit_code }));
process::exit(exit_code as i32);
}
fn stop(&mut self, pc: Instruction) -> Result<bool, AdapterError> {
let (hit_breakpoint_ids, reason) =
if let Ok(breakpoint_id) = self.state.vm_pc_to_breakpoint_id(pc) {
self.state.stopped_on_breakpoint_id = Some(breakpoint_id);
(
Some(vec![breakpoint_id]),
types::StoppedEventReason::Breakpoint,
)
} else {
self.state.stopped_on_breakpoint_id = None;
(None, types::StoppedEventReason::Step)
};
let _ = self.server.send_event(Event::Stopped(StoppedEventBody {
reason,
hit_breakpoint_ids,
description: None,
thread_id: Some(THREAD_ID),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
}));
Ok(true)
}
fn start_debugging_tests(&mut self, single_stepping: bool) -> Result<bool, AdapterError> {
self.state.update_vm_breakpoints();
while let Some(executor) = self.state.executors.first_mut() {
executor.interpreter.set_single_stepping(single_stepping);
match executor.start_debugging()? {
DebugResult::TestComplete(result) => {
self.state.test_complete(result);
}
DebugResult::Breakpoint(pc) => {
executor.interpreter.set_single_stepping(false);
return self.stop(pc);
}
};
}
self.log_test_results();
Ok(false)
}
fn continue_debugging_tests(&mut self, single_stepping: bool) -> Result<bool, AdapterError> {
self.state.update_vm_breakpoints();
if let Some(executor) = self.state.executors.first_mut() {
executor.interpreter.set_single_stepping(single_stepping);
match executor.continue_debugging()? {
DebugResult::TestComplete(result) => {
self.state.test_complete(result);
return self.start_debugging_tests(single_stepping);
}
DebugResult::Breakpoint(pc) => {
executor.interpreter.set_single_stepping(false);
return self.stop(pc);
}
}
}
self.log_test_results();
Ok(false)
}
}
#[derive(Debug)]
pub struct HandlerResult {
response: Result<ResponseBody, AdapterError>,
exit_code: Option<ExitCode>,
}
impl HandlerResult {
pub fn ok(response: ResponseBody) -> Self {
Self {
response: Ok(response),
exit_code: None,
}
}
pub fn ok_with_exit(response: ResponseBody, code: ExitCode) -> Self {
Self {
response: Ok(response),
exit_code: Some(code),
}
}
pub fn err_with_exit(error: AdapterError, code: ExitCode) -> Self {
Self {
response: Err(error),
exit_code: Some(code),
}
}
pub fn err(error: AdapterError) -> Self {
Self {
response: Err(error),
exit_code: None,
}
}
pub fn into_tuple(self) -> (Result<ResponseBody, AdapterError>, Option<ExitCode>) {
(self.response, self.exit_code)
}
}