X-Git-Url: http://nitlanguage.org diff --git a/src/interpreter/debugger.nit b/src/interpreter/debugger.nit index a7d8912..1765d9e 100644 --- a/src/interpreter/debugger.nit +++ b/src/interpreter/debugger.nit @@ -17,7 +17,6 @@ # Debugging of a nit program using the NaiveInterpreter module debugger -import breakpoint intrude import naive_interpreter import nitx intrude import semantize::local_var_init @@ -66,7 +65,7 @@ end redef class ScopeVisitor - redef init(toolcontext) + redef init do super if toolcontext.dbg != null then @@ -104,7 +103,7 @@ redef class ToolContext redef fun check_errors do if dbg == null then - super + return super else if messages.length > 0 then message_sorter.sort(messages) @@ -117,12 +116,13 @@ redef class ToolContext messages.clear end + return not had_error end # -d - var opt_debugger_mode: OptionBool = new OptionBool("Launches the target program with the debugger attached to it", "-d") + var opt_debugger_mode = 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") + var opt_debugger_autorun = new OptionBool("Launches the target program with the interpreter, such as when the program fails, the debugging prompt is summoned", "-c") redef init do @@ -167,6 +167,20 @@ redef class ModelBuilder end end +# Contains all the informations of a Breakpoint for the Debugger +class Breakpoint + + # Line to break on + var line: Int + + # File concerned by the breakpoint + var file: String + + redef init do + if not file.has_suffix(".nit") then file += ".nit" + end +end + # The class extending `NaiveInterpreter` by adding debugging methods class Debugger super NaiveInterpreter @@ -211,6 +225,8 @@ class Debugger # Auto continues the execution until the end or until an error is encountered var autocontinue = false + redef type FRAME: InterpreterFrame + ####################################################################### ## Execution of statement function ## ####################################################################### @@ -257,19 +273,8 @@ class Debugger end # Same as a regular call but for a runtime injected module - # fun rt_call(mpropdef: MMethodDef, args: Array[Instance]): nullable Instance do - args = call_commons(mpropdef, args) - return rt_call_without_varargs(mpropdef, args) - end - - # Common code to call and this function - # - # Call only executes the variadic part, this avoids - # double encapsulation of variadic parameters into an Array - fun rt_call_without_varargs(mpropdef: MMethodDef, args: Array[Instance]): nullable Instance - do if self.modelbuilder.toolcontext.opt_discover_call_trace.value and not self.discover_call_trace.has(mpropdef) then self.discover_call_trace.add mpropdef self.debug("Discovered {mpropdef}") @@ -277,20 +282,13 @@ class Debugger assert args.length == mpropdef.msignature.arity + 1 else debug("Invalid arity for {mpropdef}. {args.length} arguments given.") # Look for the AST node that implements the property - var mproperty = mpropdef.mproperty - if self.modelbuilder.mpropdef2npropdef.has_key(mpropdef) then - var npropdef = self.modelbuilder.mpropdef2npropdef[mpropdef] - self.parameter_check(npropdef, mpropdef, args) - if npropdef isa AMethPropdef then - return npropdef.rt_call(self, mpropdef, args) - else - print "Error, invalid propdef to call at runtime !" - return null - end - else if mproperty.name == "init" then - var nclassdef = self.modelbuilder.mclassdef2nclassdef[mpropdef.mclassdef] - self.parameter_check(nclassdef, mpropdef, args) - return nclassdef.call(self, mpropdef, args) + var node = modelbuilder.mpropdef2node(mpropdef) + if node isa AMethPropdef then + self.parameter_check(node, mpropdef, args) + return node.rt_call(self, mpropdef, args) + else if node isa AClassdef then + self.parameter_check(node, mpropdef, args) + return node.call(self, mpropdef, args) else fatal("Fatal Error: method {mpropdef} not found in the AST") abort @@ -324,7 +322,6 @@ class Debugger var mmod = e.mmodule if mmod != null then self.mainmodule = mmod - var local_classdefs = mmod.mclassdefs var sys_type = mmod.sys_type if sys_type == null then print "Fatal error, cannot find Class Sys !\nAborting" @@ -379,14 +376,6 @@ class Debugger var breakpoint = find_breakpoint(curr_file, n.location.line_start) if breakpoints.keys.has(curr_file) and breakpoint != null then - - breakpoint.check_in - - if not breakpoint.is_valid - then - remove_breakpoint(curr_file, n.location.line_start) - end - n.debug("Execute stmt {n.to_s}") while read_cmd do end end @@ -399,7 +388,6 @@ class Debugger 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") @@ -465,7 +453,7 @@ class Debugger # # Returns a boolean value, representing whether or not to # continue reading commands from the console input - fun process_debug_command(command:String): Bool + fun process_debug_command(command: String): Bool do # Step-out command if command == "finish" @@ -478,10 +466,17 @@ class Debugger # Step-over command else if command == "n" then return step_over + # Shows help + else if command == "help" then + help + return true # Opens a new NitIndex prompt on current model else if command == "nitx" then new NitIndex.with_infos(modelbuilder, self.mainmodule).prompt return true + else if command == "bt" or command == "backtrack" then + print stack_trace + return true # Continues execution until the end else if command == "c" then return continue_exec @@ -503,41 +498,72 @@ class Debugger print stack_trace exit(0) else - var parts_of_command = command.split_with(' ') + var parts = command.split_with(' ') + var cname = parts.first # Shows the value of a variable in the current frame - if parts_of_command[0] == "p" or parts_of_command[0] == "print" then - print_command(parts_of_command) + if cname == "p" or cname == "print" then + print_command(parts) # Places a breakpoint on line x of file y - else if parts_of_command[0] == "break" or parts_of_command[0] == "b" - then - process_place_break_fun(parts_of_command) - # Places a temporary breakpoint on line x of file y - else if parts_of_command[0] == "tbreak" and (parts_of_command.length == 2 or parts_of_command.length == 3) - then - process_place_tbreak_fun(parts_of_command) + else if cname == "break" or cname == "b" then + process_place_break_fun(parts) # Removes a breakpoint on line x of file y - else if parts_of_command[0] == "d" or parts_of_command[0] == "delete" then - process_remove_break_fun(parts_of_command) + else if cname == "d" or cname == "delete" then + process_remove_break_fun(parts) # Sets an alias for a variable - else if parts_of_command.length == 3 and parts_of_command[1] == "as" - then - add_alias(parts_of_command[0], parts_of_command[2]) + else if parts.length == 2 and parts[1] == "as" then + process_alias(parts) # 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) + else if parts.length == 3 and parts[1] == "=" then + process_mod_function(parts) # 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) + else if cname == "trace" then + process_trace_command(parts) # 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) + else if cname == "untrace" then + process_untrace_command(parts) else - print "Unknown command \"{command}\"" + bad_command(command) end end return true end + # Produces help for the commands of the debugger + fun help do + print "" + print "Help :" + print "-----------------------------------" + print "" + print "Variables" + print " * Modification: var_name = value (Warning: var_name must be primitive)" + print " * Alias: var_name as alias" + print "" + print "Printing" + print " * Variables: p(rint) var_name (Use * to print all local variables)" + print " * Collections: p(rint) var_name '[' start_index (.. end_index) ']'" + print "" + print "Breakpoints" + print " * File/line: b(reak) file_name line_number" + print " * Remove: d(elete) id" + print "" + print "Tracepoints" + print " * Variable: trace var_name break/print" + print " * Untrace variable: untrace var_name" + print "" + print "Flow control" + print " * Next instruction (same-level): n" + print " * Next instruction: s" + print " * Finish current method: finish" + print " * Continue until next breakpoint or end: c" + print "" + print "General commands" + print " * quit: Quits the debugger" + print " * abort: Aborts the interpretation, prints the stack trace before leaving" + print " * nitx: Ask questions to the model about its entities (classes, methods, etc.)" + print " * nit: Inject dynamic code for interpretation" + print "" + end + ####################################################################### ## Processing specific command functions ## ####################################################################### @@ -580,75 +606,96 @@ class Debugger return false end + fun bad_command(cmd: String) do + print "Unrecognized command {cmd}. Use 'help' to show help." + end + # Prints the demanded variable in the command # # The name of the variable in in position 1 of the array 'parts_of_command' - fun print_command(parts_of_command: Array[String]) + fun print_command(parts: Array[String]) do - if parts_of_command[1] == "*" then + if parts.length != 2 then + bad_command(parts.join(" ")) + return + end + if parts[1] == "*" then var map_of_instances = frame.map - var keys = map_of_instances.iterator - var self_var = seek_variable("self", frame) print "self: {self_var.to_s}" for instance in map_of_instances.keys do print "{instance.to_s}: {map_of_instances[instance].to_s}" end - else if parts_of_command[1] == "stack" then - print self.stack_trace - else if parts_of_command[1].chars.has('[') and parts_of_command[1].chars.has(']') then - process_array_command(parts_of_command) + else if parts[1].chars.has('[') and parts[1].chars.has(']') then + process_array_command(parts) else - var instance = seek_variable(get_real_variable_name(parts_of_command[1]), frame) + var instance = seek_variable(get_real_variable_name(parts[1]), frame) if instance != null then print_instance(instance) else - print "Cannot find variable {parts_of_command[1]}" + print "Cannot find variable {parts[1]}" end end end + # Process the input command to set an alias for a variable + fun process_alias(parts: Array[String]) do + if parts.length != 3 then + bad_command(parts.join(" ")) + return + end + add_alias(parts.first, parts.last) + end + # Processes the input string to know where to put a breakpoint - fun process_place_break_fun(parts_of_command: Array[String]) + fun process_place_break_fun(parts: Array[String]) do - var bp = get_breakpoint_from_command(parts_of_command) + if parts.length != 3 then + bad_command(parts.join(" ")) + return + end + var bp = get_breakpoint_from_command(parts) if bp != null then place_breakpoint(bp) end end # Returns a breakpoint containing the informations stored in the command - fun get_breakpoint_from_command(parts_of_command: Array[String]): nullable Breakpoint + fun get_breakpoint_from_command(parts: Array[String]): nullable Breakpoint do - if parts_of_command[1].is_numeric then - return new Breakpoint(parts_of_command[1].to_i, curr_file) - else if parts_of_command.length >= 3 and parts_of_command[2].is_numeric then - return new Breakpoint(parts_of_command[2].to_i, parts_of_command[1]) + if parts[1].is_numeric then + return new Breakpoint(parts[1].to_i, curr_file) + else if parts.length >= 3 and parts[2].is_numeric then + return new Breakpoint(parts[2].to_i, parts[1]) else return null end end # Processes the command of removing a breakpoint on specified line and file - fun process_remove_break_fun(parts_of_command: Array[String]) + fun process_remove_break_fun(parts: Array[String]) do - if parts_of_command[1].is_numeric then - remove_breakpoint(self.curr_file, parts_of_command[1].to_i) - else if parts_of_command.length >= 3 and parts_of_command[2].is_numeric then - remove_breakpoint(parts_of_command[1], parts_of_command[2].to_i) + if parts.length != 2 then + bad_command(parts.join(" ")) + return + end + if parts[1].is_numeric then + remove_breakpoint(self.curr_file, parts[1].to_i) + else if parts.length >= 3 and parts[2].is_numeric then + remove_breakpoint(parts[1], parts[2].to_i) end end # Processes an array print command - fun process_array_command(parts_of_command: Array[String]) + fun process_array_command(parts: Array[String]) do - var index_of_first_brace = parts_of_command[1].chars.index_of('[') - var variable_name = get_real_variable_name(parts_of_command[1].substring(0,index_of_first_brace)) - var braces = parts_of_command[1].substring_from(index_of_first_brace) + var index_of_first_brace = parts[1].chars.index_of('[') + var variable_name = get_real_variable_name(parts[1].substring(0,index_of_first_brace)) + var braces = parts[1].substring_from(index_of_first_brace) var indexes = remove_braces(braces) @@ -676,27 +723,32 @@ class Debugger # Processes the modification function to modify a variable dynamically # # Command of type variable = value - fun process_mod_function(parts_of_command: Array[String]) + fun process_mod_function(parts: Array[String]) do - parts_of_command[0] = get_real_variable_name(parts_of_command[0]) - var parts_of_variable = parts_of_command[0].split_with(".") + if parts.length != 3 then + bad_command(parts.join(" ")) + return + end + var p0 = parts[0] + p0 = get_real_variable_name(p0) + var parts_of_variable = p0.split_with(".") if parts_of_variable.length > 1 then var last_part = parts_of_variable.pop - var first_part = parts_of_command[0].substring(0,parts_of_command[0].length - last_part.length - 1) + var first_part = p0.substring(0,p0.length - last_part.length - 1) var papa = seek_variable(first_part, frame) if papa != null and papa isa MutableInstance then var attribute = get_attribute_in_mutable_instance(papa, last_part) if attribute != null then - modify_argument_of_complex_type(papa, attribute, parts_of_command[2]) + modify_argument_of_complex_type(papa, attribute, parts[2]) end end else var target = seek_variable(parts_of_variable[0], frame) if target != null then - modify_in_frame(target, parts_of_command[2]) + modify_in_frame(target, parts[2]) end end end @@ -704,42 +756,46 @@ class Debugger # Processes the untrace variable command # # Command pattern : "untrace variable" - fun process_untrace_command(parts_of_command: Array[String]) + fun process_untrace_command(parts: Array[String]) do - var variable_name = get_real_variable_name(parts_of_command[1]) + if parts.length != 2 then + bad_command(parts.join(" ")) + return + end + var variable_name = get_real_variable_name(parts[1]) if untrace_variable(variable_name) then - print "Untraced variable {parts_of_command[1]}" + print "Untraced variable {parts[1]}" else - print "{parts_of_command[1]} is not traced" + print "{parts[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]) + fun process_trace_command(parts: Array[String]) do - var variable_name = get_real_variable_name(parts_of_command[1]) + if parts.length != 3 then + bad_command(parts.join(" ")) + return + end + var variable_name = get_real_variable_name(parts[1]) var breaker:Bool if seek_variable(variable_name, frame) == null then - print "Cannot find a variable called {parts_of_command[1]}" + print "Cannot find a variable called {parts[1]}" return end - if parts_of_command.length == 3 then - if parts_of_command[2] == "break" then - breaker = true - else - breaker = false - end + if parts[2] == "break" then + breaker = true else breaker = false end trace_variable(variable_name, breaker) - print "Successfully tracing {parts_of_command[1]}" + print "Successfully tracing {parts[1]}" end ####################################################################### @@ -813,7 +869,7 @@ class Debugger # 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 - private fun get_position_of_variable_in_arguments(frame: Frame, variable_name: String): Int + private fun get_position_of_variable_in_arguments(frame: FRAME, variable_name: String): Int do var identifiers = get_identifiers_in_current_instruction(get_function_arguments(frame.mpropdef.location.text)) for i in [0 .. identifiers.length-1] do @@ -832,14 +888,12 @@ class Debugger var trigger_char_escape = false var trigger_string_escape = false - var trigger_concat_in_string = false for i in instruction.chars do if trigger_char_escape then if i == '\'' then trigger_char_escape = false else if trigger_string_escape then if i == '{' then - trigger_concat_in_string = true trigger_string_escape = false else if i == '\"' then trigger_string_escape = false else @@ -857,7 +911,6 @@ class Debugger else if i == '\"' then trigger_string_escape = true else if i == '}' then - trigger_concat_in_string = false trigger_string_escape = true else if instruction_buffer.length > 0 and not instruction_buffer.is_numeric and not (instruction_buffer.chars[0] >= 'A' and instruction_buffer.chars[0] <= 'Z') then result_array.push(instruction_buffer.to_s) @@ -1002,7 +1055,7 @@ class Debugger ####################################################################### # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance - private fun seek_variable(variable_path: String, frame: Frame): nullable Instance + private fun seek_variable(variable_path: String, frame: FRAME): nullable Instance do var full_variable = variable_path.split_with(".") @@ -1022,7 +1075,7 @@ class Debugger end # Gets a variable 'variable_name' contained in the frame 'frame' - private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance + private fun get_variable_in_frame(variable_name: String, frame: FRAME): nullable Instance do if variable_name == "self" then if frame.arguments.length >= 1 then return frame.arguments.first @@ -1084,8 +1137,6 @@ class Debugger do var collection_length_attribute = get_attribute_in_mutable_instance(collection, "length") - var real_collection_length: nullable Int = null - if collection_length_attribute != null then var primitive_length_instance = collection.attributes[collection_length_attribute] if primitive_length_instance isa PrimitiveInstance[Int] then @@ -1215,17 +1266,6 @@ class Debugger end end - #Places a breakpoint that will trigger once and be destroyed afterwards - fun process_place_tbreak_fun(parts_of_command: Array[String]) - do - var bp = get_breakpoint_from_command(parts_of_command) - if bp != null - then - bp.set_max_breaks(1) - place_breakpoint(bp) - end - end - ####################################################################### ## Breakpoint removing functions ## ####################################################################### @@ -1371,7 +1411,7 @@ redef class AMethPropdef # Not supposed to be used by anyone else than the Debugger. private fun rt_call(v: Debugger, mpropdef: MMethodDef, args: Array[Instance]): nullable Instance do - var f = new Frame(self, self.mpropdef.as(not null), args) + var f = new InterpreterFrame(self, self.mpropdef.as(not null), args) var curr_instances = v.frame.map for i in curr_instances.keys do f.map[i] = curr_instances[i] @@ -1398,16 +1438,11 @@ end private class TraceObject # Map of the local names bound to a frame - var trace_map: HashMap[Frame, String] + var trace_map = new HashMap[Frame, String] + # Decides if breaking or printing statement when the variable is encountered var break_on_encounter: Bool - init(break_on_encounter: Bool) - do - trace_map = new HashMap[Frame, String] - self.break_on_encounter = break_on_encounter - end - # Adds the local alias for a variable and the frame bound to it fun add_frame_variable(frame: Frame, variable_name: String) do @@ -1432,7 +1467,7 @@ redef class ANode # Breaks automatically when encountering an error # Permits the injunction of commands before crashing - redef private fun fatal(v: NaiveInterpreter, message: String) + redef fun fatal(v: NaiveInterpreter, message: String) do if v isa Debugger then print "An error was encountered, the program will stop now."