debug 1.4.0 → 1.9.2

Sign up to get free protection for your applications and to get access to all the features.
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