model-clients: use `is_root_init` instead of `.name == "init"`
[nit.git] / src / interpreter / 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 intrude import naive_interpreter
21 import nitx
22 intrude import semantize::local_var_init
23 intrude import semantize::scope
24 intrude import toolcontext
25 private import parser_util
26
27 redef class Model
28 # Cleans the model to remove a module and what it defines when semantic analysis fails on injected code
29 private fun try_remove_module(m: MModule): Bool
30 do
31 var index = -1
32 for i in [0 .. mmodules.length[ do
33 if mmodules[i] == m then
34 index = i
35 break
36 end
37 end
38 if index == -1 then return false
39 var mmodule = mmodules[index]
40 mmodules.remove_at(index)
41 for classdef in mmodule.mclassdefs do
42 var mclass = classdef.mclass
43 for i in [0 .. mclass.mclassdefs.length[ do
44 if mclass.mclassdefs[i] == classdef then
45 index = i
46 break
47 end
48 end
49 mclass.mclassdefs.remove_at(index)
50 var propdefs = classdef.mpropdefs
51 for propdef in propdefs do
52 var prop = propdef.mproperty
53 for i in [0..prop.mpropdefs.length[ do
54 if prop.mpropdefs[i] == propdef then
55 index = i
56 break
57 end
58 end
59 prop.mpropdefs.remove_at(index)
60 end
61 end
62 return true
63 end
64 end
65
66 redef class ScopeVisitor
67
68 redef init(toolcontext)
69 do
70 super
71 if toolcontext.dbg != null then
72 var localvars = toolcontext.dbg.frame.map
73 for i in localvars.keys do
74 scopes.first.variables[i.to_s] = i
75 end
76 end
77 end
78
79 end
80
81 redef class LocalVarInitVisitor
82 redef fun mark_is_unset(node: AExpr, variable: nullable Variable)
83 do
84 super
85 if toolcontext.dbg != null then
86 var varname = variable.to_s
87 var instmap = toolcontext.dbg.frame.map
88 for i in instmap.keys do
89 if i.to_s == varname then
90 mark_is_set(node, variable)
91 end
92 end
93 end
94 end
95
96 end
97
98 redef class ToolContext
99 private var dbg: nullable Debugger = null
100
101 private var had_error: Bool = false
102
103 redef fun check_errors
104 do
105 if dbg == null then
106 super
107 else
108 if messages.length > 0 then
109 message_sorter.sort(messages)
110
111 for m in messages do
112 if m.text.search("Warning") == null then had_error = true
113 sys.stderr.write("{m.to_color_string}\n")
114 end
115 end
116
117 messages.clear
118 end
119 end
120
121 # -d
122 var opt_debugger_mode = new OptionBool("Launches the target program with the debugger attached to it", "-d")
123 # -c
124 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")
125
126 redef init
127 do
128 super
129 self.option_context.add_option(self.opt_debugger_mode)
130 self.option_context.add_option(self.opt_debugger_autorun)
131 end
132 end
133
134 redef class ModelBuilder
135 # Execute the program from the entry point (Sys::main) of the `mainmodule`
136 # `arguments` are the command-line arguments in order
137 # REQUIRE that:
138 # 1. the AST is fully loaded.
139 # 2. the model is fully built.
140 # 3. the instructions are fully analysed.
141 fun run_debugger(mainmodule: MModule, arguments: Array[String])
142 do
143 var time0 = get_time
144 self.toolcontext.info("*** START INTERPRETING ***", 1)
145
146 var interpreter = new Debugger(self, mainmodule, arguments)
147
148 interpreter.start(mainmodule)
149
150 var time1 = get_time
151 self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
152 end
153
154 fun run_debugger_autorun(mainmodule: MModule, arguments: Array[String])
155 do
156 var time0 = get_time
157 self.toolcontext.info("*** START INTERPRETING ***", 1)
158
159 var interpreter = new Debugger(self, mainmodule, arguments)
160 interpreter.autocontinue = true
161
162 interpreter.start(mainmodule)
163
164 var time1 = get_time
165 self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2)
166 end
167 end
168
169 # Contains all the informations of a Breakpoint for the Debugger
170 class Breakpoint
171
172 # Line to break on
173 var line: Int
174
175 # File concerned by the breakpoint
176 var file: String
177
178 redef init do
179 if not file.has_suffix(".nit") then file += ".nit"
180 end
181 end
182
183 # The class extending `NaiveInterpreter` by adding debugging methods
184 class Debugger
185 super NaiveInterpreter
186
187 # Keeps the frame count in memory to find when to stop
188 # and launch the command prompt after a step out call
189 var step_stack_count = 1
190
191 # Triggers a step over an instruction in a nit program
192 var stop_after_step_over_trigger = true
193
194 # Triggers a step out of an instruction
195 var stop_after_step_out_trigger= false
196
197 # Triggers a step in a instruction (enters a function
198 # if the instruction is a function call)
199 var step_in_trigger = false
200
201 # HashMap containing the breakpoints bound to a file
202 var breakpoints = new HashMap[String, HashSet[Breakpoint]]
203
204 # Contains the current file
205 var curr_file = ""
206
207 # Aliases hashmap (maps an alias to a variable name)
208 var aliases = new HashMap[String, String]
209
210 # Set containing all the traced variables and their related frame
211 private var traces = new HashSet[TraceObject]
212
213 # Map containing all the positions for the positions of the arguments traced
214 # In a function call
215 private var fun_call_arguments_positions = new HashMap[Int, TraceObject]
216
217 # Triggers the remapping of a trace object in the local context after a function call
218 var aftermath = false
219
220 # Used to prevent the case when the body of the function called is empty
221 # If it is not, then, the remapping won't be happening
222 var frame_count_aftermath = 1
223
224 # Auto continues the execution until the end or until an error is encountered
225 var autocontinue = false
226
227 #######################################################################
228 ## Execution of statement function ##
229 #######################################################################
230
231 # Main loop, every call to a debug command is done here
232 redef fun stmt(n: nullable AExpr)
233 do
234 if n == null then return
235
236 var frame = self.frame
237 var old = frame.current_node
238 frame.current_node = n
239
240 if sys.stdin.poll_in then process_debug_command(gets)
241
242 if not self.autocontinue then
243 if not n isa ABlockExpr then
244 steps_fun_call(n)
245
246 breakpoint_check(n)
247
248 check_funcall_and_traced_args(n)
249
250 remap(n)
251
252 check_if_vars_are_traced(n)
253 end
254 end
255
256 n.stmt(self)
257 frame.current_node = old
258 end
259
260 # Does the same as an usual send, except it will modify the call chain on the first call when injecting code at Runtime using the debugger.
261 # Instead of creating a pristine Frame, it will copy the actual values of the frame, and re-inject them after execution in the current context.
262 fun rt_send(mproperty: MMethod, args: Array[Instance]): nullable Instance
263 do
264 var recv = args.first
265 var mtype = recv.mtype
266 var ret = send_commons(mproperty, args, mtype)
267 if ret != null then return ret
268 var propdef = mproperty.lookup_first_definition(self.mainmodule, mtype)
269 return self.rt_call(propdef, args)
270 end
271
272 # Same as a regular call but for a runtime injected module
273 fun rt_call(mpropdef: MMethodDef, args: Array[Instance]): nullable Instance
274 do
275 if self.modelbuilder.toolcontext.opt_discover_call_trace.value and not self.discover_call_trace.has(mpropdef) then
276 self.discover_call_trace.add mpropdef
277 self.debug("Discovered {mpropdef}")
278 end
279 assert args.length == mpropdef.msignature.arity + 1 else debug("Invalid arity for {mpropdef}. {args.length} arguments given.")
280
281 # Look for the AST node that implements the property
282 var mproperty = mpropdef.mproperty
283 if self.modelbuilder.mpropdef2npropdef.has_key(mpropdef) then
284 var npropdef = self.modelbuilder.mpropdef2npropdef[mpropdef]
285 self.parameter_check(npropdef, mpropdef, args)
286 if npropdef isa AMethPropdef then
287 return npropdef.rt_call(self, mpropdef, args)
288 else
289 print "Error, invalid propdef to call at runtime !"
290 return null
291 end
292 else if mproperty.is_root_init then
293 var nclassdef = self.modelbuilder.mclassdef2nclassdef[mpropdef.mclassdef]
294 self.parameter_check(nclassdef, mpropdef, args)
295 return nclassdef.call(self, mpropdef, args)
296 else
297 fatal("Fatal Error: method {mpropdef} not found in the AST")
298 abort
299 end
300 end
301
302 # Evaluates dynamically a snippet of Nit code
303 # `nit_code` : Nit code to be executed
304 fun eval(nit_code: String)
305 do
306 var local_toolctx = modelbuilder.toolcontext
307 local_toolctx.dbg = self
308 var e = local_toolctx.parse_something(nit_code)
309 if e isa ABlockExpr then
310 nit_code = "module rt_module\n" + nit_code
311 e = local_toolctx.parse_something(nit_code)
312 end
313 if e isa AExpr then
314 nit_code = "module rt_module\nprint " + nit_code
315 e = local_toolctx.parse_something(nit_code)
316 end
317 if e isa AModule then
318 local_toolctx.had_error = false
319 modelbuilder.load_rt_module(self.mainmodule, e, "rt_module")
320 local_toolctx.run_phases([e])
321 if local_toolctx.had_error then
322 modelbuilder.model.try_remove_module(e.mmodule.as(not null))
323 local_toolctx.dbg = null
324 return
325 end
326 var mmod = e.mmodule
327 if mmod != null then
328 self.mainmodule = mmod
329 var sys_type = mmod.sys_type
330 if sys_type == null then
331 print "Fatal error, cannot find Class Sys !\nAborting"
332 abort
333 end
334 var mobj = new MutableInstance(sys_type)
335 init_instance(mobj)
336 var initprop = mmod.try_get_primitive_method("init", sys_type.mclass)
337 if initprop != null then
338 self.send(initprop, [mobj])
339 end
340 var mainprop = mmod.try_get_primitive_method("run", sys_type.mclass) or else
341 mmod.try_get_primitive_method("main", sys_type.mclass)
342 if mainprop != null then
343 self.rt_send(mainprop, [mobj])
344 end
345 else
346 print "Error while loading_rt_module"
347 end
348 else
349 print "Error when parsing, e = {e.class_name}"
350 end
351 local_toolctx.dbg = null
352 end
353
354 # Encpasulates the behaviour for step over/out
355 private fun steps_fun_call(n: AExpr)
356 do
357 if self.stop_after_step_over_trigger then
358 if self.frames.length <= self.step_stack_count then
359 n.debug("Execute stmt {n.to_s}")
360 while read_cmd do end
361 end
362 else if self.stop_after_step_out_trigger then
363 if frames.length < self.step_stack_count then
364 n.debug("Execute stmt {n.to_s}")
365 while read_cmd do end
366 end
367 else if step_in_trigger then
368 n.debug("Execute stmt {n.to_s}")
369 while read_cmd do end
370 end
371 end
372
373 # Checks if a breakpoint is encountered, and launches the debugging prompt if true
374 private fun breakpoint_check(n: AExpr)
375 do
376 var currFileNameSplit = self.frame.current_node.location.file.filename.to_s.split_with("/")
377
378 self.curr_file = currFileNameSplit[currFileNameSplit.length-1]
379
380 var breakpoint = find_breakpoint(curr_file, n.location.line_start)
381
382 if breakpoints.keys.has(curr_file) and breakpoint != null then
383 n.debug("Execute stmt {n.to_s}")
384 while read_cmd do end
385 end
386 end
387
388 # Check if a variable of current expression is traced
389 # Then prints and/or breaks for command prompt
390 private fun check_if_vars_are_traced(n: AExpr)
391 do
392 var identifiers_in_instruction = get_identifiers_in_current_instruction(n.location.text)
393
394 for i in identifiers_in_instruction do
395 for j in self.traces do
396 if j.is_variable_traced_in_frame(i, frame) then
397 n.debug("Traced variable {i} used")
398 if j.break_on_encounter then while read_cmd do end
399 break
400 end
401 end
402 end
403 end
404
405 # Function remapping all the traced objects to match their name in the local context
406 private fun remap(n: AExpr)
407 do
408 if self.aftermath then
409
410 # Trace every argument variable pre-specified
411 if self.frame_count_aftermath < frames.length and fun_call_arguments_positions.length > 0 then
412
413 var ids_in_fun_def = get_identifiers_in_current_instruction(get_function_arguments(frame.mpropdef.location.text))
414
415 for i in fun_call_arguments_positions.keys do
416 self.fun_call_arguments_positions[i].add_frame_variable(frame, ids_in_fun_def[i])
417 end
418 end
419
420 self.aftermath = false
421 end
422 end
423
424 # If the current instruction is a function call
425 # We analyse its signature and the position of traced arguments if the call
426 # For future remapping when inside the function
427 private fun check_funcall_and_traced_args(n: AExpr) do
428 # If we have a function call, we need to see if any of the arguments is traced (including the caller)
429 # if it is, next time we face an instruction, we'll trace the local version on the traced variable in the next frame
430 if n isa ACallExpr then
431 self.aftermath = true
432 self.frame_count_aftermath = frames.length
433 fun_call_arguments_positions.clear
434 var fun_arguments = get_identifiers_in_current_instruction(get_function_arguments(n.location.text))
435
436 for i in self.traces do
437 for j in [0 .. fun_arguments.length - 1] do
438 if i.is_variable_traced_in_frame(fun_arguments[j],frame) then
439 fun_call_arguments_positions[j] = i
440 end
441 end
442 end
443 end
444 end
445
446 #######################################################################
447 ## Processing commands functions ##
448 #######################################################################
449
450 fun read_cmd: Bool
451 do
452 printn "> "
453 return process_debug_command(gets)
454 end
455
456 # Takes a user command as a parameter
457 #
458 # Returns a boolean value, representing whether or not to
459 # continue reading commands from the console input
460 fun process_debug_command(command: String): Bool
461 do
462 # Step-out command
463 if command == "finish"
464 then
465 return step_out
466 # Step-in command
467 else if command == "s"
468 then
469 return step_in
470 # Step-over command
471 else if command == "n" then
472 return step_over
473 # Shows help
474 else if command == "help" then
475 help
476 return true
477 # Opens a new NitIndex prompt on current model
478 else if command == "nitx" then
479 new NitIndex.with_infos(modelbuilder, self.mainmodule).prompt
480 return true
481 else if command == "bt" or command == "backtrack" then
482 print stack_trace
483 return true
484 # Continues execution until the end
485 else if command == "c" then
486 return continue_exec
487 else if command == "nit" then
488 printn "$~> "
489 command = gets
490 var nit_buf = new FlatBuffer
491 while not command == ":q" do
492 nit_buf.append(command)
493 nit_buf.append("\n")
494 printn "$~> "
495 command = gets
496 end
497 step_in
498 eval(nit_buf.to_s)
499 else if command == "quit" then
500 exit(0)
501 else if command == "abort" then
502 print stack_trace
503 exit(0)
504 else
505 var parts = command.split_with(' ')
506 var cname = parts.first
507 # Shows the value of a variable in the current frame
508 if cname == "p" or cname == "print" then
509 print_command(parts)
510 # Places a breakpoint on line x of file y
511 else if cname == "break" or cname == "b" then
512 process_place_break_fun(parts)
513 # Removes a breakpoint on line x of file y
514 else if cname == "d" or cname == "delete" then
515 process_remove_break_fun(parts)
516 # Sets an alias for a variable
517 else if parts.length == 2 and parts[1] == "as" then
518 process_alias(parts)
519 # Modifies the value of a variable in the current frame
520 else if parts.length == 3 and parts[1] == "=" then
521 process_mod_function(parts)
522 # Traces the modifications on a variable
523 else if cname == "trace" then
524 process_trace_command(parts)
525 # Untraces the modifications on a variable
526 else if cname == "untrace" then
527 process_untrace_command(parts)
528 else
529 bad_command(command)
530 end
531 end
532 return true
533 end
534
535 # Produces help for the commands of the debugger
536 fun help do
537 print ""
538 print "Help :"
539 print "-----------------------------------"
540 print ""
541 print "Variables"
542 print " * Modification: var_name = value (Warning: var_name must be primitive)"
543 print " * Alias: var_name as alias"
544 print ""
545 print "Printing"
546 print " * Variables: p(rint) var_name (Use * to print all local variables)"
547 print " * Collections: p(rint) var_name '[' start_index (.. end_index) ']'"
548 print ""
549 print "Breakpoints"
550 print " * File/line: b(reak) file_name line_number"
551 print " * Remove: d(elete) id"
552 print ""
553 print "Tracepoints"
554 print " * Variable: trace var_name break/print"
555 print " * Untrace variable: untrace var_name"
556 print ""
557 print "Flow control"
558 print " * Next instruction (same-level): n"
559 print " * Next instruction: s"
560 print " * Finish current method: finish"
561 print " * Continue until next breakpoint or end: c"
562 print ""
563 print "General commands"
564 print " * quit: Quits the debugger"
565 print " * abort: Aborts the interpretation, prints the stack trace before leaving"
566 print " * nitx: Ask questions to the model about its entities (classes, methods, etc.)"
567 print " * nit: Inject dynamic code for interpretation"
568 print ""
569 end
570
571 #######################################################################
572 ## Processing specific command functions ##
573 #######################################################################
574
575 # Sets the flags to step-over an instruction in the current file
576 fun step_over: Bool
577 do
578 self.step_stack_count = frames.length
579 self.stop_after_step_over_trigger = true
580 self.stop_after_step_out_trigger = false
581 self.step_in_trigger = false
582 return false
583 end
584
585 #Sets the flags to step-out of a function
586 fun step_out: Bool
587 do
588 self.stop_after_step_over_trigger = false
589 self.stop_after_step_out_trigger = true
590 self.step_in_trigger = false
591 self.step_stack_count = frames.length
592 return false
593 end
594
595 # Sets the flags to step-in an instruction
596 fun step_in: Bool
597 do
598 self.step_in_trigger = true
599 self.stop_after_step_over_trigger = false
600 self.stop_after_step_out_trigger = false
601 return false
602 end
603
604 # Sets the flags to continue execution
605 fun continue_exec: Bool
606 do
607 self.stop_after_step_over_trigger = false
608 self.stop_after_step_out_trigger = false
609 self.step_in_trigger = false
610 return false
611 end
612
613 fun bad_command(cmd: String) do
614 print "Unrecognized command {cmd}. Use 'help' to show help."
615 end
616
617 # Prints the demanded variable in the command
618 #
619 # The name of the variable in in position 1 of the array 'parts_of_command'
620 fun print_command(parts: Array[String])
621 do
622 if parts.length != 2 then
623 bad_command(parts.join(" "))
624 return
625 end
626 if parts[1] == "*" then
627 var map_of_instances = frame.map
628
629 var self_var = seek_variable("self", frame)
630 print "self: {self_var.to_s}"
631
632 for instance in map_of_instances.keys do
633 print "{instance.to_s}: {map_of_instances[instance].to_s}"
634 end
635 else if parts[1].chars.has('[') and parts[1].chars.has(']') then
636 process_array_command(parts)
637 else
638 var instance = seek_variable(get_real_variable_name(parts[1]), frame)
639
640 if instance != null
641 then
642 print_instance(instance)
643 else
644 print "Cannot find variable {parts[1]}"
645 end
646 end
647 end
648
649 # Process the input command to set an alias for a variable
650 fun process_alias(parts: Array[String]) do
651 if parts.length != 3 then
652 bad_command(parts.join(" "))
653 return
654 end
655 add_alias(parts.first, parts.last)
656 end
657
658 # Processes the input string to know where to put a breakpoint
659 fun process_place_break_fun(parts: Array[String])
660 do
661 if parts.length != 3 then
662 bad_command(parts.join(" "))
663 return
664 end
665 var bp = get_breakpoint_from_command(parts)
666 if bp != null then
667 place_breakpoint(bp)
668 end
669 end
670
671 # Returns a breakpoint containing the informations stored in the command
672 fun get_breakpoint_from_command(parts: Array[String]): nullable Breakpoint
673 do
674 if parts[1].is_numeric then
675 return new Breakpoint(parts[1].to_i, curr_file)
676 else if parts.length >= 3 and parts[2].is_numeric then
677 return new Breakpoint(parts[2].to_i, parts[1])
678 else
679 return null
680 end
681 end
682
683 # Processes the command of removing a breakpoint on specified line and file
684 fun process_remove_break_fun(parts: Array[String])
685 do
686 if parts.length != 2 then
687 bad_command(parts.join(" "))
688 return
689 end
690 if parts[1].is_numeric then
691 remove_breakpoint(self.curr_file, parts[1].to_i)
692 else if parts.length >= 3 and parts[2].is_numeric then
693 remove_breakpoint(parts[1], parts[2].to_i)
694 end
695 end
696
697 # Processes an array print command
698 fun process_array_command(parts: Array[String])
699 do
700 var index_of_first_brace = parts[1].chars.index_of('[')
701 var variable_name = get_real_variable_name(parts[1].substring(0,index_of_first_brace))
702 var braces = parts[1].substring_from(index_of_first_brace)
703
704 var indexes = remove_braces(braces)
705
706 var index_array = new Array[Array[Int]]
707
708 if indexes != null then
709 for index in indexes do
710 var temp_indexes_array = process_index(index)
711 if temp_indexes_array != null then
712 index_array.push(temp_indexes_array)
713 #print index_array.last
714 end
715 end
716 end
717
718 var instance = seek_variable(variable_name, frame)
719
720 if instance != null then
721 print_nested_collection(instance, index_array, 0, variable_name, "")
722 else
723 print "Cannot find variable {variable_name}"
724 end
725 end
726
727 # Processes the modification function to modify a variable dynamically
728 #
729 # Command of type variable = value
730 fun process_mod_function(parts: Array[String])
731 do
732 if parts.length != 3 then
733 bad_command(parts.join(" "))
734 return
735 end
736 var p0 = parts[0]
737 p0 = get_real_variable_name(p0)
738 var parts_of_variable = p0.split_with(".")
739
740 if parts_of_variable.length > 1 then
741 var last_part = parts_of_variable.pop
742 var first_part = p0.substring(0,p0.length - last_part.length - 1)
743 var papa = seek_variable(first_part, frame)
744
745 if papa != null and papa isa MutableInstance then
746 var attribute = get_attribute_in_mutable_instance(papa, last_part)
747
748 if attribute != null then
749 modify_argument_of_complex_type(papa, attribute, parts[2])
750 end
751 end
752 else
753 var target = seek_variable(parts_of_variable[0], frame)
754 if target != null then
755 modify_in_frame(target, parts[2])
756 end
757 end
758 end
759
760 # Processes the untrace variable command
761 #
762 # Command pattern : "untrace variable"
763 fun process_untrace_command(parts: Array[String])
764 do
765 if parts.length != 2 then
766 bad_command(parts.join(" "))
767 return
768 end
769 var variable_name = get_real_variable_name(parts[1])
770 if untrace_variable(variable_name) then
771 print "Untraced variable {parts[1]}"
772 else
773 print "{parts[1]} is not traced"
774 end
775 end
776
777 # Processes the trace variable command
778 #
779 # Command pattern : "trace variable [break/print]"
780 fun process_trace_command(parts: Array[String])
781 do
782 if parts.length != 3 then
783 bad_command(parts.join(" "))
784 return
785 end
786 var variable_name = get_real_variable_name(parts[1])
787 var breaker:Bool
788
789 if seek_variable(variable_name, frame) == null then
790 print "Cannot find a variable called {parts[1]}"
791 return
792 end
793
794 if parts[2] == "break" then
795 breaker = true
796 else
797 breaker = false
798 end
799
800 trace_variable(variable_name, breaker)
801
802 print "Successfully tracing {parts[1]}"
803 end
804
805 #######################################################################
806 ## Trace Management functions ##
807 #######################################################################
808
809 # Effectively untraces the variable called *variable_name*
810 #
811 # Returns true if the variable exists, false otherwise
812 private fun untrace_variable(variable_name: String): Bool
813 do
814 var to_remove: nullable TraceObject = null
815 for i in self.traces do
816 if i.is_variable_traced_in_frame(variable_name, frame) then
817 to_remove = i
818 end
819 end
820
821 if to_remove != null then
822 self.traces.remove(to_remove)
823 return true
824 else
825 return false
826 end
827 end
828
829 # Effectively traces the variable *variable_name* either in print or break mode depending on the value of breaker (break if true, print if false)
830 #
831 private fun trace_variable(variable_name: String, breaker: Bool)
832 do
833 for i in self.traces do
834 if i.is_variable_traced_in_frame(variable_name, frame) then
835 print "This variable is already traced"
836 return
837 end
838 end
839
840 var trace_object: TraceObject
841
842 if breaker then
843 trace_object = new TraceObject(true)
844 else
845 trace_object = new TraceObject(false)
846 end
847
848 # We trace the current variable found for the current frame
849 trace_object.add_frame_variable(self.frame, variable_name)
850
851 var position_of_variable_in_arguments = get_position_of_variable_in_arguments(frame, variable_name)
852
853 # Start parsing the frames starting with the parent of the current one, until the highest
854 # When the variable traced is declared locally, the loop stops
855 for i in [1 .. frames.length-1] do
856
857 # If the variable was reported to be an argument of the previous frame
858 if position_of_variable_in_arguments != -1 then
859
860 var local_name = get_identifiers_in_current_instruction(get_function_arguments(frames[i].current_node.location.text))[position_of_variable_in_arguments]
861
862 position_of_variable_in_arguments = get_position_of_variable_in_arguments(frames[i], local_name)
863
864 trace_object.add_frame_variable(frames[i], local_name)
865 else
866 break
867 end
868 end
869
870 self.traces.add(trace_object)
871 end
872
873 # If the variable *variable_name* is an argument of the function being executed in the frame *frame*
874 # The function returns its position in the arguments
875 # Else, it returns -1
876 private fun get_position_of_variable_in_arguments(frame: Frame, variable_name: String): Int
877 do
878 var identifiers = get_identifiers_in_current_instruction(get_function_arguments(frame.mpropdef.location.text))
879 for i in [0 .. identifiers.length-1] do
880 # If the current traced variable is an argument of the current function, we trace its parent (at least)
881 if identifiers[i] == variable_name then return i
882 end
883 return -1
884 end
885
886 # Gets all the identifiers of an instruction (uses the rules of Nit as of Mar 05 2013)
887 #
888 fun get_identifiers_in_current_instruction(instruction: Text): Array[String]
889 do
890 var result_array = new Array[String]
891 var instruction_buffer = new FlatBuffer
892
893 var trigger_char_escape = false
894 var trigger_string_escape = false
895
896 for i in instruction.chars do
897 if trigger_char_escape then
898 if i == '\'' then trigger_char_escape = false
899 else if trigger_string_escape then
900 if i == '{' then
901 trigger_string_escape = false
902 else if i == '\"' then trigger_string_escape = false
903 else
904 if i.is_alphanumeric or i == '_' then
905 instruction_buffer.add(i)
906 else if i == '.' then
907 if instruction_buffer.is_numeric or (instruction_buffer.chars[0] >= 'A' and instruction_buffer.chars[0] <= 'Z') then
908 instruction_buffer.clear
909 else
910 result_array.push(instruction_buffer.to_s)
911 instruction_buffer.add(i)
912 end
913 else if i == '\'' then
914 trigger_char_escape = true
915 else if i == '\"' then
916 trigger_string_escape = true
917 else if i == '}' then
918 trigger_string_escape = true
919 else
920 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)
921 instruction_buffer.clear
922 end
923 end
924 end
925
926 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)
927
928 return result_array
929 end
930
931 # Takes a function call or declaration and strips all but the arguments
932 #
933 fun get_function_arguments(function: Text): String
934 do
935 var buf = new FlatBuffer
936 var trigger_copy = false
937
938 for i in function.chars do
939 if i == ')' then break
940 if trigger_copy then buf.add(i)
941 if i == '(' then trigger_copy = true
942 end
943
944 return buf.to_s
945 end
946
947 #######################################################################
948 ## Alias management functions ##
949 #######################################################################
950
951 # Adds a new alias to the tables
952 fun add_alias(var_represented: String, alias: String)
953 do
954 self.aliases[alias] = var_represented
955 end
956
957 # Gets the real name of a variable hidden by an alias
958 fun get_variable_name_by_alias(alias: String): nullable String
959 do
960 if self.aliases.keys.has(alias) then
961 return self.aliases[alias]
962 end
963
964 return null
965 end
966
967 # Gets the variable named by name, whether it is an alias or not
968 fun get_real_variable_name(name: String): String
969 do
970 var explode_string = name.split_with(".")
971 var final_string = new FlatBuffer
972 for i in explode_string do
973 var alias_resolved = get_variable_name_by_alias(i)
974 if alias_resolved != null then
975 final_string.append(get_real_variable_name(alias_resolved))
976 else
977 final_string.append(i)
978 end
979 final_string.append(".")
980 end
981
982 return final_string.substring(0,final_string.length-1).to_s
983 end
984
985 #######################################################################
986 ## Print functions ##
987 #######################################################################
988
989 # Prints an object instance and its attributes if it has some
990 #
991 # If it is a primitive type, its value is directly printed
992 fun print_instance(instance: Instance)
993 do
994 if instance isa MutableInstance then
995 print "\{"
996 print "\ttype : {instance},"
997
998 printn("\t")
999
1000 print instance.attributes.join(",\n\t"," : ")
1001
1002 print "\}"
1003 else
1004 print "{instance}"
1005 end
1006 end
1007
1008 # Prints the attributes demanded in a SequenceRead
1009 # Used recursively to print nested collections
1010 fun print_nested_collection(instance: Instance, indexes: Array[Array[Int]], depth: Int, variable_name: String, depth_string: String)
1011 do
1012 var collection: nullable SequenceRead[Object] = null
1013 var real_collection_length: nullable Int = null
1014
1015 if instance isa MutableInstance then
1016 real_collection_length = get_collection_instance_real_length(instance)
1017 collection = get_primary_collection(instance)
1018 end
1019
1020 if collection != null and real_collection_length != null then
1021 for i in indexes[depth] do
1022 if i >= 0 and i < real_collection_length then
1023 if depth == indexes.length-1 then
1024 print "{variable_name}{depth_string}[{i}] = {collection[i]}"
1025 else
1026 var item_i = collection[i]
1027
1028 if item_i isa MutableInstance then
1029 print_nested_collection(item_i, indexes, depth+1, variable_name, depth_string+"[{i}]")
1030 else
1031 print "The item at {variable_name}{depth_string}[{i}] is not a collection"
1032 print item_i
1033 end
1034 end
1035 else
1036 print "Out of bounds exception : i = {i} and collection_length = {real_collection_length.to_s}"
1037
1038 if i < 0 then
1039 continue
1040 else if i >= real_collection_length then
1041 break
1042 end
1043 end
1044 end
1045 else
1046 if collection == null then
1047 print "Cannot find {variable_name}{depth_string}"
1048 else if real_collection_length != null then
1049 print "Cannot find attribute length in {instance}"
1050 else
1051 print "Unknown error."
1052 abort
1053 end
1054 end
1055 end
1056
1057 #######################################################################
1058 ## Variable Exploration functions ##
1059 #######################################################################
1060
1061 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
1062 private fun seek_variable(variable_path: String, frame: Frame): nullable Instance
1063 do
1064 var full_variable = variable_path.split_with(".")
1065
1066 var full_variable_iterator = full_variable.iterator
1067
1068 var first_instance = get_variable_in_frame(full_variable_iterator.item, frame)
1069
1070 if first_instance == null then return null
1071
1072 if full_variable.length <= 1 then return first_instance
1073
1074 full_variable_iterator.next
1075
1076 if not (first_instance isa MutableInstance and full_variable_iterator.is_ok) then return null
1077
1078 return get_variable_in_mutable_instance(first_instance, full_variable_iterator)
1079 end
1080
1081 # Gets a variable 'variable_name' contained in the frame 'frame'
1082 private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance
1083 do
1084 if variable_name == "self" then
1085 if frame.arguments.length >= 1 then return frame.arguments.first
1086 end
1087
1088 var map_of_instances = frame.map
1089
1090 for key in map_of_instances.keys do
1091 if key.to_s == variable_name then
1092 return map_of_instances[key]
1093 end
1094 end
1095
1096 return null
1097 end
1098
1099 # Gets an attribute 'attribute_name' contained in variable 'variable'
1100 fun get_attribute_in_mutable_instance(variable: MutableInstance, attribute_name: String): nullable MAttribute
1101 do
1102 var map_of_attributes = variable.attributes
1103
1104 for key in map_of_attributes.keys do
1105 if key.to_s.substring_from(1) == attribute_name then
1106 return key
1107 end
1108 end
1109
1110 return null
1111 end
1112
1113 # Recursive function, returns the variable described by 'total_chain'
1114 fun get_variable_in_mutable_instance(variable: MutableInstance, iterator: Iterator[String]): nullable Instance
1115 do
1116 var attribute = get_attribute_in_mutable_instance(variable, iterator.item)
1117
1118 if attribute == null then return null
1119
1120 iterator.next
1121
1122 if iterator.is_ok then
1123 var new_variable = variable.attributes[attribute]
1124 if new_variable isa MutableInstance then
1125 return get_variable_in_mutable_instance(new_variable, iterator)
1126 else
1127 return null
1128 end
1129 else
1130 return variable.attributes[attribute]
1131 end
1132 end
1133
1134 #######################################################################
1135 ## Array exploring functions ##
1136 #######################################################################
1137
1138 # Gets the length of a collection
1139 # Used by the debugger, else if we call Collection.length, it returns the capacity instead
1140 fun get_collection_instance_real_length(collection: MutableInstance): nullable Int
1141 do
1142 var collection_length_attribute = get_attribute_in_mutable_instance(collection, "length")
1143
1144 if collection_length_attribute != null then
1145 var primitive_length_instance = collection.attributes[collection_length_attribute]
1146 if primitive_length_instance isa PrimitiveInstance[Int] then
1147 return primitive_length_instance.val
1148 end
1149 end
1150
1151 return null
1152 end
1153
1154 # Processes the indexes of a print array call
1155 # Returns an array containing all the indexes demanded
1156 fun process_index(index_string: String): nullable Array[Int]
1157 do
1158 var from_end_index = index_string.chars.index_of('.')
1159 var to_start_index = index_string.chars.last_index_of('.')
1160
1161 if from_end_index != -1 and to_start_index != -1 then
1162 var index_from_string = index_string.substring(0,from_end_index)
1163 var index_to_string = index_string.substring_from(to_start_index+1)
1164
1165 if index_from_string.is_numeric and index_to_string.is_numeric then
1166 var result_array = new Array[Int]
1167
1168 var index_from = index_from_string.to_i
1169 var index_to = index_to_string.to_i
1170
1171 for i in [index_from..index_to] do
1172 result_array.push(i)
1173 end
1174
1175 return result_array
1176 end
1177 else
1178 if index_string.is_numeric
1179 then
1180 var result_array = new Array[Int]
1181
1182 result_array.push(index_string.to_i)
1183
1184 return result_array
1185 else
1186 return null
1187 end
1188 end
1189
1190 return null
1191 end
1192
1193 # Gets a collection in a MutableInstance
1194 fun get_primary_collection(container: MutableInstance): nullable SequenceRead[Object]
1195 do
1196 var items_of_array = get_attribute_in_mutable_instance(container, "items")
1197 if items_of_array != null then
1198 var array = container.attributes[items_of_array]
1199
1200 if array isa PrimitiveInstance[Object] then
1201 var sequenceRead_final = array.val
1202 if sequenceRead_final isa SequenceRead[Object] then
1203 return sequenceRead_final
1204 end
1205 end
1206 end
1207
1208 return null
1209 end
1210
1211 # Removes the braces '[' ']' in a print array command
1212 # Returns an array containing their content
1213 fun remove_braces(braces: String): nullable Array[String]
1214 do
1215 var buffer = new FlatBuffer
1216
1217 var result_array = new Array[String]
1218
1219 var number_of_opening_brackets = 0
1220 var number_of_closing_brackets = 0
1221
1222 var last_was_opening_bracket = false
1223
1224 for i in braces.chars do
1225 if i == '[' then
1226 if last_was_opening_bracket then
1227 return null
1228 end
1229
1230 number_of_opening_brackets += 1
1231 last_was_opening_bracket = true
1232 else if i == ']' then
1233 if not last_was_opening_bracket then
1234 return null
1235 end
1236
1237 result_array.push(buffer.to_s)
1238 buffer.clear
1239 number_of_closing_brackets += 1
1240 last_was_opening_bracket = false
1241 else if i.is_numeric or i == '.' then
1242 buffer.append(i.to_s)
1243 else if not i == ' ' then
1244 return null
1245 end
1246 end
1247
1248 if number_of_opening_brackets != number_of_closing_brackets then
1249 return null
1250 end
1251
1252 return result_array
1253 end
1254
1255 #######################################################################
1256 ## Breakpoint placing functions ##
1257 #######################################################################
1258
1259 # Places a breakpoint on line 'line_to_break' for file 'file_to_break'
1260 fun place_breakpoint(breakpoint: Breakpoint)
1261 do
1262 if not self.breakpoints.keys.has(breakpoint.file) then
1263 self.breakpoints[breakpoint.file] = new HashSet[Breakpoint]
1264 end
1265 if find_breakpoint(breakpoint.file, breakpoint.line) == null then
1266 self.breakpoints[breakpoint.file].add(breakpoint)
1267 print "Breakpoint added on line {breakpoint.line} for file {breakpoint.file}"
1268 else
1269 print "Breakpoint already present on line {breakpoint.line} for file {breakpoint.file}"
1270 end
1271 end
1272
1273 #######################################################################
1274 ## Breakpoint removing functions ##
1275 #######################################################################
1276
1277 # Removes a breakpoint on line 'line_to_break' for file 'file_to_break'
1278 fun remove_breakpoint(file_to_break: String, line_to_break: Int)
1279 do
1280 if self.breakpoints.keys.has(file_to_break) then
1281 var bp = find_breakpoint(file_to_break, line_to_break)
1282
1283 if bp != null then
1284 self.breakpoints[file_to_break].remove(bp)
1285 print "Breakpoint removed on line {line_to_break} for file {file_to_break}"
1286 return
1287 end
1288 end
1289
1290 print "No breakpoint existing on line {line_to_break} for file {file_to_break}"
1291 end
1292
1293 #######################################################################
1294 ## Breakpoint searching functions ##
1295 #######################################################################
1296
1297 # Finds a breakpoint for 'file' and 'line' in the class HashMap
1298 fun find_breakpoint(file: String, line: Int): nullable Breakpoint
1299 do
1300 if self.breakpoints.keys.has(file)
1301 then
1302 for i in self.breakpoints[file]
1303 do
1304 if i.line == line
1305 then
1306 return i
1307 end
1308 end
1309 end
1310
1311 return null
1312 end
1313
1314 #######################################################################
1315 ## Runtime modification functions ##
1316 #######################################################################
1317
1318 # Modifies the value of a variable contained in a frame
1319 fun modify_in_frame(variable: Instance, value: String)
1320 do
1321 var new_variable = get_variable_of_type_with_value(variable.mtype.to_s, value)
1322 if new_variable != null
1323 then
1324 var keys = frame.map.keys
1325 for key in keys
1326 do
1327 if frame.map[key] == variable
1328 then
1329 frame.map[key] = new_variable
1330 end
1331 end
1332 end
1333 end
1334
1335 # Modifies the value of a variable contained in a MutableInstance
1336 fun modify_argument_of_complex_type(papa: MutableInstance, attribute: MAttribute, value: String)
1337 do
1338 var final_variable = papa.attributes[attribute]
1339 var type_of_variable = final_variable.mtype.to_s
1340 var new_variable = get_variable_of_type_with_value(type_of_variable, value)
1341 if new_variable != null
1342 then
1343 papa.attributes[attribute] = new_variable
1344 end
1345 end
1346
1347 #######################################################################
1348 ## Variable generator functions ##
1349 #######################################################################
1350
1351 # Returns a new variable of the type 'type_of_variable' with a value of 'value'
1352 fun get_variable_of_type_with_value(type_of_variable: String, value: String): nullable Instance
1353 do
1354 if type_of_variable == "Int" then
1355 return get_int(value)
1356 else if type_of_variable == "Float" then
1357 return get_float(value)
1358 else if type_of_variable == "Bool" then
1359 return get_bool(value)
1360 else if type_of_variable == "Char" then
1361 return get_char(value)
1362 end
1363
1364 return null
1365 end
1366
1367 # Returns a new int instance with value 'value'
1368 fun get_int(value: String): nullable Instance
1369 do
1370 if value.is_numeric then
1371 return int_instance(value.to_i)
1372 else
1373 return null
1374 end
1375 end
1376
1377 # Returns a new float instance with value 'value'
1378 fun get_float(value:String): nullable Instance
1379 do
1380 if value.is_numeric then
1381 return float_instance(value.to_f)
1382 else
1383 return null
1384 end
1385 end
1386
1387 # Returns a new char instance with value 'value'
1388 fun get_char(value: String): nullable Instance
1389 do
1390 if value.length >= 1 then
1391 return char_instance(value.chars[0])
1392 else
1393 return null
1394 end
1395 end
1396
1397 # Returns a new bool instance with value 'value'
1398 fun get_bool(value: String): nullable Instance
1399 do
1400 if value.to_lower == "true" then
1401 return self.true_instance
1402 else if value.to_lower == "false" then
1403 return self.false_instance
1404 else
1405 print "Invalid value, a boolean must be set at \"true\" or \"false\""
1406 return null
1407 end
1408 end
1409
1410 end
1411
1412 redef class AMethPropdef
1413
1414 # Same as call except it will copy local variables of the parent frame to the frame defined in this call.
1415 # Not supposed to be used by anyone else than the Debugger.
1416 private fun rt_call(v: Debugger, mpropdef: MMethodDef, args: Array[Instance]): nullable Instance
1417 do
1418 var f = new Frame(self, self.mpropdef.as(not null), args)
1419 var curr_instances = v.frame.map
1420 for i in curr_instances.keys do
1421 f.map[i] = curr_instances[i]
1422 end
1423 call_commons(v,mpropdef,args,f)
1424 var currFra = v.frames.shift
1425 for i in curr_instances.keys do
1426 if currFra.map.keys.has(i) then
1427 curr_instances[i] = currFra.map[i]
1428 end
1429 end
1430 if v.returnmark == f then
1431 v.returnmark = null
1432 var res = v.escapevalue
1433 v.escapevalue = null
1434 return res
1435 end
1436 return null
1437
1438 end
1439 end
1440
1441 # Traces the modifications of an object linked to a certain frame
1442 private class TraceObject
1443
1444 # Map of the local names bound to a frame
1445 var trace_map: HashMap[Frame, String]
1446 # Decides if breaking or printing statement when the variable is encountered
1447 var break_on_encounter: Bool
1448
1449 init(break_on_encounter: Bool)
1450 do
1451 trace_map = new HashMap[Frame, String]
1452 self.break_on_encounter = break_on_encounter
1453 end
1454
1455 # Adds the local alias for a variable and the frame bound to it
1456 fun add_frame_variable(frame: Frame, variable_name: String)
1457 do
1458 self.trace_map[frame] = variable_name
1459 end
1460
1461 # Checks if the prompted variable is traced in the specified frame
1462 fun is_variable_traced_in_frame(variable_name: String, frame: Frame): Bool
1463 do
1464 if self.trace_map.has_key(frame) then
1465 if self.trace_map[frame] == variable_name then
1466 return true
1467 end
1468 end
1469
1470 return false
1471 end
1472
1473 end
1474
1475 redef class ANode
1476
1477 # Breaks automatically when encountering an error
1478 # Permits the injunction of commands before crashing
1479 redef fun fatal(v: NaiveInterpreter, message: String)
1480 do
1481 if v isa Debugger then
1482 print "An error was encountered, the program will stop now."
1483 self.debug(message)
1484 while v.process_debug_command(gets) do end
1485 end
1486
1487 super
1488 end
1489 end