debug 1.4.0 → 1.9.2

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +210 -6
  3. data/Gemfile +2 -0
  4. data/LICENSE.txt +0 -0
  5. data/README.md +161 -85
  6. data/Rakefile +33 -10
  7. data/TODO.md +8 -8
  8. data/debug.gemspec +9 -7
  9. data/exe/rdbg +23 -4
  10. data/ext/debug/debug.c +111 -21
  11. data/ext/debug/extconf.rb +23 -0
  12. data/ext/debug/iseq_collector.c +2 -0
  13. data/lib/debug/abbrev_command.rb +77 -0
  14. data/lib/debug/breakpoint.rb +102 -74
  15. data/lib/debug/client.rb +46 -12
  16. data/lib/debug/color.rb +0 -0
  17. data/lib/debug/config.rb +129 -36
  18. data/lib/debug/console.rb +46 -40
  19. data/lib/debug/dap_custom/traceInspector.rb +336 -0
  20. data/lib/debug/frame_info.rb +40 -25
  21. data/lib/debug/irb_integration.rb +37 -0
  22. data/lib/debug/local.rb +17 -11
  23. data/lib/debug/open.rb +0 -0
  24. data/lib/debug/open_nonstop.rb +0 -0
  25. data/lib/debug/prelude.rb +3 -2
  26. data/lib/debug/server.rb +126 -56
  27. data/lib/debug/server_cdp.rb +673 -248
  28. data/lib/debug/server_dap.rb +497 -261
  29. data/lib/debug/session.rb +899 -441
  30. data/lib/debug/source_repository.rb +122 -49
  31. data/lib/debug/start.rb +1 -1
  32. data/lib/debug/thread_client.rb +460 -155
  33. data/lib/debug/tracer.rb +10 -16
  34. data/lib/debug/version.rb +1 -1
  35. data/lib/debug.rb +7 -2
  36. data/misc/README.md.erb +106 -56
  37. metadata +14 -24
  38. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
  39. data/.github/ISSUE_TEMPLATE/custom.md +0 -10
  40. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  41. data/.github/pull_request_template.md +0 -9
  42. data/.github/workflows/ruby.yml +0 -34
  43. data/.gitignore +0 -12
  44. data/bin/console +0 -14
  45. data/bin/gentest +0 -30
  46. data/bin/setup +0 -8
  47. data/lib/debug/bp.vim +0 -68
@@ -6,11 +6,15 @@ module DEBUGGER__
6
6
  class Breakpoint
7
7
  include SkipPathHelper
8
8
 
9
- attr_reader :key
9
+ attr_reader :key, :skip_src
10
10
 
11
- def initialize do_enable = true
11
+ def initialize cond, command, path, do_enable: true
12
12
  @deleted = false
13
13
 
14
+ @cond = cond
15
+ @command = command
16
+ @path = path
17
+
14
18
  setup
15
19
  enable if do_enable
16
20
  end
@@ -82,8 +86,11 @@ module DEBUGGER__
82
86
  end
83
87
 
84
88
  def skip_path?(path)
85
- if @path
89
+ case @path
90
+ when Regexp
86
91
  !path.match?(@path)
92
+ when String
93
+ !path.include?(@path)
87
94
  else
88
95
  super
89
96
  end
@@ -108,7 +115,7 @@ module DEBUGGER__
108
115
  @oneshot = oneshot
109
116
  @key = [:iseq, @iseq.path, @iseq.first_lineno].freeze
110
117
 
111
- super()
118
+ super(nil, nil, nil)
112
119
  end
113
120
 
114
121
  def setup
@@ -124,25 +131,31 @@ module DEBUGGER__
124
131
  end
125
132
 
126
133
  class LineBreakpoint < Breakpoint
127
- attr_reader :path, :line, :iseq
134
+ attr_reader :path, :line, :iseq, :cond, :oneshot, :hook_call, :command
128
135
 
129
- def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil
130
- @path = path
136
+ def self.copy bp, root_iseq
137
+ nbp = LineBreakpoint.new bp.path, bp.line,
138
+ cond: bp.cond, oneshot: bp.oneshot, hook_call: bp.hook_call,
139
+ command: bp.command, skip_activate: true
140
+ nbp.try_activate root_iseq
141
+ nbp
142
+ end
143
+
144
+ def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil, skip_activate: false, skip_src: false
131
145
  @line = line
132
- @cond = cond
133
146
  @oneshot = oneshot
134
147
  @hook_call = hook_call
135
- @command = command
148
+ @skip_src = skip_src
136
149
  @pending = false
137
150
 
138
151
  @iseq = nil
139
152
  @type = nil
140
153
 
141
- @key = [@path, @line].freeze
154
+ @key = [path, @line].freeze
142
155
 
143
- super()
156
+ super(cond, command, path)
144
157
 
145
- try_activate
158
+ try_activate unless skip_activate
146
159
  @pending = !@iseq
147
160
  end
148
161
 
@@ -184,8 +197,10 @@ module DEBUGGER__
184
197
  enable
185
198
 
186
199
  if @pending && !@oneshot
187
- DEBUGGER__.warn "#{self} is activated."
200
+ DEBUGGER__.info "#{self} is activated."
188
201
  end
202
+
203
+ @pending = false
189
204
  end
190
205
 
191
206
  def activate_exact iseq, events, line
@@ -202,7 +217,7 @@ module DEBUGGER__
202
217
  when events.include?(:RUBY_EVENT_END)
203
218
  activate(iseq, :end, line)
204
219
  else
205
- # not actiavated
220
+ # not activated
206
221
  end
207
222
  end
208
223
 
@@ -212,42 +227,53 @@ module DEBUGGER__
212
227
 
213
228
  NearestISeq = Struct.new(:iseq, :line, :events)
214
229
 
215
- def try_activate
230
+ def iterate_iseq root_iseq
231
+ if root_iseq
232
+ is = [root_iseq]
233
+ while iseq = is.pop
234
+ yield iseq
235
+ iseq.each_child do |child_iseq|
236
+ is << child_iseq
237
+ end
238
+ end
239
+ else
240
+ ObjectSpace.each_iseq do |iseq|
241
+ if DEBUGGER__.compare_path((iseq.absolute_path || iseq.path), self.path) &&
242
+ iseq.first_lineno <= self.line &&
243
+ iseq.type != :ensure # ensure iseq is copied (duplicated)
244
+ yield iseq
245
+ end
246
+ end
247
+ end
248
+ end
249
+
250
+ def try_activate root_iseq = nil
216
251
  nearest = nil # NearestISeq
252
+ iterate_iseq root_iseq do |iseq|
253
+ iseq.traceable_lines_norec(line_events = {})
254
+ lines = line_events.keys.sort
255
+
256
+ if !lines.empty? && lines.last >= line
257
+ nline = lines.bsearch{|l| line <= l}
258
+ events = line_events[nline]
259
+
260
+ next if events == [:RUBY_EVENT_B_CALL]
261
+
262
+ if @hook_call &&
263
+ events.include?(:RUBY_EVENT_CALL) &&
264
+ self.line == iseq.first_lineno
265
+ nline = iseq.first_lineno
266
+ end
217
267
 
218
- ObjectSpace.each_iseq{|iseq|
219
- if (iseq.absolute_path || iseq.path) == self.path &&
220
- iseq.first_lineno <= self.line &&
221
- iseq.type != :ensure # ensure iseq is copied (duplicated)
222
-
223
- iseq.traceable_lines_norec(line_events = {})
224
- lines = line_events.keys.sort
225
-
226
- if !lines.empty? && lines.last >= line
227
- nline = lines.bsearch{|l| line <= l}
228
- events = line_events[nline]
229
-
230
- next if events == [:RUBY_EVENT_B_CALL]
231
-
232
- if @hook_call &&
233
- events.include?(:RUBY_EVENT_CALL) &&
234
- self.line == iseq.first_lineno
235
- nline = iseq.first_lineno
236
- end
237
-
238
- if !nearest || ((line - nline).abs < (line - nearest.line).abs)
239
- nearest = NearestISeq.new(iseq, nline, events)
240
- else
241
- if @hook_call && nearest.iseq.first_lineno <= iseq.first_lineno
242
- if (nearest.line > line && !nearest.events.include?(:RUBY_EVENT_CALL)) ||
243
- (events.include?(:RUBY_EVENT_CALL))
244
- nearest = NearestISeq.new(iseq, nline, events)
245
- end
246
- end
247
- end
268
+ if !nearest || ((line - nline).abs < (line - nearest.line).abs)
269
+ nearest = NearestISeq.new(iseq, nline, events)
270
+ elsif @hook_call &&
271
+ nearest.line == iseq.first_line &&
272
+ events.include?(:RUBY_EVENT_CALL)
273
+ nearest = NearestISeq.new(iseq, nline, events)
248
274
  end
249
275
  end
250
- }
276
+ end
251
277
 
252
278
  if nearest
253
279
  activate_exact nearest.iseq, nearest.events, nearest.line
@@ -267,6 +293,10 @@ module DEBUGGER__
267
293
  def inspect
268
294
  "<#{self.class.name} #{self.to_s}>"
269
295
  end
296
+
297
+ def path_is? path
298
+ DEBUGGER__.compare_path(@path, path)
299
+ end
270
300
  end
271
301
 
272
302
  class CatchBreakpoint < Breakpoint
@@ -277,11 +307,7 @@ module DEBUGGER__
277
307
  @key = [:catch, @pat].freeze
278
308
  @last_exc = nil
279
309
 
280
- @cond = cond
281
- @command = command
282
- @path = path
283
-
284
- super()
310
+ super(cond, command, path)
285
311
  end
286
312
 
287
313
  def setup
@@ -314,29 +340,41 @@ module DEBUGGER__
314
340
  end
315
341
 
316
342
  class CheckBreakpoint < Breakpoint
317
- def initialize expr, path
318
- @expr = expr.freeze
319
- @key = [:check, @expr].freeze
320
- @path = path
343
+ def initialize cond:, command: nil, path: nil
344
+ @key = [:check, cond].freeze
321
345
 
322
- super()
346
+ super(cond, command, path)
323
347
  end
324
348
 
325
349
  def setup
326
350
  @tp = TracePoint.new(:line){|tp|
327
- next if tp.path.start_with? __dir__
328
- next if tp.path.start_with? '<internal:'
351
+ next if SESSION.in_subsession? # TODO: Ractor support
329
352
  next if ThreadClient.current.management?
330
353
  next if skip_path?(tp.path)
331
354
 
332
- if safe_eval tp.binding, @expr
355
+ if need_suspend? safe_eval(tp.binding, @cond)
333
356
  suspend
334
357
  end
335
358
  }
336
359
  end
337
360
 
361
+ private def need_suspend? cond_result
362
+ map = ThreadClient.current.check_bp_fulfillment_map
363
+ if cond_result
364
+ if map[self]
365
+ false
366
+ else
367
+ map[self] = true
368
+ end
369
+ else
370
+ map[self] = false
371
+ end
372
+ end
373
+
338
374
  def to_s
339
- "#{generate_label("Check")} #{@expr}"
375
+ s = "#{generate_label("Check")}"
376
+ s += super
377
+ s
340
378
  end
341
379
  end
342
380
 
@@ -348,10 +386,7 @@ module DEBUGGER__
348
386
 
349
387
  @current = current
350
388
 
351
- @cond = cond
352
- @command = command
353
- @path = path
354
- super()
389
+ super(cond, command, path)
355
390
  end
356
391
 
357
392
  def watch_eval(tp)
@@ -374,9 +409,6 @@ module DEBUGGER__
374
409
 
375
410
  def setup
376
411
  @tp = TracePoint.new(:line, :return, :b_return){|tp|
377
- next if tp.path.start_with? __dir__
378
- next if tp.path.start_with? '<internal:'
379
-
380
412
  watch_eval(tp)
381
413
  }
382
414
  end
@@ -393,7 +425,7 @@ module DEBUGGER__
393
425
  end
394
426
 
395
427
  class MethodBreakpoint < Breakpoint
396
- attr_reader :sig_method_name, :method
428
+ attr_reader :sig_method_name, :method, :klass
397
429
 
398
430
  def initialize b, klass_name, op, method_name, cond: nil, command: nil, path: nil
399
431
  @sig_klass_name = klass_name
@@ -404,13 +436,10 @@ module DEBUGGER__
404
436
 
405
437
  @klass = nil
406
438
  @method = nil
407
- @cond = cond
408
439
  @cond_class = nil
409
- @command = command
410
- @path = path
411
440
  @key = "#{klass_name}#{op}#{method_name}".freeze
412
441
 
413
- super(false)
442
+ super(cond, command, path, do_enable: false)
414
443
  end
415
444
 
416
445
  def setup
@@ -419,7 +448,6 @@ module DEBUGGER__
419
448
  next if @cond_class && !tp.self.kind_of?(@cond_class)
420
449
 
421
450
  caller_location = caller_locations(2, 1).first.to_s
422
- next if caller_location.start_with?(__dir__)
423
451
  next if skip_path?(caller_location)
424
452
 
425
453
  suspend
@@ -476,7 +504,7 @@ module DEBUGGER__
476
504
  retried = false
477
505
 
478
506
  @tp.enable(target: @method)
479
- DEBUGGER__.warn "#{self} is activated." if added
507
+ DEBUGGER__.info "#{self} is activated." if added
480
508
 
481
509
  if @sig_op == '#'
482
510
  @cond_class = @klass if @method.owner != @klass
data/lib/debug/client.rb CHANGED
@@ -18,9 +18,16 @@ module DEBUGGER__
18
18
  case name
19
19
  when 'gen-sockpath'
20
20
  puts DEBUGGER__.create_unix_domain_socket_name
21
+ when 'gen-portpath'
22
+ port_path = File.join(DEBUGGER__.unix_domain_socket_dir, 'tcp_port')
23
+ File.unlink port_path if File.exist?(port_path)
24
+ puts port_path
21
25
  when 'list-socks'
22
26
  cleanup_unix_domain_sockets
23
27
  puts list_connections
28
+ when 'list-socks-verbose'
29
+ cleanup_unix_domain_sockets
30
+ puts list_connections verbose: true
24
31
  when 'setup-autoload'
25
32
  setup_autoload
26
33
  else
@@ -38,7 +45,7 @@ module DEBUGGER__
38
45
  when /csh/
39
46
  :csh
40
47
  when /zsh/
41
- :szh
48
+ :zsh
42
49
  when /dash/
43
50
  :dash
44
51
  else
@@ -76,7 +83,7 @@ module DEBUGGER__
76
83
 
77
84
  def cleanup_unix_domain_sockets
78
85
  Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*') do |file|
79
- if /(\d+)$/ =~ file
86
+ if File.socket?(file) && (/-(\d+)-\d+$/ =~ file || /-(\d+)$/ =~ file)
80
87
  begin
81
88
  Process.kill(0, $1.to_i)
82
89
  rescue Errno::EPERM
@@ -87,8 +94,24 @@ module DEBUGGER__
87
94
  end
88
95
  end
89
96
 
90
- def list_connections
91
- Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*')
97
+ def list_connections verbose: false
98
+ socks = Dir.glob(DEBUGGER__.create_unix_domain_socket_name_prefix + '*').find_all do |path|
99
+ File.socket?(path)
100
+ end
101
+
102
+ if verbose
103
+ socks = socks.map{|sock_path|
104
+ Socket.unix(sock_path){|sock|
105
+ sock.puts "info cookie: #{CONFIG[:cookie] || '-'}"
106
+ pid = sock.gets.chomp
107
+ _dbg = sock.gets.chomp
108
+ _unm = sock.gets.chomp
109
+ [sock_path, pid]
110
+ }
111
+ }
112
+ end
113
+
114
+ socks
92
115
  end
93
116
  end
94
117
 
@@ -115,7 +138,10 @@ module DEBUGGER__
115
138
  @width = IO.console_size[1]
116
139
  @width = 80 if @width == 0
117
140
 
118
- send "version: #{VERSION} width: #{@width} cookie: #{CONFIG[:cookie]}"
141
+ send "version: #{VERSION} " +
142
+ "width: #{@width} " +
143
+ "cookie: #{CONFIG[:cookie] || '-'} " +
144
+ "nonstop: #{CONFIG[:nonstop] ? 'true' : 'false'}"
119
145
  end
120
146
 
121
147
  def deactivate
@@ -148,9 +174,10 @@ module DEBUGGER__
148
174
  when 1
149
175
  @s = Socket.unix(files.first)
150
176
  else
177
+ files = Client.list_connections verbose: true
151
178
  $stderr.puts "Please select a debug session:"
152
- files.each{|f|
153
- $stderr.puts " #{File.basename(f)}"
179
+ files.each{|(f, desc)|
180
+ $stderr.puts " #{File.basename(f)} (#{desc})"
154
181
  }
155
182
  exit
156
183
  end
@@ -167,6 +194,8 @@ module DEBUGGER__
167
194
  end
168
195
 
169
196
  def connect
197
+ pre_commands = (CONFIG[:commands] || '').split(';;')
198
+
170
199
  trap(:SIGINT){
171
200
  send "pause"
172
201
  }
@@ -175,7 +204,7 @@ module DEBUGGER__
175
204
  trap(:SIGWINCH){
176
205
  @width = IO.console_size[1]
177
206
  }
178
- rescue ArgumentError => e
207
+ rescue ArgumentError
179
208
  @width = 80
180
209
  end
181
210
 
@@ -193,7 +222,12 @@ module DEBUGGER__
193
222
  prev_trap = trap(:SIGINT, 'DEFAULT')
194
223
 
195
224
  begin
196
- line = readline
225
+ if pre_commands.empty?
226
+ line = readline
227
+ else
228
+ line = pre_commands.shift
229
+ puts "(rdbg:remote:command) #{line}"
230
+ end
197
231
  rescue Interrupt
198
232
  retry
199
233
  ensure
@@ -206,7 +240,7 @@ module DEBUGGER__
206
240
  when /^ask (\d+) (.*)/
207
241
  pid = $1
208
242
  print $2
209
- send "answer #{pid} #{gets || ''}"
243
+ send "answer #{pid} #{$stdin.gets || ''}"
210
244
 
211
245
  when /^quit/
212
246
  raise 'quit'
@@ -215,8 +249,8 @@ module DEBUGGER__
215
249
  puts "(unknown) #{line.inspect}"
216
250
  end
217
251
  end
218
- rescue
219
- STDERR.puts "disconnected (#{$!})"
252
+ rescue => e
253
+ STDERR.puts "disconnected (#{e})"
220
254
  exit
221
255
  ensure
222
256
  deactivate
data/lib/debug/color.rb CHANGED
File without changes