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