From d3f7be2c1b76f9f002a6dd6ce9af17bcab71152a Mon Sep 17 00:00:00 2001 From: Martin Verzilli Date: Tue, 24 Oct 2023 10:32:29 +0200 Subject: [PATCH] feat(debugger): Print limited source code context (#3217) --- Cargo.lock | 1 + tooling/debugger/Cargo.toml | 3 +- tooling/debugger/src/lib.rs | 94 +++++++++++++++++++++++----- tooling/nargo/src/artifacts/debug.rs | 49 ++++++++++++++- tooling/nargo/src/errors.rs | 5 +- 5 files changed, 132 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9db1af43b2b..88341a39d9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2532,6 +2532,7 @@ name = "noir_debugger" version = "0.17.0" dependencies = [ "acvm", + "codespan-reporting", "easy-repl", "nargo", "noirc_printable_type", diff --git a/tooling/debugger/Cargo.toml b/tooling/debugger/Cargo.toml index e01abd9ac61..1f9a54b4fe8 100644 --- a/tooling/debugger/Cargo.toml +++ b/tooling/debugger/Cargo.toml @@ -13,5 +13,6 @@ acvm.workspace = true nargo.workspace = true noirc_printable_type.workspace = true thiserror.workspace = true +codespan-reporting.workspace = true easy-repl = "0.2.1" -owo-colors = "3" \ No newline at end of file +owo-colors = "3" diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index f8e9db19234..3da9c5c7989 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -4,16 +4,21 @@ use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; use nargo::artifacts::debug::DebugArtifact; -use nargo::errors::ExecutionError; +use nargo::errors::{ExecutionError, Location}; use nargo::NargoError; use nargo::ops::ForeignCallExecutor; use easy_repl::{command, CommandStatus, Critical, Repl}; -use std::cell::{Cell, RefCell}; +use std::{ + cell::{Cell, RefCell}, + ops::Range, +}; use owo_colors::OwoColorize; +use codespan_reporting::files::Files; + enum SolveResult { Done, Ok, @@ -74,25 +79,72 @@ impl<'backend, B: BlackBoxFunctionSolver> DebugContext<'backend, B> { println!("Finished execution"); } else { println!("Stopped at opcode {}: {}", ip, opcodes[ip]); - Self::show_source_code_location(&OpcodeLocation::Acir(ip), &self.debug_artifact); + self.show_source_code_location(&OpcodeLocation::Acir(ip), &self.debug_artifact); } } - fn show_source_code_location(location: &OpcodeLocation, debug_artifact: &DebugArtifact) { + fn print_location_path(&self, loc: Location) { + let line_number = self.debug_artifact.location_line_number(loc).unwrap(); + let column_number = self.debug_artifact.location_column_number(loc).unwrap(); + + println!( + "At {}:{line_number}:{column_number}", + self.debug_artifact.name(loc.file).unwrap() + ); + } + + fn show_source_code_location(&self, location: &OpcodeLocation, debug_artifact: &DebugArtifact) { let locations = debug_artifact.debug_symbols[0].opcode_location(location); - if let Some(locations) = locations { - for loc in locations { - let file = &debug_artifact.file_map[&loc.file]; - let source = &file.source.as_str(); - let start = loc.span.start() as usize; - let end = loc.span.end() as usize; - println!("At {}:{start}-{end}", file.path.as_path().display()); - println!( - "\n{}{}{}\n", - &source[0..start].to_string().dimmed(), - &source[start..end], - &source[end..].to_string().dimmed(), - ); + let Some(locations) = locations else { return }; + for loc in locations { + self.print_location_path(loc); + + let loc_line_index = debug_artifact.location_line_index(loc).unwrap(); + + // How many lines before or after the location's line we + // print + let context_lines = 5; + + let first_line_to_print = + if loc_line_index < context_lines { 0 } else { loc_line_index - context_lines }; + + let last_line_index = debug_artifact.last_line_index(loc).unwrap(); + let last_line_to_print = std::cmp::min(loc_line_index + context_lines, last_line_index); + + let source = debug_artifact.location_source_code(loc).unwrap(); + for (current_line_index, line) in source.lines().enumerate() { + let current_line_number = current_line_index + 1; + + if current_line_index < first_line_to_print { + // Ignore lines before range starts + continue; + } else if current_line_index == first_line_to_print && current_line_index > 0 { + // Denote that there's more lines before but we're not showing them + print_line_of_ellipsis(current_line_index); + } + + if current_line_index > last_line_to_print { + // Denote that there's more lines after but we're not showing them, + // and stop printing + print_line_of_ellipsis(current_line_number); + break; + } + + if current_line_index == loc_line_index { + // Highlight current location + let Range { start: loc_start, end: loc_end } = + debug_artifact.location_in_line(loc).unwrap(); + println!( + "{:>3} {:2} {}{}{}", + current_line_number, + "->", + &line[0..loc_start].to_string().dimmed(), + &line[loc_start..loc_end], + &line[loc_end..].to_string().dimmed() + ); + } else { + print_dimmed_line(current_line_number, line); + } } } } @@ -112,6 +164,14 @@ impl<'backend, B: BlackBoxFunctionSolver> DebugContext<'backend, B> { } } +fn print_line_of_ellipsis(line_number: usize) { + println!("{}", format!("{:>3} {}", line_number, "...").dimmed()); +} + +fn print_dimmed_line(line_number: usize, line: &str) { + println!("{}", format!("{:>3} {:2} {}", line_number, "", line).dimmed()); +} + fn map_command_status(result: SolveResult) -> CommandStatus { match result { SolveResult::Ok => CommandStatus::Done, diff --git a/tooling/nargo/src/artifacts/debug.rs b/tooling/nargo/src/artifacts/debug.rs index 31ce1d7bb6a..d2e2eb9f311 100644 --- a/tooling/nargo/src/artifacts/debug.rs +++ b/tooling/nargo/src/artifacts/debug.rs @@ -1,6 +1,6 @@ use codespan_reporting::files::{Error, Files, SimpleFile}; use noirc_driver::DebugFile; -use noirc_errors::debug_info::DebugInfo; +use noirc_errors::{debug_info::DebugInfo, Location}; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet}, @@ -45,6 +45,53 @@ impl DebugArtifact { Self { debug_symbols, file_map } } + + /// Given a location, returns its file's source code + pub fn location_source_code(&self, location: Location) -> Result<&str, Error> { + self.source(location.file) + } + + /// Given a location, returns the index of the line it starts at + pub fn location_line_index(&self, location: Location) -> Result { + let location_start = location.span.start() as usize; + self.line_index(location.file, location_start) + } + + /// Given a location, returns the line number it starts at + pub fn location_line_number(&self, location: Location) -> Result { + let location_start = location.span.start() as usize; + let line_index = self.line_index(location.file, location_start)?; + self.line_number(location.file, line_index) + } + + /// Given a location, returns the column number it starts at + pub fn location_column_number(&self, location: Location) -> Result { + let location_start = location.span.start() as usize; + let line_index = self.line_index(location.file, location_start)?; + self.column_number(location.file, line_index, location_start) + } + + /// Given a location, returns a Span relative to its line's + /// position in the file. This is useful when processing a file's + /// contents on a per-line-basis. + pub fn location_in_line(&self, location: Location) -> Result, Error> { + let location_start = location.span.start() as usize; + let location_end = location.span.end() as usize; + let line_index = self.line_index(location.file, location_start)?; + let line_span = self.line_range(location.file, line_index)?; + + let start_in_line = location_start - line_span.start; + let end_in_line = location_end - line_span.start; + + Ok(Range { start: start_in_line, end: end_in_line }) + } + + /// Given a location, returns the last line index + /// of its file + pub fn last_line_index(&self, location: Location) -> Result { + let source = self.source(location.file)?; + self.line_index(location.file, source.len()) + } } impl<'a> Files<'a> for DebugArtifact { diff --git a/tooling/nargo/src/errors.rs b/tooling/nargo/src/errors.rs index ea6e7fa8108..0c920716f2a 100644 --- a/tooling/nargo/src/errors.rs +++ b/tooling/nargo/src/errors.rs @@ -2,7 +2,10 @@ use acvm::{ acir::circuit::OpcodeLocation, pwg::{ErrorLocation, OpcodeResolutionError}, }; -use noirc_errors::{debug_info::DebugInfo, CustomDiagnostic, FileDiagnostic, Location}; +use noirc_errors::{debug_info::DebugInfo, CustomDiagnostic, FileDiagnostic}; + +pub use noirc_errors::Location; + use noirc_printable_type::ForeignCallError; use thiserror::Error;