debug 1.0.0.beta6 → 1.0.0.rc2

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