debug 1.0.0.beta4 → 1.0.0.beta5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/debug/session.rb CHANGED
@@ -1,8 +1,13 @@
1
1
  
2
+ # skip to load debugger for bundle exec
3
+ return if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
4
+
5
+ require_relative 'config'
2
6
  require_relative 'thread_client'
3
7
  require_relative 'source_repository'
4
8
  require_relative 'breakpoint'
5
- require_relative 'config'
9
+
10
+ require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
6
11
 
7
12
  class RubyVM::InstructionSequence
8
13
  def traceable_lines_norec lines
@@ -40,6 +45,10 @@ class RubyVM::InstructionSequence
40
45
  def last_line
41
46
  self.to_a[4][:code_location][2]
42
47
  end
48
+
49
+ def first_line
50
+ self.to_a[4][:code_location][0]
51
+ end
43
52
  end
44
53
 
45
54
  module DEBUGGER__
@@ -51,7 +60,7 @@ module DEBUGGER__
51
60
  # [file, line] => LineBreakpoint
52
61
  # "Error" => CatchBreakpoint
53
62
  # "Foo#bar" => MethodBreakpoint
54
- # [:watch, expr] => WatchExprBreakpoint
63
+ # [:watch, ivar] => WatchIVarBreakpoint
55
64
  # [:check, expr] => CheckBreakpoint
56
65
  @th_clients = {} # {Thread => ThreadClient}
57
66
  @q_evt = Queue.new
@@ -60,76 +69,20 @@ module DEBUGGER__
60
69
  @tc_id = 0
61
70
  @initial_commands = []
62
71
 
72
+ @frame_map = {} # {id => [threadId, frame_depth]} for DAP
73
+ @var_map = {1 => [:globals], } # {id => ...} for DAP
74
+ @src_map = {} # {id => src}
75
+
63
76
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
64
- ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
77
+ unless @management_threads.include? Thread.current
78
+ ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
79
+ end
65
80
  }
66
81
  @tp_load_script.enable
67
82
 
68
83
  @session_server = Thread.new do
69
84
  Thread.current.abort_on_exception = true
70
-
71
- while evt = @q_evt.pop
72
- tc, output, ev, *ev_args = evt
73
- output.each{|str| @ui.puts str}
74
-
75
- case ev
76
- when :load
77
- iseq, src = ev_args
78
- on_load iseq, src
79
- tc << :continue
80
- when :thread_begin
81
- th = ev_args.shift
82
- on_thread_begin th
83
- tc << :continue
84
- when :suspend
85
- case ev_args.first
86
- when :breakpoint
87
- bp, i = bp_index ev_args[1]
88
- if bp
89
- @ui.puts "\nStop by \##{i} #{bp}"
90
- end
91
- when :trap
92
- @ui.puts ''
93
- @ui.puts "\nStop by #{ev_args[1]}"
94
- end
95
-
96
- if @displays.empty?
97
- wait_command_loop tc
98
- else
99
- tc << [:eval, :display, @displays]
100
- end
101
- when :result
102
- case ev_args.first
103
- when :watch
104
- bp = ev_args[1]
105
- @bps[bp.key] = bp
106
- show_bps bp
107
- when :try_display
108
- failed_results = ev_args[1]
109
- if failed_results.size > 0
110
- i, msg = failed_results.last
111
- if i+1 == @displays.size
112
- @ui.puts "canceled: #{@displays.pop}"
113
- end
114
- end
115
- when :method_breakpoint
116
- bp = ev_args[1]
117
- if bp
118
- @bps[bp.key] = bp
119
- show_bps bp
120
- else
121
- # can't make a bp
122
- end
123
- else
124
- # ignore
125
- end
126
-
127
- wait_command_loop tc
128
- end
129
- end
130
- ensure
131
- @bps.each{|k, bp| bp.disable}
132
- @th_clients.each{|th, thc| thc.close}
85
+ session_server_main
133
86
  end
134
87
 
135
88
  @management_threads = [@session_server]
@@ -138,11 +91,84 @@ module DEBUGGER__
138
91
  setup_threads
139
92
 
140
93
  @tp_thread_begin = TracePoint.new(:thread_begin){|tp|
141
- ThreadClient.current.on_thread_begin Thread.current
94
+ unless @management_threads.include?(th = Thread.current)
95
+ ThreadClient.current.on_thread_begin th
96
+ end
142
97
  }
143
98
  @tp_thread_begin.enable
144
99
  end
145
100
 
101
+ def session_server_main
102
+ while evt = @q_evt.pop
103
+ # varible `@internal_info` is only used for test
104
+ tc, output, ev, @internal_info, *ev_args = evt
105
+ output.each{|str| @ui.puts str}
106
+
107
+ case ev
108
+ when :load
109
+ iseq, src = ev_args
110
+ on_load iseq, src
111
+ @ui.event :load
112
+ tc << :continue
113
+ when :thread_begin
114
+ th = ev_args.shift
115
+ on_thread_begin th
116
+ @ui.event :thread_begin, th
117
+ tc << :continue
118
+ when :suspend
119
+ case ev_args.first
120
+ when :breakpoint
121
+ bp, i = bp_index ev_args[1]
122
+ @ui.event :suspend_bp, i, bp
123
+ when :trap
124
+ @ui.event :suspend_trap, ev_args[1]
125
+ else
126
+ @ui.event :suspended
127
+ end
128
+
129
+ if @displays.empty?
130
+ wait_command_loop tc
131
+ else
132
+ tc << [:eval, :display, @displays]
133
+ end
134
+ when :result
135
+ case ev_args.first
136
+ when :watch
137
+ bp = ev_args[1]
138
+ @bps[bp.key] = bp
139
+ show_bps bp
140
+ when :try_display
141
+ failed_results = ev_args[1]
142
+ if failed_results.size > 0
143
+ i, _msg = failed_results.last
144
+ if i+1 == @displays.size
145
+ @ui.puts "canceled: #{@displays.pop}"
146
+ end
147
+ end
148
+ when :method_breakpoint
149
+ bp = ev_args[1]
150
+ if bp
151
+ @bps[bp.key] = bp
152
+ show_bps bp
153
+ else
154
+ # can't make a bp
155
+ end
156
+ else
157
+ # ignore
158
+ end
159
+
160
+ wait_command_loop tc
161
+
162
+ when :dap_result
163
+ dap_event ev_args # server.rb
164
+ wait_command_loop tc
165
+ end
166
+ end
167
+ ensure
168
+ @bps.each{|k, bp| bp.disable}
169
+ @th_clients.each{|th, thc| thc.close}
170
+ end
171
+
146
172
  def add_initial_commands cmds
147
173
  cmds.each{|c|
148
174
  c.gsub('#.*', '').strip!
@@ -150,11 +176,11 @@ module DEBUGGER__
150
176
  }
151
177
  end
152
178
 
153
- def source path
179
+ def source iseq
154
180
  if CONFIG[:use_colorize]
155
- @sr.get_colored(path)
181
+ @sr.get_colored(iseq)
156
182
  else
157
- @sr.get(path)
183
+ @sr.get(iseq)
158
184
  end
159
185
  end
160
186
 
@@ -182,12 +208,24 @@ module DEBUGGER__
182
208
 
183
209
  def wait_command
184
210
  if @initial_commands.empty?
211
+ @ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
185
212
  line = @ui.readline
186
213
  else
187
214
  line = @initial_commands.shift.strip
188
215
  @ui.puts "(rdbg:init) #{line}"
189
216
  end
190
217
 
218
+ case line
219
+ when String
220
+ process_command line
221
+ when Hash
222
+ process_dap_request line # defined in server.rb
223
+ else
224
+ raise "unexpected input: #{line.inspect}"
225
+ end
226
+ end
227
+
228
+ def process_command line
191
229
  if line.empty?
192
230
  if @repl_prev_line
193
231
  line = @repl_prev_line
@@ -324,8 +362,8 @@ module DEBUGGER__
324
362
  end
325
363
  return :retry
326
364
 
327
- # * `watch <expr>`
328
- # * Stop the execution when the result of `<expr>` is changed.
365
+ # * `watch @ivar`
366
+ # * Stop the execution when the result of current scope's `@ivar` is changed.
329
367
  # * Note that this feature is super slow.
330
368
  when 'wat', 'watch'
331
369
  if arg
@@ -407,7 +445,7 @@ module DEBUGGER__
407
445
  # * `i[nfo]`, `i[nfo] l[ocal[s]]`
408
446
  # * Show information about the current frame (local variables)
409
447
  # * It includes `self` as `%self` and a return value as `%return`.
410
- # * `i[nfo] th[read[s]]
448
+ # * `i[nfo] th[read[s]]`
411
449
  # * Show all threads (same as `th[read]`).
412
450
  when 'i', 'info'
413
451
  case arg
@@ -461,9 +499,11 @@ module DEBUGGER__
461
499
  case arg
462
500
  when 'on'
463
501
  dir = __dir__
464
- @tracer ||= TracePoint.new(){|tp|
502
+ @tracer ||= TracePoint.new(:call, :return, :b_call, :b_return, :line, :class, :end){|tp|
465
503
  next if File.dirname(tp.path) == dir
466
504
  next if tp.path == '<internal:trace_point>'
505
+ # Skip when `JSON.generate` is called during tests
506
+ next if tp.binding.eval('self').to_s == 'JSON' and ENV['RUBY_DEBUG_TEST_MODE']
467
507
  # next if tp.event != :line
468
508
  @ui.puts pretty_tp(tp)
469
509
  }
@@ -725,6 +765,8 @@ module DEBUGGER__
725
765
  case
726
766
  when th == Thread.current
727
767
  # ignore
768
+ when @management_threads.include?(th)
769
+ # ignore
728
770
  when @th_clients.has_key?(th)
729
771
  thcs << @th_clients[th]
730
772
  else
@@ -748,8 +790,13 @@ module DEBUGGER__
748
790
  end
749
791
  end
750
792
 
793
+ def managed_thread_clients
794
+ thcs, _unmanaged_ths = update_thread_list
795
+ thcs
796
+ end
797
+
751
798
  def thread_switch n
752
- thcs, unmanaged_ths = update_thread_list
799
+ thcs, _unmanaged_ths = update_thread_list
753
800
 
754
801
  if tc = thcs[n]
755
802
  if tc.mode
@@ -867,12 +914,16 @@ module DEBUGGER__
867
914
  def resolve_path file
868
915
  File.realpath(File.expand_path(file))
869
916
  rescue Errno::ENOENT
870
- return file if file == '-e'
871
- $LOAD_PATH.each do |lp|
872
- libpath = File.join(lp, file)
873
- return File.realpath(libpath)
874
- rescue Errno::ENOENT
875
- # next
917
+ case file
918
+ when '-e', '-'
919
+ return file
920
+ else
921
+ $LOAD_PATH.each do |lp|
922
+ libpath = File.join(lp, file)
923
+ return File.realpath(libpath)
924
+ rescue Errno::ENOENT
925
+ # next
926
+ end
876
927
  end
877
928
 
878
929
  raise
@@ -917,6 +968,29 @@ module DEBUGGER__
917
968
  end
918
969
  end
919
970
  end
971
+
972
+ def width
973
+ @ui.width
974
+ end
975
+
976
+ def check_forked
977
+ unless @session_server.status
978
+ # TODO: Support it
979
+ raise 'DEBUGGER: stop at forked process is not supported yet.'
980
+ end
981
+ end
982
+ end
983
+
984
+ class UI_Base
985
+ def event type, *args
986
+ case type
987
+ when :suspend_bp
988
+ i, bp = *args
989
+ puts "\nStop by \##{i} #{bp}" if bp
990
+ when :suspend_trap
991
+ puts "\nStop by #{args.first}"
992
+ end
993
+ end
920
994
  end
921
995
 
922
996
  # manual configuration methods
@@ -940,7 +1014,7 @@ module DEBUGGER__
940
1014
  when dir_prefix
941
1015
  when %r{rubygems/core_ext/kernel_require\.rb}
942
1016
  else
943
- return loc
1017
+ return loc if loc.absolute_path
944
1018
  end
945
1019
  end
946
1020
  nil
@@ -952,6 +1026,7 @@ module DEBUGGER__
952
1026
  set_config(kw)
953
1027
 
954
1028
  require_relative 'console'
1029
+
955
1030
  initialize_session UI_Console.new
956
1031
 
957
1032
  @prev_handler = trap(:SIGINT){
@@ -992,8 +1067,15 @@ module DEBUGGER__
992
1067
  # ::DEBUGGER__.add_catch_breakpoint 'RuntimeError'
993
1068
 
994
1069
  Binding.module_eval do
995
- ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1
996
- def bp; nil; end
1070
+ def bp command: nil
1071
+ if command
1072
+ cmds = command.split(";;")
1073
+ SESSION.add_initial_commands cmds
1074
+ end
1075
+
1076
+ ::DEBUGGER__.add_line_breakpoint __FILE__, __LINE__ + 1, oneshot: true
1077
+ true
1078
+ end
997
1079
  end
998
1080
 
999
1081
  if !::DEBUGGER__::CONFIG[:nonstop]
@@ -1018,13 +1100,15 @@ module DEBUGGER__
1018
1100
  }
1019
1101
 
1020
1102
  # debug commands file
1021
- [::DEBUGGER__::CONFIG[:init_script],
1103
+ [init_script = ::DEBUGGER__::CONFIG[:init_script],
1022
1104
  './.rdbgrc',
1023
1105
  File.expand_path('~/.rdbgrc')].each{|path|
1024
- next unless path
1106
+ next unless path
1025
1107
 
1026
1108
  if File.file? path
1027
1109
  ::DEBUGGER__::SESSION.add_initial_commands File.readlines(path)
1110
+ elsif path == init_script
1111
+ warn "Not found: #{path}"
1028
1112
  end
1029
1113
  }
1030
1114
 
@@ -1086,6 +1170,7 @@ module DEBUGGER__
1086
1170
  end
1087
1171
 
1088
1172
  class ::Module
1173
+ undef method_added
1089
1174
  def method_added mid; end
1090
1175
  def singleton_method_added mid; end
1091
1176
  end
@@ -1099,4 +1184,14 @@ module DEBUGGER__
1099
1184
  end
1100
1185
 
1101
1186
  METHOD_ADDED_TRACKER = self.create_method_added_tracker
1187
+
1188
+ SHORT_INSPECT_LENGTH = 40
1189
+ def self.short_inspect obj, use_short = true
1190
+ str = obj.inspect
1191
+ if use_short && str.length > SHORT_INSPECT_LENGTH
1192
+ str[0...SHORT_INSPECT_LENGTH] + '...'
1193
+ else
1194
+ str
1195
+ end
1196
+ end
1102
1197
  end
@@ -1,55 +1,75 @@
1
- require 'irb/color' # IRB::Color.colorize_code
1
+ require_relative 'color'
2
2
 
3
3
  module DEBUGGER__
4
4
  class SourceRepository
5
+ SrcInfo = Struct.new(:src, :colored)
6
+
5
7
  def initialize
6
- @files = {} # filename => [src, iseq]
7
- @color_files = {}
8
+ @files = {} # filename => SrcInfo
8
9
  end
9
10
 
10
11
  def add iseq, src
11
- path = iseq.absolute_path
12
- path = '-e' if iseq.path == '-e'
13
- add_path path, src: src
12
+ if (path = iseq.absolute_path) && File.exist?(path)
13
+ add_path path
14
+ elsif src
15
+ add_iseq iseq, src
16
+ end
14
17
  end
15
18
 
16
- def add_path path, src: nil
17
- case
18
- when src
19
- if File.file?(path)
20
- path = '(eval)' + path
21
- src = nil
22
- end
23
- when path == '-e'
24
- when path
25
- begin
26
- src = File.read(path)
27
- rescue SystemCallError
28
- end
29
- else
30
- src = nil
19
+ def all_iseq iseq, rs = []
20
+ rs << iseq
21
+ iseq.each_child{|ci|
22
+ all_iseq(ci, rs)
23
+ }
24
+ rs
25
+ end
26
+
27
+ private def add_iseq iseq, src
28
+ line = iseq.first_line
29
+ if line > 1
30
+ src = ("\n" * (line - 1)) + src
31
31
  end
32
+ si = SrcInfo.new(src.lines)
32
33
 
33
- if src
34
+ all_iseq(iseq).each{|e|
35
+ e.instance_variable_set(:@debugger_si, si)
36
+ e.freeze
37
+ }
38
+ end
39
+
40
+ private def add_path path
41
+ begin
42
+ src = File.read(path)
34
43
  src = src.gsub("\r\n", "\n") # CRLF -> LF
35
- @files[path] = src.lines
44
+ @files[path] = SrcInfo.new(src.lines)
45
+ rescue SystemCallError
36
46
  end
37
47
  end
38
48
 
39
- def get path
40
- if @files.has_key? path
49
+ private def get_si iseq
50
+ return unless iseq
51
+
52
+ if iseq.instance_variable_defined?(:@debugger_si)
53
+ iseq.instance_variable_get(:@debugger_si)
54
+ elsif @files.has_key?(path = iseq.absolute_path)
41
55
  @files[path]
42
- else
43
- add_path path
56
+ elsif path
57
+ add_path(path)
44
58
  end
45
59
  end
46
60
 
47
- def get_colored path
48
- if src_lines = @color_files[path]
49
- return src_lines
50
- else
51
- if src_lines = get(path)
52
- @color_files[path] = IRB::Color.colorize_code(src_lines.join).lines
61
+ def get iseq
62
+ if si = get_si(iseq)
63
+ si.src
64
+ end
65
+ end
66
+
67
+ include Color
68
+
69
+ def get_colored iseq
70
+ if si = get_si(iseq)
71
+ si.colored || begin
72
+ si.colored = colorize_code(si.src.join).lines
53
73
  end
54
74
  end
55
75
  end