1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2013 Lucas Bajolet <lucas.bajolet@gmail.com>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Debugging of a nit program using the NaiveInterpreter
21 intrude import naive_interpreter
23 redef class ToolContext
25 var opt_debugger_mode
: OptionBool = new OptionBool("Launches the target program with the debugger attached to it", "-d")
30 self.option_context
.add_option
(self.opt_debugger_mode
)
34 redef class ModelBuilder
35 # Execute the program from the entry point (Sys::main) of the `mainmodule'
36 # `arguments' are the command-line arguments in order
38 # 1. the AST is fully loaded.
39 # 2. the model is fully built.
40 # 3. the instructions are fully analysed.
41 fun run_debugger
(mainmodule
: MModule, arguments
: Array[String])
44 self.toolcontext
.info
("*** START INTERPRETING ***", 1)
46 var interpreter
= new Debugger(self, mainmodule
, arguments
)
48 init_naive_interpreter
(interpreter
, mainmodule
)
51 self.toolcontext
.info
("*** END INTERPRETING: {time1-time0} ***", 2)
55 # The class extending NaiveInterpreter by adding debugging methods
57 super NaiveInterpreter
59 # Keeps the frame count in memory to find when to stop
60 # and launch the command prompt after a step out call
61 var step_stack_count
= 1
63 # Triggers a step over an instruction in a nit program
64 var stop_after_step_over_trigger
= true
66 # Triggers a step out of an instruction
67 var stop_after_step_out_trigger
= false
69 # Triggers a step in a instruction (enters a function
70 # if the instruction is a function call)
71 var step_in_trigger
= false
73 # HashMap containing the breakpoints bound to a file
74 var breakpoints
= new HashMap[String, HashSet[Breakpoint]]
76 # Contains the current file
79 # Aliases hashmap (maps an alias to a variable name)
80 var aliases
= new HashMap[String, String]
82 #######################################################################
83 ## Execution of statement function ##
84 #######################################################################
86 # Main loop, every call to a debug command is done here
87 redef fun stmt
(n
: nullable AExpr)
89 if n
== null then return
91 var frame
= self.frame
92 var old
= frame
.current_node
93 frame
.current_node
= n
100 frame
.current_node
= old
103 # Encpasulates the behaviour for step over/out
104 private fun steps_fun_call
(n
: AExpr)
106 if self.stop_after_step_over_trigger
then
107 if self.frames
.length
<= self.step_stack_count
then
108 n
.debug
("Execute stmt {n.to_s}")
109 while process_debug_command
(gets
) do end
111 else if self.stop_after_step_out_trigger
then
112 if frames
.length
< self.step_stack_count
then
113 n
.debug
("Execute stmt {n.to_s}")
114 while process_debug_command
(gets
) do end
116 else if step_in_trigger
then
117 n
.debug
("Execute stmt {n.to_s}")
118 while process_debug_command
(gets
) do end
122 # Checks if a breakpoint is encountered, and launches the debugging prompt if true
123 private fun breakpoint_check
(n
: AExpr)
125 var currFileNameSplit
= self.frame
.current_node
.location
.file
.filename
.to_s
.split_with
("/")
127 self.curr_file
= currFileNameSplit
[currFileNameSplit
.length-1
]
129 var breakpoint
= find_breakpoint
(curr_file
, n
.location
.line_start
)
131 if breakpoints
.keys
.has
(curr_file
) and breakpoint
!= null then
135 if not breakpoint
.is_valid
137 remove_breakpoint
(curr_file
, n
.location
.line_start
)
140 n
.debug
("Execute stmt {n.to_s}")
141 while process_debug_command
(gets
) do end
145 #######################################################################
146 ## Processing commands functions ##
147 #######################################################################
149 # Takes a user command as a parameter
151 # Returns a boolean value, representing whether or not to
152 # continue reading commands from the console input
153 fun process_debug_command
(command
:String): Bool
158 # Kills the current program
159 if command
== "kill" then
162 else if command
== "finish"
166 else if command
== "s"
170 else if command
== "n" then
172 # Continues execution until the end
173 else if command
== "c" then
176 var parts_of_command
= command
.split_with
(' ')
177 # Shows the value of a variable in the current frame
178 if parts_of_command
[0] == "p" or parts_of_command
[0] == "print" then
179 print_command
(parts_of_command
)
180 # Places a breakpoint on line x of file y
181 else if parts_of_command
[0] == "break" or parts_of_command
[0] == "b"
183 process_place_break_fun
(parts_of_command
)
184 # Places a temporary breakpoint on line x of file y
185 else if parts_of_command
[0] == "tbreak" and (parts_of_command
.length
== 2 or parts_of_command
.length
== 3)
187 process_place_tbreak_fun
(parts_of_command
)
188 # Removes a breakpoint on line x of file y
189 else if parts_of_command
[0] == "d" or parts_of_command
[0] == "delete" then
190 process_remove_break_fun
(parts_of_command
)
191 # Sets an alias for a variable
192 else if parts_of_command
.length
== 3 and parts_of_command
[1] == "as"
194 add_alias
(parts_of_command
[0], parts_of_command
[2])
195 # Lists all the commands available
203 #######################################################################
204 ## Processing specific command functions ##
205 #######################################################################
207 # Sets the flags to step-over an instruction in the current file
210 self.step_stack_count
= frames
.length
211 self.stop_after_step_over_trigger
= true
212 self.stop_after_step_out_trigger
= false
213 self.step_in_trigger
= false
217 #Sets the flags to step-out of a function
220 self.stop_after_step_over_trigger
= false
221 self.stop_after_step_out_trigger
= true
222 self.step_in_trigger
= false
223 self.step_stack_count
= frames
.length
227 # Sets the flags to step-in an instruction
230 self.step_in_trigger
= true
231 self.stop_after_step_over_trigger
= false
232 self.stop_after_step_out_trigger
= false
236 # Sets the flags to continue execution
237 fun continue_exec
: Bool
239 self.stop_after_step_over_trigger
= false
240 self.stop_after_step_out_trigger
= false
241 self.step_in_trigger
= false
245 # Prints the demanded variable in the command
247 # The name of the variable in in position 1 of the array 'parts_of_command'
248 fun print_command
(parts_of_command
: Array[String])
250 if parts_of_command
[1] == "*" then
251 var map_of_instances
= frame
.map
253 var keys
= map_of_instances
.iterator
255 print
"Variables collection : \n"
257 for instance
in map_of_instances
.keys
do
258 print
"Variable {instance.to_s}, Instance {map_of_instances[instance].to_s}"
261 print
"\nEnd of current instruction \n"
263 var instance
= seek_variable
(get_real_variable_name
(parts_of_command
[1]), frame
)
267 print_instance
(instance
)
269 print
"Cannot find variable {parts_of_command[1]}"
274 # Processes the input string to know where to put a breakpoint
275 fun process_place_break_fun
(parts_of_command
: Array[String])
277 var bp
= get_breakpoint_from_command
(parts_of_command
)
285 # Returns a breakpoint containing the informations stored in the command
286 fun get_breakpoint_from_command
(parts_of_command
: Array[String]): nullable Breakpoint
288 if parts_of_command
[1].is_numeric
then
289 return new Breakpoint(parts_of_command
[1].to_i
, curr_file
)
290 else if parts_of_command
.length
>= 3 and parts_of_command
[2].is_numeric
then
291 return new Breakpoint(parts_of_command
[2].to_i
, parts_of_command
[1])
297 # Processes the command of removing a breakpoint on specified line and file
298 fun process_remove_break_fun
(parts_of_command
: Array[String])
300 if parts_of_command
[1].is_numeric
then
301 remove_breakpoint
(self.curr_file
, parts_of_command
[1].to_i
)
302 else if parts_of_command
.length
>= 3 and parts_of_command
[2].is_numeric
then
303 remove_breakpoint
(parts_of_command
[1], parts_of_command
[2].to_i
)
309 #######################################################################
310 ## Alias management functions ##
311 #######################################################################
313 # Adds a new alias to the tables
314 fun add_alias
(var_represented
: String, alias
: String)
316 self.aliases
[alias
] = var_represented
319 # Gets the real name of a variable hidden by an alias
320 fun get_variable_name_by_alias
(alias
: String): nullable String
322 if self.aliases
.keys
.has
(alias
) then
323 return self.aliases
[alias
]
329 # Gets the variable named by name, whether it is an alias or not
330 fun get_real_variable_name
(name
: String): String
332 var explode_string
= name
.split_with
(".")
333 var final_string
= new Buffer
334 for i
in explode_string
do
335 var alias_resolved
= get_variable_name_by_alias
(i
)
336 if alias_resolved
!= null then
337 final_string
.append
(get_real_variable_name
(alias_resolved
))
339 final_string
.append
(i
)
341 final_string
.append
(".")
344 return final_string
.substring
(0,final_string
.length-1
).to_s
347 #######################################################################
348 ## Print functions ##
349 #######################################################################
351 # Prints an object instance and its attributes if it has some
353 # If it is a primitive type, its value is directly printed
354 fun print_instance
(instance
: Instance)
356 if instance
isa MutableInstance then
357 var attributes
= instance
.attributes
358 print
"Object : {instance}"
360 for current_attribute
in attributes
.keys
do
361 print
"Attribute : {current_attribute.to_s} \nValeur : {attributes[current_attribute].to_s}"
364 print
"Found variable {instance}"
368 #######################################################################
369 ## Variable Exploration functions ##
370 #######################################################################
372 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
373 private fun seek_variable
(variable_path
: String, frame
: Frame): nullable Instance
375 var full_variable
= variable_path
.split_with
(".")
377 var full_variable_iterator
= full_variable
.iterator
379 var first_instance
= get_variable_in_frame
(full_variable_iterator
.item
, frame
)
381 if first_instance
== null then return null
383 if full_variable
.length
<= 1 then return first_instance
385 full_variable_iterator
.next
387 if not (first_instance
isa MutableInstance and full_variable_iterator
.is_ok
) then return null
389 return get_variable_in_mutable_instance
(first_instance
, full_variable_iterator
)
392 # Gets a variable 'variable_name' contained in the frame 'frame'
393 private fun get_variable_in_frame
(variable_name
: String, frame
: Frame): nullable Instance
395 if variable_name
== "self" then
396 if frame
.arguments
.length
>= 1 then return frame
.arguments
.first
399 var map_of_instances
= frame
.map
401 for key
in map_of_instances
.keys
do
402 if key
.to_s
== variable_name
then
403 return map_of_instances
[key
]
410 # Gets an attribute 'attribute_name' contained in variable 'variable'
411 fun get_attribute_in_mutable_instance
(variable
: MutableInstance, attribute_name
: String): nullable MAttribute
413 var map_of_attributes
= variable
.attributes
415 for key
in map_of_attributes
.keys
do
416 if key
.to_s
.substring_from
(1) == attribute_name
then
424 # Recursive function, returns the variable described by 'total_chain'
425 fun get_variable_in_mutable_instance
(variable
: MutableInstance, iterator
: Iterator[String]): nullable Instance
427 var attribute
= get_attribute_in_mutable_instance
(variable
, iterator
.item
)
429 if attribute
== null then return null
433 if iterator
.is_ok
then
434 var new_variable
= variable
.attributes
[attribute
]
435 if new_variable
isa MutableInstance then
436 return get_variable_in_mutable_instance
(new_variable
, iterator
)
441 return variable
.attributes
[attribute
]
445 #######################################################################
446 ## Breakpoint placing functions ##
447 #######################################################################
449 # Places a breakpoint on line 'line_to_break' for file 'file_to_break'
450 fun place_breakpoint
(breakpoint
: Breakpoint)
452 if not self.breakpoints
.keys
.has
(breakpoint
.file
) then
453 self.breakpoints
[breakpoint
.file
] = new HashSet[Breakpoint]
455 if find_breakpoint
(breakpoint
.file
, breakpoint
.line
) == null then
456 self.breakpoints
[breakpoint
.file
].add
(breakpoint
)
457 print
"Breakpoint added on line {breakpoint.line} for file {breakpoint.file}"
459 print
"Breakpoint already present on line {breakpoint.line} for file {breakpoint.file}"
463 #Places a breakpoint that will trigger once and be destroyed afterwards
464 fun process_place_tbreak_fun
(parts_of_command
: Array[String])
466 var bp
= get_breakpoint_from_command
(parts_of_command
)
476 #######################################################################
477 ## Breakpoint removing functions ##
478 #######################################################################
480 # Removes a breakpoint on line 'line_to_break' for file 'file_to_break'
481 fun remove_breakpoint
(file_to_break
: String, line_to_break
: Int)
483 if self.breakpoints
.keys
.has
(file_to_break
) then
484 var bp
= find_breakpoint
(file_to_break
, line_to_break
)
487 self.breakpoints
[file_to_break
].remove
(bp
)
488 print
"Breakpoint removed on line {line_to_break} for file {file_to_break}"
493 print
"No breakpoint existing on line {line_to_break} for file {file_to_break}"
496 #######################################################################
497 ## Breakpoint searching functions ##
498 #######################################################################
500 # Finds a breakpoint for 'file' and 'line' in the class HashMap
501 fun find_breakpoint
(file
: String, line
: Int): nullable Breakpoint
503 if self.breakpoints
.keys
.has
(file
)
505 for i
in self.breakpoints
[file
]
517 #######################################################################
518 ## Command listing function ##
519 #######################################################################
521 # Lists the commands available when using the debugger
524 print
"\nCommand not recognized\n"
525 print
"Commands accepted : \n"
526 print
"[break/b] line : Adds a breakpoint on line *line_nb* of the current file\n"
527 print
"[break/b] file_name line_nb : Adds a breakpoint on line *line_nb* of file *file_name* \n"
528 print
"[p/print] variable : [p/print] * shows the status of all the variables\n"
529 print
"s : steps in on the current function\n"
530 print
"n : steps-over the current instruction\n"
531 print
"finish : steps out of the current function\n"
532 print
"variable as alias : Adds an alias called *alias* for the variable *variable*"
533 print
"An alias can reference another alias\n"
534 print
"[d/delete] line_nb : Removes a breakpoint on line *line_nb* of the current file \n"
535 print
"[d/delete] file_name line_nb : Removes a breakpoint on line *line_nb* of file *file_name* \n"
536 print
"kill : kills the current program (Exits with an error and stack trace)\n"