debug 1.0.0.beta5 → 1.0.0.rc1

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/bin/gentest ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ require_relative '../test/tool/test_builder'
6
+
7
+ file_info = {}
8
+
9
+ OptionParser.new do |opt|
10
+ opt.banner = 'Usage: bin/gentest [file] [option]'
11
+ opt.on('-m METHOD', 'Method name in the test file') do |m|
12
+ file_info[:method] = m
13
+ end
14
+ opt.on('-c CLASS', 'Class name in the test file') do |c|
15
+ file_info[:class] = c
16
+ end
17
+ opt.parse!(ARGV)
18
+ end
19
+
20
+ exit if ARGV.empty?
21
+
22
+ DEBUGGER__::TestBuilder.new(ARGV, file_info[:method], file_info[:class]).start
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,37 +1,30 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/debug/client'
4
-
5
- config = DEBUGGER__.parse_argv(ARGV)
3
+ require_relative '../lib/debug/config'
4
+ config = DEBUGGER__::Config::parse_argv(ARGV)
6
5
 
7
6
  case config[:mode]
8
7
  when :start
9
8
  require 'rbconfig'
10
9
 
11
- if config[:remote]
12
- start_mode = "debug/open"
13
- else
14
- start_mode = "debug/run"
15
- end
10
+ libpath = File.join(File.expand_path(File.dirname(__dir__)), 'lib/debug')
11
+ start_mode = config[:remote] ? "open" : 'start'
12
+ cmd = config[:command] ? ARGV.shift : RbConfig.ruby
16
13
 
17
- ::DEBUGGER__.config_to_env(config)
14
+ env = ::DEBUGGER__::Config.config_to_env_hash(config)
15
+ env['RUBYOPT'] = "-r #{libpath}/#{start_mode}"
18
16
 
19
- cmd = config[:command] ? ARGV.shift : RbConfig.ruby
20
- exec({'RUBYOPT' => "-I#{File.expand_path(File.dirname(__dir__))}/lib -r #{start_mode}"},
21
- cmd, *ARGV)
17
+ exec(env, cmd, *ARGV)
22
18
 
23
19
  when :attach
24
20
  require_relative "../lib/debug/client"
25
-
26
- config.each{|k, v|
27
- DEBUGGER__::CONFIG[k] = v
28
- }
21
+ ::DEBUGGER__::CONFIG.update config
29
22
 
30
23
  begin
31
24
  if ARGV.empty? && config[:port]
32
- connect [config[:host], config[:port]].compact
25
+ DEBUGGER__::Client.new([config[:host], config[:port]].compact).connect
33
26
  else
34
- connect ARGV
27
+ DEBUGGER__::Client.new(ARGV).connect
35
28
  end
36
29
  rescue DEBUGGER__::CommandLineOptionError
37
30
  puts opt.help
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'color'
2
4
 
3
5
  module DEBUGGER__
@@ -47,42 +49,55 @@ module DEBUGGER__
47
49
  end
48
50
 
49
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
+
50
59
  ThreadClient.current.on_breakpoint @tp, self
51
60
  end
52
61
 
53
62
  def to_s
54
- if @cond
55
- " if #{@cond}"
56
- else
57
- ""
58
- 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
59
68
  end
60
69
 
61
70
  def description
62
71
  to_s
63
72
  end
64
73
 
65
- class << self
66
- include Color
74
+ def duplicable?
75
+ false
76
+ end
67
77
 
68
- def generate_label(name)
69
- colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
70
- end
78
+ include Color
79
+
80
+ def generate_label(name)
81
+ colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
71
82
  end
72
83
  end
73
84
 
74
- class LineBreakpoint < Breakpoint
75
- LABEL = generate_label("Line")
76
- 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
77
89
 
90
+ class LineBreakpoint < Breakpoint
78
91
  attr_reader :path, :line, :iseq
79
92
 
80
- 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
81
94
  @path = path
82
95
  @line = line
83
96
  @cond = cond
84
97
  @oneshot = oneshot
85
98
  @hook_call = hook_call
99
+ @command = command
100
+ @pending = false
86
101
 
87
102
  @iseq = nil
88
103
  @type = nil
@@ -90,23 +105,20 @@ module DEBUGGER__
90
105
  @key = [@path, @line].freeze
91
106
 
92
107
  super()
108
+
93
109
  try_activate
110
+ @pending = !@iseq
94
111
  end
95
112
 
96
113
  def setup
97
114
  return unless @type
98
115
 
99
- if !@cond
100
- @tp = TracePoint.new(@type) do |tp|
101
- delete if @oneshot
102
- suspend
103
- end
104
- else
105
- @tp = TracePoint.new(@type) do |tp|
116
+ @tp = TracePoint.new(@type) do |tp|
117
+ if @cond
106
118
  next unless safe_eval tp.binding, @cond
107
- delete if @oneshot
108
- suspend
109
119
  end
120
+ delete if @oneshot
121
+ suspend
110
122
  end
111
123
  end
112
124
 
@@ -132,9 +144,12 @@ module DEBUGGER__
132
144
 
133
145
  @key = [@path, @line].freeze
134
146
  SESSION.rehash_bps
135
-
136
147
  setup
137
148
  enable
149
+
150
+ if @pending && !@oneshot
151
+ DEBUGGER__.warn "#{self} is activated."
152
+ end
138
153
  end
139
154
 
140
155
  def activate_exact iseq, events, line
@@ -155,6 +170,10 @@ module DEBUGGER__
155
170
  end
156
171
  end
157
172
 
173
+ def duplicable?
174
+ @oneshot
175
+ end
176
+
158
177
  NearestISeq = Struct.new(:iseq, :line, :events)
159
178
 
160
179
  def try_activate
@@ -203,9 +222,9 @@ module DEBUGGER__
203
222
  oneshot = @oneshot ? " (oneshot)" : ""
204
223
 
205
224
  if @iseq
206
- "#{LABEL} #{@path}:#{@line} (#{@type})#{oneshot}" + super
225
+ "#{generate_label("Line")} #{@path}:#{@line} (#{@type})#{oneshot}" + super
207
226
  else
208
- "#{PENDING_LABEL} #{@path}:#{@line}#{oneshot}" + super
227
+ "#{generate_label("Line (pending)")} #{@path}:#{@line}#{oneshot}" + super
209
228
  end
210
229
  end
211
230
 
@@ -215,21 +234,28 @@ module DEBUGGER__
215
234
  end
216
235
 
217
236
  class CatchBreakpoint < Breakpoint
218
- LABEL = generate_label("Catch")
219
237
  attr_reader :last_exc
238
+ include SkipPathHelper
220
239
 
221
- def initialize pat
240
+ def initialize pat, cond: nil, command: nil
222
241
  @pat = pat.freeze
223
242
  @key = [:catch, @pat].freeze
224
243
  @last_exc = nil
225
244
 
245
+ @cond = cond
246
+ @command = command
247
+
226
248
  super()
227
249
  end
228
250
 
229
251
  def setup
230
252
  @tp = TracePoint.new(:raise){|tp|
253
+ next if skip_path?(tp.path)
231
254
  exc = tp.raised_exception
255
+ next if SystemExit === exc
256
+ next if !safe_eval(tp.binding, @cond) if @cond
232
257
  should_suspend = false
258
+
233
259
  exc.class.ancestors.each{|cls|
234
260
  if @pat === cls.name
235
261
  should_suspend = true
@@ -242,7 +268,7 @@ module DEBUGGER__
242
268
  end
243
269
 
244
270
  def to_s
245
- "#{LABEL} #{@pat.inspect}"
271
+ "#{generate_label("Catch")} #{@pat.inspect}"
246
272
  end
247
273
 
248
274
  def description
@@ -251,8 +277,6 @@ module DEBUGGER__
251
277
  end
252
278
 
253
279
  class CheckBreakpoint < Breakpoint
254
- LABEL = generate_label("Check")
255
-
256
280
  def initialize expr
257
281
  @expr = expr.freeze
258
282
  @key = [:check, @expr].freeze
@@ -264,8 +288,6 @@ module DEBUGGER__
264
288
  @tp = TracePoint.new(:line){|tp|
265
289
  next if tp.path.start_with? __dir__
266
290
  next if tp.path.start_with? '<internal:'
267
- # Skip when `JSON.generate` is called during tests
268
- next if tp.defined_class.to_s == '#<Class:JSON>' and ENV['RUBY_DEBUG_TEST_MODE']
269
291
 
270
292
  if safe_eval tp.binding, @expr
271
293
  suspend
@@ -274,13 +296,11 @@ module DEBUGGER__
274
296
  end
275
297
 
276
298
  def to_s
277
- "#{LABEL} #{@expr}"
299
+ "#{generate_label("Check")} #{@expr}"
278
300
  end
279
301
  end
280
302
 
281
303
  class WatchIVarBreakpoint < Breakpoint
282
- LABEL = generate_label("Watch")
283
-
284
304
  def initialize ivar, object, current
285
305
  @ivar = ivar.to_sym
286
306
  @object = object
@@ -321,41 +341,38 @@ module DEBUGGER__
321
341
  else
322
342
  "#{@current}"
323
343
  end
324
- "#{LABEL} #{@object} #{@ivar} = #{value_str}"
344
+ "#{generate_label("Watch")} #{@object} #{@ivar} = #{value_str}"
325
345
  end
326
346
  end
327
347
 
328
348
  class MethodBreakpoint < Breakpoint
329
- LABEL = generate_label("Method")
330
- PENDING_LABEL = generate_label("Method (pending)")
331
-
332
349
  attr_reader :sig_method_name, :method
333
350
 
334
- def initialize b, klass_name, op, method_name, cond
351
+ def initialize b, klass_name, op, method_name, cond: nil, command: nil
335
352
  @sig_klass_name = klass_name
336
353
  @sig_op = op
337
354
  @sig_method_name = method_name
338
355
  @klass_eval_binding = b
356
+ @override_method = false
339
357
 
340
358
  @klass = nil
341
359
  @method = nil
342
360
  @cond = cond
361
+ @cond_class = nil
362
+ @command = command
343
363
  @key = "#{klass_name}#{op}#{method_name}".freeze
344
364
 
345
365
  super(false)
346
366
  end
347
367
 
348
368
  def setup
349
- if @cond
350
- @tp = TracePoint.new(:call){|tp|
351
- next unless safe_eval tp.binding, @cond
352
- suspend
353
- }
354
- else
355
- @tp = TracePoint.new(:call){|tp|
356
- suspend
357
- }
358
- 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
+ }
359
376
  end
360
377
 
361
378
  def eval_class_name
@@ -380,34 +397,61 @@ module DEBUGGER__
380
397
  try_enable
381
398
  end
382
399
 
383
- 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
384
421
  eval_class_name
385
422
  search_method
386
423
 
387
424
  begin
388
425
  retried = false
426
+
389
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
390
435
 
391
436
  rescue ArgumentError
392
437
  raise if retried
393
438
  retried = true
394
- sig_method_name = @sig_method_name
395
439
 
396
440
  # maybe C method
397
- @klass.module_eval do
398
- orig_name = sig_method_name + '__orig__'
399
- alias_method orig_name, sig_method_name
400
- define_method(sig_method_name) do |*args|
401
- send(orig_name, *args)
402
- end
441
+ case @sig_op
442
+ when '.'
443
+ override @klass.singleton_class
444
+ when '#'
445
+ override @klass
403
446
  end
404
447
 
405
448
  # re-collect the method object after the above patch
406
449
  search_method
450
+ @override_method = true if @method
407
451
  retry
408
452
  end
409
453
  rescue Exception
410
- raise unless quiet
454
+ raise unless added
411
455
  end
412
456
 
413
457
  def sig
@@ -416,9 +460,9 @@ module DEBUGGER__
416
460
 
417
461
  def to_s
418
462
  if @method
419
- "#{LABEL} #{sig}"
463
+ "#{generate_label("Method")} #{sig} at #{@method.source_location.join(':')}"
420
464
  else
421
- "#{PENDING_LABEL} #{sig}"
465
+ "#{generate_label("Method (pending)")} #{sig}"
422
466
  end + super
423
467
  end
424
468
  end