debug 1.0.0.beta6 → 1.0.0.rc2

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/Rakefile CHANGED
@@ -17,7 +17,8 @@ end
17
17
 
18
18
  task :default => [:clobber, :compile, :test, 'README.md']
19
19
 
20
- file 'README.md' => ['lib/debug/session.rb', 'exe/rdbg', 'misc/README.md.erb'] do
20
+ file 'README.md' => ['lib/debug/session.rb', 'lib/debug/config.rb',
21
+ 'exe/rdbg', 'misc/README.md.erb'] do
21
22
  require_relative 'lib/debug/session'
22
23
  require 'erb'
23
24
  File.write 'README.md', ERB.new(File.read('misc/README.md.erb')).result
data/TODO.md CHANGED
@@ -2,14 +2,11 @@
2
2
 
3
3
  ## Basic functionality
4
4
 
5
- * VScode DAP support
6
5
  * Support Ractors
7
6
  * Signal (SIGINT) support
8
- * Remote connection with openssl
9
7
 
10
8
  ## UI
11
9
 
12
- * Coloring
13
10
  * Interactive breakpoint setting
14
11
  * irb integration
15
12
  * Web browser integrated UI
@@ -28,6 +25,3 @@
28
25
 
29
26
  ## Tests
30
27
 
31
- * Test framework
32
- * Tests for commands
33
- * Tests for remote debugging
data/debug.gemspec CHANGED
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.extensions = ['ext/debug/extconf.rb']
27
27
 
28
28
  spec.add_dependency "irb" # for its color_printer class, which was added after 1.3
29
+ spec.add_dependency "reline", ">= 0.2.7"
29
30
  end
data/exe/rdbg CHANGED
@@ -1,27 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require_relative '../lib/debug/config'
4
- config = DEBUGGER__.parse_argv(ARGV)
4
+ config = DEBUGGER__::Config::parse_argv(ARGV)
5
5
 
6
6
  case config[:mode]
7
7
  when :start
8
8
  require 'rbconfig'
9
9
 
10
10
  libpath = File.join(File.expand_path(File.dirname(__dir__)), 'lib/debug')
11
- start_mode = config[:remote] ? "open" : 'run'
11
+ start_mode = config[:remote] ? "open" : 'start'
12
12
  cmd = config[:command] ? ARGV.shift : RbConfig.ruby
13
13
 
14
- env = ::DEBUGGER__.config_to_env_hash(config)
14
+ env = ::DEBUGGER__::Config.config_to_env_hash(config)
15
15
  env['RUBYOPT'] = "-r #{libpath}/#{start_mode}"
16
16
 
17
17
  exec(env, cmd, *ARGV)
18
18
 
19
19
  when :attach
20
20
  require_relative "../lib/debug/client"
21
-
22
- config.each{|k, v|
23
- DEBUGGER__::CONFIG[k] = v
24
- }
21
+ ::DEBUGGER__::CONFIG.update config
25
22
 
26
23
  begin
27
24
  if ARGV.empty? && config[:port]
data/ext/debug/debug.c CHANGED
@@ -22,7 +22,17 @@ static VALUE rb_cFrameInfo;
22
22
  static VALUE
23
23
  di_entry(VALUE loc, VALUE self, VALUE binding, VALUE iseq, VALUE klass, VALUE depth)
24
24
  {
25
- return rb_struct_new(rb_cFrameInfo, loc, self, binding, iseq, klass, depth, Qnil, Qnil, Qnil, Qnil, Qnil);
25
+ return rb_struct_new(rb_cFrameInfo,
26
+ // :location, :self, :binding, :iseq, :class, :frame_depth,
27
+ loc, self, binding, iseq, klass, depth,
28
+ // :has_return_value, :return_value,
29
+ Qnil, Qnil,
30
+ // :has_raised_exception, :raised_exception,
31
+ Qnil, Qnil,
32
+ // :show_line, :local_variables
33
+ Qnil,
34
+ // :_local_variables, :_callee # for recorder
35
+ Qnil, Qnil);
26
36
  }
27
37
 
28
38
  static int
@@ -49,15 +49,22 @@ module DEBUGGER__
49
49
  end
50
50
 
51
51
  def suspend
52
+ if @command
53
+ provider, pre_cmds, do_cmds = @command
54
+ nonstop = true if do_cmds
55
+ cmds = [*pre_cmds&.split(';;'), *do_cmds&.split(';;')]
56
+ SESSION.add_preset_commands provider, cmds, kick: false, continue: nonstop
57
+ end
58
+
52
59
  ThreadClient.current.on_breakpoint @tp, self
53
60
  end
54
61
 
55
62
  def to_s
56
- if @cond
57
- " if #{@cond}"
58
- else
59
- ""
60
- end
63
+ s = ''.dup
64
+ s << " if: #{@cond}" if defined?(@cond) && @cond
65
+ s << " pre: #{@command[1]}" if defined?(@command) && @command && @command[1]
66
+ s << " do: #{@command[2]}" if defined?(@command) && @command && @command[2]
67
+ s
61
68
  end
62
69
 
63
70
  def description
@@ -68,27 +75,29 @@ module DEBUGGER__
68
75
  false
69
76
  end
70
77
 
71
- class << self
72
- include Color
78
+ include Color
73
79
 
74
- def generate_label(name)
75
- colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
76
- end
80
+ def generate_label(name)
81
+ colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
77
82
  end
78
83
  end
79
84
 
80
- class LineBreakpoint < Breakpoint
81
- LABEL = generate_label("Line")
82
- PENDING_LABEL = generate_label("Line (pending)")
85
+ if RUBY_VERSION.to_f <= 2.7
86
+ # workaround for https://bugs.ruby-lang.org/issues/17302
87
+ TracePoint.new(:line){}.enable{}
88
+ end
83
89
 
90
+ class LineBreakpoint < Breakpoint
84
91
  attr_reader :path, :line, :iseq
85
92
 
86
- def initialize path, line, cond: nil, oneshot: false, hook_call: true
93
+ def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil
87
94
  @path = path
88
95
  @line = line
89
96
  @cond = cond
90
97
  @oneshot = oneshot
91
98
  @hook_call = hook_call
99
+ @command = command
100
+ @pending = false
92
101
 
93
102
  @iseq = nil
94
103
  @type = nil
@@ -96,23 +105,20 @@ module DEBUGGER__
96
105
  @key = [@path, @line].freeze
97
106
 
98
107
  super()
108
+
99
109
  try_activate
110
+ @pending = !@iseq
100
111
  end
101
112
 
102
113
  def setup
103
114
  return unless @type
104
115
 
105
- if !@cond
106
- @tp = TracePoint.new(@type) do |tp|
107
- delete if @oneshot
108
- suspend
109
- end
110
- else
111
- @tp = TracePoint.new(@type) do |tp|
116
+ @tp = TracePoint.new(@type) do |tp|
117
+ if @cond
112
118
  next unless safe_eval tp.binding, @cond
113
- delete if @oneshot
114
- suspend
115
119
  end
120
+ delete if @oneshot
121
+ suspend
116
122
  end
117
123
  end
118
124
 
@@ -138,9 +144,12 @@ module DEBUGGER__
138
144
 
139
145
  @key = [@path, @line].freeze
140
146
  SESSION.rehash_bps
141
-
142
147
  setup
143
148
  enable
149
+
150
+ if @pending && !@oneshot
151
+ DEBUGGER__.warn "#{self} is activated."
152
+ end
144
153
  end
145
154
 
146
155
  def activate_exact iseq, events, line
@@ -162,7 +171,6 @@ module DEBUGGER__
162
171
  end
163
172
 
164
173
  def duplicable?
165
- # only binding.bp or DEBUGGER__.console are duplicable
166
174
  @oneshot
167
175
  end
168
176
 
@@ -214,9 +222,9 @@ module DEBUGGER__
214
222
  oneshot = @oneshot ? " (oneshot)" : ""
215
223
 
216
224
  if @iseq
217
- "#{LABEL} #{@path}:#{@line} (#{@type})#{oneshot}" + super
225
+ "#{generate_label("Line")} #{@path}:#{@line} (#{@type})#{oneshot}" + super
218
226
  else
219
- "#{PENDING_LABEL} #{@path}:#{@line}#{oneshot}" + super
227
+ "#{generate_label("Line (pending)")} #{@path}:#{@line}#{oneshot}" + super
220
228
  end
221
229
  end
222
230
 
@@ -226,21 +234,28 @@ module DEBUGGER__
226
234
  end
227
235
 
228
236
  class CatchBreakpoint < Breakpoint
229
- LABEL = generate_label("Catch")
230
237
  attr_reader :last_exc
238
+ include SkipPathHelper
231
239
 
232
- def initialize pat
240
+ def initialize pat, cond: nil, command: nil
233
241
  @pat = pat.freeze
234
242
  @key = [:catch, @pat].freeze
235
243
  @last_exc = nil
236
244
 
245
+ @cond = cond
246
+ @command = command
247
+
237
248
  super()
238
249
  end
239
250
 
240
251
  def setup
241
252
  @tp = TracePoint.new(:raise){|tp|
253
+ next if skip_path?(tp.path)
242
254
  exc = tp.raised_exception
255
+ next if SystemExit === exc
256
+ next if !safe_eval(tp.binding, @cond) if @cond
243
257
  should_suspend = false
258
+
244
259
  exc.class.ancestors.each{|cls|
245
260
  if @pat === cls.name
246
261
  should_suspend = true
@@ -253,7 +268,7 @@ module DEBUGGER__
253
268
  end
254
269
 
255
270
  def to_s
256
- "#{LABEL} #{@pat.inspect}"
271
+ "#{generate_label("Catch")} #{@pat.inspect}"
257
272
  end
258
273
 
259
274
  def description
@@ -262,8 +277,6 @@ module DEBUGGER__
262
277
  end
263
278
 
264
279
  class CheckBreakpoint < Breakpoint
265
- LABEL = generate_label("Check")
266
-
267
280
  def initialize expr
268
281
  @expr = expr.freeze
269
282
  @key = [:check, @expr].freeze
@@ -275,8 +288,6 @@ module DEBUGGER__
275
288
  @tp = TracePoint.new(:line){|tp|
276
289
  next if tp.path.start_with? __dir__
277
290
  next if tp.path.start_with? '<internal:'
278
- # Skip when `JSON.generate` is called during tests
279
- next if tp.defined_class.to_s == '#<Class:JSON>' and ENV['RUBY_DEBUG_TEST_MODE']
280
291
 
281
292
  if safe_eval tp.binding, @expr
282
293
  suspend
@@ -285,13 +296,11 @@ module DEBUGGER__
285
296
  end
286
297
 
287
298
  def to_s
288
- "#{LABEL} #{@expr}"
299
+ "#{generate_label("Check")} #{@expr}"
289
300
  end
290
301
  end
291
302
 
292
303
  class WatchIVarBreakpoint < Breakpoint
293
- LABEL = generate_label("Watch")
294
-
295
304
  def initialize ivar, object, current
296
305
  @ivar = ivar.to_sym
297
306
  @object = object
@@ -332,41 +341,38 @@ module DEBUGGER__
332
341
  else
333
342
  "#{@current}"
334
343
  end
335
- "#{LABEL} #{@object} #{@ivar} = #{value_str}"
344
+ "#{generate_label("Watch")} #{@object} #{@ivar} = #{value_str}"
336
345
  end
337
346
  end
338
347
 
339
348
  class MethodBreakpoint < Breakpoint
340
- LABEL = generate_label("Method")
341
- PENDING_LABEL = generate_label("Method (pending)")
342
-
343
349
  attr_reader :sig_method_name, :method
344
350
 
345
- def initialize b, klass_name, op, method_name, cond
351
+ def initialize b, klass_name, op, method_name, cond: nil, command: nil
346
352
  @sig_klass_name = klass_name
347
353
  @sig_op = op
348
354
  @sig_method_name = method_name
349
355
  @klass_eval_binding = b
356
+ @override_method = false
350
357
 
351
358
  @klass = nil
352
359
  @method = nil
353
360
  @cond = cond
361
+ @cond_class = nil
362
+ @command = command
354
363
  @key = "#{klass_name}#{op}#{method_name}".freeze
355
364
 
356
365
  super(false)
357
366
  end
358
367
 
359
368
  def setup
360
- if @cond
361
- @tp = TracePoint.new(:call){|tp|
362
- next unless safe_eval tp.binding, @cond
363
- suspend
364
- }
365
- else
366
- @tp = TracePoint.new(:call){|tp|
367
- suspend
368
- }
369
- end
369
+ @tp = TracePoint.new(:call){|tp|
370
+ next if !safe_eval(tp.binding, @cond) if @cond
371
+ next if @cond_class && !tp.self.kind_of?(@cond_class)
372
+ next if @override_method ? (caller_locations(2, 1).first.to_s.start_with?(__dir__)) : tp.path.start_with?(__dir__)
373
+
374
+ suspend
375
+ }
370
376
  end
371
377
 
372
378
  def eval_class_name
@@ -391,34 +397,61 @@ module DEBUGGER__
391
397
  try_enable
392
398
  end
393
399
 
394
- def try_enable quiet: false
400
+ if RUBY_VERSION.to_f <= 2.6
401
+ def override klass
402
+ sig_method_name = @sig_method_name
403
+ klass.prepend Module.new{
404
+ define_method(sig_method_name) do |*args, &block|
405
+ super(*args, &block)
406
+ end
407
+ }
408
+ end
409
+ else
410
+ def override klass
411
+ sig_method_name = @sig_method_name
412
+ klass.prepend Module.new{
413
+ define_method(sig_method_name) do |*args, **kw, &block|
414
+ super(*args, **kw, &block)
415
+ end
416
+ }
417
+ end
418
+ end
419
+
420
+ def try_enable added: false
395
421
  eval_class_name
396
422
  search_method
397
423
 
398
424
  begin
399
425
  retried = false
426
+
400
427
  @tp.enable(target: @method)
428
+ DEBUGGER__.warn "#{self} is activated." if added
429
+
430
+ if @sig_op == '#'
431
+ @cond_class = @klass if @method.owner != @klass
432
+ else # '.'
433
+ @cond_class = @klass.singleton_class if @method.owner != @klass.singleton_class
434
+ end
401
435
 
402
436
  rescue ArgumentError
403
437
  raise if retried
404
438
  retried = true
405
- sig_method_name = @sig_method_name
406
439
 
407
440
  # maybe C method
408
- @klass.module_eval do
409
- orig_name = sig_method_name + '__orig__'
410
- alias_method orig_name, sig_method_name
411
- define_method(sig_method_name) do |*args|
412
- send(orig_name, *args)
413
- end
441
+ case @sig_op
442
+ when '.'
443
+ override @klass.singleton_class
444
+ when '#'
445
+ override @klass
414
446
  end
415
447
 
416
448
  # re-collect the method object after the above patch
417
449
  search_method
450
+ @override_method = true if @method
418
451
  retry
419
452
  end
420
453
  rescue Exception
421
- raise unless quiet
454
+ raise unless added
422
455
  end
423
456
 
424
457
  def sig
@@ -427,9 +460,9 @@ module DEBUGGER__
427
460
 
428
461
  def to_s
429
462
  if @method
430
- "#{LABEL} #{sig}"
463
+ "#{generate_label("Method")} #{sig} at #{@method.source_location.join(':')}"
431
464
  else
432
- "#{PENDING_LABEL} #{sig}"
465
+ "#{generate_label("Method (pending)")} #{sig}"
433
466
  end + super
434
467
  end
435
468
  end
data/lib/debug/client.rb CHANGED
@@ -5,6 +5,7 @@ require 'io/console/size'
5
5
 
6
6
  require_relative 'config'
7
7
  require_relative 'version'
8
+ require_relative 'console'
8
9
 
9
10
  # $VERBOSE = true
10
11
 
@@ -12,21 +13,11 @@ module DEBUGGER__
12
13
  class CommandLineOptionError < Exception; end
13
14
 
14
15
  class Client
15
- begin
16
- require 'readline'
17
- def readline
18
- Readline.readline("\n(rdbg:remote) ", true)
19
- end
20
- rescue LoadError
21
- def readline
22
- print "\n(rdbg:remote) "
23
- gets
24
- end
25
- end
26
-
27
16
  def initialize argv
28
17
  return util(argv) if String === argv
29
18
 
19
+ @console = Console.new
20
+
30
21
  case argv.size
31
22
  when 0
32
23
  connect_unix
@@ -47,11 +38,16 @@ module DEBUGGER__
47
38
  end
48
39
 
49
40
  @width = IO.console_size[1]
41
+ @width = 80 if @width == 0
50
42
  @width_changed = false
51
43
 
52
44
  send "version: #{VERSION} width: #{@width} cookie: #{CONFIG[:cookie]}"
53
45
  end
54
46
 
47
+ def readline
48
+ @console.readline "(rdbg:remote) "
49
+ end
50
+
55
51
  def util name
56
52
  case name
57
53
  when 'gen-sockpath'