diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 9dc5c758c6f..243feb48b19 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -178,6 +178,17 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { .filter(|v: &Vec| !v.is_empty()) } + /// Returns the `FileId` of the file associated with the innermost function on the call stack. + pub(super) fn get_current_file(&mut self) -> Option { + match self.get_current_source_location() { + Some(locations) => match locations.last() { + Some(location) => Some(location.file), + None => None, + }, + None => None, + } + } + /// Returns the (possible) stack of source locations corresponding to the /// given opcode location. Due to compiler inlining it's possible for this /// function to return multiple source locations. An empty vector means that diff --git a/tooling/debugger/src/repl.rs b/tooling/debugger/src/repl.rs index 5aef12ad8d4..db33817e012 100644 --- a/tooling/debugger/src/repl.rs +++ b/tooling/debugger/src/repl.rs @@ -199,6 +199,70 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { } } + fn add_breakpoint_at_line(&mut self, mut line_number: usize) { + // Make line number 0-indexed. + line_number -= 1; + + let current_file = match self.context.get_current_file() { + Some(file) => file.clone(), + None => { + println!("No current file."); + return; + } + }; + + let mut best_start: usize = 0; + let mut best_location: Option = None; + + // Helper function that first finds the source locations associated with the given opcode. + // Then, it finds the start and end lines of the source location and + // updates the best_start and best_location temporaries, if the given opcode_location + // matches the desired break-line better than the previous best match. + let mut update_best_match = |opcode_location: OpcodeLocation| { + let source_locations = + self.context.get_source_location_for_opcode_location(&opcode_location); + // Last means inner-most in stack trace. + match source_locations.last() { + Some(source_location) => { + let start = self.debug_artifact.location_line_index(*source_location).unwrap(); + let end = + self.debug_artifact.location_end_line_index(*source_location).unwrap(); + if source_location.file == current_file + && start <= line_number + && line_number <= end + && line_number - start < line_number - best_start + { + best_start = start; + best_location = Some(opcode_location); + } + } + None => (), + } + }; + + let opcodes = self.context.get_opcodes(); + for (acir_index, opcode) in opcodes.iter().enumerate() { + match &opcode { + Opcode::BrilligCall { id, .. } => { + let bytecode = &self.unconstrained_functions[*id as usize].bytecode; + for (brillig_index, ..) in bytecode.iter().enumerate() { + let opcode_location = OpcodeLocation::Brillig { acir_index, brillig_index }; + update_best_match(opcode_location); + } + } + _ => { + println!("Not supported: break-line for {:?}", opcode); + return; + } + } + } + + match best_location { + Some(location) => self.add_breakpoint_at(location), + None => println!("No opcode at line {}", line_number), + } + } + fn delete_breakpoint_at(&mut self, location: OpcodeLocation) { if self.context.delete_breakpoint(&location) { println!("Breakpoint at opcode {location} deleted"); @@ -475,6 +539,16 @@ pub fn run>( } }, ) + .add( + "break", + command! { + "add a breakpoint at a line of the current file", + (line_number: usize) => |line_number| { + ref_context.borrow_mut().add_breakpoint_at_line(line_number); + Ok(CommandStatus::Done) + } + }, + ) .add( "break", command! {