debug 1.3.4 → 1.6.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.
data/lib/debug/session.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ return if ENV['RUBY_DEBUG_ENABLE'] == '0'
4
+
3
5
  # skip to load debugger for bundle exec
4
6
 
5
7
  if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
6
8
  trace_var(:$0) do |file|
7
9
  trace_var(:$0, nil)
8
- if /-r (#{__dir__}\S+)/ =~ ENV['RUBYOPT']
10
+ if /-r (#{Regexp.escape(__dir__)}\S+)/ =~ ENV['RUBYOPT']
9
11
  lib = $1
10
12
  $LOADED_FEATURES.delete_if{|path| path.start_with?(__dir__)}
11
13
  ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = file
@@ -17,6 +19,14 @@ if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
17
19
  return
18
20
  end
19
21
 
22
+ # restore RUBYOPT
23
+ if (added_opt = ENV['RUBY_DEBUG_ADDED_RUBYOPT']) &&
24
+ (rubyopt = ENV['RUBYOPT']) &&
25
+ rubyopt.start_with?(added_opt)
26
+ ENV['RUBYOPT'] = rubyopt.delete_prefix(rubyopt)
27
+ ENV['RUBY_DEBUG_ADDED_RUBYOPT'] = nil
28
+ end
29
+
20
30
  require_relative 'frame_info'
21
31
  require_relative 'config'
22
32
  require_relative 'thread_client'
@@ -29,7 +39,8 @@ $LOADED_FEATURES << 'debug.rb'
29
39
  $LOADED_FEATURES << File.expand_path(File.join(__dir__, '..', 'debug.rb'))
30
40
  require 'debug' # invalidate the $LOADED_FEATURE cache
31
41
 
32
- require 'json' if ENV['RUBY_DEBUG_TEST_MODE']
42
+ require 'json' if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
43
+ require 'pp'
33
44
 
34
45
  class RubyVM::InstructionSequence
35
46
  def traceable_lines_norec lines
@@ -54,23 +65,22 @@ class RubyVM::InstructionSequence
54
65
 
55
66
  def type
56
67
  self.to_a[9]
57
- end
68
+ end unless method_defined?(:type)
58
69
 
59
- def argc
60
- self.to_a[4][:arg_size]
61
- end
62
-
63
- def locals
64
- self.to_a[10]
65
- end
70
+ def parameters_symbols
71
+ ary = self.to_a
72
+ argc = ary[4][:arg_size]
73
+ locals = ary.to_a[10]
74
+ locals[0...argc]
75
+ end unless method_defined?(:parameters_symbols)
66
76
 
67
77
  def last_line
68
78
  self.to_a[4][:code_location][2]
69
- end
79
+ end unless method_defined?(:last_line)
70
80
 
71
81
  def first_line
72
82
  self.to_a[4][:code_location][0]
73
- end
83
+ end unless method_defined?(:first_line)
74
84
  end
75
85
 
76
86
  module DEBUGGER__
@@ -80,8 +90,10 @@ module DEBUGGER__
80
90
  class Session
81
91
  attr_reader :intercepted_sigint_cmd, :process_group
82
92
 
83
- def initialize ui
84
- @ui = ui
93
+ include Color
94
+
95
+ def initialize
96
+ @ui = nil
85
97
  @sr = SourceRepository.new
86
98
  @bps = {} # bp.key => bp
87
99
  # [file, line] => LineBreakpoint
@@ -99,17 +111,17 @@ module DEBUGGER__
99
111
  @preset_command = nil
100
112
  @postmortem_hook = nil
101
113
  @postmortem = false
102
- @thread_stopper = nil
103
114
  @intercept_trap_sigint = false
104
115
  @intercepted_sigint_cmd = 'DEFAULT'
105
116
  @process_group = ProcessGroup.new
106
- @subsession = nil
117
+ @subsession_stack = []
107
118
 
108
- @frame_map = {} # {id => [threadId, frame_depth]} for DAP
119
+ @frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
109
120
  @var_map = {1 => [:globals], } # {id => ...} for DAP
110
121
  @src_map = {} # {id => src}
111
122
 
112
- @script_paths = [File.absolute_path($0)] # for CDP
123
+ @scr_id_map = {} # for CDP
124
+ @obj_map = {} # { object_id => ... } for CDP
113
125
 
114
126
  @tp_thread_begin = nil
115
127
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
@@ -117,8 +129,7 @@ module DEBUGGER__
117
129
  }
118
130
  @tp_load_script.enable
119
131
 
120
- activate
121
-
132
+ @thread_stopper = thread_stopper
122
133
  self.postmortem = CONFIG[:postmortem]
123
134
  end
124
135
 
@@ -130,15 +141,13 @@ module DEBUGGER__
130
141
  @bps.has_key? [file, line]
131
142
  end
132
143
 
133
- def activate on_fork: false
144
+ def activate ui = nil, on_fork: false
145
+ @ui = ui if ui
146
+
134
147
  @tp_thread_begin&.disable
135
148
  @tp_thread_begin = nil
136
149
 
137
- if on_fork
138
- @ui.activate self, on_fork: true
139
- else
140
- @ui.activate self, on_fork: false
141
- end
150
+ @ui.activate self, on_fork: on_fork
142
151
 
143
152
  q = Queue.new
144
153
  @session_server = Thread.new do
@@ -147,15 +156,15 @@ module DEBUGGER__
147
156
 
148
157
  # Thread management
149
158
  setup_threads
150
- thc = thread_client Thread.current
151
- thc.is_management
159
+ thc = get_thread_client Thread.current
160
+ thc.mark_as_management
152
161
 
153
- if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
154
- thc.is_management
162
+ if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
163
+ thc.mark_as_management
155
164
  end
156
165
 
157
166
  @tp_thread_begin = TracePoint.new(:thread_begin) do |tp|
158
- thread_client
167
+ get_thread_client
159
168
  end
160
169
  @tp_thread_begin.enable
161
170
 
@@ -168,12 +177,12 @@ module DEBUGGER__
168
177
  end
169
178
 
170
179
  def deactivate
171
- thread_client.deactivate
172
- @thread_stopper.disable if @thread_stopper
180
+ get_thread_client.deactivate
181
+ @thread_stopper.disable
173
182
  @tp_load_script.disable
174
183
  @tp_thread_begin.disable
175
- @bps.each{|k, bp| bp.disable}
176
- @th_clients.each{|th, thc| thc.close}
184
+ @bps.each_value{|bp| bp.disable}
185
+ @th_clients.each_value{|thc| thc.close}
177
186
  @tracers.values.each{|t| t.disable}
178
187
  @q_evt.close
179
188
  @ui&.deactivate
@@ -197,9 +206,14 @@ module DEBUGGER__
197
206
  deactivate
198
207
  end
199
208
 
209
+ def request_tc(req)
210
+ @tc << req
211
+ end
212
+
200
213
  def process_event evt
201
214
  # variable `@internal_info` is only used for test
202
- tc, output, ev, @internal_info, *ev_args = evt
215
+ @tc, output, ev, @internal_info, *ev_args = evt
216
+
203
217
  output.each{|str| @ui.puts str} if ev != :suspend
204
218
 
205
219
  case ev
@@ -211,20 +225,19 @@ module DEBUGGER__
211
225
  q << true
212
226
 
213
227
  when :init
214
- wait_command_loop tc
215
-
228
+ enter_subsession
229
+ wait_command_loop
216
230
  when :load
217
231
  iseq, src = ev_args
218
232
  on_load iseq, src
219
- @ui.event :load
220
- tc << :continue
233
+ request_tc :continue
221
234
 
222
235
  when :trace
223
236
  trace_id, msg = ev_args
224
237
  if t = @tracers.values.find{|t| t.object_id == trace_id}
225
238
  t.puts msg
226
239
  end
227
- tc << :continue
240
+ request_tc :continue
228
241
 
229
242
  when :suspend
230
243
  enter_subsession if ev_args.first != :replay
@@ -233,26 +246,26 @@ module DEBUGGER__
233
246
  case ev_args.first
234
247
  when :breakpoint
235
248
  bp, i = bp_index ev_args[1]
236
- @ui.event :suspend_bp, i, bp, tc.id
249
+ clean_bps unless bp
250
+ @ui.event :suspend_bp, i, bp, @tc.id
237
251
  when :trap
238
- @ui.event :suspend_trap, sig = ev_args[1], tc.id
252
+ @ui.event :suspend_trap, sig = ev_args[1], @tc.id
239
253
 
240
254
  if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
241
- @ui.puts "#{@intercepted_sigint_cmd.inspect} is registerred as SIGINT handler."
255
+ @ui.puts "#{@intercepted_sigint_cmd.inspect} is registered as SIGINT handler."
242
256
  @ui.puts "`sigint` command execute it."
243
257
  end
244
258
  else
245
- @ui.event :suspended, tc.id
259
+ @ui.event :suspended, @tc.id
246
260
  end
247
261
 
248
262
  if @displays.empty?
249
- wait_command_loop tc
263
+ wait_command_loop
250
264
  else
251
- tc << [:eval, :display, @displays]
265
+ request_tc [:eval, :display, @displays]
252
266
  end
253
-
254
267
  when :result
255
- raise "[BUG] not in subsession" unless @subsession
268
+ raise "[BUG] not in subsession" if @subsession_stack.empty?
256
269
 
257
270
  case ev_args.first
258
271
  when :try_display
@@ -281,14 +294,14 @@ module DEBUGGER__
281
294
  # ignore
282
295
  end
283
296
 
284
- wait_command_loop tc
297
+ wait_command_loop
285
298
 
286
299
  when :dap_result
287
300
  dap_event ev_args # server.rb
288
- wait_command_loop tc
301
+ wait_command_loop
289
302
  when :cdp_result
290
303
  cdp_event ev_args
291
- wait_command_loop tc
304
+ wait_command_loop
292
305
  end
293
306
  end
294
307
 
@@ -321,9 +334,7 @@ module DEBUGGER__
321
334
  "DEBUGGER__::SESSION"
322
335
  end
323
336
 
324
- def wait_command_loop tc
325
- @tc = tc
326
-
337
+ def wait_command_loop
327
338
  loop do
328
339
  case wait_command
329
340
  when :retry
@@ -364,7 +375,7 @@ module DEBUGGER__
364
375
  @ui.puts "(rdbg:#{@preset_command.source}) #{line}"
365
376
  end
366
377
  else
367
- @ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_MODE']
378
+ @ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
368
379
  line = @ui.readline prompt
369
380
  end
370
381
 
@@ -418,10 +429,15 @@ module DEBUGGER__
418
429
  # * `fin[ish]`
419
430
  # * Finish this frame. Resume the program until the current frame is finished.
420
431
  # * `fin[ish] <n>`
421
- # * Finish frames, same as `step <n>`.
432
+ # * Finish `<n>`th frames.
422
433
  when 'fin', 'finish'
423
434
  cancel_auto_continue
424
435
  check_postmortem
436
+
437
+ if arg&.to_i == 0
438
+ raise 'finish command with 0 does not make sense.'
439
+ end
440
+
425
441
  step_command :finish, arg
426
442
 
427
443
  # * `c[ontinue]`
@@ -447,7 +463,7 @@ module DEBUGGER__
447
463
  leave_subsession nil
448
464
 
449
465
  # * `kill`
450
- # * Stop the debuggee process with `Kernal#exit!`.
466
+ # * Stop the debuggee process with `Kernel#exit!`.
451
467
  when 'kill'
452
468
  if ask 'Really kill?'
453
469
  exit! (arg || 1).to_i
@@ -461,7 +477,7 @@ module DEBUGGER__
461
477
  exit! (arg || 1).to_i
462
478
 
463
479
  # * `sigint`
464
- # * Execute SIGINT handler registerred by the debuggee.
480
+ # * Execute SIGINT handler registered by the debuggee.
465
481
  # * Note that this command should be used just after stop by `SIGINT`.
466
482
  when 'sigint'
467
483
  begin
@@ -500,6 +516,8 @@ module DEBUGGER__
500
516
  # * break and run `<command>` before stopping.
501
517
  # * `b[reak] ... do: <command>`
502
518
  # * break and run `<command>`, and continue.
519
+ # * `b[reak] ... path: <path>`
520
+ # * break if the path matches to `<path>`. `<path>` can be a regexp with `/regexp/`.
503
521
  # * `b[reak] if: <expr>`
504
522
  # * break if: `<expr>` is true at any lines.
505
523
  # * Note that this feature is super slow.
@@ -520,34 +538,16 @@ module DEBUGGER__
520
538
  end
521
539
  end
522
540
 
523
- # skip
524
- when 'bv'
525
- check_postmortem
526
- require 'json'
527
-
528
- h = Hash.new{|h, k| h[k] = []}
529
- @bps.each{|key, bp|
530
- if LineBreakpoint === bp
531
- h[bp.path] << {lnum: bp.line}
532
- end
533
- }
534
- if h.empty?
535
- # TODO: clean?
536
- else
537
- open(".rdb_breakpoints.json", 'w'){|f| JSON.dump(h, f)}
538
- end
539
-
540
- vimsrc = File.join(__dir__, 'bp.vim')
541
- system("vim -R -S #{vimsrc} #{@tc.location.path}")
542
-
543
- if File.exist?(".rdb_breakpoints.json")
544
- pp JSON.load(File.read(".rdb_breakpoints.json"))
545
- end
546
-
547
- return :retry
548
-
549
541
  # * `catch <Error>`
550
542
  # * Set breakpoint on raising `<Error>`.
543
+ # * `catch ... if: <expr>`
544
+ # * stops only if `<expr>` is true as well.
545
+ # * `catch ... pre: <command>`
546
+ # * runs `<command>` before stopping.
547
+ # * `catch ... do: <command>`
548
+ # * stops and run `<command>`, and continue.
549
+ # * `catch ... path: <path>`
550
+ # * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
551
551
  when 'catch'
552
552
  check_postmortem
553
553
 
@@ -562,11 +562,19 @@ module DEBUGGER__
562
562
  # * `watch @ivar`
563
563
  # * Stop the execution when the result of current scope's `@ivar` is changed.
564
564
  # * Note that this feature is super slow.
565
+ # * `watch ... if: <expr>`
566
+ # * stops only if `<expr>` is true as well.
567
+ # * `watch ... pre: <command>`
568
+ # * runs `<command>` before stopping.
569
+ # * `watch ... do: <command>`
570
+ # * stops and run `<command>`, and continue.
571
+ # * `watch ... path: <path>`
572
+ # * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
565
573
  when 'wat', 'watch'
566
574
  check_postmortem
567
575
 
568
576
  if arg && arg.match?(/\A@\w+/)
569
- @tc << [:breakpoint, :watch, arg]
577
+ repl_add_watch_breakpoint(arg)
570
578
  else
571
579
  show_bps
572
580
  return :retry
@@ -607,15 +615,15 @@ module DEBUGGER__
607
615
  when 'bt', 'backtrace'
608
616
  case arg
609
617
  when /\A(\d+)\z/
610
- @tc << [:show, :backtrace, arg.to_i, nil]
618
+ request_tc [:show, :backtrace, arg.to_i, nil]
611
619
  when /\A\/(.*)\/\z/
612
620
  pattern = $1
613
- @tc << [:show, :backtrace, nil, Regexp.compile(pattern)]
621
+ request_tc [:show, :backtrace, nil, Regexp.compile(pattern)]
614
622
  when /\A(\d+)\s+\/(.*)\/\z/
615
623
  max, pattern = $1, $2
616
- @tc << [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
624
+ request_tc [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
617
625
  else
618
- @tc << [:show, :backtrace, nil, nil]
626
+ request_tc [:show, :backtrace, nil, nil]
619
627
  end
620
628
 
621
629
  # * `l[ist]`
@@ -628,13 +636,13 @@ module DEBUGGER__
628
636
  when 'l', 'list'
629
637
  case arg ? arg.strip : nil
630
638
  when /\A(\d+)\z/
631
- @tc << [:show, :list, {start_line: arg.to_i - 1}]
639
+ request_tc [:show, :list, {start_line: arg.to_i - 1}]
632
640
  when /\A-\z/
633
- @tc << [:show, :list, {dir: -1}]
641
+ request_tc [:show, :list, {dir: -1}]
634
642
  when /\A(\d+)-(\d+)\z/
635
- @tc << [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
643
+ request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
636
644
  when nil
637
- @tc << [:show, :list]
645
+ request_tc [:show, :list]
638
646
  else
639
647
  @ui.puts "Can not handle list argument: #{arg}"
640
648
  return :retry
@@ -658,7 +666,7 @@ module DEBUGGER__
658
666
  return :retry
659
667
  end
660
668
 
661
- @tc << [:show, :edit, arg]
669
+ request_tc [:show, :edit, arg]
662
670
 
663
671
  # * `i[nfo]`
664
672
  # * Show information about current frame (local/instance variables and defined constants).
@@ -671,8 +679,8 @@ module DEBUGGER__
671
679
  # * Show information about accessible constants except toplevel constants.
672
680
  # * `i[nfo] g[lobal[s]]`
673
681
  # * Show information about global variables
674
- # * `i[nfo] ... </pattern/>`
675
- # * Filter the output with `</pattern/>`.
682
+ # * `i[nfo] ... /regexp/`
683
+ # * Filter the output with `/regexp/`.
676
684
  # * `i[nfo] th[read[s]]`
677
685
  # * Show all threads (same as `th[read]`).
678
686
  when 'i', 'info'
@@ -685,15 +693,15 @@ module DEBUGGER__
685
693
 
686
694
  case sub
687
695
  when nil
688
- @tc << [:show, :default, pat] # something useful
696
+ request_tc [:show, :default, pat] # something useful
689
697
  when 'l', /^locals?/
690
- @tc << [:show, :locals, pat]
698
+ request_tc [:show, :locals, pat]
691
699
  when 'i', /^ivars?/i, /^instance[_ ]variables?/i
692
- @tc << [:show, :ivars, pat]
700
+ request_tc [:show, :ivars, pat]
693
701
  when 'c', /^consts?/i, /^constants?/i
694
- @tc << [:show, :consts, pat]
702
+ request_tc [:show, :consts, pat]
695
703
  when 'g', /^globals?/i, /^global[_ ]variables?/i
696
- @tc << [:show, :globals, pat]
704
+ request_tc [:show, :globals, pat]
697
705
  when 'th', /threads?/
698
706
  thread_list
699
707
  return :retry
@@ -709,7 +717,7 @@ module DEBUGGER__
709
717
  # * Show you available methods and instance variables of the given object.
710
718
  # * If the object is a class/module, it also lists its constants.
711
719
  when 'outline', 'o', 'ls'
712
- @tc << [:show, :outline, arg]
720
+ request_tc [:show, :outline, arg]
713
721
 
714
722
  # * `display`
715
723
  # * Show display setting.
@@ -718,9 +726,9 @@ module DEBUGGER__
718
726
  when 'display'
719
727
  if arg && !arg.empty?
720
728
  @displays << arg
721
- @tc << [:eval, :try_display, @displays]
729
+ request_tc [:eval, :try_display, @displays]
722
730
  else
723
- @tc << [:eval, :display, @displays]
731
+ request_tc [:eval, :display, @displays]
724
732
  end
725
733
 
726
734
  # * `undisplay`
@@ -733,7 +741,7 @@ module DEBUGGER__
733
741
  if @displays[n = $1.to_i]
734
742
  @displays.delete_at n
735
743
  end
736
- @tc << [:eval, :display, @displays]
744
+ request_tc [:eval, :display, @displays]
737
745
  when nil
738
746
  if ask "clear all?", 'N'
739
747
  @displays.clear
@@ -748,29 +756,29 @@ module DEBUGGER__
748
756
  # * `f[rame] <framenum>`
749
757
  # * Specify a current frame. Evaluation are run on specified frame.
750
758
  when 'frame', 'f'
751
- @tc << [:frame, :set, arg]
759
+ request_tc [:frame, :set, arg]
752
760
 
753
761
  # * `up`
754
762
  # * Specify the upper frame.
755
763
  when 'up'
756
- @tc << [:frame, :up]
764
+ request_tc [:frame, :up]
757
765
 
758
766
  # * `down`
759
767
  # * Specify the lower frame.
760
768
  when 'down'
761
- @tc << [:frame, :down]
769
+ request_tc [:frame, :down]
762
770
 
763
771
  ### Evaluate
764
772
 
765
773
  # * `p <expr>`
766
774
  # * Evaluate like `p <expr>` on the current frame.
767
775
  when 'p'
768
- @tc << [:eval, :p, arg.to_s]
776
+ request_tc [:eval, :p, arg.to_s]
769
777
 
770
778
  # * `pp <expr>`
771
779
  # * Evaluate like `pp <expr>` on the current frame.
772
780
  when 'pp'
773
- @tc << [:eval, :pp, arg.to_s]
781
+ request_tc [:eval, :pp, arg.to_s]
774
782
 
775
783
  # * `eval <expr>`
776
784
  # * Evaluate `<expr>` on the current frame.
@@ -780,7 +788,7 @@ module DEBUGGER__
780
788
  @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
781
789
  return :retry
782
790
  else
783
- @tc << [:eval, :call, arg]
791
+ request_tc [:eval, :call, arg]
784
792
  end
785
793
 
786
794
  # * `irb`
@@ -790,7 +798,7 @@ module DEBUGGER__
790
798
  @ui.puts "not supported on the remote console."
791
799
  return :retry
792
800
  end
793
- @tc << [:eval, :irb]
801
+ request_tc [:eval, :irb]
794
802
 
795
803
  # don't repeat irb command
796
804
  @repl_prev_line = nil
@@ -806,8 +814,8 @@ module DEBUGGER__
806
814
  # * Add an exception tracer. It indicates raising exceptions.
807
815
  # * `trace object <expr>`
808
816
  # * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
809
- # * `trace ... </pattern/>`
810
- # * Indicates only matched events to `</pattern/>` (RegExp).
817
+ # * `trace ... /regexp/`
818
+ # * Indicates only matched events to `/regexp/`.
811
819
  # * `trace ... into: <file>`
812
820
  # * Save trace information into: `<file>`.
813
821
  # * `trace off <num>`
@@ -847,7 +855,7 @@ module DEBUGGER__
847
855
  return :retry
848
856
 
849
857
  when /\Aobject\s+(.+)/
850
- @tc << [:trace, :object, $1.strip, {pattern: pattern, into: into}]
858
+ request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}]
851
859
 
852
860
  when /\Aoff\s+(\d+)\z/
853
861
  if t = @tracers.values[$1.to_i]
@@ -885,7 +893,7 @@ module DEBUGGER__
885
893
  when 'record'
886
894
  case arg
887
895
  when nil, 'on', 'off'
888
- @tc << [:record, arg&.to_sym]
896
+ request_tc [:record, arg&.to_sym]
889
897
  else
890
898
  @ui.puts "unknown command: #{arg}"
891
899
  return :retry
@@ -902,7 +910,7 @@ module DEBUGGER__
902
910
  when nil, 'list', 'l'
903
911
  thread_list
904
912
  when /(\d+)/
905
- thread_switch $1.to_i
913
+ switch_thread $1.to_i
906
914
  else
907
915
  @ui.puts "unknown thread command: #{arg}"
908
916
  end
@@ -950,7 +958,7 @@ module DEBUGGER__
950
958
  when 'open'
951
959
  case arg&.downcase
952
960
  when '', nil
953
- repl_open_unix
961
+ repl_open
954
962
  when 'vscode'
955
963
  repl_open_vscode
956
964
  when /\A(.+):(\d+)\z/
@@ -975,16 +983,12 @@ module DEBUGGER__
975
983
  # * `h[elp] <command>`
976
984
  # * Show help for the given command.
977
985
  when 'h', 'help', '?'
978
- if arg
979
- show_help arg
980
- else
981
- @ui.puts DEBUGGER__.help
982
- end
986
+ show_help arg
983
987
  return :retry
984
988
 
985
989
  ### END
986
990
  else
987
- @tc << [:eval, :pp, line]
991
+ request_tc [:eval, :pp, line]
988
992
  =begin
989
993
  @repl_prev_line = nil
990
994
  @ui.puts "unknown command: #{line}"
@@ -1016,8 +1020,8 @@ module DEBUGGER__
1016
1020
  def repl_open_setup
1017
1021
  @tp_thread_begin.disable
1018
1022
  @ui.activate self
1019
- if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
1020
- thc.is_management
1023
+ if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
1024
+ thc.mark_as_management
1021
1025
  end
1022
1026
  @tp_thread_begin.enable
1023
1027
  end
@@ -1027,21 +1031,21 @@ module DEBUGGER__
1027
1031
  repl_open_setup
1028
1032
  end
1029
1033
 
1030
- def repl_open_unix
1031
- DEBUGGER__.open_unix nonstop: true
1034
+ def repl_open
1035
+ DEBUGGER__.open nonstop: true
1032
1036
  repl_open_setup
1033
1037
  end
1034
1038
 
1035
1039
  def repl_open_vscode
1036
1040
  CONFIG[:open_frontend] = 'vscode'
1037
- repl_open_unix
1041
+ repl_open
1038
1042
  end
1039
1043
 
1040
1044
  def step_command type, arg
1041
1045
  case arg
1042
1046
  when nil, /\A\d+\z/
1043
1047
  if type == :in && @tc.recorder&.replaying?
1044
- @tc << [:step, type, arg&.to_i]
1048
+ request_tc [:step, type, arg&.to_i]
1045
1049
  else
1046
1050
  leave_subsession [:step, type, arg&.to_i]
1047
1051
  end
@@ -1050,7 +1054,7 @@ module DEBUGGER__
1050
1054
  @ui.puts "only `step #{arg}` is supported."
1051
1055
  :retry
1052
1056
  else
1053
- @tc << [:step, arg.to_sym]
1057
+ request_tc [:step, arg.to_sym]
1054
1058
  end
1055
1059
  else
1056
1060
  @ui.puts "Unknown option: #{arg}"
@@ -1060,11 +1064,18 @@ module DEBUGGER__
1060
1064
 
1061
1065
  def config_show key
1062
1066
  key = key.to_sym
1063
- if CONFIG_SET[key]
1067
+ config_detail = CONFIG_SET[key]
1068
+
1069
+ if config_detail
1064
1070
  v = CONFIG[key]
1065
- kv = "#{key} = #{v.nil? ? '(default)' : v.inspect}"
1066
- desc = CONFIG_SET[key][1]
1067
- line = "%-30s \# %s" % [kv, desc]
1071
+ kv = "#{key} = #{v.inspect}"
1072
+ desc = config_detail[1]
1073
+
1074
+ if config_default = config_detail[3]
1075
+ desc += " (default: #{config_default})"
1076
+ end
1077
+
1078
+ line = "%-34s \# %s" % [kv, desc]
1068
1079
  if line.size > SESSION.width
1069
1080
  @ui.puts "\# #{desc}\n#{kv}"
1070
1081
  else
@@ -1114,7 +1125,7 @@ module DEBUGGER__
1114
1125
  config_set $1, $2, append: true
1115
1126
 
1116
1127
  when /\A\s*append\s+(\w+)\s+(.+)\z/
1117
- config_set $1, $2
1128
+ config_set $1, $2, append: true
1118
1129
 
1119
1130
  when /\A(\w+)\z/
1120
1131
  config_show $1
@@ -1131,16 +1142,50 @@ module DEBUGGER__
1131
1142
  end
1132
1143
  end
1133
1144
 
1134
- def show_help arg
1135
- DEBUGGER__.helps.each{|cat, cs|
1136
- cs.each{|ws, desc|
1137
- if ws.include? arg
1138
- @ui.puts desc
1139
- return
1145
+ def show_help arg = nil
1146
+ instructions = (DEBUGGER__.commands.keys + DEBUGGER__.commands.values).uniq
1147
+ print_instructions = proc do |desc|
1148
+ desc.split("\n").each do |line|
1149
+ next if line.start_with?(" ") # workaround for step back
1150
+ formatted_line = line.gsub(/[\[\]\*]/, "").strip
1151
+ instructions.each do |inst|
1152
+ if formatted_line.start_with?("`#{inst}")
1153
+ desc.sub!(line, colorize(line, [:CYAN, :BOLD]))
1154
+ end
1155
+ end
1156
+ end
1157
+ @ui.puts desc
1158
+ end
1159
+
1160
+ print_category = proc do |cat|
1161
+ @ui.puts "\n"
1162
+ @ui.puts colorize("### #{cat}", [:GREEN, :BOLD])
1163
+ @ui.puts "\n"
1164
+ end
1165
+
1166
+ DEBUGGER__.helps.each { |cat, cs|
1167
+ # categories
1168
+ if arg.nil?
1169
+ print_category.call(cat)
1170
+ else
1171
+ cs.each { |ws, _|
1172
+ if ws.include?(arg)
1173
+ print_category.call(cat)
1174
+ break
1175
+ end
1176
+ }
1177
+ end
1178
+
1179
+ # instructions
1180
+ cs.each { |ws, desc|
1181
+ if arg.nil? || ws.include?(arg)
1182
+ print_instructions.call(desc.dup)
1183
+ return if arg
1140
1184
  end
1141
1185
  }
1142
1186
  }
1143
- @ui.puts "not found: #{arg}"
1187
+
1188
+ @ui.puts "not found: #{arg}" if arg
1144
1189
  end
1145
1190
 
1146
1191
  def ask msg, default = 'Y'
@@ -1195,14 +1240,23 @@ module DEBUGGER__
1195
1240
  }
1196
1241
  end
1197
1242
 
1243
+ def clean_bps
1244
+ @bps.delete_if{|_k, bp|
1245
+ bp.deleted?
1246
+ }
1247
+ end
1248
+
1198
1249
  def add_bp bp
1199
1250
  # don't repeat commands that add breakpoints
1200
1251
  @repl_prev_line = nil
1201
1252
 
1202
1253
  if @bps.has_key? bp.key
1203
- unless bp.duplicable?
1254
+ if bp.duplicable?
1255
+ bp
1256
+ else
1204
1257
  @ui.puts "duplicated breakpoint: #{bp}"
1205
1258
  bp.disable
1259
+ nil
1206
1260
  end
1207
1261
  else
1208
1262
  @bps[bp.key] = bp
@@ -1225,7 +1279,7 @@ module DEBUGGER__
1225
1279
  end
1226
1280
  end
1227
1281
 
1228
- BREAK_KEYWORDS = %w(if: do: pre:).freeze
1282
+ BREAK_KEYWORDS = %w(if: do: pre: path:).freeze
1229
1283
 
1230
1284
  def parse_break arg
1231
1285
  mode = :sig
@@ -1238,13 +1292,20 @@ module DEBUGGER__
1238
1292
  end
1239
1293
  }
1240
1294
  expr.default_proc = nil
1241
- expr.transform_values{|v| v.join(' ')}
1295
+ expr = expr.transform_values{|v| v.join(' ')}
1296
+
1297
+ if (path = expr[:path]) && path =~ /\A\/(.*)\/\z/
1298
+ expr[:path] = Regexp.compile($1)
1299
+ end
1300
+
1301
+ expr
1242
1302
  end
1243
1303
 
1244
1304
  def repl_add_breakpoint arg
1245
1305
  expr = parse_break arg.strip
1246
1306
  cond = expr[:if]
1247
1307
  cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1308
+ path = expr[:path]
1248
1309
 
1249
1310
  case expr[:sig]
1250
1311
  when /\A(\d+)\z/
@@ -1252,10 +1313,10 @@ module DEBUGGER__
1252
1313
  when /\A(.+)[:\s+](\d+)\z/
1253
1314
  add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
1254
1315
  when /\A(.+)([\.\#])(.+)\z/
1255
- @tc << [:breakpoint, :method, $1, $2, $3, cond, cmd]
1316
+ request_tc [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
1256
1317
  return :noretry
1257
1318
  when nil
1258
- add_check_breakpoint cond
1319
+ add_check_breakpoint cond, path, cmd
1259
1320
  else
1260
1321
  @ui.puts "Unknown breakpoint format: #{arg}"
1261
1322
  @ui.puts
@@ -1267,18 +1328,28 @@ module DEBUGGER__
1267
1328
  expr = parse_break arg.strip
1268
1329
  cond = expr[:if]
1269
1330
  cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1331
+ path = expr[:path]
1270
1332
 
1271
- bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd)
1333
+ bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
1272
1334
  add_bp bp
1273
1335
  end
1274
1336
 
1275
- def add_catch_breakpoint pat
1276
- bp = CatchBreakpoint.new(pat)
1337
+ def repl_add_watch_breakpoint arg
1338
+ expr = parse_break arg.strip
1339
+ cond = expr[:if]
1340
+ cmd = ['watch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1341
+ path = Regexp.compile(expr[:path]) if expr[:path]
1342
+
1343
+ request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
1344
+ end
1345
+
1346
+ def add_catch_breakpoint pat, cond: nil
1347
+ bp = CatchBreakpoint.new(pat, cond: cond)
1277
1348
  add_bp bp
1278
1349
  end
1279
1350
 
1280
- def add_check_breakpoint expr
1281
- bp = CheckBreakpoint.new(expr)
1351
+ def add_check_breakpoint cond, path, command
1352
+ bp = CheckBreakpoint.new(cond: cond, path: path, command: command)
1282
1353
  add_bp bp
1283
1354
  end
1284
1355
 
@@ -1291,6 +1362,39 @@ module DEBUGGER__
1291
1362
  @ui.puts e.message
1292
1363
  end
1293
1364
 
1365
+ def clear_breakpoints(&condition)
1366
+ @bps.delete_if do |k, bp|
1367
+ if condition.call(k, bp)
1368
+ bp.delete
1369
+ true
1370
+ end
1371
+ end
1372
+ end
1373
+
1374
+ def clear_line_breakpoints path
1375
+ path = resolve_path(path)
1376
+ clear_breakpoints do |k, bp|
1377
+ bp.is_a?(LineBreakpoint) && DEBUGGER__.compare_path(k.first, path)
1378
+ end
1379
+ rescue Errno::ENOENT
1380
+ # just ignore
1381
+ end
1382
+
1383
+ def clear_catch_breakpoints *exception_names
1384
+ clear_breakpoints do |k, bp|
1385
+ bp.is_a?(CatchBreakpoint) && exception_names.include?(k[1])
1386
+ end
1387
+ end
1388
+
1389
+ def clear_all_breakpoints
1390
+ clear_breakpoints{true}
1391
+ end
1392
+
1393
+ def add_iseq_breakpoint iseq, **kw
1394
+ bp = ISeqBreakpoint.new(iseq, [:line], **kw)
1395
+ add_bp bp
1396
+ end
1397
+
1294
1398
  # tracers
1295
1399
 
1296
1400
  def add_tracer tracer
@@ -1344,7 +1448,7 @@ module DEBUGGER__
1344
1448
  thcs
1345
1449
  end
1346
1450
 
1347
- def thread_switch n
1451
+ def switch_thread n
1348
1452
  thcs, _unmanaged_ths = update_thread_list
1349
1453
 
1350
1454
  if tc = thcs[n]
@@ -1365,7 +1469,7 @@ module DEBUGGER__
1365
1469
  if tc = prev_clients[th]
1366
1470
  @th_clients[th] = tc
1367
1471
  else
1368
- thread_client_create(th)
1472
+ create_thread_client(th)
1369
1473
  end
1370
1474
  }
1371
1475
  end
@@ -1374,17 +1478,17 @@ module DEBUGGER__
1374
1478
  if @th_clients.has_key? th
1375
1479
  # TODO: NG?
1376
1480
  else
1377
- thread_client_create th
1481
+ create_thread_client th
1378
1482
  end
1379
1483
  end
1380
1484
 
1381
- private def thread_client_create th
1485
+ private def create_thread_client th
1382
1486
  # TODO: Ractor support
1383
1487
  raise "Only session_server can create thread_client" unless Thread.current == @session_server
1384
1488
  @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
1385
1489
  end
1386
1490
 
1387
- private def ask_thread_client th = Thread.current
1491
+ private def ask_thread_client th
1388
1492
  # TODO: Ractor support
1389
1493
  q2 = Queue.new
1390
1494
  # tc, output, ev, @internal_info, *ev_args = evt
@@ -1395,30 +1499,18 @@ module DEBUGGER__
1395
1499
  end
1396
1500
 
1397
1501
  # can be called by other threads
1398
- def thread_client th = Thread.current
1502
+ def get_thread_client th = Thread.current
1399
1503
  if @th_clients.has_key? th
1400
1504
  @th_clients[th]
1401
1505
  else
1402
1506
  if Thread.current == @session_server
1403
- thread_client_create th
1507
+ create_thread_client th
1404
1508
  else
1405
1509
  ask_thread_client th
1406
1510
  end
1407
1511
  end
1408
1512
  end
1409
1513
 
1410
- private def thread_stopper
1411
- @thread_stopper ||= TracePoint.new(:line) do
1412
- # run on each thread
1413
- tc = ThreadClient.current
1414
- next if tc.management?
1415
- next unless tc.running?
1416
- next if tc == @tc
1417
-
1418
- tc.on_pause
1419
- end
1420
- end
1421
-
1422
1514
  private def running_thread_clients_count
1423
1515
  @th_clients.count{|th, tc|
1424
1516
  next if tc.management?
@@ -1435,15 +1527,27 @@ module DEBUGGER__
1435
1527
  }.compact
1436
1528
  end
1437
1529
 
1530
+ private def thread_stopper
1531
+ TracePoint.new(:line) do
1532
+ # run on each thread
1533
+ tc = ThreadClient.current
1534
+ next if tc.management?
1535
+ next unless tc.running?
1536
+ next if tc == @tc
1537
+
1538
+ tc.on_pause
1539
+ end
1540
+ end
1541
+
1438
1542
  private def stop_all_threads
1439
1543
  return if running_thread_clients_count == 0
1440
1544
 
1441
- stopper = thread_stopper
1545
+ stopper = @thread_stopper
1442
1546
  stopper.enable unless stopper.enabled?
1443
1547
  end
1444
1548
 
1445
1549
  private def restart_all_threads
1446
- stopper = thread_stopper
1550
+ stopper = @thread_stopper
1447
1551
  stopper.disable if stopper.enabled?
1448
1552
 
1449
1553
  waiting_thread_clients.each{|tc|
@@ -1453,42 +1557,66 @@ module DEBUGGER__
1453
1557
  end
1454
1558
 
1455
1559
  private def enter_subsession
1456
- raise "already in subsession" if @subsession
1457
- @subsession = true
1458
- stop_all_threads
1459
- @process_group.lock
1460
- DEBUGGER__.info "enter_subsession"
1560
+ if !@subsession_stack.empty?
1561
+ DEBUGGER__.info "Enter subsession (nested #{@subsession_stack.size})"
1562
+ else
1563
+ DEBUGGER__.info "Enter subsession"
1564
+ stop_all_threads
1565
+ @process_group.lock
1566
+ end
1567
+
1568
+ @subsession_stack << true
1461
1569
  end
1462
1570
 
1463
1571
  private def leave_subsession type
1464
- DEBUGGER__.info "leave_subsession"
1465
- @process_group.unlock
1466
- restart_all_threads
1467
- @tc << type if type
1572
+ raise '[BUG] leave_subsession: not entered' if @subsession_stack.empty?
1573
+ @subsession_stack.pop
1574
+
1575
+ if @subsession_stack.empty?
1576
+ DEBUGGER__.info "Leave subsession"
1577
+ @process_group.unlock
1578
+ restart_all_threads
1579
+ else
1580
+ DEBUGGER__.info "Leave subsession (nested #{@subsession_stack.size})"
1581
+ end
1582
+
1583
+ request_tc type if type
1468
1584
  @tc = nil
1469
- @subsession = false
1470
1585
  rescue Exception => e
1471
- STDERR.puts [e, e.backtrace].inspect
1586
+ STDERR.puts PP.pp([e, e.backtrace], ''.dup)
1472
1587
  raise
1473
1588
  end
1474
1589
 
1475
1590
  def in_subsession?
1476
- @subsession
1591
+ !@subsession_stack.empty?
1477
1592
  end
1478
1593
 
1479
1594
  ## event
1480
1595
 
1481
1596
  def on_load iseq, src
1482
1597
  DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
1483
- @sr.add iseq, src
1598
+
1599
+ file_path, reloaded = @sr.add(iseq, src)
1600
+ @ui.event :load, file_path, reloaded
1484
1601
 
1485
1602
  pending_line_breakpoints = @bps.find_all do |key, bp|
1486
1603
  LineBreakpoint === bp && !bp.iseq
1487
1604
  end
1488
1605
 
1489
1606
  pending_line_breakpoints.each do |_key, bp|
1490
- if bp.path == (iseq.absolute_path || iseq.path)
1491
- bp.try_activate
1607
+ if DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
1608
+ bp.try_activate iseq
1609
+ end
1610
+ end
1611
+
1612
+ if reloaded
1613
+ @bps.find_all do |key, bp|
1614
+ LineBreakpoint === bp && DEBUGGER__.compare_path(bp.path, file_path)
1615
+ end.each do |_key, bp|
1616
+ @bps.delete bp.key # to allow duplicate
1617
+ if nbp = LineBreakpoint.copy(bp, iseq)
1618
+ add_bp nbp
1619
+ end
1492
1620
  end
1493
1621
  end
1494
1622
  end
@@ -1515,7 +1643,7 @@ module DEBUGGER__
1515
1643
  b = tp.binding
1516
1644
  if var_name = b.local_variables.first
1517
1645
  mid = b.local_variable_get(var_name)
1518
- unresolved = false
1646
+ resolved = true
1519
1647
 
1520
1648
  @bps.each{|k, bp|
1521
1649
  case bp
@@ -1526,15 +1654,53 @@ module DEBUGGER__
1526
1654
  end
1527
1655
  end
1528
1656
 
1529
- unresolved = true unless bp.enabled?
1657
+ resolved = false if !bp.enabled?
1530
1658
  end
1531
1659
  }
1532
- unless unresolved
1533
- METHOD_ADDED_TRACKER.disable
1660
+
1661
+ if resolved
1662
+ Session.deactivate_method_added_trackers
1663
+ end
1664
+
1665
+ case mid
1666
+ when :method_added, :singleton_method_added
1667
+ Session.create_method_added_tracker(tp.self, mid)
1668
+ Session.create_method_added_tracker unless resolved
1534
1669
  end
1535
1670
  end
1536
1671
  end
1537
1672
 
1673
+ class ::Module
1674
+ undef method_added
1675
+ def method_added mid; end
1676
+ def singleton_method_added mid; end
1677
+ end
1678
+
1679
+ def self.create_method_added_tracker mod, method_added_id, method_accessor = :method
1680
+ m = mod.__send__(method_accessor, method_added_id)
1681
+ METHOD_ADDED_TRACKERS[m] = TracePoint.new(:call) do |tp|
1682
+ SESSION.method_added tp
1683
+ end
1684
+ end
1685
+
1686
+ def self.activate_method_added_trackers
1687
+ METHOD_ADDED_TRACKERS.each do |m, tp|
1688
+ tp.enable(target: m) unless tp.enabled?
1689
+ rescue ArgumentError
1690
+ DEBUGGER__.warn "Methods defined under #{m.owner} can not track by the debugger."
1691
+ end
1692
+ end
1693
+
1694
+ def self.deactivate_method_added_trackers
1695
+ METHOD_ADDED_TRACKERS.each do |m, tp|
1696
+ tp.disable if tp.enabled?
1697
+ end
1698
+ end
1699
+
1700
+ METHOD_ADDED_TRACKERS = Hash.new
1701
+ create_method_added_tracker Module, :method_added, :instance_method
1702
+ create_method_added_tracker Module, :singleton_method_added, :instance_method
1703
+
1538
1704
  def width
1539
1705
  @ui.width
1540
1706
  end
@@ -1550,7 +1716,7 @@ module DEBUGGER__
1550
1716
 
1551
1717
  frames = exc.instance_variable_get(:@__debugger_postmortem_frames)
1552
1718
  @postmortem = true
1553
- ThreadClient.current.suspend :postmortem, postmortem_frames: frames
1719
+ ThreadClient.current.suspend :postmortem, postmortem_frames: frames, postmortem_exc: exc
1554
1720
  ensure
1555
1721
  @postmortem = false
1556
1722
  end
@@ -1810,6 +1976,9 @@ module DEBUGGER__
1810
1976
  puts "\nStop by #{args.first}"
1811
1977
  end
1812
1978
  end
1979
+
1980
+ def flush
1981
+ end
1813
1982
  end
1814
1983
 
1815
1984
  # manual configuration methods
@@ -1822,11 +1991,11 @@ module DEBUGGER__
1822
1991
  ::DEBUGGER__::SESSION.add_catch_breakpoint pat
1823
1992
  end
1824
1993
 
1825
- # String for requring location
1994
+ # String for requiring location
1826
1995
  # nil for -r
1827
1996
  def self.require_location
1828
1997
  locs = caller_locations
1829
- dir_prefix = /#{__dir__}/
1998
+ dir_prefix = /#{Regexp.escape(__dir__)}/
1830
1999
 
1831
2000
  locs.each do |loc|
1832
2001
  case loc.absolute_path
@@ -1846,7 +2015,7 @@ module DEBUGGER__
1846
2015
 
1847
2016
  unless defined? SESSION
1848
2017
  require_relative 'local'
1849
- initialize_session UI_LocalConsole.new
2018
+ initialize_session{ UI_LocalConsole.new }
1850
2019
  end
1851
2020
 
1852
2021
  setup_initial_suspend unless nonstop
@@ -1854,8 +2023,9 @@ module DEBUGGER__
1854
2023
 
1855
2024
  def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
1856
2025
  CONFIG.set_config(**kw)
2026
+ require_relative 'server'
1857
2027
 
1858
- if port || CONFIG[:open_frontend] == 'chrome'
2028
+ if port || CONFIG[:open_frontend] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
1859
2029
  open_tcp host: host, port: (port || 0), nonstop: nonstop
1860
2030
  else
1861
2031
  open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
@@ -1869,7 +2039,7 @@ module DEBUGGER__
1869
2039
  if defined? SESSION
1870
2040
  SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
1871
2041
  else
1872
- initialize_session UI_TcpServer.new(host: host, port: port)
2042
+ initialize_session{ UI_TcpServer.new(host: host, port: port) }
1873
2043
  end
1874
2044
 
1875
2045
  setup_initial_suspend unless nonstop
@@ -1882,7 +2052,7 @@ module DEBUGGER__
1882
2052
  if defined? SESSION
1883
2053
  SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
1884
2054
  else
1885
- initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
2055
+ initialize_session{ UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path) }
1886
2056
  end
1887
2057
 
1888
2058
  setup_initial_suspend unless nonstop
@@ -1909,9 +2079,10 @@ module DEBUGGER__
1909
2079
  end
1910
2080
 
1911
2081
  class << self
1912
- define_method :initialize_session do |ui|
2082
+ define_method :initialize_session do |&init_ui|
1913
2083
  DEBUGGER__.info "Session start (pid: #{Process.pid})"
1914
- ::DEBUGGER__.const_set(:SESSION, Session.new(ui))
2084
+ ::DEBUGGER__.const_set(:SESSION, Session.new)
2085
+ SESSION.activate init_ui.call
1915
2086
  load_rc
1916
2087
  end
1917
2088
  end
@@ -1943,30 +2114,53 @@ module DEBUGGER__
1943
2114
  end
1944
2115
  end
1945
2116
 
1946
- class ::Module
1947
- undef method_added
1948
- def method_added mid; end
1949
- def singleton_method_added mid; end
1950
- end
2117
+ # Inspector
2118
+
2119
+ SHORT_INSPECT_LENGTH = 40
1951
2120
 
1952
- def self.method_added tp
1953
- begin
1954
- SESSION.method_added tp
1955
- rescue Exception => e
1956
- p e
2121
+ class LimitedPP
2122
+ def self.pp(obj, max=80)
2123
+ out = self.new(max)
2124
+ catch out do
2125
+ PP.singleline_pp(obj, out)
2126
+ end
2127
+ out.buf
1957
2128
  end
1958
- end
1959
2129
 
1960
- METHOD_ADDED_TRACKER = self.create_method_added_tracker
2130
+ attr_reader :buf
1961
2131
 
1962
- SHORT_INSPECT_LENGTH = 40
1963
- def self.short_inspect obj, use_short = true
1964
- str = obj.inspect
1965
- if use_short && str.length > SHORT_INSPECT_LENGTH
1966
- str[0...SHORT_INSPECT_LENGTH] + '...'
2132
+ def initialize max
2133
+ @max = max
2134
+ @cnt = 0
2135
+ @buf = String.new
2136
+ end
2137
+
2138
+ def <<(other)
2139
+ @buf << other
2140
+
2141
+ if @buf.size >= @max
2142
+ @buf = @buf[0..@max] + '...'
2143
+ throw self
2144
+ end
2145
+ end
2146
+ end
2147
+
2148
+ def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
2149
+ if short
2150
+ LimitedPP.pp(obj, max_length)
1967
2151
  else
1968
- str
2152
+ obj.inspect
1969
2153
  end
2154
+ rescue NoMethodError => e
2155
+ klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
2156
+ if obj == (r = e.receiver)
2157
+ "<\##{klass.name}#{oid} does not have \#inspect>"
2158
+ else
2159
+ rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
2160
+ "<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
2161
+ end
2162
+ rescue Exception => e
2163
+ "<#inspect raises #{e.inspect}>"
1970
2164
  end
1971
2165
 
1972
2166
  def self.warn msg
@@ -1977,18 +2171,27 @@ module DEBUGGER__
1977
2171
  log :INFO, msg
1978
2172
  end
1979
2173
 
1980
- def self.log level, msg
1981
- @logfile = STDERR unless defined? @logfile
1982
-
2174
+ def self.check_loglevel level
1983
2175
  lv = LOG_LEVELS[level]
1984
- config_lv = LOG_LEVELS[CONFIG[:log_level] || :WARN]
2176
+ config_lv = LOG_LEVELS[CONFIG[:log_level]]
2177
+ lv <= config_lv
2178
+ end
1985
2179
 
1986
- if defined? SESSION
1987
- pi = SESSION.process_info
1988
- process_info = pi ? "[#{pi}]" : nil
2180
+ def self.debug(&b)
2181
+ if check_loglevel :DEBUG
2182
+ log :DEBUG, b.call
1989
2183
  end
2184
+ end
2185
+
2186
+ def self.log level, msg
2187
+ if check_loglevel level
2188
+ @logfile = STDERR unless defined? @logfile
2189
+
2190
+ if defined? SESSION
2191
+ pi = SESSION.process_info
2192
+ process_info = pi ? "[#{pi}]" : nil
2193
+ end
1990
2194
 
1991
- if lv <= config_lv
1992
2195
  if level == :WARN
1993
2196
  # :WARN on debugger is general information
1994
2197
  @logfile.puts "DEBUGGER#{process_info}: #{msg}"
@@ -2000,17 +2203,77 @@ module DEBUGGER__
2000
2203
  end
2001
2204
  end
2002
2205
 
2206
+ def self.step_in &b
2207
+ if defined?(SESSION) && SESSION.active?
2208
+ SESSION.add_iseq_breakpoint RubyVM::InstructionSequence.of(b), oneshot: true
2209
+ end
2210
+
2211
+ yield
2212
+ end
2213
+
2214
+ if File.identical?(__FILE__.upcase, __FILE__.downcase)
2215
+ # For case insensitive file system (like Windows)
2216
+ # Note that this check is not enough because case sensitive/insensitive is
2217
+ # depend on the file system. So this check is only roughly estimation.
2218
+
2219
+ def self.compare_path(a, b)
2220
+ a.downcase == b.downcase
2221
+ end
2222
+ else
2223
+ def self.compare_path(a, b)
2224
+ a == b
2225
+ end
2226
+ end
2227
+
2003
2228
  module ForkInterceptor
2004
- def fork(&given_block)
2005
- return super unless defined?(SESSION) && SESSION.active?
2229
+ if Process.respond_to? :_fork
2230
+ def _fork
2231
+ return super unless defined?(SESSION) && SESSION.active?
2232
+
2233
+ parent_hook, child_hook = __fork_setup_for_debugger
2006
2234
 
2007
- unless fork_mode = CONFIG[:fork_mode]
2008
- if CONFIG[:parent_on_fork]
2009
- fork_mode = :parent
2235
+ super.tap do |pid|
2236
+ if pid != 0
2237
+ # after fork: parent
2238
+ parent_hook.call pid
2239
+ else
2240
+ # after fork: child
2241
+ child_hook.call
2242
+ end
2243
+ end
2244
+ end
2245
+ else
2246
+ def fork(&given_block)
2247
+ return super unless defined?(SESSION) && SESSION.active?
2248
+ parent_hook, child_hook = __fork_setup_for_debugger
2249
+
2250
+ if given_block
2251
+ new_block = proc {
2252
+ # after fork: child
2253
+ child_hook.call
2254
+ given_block.call
2255
+ }
2256
+ super(&new_block).tap{|pid| parent_hook.call(pid)}
2010
2257
  else
2011
- fork_mode = :both
2258
+ super.tap do |pid|
2259
+ if pid
2260
+ # after fork: parent
2261
+ parent_hook.call pid
2262
+ else
2263
+ # after fork: child
2264
+ child_hook.call
2265
+ end
2266
+ end
2012
2267
  end
2013
2268
  end
2269
+ end
2270
+
2271
+ private def __fork_setup_for_debugger
2272
+ fork_mode = CONFIG[:fork_mode]
2273
+
2274
+ if fork_mode == :both && CONFIG[:parent_on_fork]
2275
+ fork_mode = :parent
2276
+ end
2014
2277
 
2015
2278
  parent_pid = Process.pid
2016
2279
 
@@ -2050,26 +2313,7 @@ module DEBUGGER__
2050
2313
  }
2051
2314
  end
2052
2315
 
2053
- if given_block
2054
- new_block = proc {
2055
- # after fork: child
2056
- child_hook.call
2057
- given_block.call
2058
- }
2059
- pid = super(&new_block)
2060
- parent_hook.call(pid)
2061
- pid
2062
- else
2063
- if pid = super
2064
- # after fork: parent
2065
- parent_hook.call pid
2066
- else
2067
- # after fork: child
2068
- child_hook.call
2069
- end
2070
-
2071
- pid
2072
- end
2316
+ return parent_hook, child_hook
2073
2317
  end
2074
2318
  end
2075
2319
 
@@ -2086,28 +2330,46 @@ module DEBUGGER__
2086
2330
  end
2087
2331
  end
2088
2332
 
2089
- if RUBY_VERSION >= '3.0.0'
2333
+ if Process.respond_to? :_fork
2334
+ module ::Process
2335
+ class << self
2336
+ prepend ForkInterceptor
2337
+ end
2338
+ end
2339
+
2340
+ # trap
2090
2341
  module ::Kernel
2091
- prepend ForkInterceptor
2092
2342
  prepend TrapInterceptor
2093
2343
  end
2344
+ module ::Signal
2345
+ class << self
2346
+ prepend TrapInterceptor
2347
+ end
2348
+ end
2094
2349
  else
2095
- class ::Object
2096
- include ForkInterceptor
2097
- include TrapInterceptor
2350
+ if RUBY_VERSION >= '3.0.0'
2351
+ module ::Kernel
2352
+ prepend ForkInterceptor
2353
+ prepend TrapInterceptor
2354
+ end
2355
+ else
2356
+ class ::Object
2357
+ include ForkInterceptor
2358
+ include TrapInterceptor
2359
+ end
2098
2360
  end
2099
- end
2100
2361
 
2101
- module ::Kernel
2102
- class << self
2103
- prepend ForkInterceptor
2104
- prepend TrapInterceptor
2362
+ module ::Kernel
2363
+ class << self
2364
+ prepend ForkInterceptor
2365
+ prepend TrapInterceptor
2366
+ end
2105
2367
  end
2106
- end
2107
2368
 
2108
- module ::Process
2109
- class << self
2110
- prepend ForkInterceptor
2369
+ module ::Process
2370
+ class << self
2371
+ prepend ForkInterceptor
2372
+ end
2111
2373
  end
2112
2374
  end
2113
2375
 
@@ -2137,3 +2399,12 @@ class Binding
2137
2399
  alias break debugger
2138
2400
  alias b debugger
2139
2401
  end
2402
+
2403
+ # for Ruby 2.6 compatibility
2404
+ unless method(:p).unbind.respond_to? :bind_call
2405
+ class UnboundMethod
2406
+ def bind_call(obj, *args)
2407
+ self.bind(obj).call(*args)
2408
+ end
2409
+ end
2410
+ end