63c09b6d863dc3752c7aead6f79378c2c9490187
[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 if instance isa MutableInstance then
996 print "\{"
997 print "\ttype : {instance},"
998
999 printn("\t")
1000
1001 print instance.attributes.join(",\n\t"," : ")
1002
1003 print "\}"
1004 else
1005 print "{instance}"
1006 end
1007 end
1008
1009 # Prints the attributes demanded in a SequenceRead
1010 # Used recursively to print nested collections
1011 fun print_nested_collection(instance: Instance, indexes: Array[Array[Int]], depth: Int, variable_name: String, depth_string: String)
1012 do
1013 var collection: nullable SequenceRead[Object] = null
1014 var real_collection_length: nullable Int = null
1015
1016 if instance isa MutableInstance then
1017 real_collection_length = get_collection_instance_real_length(instance)
1018 collection = get_primary_collection(instance)
1019 end
1020
1021 if collection != null and real_collection_length != null then
1022 for i in indexes[depth] do
1023 if i >= 0 and i < real_collection_length then
1024 if depth == indexes.length-1 then
1025 print "{variable_name}{depth_string}[{i}] = {collection[i]}"
1026 else
1027 var item_i = collection[i]
1028
1029 if item_i isa MutableInstance then
1030 print_nested_collection(item_i, indexes, depth+1, variable_name, depth_string+"[{i}]")
1031 else
1032 print "The item at {variable_name}{depth_string}[{i}] is not a collection"
1033 print item_i
1034 end
1035 end
1036 else
1037 print "Out of bounds exception : i = {i} and collection_length = {real_collection_length.to_s}"
1038
1039 if i < 0 then
1040 continue
1041 else if i >= real_collection_length then
1042 break
1043 end
1044 end
1045 end
1046 else
1047 if collection == null then
1048 print "Cannot find {variable_name}{depth_string}"
1049 else if real_collection_length != null then
1050 print "Cannot find attribute length in {instance}"
1051 else
1052 print "Unknown error."
1053 abort
1054 end
1055 end
1056 end
1057
1058 #######################################################################
1059 ## Variable Exploration functions ##
1060 #######################################################################
1061
1062 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
1063 private fun seek_variable(variable_path: String, frame: Frame): nullable Instance
1064 do
1065 var full_variable = variable_path.split_with(".")
1066
1067 var full_variable_iterator = full_variable.iterator
1068
1069 var first_instance = get_variable_in_frame(full_variable_iterator.item, frame)
1070
1071 if first_instance == null then return null
1072
1073 if full_variable.length <= 1 then return first_instance
1074
1075 full_variable_iterator.next
1076
1077 if not (first_instance isa MutableInstance and full_variable_iterator.is_ok) then return null
1078
1079 return get_variable_in_mutable_instance(first_instance, full_variable_iterator)
1080 end
1081
1082 # Gets a variable 'variable_name' contained in the frame 'frame'
1083 private fun get_variable_in_frame(variable_name: String, frame: Frame): nullable Instance
1084 do
1085 if variable_name == "self" then
1086 if frame.arguments.length >= 1 then return frame.arguments.first
1087 end
1088
1089 var map_of_instances = frame.map
1090
1091 for key in map_of_instances.keys do
1092 if key.to_s == variable_name then
1093 return map_of_instances[key]
1094 end
1095 end
1096
1097 return null
1098 end
1099
1100 # Gets an attribute 'attribute_name' contained in variable 'variable'
1101 fun get_attribute_in_mutable_instance(variable: MutableInstance, attribute_name: String): nullable MAttribute
1102 do
1103 var map_of_attributes = variable.attributes
1104
1105 for key in map_of_attributes.keys do
1106 if key.to_s.substring_from(1) == attribute_name then
1107 return key
1108 end
1109 end
1110
1111 return null
1112 end
1113
1114 # Recursive function, returns the variable described by 'total_chain'
1115 fun get_variable_in_mutable_instance(variable: MutableInstance, iterator: Iterator[String]): nullable Instance
1116 do
1117 var attribute = get_attribute_in_mutable_instance(variable, iterator.item)
1118
1119 if attribute == null then return null
1120
1121 iterator.next
1122
1123 if iterator.is_ok then
1124 var new_variable = variable.attributes[attribute]
1125 if new_variable isa MutableInstance then
1126 return get_variable_in_mutable_instance(new_variable, iterator)
1127 else
1128 return null
1129 end
1130 else
1131 return variable.attributes[attribute]
1132 end
1133 end
1134
1135 #######################################################################
1136 ## Array exploring functions ##
1137 #######################################################################
1138
1139 # Gets the length of a collection
1140 # Used by the debugger, else if we call Collection.length, it returns the capacity instead
1141 fun get_collection_instance_real_length(collection: MutableInstance): nullable Int
1142 do
1143 var collection_length_attribute = get_attribute_in_mutable_instance(collection, "length")
1144
1145 var real_collection_length: nullable Int = null
1146
1147 if collection_length_attribute != null then
1148 var primitive_length_instance = collection.attributes[collection_length_attribute]
1149 if primitive_length_instance isa PrimitiveInstance[Int] then
1150 return primitive_length_instance.val
1151 end
1152 end
1153
1154 return null
1155 end
1156
1157 # Processes the indexes of a print array call
1158 # Returns an array containing all the indexes demanded
1159 fun process_index(index_string: String): nullable Array[Int]
1160 do
1161 var from_end_index = index_string.chars.index_of('.')
1162 var to_start_index = index_string.chars.last_index_of('.')
1163
1164 if from_end_index != -1 and to_start_index != -1 then
1165 var index_from_string = index_string.substring(0,from_end_index)
1166 var index_to_string = index_string.substring_from(to_start_index+1)
1167
1168 if index_from_string.is_numeric and index_to_string.is_numeric then
1169 var result_array = new Array[Int]
1170
1171 var index_from = index_from_string.to_i
1172 var index_to = index_to_string.to_i
1173
1174 for i in [index_from..index_to] do
1175 result_array.push(i)
1176 end
1177
1178 return result_array
1179 end
1180 else
1181 if index_string.is_numeric
1182 then
1183 var result_array = new Array[Int]
1184
1185 result_array.push(index_string.to_i)
1186
1187 return result_array
1188 else
1189 return null
1190 end
1191 end
1192
1193 return null
1194 end
1195
1196 # Gets a collection in a MutableInstance
1197 fun get_primary_collection(container: MutableInstance): nullable SequenceRead[Object]
1198 do
1199 var items_of_array = get_attribute_in_mutable_instance(container, "items")
1200 if items_of_array != null then
1201 var array = container.attributes[items_of_array]
1202
1203 if array isa PrimitiveInstance[Object] then
1204 var sequenceRead_final = array.val
1205 if sequenceRead_final isa SequenceRead[Object] then
1206 return sequenceRead_final
1207 end
1208 end
1209 end
1210
1211 return null
1212 end
1213
1214 # Removes the braces '[' ']' in a print array command
1215 # Returns an array containing their content
1216 fun remove_braces(braces: String): nullable Array[String]
1217 do
1218 var buffer = new FlatBuffer
1219
1220 var result_array = new Array[String]
1221
1222 var number_of_opening_brackets = 0
1223 var number_of_closing_brackets = 0
1224
1225 var last_was_opening_bracket = false
1226
1227 for i in braces.chars do
1228 if i == '[' then
1229 if last_was_opening_bracket then
1230 return null
1231 end
1232
1233 number_of_opening_brackets += 1
1234 last_was_opening_bracket = true
1235 else if i == ']' then
1236 if not last_was_opening_bracket then
1237 return null
1238 end
1239
1240 result_array.push(buffer.to_s)
1241 buffer.clear
1242 number_of_closing_brackets += 1
1243 last_was_opening_bracket = false
1244 else if i.is_numeric or i == '.' then
1245 buffer.append(i.to_s)
1246 else if not i == ' ' then
1247 return null
1248 end
1249 end
1250
1251 if number_of_opening_brackets != number_of_closing_brackets then
1252 return null
1253 end
1254
1255 return result_array
1256 end
1257
1258 #######################################################################
1259 ## Breakpoint placing functions ##
1260 #######################################################################
1261
1262 # Places a breakpoint on line 'line_to_break' for file 'file_to_break'
1263 fun place_breakpoint(breakpoint: Breakpoint)
1264 do
1265 if not self.breakpoints.keys.has(breakpoint.file) then
1266 self.breakpoints[breakpoint.file] = new HashSet[Breakpoint]
1267 end
1268 if find_breakpoint(breakpoint.file, breakpoint.line) == null then
1269 self.breakpoints[breakpoint.file].add(breakpoint)
1270 print "Breakpoint added on line {breakpoint.line} for file {breakpoint.file}"
1271 else
1272 print "Breakpoint already present on line {breakpoint.line} for file {breakpoint.file}"
1273 end
1274 end
1275
1276 #Places a breakpoint that will trigger once and be destroyed afterwards
1277 fun process_place_tbreak_fun(parts_of_command: Array[String])
1278 do
1279 var bp = get_breakpoint_from_command(parts_of_command)
1280 if bp != null
1281 then
1282 bp.set_max_breaks(1)
1283 place_breakpoint(bp)
1284 end
1285 end
1286
1287 #######################################################################
1288 ## Breakpoint removing functions ##
1289 #######################################################################
1290
1291 # Removes a breakpoint on line 'line_to_break' for file 'file_to_break'
1292 fun remove_breakpoint(file_to_break: String, line_to_break: Int)
1293 do
1294 if self.breakpoints.keys.has(file_to_break) then
1295 var bp = find_breakpoint(file_to_break, line_to_break)
1296
1297 if bp != null then
1298 self.breakpoints[file_to_break].remove(bp)
1299 print "Breakpoint removed on line {line_to_break} for file {file_to_break}"
1300 return
1301 end
1302 end
1303
1304 print "No breakpoint existing on line {line_to_break} for file {file_to_break}"
1305 end
1306
1307 #######################################################################
1308 ## Breakpoint searching functions ##
1309 #######################################################################
1310
1311 # Finds a breakpoint for 'file' and 'line' in the class HashMap
1312 fun find_breakpoint(file: String, line: Int): nullable Breakpoint
1313 do
1314 if self.breakpoints.keys.has(file)
1315 then
1316 for i in self.breakpoints[file]
1317 do
1318 if i.line == line
1319 then
1320 return i
1321 end
1322 end
1323 end
1324
1325 return null
1326 end
1327
1328 #######################################################################
1329 ## Runtime modification functions ##
1330 #######################################################################
1331
1332 # Modifies the value of a variable contained in a frame
1333 fun modify_in_frame(variable: Instance, value: String)
1334 do
1335 var new_variable = get_variable_of_type_with_value(variable.mtype.to_s, value)
1336 if new_variable != null
1337 then
1338 var keys = frame.map.keys
1339 for key in keys
1340 do
1341 if frame.map[key] == variable
1342 then
1343 frame.map[key] = new_variable
1344 end
1345 end
1346 end
1347 end
1348
1349 # Modifies the value of a variable contained in a MutableInstance
1350 fun modify_argument_of_complex_type(papa: MutableInstance, attribute: MAttribute, value: String)
1351 do
1352 var final_variable = papa.attributes[attribute]
1353 var type_of_variable = final_variable.mtype.to_s
1354 var new_variable = get_variable_of_type_with_value(type_of_variable, value)
1355 if new_variable != null
1356 then
1357 papa.attributes[attribute] = new_variable
1358 end
1359 end
1360
1361 #######################################################################
1362 ## Variable generator functions ##
1363 #######################################################################
1364
1365 # Returns a new variable of the type 'type_of_variable' with a value of 'value'
1366 fun get_variable_of_type_with_value(type_of_variable: String, value: String): nullable Instance
1367 do
1368 if type_of_variable == "Int" then
1369 return get_int(value)
1370 else if type_of_variable == "Float" then
1371 return get_float(value)
1372 else if type_of_variable == "Bool" then
1373 return get_bool(value)
1374 else if type_of_variable == "Char" then
1375 return get_char(value)
1376 end
1377
1378 return null
1379 end
1380
1381 # Returns a new int instance with value 'value'
1382 fun get_int(value: String): nullable Instance
1383 do
1384 if value.is_numeric then
1385 return int_instance(value.to_i)
1386 else
1387 return null
1388 end
1389 end
1390
1391 # Returns a new float instance with value 'value'
1392 fun get_float(value:String): nullable Instance
1393 do
1394 if value.is_numeric then
1395 return float_instance(value.to_f)
1396 else
1397 return null
1398 end
1399 end
1400
1401 # Returns a new char instance with value 'value'
1402 fun get_char(value: String): nullable Instance
1403 do
1404 if value.length >= 1 then
1405 return char_instance(value.chars[0])
1406 else
1407 return null
1408 end
1409 end
1410
1411 # Returns a new bool instance with value 'value'
1412 fun get_bool(value: String): nullable Instance
1413 do
1414 if value.to_lower == "true" then
1415 return self.true_instance
1416 else if value.to_lower == "false" then
1417 return self.false_instance
1418 else
1419 print "Invalid value, a boolean must be set at \"true\" or \"false\""
1420 return null
1421 end
1422 end
1423
1424 end
1425
1426 redef class AMethPropdef
1427
1428 # Same as call except it will copy local variables of the parent frame to the frame defined in this call.
1429 # Not supposed to be used by anyone else than the Debugger.
1430 private fun rt_call(v: Debugger, mpropdef: MMethodDef, args: Array[Instance]): nullable Instance
1431 do
1432 var f = new Frame(self, self.mpropdef.as(not null), args)
1433 var curr_instances = v.frame.map
1434 for i in curr_instances.keys do
1435 f.map[i] = curr_instances[i]
1436 end
1437 call_commons(v,mpropdef,args,f)
1438 var currFra = v.frames.shift
1439 for i in curr_instances.keys do
1440 if currFra.map.keys.has(i) then
1441 curr_instances[i] = currFra.map[i]
1442 end
1443 end
1444 if v.returnmark == f then
1445 v.returnmark = null
1446 var res = v.escapevalue
1447 v.escapevalue = null
1448 return res
1449 end
1450 return null
1451
1452 end
1453 end
1454
1455 # Traces the modifications of an object linked to a certain frame
1456 private class TraceObject
1457
1458 # Map of the local names bound to a frame
1459 var trace_map: HashMap[Frame, String]
1460 # Decides if breaking or printing statement when the variable is encountered
1461 var break_on_encounter: Bool
1462
1463 init(break_on_encounter: Bool)
1464 do
1465 trace_map = new HashMap[Frame, String]
1466 self.break_on_encounter = break_on_encounter
1467 end
1468
1469 # Adds the local alias for a variable and the frame bound to it
1470 fun add_frame_variable(frame: Frame, variable_name: String)
1471 do
1472 self.trace_map[frame] = variable_name
1473 end
1474
1475 # Checks if the prompted variable is traced in the specified frame
1476 fun is_variable_traced_in_frame(variable_name: String, frame: Frame): Bool
1477 do
1478 if self.trace_map.has_key(frame) then
1479 if self.trace_map[frame] == variable_name then
1480 return true
1481 end
1482 end
1483
1484 return false
1485 end
1486
1487 end
1488
1489 redef class ANode
1490
1491 # Breaks automatically when encountering an error
1492 # Permits the injunction of commands before crashing
1493 redef private fun fatal(v: NaiveInterpreter, message: String)
1494 do
1495 if v isa Debugger then
1496 print "An error was encountered, the program will stop now."
1497 self.debug(message)
1498 while v.process_debug_command(gets) do end
1499 end
1500
1501 super
1502 end
1503 end