697f86bfcb72a88e5b3ce7b9d0ac44c960880415
[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 var self_var = seek_variable("self", frame)
651 print "self: {self_var.to_s}"
652
653 for instance in map_of_instances.keys do
654 print "{instance.to_s}: {map_of_instances[instance].to_s}"
655 end
656 else if parts_of_command[1] == "stack" then
657 print self.stack_trace
658 else if parts_of_command[1].chars.has('[') and parts_of_command[1].chars.has(']') then
659 process_array_command(parts_of_command)
660 else
661 var instance = seek_variable(get_real_variable_name(parts_of_command[1]), frame)
662
663 if instance != null
664 then
665 print_instance(instance)
666 else
667 print "Cannot find variable {parts_of_command[1]}"
668 end
669 end
670 end
671
672 # Processes the input string to know where to put a breakpoint
673 fun process_place_break_fun(parts_of_command: Array[String])
674 do
675 var bp = get_breakpoint_from_command(parts_of_command)
676 if bp != null then
677 place_breakpoint(bp)
678 end
679 end
680
681 # Returns a breakpoint containing the informations stored in the command
682 fun get_breakpoint_from_command(parts_of_command: Array[String]): nullable Breakpoint
683 do
684 if parts_of_command[1].is_numeric then
685 return new Breakpoint(parts_of_command[1].to_i, curr_file)
686 else if parts_of_command.length >= 3 and parts_of_command[2].is_numeric then
687 return new Breakpoint(parts_of_command[2].to_i, parts_of_command[1])
688 else
689 return null
690 end
691 end
692
693 # Processes the command of removing a breakpoint on specified line and file
694 fun process_remove_break_fun(parts_of_command: Array[String])
695 do
696 if parts_of_command[1].is_numeric then
697 remove_breakpoint(self.curr_file, parts_of_command[1].to_i)
698 else if parts_of_command.length >= 3 and parts_of_command[2].is_numeric then
699 remove_breakpoint(parts_of_command[1], parts_of_command[2].to_i)
700 end
701 end
702
703 # Processes an array print command
704 fun process_array_command(parts_of_command: Array[String])
705 do
706 var index_of_first_brace = parts_of_command[1].chars.index_of('[')
707 var variable_name = get_real_variable_name(parts_of_command[1].substring(0,index_of_first_brace))
708 var braces = parts_of_command[1].substring_from(index_of_first_brace)
709
710 var indexes = remove_braces(braces)
711
712 var index_array = new Array[Array[Int]]
713
714 if indexes != null then
715 for index in indexes do
716 var temp_indexes_array = process_index(index)
717 if temp_indexes_array != null then
718 index_array.push(temp_indexes_array)
719 #print index_array.last
720 end
721 end
722 end
723
724 var instance = seek_variable(variable_name, frame)
725
726 if instance != null then
727 print_nested_collection(instance, index_array, 0, variable_name, "")
728 else
729 print "Cannot find variable {variable_name}"
730 end
731 end
732
733 # Processes the modification function to modify a variable dynamically
734 #
735 # Command of type variable = value
736 fun process_mod_function(parts_of_command: Array[String])
737 do
738 parts_of_command[0] = get_real_variable_name(parts_of_command[0])
739 var parts_of_variable = parts_of_command[0].split_with(".")
740
741 if parts_of_variable.length > 1 then
742 var last_part = parts_of_variable.pop
743 var first_part = parts_of_command[0].substring(0,parts_of_command[0].length - last_part.length - 1)
744 var papa = seek_variable(first_part, frame)
745
746 if papa != null and papa isa MutableInstance then
747 var attribute = get_attribute_in_mutable_instance(papa, last_part)
748
749 if attribute != null then
750 modify_argument_of_complex_type(papa, attribute, parts_of_command[2])
751 end
752 end
753 else
754 var target = seek_variable(parts_of_variable[0], frame)
755 if target != null then
756 modify_in_frame(target, parts_of_command[2])
757 end
758 end
759 end
760
761 # Processes the untrace variable command
762 #
763 # Command pattern : "untrace variable"
764 fun process_untrace_command(parts_of_command: Array[String])
765 do
766 var variable_name = get_real_variable_name(parts_of_command[1])
767 if untrace_variable(variable_name) then
768 print "Untraced variable {parts_of_command[1]}"
769 else
770 print "{parts_of_command[1]} is not traced"
771 end
772 end
773
774 # Processes the trace variable command
775 #
776 # Command pattern : "trace variable [break/print]"
777 fun process_trace_command(parts_of_command: Array[String])
778 do
779 var variable_name = get_real_variable_name(parts_of_command[1])
780 var breaker:Bool
781
782 if seek_variable(variable_name, frame) == null then
783 print "Cannot find a variable called {parts_of_command[1]}"
784 return
785 end
786
787 if parts_of_command.length == 3 then
788 if parts_of_command[2] == "break" then
789 breaker = true
790 else
791 breaker = false
792 end
793 else
794 breaker = false
795 end
796
797 trace_variable(variable_name, breaker)
798
799 print "Successfully tracing {parts_of_command[1]}"
800 end
801
802 #######################################################################
803 ## Trace Management functions ##
804 #######################################################################
805
806 # Effectively untraces the variable called *variable_name*
807 #
808 # Returns true if the variable exists, false otherwise
809 private fun untrace_variable(variable_name: String): Bool
810 do
811 var to_remove: nullable TraceObject = null
812 for i in self.traces do
813 if i.is_variable_traced_in_frame(variable_name, frame) then
814 to_remove = i
815 end
816 end
817
818 if to_remove != null then
819 self.traces.remove(to_remove)
820 return true
821 else
822 return false
823 end
824 end
825
826 # Effectively traces the variable *variable_name* either in print or break mode depending on the value of breaker (break if true, print if false)
827 #
828 private fun trace_variable(variable_name: String, breaker: Bool)
829 do
830 for i in self.traces do
831 if i.is_variable_traced_in_frame(variable_name, frame) then
832 print "This variable is already traced"
833 return
834 end
835 end
836
837 var trace_object: TraceObject
838
839 if breaker then
840 trace_object = new TraceObject(true)
841 else
842 trace_object = new TraceObject(false)
843 end
844
845 # We trace the current variable found for the current frame
846 trace_object.add_frame_variable(self.frame, variable_name)
847
848 var position_of_variable_in_arguments = get_position_of_variable_in_arguments(frame, variable_name)
849
850 # Start parsing the frames starting with the parent of the current one, until the highest
851 # When the variable traced is declared locally, the loop stops
852 for i in [1 .. frames.length-1] do
853
854 # If the variable was reported to be an argument of the previous frame
855 if position_of_variable_in_arguments != -1 then
856
857 var local_name = get_identifiers_in_current_instruction(get_function_arguments(frames[i].current_node.location.text))[position_of_variable_in_arguments]
858
859 position_of_variable_in_arguments = get_position_of_variable_in_arguments(frames[i], local_name)
860
861 trace_object.add_frame_variable(frames[i], local_name)
862 else
863 break
864 end
865 end
866
867 self.traces.add(trace_object)
868 end
869
870 # If the variable *variable_name* is an argument of the function being executed in the frame *frame*
871 # The function returns its position in the arguments
872 # Else, it returns -1
873 private fun get_position_of_variable_in_arguments(frame: Frame, variable_name: String): Int
874 do
875 var identifiers = get_identifiers_in_current_instruction(get_function_arguments(frame.mpropdef.location.text))
876 for i in [0 .. identifiers.length-1] do
877 # If the current traced variable is an argument of the current function, we trace its parent (at least)
878 if identifiers[i] == variable_name then return i
879 end
880 return -1
881 end
882
883 # Gets all the identifiers of an instruction (uses the rules of Nit as of Mar 05 2013)
884 #
885 fun get_identifiers_in_current_instruction(instruction: Text): Array[String]
886 do
887 var result_array = new Array[String]
888 var instruction_buffer = new FlatBuffer
889
890 var trigger_char_escape = false
891 var trigger_string_escape = false
892 var trigger_concat_in_string = false
893
894 for i in instruction.chars do
895 if trigger_char_escape then
896 if i == '\'' then trigger_char_escape = false
897 else if trigger_string_escape then
898 if i == '{' then
899 trigger_concat_in_string = true
900 trigger_string_escape = false
901 else if i == '\"' then trigger_string_escape = false
902 else
903 if i.is_alphanumeric or i == '_' then
904 instruction_buffer.add(i)
905 else if i == '.' then
906 if instruction_buffer.is_numeric or (instruction_buffer.chars[0] >= 'A' and instruction_buffer.chars[0] <= 'Z') then
907 instruction_buffer.clear
908 else
909 result_array.push(instruction_buffer.to_s)
910 instruction_buffer.add(i)
911 end
912 else if i == '\'' then
913 trigger_char_escape = true
914 else if i == '\"' then
915 trigger_string_escape = true
916 else if i == '}' then
917 trigger_concat_in_string = false
918 trigger_string_escape = true
919 else
920 if instruction_buffer.length > 0 and not instruction_buffer.is_numeric and not (instruction_buffer.chars[0] >= 'A' and instruction_buffer.chars[0] <= 'Z') then result_array.push(instruction_buffer.to_s)
921 instruction_buffer.clear
922 end
923 end
924 end
925
926 if instruction_buffer.length > 0 and not instruction_buffer.is_numeric and not (instruction_buffer.chars[0] >= 'A' and instruction_buffer.chars[0] <= 'Z') then result_array.push(instruction_buffer.to_s)
927
928 return result_array
929 end
930
931 # Takes a function call or declaration and strips all but the arguments
932 #
933 fun get_function_arguments(function: Text): String
934 do
935 var buf = new FlatBuffer
936 var trigger_copy = false
937
938 for i in function.chars do
939 if i == ')' then break
940 if trigger_copy then buf.add(i)
941 if i == '(' then trigger_copy = true
942 end
943
944 return buf.to_s
945 end
946
947 #######################################################################
948 ## Alias management functions ##
949 #######################################################################
950
951 # Adds a new alias to the tables
952 fun add_alias(var_represented: String, alias: String)
953 do
954 self.aliases[alias] = var_represented
955 end
956
957 # Gets the real name of a variable hidden by an alias
958 fun get_variable_name_by_alias(alias: String): nullable String
959 do
960 if self.aliases.keys.has(alias) then
961 return self.aliases[alias]
962 end
963
964 return null
965 end
966
967 # Gets the variable named by name, whether it is an alias or not
968 fun get_real_variable_name(name: String): String
969 do
970 var explode_string = name.split_with(".")
971 var final_string = new FlatBuffer
972 for i in explode_string do
973 var alias_resolved = get_variable_name_by_alias(i)
974 if alias_resolved != null then
975 final_string.append(get_real_variable_name(alias_resolved))
976 else
977 final_string.append(i)
978 end
979 final_string.append(".")
980 end
981
982 return final_string.substring(0,final_string.length-1).to_s
983 end
984
985 #######################################################################
986 ## Print functions ##
987 #######################################################################
988
989 # Prints an object instance and its attributes if it has some
990 #
991 # If it is a primitive type, its value is directly printed
992 fun print_instance(instance: Instance)
993 do
994 if instance isa MutableInstance then
995 print "\{"
996 print "\ttype : {instance},"
997
998 printn("\t")
999
1000 print instance.attributes.join(",\n\t"," : ")
1001
1002 print "\}"
1003 else
1004 print "{instance}"
1005 end
1006 end
1007
1008 # Prints the attributes demanded in a SequenceRead
1009 # Used recursively to print nested collections
1010 fun print_nested_collection(instance: Instance, indexes: Array[Array[Int]], depth: Int, variable_name: String, depth_string: String)
1011 do
1012 var collection: nullable SequenceRead[Object] = null
1013 var real_collection_length: nullable Int = null
1014
1015 if instance isa MutableInstance then
1016 real_collection_length = get_collection_instance_real_length(instance)
1017 collection = get_primary_collection(instance)
1018 end
1019
1020 if collection != null and real_collection_length != null then
1021 for i in indexes[depth] do
1022 if i >= 0 and i < real_collection_length then
1023 if depth == indexes.length-1 then
1024 print "{variable_name}{depth_string}[{i}] = {collection[i]}"
1025 else
1026 var item_i = collection[i]
1027
1028 if item_i isa MutableInstance then
1029 print_nested_collection(item_i, indexes, depth+1, variable_name, depth_string+"[{i}]")
1030 else
1031 print "The item at {variable_name}{depth_string}[{i}] is not a collection"
1032 print item_i
1033 end
1034 end
1035 else
1036 print "Out of bounds exception : i = {i} and collection_length = {real_collection_length.to_s}"
1037
1038 if i < 0 then
1039 continue
1040 else if i >= real_collection_length then
1041 break
1042 end
1043 end
1044 end
1045 else
1046 if collection == null then
1047 print "Cannot find {variable_name}{depth_string}"
1048 else if real_collection_length != null then
1049 print "Cannot find attribute length in {instance}"
1050 else
1051 print "Unknown error."
1052 abort
1053 end
1054 end
1055 end
1056
1057 #######################################################################
1058 ## Variable Exploration functions ##
1059 #######################################################################
1060
1061 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
1062 private fun seek_variable(variable_path: String, frame: Frame): nullable Instance
1063 do
1064 var full_variable = variable_path.split_with(".")
1065
1066 var full_variable_iterator = full_variable.iterator
1067
1068 var first_instance = get_variable_in_frame(full_variable_iterator.item, frame)
1069
1070 if first_instance == null then return null
1071
1072 if full_variable.length <= 1 then return first_instance
1073
1074 full_variable_iterator.next
1075
1076 if not (first_instance isa MutableInstance and full_variable_iterator.is_ok) then return null
1077
1078 return get_variable_in_mutable_instance(first_instance, full_variable_iterator)
1079 end
1080
1081 # Gets a variable 'variable_name' contained in the frame 'frame'
1082 private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance
1083 do
1084 if variable_name == "self" then
1085 if frame.arguments.length >= 1 then return frame.arguments.first
1086 end
1087
1088 var map_of_instances = frame.map
1089
1090 for key in map_of_instances.keys do
1091 if key.to_s == variable_name then
1092 return map_of_instances[key]
1093 end
1094 end
1095
1096 return null
1097 end
1098
1099 # Gets an attribute 'attribute_name' contained in variable 'variable'
1100 fun get_attribute_in_mutable_instance(variable: MutableInstance, attribute_name: String): nullable MAttribute
1101 do
1102 var map_of_attributes = variable.attributes
1103
1104 for key in map_of_attributes.keys do
1105 if key.to_s.substring_from(1) == attribute_name then
1106 return key
1107 end
1108 end
1109
1110 return null
1111 end
1112
1113 # Recursive function, returns the variable described by 'total_chain'
1114 fun get_variable_in_mutable_instance(variable: MutableInstance, iterator: Iterator[String]): nullable Instance
1115 do
1116 var attribute = get_attribute_in_mutable_instance(variable, iterator.item)
1117
1118 if attribute == null then return null
1119
1120 iterator.next
1121
1122 if iterator.is_ok then
1123 var new_variable = variable.attributes[attribute]
1124 if new_variable isa MutableInstance then
1125 return get_variable_in_mutable_instance(new_variable, iterator)
1126 else
1127 return null
1128 end
1129 else
1130 return variable.attributes[attribute]
1131 end
1132 end
1133
1134 #######################################################################
1135 ## Array exploring functions ##
1136 #######################################################################
1137
1138 # Gets the length of a collection
1139 # Used by the debugger, else if we call Collection.length, it returns the capacity instead
1140 fun get_collection_instance_real_length(collection: MutableInstance): nullable Int
1141 do
1142 var collection_length_attribute = get_attribute_in_mutable_instance(collection, "length")
1143
1144 var real_collection_length: nullable Int = null
1145
1146 if collection_length_attribute != null then
1147 var primitive_length_instance = collection.attributes[collection_length_attribute]
1148 if primitive_length_instance isa PrimitiveInstance[Int] then
1149 return primitive_length_instance.val
1150 end
1151 end
1152
1153 return null
1154 end
1155
1156 # Processes the indexes of a print array call
1157 # Returns an array containing all the indexes demanded
1158 fun process_index(index_string: String): nullable Array[Int]
1159 do
1160 var from_end_index = index_string.chars.index_of('.')
1161 var to_start_index = index_string.chars.last_index_of('.')
1162
1163 if from_end_index != -1 and to_start_index != -1 then
1164 var index_from_string = index_string.substring(0,from_end_index)
1165 var index_to_string = index_string.substring_from(to_start_index+1)
1166
1167 if index_from_string.is_numeric and index_to_string.is_numeric then
1168 var result_array = new Array[Int]
1169
1170 var index_from = index_from_string.to_i
1171 var index_to = index_to_string.to_i
1172
1173 for i in [index_from..index_to] do
1174 result_array.push(i)
1175 end
1176
1177 return result_array
1178 end
1179 else
1180 if index_string.is_numeric
1181 then
1182 var result_array = new Array[Int]
1183
1184 result_array.push(index_string.to_i)
1185
1186 return result_array
1187 else
1188 return null
1189 end
1190 end
1191
1192 return null
1193 end
1194
1195 # Gets a collection in a MutableInstance
1196 fun get_primary_collection(container: MutableInstance): nullable SequenceRead[Object]
1197 do
1198 var items_of_array = get_attribute_in_mutable_instance(container, "items")
1199 if items_of_array != null then
1200 var array = container.attributes[items_of_array]
1201
1202 if array isa PrimitiveInstance[Object] then
1203 var sequenceRead_final = array.val
1204 if sequenceRead_final isa SequenceRead[Object] then
1205 return sequenceRead_final
1206 end
1207 end
1208 end
1209
1210 return null
1211 end
1212
1213 # Removes the braces '[' ']' in a print array command
1214 # Returns an array containing their content
1215 fun remove_braces(braces: String): nullable Array[String]
1216 do
1217 var buffer = new FlatBuffer
1218
1219 var result_array = new Array[String]
1220
1221 var number_of_opening_brackets = 0
1222 var number_of_closing_brackets = 0
1223
1224 var last_was_opening_bracket = false
1225
1226 for i in braces.chars do
1227 if i == '[' then
1228 if last_was_opening_bracket then
1229 return null
1230 end
1231
1232 number_of_opening_brackets += 1
1233 last_was_opening_bracket = true
1234 else if i == ']' then
1235 if not last_was_opening_bracket then
1236 return null
1237 end
1238
1239 result_array.push(buffer.to_s)
1240 buffer.clear
1241 number_of_closing_brackets += 1
1242 last_was_opening_bracket = false
1243 else if i.is_numeric or i == '.' then
1244 buffer.append(i.to_s)
1245 else if not i == ' ' then
1246 return null
1247 end
1248 end
1249
1250 if number_of_opening_brackets != number_of_closing_brackets then
1251 return null
1252 end
1253
1254 return result_array
1255 end
1256
1257 #######################################################################
1258 ## Breakpoint placing functions ##
1259 #######################################################################
1260
1261 # Places a breakpoint on line 'line_to_break' for file 'file_to_break'
1262 fun place_breakpoint(breakpoint: Breakpoint)
1263 do
1264 if not self.breakpoints.keys.has(breakpoint.file) then
1265 self.breakpoints[breakpoint.file] = new HashSet[Breakpoint]
1266 end
1267 if find_breakpoint(breakpoint.file, breakpoint.line) == null then
1268 self.breakpoints[breakpoint.file].add(breakpoint)
1269 print "Breakpoint added on line {breakpoint.line} for file {breakpoint.file}"
1270 else
1271 print "Breakpoint already present on line {breakpoint.line} for file {breakpoint.file}"
1272 end
1273 end
1274
1275 #Places a breakpoint that will trigger once and be destroyed afterwards
1276 fun process_place_tbreak_fun(parts_of_command: Array[String])
1277 do
1278 var bp = get_breakpoint_from_command(parts_of_command)
1279 if bp != null
1280 then
1281 bp.set_max_breaks(1)
1282 place_breakpoint(bp)
1283 end
1284 end
1285
1286 #######################################################################
1287 ## Breakpoint removing functions ##
1288 #######################################################################
1289
1290 # Removes a breakpoint on line 'line_to_break' for file 'file_to_break'
1291 fun remove_breakpoint(file_to_break: String, line_to_break: Int)
1292 do
1293 if self.breakpoints.keys.has(file_to_break) then
1294 var bp = find_breakpoint(file_to_break, line_to_break)
1295
1296 if bp != null then
1297 self.breakpoints[file_to_break].remove(bp)
1298 print "Breakpoint removed on line {line_to_break} for file {file_to_break}"
1299 return
1300 end
1301 end
1302
1303 print "No breakpoint existing on line {line_to_break} for file {file_to_break}"
1304 end
1305
1306 #######################################################################
1307 ## Breakpoint searching functions ##
1308 #######################################################################
1309
1310 # Finds a breakpoint for 'file' and 'line' in the class HashMap
1311 fun find_breakpoint(file: String, line: Int): nullable Breakpoint
1312 do
1313 if self.breakpoints.keys.has(file)
1314 then
1315 for i in self.breakpoints[file]
1316 do
1317 if i.line == line
1318 then
1319 return i
1320 end
1321 end
1322 end
1323
1324 return null
1325 end
1326
1327 #######################################################################
1328 ## Runtime modification functions ##
1329 #######################################################################
1330
1331 # Modifies the value of a variable contained in a frame
1332 fun modify_in_frame(variable: Instance, value: String)
1333 do
1334 var new_variable = get_variable_of_type_with_value(variable.mtype.to_s, value)
1335 if new_variable != null
1336 then
1337 var keys = frame.map.keys
1338 for key in keys
1339 do
1340 if frame.map[key] == variable
1341 then
1342 frame.map[key] = new_variable
1343 end
1344 end
1345 end
1346 end
1347
1348 # Modifies the value of a variable contained in a MutableInstance
1349 fun modify_argument_of_complex_type(papa: MutableInstance, attribute: MAttribute, value: String)
1350 do
1351 var final_variable = papa.attributes[attribute]
1352 var type_of_variable = final_variable.mtype.to_s
1353 var new_variable = get_variable_of_type_with_value(type_of_variable, value)
1354 if new_variable != null
1355 then
1356 papa.attributes[attribute] = new_variable
1357 end
1358 end
1359
1360 #######################################################################
1361 ## Variable generator functions ##
1362 #######################################################################
1363
1364 # Returns a new variable of the type 'type_of_variable' with a value of 'value'
1365 fun get_variable_of_type_with_value(type_of_variable: String, value: String): nullable Instance
1366 do
1367 if type_of_variable == "Int" then
1368 return get_int(value)
1369 else if type_of_variable == "Float" then
1370 return get_float(value)
1371 else if type_of_variable == "Bool" then
1372 return get_bool(value)
1373 else if type_of_variable == "Char" then
1374 return get_char(value)
1375 end
1376
1377 return null
1378 end
1379
1380 # Returns a new int instance with value 'value'
1381 fun get_int(value: String): nullable Instance
1382 do
1383 if value.is_numeric then
1384 return int_instance(value.to_i)
1385 else
1386 return null
1387 end
1388 end
1389
1390 # Returns a new float instance with value 'value'
1391 fun get_float(value:String): nullable Instance
1392 do
1393 if value.is_numeric then
1394 return float_instance(value.to_f)
1395 else
1396 return null
1397 end
1398 end
1399
1400 # Returns a new char instance with value 'value'
1401 fun get_char(value: String): nullable Instance
1402 do
1403 if value.length >= 1 then
1404 return char_instance(value.chars[0])
1405 else
1406 return null
1407 end
1408 end
1409
1410 # Returns a new bool instance with value 'value'
1411 fun get_bool(value: String): nullable Instance
1412 do
1413 if value.to_lower == "true" then
1414 return self.true_instance
1415 else if value.to_lower == "false" then
1416 return self.false_instance
1417 else
1418 print "Invalid value, a boolean must be set at \"true\" or \"false\""
1419 return null
1420 end
1421 end
1422
1423 end
1424
1425 redef class AMethPropdef
1426
1427 # Same as call except it will copy local variables of the parent frame to the frame defined in this call.
1428 # Not supposed to be used by anyone else than the Debugger.
1429 private fun rt_call(v: Debugger, mpropdef: MMethodDef, args: Array[Instance]): nullable Instance
1430 do
1431 var f = new Frame(self, self.mpropdef.as(not null), args)
1432 var curr_instances = v.frame.map
1433 for i in curr_instances.keys do
1434 f.map[i] = curr_instances[i]
1435 end
1436 call_commons(v,mpropdef,args,f)
1437 var currFra = v.frames.shift
1438 for i in curr_instances.keys do
1439 if currFra.map.keys.has(i) then
1440 curr_instances[i] = currFra.map[i]
1441 end
1442 end
1443 if v.returnmark == f then
1444 v.returnmark = null
1445 var res = v.escapevalue
1446 v.escapevalue = null
1447 return res
1448 end
1449 return null
1450
1451 end
1452 end
1453
1454 # Traces the modifications of an object linked to a certain frame
1455 private class TraceObject
1456
1457 # Map of the local names bound to a frame
1458 var trace_map: HashMap[Frame, String]
1459 # Decides if breaking or printing statement when the variable is encountered
1460 var break_on_encounter: Bool
1461
1462 init(break_on_encounter: Bool)
1463 do
1464 trace_map = new HashMap[Frame, String]
1465 self.break_on_encounter = break_on_encounter
1466 end
1467
1468 # Adds the local alias for a variable and the frame bound to it
1469 fun add_frame_variable(frame: Frame, variable_name: String)
1470 do
1471 self.trace_map[frame] = variable_name
1472 end
1473
1474 # Checks if the prompted variable is traced in the specified frame
1475 fun is_variable_traced_in_frame(variable_name: String, frame: Frame): Bool
1476 do
1477 if self.trace_map.has_key(frame) then
1478 if self.trace_map[frame] == variable_name then
1479 return true
1480 end
1481 end
1482
1483 return false
1484 end
1485
1486 end
1487
1488 redef class ANode
1489
1490 # Breaks automatically when encountering an error
1491 # Permits the injunction of commands before crashing
1492 redef private fun fatal(v: NaiveInterpreter, message: String)
1493 do
1494 if v isa Debugger then
1495 print "An error was encountered, the program will stop now."
1496 self.debug(message)
1497 while v.process_debug_command(gets) do end
1498 end
1499
1500 super
1501 end
1502 end