debug 1.0.0.beta4 → 1.0.0.beta8

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
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ["lib"]
26
26
  spec.extensions = ['ext/debug/extconf.rb']
27
+
28
+ spec.add_dependency "irb" # for its color_printer class, which was added after 1.3
27
29
  end
data/exe/rdbg CHANGED
@@ -1,30 +1,33 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/debug/client'
4
-
3
+ require_relative '../lib/debug/config'
5
4
  config = DEBUGGER__.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)
18
- exec("#{RbConfig.ruby}", "-I#{File.expand_path(File.dirname(__dir__))}/lib", "-r" + start_mode, *ARGV)
14
+ env = ::DEBUGGER__.config_to_env_hash(config)
15
+ env['RUBYOPT'] = "-r #{libpath}/#{start_mode}"
16
+
17
+ exec(env, cmd, *ARGV)
19
18
 
20
19
  when :attach
21
20
  require_relative "../lib/debug/client"
22
21
 
22
+ config.each{|k, v|
23
+ DEBUGGER__::CONFIG[k] = v
24
+ }
25
+
23
26
  begin
24
27
  if ARGV.empty? && config[:port]
25
- connect [config[:host], config[:port]].compact
28
+ DEBUGGER__::Client.new([config[:host], config[:port]].compact).connect
26
29
  else
27
- connect ARGV
30
+ DEBUGGER__::Client.new(ARGV).connect
28
31
  end
29
32
  rescue DEBUGGER__::CommandLineOptionError
30
33
  puts opt.help
data/ext/debug/debug.c CHANGED
@@ -22,7 +22,7 @@ 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);
25
+ return rb_struct_new(rb_cFrameInfo, loc, self, binding, iseq, klass, depth, Qnil, Qnil, Qnil, Qnil, Qnil);
26
26
  }
27
27
 
28
28
  static int
@@ -49,6 +49,7 @@ di_body(const rb_debug_inspector_t *dc, void *ptr)
49
49
  long i;
50
50
 
51
51
  for (i=1; i<len; i++) {
52
+ VALUE loc, e;
52
53
  VALUE iseq = rb_debug_inspector_frame_iseq_get(dc, i);
53
54
 
54
55
  if (!NIL_P(iseq)) {
@@ -56,13 +57,13 @@ di_body(const rb_debug_inspector_t *dc, void *ptr)
56
57
  if (!NIL_P(path) && str_start_with(path, skip_path_prefix)) continue;
57
58
  }
58
59
 
59
- VALUE loc = RARRAY_AREF(locs, i);
60
- VALUE e = di_entry(loc,
61
- rb_debug_inspector_frame_self_get(dc, i),
62
- rb_debug_inspector_frame_binding_get(dc, i),
63
- iseq,
64
- rb_debug_inspector_frame_class_get(dc, i),
65
- INT2FIX(len - i));
60
+ loc = RARRAY_AREF(locs, i);
61
+ e = di_entry(loc,
62
+ rb_debug_inspector_frame_self_get(dc, i),
63
+ rb_debug_inspector_frame_binding_get(dc, i),
64
+ iseq,
65
+ rb_debug_inspector_frame_class_get(dc, i),
66
+ INT2FIX(len - i));
66
67
  rb_ary_push(ary, e);
67
68
  }
68
69
 
data/lib/debug.rb CHANGED
@@ -1 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'debug/session'
4
+ DEBUGGER__::start no_sigint_hook: true, nonstop: true
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'color'
4
+
1
5
  module DEBUGGER__
2
6
  class Breakpoint
3
7
  attr_reader :key
@@ -45,27 +49,50 @@ module DEBUGGER__
45
49
  end
46
50
 
47
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
+
48
59
  ThreadClient.current.on_breakpoint @tp, self
49
60
  end
50
61
 
51
62
  def to_s
52
- if @cond
53
- " if #{@cond}"
54
- else
55
- ""
56
- 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
68
+ end
69
+
70
+ def description
71
+ to_s
72
+ end
73
+
74
+ def duplicable?
75
+ false
76
+ end
77
+
78
+ include Color
79
+
80
+ def generate_label(name)
81
+ colorize(" BP - #{name} ", [:YELLOW, :BOLD, :REVERSE])
57
82
  end
58
83
  end
59
84
 
60
85
  class LineBreakpoint < Breakpoint
61
86
  attr_reader :path, :line, :iseq
62
87
 
63
- def initialize path, line, cond: nil, oneshot: false, hook_call: true
88
+ def initialize path, line, cond: nil, oneshot: false, hook_call: true, command: nil
64
89
  @path = path
65
90
  @line = line
66
91
  @cond = cond
67
92
  @oneshot = oneshot
68
93
  @hook_call = hook_call
94
+ @command = command
95
+ @pending = false
69
96
 
70
97
  @iseq = nil
71
98
  @type = nil
@@ -73,23 +100,21 @@ module DEBUGGER__
73
100
  @key = [@path, @line].freeze
74
101
 
75
102
  super()
103
+
76
104
  try_activate
105
+ @pending = !@iseq
77
106
  end
78
107
 
79
108
  def setup
80
109
  return unless @type
81
110
 
82
- if !@cond
83
- @tp = TracePoint.new(@type) do |tp|
84
- delete if @oneshot
85
- suspend
86
- end
87
- else
88
- @tp = TracePoint.new(@type) do |tp|
111
+ @tp = TracePoint.new(@type) do |tp|
112
+ if @cond
89
113
  next unless safe_eval tp.binding, @cond
90
- delete if @oneshot
91
- suspend
92
114
  end
115
+ delete if @oneshot
116
+
117
+ suspend
93
118
  end
94
119
  end
95
120
 
@@ -115,9 +140,12 @@ module DEBUGGER__
115
140
 
116
141
  @key = [@path, @line].freeze
117
142
  SESSION.rehash_bps
118
-
119
143
  setup
120
144
  enable
145
+
146
+ if @pending && !@oneshot
147
+ DEBUGGER__.warn "#{self} is activated."
148
+ end
121
149
  end
122
150
 
123
151
  def activate_exact iseq, events, line
@@ -138,6 +166,10 @@ module DEBUGGER__
138
166
  end
139
167
  end
140
168
 
169
+ def duplicable?
170
+ @oneshot
171
+ end
172
+
141
173
  NearestISeq = Struct.new(:iseq, :line, :events)
142
174
 
143
175
  def try_activate
@@ -186,9 +218,9 @@ module DEBUGGER__
186
218
  oneshot = @oneshot ? " (oneshot)" : ""
187
219
 
188
220
  if @iseq
189
- "line bp #{@path}:#{@line} (#{@type})#{oneshot}" + super
221
+ "#{generate_label("Line")} #{@path}:#{@line} (#{@type})#{oneshot}" + super
190
222
  else
191
- "line bp (pending) #{@path}:#{@line}#{oneshot}" + super
223
+ "#{generate_label("Line (pending)")} #{@path}:#{@line}#{oneshot}" + super
192
224
  end
193
225
  end
194
226
 
@@ -198,9 +230,12 @@ module DEBUGGER__
198
230
  end
199
231
 
200
232
  class CatchBreakpoint < Breakpoint
233
+ attr_reader :last_exc
234
+
201
235
  def initialize pat
202
236
  @pat = pat.freeze
203
237
  @key = [:catch, @pat].freeze
238
+ @last_exc = nil
204
239
 
205
240
  super()
206
241
  end
@@ -208,14 +243,26 @@ module DEBUGGER__
208
243
  def setup
209
244
  @tp = TracePoint.new(:raise){|tp|
210
245
  exc = tp.raised_exception
246
+ next if SystemExit === exc
247
+ should_suspend = false
248
+
211
249
  exc.class.ancestors.each{|cls|
212
- suspend if @pat === cls.name
250
+ if @pat === cls.name
251
+ should_suspend = true
252
+ @last_exc = exc
253
+ break
254
+ end
213
255
  }
256
+ suspend if should_suspend
214
257
  }
215
258
  end
216
259
 
217
260
  def to_s
218
- "catch bp #{@pat.inspect}"
261
+ "#{generate_label("Catch")} #{@pat.inspect}"
262
+ end
263
+
264
+ def description
265
+ "#{@last_exc.inspect} is raised."
219
266
  end
220
267
  end
221
268
 
@@ -231,6 +278,8 @@ module DEBUGGER__
231
278
  @tp = TracePoint.new(:line){|tp|
232
279
  next if tp.path.start_with? __dir__
233
280
  next if tp.path.start_with? '<internal:'
281
+ # Skip when `JSON.generate` is called during tests
282
+ next if tp.defined_class.to_s == '#<Class:JSON>' and ENV['RUBY_DEBUG_TEST_MODE']
234
283
 
235
284
  if safe_eval tp.binding, @expr
236
285
  suspend
@@ -239,21 +288,22 @@ module DEBUGGER__
239
288
  end
240
289
 
241
290
  def to_s
242
- "check bp: #{@expr}"
291
+ "#{generate_label("Check")} #{@expr}"
243
292
  end
244
293
  end
245
294
 
246
- class WatchExprBreakpoint < Breakpoint
247
- def initialize expr, current
248
- @expr = expr.freeze
249
- @key = [:watch, @expr].freeze
295
+ class WatchIVarBreakpoint < Breakpoint
296
+ def initialize ivar, object, current
297
+ @ivar = ivar.to_sym
298
+ @object = object
299
+ @key = [:watch, @ivar].freeze
250
300
 
251
301
  @current = current
252
302
  super()
253
303
  end
254
304
 
255
- def watch_eval b
256
- result = b.eval(@expr)
305
+ def watch_eval
306
+ result = @object.instance_variable_get(@ivar)
257
307
  if result != @current
258
308
  begin
259
309
  @prev = @current
@@ -263,7 +313,7 @@ module DEBUGGER__
263
313
  remove_instance_variable(:@prev)
264
314
  end
265
315
  end
266
- rescue Exception => e
316
+ rescue Exception
267
317
  false
268
318
  end
269
319
 
@@ -272,23 +322,25 @@ module DEBUGGER__
272
322
  next if tp.path.start_with? __dir__
273
323
  next if tp.path.start_with? '<internal:'
274
324
 
275
- watch_eval(tp.binding)
325
+ watch_eval
276
326
  }
277
327
  end
278
328
 
279
329
  def to_s
280
- if defined? @prev
281
- "watch bp: #{@expr} = #{@prev} -> #{@current}"
282
- else
283
- "watch bp: #{@expr} = #{@current}"
284
- end
330
+ value_str =
331
+ if defined?(@prev)
332
+ "#{@prev} -> #{@current}"
333
+ else
334
+ "#{@current}"
335
+ end
336
+ "#{generate_label("Watch")} #{@object} #{@ivar} = #{value_str}"
285
337
  end
286
338
  end
287
339
 
288
340
  class MethodBreakpoint < Breakpoint
289
341
  attr_reader :sig_method_name, :method
290
342
 
291
- def initialize b, klass_name, op, method_name, cond
343
+ def initialize b, klass_name, op, method_name, cond, command: nil
292
344
  @sig_klass_name = klass_name
293
345
  @sig_op = op
294
346
  @sig_method_name = method_name
@@ -297,6 +349,7 @@ module DEBUGGER__
297
349
  @klass = nil
298
350
  @method = nil
299
351
  @cond = cond
352
+ @command = command
300
353
  @key = "#{klass_name}#{op}#{method_name}".freeze
301
354
 
302
355
  super(false)
@@ -337,30 +390,35 @@ module DEBUGGER__
337
390
  try_enable
338
391
  end
339
392
 
340
- def try_enable quiet: false
393
+ def try_enable added: false
341
394
  eval_class_name
342
395
  search_method
343
396
 
344
397
  begin
345
398
  retried = false
346
399
  @tp.enable(target: @method)
400
+ DEBUGGER__.warn "#{self} is activated." if added
347
401
 
348
- rescue ArgumentError => e
402
+ rescue ArgumentError
349
403
  raise if retried
350
404
  retried = true
405
+ sig_method_name = @sig_method_name
351
406
 
352
407
  # maybe C method
353
408
  @klass.module_eval do
354
- orig_name = @sig_method_name + '__orig__'
355
- alias_method orig_name, @sig_method_name
356
- define_method(@sig_method_name) do |*args|
409
+ orig_name = sig_method_name + '__orig__'
410
+ alias_method orig_name, sig_method_name
411
+ define_method(sig_method_name) do |*args|
357
412
  send(orig_name, *args)
358
413
  end
359
414
  end
415
+
416
+ # re-collect the method object after the above patch
417
+ search_method
360
418
  retry
361
419
  end
362
420
  rescue Exception
363
- raise unless quiet
421
+ raise unless added
364
422
  end
365
423
 
366
424
  def sig
@@ -369,9 +427,9 @@ module DEBUGGER__
369
427
 
370
428
  def to_s
371
429
  if @method
372
- "method bp: #{sig}"
430
+ "#{generate_label("Method")} #{sig} at #{@method.source_location.join(':')}"
373
431
  else
374
- "method bp (pending): #{sig}"
432
+ "#{generate_label("Method (pending)")} #{sig}"
375
433
  end + super
376
434
  end
377
435
  end