X-Git-Url: http://nitlanguage.org?ds=sidebyside diff --git a/src/debugger.nit b/src/debugger.nit index 3a5e4b1..87c9a17 100644 --- a/src/debugger.nit +++ b/src/debugger.nit @@ -23,11 +23,14 @@ intrude import naive_interpreter redef class ToolContext # -d var opt_debugger_mode: OptionBool = new OptionBool("Launches the target program with the debugger attached to it", "-d") + # -c + var opt_debugger_autorun: OptionBool = new OptionBool("Launches the target program with the interpreter, such as when the program fails, the debugging prompt is summoned", "-c") redef init do super self.option_context.add_option(self.opt_debugger_mode) + self.option_context.add_option(self.opt_debugger_autorun) end end @@ -50,6 +53,20 @@ redef class ModelBuilder var time1 = get_time self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2) end + + fun run_debugger_autorun(mainmodule: MModule, arguments: Array[String]) + do + var time0 = get_time + self.toolcontext.info("*** START INTERPRETING ***", 1) + + var interpreter = new Debugger(self, mainmodule, arguments) + interpreter.autocontinue = true + + init_naive_interpreter(interpreter, mainmodule) + + var time1 = get_time + self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2) + end end # The class extending NaiveInterpreter by adding debugging methods @@ -79,6 +96,23 @@ class Debugger # Aliases hashmap (maps an alias to a variable name) var aliases = new HashMap[String, String] + # Set containing all the traced variables and their related frame + private var traces = new HashSet[TraceObject] + + # Map containing all the positions for the positions of the arguments traced + # In a function call + private var fun_call_arguments_positions = new HashMap[Int, TraceObject] + + # Triggers the remapping of a trace object in the local context after a function call + var aftermath = false + + # Used to prevent the case when the body of the function called is empty + # If it is not, then, the remapping won't be happening + var frame_count_aftermath = 1 + + # Auto continues the execution until the end or until an error is encountered + var autocontinue = false + ####################################################################### ## Execution of statement function ## ####################################################################### @@ -92,10 +126,18 @@ class Debugger var old = frame.current_node frame.current_node = n - if not n isa ABlockExpr then - steps_fun_call(n) + if not self.autocontinue then + if not n isa ABlockExpr then + steps_fun_call(n) + + breakpoint_check(n) - breakpoint_check(n) + check_funcall_and_traced_args(n) + + remap(n) + + check_if_vars_are_traced(n) + end end n.stmt(self) @@ -144,6 +186,65 @@ class Debugger end end + # Check if a variable of current expression is traced + # Then prints and/or breaks for command prompt + private fun check_if_vars_are_traced(n: AExpr) + do + var identifiers_in_instruction = get_identifiers_in_current_instruction(n.location.text) + + for i in identifiers_in_instruction do + var variable = seek_variable(i, frame) + for j in self.traces do + if j.is_variable_traced_in_frame(i, frame) then + n.debug("Traced variable {i} used") + if j.break_on_encounter then while process_debug_command(gets) do end + break + end + end + end + end + + # Function remapping all the traced objects to match their name in the local context + private fun remap(n: AExpr) + do + if self.aftermath then + + # Trace every argument variable pre-specified + if self.frame_count_aftermath < frames.length and fun_call_arguments_positions.length > 0 then + + var ids_in_fun_def = get_identifiers_in_current_instruction(get_function_arguments(frame.mpropdef.location.text)) + + for i in fun_call_arguments_positions.keys do + self.fun_call_arguments_positions[i].add_frame_variable(frame, ids_in_fun_def[i]) + end + end + + self.aftermath = false + end + end + + # If the current instruction is a function call + # We analyse its signature and the position of traced arguments if the call + # For future remapping when inside the function + private fun check_funcall_and_traced_args(n: AExpr) do + # If we have a function call, we need to see if any of the arguments is traced (including the caller) + # if it is, next time we face an instruction, we'll trace the local version on the traced variable in the next frame + if n isa ACallExpr then + self.aftermath = true + self.frame_count_aftermath = frames.length + fun_call_arguments_positions.clear + var fun_arguments = get_identifiers_in_current_instruction(get_function_arguments(n.location.text)) + + for i in self.traces do + for j in [0 .. fun_arguments.length - 1] do + if i.is_variable_traced_in_frame(fun_arguments[j],frame) then + fun_call_arguments_positions[j] = i + end + end + end + end + end + ####################################################################### ## Processing commands functions ## ####################################################################### @@ -197,6 +298,12 @@ class Debugger # Modifies the value of a variable in the current frame else if parts_of_command.length >= 3 and parts_of_command[1] == "=" then process_mod_function(parts_of_command) + # Traces the modifications on a variable + else if parts_of_command.length >= 2 and parts_of_command[0] == "trace" then + process_trace_command(parts_of_command) + # Untraces the modifications on a variable + else if parts_of_command.length == 2 and parts_of_command[0] == "untrace" then + process_untrace_command(parts_of_command) # Lists all the commands available else list_commands @@ -264,6 +371,8 @@ class Debugger end print "\nEnd of current instruction \n" + else if parts_of_command[1] == "stack" then + print self.stack_trace else if parts_of_command[1].has('[') and parts_of_command[1].has(']') then process_array_command(parts_of_command) else @@ -371,10 +480,115 @@ class Debugger end end + # Processes the untrace variable command + # + # Command pattern : "untrace variable" + fun process_untrace_command(parts_of_command: Array[String]) + do + var variable_name = get_real_variable_name(parts_of_command[1]) + if untrace_variable(variable_name) then + print "Untraced variable {parts_of_command[1]}" + else + print "{parts_of_command[1]} is not traced" + end + end + + # Processes the trace variable command + # + # Command pattern : "trace variable [break/print]" + fun process_trace_command(parts_of_command: Array[String]) + do + var variable_name = get_real_variable_name(parts_of_command[1]) + var breaker:Bool + + if seek_variable(variable_name, frame) == null then + print "Cannot find a variable called {parts_of_command[1]}" + return + end + + if parts_of_command.length == 3 then + if parts_of_command[2] == "break" then + breaker = true + else + breaker = false + end + else + breaker = false + end + + trace_variable(variable_name, breaker) + + print "Successfully tracing {parts_of_command[1]}" + end + ####################################################################### ## Trace Management functions ## ####################################################################### + # Effectively untraces the variable called *variable_name* + # + # Returns true if the variable exists, false otherwise + private fun untrace_variable(variable_name: String): Bool + do + var to_remove: nullable TraceObject = null + for i in self.traces do + if i.is_variable_traced_in_frame(variable_name, frame) then + to_remove = i + end + end + + if to_remove != null then + self.traces.remove(to_remove) + return true + else + return false + end + end + + # Effectively traces the variable *variable_name* either in print or break mode depending on the value of breaker (break if true, print if false) + # + private fun trace_variable(variable_name: String, breaker: Bool) + do + for i in self.traces do + if i.is_variable_traced_in_frame(variable_name, frame) then + print "This variable is already traced" + return + end + end + + var trace_object: TraceObject + + if breaker then + trace_object = new TraceObject(true) + else + trace_object = new TraceObject(false) + end + + # We trace the current variable found for the current frame + trace_object.add_frame_variable(self.frame, variable_name) + + var position_of_variable_in_arguments = get_position_of_variable_in_arguments(frame, variable_name) + + # Start parsing the frames starting with the parent of the current one, until the highest + # When the variable traced is declared locally, the loop stops + for i in [1 .. frames.length-1] do + + # If the variable was reported to be an argument of the previous frame + if position_of_variable_in_arguments != -1 then + + var local_name = get_identifiers_in_current_instruction(get_function_arguments(frames[i].current_node.location.text))[position_of_variable_in_arguments] + + position_of_variable_in_arguments = get_position_of_variable_in_arguments(frames[i], local_name) + + trace_object.add_frame_variable(frames[i], local_name) + else + break + end + end + + self.traces.add(trace_object) + end + # If the variable *variable_name* is an argument of the function being executed in the frame *frame* # The function returns its position in the arguments # Else, it returns -1 @@ -942,6 +1156,7 @@ class Debugger print "[p/print] variable : [p/print] * shows the status of all the variables\n" print "[p/print] variable[i] : Prints the value of the variable contained at position *i* in SequenceRead collection *variable*\n" print "[p/print] variable[i..j]: Prints the value of all the variables contained between positions *i* and *j* in SequenceRead collection *variable*\n" + print "[p/print] stack: Prints a stack trace at current instruction\n" print "Note : The arrays can be multi-dimensional (Ex : variable[i..j][k] will print all the values at position *k* of all the SequenceRead collections contained between positions *i* and *j* in SequenceRead collection *variable*)\n" print "s : steps in on the current function\n" print "n : steps-over the current instruction\n" @@ -951,6 +1166,8 @@ class Debugger print "variable = value : Sets the value of *variable* to *value*\n" print "[d/delete] line_nb : Removes a breakpoint on line *line_nb* of the current file \n" print "[d/delete] file_name line_nb : Removes a breakpoint on line *line_nb* of file *file_name* \n" + print "trace variable_name [break/print] : Traces the uses of the variable you chose to trace by printing the statement it appears in or by breaking on each use." + print "untrace variable_name : Removes the trace on the variable you chose to trace earlier in the program" print "kill : kills the current program (Exits with an error and stack trace)\n" end