1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2013 Lucas Bajolet <lucas.bajolet@gmail.com>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Debugging of a nit program using the NaiveInterpreter
21 intrude import naive_interpreter
23 redef class ToolContext
25 var opt_debugger_mode
: OptionBool = new OptionBool("Launches the target program with the debugger attached to it", "-d")
30 self.option_context
.add_option
(self.opt_debugger_mode
)
34 redef class ModelBuilder
35 # Execute the program from the entry point (Sys::main) of the `mainmodule'
36 # `arguments' are the command-line arguments in order
38 # 1. the AST is fully loaded.
39 # 2. the model is fully built.
40 # 3. the instructions are fully analysed.
41 fun run_debugger
(mainmodule
: MModule, arguments
: Array[String])
44 self.toolcontext
.info
("*** START INTERPRETING ***", 1)
46 var interpreter
= new Debugger(self, mainmodule
, arguments
)
48 init_naive_interpreter
(interpreter
, mainmodule
)
51 self.toolcontext
.info
("*** END INTERPRETING: {time1-time0} ***", 2)
55 # The class extending NaiveInterpreter by adding debugging methods
57 super NaiveInterpreter
59 # Keeps the frame count in memory to find when to stop
60 # and launch the command prompt after a step out call
61 var step_stack_count
= 1
63 # Triggers a step over an instruction in a nit program
64 var stop_after_step_over_trigger
= true
66 # Triggers a step out of an instruction
67 var stop_after_step_out_trigger
= false
69 # Triggers a step in a instruction (enters a function
70 # if the instruction is a function call)
71 var step_in_trigger
= false
73 # HashMap containing the breakpoints bound to a file
74 var breakpoints
= new HashMap[String, HashSet[Breakpoint]]
76 # Contains the current file
79 # Aliases hashmap (maps an alias to a variable name)
80 var aliases
= new HashMap[String, String]
82 #######################################################################
83 ## Execution of statement function ##
84 #######################################################################
86 # Main loop, every call to a debug command is done here
87 redef fun stmt
(n
: nullable AExpr)
89 if n
== null then return
91 var frame
= self.frame
92 var old
= frame
.current_node
93 frame
.current_node
= n
100 frame
.current_node
= old
103 # Encpasulates the behaviour for step over/out
104 private fun steps_fun_call
(n
: AExpr)
106 if self.stop_after_step_over_trigger
then
107 if self.frames
.length
<= self.step_stack_count
then
108 n
.debug
("Execute stmt {n.to_s}")
109 while process_debug_command
(gets
) do end
111 else if self.stop_after_step_out_trigger
then
112 if frames
.length
< self.step_stack_count
then
113 n
.debug
("Execute stmt {n.to_s}")
114 while process_debug_command
(gets
) do end
116 else if step_in_trigger
then
117 n
.debug
("Execute stmt {n.to_s}")
118 while process_debug_command
(gets
) do end
122 # Checks if a breakpoint is encountered, and launches the debugging prompt if true
123 private fun breakpoint_check
(n
: AExpr)
125 var currFileNameSplit
= self.frame
.current_node
.location
.file
.filename
.to_s
.split_with
("/")
127 self.curr_file
= currFileNameSplit
[currFileNameSplit
.length-1
]
129 var breakpoint
= find_breakpoint
(curr_file
, n
.location
.line_start
)
131 if breakpoints
.keys
.has
(curr_file
) and breakpoint
!= null then
135 if not breakpoint
.is_valid
137 remove_breakpoint
(curr_file
, n
.location
.line_start
)
140 n
.debug
("Execute stmt {n.to_s}")
141 while process_debug_command
(gets
) do end
145 #######################################################################
146 ## Processing commands functions ##
147 #######################################################################
149 # Takes a user command as a parameter
151 # Returns a boolean value, representing whether or not to
152 # continue reading commands from the console input
153 fun process_debug_command
(command
:String): Bool
158 # Kills the current program
159 if command
== "kill" then
162 else if command
== "finish"
166 else if command
== "s"
170 else if command
== "n" then
172 # Continues execution until the end
173 else if command
== "c" then
176 var parts_of_command
= command
.split_with
(' ')
177 # Shows the value of a variable in the current frame
178 if parts_of_command
[0] == "p" or parts_of_command
[0] == "print" then
179 print_command
(parts_of_command
)
180 # Places a breakpoint on line x of file y
181 else if parts_of_command
[0] == "break" or parts_of_command
[0] == "b"
183 process_place_break_fun
(parts_of_command
)
184 # Places a temporary breakpoint on line x of file y
185 else if parts_of_command
[0] == "tbreak" and (parts_of_command
.length
== 2 or parts_of_command
.length
== 3)
187 process_place_tbreak_fun
(parts_of_command
)
188 # Removes a breakpoint on line x of file y
189 else if parts_of_command
[0] == "d" or parts_of_command
[0] == "delete" then
190 process_remove_break_fun
(parts_of_command
)
191 # Sets an alias for a variable
192 else if parts_of_command
.length
== 3 and parts_of_command
[1] == "as"
194 add_alias
(parts_of_command
[0], parts_of_command
[2])
195 # Lists all the commands available
203 #######################################################################
204 ## Processing specific command functions ##
205 #######################################################################
207 # Sets the flags to step-over an instruction in the current file
210 self.step_stack_count
= frames
.length
211 self.stop_after_step_over_trigger
= true
212 self.stop_after_step_out_trigger
= false
213 self.step_in_trigger
= false
217 #Sets the flags to step-out of a function
220 self.stop_after_step_over_trigger
= false
221 self.stop_after_step_out_trigger
= true
222 self.step_in_trigger
= false
223 self.step_stack_count
= frames
.length
227 # Sets the flags to step-in an instruction
230 self.step_in_trigger
= true
231 self.stop_after_step_over_trigger
= false
232 self.stop_after_step_out_trigger
= false
236 # Sets the flags to continue execution
237 fun continue_exec
: Bool
239 self.stop_after_step_over_trigger
= false
240 self.stop_after_step_out_trigger
= false
241 self.step_in_trigger
= false
245 # Prints the demanded variable in the command
247 # The name of the variable in in position 1 of the array 'parts_of_command'
248 fun print_command
(parts_of_command
: Array[String])
250 if parts_of_command
[1] == "*" then
251 var map_of_instances
= frame
.map
253 var keys
= map_of_instances
.iterator
255 print
"Variables collection : \n"
257 for instance
in map_of_instances
.keys
do
258 print
"Variable {instance.to_s}, Instance {map_of_instances[instance].to_s}"
261 print
"\nEnd of current instruction \n"
262 else if parts_of_command
[1].has
('[') and parts_of_command
[1].has
(']') then
263 process_array_command
(parts_of_command
)
265 var instance
= seek_variable
(get_real_variable_name
(parts_of_command
[1]), frame
)
269 print_instance
(instance
)
271 print
"Cannot find variable {parts_of_command[1]}"
276 # Processes the input string to know where to put a breakpoint
277 fun process_place_break_fun
(parts_of_command
: Array[String])
279 var bp
= get_breakpoint_from_command
(parts_of_command
)
287 # Returns a breakpoint containing the informations stored in the command
288 fun get_breakpoint_from_command
(parts_of_command
: Array[String]): nullable Breakpoint
290 if parts_of_command
[1].is_numeric
then
291 return new Breakpoint(parts_of_command
[1].to_i
, curr_file
)
292 else if parts_of_command
.length
>= 3 and parts_of_command
[2].is_numeric
then
293 return new Breakpoint(parts_of_command
[2].to_i
, parts_of_command
[1])
299 # Processes the command of removing a breakpoint on specified line and file
300 fun process_remove_break_fun
(parts_of_command
: Array[String])
302 if parts_of_command
[1].is_numeric
then
303 remove_breakpoint
(self.curr_file
, parts_of_command
[1].to_i
)
304 else if parts_of_command
.length
>= 3 and parts_of_command
[2].is_numeric
then
305 remove_breakpoint
(parts_of_command
[1], parts_of_command
[2].to_i
)
311 # Processes an array print command
312 fun process_array_command
(parts_of_command
: Array[String])
314 var index_of_first_brace
= parts_of_command
[1].index_of
('[')
315 var variable_name
= get_real_variable_name
(parts_of_command
[1].substring
(0,index_of_first_brace
))
316 var braces
= parts_of_command
[1].substring_from
(index_of_first_brace
)
318 var indexes
= remove_braces
(braces
)
320 var index_array
= new Array[Array[Int]]
322 if indexes
!= null then
323 for index
in indexes
do
324 var temp_indexes_array
= process_index
(index
)
325 if temp_indexes_array
!= null then
326 index_array
.push
(temp_indexes_array
)
327 #print index_array.last
332 var instance
= seek_variable
(variable_name
, frame
)
334 if instance
!= null then
335 print_nested_collection
(instance
, index_array
, 0, variable_name
, "")
337 print
"Cannot find variable {variable_name}"
341 #######################################################################
342 ## Alias management functions ##
343 #######################################################################
345 # Adds a new alias to the tables
346 fun add_alias
(var_represented
: String, alias
: String)
348 self.aliases
[alias
] = var_represented
351 # Gets the real name of a variable hidden by an alias
352 fun get_variable_name_by_alias
(alias
: String): nullable String
354 if self.aliases
.keys
.has
(alias
) then
355 return self.aliases
[alias
]
361 # Gets the variable named by name, whether it is an alias or not
362 fun get_real_variable_name
(name
: String): String
364 var explode_string
= name
.split_with
(".")
365 var final_string
= new Buffer
366 for i
in explode_string
do
367 var alias_resolved
= get_variable_name_by_alias
(i
)
368 if alias_resolved
!= null then
369 final_string
.append
(get_real_variable_name
(alias_resolved
))
371 final_string
.append
(i
)
373 final_string
.append
(".")
376 return final_string
.substring
(0,final_string
.length-1
).to_s
379 #######################################################################
380 ## Print functions ##
381 #######################################################################
383 # Prints an object instance and its attributes if it has some
385 # If it is a primitive type, its value is directly printed
386 fun print_instance
(instance
: Instance)
388 if instance
isa MutableInstance then
389 var attributes
= instance
.attributes
390 print
"Object : {instance}"
392 for current_attribute
in attributes
.keys
do
393 print
"Attribute : {current_attribute.to_s} \nValeur : {attributes[current_attribute].to_s}"
396 print
"Found variable {instance}"
400 # Prints the attributes demanded in a SequenceRead
401 # Used recursively to print nested collections
402 fun print_nested_collection
(instance
: Instance, indexes
: Array[Array[Int]], depth
: Int, variable_name
: String, depth_string
: String)
404 var collection
: nullable SequenceRead[Object] = null
405 var real_collection_length
: nullable Int = null
407 if instance
isa MutableInstance then
408 real_collection_length
= get_collection_instance_real_length
(instance
)
409 collection
= get_primary_collection
(instance
)
412 if collection
!= null and real_collection_length
!= null then
413 for i
in indexes
[depth
] do
414 if i
>= 0 and i
< real_collection_length
then
415 if depth
== indexes
.length-1
then
416 print
"{variable_name}{depth_string}[{i}] = {collection[i]}"
418 var item_i
= collection
[i
]
420 if item_i
isa MutableInstance then
421 print_nested_collection
(item_i
, indexes
, depth
+1, variable_name
, depth_string
+"[{i}]")
423 print
"The item at {variable_name}{depth_string}[{i}] is not a collection"
428 print
"Out of bounds exception : i = {i} and collection_length = {real_collection_length.to_s}"
432 else if i
>= real_collection_length
then
438 if collection
== null then
439 print
"Cannot find {variable_name}{depth_string}"
440 else if real_collection_length
!= null then
441 print
"Cannot find attribute length in {instance}"
443 print
"Unknown error."
449 #######################################################################
450 ## Variable Exploration functions ##
451 #######################################################################
453 # Seeks a variable from the current frame called 'variable_path', can introspect complex objects using function get_variable_in_mutable_instance
454 private fun seek_variable
(variable_path
: String, frame
: Frame): nullable Instance
456 var full_variable
= variable_path
.split_with
(".")
458 var full_variable_iterator
= full_variable
.iterator
460 var first_instance
= get_variable_in_frame
(full_variable_iterator
.item
, frame
)
462 if first_instance
== null then return null
464 if full_variable
.length
<= 1 then return first_instance
466 full_variable_iterator
.next
468 if not (first_instance
isa MutableInstance and full_variable_iterator
.is_ok
) then return null
470 return get_variable_in_mutable_instance
(first_instance
, full_variable_iterator
)
473 # Gets a variable 'variable_name' contained in the frame 'frame'
474 private fun get_variable_in_frame
(variable_name
: String, frame
: Frame): nullable Instance
476 if variable_name
== "self" then
477 if frame
.arguments
.length
>= 1 then return frame
.arguments
.first
480 var map_of_instances
= frame
.map
482 for key
in map_of_instances
.keys
do
483 if key
.to_s
== variable_name
then
484 return map_of_instances
[key
]
491 # Gets an attribute 'attribute_name' contained in variable 'variable'
492 fun get_attribute_in_mutable_instance
(variable
: MutableInstance, attribute_name
: String): nullable MAttribute
494 var map_of_attributes
= variable
.attributes
496 for key
in map_of_attributes
.keys
do
497 if key
.to_s
.substring_from
(1) == attribute_name
then
505 # Recursive function, returns the variable described by 'total_chain'
506 fun get_variable_in_mutable_instance
(variable
: MutableInstance, iterator
: Iterator[String]): nullable Instance
508 var attribute
= get_attribute_in_mutable_instance
(variable
, iterator
.item
)
510 if attribute
== null then return null
514 if iterator
.is_ok
then
515 var new_variable
= variable
.attributes
[attribute
]
516 if new_variable
isa MutableInstance then
517 return get_variable_in_mutable_instance
(new_variable
, iterator
)
522 return variable
.attributes
[attribute
]
526 #######################################################################
527 ## Array exploring functions ##
528 #######################################################################
530 # Gets the length of a collection
531 # Used by the debugger, else if we call Collection.length, it returns the capacity instead
532 fun get_collection_instance_real_length
(collection
: MutableInstance): nullable Int
534 var collection_length_attribute
= get_attribute_in_mutable_instance
(collection
, "length")
536 var real_collection_length
: nullable Int = null
538 if collection_length_attribute
!= null then
539 var primitive_length_instance
= collection
.attributes
[collection_length_attribute
]
540 if primitive_length_instance
isa PrimitiveInstance[Int] then
541 return primitive_length_instance
.val
548 # Processes the indexes of a print array call
549 # Returns an array containing all the indexes demanded
550 fun process_index
(index_string
: String): nullable Array[Int]
552 var from_end_index
= index_string
.index_of
('.')
553 var to_start_index
= index_string
.last_index_of
('.')
555 if from_end_index
!= -1 and to_start_index
!= -1 then
556 var index_from_string
= index_string
.substring
(0,from_end_index
)
557 var index_to_string
= index_string
.substring_from
(to_start_index
+1)
559 if index_from_string
.is_numeric
and index_to_string
.is_numeric
then
560 var result_array
= new Array[Int]
562 var index_from
= index_from_string
.to_i
563 var index_to
= index_to_string
.to_i
565 for i
in [index_from
..index_to
] do
572 if index_string
.is_numeric
574 var result_array
= new Array[Int]
576 result_array
.push
(index_string
.to_i
)
587 # Gets a collection in a MutableInstance
588 fun get_primary_collection
(container
: MutableInstance): nullable SequenceRead[Object]
590 var items_of_array
= get_attribute_in_mutable_instance
(container
, "items")
591 if items_of_array
!= null then
592 var array
= container
.attributes
[items_of_array
]
594 if array
isa PrimitiveInstance[Object] then
595 var sequenceRead_final
= array
.val
596 if sequenceRead_final
isa SequenceRead[Object] then
597 return sequenceRead_final
605 # Removes the braces '[' ']' in a print array command
606 # Returns an array containing their content
607 fun remove_braces
(braces
: String): nullable Array[String]
609 var buffer
= new Buffer
611 var result_array
= new Array[String]
613 var number_of_opening_brackets
= 0
614 var number_of_closing_brackets
= 0
616 var last_was_opening_bracket
= false
620 if last_was_opening_bracket
then
624 number_of_opening_brackets
+= 1
625 last_was_opening_bracket
= true
626 else if i
== ']' then
627 if not last_was_opening_bracket
then
631 result_array
.push
(buffer
.to_s
)
633 number_of_closing_brackets
+= 1
634 last_was_opening_bracket
= false
635 else if i
.is_numeric
or i
== '.' then
636 buffer
.append
(i
.to_s
)
637 else if not i
== ' ' then
642 if number_of_opening_brackets
!= number_of_closing_brackets
then
649 #######################################################################
650 ## Breakpoint placing functions ##
651 #######################################################################
653 # Places a breakpoint on line 'line_to_break' for file 'file_to_break'
654 fun place_breakpoint
(breakpoint
: Breakpoint)
656 if not self.breakpoints
.keys
.has
(breakpoint
.file
) then
657 self.breakpoints
[breakpoint
.file
] = new HashSet[Breakpoint]
659 if find_breakpoint
(breakpoint
.file
, breakpoint
.line
) == null then
660 self.breakpoints
[breakpoint
.file
].add
(breakpoint
)
661 print
"Breakpoint added on line {breakpoint.line} for file {breakpoint.file}"
663 print
"Breakpoint already present on line {breakpoint.line} for file {breakpoint.file}"
667 #Places a breakpoint that will trigger once and be destroyed afterwards
668 fun process_place_tbreak_fun
(parts_of_command
: Array[String])
670 var bp
= get_breakpoint_from_command
(parts_of_command
)
680 #######################################################################
681 ## Breakpoint removing functions ##
682 #######################################################################
684 # Removes a breakpoint on line 'line_to_break' for file 'file_to_break'
685 fun remove_breakpoint
(file_to_break
: String, line_to_break
: Int)
687 if self.breakpoints
.keys
.has
(file_to_break
) then
688 var bp
= find_breakpoint
(file_to_break
, line_to_break
)
691 self.breakpoints
[file_to_break
].remove
(bp
)
692 print
"Breakpoint removed on line {line_to_break} for file {file_to_break}"
697 print
"No breakpoint existing on line {line_to_break} for file {file_to_break}"
700 #######################################################################
701 ## Breakpoint searching functions ##
702 #######################################################################
704 # Finds a breakpoint for 'file' and 'line' in the class HashMap
705 fun find_breakpoint
(file
: String, line
: Int): nullable Breakpoint
707 if self.breakpoints
.keys
.has
(file
)
709 for i
in self.breakpoints
[file
]
721 #######################################################################
722 ## Command listing function ##
723 #######################################################################
725 # Lists the commands available when using the debugger
728 print
"\nCommand not recognized\n"
729 print
"Commands accepted : \n"
730 print
"[break/b] line : Adds a breakpoint on line *line_nb* of the current file\n"
731 print
"[break/b] file_name line_nb : Adds a breakpoint on line *line_nb* of file *file_name* \n"
732 print
"[p/print] variable : [p/print] * shows the status of all the variables\n"
733 print
"s : steps in on the current function\n"
734 print
"n : steps-over the current instruction\n"
735 print
"finish : steps out of the current function\n"
736 print
"variable as alias : Adds an alias called *alias* for the variable *variable*"
737 print
"An alias can reference another alias\n"
738 print
"[d/delete] line_nb : Removes a breakpoint on line *line_nb* of the current file \n"
739 print
"[d/delete] file_name line_nb : Removes a breakpoint on line *line_nb* of file *file_name* \n"
740 print
"kill : kills the current program (Exits with an error and stack trace)\n"