debug 1.0.0.beta5 → 1.0.0.rc1

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/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