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