debug 1.3.4 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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