debug 1.4.0 → 1.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +210 -6
  3. data/Gemfile +2 -0
  4. data/LICENSE.txt +0 -0
  5. data/README.md +161 -85
  6. data/Rakefile +33 -10
  7. data/TODO.md +8 -8
  8. data/debug.gemspec +9 -7
  9. data/exe/rdbg +23 -4
  10. data/ext/debug/debug.c +111 -21
  11. data/ext/debug/extconf.rb +23 -0
  12. data/ext/debug/iseq_collector.c +2 -0
  13. data/lib/debug/abbrev_command.rb +77 -0
  14. data/lib/debug/breakpoint.rb +102 -74
  15. data/lib/debug/client.rb +46 -12
  16. data/lib/debug/color.rb +0 -0
  17. data/lib/debug/config.rb +129 -36
  18. data/lib/debug/console.rb +46 -40
  19. data/lib/debug/dap_custom/traceInspector.rb +336 -0
  20. data/lib/debug/frame_info.rb +40 -25
  21. data/lib/debug/irb_integration.rb +37 -0
  22. data/lib/debug/local.rb +17 -11
  23. data/lib/debug/open.rb +0 -0
  24. data/lib/debug/open_nonstop.rb +0 -0
  25. data/lib/debug/prelude.rb +3 -2
  26. data/lib/debug/server.rb +126 -56
  27. data/lib/debug/server_cdp.rb +673 -248
  28. data/lib/debug/server_dap.rb +497 -261
  29. data/lib/debug/session.rb +899 -441
  30. data/lib/debug/source_repository.rb +122 -49
  31. data/lib/debug/start.rb +1 -1
  32. data/lib/debug/thread_client.rb +460 -155
  33. data/lib/debug/tracer.rb +10 -16
  34. data/lib/debug/version.rb +1 -1
  35. data/lib/debug.rb +7 -2
  36. data/misc/README.md.erb +106 -56
  37. metadata +14 -24
  38. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
  39. data/.github/ISSUE_TEMPLATE/custom.md +0 -10
  40. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  41. data/.github/pull_request_template.md +0 -9
  42. data/.github/workflows/ruby.yml +0 -34
  43. data/.gitignore +0 -12
  44. data/bin/console +0 -14
  45. data/bin/gentest +0 -30
  46. data/bin/setup +0 -8
  47. data/lib/debug/bp.vim +0 -68
@@ -5,32 +5,65 @@ require 'pp'
5
5
 
6
6
  require_relative 'color'
7
7
 
8
+ class ::Thread
9
+ attr_accessor :debug_thread_client
10
+ end
11
+
8
12
  module DEBUGGER__
13
+ M_INSTANCE_VARIABLES = method(:instance_variables).unbind
14
+ M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
15
+ M_CLASS = method(:class).unbind
16
+ M_SINGLETON_CLASS = method(:singleton_class).unbind
17
+ M_KIND_OF_P = method(:kind_of?).unbind
18
+ M_RESPOND_TO_P = method(:respond_to?).unbind
19
+ M_METHOD = method(:method).unbind
20
+ M_OBJECT_ID = method(:object_id).unbind
21
+ M_NAME = method(:name).unbind
22
+
9
23
  module SkipPathHelper
10
24
  def skip_path?(path)
25
+ !path ||
26
+ DEBUGGER__.skip? ||
27
+ ThreadClient.current.management? ||
28
+ skip_internal_path?(path) ||
29
+ skip_config_skip_path?(path)
30
+ end
31
+
32
+ def skip_config_skip_path?(path)
11
33
  (skip_paths = CONFIG[:skip_path]) && skip_paths.any?{|skip_path| path.match?(skip_path)}
12
34
  end
13
35
 
36
+ def skip_internal_path?(path)
37
+ path.start_with?(__dir__) || path.delete_prefix('!eval:').start_with?('<internal:')
38
+ end
39
+
14
40
  def skip_location?(loc)
15
41
  loc_path = loc.absolute_path || "!eval:#{loc.path}"
16
42
  skip_path?(loc_path)
17
43
  end
18
44
  end
19
45
 
46
+ module GlobalVariablesHelper
47
+ SKIP_GLOBAL_LIST = %i[$= $KCODE $-K $SAFE].freeze
48
+ def safe_global_variables
49
+ global_variables.reject{|name| SKIP_GLOBAL_LIST.include? name }
50
+ end
51
+ end
52
+
20
53
  class ThreadClient
21
54
  def self.current
22
- if thc = Thread.current[:DEBUGGER__ThreadClient]
23
- thc
24
- else
25
- thc = SESSION.get_thread_client
26
- Thread.current[:DEBUGGER__ThreadClient] = thc
27
- end
55
+ Thread.current.debug_thread_client ||= SESSION.get_thread_client
28
56
  end
29
57
 
30
58
  include Color
31
59
  include SkipPathHelper
60
+ include GlobalVariablesHelper
61
+
62
+ attr_reader :thread, :id, :recorder, :check_bp_fulfillment_map
32
63
 
33
- attr_reader :location, :thread, :id, :recorder
64
+ def location
65
+ current_frame&.location
66
+ end
34
67
 
35
68
  def assemble_arguments(args)
36
69
  args.map do |arg|
@@ -42,7 +75,8 @@ module DEBUGGER__
42
75
  call_identifier_str =
43
76
  case frame.frame_type
44
77
  when :block
45
- level, block_loc, args = frame.block_identifier
78
+ level, block_loc = frame.block_identifier
79
+ args = frame.parameters_info
46
80
 
47
81
  if !args.empty?
48
82
  args_str = " {|#{assemble_arguments(args)}|}"
@@ -50,7 +84,8 @@ module DEBUGGER__
50
84
 
51
85
  "#{colorize_blue("block")}#{args_str} in #{colorize_blue(block_loc + level)}"
52
86
  when :method
53
- ci, args = frame.method_identifier
87
+ ci = frame.method_identifier
88
+ args = frame.parameters_info
54
89
 
55
90
  if !args.empty?
56
91
  args_str = "(#{assemble_arguments(args)})"
@@ -87,6 +122,9 @@ module DEBUGGER__
87
122
  @obj_map = {} # { object_id => obj } for CDP
88
123
  @recorder = nil
89
124
  @mode = :waiting
125
+ @current_frame_index = 0
126
+ # every thread should maintain its own CheckBreakpoint fulfillment state
127
+ @check_bp_fulfillment_map = {} # { check_bp => boolean }
90
128
  set_mode :running
91
129
  thr.instance_variable_set(:@__thread_client_id, id)
92
130
 
@@ -106,6 +144,7 @@ module DEBUGGER__
106
144
  end
107
145
 
108
146
  def set_mode mode
147
+ debug_mode(@mode, mode)
109
148
  # STDERR.puts "#{@mode} => #{mode} @ #{caller.inspect}"
110
149
  # pp caller
111
150
 
@@ -149,14 +188,7 @@ module DEBUGGER__
149
188
  end
150
189
 
151
190
  def to_s
152
- loc = current_frame&.location
153
-
154
- if loc
155
- str = "(#{@thread.name || @thread.status})@#{loc}"
156
- else
157
- str = "(#{@thread.name || @thread.status})@#{@thread.to_s}"
158
- end
159
-
191
+ str = "(#{@thread.name || @thread.status})@#{current_frame&.location || @thread.to_s}"
160
192
  str += " (not under control)" unless self.waiting?
161
193
  str
162
194
  end
@@ -176,6 +208,7 @@ module DEBUGGER__
176
208
  end
177
209
 
178
210
  def << req
211
+ debug_cmd(req)
179
212
  @q_cmd << req
180
213
  end
181
214
 
@@ -186,6 +219,7 @@ module DEBUGGER__
186
219
  end
187
220
 
188
221
  def event! ev, *args
222
+ debug_event(ev, args)
189
223
  @q_evt << [self, @output, ev, generate_info, *args]
190
224
  @output = []
191
225
  end
@@ -231,6 +265,7 @@ module DEBUGGER__
231
265
 
232
266
  def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil
233
267
  return if management?
268
+ debug_suspend(event)
234
269
 
235
270
  @current_frame_index = 0
236
271
 
@@ -246,7 +281,6 @@ module DEBUGGER__
246
281
 
247
282
  cf = @target_frames.first
248
283
  if cf
249
- @location = cf.location
250
284
  case event
251
285
  when :return, :b_return, :c_return
252
286
  cf.has_return_value = true
@@ -265,8 +299,10 @@ module DEBUGGER__
265
299
  end
266
300
 
267
301
  if event != :pause
268
- show_src
269
- show_frames CONFIG[:show_frames] || 2
302
+ unless bp&.skip_src
303
+ show_src
304
+ show_frames CONFIG[:show_frames]
305
+ end
270
306
 
271
307
  set_mode :waiting
272
308
 
@@ -302,11 +338,15 @@ module DEBUGGER__
302
338
  @step_tp.disable if @step_tp
303
339
 
304
340
  thread = Thread.current
341
+ subsession_id = SESSION.subsession_id
305
342
 
306
343
  if SUPPORT_TARGET_THREAD
307
344
  @step_tp = TracePoint.new(*events){|tp|
308
- next if SESSION.break_at? tp.path, tp.lineno
309
- next if !yield(tp.event)
345
+ if SESSION.stop_stepping? tp.path, tp.lineno, subsession_id
346
+ tp.disable
347
+ next
348
+ end
349
+ next if !yield(tp)
310
350
  next if tp.path.start_with?(__dir__)
311
351
  next if tp.path.start_with?('<internal:trace_point>')
312
352
  next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
@@ -321,8 +361,11 @@ module DEBUGGER__
321
361
  else
322
362
  @step_tp = TracePoint.new(*events){|tp|
323
363
  next if thread != Thread.current
324
- next if SESSION.break_at? tp.path, tp.lineno
325
- next if !yield(tp.event)
364
+ if SESSION.stop_stepping? tp.path, tp.lineno, subsession_id
365
+ tp.disable
366
+ next
367
+ end
368
+ next if !yield(tp)
326
369
  next if tp.path.start_with?(__dir__)
327
370
  next if tp.path.start_with?('<internal:trace_point>')
328
371
  next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
@@ -339,9 +382,49 @@ module DEBUGGER__
339
382
 
340
383
  ## cmd helpers
341
384
 
342
- # this method is extracted to hide frame_eval's local variables from C method eval's binding
343
- def instance_eval_for_cmethod frame_self, src
344
- frame_self.instance_eval(src)
385
+ if TracePoint.respond_to? :allow_reentry
386
+ def tp_allow_reentry
387
+ TracePoint.allow_reentry do
388
+ yield
389
+ end
390
+ rescue RuntimeError => e
391
+ # on the postmortem mode, it is not stopped in TracePoint
392
+ if e.message == 'No need to allow reentrance.'
393
+ yield
394
+ else
395
+ raise
396
+ end
397
+ end
398
+ else
399
+ def tp_allow_reentry
400
+ yield
401
+ end
402
+ end
403
+
404
+ def frame_eval_core src, b, binding_location: false
405
+ saved_target_frames = @target_frames
406
+ saved_current_frame_index = @current_frame_index
407
+
408
+ if b
409
+ file, lineno = b.source_location
410
+
411
+ tp_allow_reentry do
412
+ if binding_location
413
+ b.eval(src, file, lineno)
414
+ else
415
+ b.eval(src, "(rdbg)/#{file}")
416
+ end
417
+ end
418
+ else
419
+ frame_self = current_frame.self
420
+
421
+ tp_allow_reentry do
422
+ frame_self.instance_eval(src)
423
+ end
424
+ end
425
+ ensure
426
+ @target_frames = saved_target_frames
427
+ @current_frame_index = saved_current_frame_index
345
428
  end
346
429
 
347
430
  SPECIAL_LOCAL_VARS = [
@@ -349,25 +432,22 @@ module DEBUGGER__
349
432
  [:return_value, "_return"],
350
433
  ]
351
434
 
352
- def frame_eval src, re_raise: false
435
+ def frame_eval src, re_raise: false, binding_location: false
353
436
  @success_last_eval = false
354
437
 
355
- b = current_frame.eval_binding
438
+ b = current_frame&.eval_binding || TOPLEVEL_BINDING
356
439
 
357
440
  special_local_variables current_frame do |name, var|
358
441
  b.local_variable_set(name, var) if /\%/ !~ name
359
442
  end
360
443
 
361
- result = if b
362
- f, _l = b.source_location
363
- b.eval(src, "(rdbg)/#{f}")
364
- else
365
- frame_self = current_frame.self
366
- instance_eval_for_cmethod(frame_self, src)
367
- end
444
+ result = frame_eval_core(src, b, binding_location: binding_location)
445
+
368
446
  @success_last_eval = true
369
447
  result
370
448
 
449
+ rescue SystemExit
450
+ raise
371
451
  rescue Exception => e
372
452
  return yield(e) if block_given?
373
453
 
@@ -380,50 +460,46 @@ module DEBUGGER__
380
460
  raise if re_raise
381
461
  end
382
462
 
383
- def show_src(frame_index: @current_frame_index,
384
- update_line: false,
385
- max_lines: CONFIG[:show_src_lines] || 10,
386
- start_line: nil,
387
- end_line: nil,
388
- dir: +1)
389
- if @target_frames && frame = @target_frames[frame_index]
390
- if file_lines = frame.file_lines
391
- frame_line = frame.location.lineno - 1
463
+ def get_src(frame,
464
+ max_lines:,
465
+ start_line: nil,
466
+ end_line: nil,
467
+ dir: +1)
468
+ if file_lines = frame.file_lines
469
+ frame_line = frame.location.lineno - 1
392
470
 
471
+ if CONFIG[:no_lineno]
472
+ lines = file_lines
473
+ else
393
474
  lines = file_lines.map.with_index do |e, i|
394
475
  cur = i == frame_line ? '=>' : ' '
395
476
  line = colorize_dim('%4d|' % (i+1))
396
477
  "#{cur}#{line} #{e}"
397
478
  end
479
+ end
398
480
 
399
- unless start_line
400
- if frame.show_line
401
- if dir > 0
402
- start_line = frame.show_line
403
- else
404
- end_line = frame.show_line - max_lines
405
- start_line = [end_line - max_lines, 0].max
406
- end
481
+ unless start_line
482
+ if frame.show_line
483
+ if dir > 0
484
+ start_line = frame.show_line
407
485
  else
408
- start_line = [frame_line - max_lines/2, 0].max
486
+ end_line = frame.show_line - max_lines
487
+ start_line = [end_line - max_lines, 0].max
409
488
  end
489
+ else
490
+ start_line = [frame_line - max_lines/2, 0].max
410
491
  end
492
+ end
411
493
 
412
- unless end_line
413
- end_line = [start_line + max_lines, lines.size].min
414
- end
415
-
416
- if update_line
417
- frame.show_line = end_line
418
- end
494
+ unless end_line
495
+ end_line = [start_line + max_lines, lines.size].min
496
+ end
419
497
 
420
- if start_line != end_line && max_lines
421
- puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
422
- puts lines[start_line ... end_line]
423
- end
424
- else # no file lines
425
- puts "# No sourcefile available for #{frame.path}"
498
+ if start_line != end_line && max_lines
499
+ [start_line, end_line, lines]
426
500
  end
501
+ else # no file lines
502
+ nil
427
503
  end
428
504
  rescue Exception => e
429
505
  p e
@@ -431,14 +507,63 @@ module DEBUGGER__
431
507
  exit!
432
508
  end
433
509
 
510
+ def show_src(frame_index: @current_frame_index, update_line: false, ignore_show_line: false, max_lines: CONFIG[:show_src_lines], **options)
511
+ if frame = get_frame(frame_index)
512
+ begin
513
+ if ignore_show_line
514
+ prev_show_line = frame.show_line
515
+ frame.show_line = nil
516
+ end
517
+
518
+ start_line, end_line, lines = *get_src(frame, max_lines: max_lines, **options)
519
+
520
+ if start_line
521
+ if update_line
522
+ frame.show_line = end_line
523
+ end
524
+
525
+ puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
526
+ puts lines[start_line...end_line]
527
+ else
528
+ puts "# No sourcefile available for #{frame.path}"
529
+ end
530
+ ensure
531
+ frame.show_line = prev_show_line if prev_show_line
532
+ end
533
+ end
534
+ end
535
+
434
536
  def current_frame
537
+ get_frame(@current_frame_index)
538
+ end
539
+
540
+ def get_frame(index)
435
541
  if @target_frames
436
- @target_frames[@current_frame_index]
542
+ @target_frames[index]
437
543
  else
438
544
  nil
439
545
  end
440
546
  end
441
547
 
548
+ def collect_locals(frame)
549
+ locals = []
550
+
551
+ if s = frame&.self
552
+ locals << ["%self", s]
553
+ end
554
+ special_local_variables frame do |name, val|
555
+ locals << [name, val]
556
+ end
557
+
558
+ if vars = frame&.local_variables
559
+ vars.each{|var, val|
560
+ locals << [var, val]
561
+ }
562
+ end
563
+
564
+ locals
565
+ end
566
+
442
567
  ## cmd: show
443
568
 
444
569
  def special_local_variables frame
@@ -450,62 +575,86 @@ module DEBUGGER__
450
575
  end
451
576
 
452
577
  def show_locals pat
453
- if s = current_frame&.self
454
- puts_variable_info '%self', s, pat
455
- end
456
- special_local_variables current_frame do |name, val|
457
- puts_variable_info name, val, pat
578
+ collect_locals(current_frame).each do |var, val|
579
+ puts_variable_info(var, val, pat)
458
580
  end
581
+ end
459
582
 
460
- if vars = current_frame&.local_variables
461
- vars.each{|var, val|
462
- puts_variable_info var, val, pat
463
- }
583
+ def show_ivars pat, expr = nil
584
+ if expr && !expr.empty?
585
+ _self = frame_eval(expr);
586
+ elsif _self = current_frame&.self
587
+ else
588
+ _self = nil
464
589
  end
465
- end
466
590
 
467
- def show_ivars pat
468
- if s = current_frame&.self
469
- s.instance_variables.sort.each{|iv|
470
- value = s.instance_variable_get(iv)
591
+ if _self
592
+ M_INSTANCE_VARIABLES.bind_call(_self).sort.each{|iv|
593
+ value = M_INSTANCE_VARIABLE_GET.bind_call(_self, iv)
471
594
  puts_variable_info iv, value, pat
472
595
  }
473
596
  end
474
597
  end
475
598
 
476
- def show_consts pat, only_self: false
477
- if s = current_frame&.self
599
+ def iter_consts c, names = {}
600
+ c.constants(false).sort.each{|name|
601
+ next if names.has_key? name
602
+ names[name] = nil
603
+ begin
604
+ value = c.const_get(name)
605
+ rescue Exception => e
606
+ value = e
607
+ end
608
+ yield name, value
609
+ }
610
+ end
611
+
612
+ def get_consts expr = nil, only_self: false, &block
613
+ if expr && !expr.empty?
614
+ begin
615
+ _self = frame_eval(expr, re_raise: true)
616
+ rescue Exception
617
+ # ignore
618
+ else
619
+ if M_KIND_OF_P.bind_call(_self, Module)
620
+ iter_consts _self, &block
621
+ return
622
+ else
623
+ puts "#{_self.inspect} (by #{expr}) is not a Module."
624
+ end
625
+ end
626
+ elsif _self = current_frame&.self
478
627
  cs = {}
479
- if s.kind_of? Module
480
- cs[s] = :self
628
+ if M_KIND_OF_P.bind_call(_self, Module)
629
+ cs[_self] = :self
481
630
  else
482
- s = s.class
483
- cs[s] = :self unless only_self
631
+ _self = M_CLASS.bind_call(_self)
632
+ cs[_self] = :self unless only_self
484
633
  end
485
634
 
486
635
  unless only_self
487
- s.ancestors.each{|c| break if c == Object; cs[c] = :ancestors}
636
+ _self.ancestors.each{|c| break if c == Object; cs[c] = :ancestors}
488
637
  if b = current_frame&.binding
489
- b.eval('Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c}
638
+ b.eval('::Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c}
490
639
  end
491
640
  end
492
641
 
493
642
  names = {}
494
643
 
495
644
  cs.each{|c, _|
496
- c.constants(false).sort.each{|name|
497
- next if names.has_key? name
498
- names[name] = nil
499
- value = c.const_get(name)
500
- puts_variable_info name, value, pat
501
- }
645
+ iter_consts c, names, &block
502
646
  }
503
647
  end
504
648
  end
505
649
 
506
- SKIP_GLOBAL_LIST = %i[$= $KCODE $-K $SAFE].freeze
650
+ def show_consts pat, expr = nil, only_self: false
651
+ get_consts expr, only_self: only_self do |name, value|
652
+ puts_variable_info name, value, pat
653
+ end
654
+ end
655
+
507
656
  def show_globals pat
508
- global_variables.sort.each{|name|
657
+ safe_global_variables.sort.each{|name|
509
658
  next if SKIP_GLOBAL_LIST.include? name
510
659
 
511
660
  value = eval(name.to_s)
@@ -517,7 +666,7 @@ module DEBUGGER__
517
666
  return if pat && pat !~ label
518
667
 
519
668
  begin
520
- inspected = obj.inspect
669
+ inspected = DEBUGGER__.safe_inspect(obj)
521
670
  rescue Exception => e
522
671
  inspected = e.inspect
523
672
  end
@@ -526,28 +675,32 @@ module DEBUGGER__
526
675
  w = SESSION::width
527
676
 
528
677
  if mono_info.length >= w
529
- info = truncate(mono_info, width: w)
678
+ maximum_value_width = w - "#{label} = ".length
679
+ valstr = truncate(inspected, width: maximum_value_width)
530
680
  else
531
681
  valstr = colored_inspect(obj, width: 2 ** 30)
532
682
  valstr = inspected if valstr.lines.size > 1
533
- info = "#{colorize_cyan(label)} = #{valstr}"
534
683
  end
535
684
 
685
+ info = "#{colorize_cyan(label)} = #{valstr}"
686
+
536
687
  puts info
537
688
  end
538
689
 
539
690
  def truncate(string, width:)
540
- str = string[0 .. (width-4)] + '...'
541
- str += ">" if str.start_with?("#<")
542
- str
691
+ if string.start_with?("#<")
692
+ string[0 .. (width-5)] + '...>'
693
+ else
694
+ string[0 .. (width-4)] + '...'
695
+ end
543
696
  end
544
697
 
545
698
  ### cmd: show edit
546
699
 
547
700
  def show_by_editor path = nil
548
701
  unless path
549
- if @target_frames && frame = @target_frames[@current_frame_index]
550
- path = frame.path
702
+ if current_frame
703
+ path = current_frame.path
551
704
  else
552
705
  return # can't get path
553
706
  end
@@ -557,7 +710,8 @@ module DEBUGGER__
557
710
  if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
558
711
  puts "command: #{editor}"
559
712
  puts " path: #{path}"
560
- system(editor, path)
713
+ require 'shellwords'
714
+ system(*Shellwords.split(editor), path)
561
715
  else
562
716
  puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
563
717
  end
@@ -572,15 +726,11 @@ module DEBUGGER__
572
726
  if @target_frames && (max ||= @target_frames.size) > 0
573
727
  frames = []
574
728
  @target_frames.each_with_index{|f, i|
575
- next if pattern && !(f.name.match?(pattern) || f.location_str.match?(pattern))
576
- next if CONFIG[:skip_path] && CONFIG[:skip_path].any?{|pat|
577
- case pat
578
- when String
579
- f.location_str.start_with?(pat)
580
- when Regexp
581
- f.location_str.match?(pat)
582
- end
583
- }
729
+ # we need to use FrameInfo#matchable_location because #location_str is for display
730
+ # and it may change based on configs (e.g. use_short_path)
731
+ next if pattern && !(f.name.match?(pattern) || f.matchable_location.match?(pattern))
732
+ # avoid using skip_path? because we still want to display internal frames
733
+ next if skip_config_skip_path?(f.matchable_location)
584
734
 
585
735
  frames << [i, f]
586
736
  }
@@ -617,18 +767,25 @@ module DEBUGGER__
617
767
  o = Output.new(@output)
618
768
 
619
769
  locals = current_frame&.local_variables
620
- klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
621
770
 
622
- o.dump("constants", obj.constants) if obj.respond_to?(:constants)
771
+ klass = M_CLASS.bind_call(obj)
772
+ klass = obj if Class == klass || Module == klass
773
+
774
+ o.dump("constants", obj.constants) if M_RESPOND_TO_P.bind_call(obj, :constants)
623
775
  outline_method(o, klass, obj)
624
- o.dump("instance variables", obj.instance_variables)
776
+ o.dump("instance variables", M_INSTANCE_VARIABLES.bind_call(obj))
625
777
  o.dump("class variables", klass.class_variables)
626
778
  o.dump("locals", locals.keys) if locals
627
779
  end
628
780
  end
629
781
 
630
782
  def outline_method(o, klass, obj)
631
- singleton_class = begin obj.singleton_class; rescue TypeError; nil end
783
+ begin
784
+ singleton_class = M_SINGLETON_CLASS.bind_call(obj)
785
+ rescue TypeError
786
+ singleton_class = nil
787
+ end
788
+
632
789
  maps = class_method_map((singleton_class || klass).ancestors)
633
790
  maps.each do |mod, methods|
634
791
  name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods"
@@ -648,16 +805,48 @@ module DEBUGGER__
648
805
 
649
806
  ## cmd: breakpoint
650
807
 
808
+ # TODO: support non-ASCII Constant name
809
+ def constant_name? name
810
+ case name
811
+ when /\A::\b/
812
+ constant_name? $~.post_match
813
+ when /\A[A-Z]\w*/
814
+ post = $~.post_match
815
+ if post.empty?
816
+ true
817
+ else
818
+ constant_name? post
819
+ end
820
+ else
821
+ false
822
+ end
823
+ end
824
+
651
825
  def make_breakpoint args
652
826
  case args.first
653
827
  when :method
654
828
  klass_name, op, method_name, cond, cmd, path = args[1..]
655
- bp = MethodBreakpoint.new(current_frame.eval_binding, klass_name, op, method_name, cond: cond, command: cmd, path: path)
829
+ bp = MethodBreakpoint.new(current_frame&.eval_binding || TOPLEVEL_BINDING, klass_name, op, method_name, cond: cond, command: cmd, path: path)
656
830
  begin
657
831
  bp.enable
832
+ rescue NameError => e
833
+ if bp.klass
834
+ puts "Unknown method name: \"#{e.name}\""
835
+ else
836
+ # klass_name can not be evaluated
837
+ if constant_name? klass_name
838
+ puts "Unknown constant name: \"#{e.name}\""
839
+ else
840
+ # only Class name is allowed
841
+ puts "Not a constant name: \"#{klass_name}\""
842
+ bp = nil
843
+ end
844
+ end
845
+
846
+ Session.activate_method_added_trackers if bp
658
847
  rescue Exception => e
659
- puts e.message
660
- ::DEBUGGER__::METHOD_ADDED_TRACKER.enable
848
+ puts e.inspect
849
+ bp = nil
661
850
  end
662
851
 
663
852
  bp
@@ -672,8 +861,18 @@ module DEBUGGER__
672
861
  class SuspendReplay < Exception
673
862
  end
674
863
 
864
+ if ::Fiber.respond_to?(:blocking)
865
+ private def fiber_blocking
866
+ ::Fiber.blocking{yield}
867
+ end
868
+ else
869
+ private def fiber_blocking
870
+ yield
871
+ end
872
+ end
873
+
675
874
  def wait_next_action
676
- wait_next_action_
875
+ fiber_blocking{wait_next_action_}
677
876
  rescue SuspendReplay
678
877
  replay_suspend
679
878
  end
@@ -693,6 +892,7 @@ module DEBUGGER__
693
892
  set_mode :waiting if !waiting?
694
893
  cmds = @q_cmd.pop
695
894
  # pp [self, cmds: cmds]
895
+
696
896
  break unless cmds
697
897
  ensure
698
898
  set_mode :running
@@ -710,8 +910,9 @@ module DEBUGGER__
710
910
 
711
911
  case step_type
712
912
  when :in
913
+ iter = iter || 1
713
914
  if @recorder&.replaying?
714
- @recorder.step_forward
915
+ @recorder.step_forward iter
715
916
  raise SuspendReplay
716
917
  else
717
918
  step_tp iter do
@@ -724,6 +925,7 @@ module DEBUGGER__
724
925
  frame = @target_frames.first
725
926
  path = frame.location.absolute_path || "!eval:#{frame.path}"
726
927
  line = frame.location.lineno
928
+ label = frame.location.base_label
727
929
 
728
930
  if frame.iseq
729
931
  frame.iseq.traceable_lines_norec(lines = {})
@@ -735,35 +937,89 @@ module DEBUGGER__
735
937
 
736
938
  depth = @target_frames.first.frame_depth
737
939
 
738
- step_tp iter do
940
+ step_tp iter do |tp|
739
941
  loc = caller_locations(2, 1).first
740
942
  loc_path = loc.absolute_path || "!eval:#{loc.path}"
943
+ loc_label = loc.base_label
944
+ loc_depth = DEBUGGER__.frame_depth - 3
741
945
 
742
- # same stack depth
743
- (DEBUGGER__.frame_depth - 3 <= depth) ||
744
-
745
- # different frame
746
- (next_line && loc_path == path &&
747
- (loc_lineno = loc.lineno) > line &&
748
- loc_lineno <= next_line)
946
+ case
947
+ when loc_depth == depth && loc_label == label
948
+ true
949
+ when loc_depth < depth
950
+ # lower stack depth
951
+ true
952
+ when (next_line &&
953
+ loc_path == path &&
954
+ (loc_lineno = loc.lineno) > line &&
955
+ loc_lineno <= next_line)
956
+ # different frame (maybe block) but the line is before next_line
957
+ true
958
+ end
749
959
  end
750
960
  break
751
961
 
752
962
  when :finish
753
963
  finish_frames = (iter || 1) - 1
754
- goal_depth = @target_frames.first.frame_depth - finish_frames
964
+ frame = @target_frames.first
965
+ goal_depth = frame.frame_depth - finish_frames - (frame.has_return_value ? 1 : 0)
755
966
 
756
967
  step_tp nil, [:return, :b_return] do
757
968
  DEBUGGER__.frame_depth - 3 <= goal_depth ? true : false
758
969
  end
759
970
  break
760
971
 
972
+ when :until
973
+ location = iter&.strip
974
+ frame = @target_frames.first
975
+ depth = frame.frame_depth - (frame.has_return_value ? 1 : 0)
976
+ target_location_label = frame.location.base_label
977
+
978
+ case location
979
+ when nil, /\A(?:(.+):)?(\d+)\z/
980
+ no_loc = !location
981
+ file = $1 || frame.location.path
982
+ line = ($2 || frame.location.lineno + 1).to_i
983
+
984
+ step_tp nil, [:line, :return] do |tp|
985
+ if tp.event == :line
986
+ next false if no_loc && depth < DEBUGGER__.frame_depth - 3
987
+ next false unless tp.path.end_with?(file)
988
+ next false unless tp.lineno >= line
989
+ true
990
+ else
991
+ true if depth >= DEBUGGER__.frame_depth - 3 &&
992
+ caller_locations(2, 1).first.base_label == target_location_label
993
+ # TODO: imcomplete condition
994
+ end
995
+ end
996
+ else
997
+ pat = location
998
+ if /\A\/(.+)\/\z/ =~ pat
999
+ pat = Regexp.new($1)
1000
+ end
1001
+
1002
+ step_tp nil, [:call, :c_call, :return] do |tp|
1003
+ case tp.event
1004
+ when :call, :c_call
1005
+ true if pat === tp.callee_id.to_s
1006
+ else # :return, :b_return
1007
+ true if depth >= DEBUGGER__.frame_depth - 3 &&
1008
+ caller_locations(2, 1).first.base_label == target_location_label
1009
+ # TODO: imcomplete condition
1010
+ end
1011
+ end
1012
+ end
1013
+
1014
+ break
1015
+
761
1016
  when :back
1017
+ iter = iter || 1
762
1018
  if @recorder&.can_step_back?
763
1019
  unless @recorder.backup_frames
764
1020
  @recorder.backup_frames = @target_frames
765
1021
  end
766
- @recorder.step_back
1022
+ @recorder.step_back iter
767
1023
  raise SuspendReplay
768
1024
  else
769
1025
  puts "Can not step back more."
@@ -800,13 +1056,6 @@ module DEBUGGER__
800
1056
  end
801
1057
  when :call
802
1058
  result = frame_eval(eval_src)
803
- when :irb
804
- begin
805
- result = frame_eval('binding.irb')
806
- ensure
807
- # workaround: https://github.com/ruby/debug/issues/308
808
- Reline.prompt_proc = nil if defined? Reline
809
- end
810
1059
  when :display, :try_display
811
1060
  failed_results = []
812
1061
  eval_src.each_with_index{|src, i|
@@ -853,6 +1102,7 @@ module DEBUGGER__
853
1102
  else
854
1103
  raise "unsupported frame operation: #{arg.inspect}"
855
1104
  end
1105
+
856
1106
  event! :result, nil
857
1107
 
858
1108
  when :show
@@ -866,6 +1116,10 @@ module DEBUGGER__
866
1116
  when :list
867
1117
  show_src(update_line: true, **(args.first || {}))
868
1118
 
1119
+ when :whereami
1120
+ show_src ignore_show_line: true
1121
+ show_frames CONFIG[:show_frames]
1122
+
869
1123
  when :edit
870
1124
  show_by_editor(args.first)
871
1125
 
@@ -881,11 +1135,13 @@ module DEBUGGER__
881
1135
 
882
1136
  when :ivars
883
1137
  pat = args.shift
884
- show_ivars pat
1138
+ expr = args.shift
1139
+ show_ivars pat, expr
885
1140
 
886
1141
  when :consts
887
1142
  pat = args.shift
888
- show_consts pat
1143
+ expr = args.shift
1144
+ show_consts pat, expr
889
1145
 
890
1146
  when :globals
891
1147
  pat = args.shift
@@ -929,7 +1185,7 @@ module DEBUGGER__
929
1185
  begin
930
1186
  obj = frame_eval args.shift, re_raise: true
931
1187
  opt = args.shift
932
- obj_inspect = obj.inspect
1188
+ obj_inspect = DEBUGGER__.safe_inspect(obj)
933
1189
 
934
1190
  width = 50
935
1191
 
@@ -937,7 +1193,7 @@ module DEBUGGER__
937
1193
  obj_inspect = truncate(obj_inspect, width: width)
938
1194
  end
939
1195
 
940
- event! :result, :trace_pass, obj.object_id, obj_inspect, opt
1196
+ event! :result, :trace_pass, M_OBJECT_ID.bind_call(obj), obj_inspect, opt
941
1197
  rescue => e
942
1198
  puts e.message
943
1199
  event! :result, nil
@@ -954,8 +1210,8 @@ module DEBUGGER__
954
1210
  # enable recording
955
1211
  if !@recorder
956
1212
  @recorder = Recorder.new
957
- @recorder.enable
958
1213
  end
1214
+ @recorder.enable
959
1215
  when :off
960
1216
  if @recorder&.enabled?
961
1217
  @recorder.disable
@@ -971,6 +1227,8 @@ module DEBUGGER__
971
1227
  end
972
1228
  event! :result, nil
973
1229
 
1230
+ when :quit
1231
+ sleep # wait for SystemExit
974
1232
  when :dap
975
1233
  process_dap args
976
1234
  when :cdp
@@ -983,8 +1241,45 @@ module DEBUGGER__
983
1241
  rescue SuspendReplay, SystemExit, Interrupt
984
1242
  raise
985
1243
  rescue Exception => e
986
- pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
1244
+ STDERR.puts e.cause.inspect
1245
+ STDERR.puts e.inspect
1246
+ Thread.list.each{|th|
1247
+ STDERR.puts "@@@ #{th}"
1248
+ th.backtrace.each{|b|
1249
+ STDERR.puts " > #{b}"
1250
+ }
1251
+ }
1252
+ p ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
987
1253
  raise
1254
+ ensure
1255
+ @returning = false
1256
+ end
1257
+
1258
+ def debug_event(ev, args)
1259
+ DEBUGGER__.debug{
1260
+ args = args.map { |arg| DEBUGGER__.safe_inspect(arg) }
1261
+ "#{inspect} sends Event { type: #{ev.inspect}, args: #{args} } to Session"
1262
+ }
1263
+ end
1264
+
1265
+ def debug_mode(old_mode, new_mode)
1266
+ DEBUGGER__.debug{
1267
+ "#{inspect} changes mode (#{old_mode} -> #{new_mode})"
1268
+ }
1269
+ end
1270
+
1271
+ def debug_cmd(cmds)
1272
+ DEBUGGER__.debug{
1273
+ cmd, *args = *cmds
1274
+ args = args.map { |arg| DEBUGGER__.safe_inspect(arg) }
1275
+ "#{inspect} receives Cmd { type: #{cmd.inspect}, args: #{args} } from Session"
1276
+ }
1277
+ end
1278
+
1279
+ def debug_suspend(event)
1280
+ DEBUGGER__.debug{
1281
+ "#{inspect} is suspended for #{event.inspect}"
1282
+ }
988
1283
  end
989
1284
 
990
1285
  class Recorder
@@ -1001,8 +1296,8 @@ module DEBUGGER__
1001
1296
 
1002
1297
  @tp_recorder ||= TracePoint.new(:line){|tp|
1003
1298
  next unless Thread.current == thread
1004
- next if tp.path.start_with? __dir__
1005
- next if tp.path.start_with? '<internal:'
1299
+ # can't be replaced by skip_location
1300
+ next if skip_internal_path?(tp.path)
1006
1301
  loc = caller_locations(1, 1).first
1007
1302
  next if skip_location?(loc)
1008
1303
 
@@ -1016,10 +1311,14 @@ module DEBUGGER__
1016
1311
  frame._callee = b.eval('__callee__')
1017
1312
  end
1018
1313
  }
1019
- @log << frames
1314
+ append(frames)
1020
1315
  }
1021
1316
  end
1022
1317
 
1318
+ def append frames
1319
+ @log << frames
1320
+ end
1321
+
1023
1322
  def enable
1024
1323
  unless @tp_recorder.enabled?
1025
1324
  @log.clear
@@ -1038,12 +1337,18 @@ module DEBUGGER__
1038
1337
  @tp_recorder.enabled?
1039
1338
  end
1040
1339
 
1041
- def step_back
1042
- @index += 1
1340
+ def step_back iter
1341
+ @index += iter
1342
+ if @index > @log.size
1343
+ @index = @log.size
1344
+ end
1043
1345
  end
1044
1346
 
1045
- def step_forward
1046
- @index -= 1
1347
+ def step_forward iter
1348
+ @index -= iter
1349
+ if @index < 0
1350
+ @index = 0
1351
+ end
1047
1352
  end
1048
1353
 
1049
1354
  def step_reset