debug 1.3.3 → 1.5.0

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.
@@ -4,74 +4,112 @@ require_relative 'color'
4
4
 
5
5
  module DEBUGGER__
6
6
  class SourceRepository
7
- SrcInfo = Struct.new(:src, :colored)
7
+ include Color
8
8
 
9
- def initialize
10
- @files = {} # filename => SrcInfo
11
- end
9
+ if RubyVM.respond_to? :keep_script_lines
10
+ # Ruby 3.1 and later
11
+ RubyVM.keep_script_lines = true
12
+ require 'objspace'
12
13
 
13
- def add iseq, src
14
- if (path = iseq.absolute_path) && File.exist?(path)
15
- add_path path
16
- elsif src
17
- add_iseq iseq, src
14
+ def initialize
15
+ # cache
16
+ @cmap = ObjectSpace::WeakMap.new
18
17
  end
19
- end
20
18
 
21
- def all_iseq iseq, rs = []
22
- rs << iseq
23
- iseq.each_child{|ci|
24
- all_iseq(ci, rs)
25
- }
26
- rs
27
- end
19
+ def add iseq, src
20
+ # do nothing
21
+ end
22
+
23
+ def get iseq
24
+ return unless iseq
28
25
 
29
- private def add_iseq iseq, src
30
- line = iseq.first_line
31
- if line > 1
32
- src = ("\n" * (line - 1)) + src
26
+ if lines = iseq.script_lines&.map(&:chomp)
27
+ lines
28
+ else
29
+ if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
30
+ File.readlines(path, chomp: true)
31
+ else
32
+ nil
33
+ end
34
+ end
33
35
  end
34
- si = SrcInfo.new(src.lines)
35
36
 
36
- all_iseq(iseq).each{|e|
37
- e.instance_variable_set(:@debugger_si, si)
38
- e.freeze
39
- }
40
- end
37
+ def get_colored iseq
38
+ if lines = @cmap[iseq]
39
+ lines
40
+ else
41
+ if src_lines = get(iseq)
42
+ @cmap[iseq] = colorize_code(src_lines.join("\n")).lines
43
+ else
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ else
49
+ # ruby 3.0 or earlier
50
+ SrcInfo = Struct.new(:src, :colored)
41
51
 
42
- private def add_path path
43
- begin
44
- src = File.read(path)
45
- src = src.gsub("\r\n", "\n") # CRLF -> LF
46
- @files[path] = SrcInfo.new(src.lines)
47
- rescue SystemCallError
52
+ def initialize
53
+ @files = {} # filename => SrcInfo
54
+ end
55
+
56
+ def add iseq, src
57
+ if (path = (iseq.absolute_path || iseq.path)) && File.exist?(path)
58
+ add_path path
59
+ elsif src
60
+ add_iseq iseq, src
61
+ end
48
62
  end
49
- end
50
63
 
51
- private def get_si iseq
52
- return unless iseq
64
+ private def all_iseq iseq, rs = []
65
+ rs << iseq
66
+ iseq.each_child{|ci|
67
+ all_iseq(ci, rs)
68
+ }
69
+ rs
70
+ end
53
71
 
54
- if iseq.instance_variable_defined?(:@debugger_si)
55
- iseq.instance_variable_get(:@debugger_si)
56
- elsif @files.has_key?(path = iseq.absolute_path)
57
- @files[path]
58
- elsif path
59
- add_path(path)
72
+ private def add_iseq iseq, src
73
+ line = iseq.first_line
74
+ if line > 1
75
+ src = ("\n" * (line - 1)) + src
76
+ end
77
+ si = SrcInfo.new(src.lines)
78
+ all_iseq(iseq).each{|e|
79
+ e.instance_variable_set(:@debugger_si, si)
80
+ e.freeze
81
+ }
60
82
  end
61
- end
62
83
 
63
- def get iseq
64
- if si = get_si(iseq)
65
- si.src
84
+ private def add_path path
85
+ src_lines = File.readlines(path, chomp: true)
86
+ @files[path] = SrcInfo.new(src_lines)
87
+ rescue SystemCallError
66
88
  end
67
- end
68
89
 
69
- include Color
90
+ private def get_si iseq
91
+ return unless iseq
92
+
93
+ if iseq.instance_variable_defined?(:@debugger_si)
94
+ iseq.instance_variable_get(:@debugger_si)
95
+ elsif @files.has_key?(path = (iseq.absolute_path || iseq.path))
96
+ @files[path]
97
+ elsif path
98
+ add_path(path)
99
+ end
100
+ end
101
+
102
+ def get iseq
103
+ if si = get_si(iseq)
104
+ si.src
105
+ end
106
+ end
70
107
 
71
- def get_colored iseq
72
- if si = get_si(iseq)
73
- si.colored || begin
74
- si.colored = colorize_code(si.src.join).lines
108
+ def get_colored iseq
109
+ if si = get_si(iseq)
110
+ si.colored || begin
111
+ si.colored = colorize_code(si.src.join("\n")).lines
112
+ end
75
113
  end
76
114
  end
77
115
  end
@@ -8,7 +8,11 @@ require_relative 'color'
8
8
  module DEBUGGER__
9
9
  module SkipPathHelper
10
10
  def skip_path?(path)
11
- (skip_paths = CONFIG[:skip_path]) && skip_paths.any?{|skip_path| path.match?(skip_path)}
11
+ !path || skip_internal_path?(path) || (skip_paths = CONFIG[:skip_path]) && skip_paths.any?{|skip_path| path.match?(skip_path)}
12
+ end
13
+
14
+ def skip_internal_path?(path)
15
+ path.start_with?(__dir__) || path.start_with?('<internal:')
12
16
  end
13
17
 
14
18
  def skip_location?(loc)
@@ -22,7 +26,7 @@ module DEBUGGER__
22
26
  if thc = Thread.current[:DEBUGGER__ThreadClient]
23
27
  thc
24
28
  else
25
- thc = SESSION.thread_client
29
+ thc = SESSION.get_thread_client
26
30
  Thread.current[:DEBUGGER__ThreadClient] = thc
27
31
  end
28
32
  end
@@ -30,7 +34,11 @@ module DEBUGGER__
30
34
  include Color
31
35
  include SkipPathHelper
32
36
 
33
- attr_reader :location, :thread, :id, :recorder
37
+ attr_reader :thread, :id, :recorder
38
+
39
+ def location
40
+ current_frame&.location
41
+ end
34
42
 
35
43
  def assemble_arguments(args)
36
44
  args.map do |arg|
@@ -67,7 +75,7 @@ module DEBUGGER__
67
75
  result = "#{call_identifier_str} at #{location_str}"
68
76
 
69
77
  if return_str = frame.return_str
70
- result += " #=> #{colorize_magenta(frame.return_str)}"
78
+ result += " #=> #{colorize_magenta(return_str)}"
71
79
  end
72
80
 
73
81
  result
@@ -84,6 +92,7 @@ module DEBUGGER__
84
92
  @output = []
85
93
  @frame_formatter = method(:default_frame_formatter)
86
94
  @var_map = {} # { thread_local_var_id => obj } for DAP
95
+ @obj_map = {} # { object_id => obj } for CDP
87
96
  @recorder = nil
88
97
  @mode = :waiting
89
98
  set_mode :running
@@ -100,13 +109,13 @@ module DEBUGGER__
100
109
  @is_management
101
110
  end
102
111
 
103
- def is_management
112
+ def mark_as_management
104
113
  @is_management = true
105
114
  end
106
115
 
107
116
  def set_mode mode
108
117
  # STDERR.puts "#{@mode} => #{mode} @ #{caller.inspect}"
109
- #pp caller
118
+ # pp caller
110
119
 
111
120
  # mode transition check
112
121
  case mode
@@ -148,14 +157,7 @@ module DEBUGGER__
148
157
  end
149
158
 
150
159
  def to_s
151
- loc = current_frame&.location
152
-
153
- if loc
154
- str = "(#{@thread.name || @thread.status})@#{loc}"
155
- else
156
- str = "(#{@thread.name || @thread.status})@#{@thread.to_s}"
157
- end
158
-
160
+ str = "(#{@thread.name || @thread.status})@#{current_frame&.location || @thread.to_s}"
159
161
  str += " (not under control)" unless self.waiting?
160
162
  str
161
163
  end
@@ -228,7 +230,7 @@ module DEBUGGER__
228
230
  suspend :pause
229
231
  end
230
232
 
231
- def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil
233
+ def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil
232
234
  return if management?
233
235
 
234
236
  @current_frame_index = 0
@@ -245,7 +247,6 @@ module DEBUGGER__
245
247
 
246
248
  cf = @target_frames.first
247
249
  if cf
248
- @location = cf.location
249
250
  case event
250
251
  when :return, :b_return, :c_return
251
252
  cf.has_return_value = true
@@ -256,10 +257,15 @@ module DEBUGGER__
256
257
  cf.has_raised_exception = true
257
258
  cf.raised_exception = bp.last_exc
258
259
  end
260
+
261
+ if postmortem_exc
262
+ cf.has_raised_exception = true
263
+ cf.raised_exception = postmortem_exc
264
+ end
259
265
  end
260
266
 
261
267
  if event != :pause
262
- show_src max_lines: (CONFIG[:show_src_lines] || 10)
268
+ show_src
263
269
  show_frames CONFIG[:show_frames] || 2
264
270
 
265
271
  set_mode :waiting
@@ -292,15 +298,15 @@ module DEBUGGER__
292
298
  SUPPORT_TARGET_THREAD = false
293
299
  end
294
300
 
295
- def step_tp iter
301
+ def step_tp iter, events = [:line, :b_return, :return]
296
302
  @step_tp.disable if @step_tp
297
303
 
298
304
  thread = Thread.current
299
305
 
300
306
  if SUPPORT_TARGET_THREAD
301
- @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
307
+ @step_tp = TracePoint.new(*events){|tp|
302
308
  next if SESSION.break_at? tp.path, tp.lineno
303
- next if !yield
309
+ next if !yield(tp.event)
304
310
  next if tp.path.start_with?(__dir__)
305
311
  next if tp.path.start_with?('<internal:trace_point>')
306
312
  next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
@@ -313,10 +319,10 @@ module DEBUGGER__
313
319
  }
314
320
  @step_tp.enable(target_thread: thread)
315
321
  else
316
- @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
322
+ @step_tp = TracePoint.new(*events){|tp|
317
323
  next if thread != Thread.current
318
324
  next if SESSION.break_at? tp.path, tp.lineno
319
- next if !yield
325
+ next if !yield(tp.event)
320
326
  next if tp.path.start_with?(__dir__)
321
327
  next if tp.path.start_with?('<internal:trace_point>')
322
328
  next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
@@ -333,43 +339,83 @@ module DEBUGGER__
333
339
 
334
340
  ## cmd helpers
335
341
 
336
- # this method is extracted to hide frame_eval's local variables from C method eval's binding
337
- def instance_eval_for_cmethod frame_self, src
338
- frame_self.instance_eval(src)
342
+ if TracePoint.respond_to? :allow_reentry
343
+ def tp_allow_reentry
344
+ TracePoint.allow_reentry do
345
+ yield
346
+ end
347
+ rescue RuntimeError => e
348
+ # on the postmortem mode, it is not stopped in TracePoint
349
+ if e.message == 'No need to allow reentrance.'
350
+ yield
351
+ else
352
+ raise
353
+ end
354
+ end
355
+ else
356
+ def tp_allow_reentry
357
+ yield
358
+ end
359
+ end
360
+
361
+ def frame_eval_core src, b
362
+ saved_target_frames = @target_frames
363
+ saved_current_frame_index = @current_frame_index
364
+
365
+ if b
366
+ f, _l = b.source_location
367
+
368
+ tp_allow_reentry do
369
+ b.eval(src, "(rdbg)/#{f}")
370
+ end
371
+ else
372
+ frame_self = current_frame.self
373
+
374
+ tp_allow_reentry do
375
+ frame_self.instance_eval(src)
376
+ end
377
+ end
378
+ ensure
379
+ @target_frames = saved_target_frames
380
+ @current_frame_index = saved_current_frame_index
339
381
  end
340
382
 
383
+ SPECIAL_LOCAL_VARS = [
384
+ [:raised_exception, "_raised"],
385
+ [:return_value, "_return"],
386
+ ]
387
+
341
388
  def frame_eval src, re_raise: false
342
- begin
343
- @success_last_eval = false
389
+ @success_last_eval = false
344
390
 
345
- b = current_frame&.eval_binding || TOPLEVEL_BINDING
391
+ b = current_frame.eval_binding
346
392
 
347
- result = if b
348
- f, _l = b.source_location
349
- b.eval(src, "(rdbg)/#{f}")
350
- else
351
- frame_self = current_frame.self
352
- instance_eval_for_cmethod(frame_self, src)
353
- end
354
- @success_last_eval = true
355
- result
393
+ special_local_variables current_frame do |name, var|
394
+ b.local_variable_set(name, var) if /\%/ !~ name
395
+ end
356
396
 
357
- rescue Exception => e
358
- return yield(e) if block_given?
397
+ result = frame_eval_core(src, b)
359
398
 
360
- puts "eval error: #{e}"
399
+ @success_last_eval = true
400
+ result
361
401
 
362
- e.backtrace_locations&.each do |loc|
363
- break if loc.path == __FILE__
364
- puts " #{loc}"
365
- end
366
- raise if re_raise
402
+ rescue SystemExit
403
+ raise
404
+ rescue Exception => e
405
+ return yield(e) if block_given?
406
+
407
+ puts "eval error: #{e}"
408
+
409
+ e.backtrace_locations&.each do |loc|
410
+ break if loc.path == __FILE__
411
+ puts " #{loc}"
367
412
  end
413
+ raise if re_raise
368
414
  end
369
415
 
370
416
  def show_src(frame_index: @current_frame_index,
371
417
  update_line: false,
372
- max_lines: 10,
418
+ max_lines: CONFIG[:show_src_lines] || 10,
373
419
  start_line: nil,
374
420
  end_line: nil,
375
421
  dir: +1)
@@ -419,31 +465,50 @@ module DEBUGGER__
419
465
  end
420
466
 
421
467
  def current_frame
468
+ get_frame(@current_frame_index)
469
+ end
470
+
471
+ def get_frame(index)
422
472
  if @target_frames
423
- @target_frames[@current_frame_index]
473
+ @target_frames[index]
424
474
  else
425
475
  nil
426
476
  end
427
477
  end
428
478
 
429
- ## cmd: show
479
+ def collect_locals(frame)
480
+ locals = []
430
481
 
431
- def show_locals pat
432
- if s = current_frame&.self
433
- puts_variable_info '%self', s, pat
482
+ if s = frame&.self
483
+ locals << ["%self", s]
434
484
  end
435
- if current_frame&.has_return_value
436
- puts_variable_info '%return', current_frame.return_value, pat
437
- end
438
- if current_frame&.has_raised_exception
439
- puts_variable_info "%raised", current_frame.raised_exception, pat
485
+ special_local_variables frame do |name, val|
486
+ locals << [name, val]
440
487
  end
441
488
 
442
- if vars = current_frame&.local_variables
489
+ if vars = frame&.local_variables
443
490
  vars.each{|var, val|
444
- puts_variable_info var, val, pat
491
+ locals << [var, val]
445
492
  }
446
493
  end
494
+
495
+ locals
496
+ end
497
+
498
+ ## cmd: show
499
+
500
+ def special_local_variables frame
501
+ SPECIAL_LOCAL_VARS.each do |mid, name|
502
+ next unless frame&.send("has_#{mid}")
503
+ name = name.sub('_', '%') if frame.eval_binding.local_variable_defined?(name)
504
+ yield name, frame.send(mid)
505
+ end
506
+ end
507
+
508
+ def show_locals pat
509
+ collect_locals(current_frame).each do |var, val|
510
+ puts_variable_info(var, val, pat)
511
+ end
447
512
  end
448
513
 
449
514
  def show_ivars pat
@@ -508,28 +573,32 @@ module DEBUGGER__
508
573
  w = SESSION::width
509
574
 
510
575
  if mono_info.length >= w
511
- info = truncate(mono_info, width: w)
576
+ maximum_value_width = w - "#{label} = ".length
577
+ valstr = truncate(inspected, width: maximum_value_width)
512
578
  else
513
579
  valstr = colored_inspect(obj, width: 2 ** 30)
514
580
  valstr = inspected if valstr.lines.size > 1
515
- info = "#{colorize_cyan(label)} = #{valstr}"
516
581
  end
517
582
 
583
+ info = "#{colorize_cyan(label)} = #{valstr}"
584
+
518
585
  puts info
519
586
  end
520
587
 
521
588
  def truncate(string, width:)
522
- str = string[0 .. (width-4)] + '...'
523
- str += ">" if str.start_with?("#<")
524
- str
589
+ if string.start_with?("#<")
590
+ string[0 .. (width-5)] + '...>'
591
+ else
592
+ string[0 .. (width-4)] + '...'
593
+ end
525
594
  end
526
595
 
527
596
  ### cmd: show edit
528
597
 
529
598
  def show_by_editor path = nil
530
599
  unless path
531
- if @target_frames && frame = @target_frames[@current_frame_index]
532
- path = frame.path
600
+ if current_frame
601
+ path = current_frame.path
533
602
  else
534
603
  return # can't get path
535
604
  end
@@ -633,8 +702,8 @@ module DEBUGGER__
633
702
  def make_breakpoint args
634
703
  case args.first
635
704
  when :method
636
- klass_name, op, method_name, cond, cmd = args[1..]
637
- bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond: cond, command: cmd)
705
+ klass_name, op, method_name, cond, cmd, path = args[1..]
706
+ bp = MethodBreakpoint.new(current_frame.eval_binding, klass_name, op, method_name, cond: cond, command: cmd, path: path)
638
707
  begin
639
708
  bp.enable
640
709
  rescue Exception => e
@@ -644,8 +713,8 @@ module DEBUGGER__
644
713
 
645
714
  bp
646
715
  when :watch
647
- ivar, object, result = args[1..]
648
- WatchIVarBreakpoint.new(ivar, object, result)
716
+ ivar, object, result, cond, command, path = args[1..]
717
+ WatchIVarBreakpoint.new(ivar, object, result, cond: cond, command: command, path: path)
649
718
  else
650
719
  raise "unknown breakpoint: #{args}"
651
720
  end
@@ -732,10 +801,11 @@ module DEBUGGER__
732
801
  break
733
802
 
734
803
  when :finish
735
- depth = @target_frames.first.frame_depth
736
- step_tp iter do
737
- # 3 is debugger's frame count
738
- DEBUGGER__.frame_depth - 3 < depth
804
+ finish_frames = (iter || 1) - 1
805
+ goal_depth = @target_frames.first.frame_depth - finish_frames
806
+
807
+ step_tp nil, [:return, :b_return] do
808
+ DEBUGGER__.frame_depth - 3 <= goal_depth ? true : false
739
809
  end
740
810
  break
741
811
 
@@ -834,6 +904,7 @@ module DEBUGGER__
834
904
  else
835
905
  raise "unsupported frame operation: #{arg.inspect}"
836
906
  end
907
+
837
908
  event! :result, nil
838
909
 
839
910
  when :show
@@ -887,7 +958,7 @@ module DEBUGGER__
887
958
  bp = make_breakpoint args
888
959
  event! :result, :method_breakpoint, bp
889
960
  when :watch
890
- ivar = args[1]
961
+ ivar, cond, command, path = args[1..]
891
962
  result = frame_eval(ivar)
892
963
 
893
964
  if @success_last_eval
@@ -897,7 +968,7 @@ module DEBUGGER__
897
968
  else
898
969
  current_frame.self
899
970
  end
900
- bp = make_breakpoint [:watch, ivar, object, result]
971
+ bp = make_breakpoint [:watch, ivar, object, result, cond, command, path]
901
972
  event! :result, :watch_breakpoint, bp
902
973
  else
903
974
  event! :result, nil
@@ -982,8 +1053,8 @@ module DEBUGGER__
982
1053
 
983
1054
  @tp_recorder ||= TracePoint.new(:line){|tp|
984
1055
  next unless Thread.current == thread
985
- next if tp.path.start_with? __dir__
986
- next if tp.path.start_with? '<internal:'
1056
+ # can't be replaced by skip_location
1057
+ next if skip_internal_path?(tp.path)
987
1058
  loc = caller_locations(1, 1).first
988
1059
  next if skip_location?(loc)
989
1060
 
@@ -1067,7 +1138,7 @@ module DEBUGGER__
1067
1138
  end
1068
1139
  end
1069
1140
 
1070
- # copyed from irb
1141
+ # copied from irb
1071
1142
  class Output
1072
1143
  include Color
1073
1144
 
data/lib/debug/tracer.rb CHANGED
@@ -66,15 +66,7 @@ module DEBUGGER__
66
66
  end
67
67
 
68
68
  def skip? tp
69
- if tp.path.start_with?(__dir__) ||
70
- tp.path.start_with?('<internal:') ||
71
- ThreadClient.current.management? ||
72
- skip_path?(tp.path) ||
73
- skip_with_pattern?(tp)
74
- true
75
- else
76
- false
77
- end
69
+ ThreadClient.current.management? || skip_path?(tp.path) || skip_with_pattern?(tp)
78
70
  end
79
71
 
80
72
  def skip_with_pattern?(tp)
@@ -89,11 +81,13 @@ module DEBUGGER__
89
81
  ThreadClient.current.on_trace self.object_id, buff
90
82
  else
91
83
  @output.puts buff
84
+ @output.flush
92
85
  end
93
86
  end
94
87
 
95
88
  def puts msg
96
89
  @output.puts msg
90
+ @output.flush
97
91
  end
98
92
 
99
93
  def minfo tp
@@ -142,7 +136,7 @@ module DEBUGGER__
142
136
  out tp, ">#{sp}#{call_identifier_str}", depth
143
137
  when :return, :c_return, :b_return
144
138
  depth += 1 if tp.event == :c_return
145
- return_str = colorize_magenta(DEBUGGER__.short_inspect(tp.return_value))
139
+ return_str = colorize_magenta(DEBUGGER__.safe_inspect(tp.return_value, short: true))
146
140
  out tp, "<#{sp}#{call_identifier_str} #=> #{return_str}", depth
147
141
  end
148
142
  }
data/lib/debug/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DEBUGGER__
4
- VERSION = "1.3.3"
4
+ VERSION = "1.5.0"
5
5
  end