nitdbg: Added aliases
[nit.git] / src / debugger.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Lucas Bajolet <lucas.bajolet@gmail.com>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # Debugging of a nit program using the NaiveInterpreter
18 module debugger
19
20 import breakpoint
21 intrude import naive_interpreter
22
23 redef class ToolContext
24 # -d
25 var opt_debugger_mode: OptionBool = new OptionBool("Launches the target program with the debugger attached to it", "-d")
26
27 redef init
28 do
29 super
30 self.option_context.add_option(self.opt_debugger_mode)
31 end
32 end
33
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
37 # REQUIRE that:
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])
42 do
43 var time0 = get_time
44 self.toolcontext.info("*** START INTERPRETING ***", 1)
45
46 var interpreter = new Debugger(self, mainmodule, arguments)
47
48 init_naive_interpreter(interpreter, mainmodule)
49
50 var time1 = get_time
51 self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
52 end
53 end
54
55 # The class extending NaiveInterpreter by adding debugging methods
56 class Debugger
57 super NaiveInterpreter
58
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
62
63 # Triggers a step over an instruction in a nit program
64 var stop_after_step_over_trigger = true
65
66 # Triggers a step out of an instruction
67 var stop_after_step_out_trigger= false
68
69 # Triggers a step in a instruction (enters a function
70 # if the instruction is a function call)
71 var step_in_trigger = false
72
73 # HashMap containing the breakpoints bound to a file
74 var breakpoints = new HashMap[String, HashSet[Breakpoint]]
75
76 # Contains the current file
77 var curr_file = ""
78
79 # Aliases hashmap (maps an alias to a variable name)
80 var aliases = new HashMap[String, String]
81
82 #######################################################################
83 ## Execution of statement function ##
84 #######################################################################
85
86 # Main loop, every call to a debug command is done here
87 redef fun stmt(n: nullable AExpr)
88 do
89 if n == null then return
90
91 var frame = self.frame
92 var old = frame.current_node
93 frame.current_node = n
94
95 steps_fun_call(n)
96
97 breakpoint_check(n)
98
99 n.stmt(self)
100 frame.current_node = old
101 end
102
103 # Encpasulates the behaviour for step over/out
104 private fun steps_fun_call(n: AExpr)
105 do
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
110 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
115 end
116 else if step_in_trigger then
117 n.debug("Execute stmt {n.to_s}")
118 while process_debug_command(gets) do end
119 end
120 end
121
122 # Checks if a breakpoint is encountered, and launches the debugging prompt if true
123 private fun breakpoint_check(n: AExpr)
124 do
125 var currFileNameSplit = self.frame.current_node.location.file.filename.to_s.split_with("/")
126
127 self.curr_file = currFileNameSplit[currFileNameSplit.length-1]
128
129 var breakpoint = find_breakpoint(curr_file, n.location.line_start)
130
131 if breakpoints.keys.has(curr_file) and breakpoint != null then
132
133 breakpoint.check_in
134
135 if not breakpoint.is_valid
136 then
137 remove_breakpoint(curr_file, n.location.line_start)
138 end
139
140 n.debug("Execute stmt {n.to_s}")
141 while process_debug_command(gets) do end
142 end
143 end
144
145 #######################################################################
146 ## Processing commands functions ##
147 #######################################################################
148
149 # Takes a user command as a parameter
150 #
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
154 do
155 # For lisibility
156 print "\n"
157
158 # Kills the current program
159 if command == "kill" then
160 abort
161 # Step-out command
162 else if command == "finish"
163 then
164 return step_out
165 # Step-in command
166 else if command == "s"
167 then
168 return step_in
169 # Step-over command
170 else if command == "n" then
171 return step_over
172 # Continues execution until the end
173 else if command == "c" then
174 return continue_exec
175 else
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"
182 then
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)
186 then
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"
193 then
194 add_alias(parts_of_command[0], parts_of_command[2])
195 # Lists all the commands available
196 else
197 list_commands
198 end
199 end
200 return true
201 end
202
203 #######################################################################
204 ## Processing specific command functions ##
205 #######################################################################
206
207 # Sets the flags to step-over an instruction in the current file
208 fun step_over: Bool
209 do
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
214 return false
215 end
216
217 #Sets the flags to step-out of a function
218 fun step_out: Bool
219 do
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
224 return false
225 end
226
227 # Sets the flags to step-in an instruction
228 fun step_in: Bool
229 do
230 self.step_in_trigger = true
231 self.stop_after_step_over_trigger = false
232 self.stop_after_step_out_trigger = false
233 return false
234 end
235
236 # Sets the flags to continue execution
237 fun continue_exec: Bool
238 do
239 self.stop_after_step_over_trigger = false
240 self.stop_after_step_out_trigger = false
241 self.step_in_trigger = false
242 return false
243 end
244
245 # Prints the demanded variable in the command
246 #
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])
249 do
250 if parts_of_command[1] == "*" then
251 var map_of_instances = frame.map
252
253 var keys = map_of_instances.iterator
254
255 print "Variables collection : \n"
256
257 for instance in map_of_instances.keys do
258 print "Variable {instance.to_s}, Instance {map_of_instances[instance].to_s}"
259 end
260
261 print "\nEnd of current instruction \n"
262 else
263 var instance = seek_variable(get_real_variable_name(parts_of_command[1]), frame)
264
265 if instance != null
266 then
267 print_instance(instance)
268 else
269 print "Cannot find variable {parts_of_command[1]}"
270 end
271 end
272 end
273
274 # Processes the input string to know where to put a breakpoint
275 fun process_place_break_fun(parts_of_command: Array[String])
276 do
277 var bp = get_breakpoint_from_command(parts_of_command)
278 if bp != null then
279 place_breakpoint(bp)
280 else
281 list_commands
282 end
283 end
284
285 # Returns a breakpoint containing the informations stored in the command
286 fun get_breakpoint_from_command(parts_of_command: Array[String]): nullable Breakpoint
287 do
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])
292 else
293 return null
294 end
295 end
296
297 # Processes the command of removing a breakpoint on specified line and file
298 fun process_remove_break_fun(parts_of_command: Array[String])
299 do
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)
304 else
305 list_commands
306 end
307 end
308
309 #######################################################################
310 ## Alias management functions ##
311 #######################################################################
312
313 # Adds a new alias to the tables
314 fun add_alias(var_represented: String, alias: String)
315 do
316 self.aliases[alias] = var_represented
317 end
318
319 # Gets the real name of a variable hidden by an alias
320 fun get_variable_name_by_alias(alias: String): nullable String
321 do
322 if self.aliases.keys.has(alias) then
323 return self.aliases[alias]
324 end
325
326 return null
327 end
328
329 # Gets the variable named by name, whether it is an alias or not
330 fun get_real_variable_name(name: String): String
331 do
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))
338 else
339 final_string.append(i)
340 end
341 final_string.append(".")
342 end
343
344 return final_string.substring(0,final_string.length-1).to_s
345 end
346
347 #######################################################################
348 ## Print functions ##
349 #######################################################################
350
351 # Prints an object instance and its attributes if it has some
352 #
353 # If it is a primitive type, its value is directly printed
354 fun print_instance(instance: Instance)
355 do
356 if instance isa MutableInstance then
357 var attributes = instance.attributes
358 print "Object : {instance}"
359
360 for current_attribute in attributes.keys do
361 print "Attribute : {current_attribute.to_s} \nValeur : {attributes[current_attribute].to_s}"
362 end
363 else
364 print "Found variable {instance}"
365 end
366 end
367
368 #######################################################################
369 ## Variable Exploration functions ##
370 #######################################################################
371
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
374 do
375 var full_variable = variable_path.split_with(".")
376
377 var full_variable_iterator = full_variable.iterator
378
379 var first_instance = get_variable_in_frame(full_variable_iterator.item, frame)
380
381 if first_instance == null then return null
382
383 if full_variable.length <= 1 then return first_instance
384
385 full_variable_iterator.next
386
387 if not (first_instance isa MutableInstance and full_variable_iterator.is_ok) then return null
388
389 return get_variable_in_mutable_instance(first_instance, full_variable_iterator)
390 end
391
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
394 do
395 if variable_name == "self" then
396 if frame.arguments.length >= 1 then return frame.arguments.first
397 end
398
399 var map_of_instances = frame.map
400
401 for key in map_of_instances.keys do
402 if key.to_s == variable_name then
403 return map_of_instances[key]
404 end
405 end
406
407 return null
408 end
409
410 # Gets an attribute 'attribute_name' contained in variable 'variable'
411 fun get_attribute_in_mutable_instance(variable: MutableInstance, attribute_name: String): nullable MAttribute
412 do
413 var map_of_attributes = variable.attributes
414
415 for key in map_of_attributes.keys do
416 if key.to_s.substring_from(1) == attribute_name then
417 return key
418 end
419 end
420
421 return null
422 end
423
424 # Recursive function, returns the variable described by 'total_chain'
425 fun get_variable_in_mutable_instance(variable: MutableInstance, iterator: Iterator[String]): nullable Instance
426 do
427 var attribute = get_attribute_in_mutable_instance(variable, iterator.item)
428
429 if attribute == null then return null
430
431 iterator.next
432
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)
437 else
438 return null
439 end
440 else
441 return variable.attributes[attribute]
442 end
443 end
444
445 #######################################################################
446 ## Breakpoint placing functions ##
447 #######################################################################
448
449 # Places a breakpoint on line 'line_to_break' for file 'file_to_break'
450 fun place_breakpoint(breakpoint: Breakpoint)
451 do
452 if not self.breakpoints.keys.has(breakpoint.file) then
453 self.breakpoints[breakpoint.file] = new HashSet[Breakpoint]
454 end
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}"
458 else
459 print "Breakpoint already present on line {breakpoint.line} for file {breakpoint.file}"
460 end
461 end
462
463 #Places a breakpoint that will trigger once and be destroyed afterwards
464 fun process_place_tbreak_fun(parts_of_command: Array[String])
465 do
466 var bp = get_breakpoint_from_command(parts_of_command)
467 if bp != null
468 then
469 bp.set_max_breaks(1)
470 place_breakpoint(bp)
471 else
472 list_commands
473 end
474 end
475
476 #######################################################################
477 ## Breakpoint removing functions ##
478 #######################################################################
479
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)
482 do
483 if self.breakpoints.keys.has(file_to_break) then
484 var bp = find_breakpoint(file_to_break, line_to_break)
485
486 if bp != null then
487 self.breakpoints[file_to_break].remove(bp)
488 print "Breakpoint removed on line {line_to_break} for file {file_to_break}"
489 return
490 end
491 end
492
493 print "No breakpoint existing on line {line_to_break} for file {file_to_break}"
494 end
495
496 #######################################################################
497 ## Breakpoint searching functions ##
498 #######################################################################
499
500 # Finds a breakpoint for 'file' and 'line' in the class HashMap
501 fun find_breakpoint(file: String, line: Int): nullable Breakpoint
502 do
503 if self.breakpoints.keys.has(file)
504 then
505 for i in self.breakpoints[file]
506 do
507 if i.line == line
508 then
509 return i
510 end
511 end
512 end
513
514 return null
515 end
516
517 #######################################################################
518 ## Command listing function ##
519 #######################################################################
520
521 # Lists the commands available when using the debugger
522 fun list_commands
523 do
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"
537 end
538
539 end