debug 1.5.0 → 1.6.3

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/lib/debug/session.rb CHANGED
@@ -19,6 +19,15 @@ if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
19
19
  return
20
20
  end
21
21
 
22
+ # restore RUBYOPT
23
+ if (added_opt = ENV['RUBY_DEBUG_ADDED_RUBYOPT']) &&
24
+ (rubyopt = ENV['RUBYOPT']) &&
25
+ rubyopt.end_with?(added_opt)
26
+
27
+ ENV['RUBYOPT'] = rubyopt.delete_suffix(added_opt)
28
+ ENV['RUBY_DEBUG_ADDED_RUBYOPT'] = nil
29
+ end
30
+
22
31
  require_relative 'frame_info'
23
32
  require_relative 'config'
24
33
  require_relative 'thread_client'
@@ -32,6 +41,7 @@ $LOADED_FEATURES << File.expand_path(File.join(__dir__, '..', 'debug.rb'))
32
41
  require 'debug' # invalidate the $LOADED_FEATURE cache
33
42
 
34
43
  require 'json' if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
44
+ require 'pp'
35
45
 
36
46
  class RubyVM::InstructionSequence
37
47
  def traceable_lines_norec lines
@@ -197,35 +207,44 @@ module DEBUGGER__
197
207
  deactivate
198
208
  end
199
209
 
210
+ def request_tc(req)
211
+ @tc << req
212
+ end
213
+
200
214
  def process_event evt
201
215
  # variable `@internal_info` is only used for test
202
216
  tc, output, ev, @internal_info, *ev_args = evt
203
- output.each{|str| @ui.puts str} if ev != :suspend
204
217
 
205
- case ev
218
+ output.each{|str| @ui.puts str} if ev != :suspend
206
219
 
207
- when :thread_begin # special event, tc is nil
220
+ # special event, tc is nil
221
+ # and we don't want to set @tc to the newly created thread's ThreadClient
222
+ if ev == :thread_begin
208
223
  th = ev_args.shift
209
224
  q = ev_args.shift
210
225
  on_thread_begin th
211
226
  q << true
212
227
 
228
+ return
229
+ end
230
+
231
+ @tc = tc
232
+
233
+ case ev
213
234
  when :init
214
235
  enter_subsession
215
- wait_command_loop tc
216
-
236
+ wait_command_loop
217
237
  when :load
218
238
  iseq, src = ev_args
219
239
  on_load iseq, src
220
- @ui.event :load
221
- tc << :continue
240
+ request_tc :continue
222
241
 
223
242
  when :trace
224
243
  trace_id, msg = ev_args
225
244
  if t = @tracers.values.find{|t| t.object_id == trace_id}
226
245
  t.puts msg
227
246
  end
228
- tc << :continue
247
+ request_tc :continue
229
248
 
230
249
  when :suspend
231
250
  enter_subsession if ev_args.first != :replay
@@ -235,24 +254,23 @@ module DEBUGGER__
235
254
  when :breakpoint
236
255
  bp, i = bp_index ev_args[1]
237
256
  clean_bps unless bp
238
- @ui.event :suspend_bp, i, bp, tc.id
257
+ @ui.event :suspend_bp, i, bp, @tc.id
239
258
  when :trap
240
- @ui.event :suspend_trap, sig = ev_args[1], tc.id
259
+ @ui.event :suspend_trap, sig = ev_args[1], @tc.id
241
260
 
242
261
  if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
243
262
  @ui.puts "#{@intercepted_sigint_cmd.inspect} is registered as SIGINT handler."
244
263
  @ui.puts "`sigint` command execute it."
245
264
  end
246
265
  else
247
- @ui.event :suspended, tc.id
266
+ @ui.event :suspended, @tc.id
248
267
  end
249
268
 
250
269
  if @displays.empty?
251
- wait_command_loop tc
270
+ wait_command_loop
252
271
  else
253
- tc << [:eval, :display, @displays]
272
+ request_tc [:eval, :display, @displays]
254
273
  end
255
-
256
274
  when :result
257
275
  raise "[BUG] not in subsession" if @subsession_stack.empty?
258
276
 
@@ -283,14 +301,14 @@ module DEBUGGER__
283
301
  # ignore
284
302
  end
285
303
 
286
- wait_command_loop tc
304
+ wait_command_loop
287
305
 
288
306
  when :dap_result
289
307
  dap_event ev_args # server.rb
290
- wait_command_loop tc
308
+ wait_command_loop
291
309
  when :cdp_result
292
310
  cdp_event ev_args
293
- wait_command_loop tc
311
+ wait_command_loop
294
312
  end
295
313
  end
296
314
 
@@ -323,9 +341,7 @@ module DEBUGGER__
323
341
  "DEBUGGER__::SESSION"
324
342
  end
325
343
 
326
- def wait_command_loop tc
327
- @tc = tc
328
-
344
+ def wait_command_loop
329
345
  loop do
330
346
  case wait_command
331
347
  when :retry
@@ -529,32 +545,6 @@ module DEBUGGER__
529
545
  end
530
546
  end
531
547
 
532
- # skip
533
- when 'bv'
534
- check_postmortem
535
- require 'json'
536
-
537
- h = Hash.new{|h, k| h[k] = []}
538
- @bps.each_value{|bp|
539
- if LineBreakpoint === bp
540
- h[bp.path] << {lnum: bp.line}
541
- end
542
- }
543
- if h.empty?
544
- # TODO: clean?
545
- else
546
- open(".rdb_breakpoints.json", 'w'){|f| JSON.dump(h, f)}
547
- end
548
-
549
- vimsrc = File.join(__dir__, 'bp.vim')
550
- system("vim -R -S #{vimsrc} #{@tc.location.path}")
551
-
552
- if File.exist?(".rdb_breakpoints.json")
553
- pp JSON.load(File.read(".rdb_breakpoints.json"))
554
- end
555
-
556
- return :retry
557
-
558
548
  # * `catch <Error>`
559
549
  # * Set breakpoint on raising `<Error>`.
560
550
  # * `catch ... if: <expr>`
@@ -632,15 +622,15 @@ module DEBUGGER__
632
622
  when 'bt', 'backtrace'
633
623
  case arg
634
624
  when /\A(\d+)\z/
635
- @tc << [:show, :backtrace, arg.to_i, nil]
625
+ request_tc [:show, :backtrace, arg.to_i, nil]
636
626
  when /\A\/(.*)\/\z/
637
627
  pattern = $1
638
- @tc << [:show, :backtrace, nil, Regexp.compile(pattern)]
628
+ request_tc [:show, :backtrace, nil, Regexp.compile(pattern)]
639
629
  when /\A(\d+)\s+\/(.*)\/\z/
640
630
  max, pattern = $1, $2
641
- @tc << [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
631
+ request_tc [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
642
632
  else
643
- @tc << [:show, :backtrace, nil, nil]
633
+ request_tc [:show, :backtrace, nil, nil]
644
634
  end
645
635
 
646
636
  # * `l[ist]`
@@ -653,13 +643,13 @@ module DEBUGGER__
653
643
  when 'l', 'list'
654
644
  case arg ? arg.strip : nil
655
645
  when /\A(\d+)\z/
656
- @tc << [:show, :list, {start_line: arg.to_i - 1}]
646
+ request_tc [:show, :list, {start_line: arg.to_i - 1}]
657
647
  when /\A-\z/
658
- @tc << [:show, :list, {dir: -1}]
648
+ request_tc [:show, :list, {dir: -1}]
659
649
  when /\A(\d+)-(\d+)\z/
660
- @tc << [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
650
+ request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
661
651
  when nil
662
- @tc << [:show, :list]
652
+ request_tc [:show, :list]
663
653
  else
664
654
  @ui.puts "Can not handle list argument: #{arg}"
665
655
  return :retry
@@ -683,7 +673,7 @@ module DEBUGGER__
683
673
  return :retry
684
674
  end
685
675
 
686
- @tc << [:show, :edit, arg]
676
+ request_tc [:show, :edit, arg]
687
677
 
688
678
  # * `i[nfo]`
689
679
  # * Show information about current frame (local/instance variables and defined constants).
@@ -710,15 +700,15 @@ module DEBUGGER__
710
700
 
711
701
  case sub
712
702
  when nil
713
- @tc << [:show, :default, pat] # something useful
703
+ request_tc [:show, :default, pat] # something useful
714
704
  when 'l', /^locals?/
715
- @tc << [:show, :locals, pat]
705
+ request_tc [:show, :locals, pat]
716
706
  when 'i', /^ivars?/i, /^instance[_ ]variables?/i
717
- @tc << [:show, :ivars, pat]
707
+ request_tc [:show, :ivars, pat]
718
708
  when 'c', /^consts?/i, /^constants?/i
719
- @tc << [:show, :consts, pat]
709
+ request_tc [:show, :consts, pat]
720
710
  when 'g', /^globals?/i, /^global[_ ]variables?/i
721
- @tc << [:show, :globals, pat]
711
+ request_tc [:show, :globals, pat]
722
712
  when 'th', /threads?/
723
713
  thread_list
724
714
  return :retry
@@ -734,7 +724,7 @@ module DEBUGGER__
734
724
  # * Show you available methods and instance variables of the given object.
735
725
  # * If the object is a class/module, it also lists its constants.
736
726
  when 'outline', 'o', 'ls'
737
- @tc << [:show, :outline, arg]
727
+ request_tc [:show, :outline, arg]
738
728
 
739
729
  # * `display`
740
730
  # * Show display setting.
@@ -743,9 +733,9 @@ module DEBUGGER__
743
733
  when 'display'
744
734
  if arg && !arg.empty?
745
735
  @displays << arg
746
- @tc << [:eval, :try_display, @displays]
736
+ request_tc [:eval, :try_display, @displays]
747
737
  else
748
- @tc << [:eval, :display, @displays]
738
+ request_tc [:eval, :display, @displays]
749
739
  end
750
740
 
751
741
  # * `undisplay`
@@ -758,7 +748,7 @@ module DEBUGGER__
758
748
  if @displays[n = $1.to_i]
759
749
  @displays.delete_at n
760
750
  end
761
- @tc << [:eval, :display, @displays]
751
+ request_tc [:eval, :display, @displays]
762
752
  when nil
763
753
  if ask "clear all?", 'N'
764
754
  @displays.clear
@@ -773,29 +763,29 @@ module DEBUGGER__
773
763
  # * `f[rame] <framenum>`
774
764
  # * Specify a current frame. Evaluation are run on specified frame.
775
765
  when 'frame', 'f'
776
- @tc << [:frame, :set, arg]
766
+ request_tc [:frame, :set, arg]
777
767
 
778
768
  # * `up`
779
769
  # * Specify the upper frame.
780
770
  when 'up'
781
- @tc << [:frame, :up]
771
+ request_tc [:frame, :up]
782
772
 
783
773
  # * `down`
784
774
  # * Specify the lower frame.
785
775
  when 'down'
786
- @tc << [:frame, :down]
776
+ request_tc [:frame, :down]
787
777
 
788
778
  ### Evaluate
789
779
 
790
780
  # * `p <expr>`
791
781
  # * Evaluate like `p <expr>` on the current frame.
792
782
  when 'p'
793
- @tc << [:eval, :p, arg.to_s]
783
+ request_tc [:eval, :p, arg.to_s]
794
784
 
795
785
  # * `pp <expr>`
796
786
  # * Evaluate like `pp <expr>` on the current frame.
797
787
  when 'pp'
798
- @tc << [:eval, :pp, arg.to_s]
788
+ request_tc [:eval, :pp, arg.to_s]
799
789
 
800
790
  # * `eval <expr>`
801
791
  # * Evaluate `<expr>` on the current frame.
@@ -805,7 +795,7 @@ module DEBUGGER__
805
795
  @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
806
796
  return :retry
807
797
  else
808
- @tc << [:eval, :call, arg]
798
+ request_tc [:eval, :call, arg]
809
799
  end
810
800
 
811
801
  # * `irb`
@@ -815,7 +805,7 @@ module DEBUGGER__
815
805
  @ui.puts "not supported on the remote console."
816
806
  return :retry
817
807
  end
818
- @tc << [:eval, :irb]
808
+ request_tc [:eval, :irb]
819
809
 
820
810
  # don't repeat irb command
821
811
  @repl_prev_line = nil
@@ -872,7 +862,7 @@ module DEBUGGER__
872
862
  return :retry
873
863
 
874
864
  when /\Aobject\s+(.+)/
875
- @tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
865
+ request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}]
876
866
 
877
867
  when /\Aoff\s+(\d+)\z/
878
868
  if t = @tracers.values[$1.to_i]
@@ -910,7 +900,7 @@ module DEBUGGER__
910
900
  when 'record'
911
901
  case arg
912
902
  when nil, 'on', 'off'
913
- @tc << [:record, arg&.to_sym]
903
+ request_tc [:record, arg&.to_sym]
914
904
  else
915
905
  @ui.puts "unknown command: #{arg}"
916
906
  return :retry
@@ -1005,7 +995,7 @@ module DEBUGGER__
1005
995
 
1006
996
  ### END
1007
997
  else
1008
- @tc << [:eval, :pp, line]
998
+ request_tc [:eval, :pp, line]
1009
999
  =begin
1010
1000
  @repl_prev_line = nil
1011
1001
  @ui.puts "unknown command: #{line}"
@@ -1062,7 +1052,7 @@ module DEBUGGER__
1062
1052
  case arg
1063
1053
  when nil, /\A\d+\z/
1064
1054
  if type == :in && @tc.recorder&.replaying?
1065
- @tc << [:step, type, arg&.to_i]
1055
+ request_tc [:step, type, arg&.to_i]
1066
1056
  else
1067
1057
  leave_subsession [:step, type, arg&.to_i]
1068
1058
  end
@@ -1071,7 +1061,7 @@ module DEBUGGER__
1071
1061
  @ui.puts "only `step #{arg}` is supported."
1072
1062
  :retry
1073
1063
  else
1074
- @tc << [:step, arg.to_sym]
1064
+ request_tc [:step, arg.to_sym]
1075
1065
  end
1076
1066
  else
1077
1067
  @ui.puts "Unknown option: #{arg}"
@@ -1081,11 +1071,18 @@ module DEBUGGER__
1081
1071
 
1082
1072
  def config_show key
1083
1073
  key = key.to_sym
1084
- if CONFIG_SET[key]
1074
+ config_detail = CONFIG_SET[key]
1075
+
1076
+ if config_detail
1085
1077
  v = CONFIG[key]
1086
- kv = "#{key} = #{v.nil? ? '(default)' : v.inspect}"
1087
- desc = CONFIG_SET[key][1]
1088
- line = "%-30s \# %s" % [kv, desc]
1078
+ kv = "#{key} = #{v.inspect}"
1079
+ desc = config_detail[1]
1080
+
1081
+ if config_default = config_detail[3]
1082
+ desc += " (default: #{config_default})"
1083
+ end
1084
+
1085
+ line = "%-34s \# %s" % [kv, desc]
1089
1086
  if line.size > SESSION.width
1090
1087
  @ui.puts "\# #{desc}\n#{kv}"
1091
1088
  else
@@ -1135,7 +1132,7 @@ module DEBUGGER__
1135
1132
  config_set $1, $2, append: true
1136
1133
 
1137
1134
  when /\A\s*append\s+(\w+)\s+(.+)\z/
1138
- config_set $1, $2
1135
+ config_set $1, $2, append: true
1139
1136
 
1140
1137
  when /\A(\w+)\z/
1141
1138
  config_show $1
@@ -1261,9 +1258,12 @@ module DEBUGGER__
1261
1258
  @repl_prev_line = nil
1262
1259
 
1263
1260
  if @bps.has_key? bp.key
1264
- unless bp.duplicable?
1261
+ if bp.duplicable?
1262
+ bp
1263
+ else
1265
1264
  @ui.puts "duplicated breakpoint: #{bp}"
1266
1265
  bp.disable
1266
+ nil
1267
1267
  end
1268
1268
  else
1269
1269
  @bps[bp.key] = bp
@@ -1320,7 +1320,7 @@ module DEBUGGER__
1320
1320
  when /\A(.+)[:\s+](\d+)\z/
1321
1321
  add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
1322
1322
  when /\A(.+)([\.\#])(.+)\z/
1323
- @tc << [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
1323
+ request_tc [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
1324
1324
  return :noretry
1325
1325
  when nil
1326
1326
  add_check_breakpoint cond, path, cmd
@@ -1347,11 +1347,11 @@ module DEBUGGER__
1347
1347
  cmd = ['watch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1348
1348
  path = Regexp.compile(expr[:path]) if expr[:path]
1349
1349
 
1350
- @tc << [:breakpoint, :watch, expr[:sig], cond, cmd, path]
1350
+ request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
1351
1351
  end
1352
1352
 
1353
- def add_catch_breakpoint pat
1354
- bp = CatchBreakpoint.new(pat)
1353
+ def add_catch_breakpoint pat, cond: nil
1354
+ bp = CatchBreakpoint.new(pat, cond: cond)
1355
1355
  add_bp bp
1356
1356
  end
1357
1357
 
@@ -1369,15 +1369,34 @@ module DEBUGGER__
1369
1369
  @ui.puts e.message
1370
1370
  end
1371
1371
 
1372
- def clear_line_breakpoints path
1373
- path = resolve_path(path)
1372
+ def clear_breakpoints(&condition)
1374
1373
  @bps.delete_if do |k, bp|
1375
- if (Array === k) && DEBUGGER__.compare_path(k.first, path)
1374
+ if condition.call(k, bp)
1376
1375
  bp.delete
1376
+ true
1377
1377
  end
1378
1378
  end
1379
1379
  end
1380
1380
 
1381
+ def clear_line_breakpoints path
1382
+ path = resolve_path(path)
1383
+ clear_breakpoints do |k, bp|
1384
+ bp.is_a?(LineBreakpoint) && DEBUGGER__.compare_path(k.first, path)
1385
+ end
1386
+ rescue Errno::ENOENT
1387
+ # just ignore
1388
+ end
1389
+
1390
+ def clear_catch_breakpoints *exception_names
1391
+ clear_breakpoints do |k, bp|
1392
+ bp.is_a?(CatchBreakpoint) && exception_names.include?(k[1])
1393
+ end
1394
+ end
1395
+
1396
+ def clear_all_breakpoints
1397
+ clear_breakpoints{true}
1398
+ end
1399
+
1381
1400
  def add_iseq_breakpoint iseq, **kw
1382
1401
  bp = ISeqBreakpoint.new(iseq, [:line], **kw)
1383
1402
  add_bp bp
@@ -1568,7 +1587,7 @@ module DEBUGGER__
1568
1587
  DEBUGGER__.info "Leave subsession (nested #{@subsession_stack.size})"
1569
1588
  end
1570
1589
 
1571
- @tc << type if type
1590
+ request_tc type if type
1572
1591
  @tc = nil
1573
1592
  rescue Exception => e
1574
1593
  STDERR.puts PP.pp([e, e.backtrace], ''.dup)
@@ -1583,7 +1602,9 @@ module DEBUGGER__
1583
1602
 
1584
1603
  def on_load iseq, src
1585
1604
  DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
1586
- @sr.add iseq, src
1605
+
1606
+ file_path, reloaded = @sr.add(iseq, src)
1607
+ @ui.event :load, file_path, reloaded
1587
1608
 
1588
1609
  pending_line_breakpoints = @bps.find_all do |key, bp|
1589
1610
  LineBreakpoint === bp && !bp.iseq
@@ -1591,7 +1612,18 @@ module DEBUGGER__
1591
1612
 
1592
1613
  pending_line_breakpoints.each do |_key, bp|
1593
1614
  if DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
1594
- bp.try_activate
1615
+ bp.try_activate iseq
1616
+ end
1617
+ end
1618
+
1619
+ if reloaded
1620
+ @bps.find_all do |key, bp|
1621
+ LineBreakpoint === bp && DEBUGGER__.compare_path(bp.path, file_path)
1622
+ end.each do |_key, bp|
1623
+ @bps.delete bp.key # to allow duplicate
1624
+ if nbp = LineBreakpoint.copy(bp, iseq)
1625
+ add_bp nbp
1626
+ end
1595
1627
  end
1596
1628
  end
1597
1629
  end
@@ -1616,9 +1648,10 @@ module DEBUGGER__
1616
1648
 
1617
1649
  def method_added tp
1618
1650
  b = tp.binding
1651
+
1619
1652
  if var_name = b.local_variables.first
1620
1653
  mid = b.local_variable_get(var_name)
1621
- unresolved = false
1654
+ resolved = true
1622
1655
 
1623
1656
  @bps.each{|k, bp|
1624
1657
  case bp
@@ -1629,15 +1662,57 @@ module DEBUGGER__
1629
1662
  end
1630
1663
  end
1631
1664
 
1632
- unresolved = true unless bp.enabled?
1665
+ resolved = false if !bp.enabled?
1633
1666
  end
1634
1667
  }
1635
- unless unresolved
1636
- METHOD_ADDED_TRACKER.disable
1668
+
1669
+ if resolved
1670
+ Session.deactivate_method_added_trackers
1671
+ end
1672
+
1673
+ case mid
1674
+ when :method_added, :singleton_method_added
1675
+ Session.create_method_added_tracker(tp.self, mid)
1676
+ Session.activate_method_added_trackers unless resolved
1637
1677
  end
1638
1678
  end
1639
1679
  end
1640
1680
 
1681
+ class ::Module
1682
+ undef method_added
1683
+ def method_added mid; end
1684
+ end
1685
+
1686
+ class ::BasicObject
1687
+ undef singleton_method_added
1688
+ def singleton_method_added mid; end
1689
+ end
1690
+
1691
+ def self.create_method_added_tracker mod, method_added_id, method_accessor = :method
1692
+ m = mod.__send__(method_accessor, method_added_id)
1693
+ METHOD_ADDED_TRACKERS[m] = TracePoint.new(:call) do |tp|
1694
+ SESSION.method_added tp
1695
+ end
1696
+ end
1697
+
1698
+ def self.activate_method_added_trackers
1699
+ METHOD_ADDED_TRACKERS.each do |m, tp|
1700
+ tp.enable(target: m) unless tp.enabled?
1701
+ rescue ArgumentError
1702
+ DEBUGGER__.warn "Methods defined under #{m.owner} can not track by the debugger."
1703
+ end
1704
+ end
1705
+
1706
+ def self.deactivate_method_added_trackers
1707
+ METHOD_ADDED_TRACKERS.each do |m, tp|
1708
+ tp.disable if tp.enabled?
1709
+ end
1710
+ end
1711
+
1712
+ METHOD_ADDED_TRACKERS = Hash.new
1713
+ create_method_added_tracker Module, :method_added, :instance_method
1714
+ create_method_added_tracker BasicObject, :singleton_method_added, :instance_method
1715
+
1641
1716
  def width
1642
1717
  @ui.width
1643
1718
  end
@@ -1818,9 +1893,11 @@ module DEBUGGER__
1818
1893
 
1819
1894
  def after_fork child: true
1820
1895
  if child || !@lock_file
1821
- @m = Mutex.new
1822
- @lock_level = 0
1823
- @lock_file = open(@lock_tempfile.path, 'w')
1896
+ @m = Mutex.new unless @m
1897
+ @m.synchronize do
1898
+ @lock_level = 0
1899
+ @lock_file = open(@lock_tempfile.path, 'w')
1900
+ end
1824
1901
  end
1825
1902
  end
1826
1903
 
@@ -2051,34 +2128,53 @@ module DEBUGGER__
2051
2128
  end
2052
2129
  end
2053
2130
 
2054
- class ::Module
2055
- undef method_added
2056
- def method_added mid; end
2057
- def singleton_method_added mid; end
2058
- end
2131
+ # Inspector
2059
2132
 
2060
- def self.method_added tp
2061
- begin
2062
- SESSION.method_added tp
2063
- rescue Exception => e
2064
- p e
2133
+ SHORT_INSPECT_LENGTH = 40
2134
+
2135
+ class LimitedPP
2136
+ def self.pp(obj, max=80)
2137
+ out = self.new(max)
2138
+ catch out do
2139
+ PP.singleline_pp(obj, out)
2140
+ end
2141
+ out.buf
2065
2142
  end
2066
- end
2067
2143
 
2068
- METHOD_ADDED_TRACKER = self.create_method_added_tracker
2144
+ attr_reader :buf
2069
2145
 
2070
- SHORT_INSPECT_LENGTH = 40
2146
+ def initialize max
2147
+ @max = max
2148
+ @cnt = 0
2149
+ @buf = String.new
2150
+ end
2071
2151
 
2072
- def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
2073
- str = obj.inspect
2152
+ def <<(other)
2153
+ @buf << other
2154
+
2155
+ if @buf.size >= @max
2156
+ @buf = @buf[0..@max] + '...'
2157
+ throw self
2158
+ end
2159
+ end
2160
+ end
2074
2161
 
2075
- if short && str.length > max_length
2076
- str[0...max_length] + '...'
2162
+ def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
2163
+ if short
2164
+ LimitedPP.pp(obj, max_length)
2165
+ else
2166
+ obj.inspect
2167
+ end
2168
+ rescue NoMethodError => e
2169
+ klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
2170
+ if obj == (r = e.receiver)
2171
+ "<\##{klass.name}#{oid} does not have \#inspect>"
2077
2172
  else
2078
- str
2173
+ rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
2174
+ "<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
2079
2175
  end
2080
2176
  rescue Exception => e
2081
- str = "<#inspect raises #{e.inspect}>"
2177
+ "<#inspect raises #{e.inspect}>"
2082
2178
  end
2083
2179
 
2084
2180
  def self.warn msg
@@ -2089,18 +2185,27 @@ module DEBUGGER__
2089
2185
  log :INFO, msg
2090
2186
  end
2091
2187
 
2092
- def self.log level, msg
2093
- @logfile = STDERR unless defined? @logfile
2094
-
2188
+ def self.check_loglevel level
2095
2189
  lv = LOG_LEVELS[level]
2096
- config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
2190
+ config_lv = LOG_LEVELS[CONFIG[:log_level]]
2191
+ lv <= config_lv
2192
+ end
2097
2193
 
2098
- if defined? SESSION
2099
- pi = SESSION.process_info
2100
- process_info = pi ? "[#{pi}]" : nil
2194
+ def self.debug(&b)
2195
+ if check_loglevel :DEBUG
2196
+ log :DEBUG, b.call
2101
2197
  end
2198
+ end
2199
+
2200
+ def self.log level, msg
2201
+ if check_loglevel level
2202
+ @logfile = STDERR unless defined? @logfile
2203
+
2204
+ if defined? SESSION
2205
+ pi = SESSION.process_info
2206
+ process_info = pi ? "[#{pi}]" : nil
2207
+ end
2102
2208
 
2103
- if lv <= config_lv
2104
2209
  if level == :WARN
2105
2210
  # :WARN on debugger is general information
2106
2211
  @logfile.puts "DEBUGGER#{process_info}: #{msg}"
@@ -2126,7 +2231,7 @@ module DEBUGGER__
2126
2231
  # depend on the file system. So this check is only roughly estimation.
2127
2232
 
2128
2233
  def self.compare_path(a, b)
2129
- a.downcase == b.downcase
2234
+ a&.downcase == b&.downcase
2130
2235
  end
2131
2236
  else
2132
2237
  def self.compare_path(a, b)
@@ -2137,7 +2242,7 @@ module DEBUGGER__
2137
2242
  module ForkInterceptor
2138
2243
  if Process.respond_to? :_fork
2139
2244
  def _fork
2140
- return yield unless defined?(SESSION) && SESSION.active?
2245
+ return super unless defined?(SESSION) && SESSION.active?
2141
2246
 
2142
2247
  parent_hook, child_hook = __fork_setup_for_debugger
2143
2248
 
@@ -2153,7 +2258,7 @@ module DEBUGGER__
2153
2258
  end
2154
2259
  else
2155
2260
  def fork(&given_block)
2156
- return yield unless defined?(SESSION) && SESSION.active?
2261
+ return super unless defined?(SESSION) && SESSION.active?
2157
2262
  parent_hook, child_hook = __fork_setup_for_debugger
2158
2263
 
2159
2264
  if given_block
@@ -2178,12 +2283,10 @@ module DEBUGGER__
2178
2283
  end
2179
2284
 
2180
2285
  private def __fork_setup_for_debugger
2181
- unless fork_mode = CONFIG[:fork_mode]
2182
- if CONFIG[:parent_on_fork]
2183
- fork_mode = :parent
2184
- else
2185
- fork_mode = :both
2186
- end
2286
+ fork_mode = CONFIG[:fork_mode]
2287
+
2288
+ if fork_mode == :both && CONFIG[:parent_on_fork]
2289
+ fork_mode = :parent
2187
2290
  end
2188
2291
 
2189
2292
  parent_pid = Process.pid
@@ -2310,3 +2413,12 @@ class Binding
2310
2413
  alias break debugger
2311
2414
  alias b debugger
2312
2415
  end
2416
+
2417
+ # for Ruby 2.6 compatibility
2418
+ unless method(:p).unbind.respond_to? :bind_call
2419
+ class UnboundMethod
2420
+ def bind_call(obj, *args)
2421
+ self.bind(obj).call(*args)
2422
+ end
2423
+ end
2424
+ end