debug 1.6.1 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/debug/session.rb CHANGED
@@ -22,8 +22,9 @@ end
22
22
  # restore RUBYOPT
23
23
  if (added_opt = ENV['RUBY_DEBUG_ADDED_RUBYOPT']) &&
24
24
  (rubyopt = ENV['RUBYOPT']) &&
25
- rubyopt.start_with?(added_opt)
26
- ENV['RUBYOPT'] = rubyopt.delete_prefix(rubyopt)
25
+ rubyopt.end_with?(added_opt)
26
+
27
+ ENV['RUBYOPT'] = rubyopt.delete_suffix(added_opt)
27
28
  ENV['RUBY_DEBUG_ADDED_RUBYOPT'] = nil
28
29
  end
29
30
 
@@ -81,14 +82,16 @@ class RubyVM::InstructionSequence
81
82
  def first_line
82
83
  self.to_a[4][:code_location][0]
83
84
  end unless method_defined?(:first_line)
84
- end
85
+ end if defined?(RubyVM::InstructionSequence)
85
86
 
86
87
  module DEBUGGER__
87
- PresetCommand = Struct.new(:commands, :source, :auto_continue)
88
+ PresetCommands = Struct.new(:commands, :source, :auto_continue)
89
+ SessionCommand = Struct.new(:block, :repeat, :unsafe, :cancel_auto_continue, :postmortem)
90
+
88
91
  class PostmortemError < RuntimeError; end
89
92
 
90
93
  class Session
91
- attr_reader :intercepted_sigint_cmd, :process_group
94
+ attr_reader :intercepted_sigint_cmd, :process_group, :subsession_id
92
95
 
93
96
  include Color
94
97
 
@@ -115,6 +118,7 @@ module DEBUGGER__
115
118
  @intercepted_sigint_cmd = 'DEFAULT'
116
119
  @process_group = ProcessGroup.new
117
120
  @subsession_stack = []
121
+ @subsession_id = 0
118
122
 
119
123
  @frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
120
124
  @var_map = {1 => [:globals], } # {id => ...} for DAP
@@ -124,33 +128,58 @@ module DEBUGGER__
124
128
  @obj_map = {} # { object_id => ... } for CDP
125
129
 
126
130
  @tp_thread_begin = nil
131
+ @tp_thread_end = nil
132
+
133
+ @commands = {}
134
+ @unsafe_context = false
135
+
136
+ @has_keep_script_lines = defined?(RubyVM.keep_script_lines)
137
+
127
138
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
128
- ThreadClient.current.on_load tp.instruction_sequence, tp.eval_script
139
+ eval_script = tp.eval_script unless @has_keep_script_lines
140
+ ThreadClient.current.on_load tp.instruction_sequence, eval_script
129
141
  }
130
142
  @tp_load_script.enable
131
143
 
132
144
  @thread_stopper = thread_stopper
133
145
  self.postmortem = CONFIG[:postmortem]
146
+
147
+ register_default_command
134
148
  end
135
149
 
136
150
  def active?
137
151
  !@q_evt.closed?
138
152
  end
139
153
 
140
- def break_at? file, line
141
- @bps.has_key? [file, line]
154
+ def remote?
155
+ @ui.remote?
156
+ end
157
+
158
+ def stop_stepping? file, line, subsession_id = nil
159
+ if @bps.has_key? [file, line]
160
+ true
161
+ elsif subsession_id && @subsession_id != subsession_id
162
+ true
163
+ else
164
+ false
165
+ end
142
166
  end
143
167
 
144
168
  def activate ui = nil, on_fork: false
145
169
  @ui = ui if ui
146
170
 
147
171
  @tp_thread_begin&.disable
172
+ @tp_thread_end&.disable
148
173
  @tp_thread_begin = nil
149
-
174
+ @tp_thread_end = nil
150
175
  @ui.activate self, on_fork: on_fork
151
176
 
152
177
  q = Queue.new
178
+ first_q = Queue.new
153
179
  @session_server = Thread.new do
180
+ # make sure `@session_server` is assigned
181
+ first_q.pop; first_q = nil
182
+
154
183
  Thread.current.name = 'DEBUGGER__::SESSION@server'
155
184
  Thread.current.abort_on_exception = true
156
185
 
@@ -168,10 +197,21 @@ module DEBUGGER__
168
197
  end
169
198
  @tp_thread_begin.enable
170
199
 
200
+ @tp_thread_end = TracePoint.new(:thread_end) do |tp|
201
+ @th_clients.delete(Thread.current)
202
+ end
203
+ @tp_thread_end.enable
204
+
205
+ if CONFIG[:irb_console] && !CONFIG[:open]
206
+ require_relative "irb_integration"
207
+ thc.activate_irb_integration
208
+ end
209
+
171
210
  # session start
172
211
  q << true
173
212
  session_server_main
174
213
  end
214
+ first_q << :ok
175
215
 
176
216
  q.pop
177
217
  end
@@ -181,6 +221,7 @@ module DEBUGGER__
181
221
  @thread_stopper.disable
182
222
  @tp_load_script.disable
183
223
  @tp_thread_begin.disable
224
+ @tp_thread_end.disable
184
225
  @bps.each_value{|bp| bp.disable}
185
226
  @th_clients.each_value{|thc| thc.close}
186
227
  @tracers.values.each{|t| t.disable}
@@ -192,6 +233,16 @@ module DEBUGGER__
192
233
  def reset_ui ui
193
234
  @ui.deactivate
194
235
  @ui = ui
236
+
237
+ # activate new ui
238
+ @tp_thread_begin.disable
239
+ @tp_thread_end.disable
240
+ @ui.activate self
241
+ if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
242
+ thc.mark_as_management
243
+ end
244
+ @tp_thread_begin.enable
245
+ @tp_thread_end.enable
195
246
  end
196
247
 
197
248
  def pop_event
@@ -210,20 +261,35 @@ module DEBUGGER__
210
261
  @tc << req
211
262
  end
212
263
 
264
+ def request_tc_with_restarted_threads(req)
265
+ restart_all_threads
266
+ request_tc(req)
267
+ end
268
+
269
+ def request_eval type, src
270
+ request_tc_with_restarted_threads [:eval, type, src]
271
+ end
272
+
213
273
  def process_event evt
214
274
  # variable `@internal_info` is only used for test
215
- @tc, output, ev, @internal_info, *ev_args = evt
275
+ tc, output, ev, @internal_info, *ev_args = evt
216
276
 
217
277
  output.each{|str| @ui.puts str} if ev != :suspend
218
278
 
219
- case ev
220
-
221
- when :thread_begin # special event, tc is nil
279
+ # special event, tc is nil
280
+ # and we don't want to set @tc to the newly created thread's ThreadClient
281
+ if ev == :thread_begin
222
282
  th = ev_args.shift
223
283
  q = ev_args.shift
224
284
  on_thread_begin th
225
285
  q << true
226
286
 
287
+ return
288
+ end
289
+
290
+ @tc = tc
291
+
292
+ case ev
227
293
  when :init
228
294
  enter_subsession
229
295
  wait_command_loop
@@ -241,7 +307,7 @@ module DEBUGGER__
241
307
 
242
308
  when :suspend
243
309
  enter_subsession if ev_args.first != :replay
244
- output.each{|str| @ui.puts str}
310
+ output.each{|str| @ui.puts str} unless @ui.ignore_output_on_suspend?
245
311
 
246
312
  case ev_args.first
247
313
  when :breakpoint
@@ -262,7 +328,7 @@ module DEBUGGER__
262
328
  if @displays.empty?
263
329
  wait_command_loop
264
330
  else
265
- request_tc [:eval, :display, @displays]
331
+ request_eval :display, @displays
266
332
  end
267
333
  when :result
268
334
  raise "[BUG] not in subsession" if @subsession_stack.empty?
@@ -277,6 +343,7 @@ module DEBUGGER__
277
343
  end
278
344
  end
279
345
 
346
+ stop_all_threads
280
347
  when :method_breakpoint, :watch_breakpoint
281
348
  bp = ev_args[1]
282
349
  if bp
@@ -290,17 +357,15 @@ module DEBUGGER__
290
357
  obj_inspect = ev_args[2]
291
358
  opt = ev_args[3]
292
359
  add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
360
+ stop_all_threads
293
361
  else
294
- # ignore
362
+ stop_all_threads
295
363
  end
296
364
 
297
365
  wait_command_loop
298
366
 
299
- when :dap_result
300
- dap_event ev_args # server.rb
301
- wait_command_loop
302
- when :cdp_result
303
- cdp_event ev_args
367
+ when :protocol_result
368
+ process_protocol_result ev_args
304
369
  wait_command_loop
305
370
  end
306
371
  end
@@ -316,7 +381,7 @@ module DEBUGGER__
316
381
  if @preset_command && !@preset_command.commands.empty?
317
382
  @preset_command.commands += cs
318
383
  else
319
- @preset_command = PresetCommand.new(cs, name, continue)
384
+ @preset_command = PresetCommands.new(cs, name, continue)
320
385
  end
321
386
 
322
387
  ThreadClient.current.on_init name if kick
@@ -389,97 +454,121 @@ module DEBUGGER__
389
454
  end
390
455
  end
391
456
 
392
- def process_command line
393
- if line.empty?
394
- if @repl_prev_line
395
- line = @repl_prev_line
396
- else
397
- return :retry
398
- end
399
- else
400
- @repl_prev_line = line
401
- end
457
+ private def register_command *names,
458
+ repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true,
459
+ &b
460
+ cmd = SessionCommand.new(b, repeat, unsafe, cancel_auto_continue, postmortem)
402
461
 
403
- /([^\s]+)(?:\s+(.+))?/ =~ line
404
- cmd, arg = $1, $2
405
-
406
- # p cmd: [cmd, *arg]
462
+ names.each{|name|
463
+ @commands[name] = cmd
464
+ }
465
+ end
407
466
 
408
- case cmd
467
+ def register_default_command
409
468
  ### Control flow
410
469
 
411
470
  # * `s[tep]`
412
471
  # * Step in. Resume the program until next breakable point.
413
472
  # * `s[tep] <n>`
414
473
  # * Step in, resume the program at `<n>`th breakable point.
415
- when 's', 'step'
416
- cancel_auto_continue
417
- check_postmortem
474
+ register_command 's', 'step',
475
+ repeat: true,
476
+ cancel_auto_continue: true,
477
+ postmortem: false do |arg|
418
478
  step_command :in, arg
479
+ end
419
480
 
420
481
  # * `n[ext]`
421
482
  # * Step over. Resume the program until next line.
422
483
  # * `n[ext] <n>`
423
484
  # * Step over, same as `step <n>`.
424
- when 'n', 'next'
425
- cancel_auto_continue
426
- check_postmortem
485
+ register_command 'n', 'next',
486
+ repeat: true,
487
+ cancel_auto_continue: true,
488
+ postmortem: false do |arg|
427
489
  step_command :next, arg
490
+ end
428
491
 
429
492
  # * `fin[ish]`
430
493
  # * Finish this frame. Resume the program until the current frame is finished.
431
494
  # * `fin[ish] <n>`
432
495
  # * Finish `<n>`th frames.
433
- when 'fin', 'finish'
434
- cancel_auto_continue
435
- check_postmortem
436
-
496
+ register_command 'fin', 'finish',
497
+ repeat: true,
498
+ cancel_auto_continue: true,
499
+ postmortem: false do |arg|
437
500
  if arg&.to_i == 0
438
501
  raise 'finish command with 0 does not make sense.'
439
502
  end
440
503
 
441
504
  step_command :finish, arg
505
+ end
442
506
 
443
- # * `c[ontinue]`
507
+ # * `u[ntil]`
508
+ # * Similar to `next` command, but only stop later lines or the end of the current frame.
509
+ # * Similar to gdb's `advance` command.
510
+ # * `u[ntil] <[file:]line>`
511
+ # * Run til the program reaches given location or the end of the current frame.
512
+ # * `u[ntil] <name>`
513
+ # * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
514
+ register_command 'u', 'until',
515
+ repeat: true,
516
+ cancel_auto_continue: true,
517
+ postmortem: false do |arg|
518
+
519
+ step_command :until, arg
520
+ end
521
+
522
+ # * `c` or `cont` or `continue`
444
523
  # * Resume the program.
445
- when 'c', 'continue'
446
- cancel_auto_continue
524
+ register_command 'c', 'cont', 'continue',
525
+ repeat: true,
526
+ cancel_auto_continue: true do |arg|
447
527
  leave_subsession :continue
528
+ end
448
529
 
449
530
  # * `q[uit]` or `Ctrl-D`
450
531
  # * Finish debugger (with the debuggee process on non-remote debugging).
451
- when 'q', 'quit'
532
+ register_command 'q', 'quit' do |arg|
452
533
  if ask 'Really quit?'
453
- @ui.quit arg.to_i
534
+ @ui.quit arg.to_i do
535
+ request_tc :quit
536
+ end
454
537
  leave_subsession :continue
455
538
  else
456
- return :retry
539
+ next :retry
457
540
  end
541
+ end
458
542
 
459
543
  # * `q[uit]!`
460
544
  # * Same as q[uit] but without the confirmation prompt.
461
- when 'q!', 'quit!'
462
- @ui.quit arg.to_i
463
- leave_subsession nil
545
+ register_command 'q!', 'quit!', unsafe: false do |arg|
546
+ @ui.quit arg.to_i do
547
+ request_tc :quit
548
+ end
549
+ leave_subsession :continue
550
+ end
464
551
 
465
552
  # * `kill`
466
553
  # * Stop the debuggee process with `Kernel#exit!`.
467
- when 'kill'
554
+ register_command 'kill' do |arg|
468
555
  if ask 'Really kill?'
469
556
  exit! (arg || 1).to_i
470
557
  else
471
- return :retry
558
+ next :retry
472
559
  end
560
+ end
473
561
 
474
562
  # * `kill!`
475
563
  # * Same as kill but without the confirmation prompt.
476
- when 'kill!'
564
+ register_command 'kill!', unsafe: false do |arg|
477
565
  exit! (arg || 1).to_i
566
+ end
478
567
 
479
568
  # * `sigint`
480
569
  # * Execute SIGINT handler registered by the debuggee.
481
570
  # * Note that this command should be used just after stop by `SIGINT`.
482
- when 'sigint'
571
+ register_command 'sigint' do
483
572
  begin
484
573
  case cmd = @intercepted_sigint_cmd
485
574
  when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
@@ -495,8 +584,9 @@ module DEBUGGER__
495
584
  rescue Exception => e
496
585
  @ui.puts "Exception: #{e}"
497
586
  @ui.puts e.backtrace.map{|line| " #{e}"}
498
- return :retry
587
+ next :retry
499
588
  end
589
+ end
500
590
 
501
591
  ### Breakpoint
502
592
 
@@ -521,22 +611,21 @@ module DEBUGGER__
521
611
  # * `b[reak] if: <expr>`
522
612
  # * break if: `<expr>` is true at any lines.
523
613
  # * Note that this feature is super slow.
524
- when 'b', 'break'
525
- check_postmortem
526
-
614
+ register_command 'b', 'break', postmortem: false, unsafe: false do |arg|
527
615
  if arg == nil
528
616
  show_bps
529
- return :retry
617
+ next :retry
530
618
  else
531
619
  case bp = repl_add_breakpoint(arg)
532
620
  when :noretry
533
621
  when nil
534
- return :retry
622
+ next :retry
535
623
  else
536
624
  show_bps bp
537
- return :retry
625
+ next :retry
538
626
  end
539
627
  end
628
+ end
540
629
 
541
630
  # * `catch <Error>`
542
631
  # * Set breakpoint on raising `<Error>`.
@@ -548,16 +637,16 @@ module DEBUGGER__
548
637
  # * stops and run `<command>`, and continue.
549
638
  # * `catch ... path: <path>`
550
639
  # * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
551
- when 'catch'
552
- check_postmortem
553
-
640
+ register_command 'catch', postmortem: false, unsafe: false do |arg|
554
641
  if arg
555
642
  bp = repl_add_catch_breakpoint arg
556
643
  show_bps bp if bp
557
644
  else
558
645
  show_bps
559
646
  end
560
- return :retry
647
+
648
+ :retry
649
+ end
561
650
 
562
651
  # * `watch @ivar`
563
652
  # * Stop the execution when the result of current scope's `@ivar` is changed.
@@ -570,24 +659,20 @@ module DEBUGGER__
570
659
  # * stops and run `<command>`, and continue.
571
660
  # * `watch ... path: <path>`
572
661
  # * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
573
- when 'wat', 'watch'
574
- check_postmortem
575
-
662
+ register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg|
576
663
  if arg && arg.match?(/\A@\w+/)
577
664
  repl_add_watch_breakpoint(arg)
578
665
  else
579
666
  show_bps
580
- return :retry
667
+ :retry
581
668
  end
669
+ end
582
670
 
583
671
  # * `del[ete]`
584
672
  # * delete all breakpoints.
585
673
  # * `del[ete] <bpnum>`
586
674
  # * delete specified breakpoint.
587
- when 'del', 'delete'
588
- check_postmortem
589
-
590
- bp =
675
+ register_command 'del', 'delete', postmortem: false, unsafe: false do |arg|
591
676
  case arg
592
677
  when nil
593
678
  show_bps
@@ -595,12 +680,13 @@ module DEBUGGER__
595
680
  delete_bp
596
681
  end
597
682
  when /\d+/
598
- delete_bp arg.to_i
683
+ bp = delete_bp arg.to_i
599
684
  else
600
685
  nil
601
686
  end
602
687
  @ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
603
- return :retry
688
+ :retry
689
+ end
604
690
 
605
691
  ### Information
606
692
 
@@ -612,19 +698,20 @@ module DEBUGGER__
612
698
  # * Only shows frames with method name or location info that matches `/regexp/`.
613
699
  # * `bt <num> /regexp/` or `backtrace <num> /regexp/`
614
700
  # * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
615
- when 'bt', 'backtrace'
701
+ register_command 'bt', 'backtrace', unsafe: false do |arg|
616
702
  case arg
617
703
  when /\A(\d+)\z/
618
- request_tc [:show, :backtrace, arg.to_i, nil]
704
+ request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
619
705
  when /\A\/(.*)\/\z/
620
706
  pattern = $1
621
- request_tc [:show, :backtrace, nil, Regexp.compile(pattern)]
707
+ request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
622
708
  when /\A(\d+)\s+\/(.*)\/\z/
623
709
  max, pattern = $1, $2
624
- request_tc [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
710
+ request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
625
711
  else
626
- request_tc [:show, :backtrace, nil, nil]
712
+ request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
627
713
  end
714
+ end
628
715
 
629
716
  # * `l[ist]`
630
717
  # * Show current frame's source code.
@@ -633,7 +720,7 @@ module DEBUGGER__
633
720
  # * Show predecessor lines as opposed to the `list` command.
634
721
  # * `l[ist] <start>` or `l[ist] <start>-<end>`
635
722
  # * Show current frame's source code from the line <start> to <end> if given.
636
- when 'l', 'list'
723
+ register_command 'l', 'list', repeat: true, unsafe: false do |arg|
637
724
  case arg ? arg.strip : nil
638
725
  when /\A(\d+)\z/
639
726
  request_tc [:show, :list, {start_line: arg.to_i - 1}]
@@ -645,45 +732,63 @@ module DEBUGGER__
645
732
  request_tc [:show, :list]
646
733
  else
647
734
  @ui.puts "Can not handle list argument: #{arg}"
648
- return :retry
735
+ :retry
649
736
  end
737
+ end
738
+
739
+ # * `whereami`
740
+ # * Show the current frame with source code.
741
+ register_command 'whereami', unsafe: false do
742
+ request_tc [:show, :whereami]
743
+ end
650
744
 
651
745
  # * `edit`
652
746
  # * Open the current file on the editor (use `EDITOR` environment variable).
653
747
  # * Note that edited file will not be reloaded.
654
748
  # * `edit <file>`
655
749
  # * Open <file> on the editor.
656
- when 'edit'
750
+ register_command 'edit' do |arg|
657
751
  if @ui.remote?
658
752
  @ui.puts "not supported on the remote console."
659
- return :retry
753
+ next :retry
660
754
  end
661
755
 
662
756
  begin
663
757
  arg = resolve_path(arg) if arg
664
758
  rescue Errno::ENOENT
665
759
  @ui.puts "not found: #{arg}"
666
- return :retry
760
+ next :retry
667
761
  end
668
762
 
669
763
  request_tc [:show, :edit, arg]
764
+ end
765
+
766
+ info_subcommands = nil
767
+ info_subcommands_abbrev = nil
670
768
 
671
769
  # * `i[nfo]`
672
- # * Show information about current frame (local/instance variables and defined constants).
673
- # * `i[nfo] l[ocal[s]]`
770
+ # * Show information about current frame (local/instance variables and defined constants).
771
+ # * `i[nfo]` <subcommand>
772
+ # * `info` has the following sub-commands.
773
+ # * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'.
774
+ # * `i[nfo] l or locals or local_variables`
674
775
  # * Show information about the current frame (local variables)
675
- # * It includes `self` as `%self` and a return value as `%return`.
676
- # * `i[nfo] i[var[s]]` or `i[nfo] instance`
776
+ # * It includes `self` as `%self` and a return value as `_return`.
777
+ # * `i[nfo] i or ivars or instance_variables`
677
778
  # * Show information about instance variables about `self`.
678
- # * `i[nfo] c[onst[s]]` or `i[nfo] constant[s]`
779
+ # * `info ivars <expr>` shows the instance variables of the result of `<expr>`.
780
+ # * `i[nfo] c or consts or constants`
679
781
  # * Show information about accessible constants except toplevel constants.
680
- # * `i[nfo] g[lobal[s]]`
782
+ # * `info consts <expr>` shows the constants of a class/module of the result of `<expr>`
783
+ # * `i[nfo] g or globals or global_variables`
681
784
  # * Show information about global variables
785
+ # * `i[nfo] th or threads`
786
+ # * Show all threads (same as `th[read]`).
787
+ # * `i[nfo] b or breakpoints or w or watchpoints`
788
+ # * Show all breakpoints and watchpoints.
682
789
  # * `i[nfo] ... /regexp/`
683
790
  # * Filter the output with `/regexp/`.
684
- # * `i[nfo] th[read[s]]`
685
- # * Show all threads (same as `th[read]`).
686
- when 'i', 'info'
791
+ register_command 'i', 'info', unsafe: false do |arg|
687
792
  if /\/(.+)\/\z/ =~ arg
688
793
  pat = Regexp.compile($1)
689
794
  sub = $~.pre_match.strip
@@ -691,63 +796,98 @@ module DEBUGGER__
691
796
  sub = arg
692
797
  end
693
798
 
799
+ if /\A(.+?)\b(.+)/ =~ sub
800
+ sub = $1
801
+ opt = $2.strip
802
+ opt = nil if opt.empty?
803
+ end
804
+
805
+ if sub && !info_subcommands
806
+ info_subcommands = {
807
+ locals: %w[ locals local_variables ],
808
+ ivars: %w[ ivars instance_variables ],
809
+ consts: %w[ consts constants ],
810
+ globals:%w[ globals global_variables ],
811
+ threads:%w[ threads ],
812
+ breaks: %w[ breakpoints ],
813
+ watchs: %w[ watchpoints ],
814
+ }
815
+
816
+ require_relative 'abbrev_command'
817
+ info_subcommands_abbrev = AbbrevCommand.new(info_subcommands)
818
+ end
819
+
820
+ if sub
821
+ sub = info_subcommands_abbrev.search sub, :unknown do |candidates|
822
+ # note: unreached now
823
+ @ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}"
824
+ end
825
+ end
826
+
694
827
  case sub
695
828
  when nil
696
- request_tc [:show, :default, pat] # something useful
697
- when 'l', /^locals?/
698
- request_tc [:show, :locals, pat]
699
- when 'i', /^ivars?/i, /^instance[_ ]variables?/i
700
- request_tc [:show, :ivars, pat]
701
- when 'c', /^consts?/i, /^constants?/i
702
- request_tc [:show, :consts, pat]
703
- when 'g', /^globals?/i, /^global[_ ]variables?/i
704
- request_tc [:show, :globals, pat]
705
- when 'th', /threads?/
829
+ request_tc_with_restarted_threads [:show, :default, pat] # something useful
830
+ when :locals
831
+ request_tc_with_restarted_threads [:show, :locals, pat]
832
+ when :ivars
833
+ request_tc_with_restarted_threads [:show, :ivars, pat, opt]
834
+ when :consts
835
+ request_tc_with_restarted_threads [:show, :consts, pat, opt]
836
+ when :globals
837
+ request_tc_with_restarted_threads [:show, :globals, pat]
838
+ when :threads
706
839
  thread_list
707
- return :retry
840
+ :retry
841
+ when :breaks, :watchs
842
+ show_bps
843
+ :retry
708
844
  else
709
845
  @ui.puts "unrecognized argument for info command: #{arg}"
710
846
  show_help 'info'
711
- return :retry
847
+ :retry
712
848
  end
849
+ end
713
850
 
714
851
  # * `o[utline]` or `ls`
715
852
  # * Show you available methods, constants, local variables, and instance variables in the current scope.
716
853
  # * `o[utline] <expr>` or `ls <expr>`
717
854
  # * Show you available methods and instance variables of the given object.
718
855
  # * If the object is a class/module, it also lists its constants.
719
- when 'outline', 'o', 'ls'
720
- request_tc [:show, :outline, arg]
856
+ register_command 'outline', 'o', 'ls', unsafe: false do |arg|
857
+ request_tc_with_restarted_threads [:show, :outline, arg]
858
+ end
721
859
 
722
860
  # * `display`
723
861
  # * Show display setting.
724
862
  # * `display <expr>`
725
863
  # * Show the result of `<expr>` at every suspended timing.
726
- when 'display'
864
+ register_command 'display', postmortem: false do |arg|
727
865
  if arg && !arg.empty?
728
866
  @displays << arg
729
- request_tc [:eval, :try_display, @displays]
867
+ request_eval :try_display, @displays
730
868
  else
731
- request_tc [:eval, :display, @displays]
869
+ request_eval :display, @displays
732
870
  end
871
+ end
733
872
 
734
873
  # * `undisplay`
735
874
  # * Remove all display settings.
736
875
  # * `undisplay <displaynum>`
737
876
  # * Remove a specified display setting.
738
- when 'undisplay'
877
+ register_command 'undisplay', postmortem: false, unsafe: false do |arg|
739
878
  case arg
740
879
  when /(\d+)/
741
880
  if @displays[n = $1.to_i]
742
881
  @displays.delete_at n
743
882
  end
744
- request_tc [:eval, :display, @displays]
883
+ request_eval :display, @displays
745
884
  when nil
746
885
  if ask "clear all?", 'N'
747
886
  @displays.clear
748
887
  end
749
- return :retry
888
+ :retry
750
889
  end
890
+ end
751
891
 
752
892
  ### Frame control
753
893
 
@@ -755,53 +895,58 @@ module DEBUGGER__
755
895
  # * Show the current frame.
756
896
  # * `f[rame] <framenum>`
757
897
  # * Specify a current frame. Evaluation are run on specified frame.
758
- when 'frame', 'f'
898
+ register_command 'frame', 'f', unsafe: false do |arg|
759
899
  request_tc [:frame, :set, arg]
900
+ end
760
901
 
761
902
  # * `up`
762
903
  # * Specify the upper frame.
763
- when 'up'
904
+ register_command 'up', repeat: true, unsafe: false do |arg|
764
905
  request_tc [:frame, :up]
906
+ end
765
907
 
766
908
  # * `down`
767
909
  # * Specify the lower frame.
768
- when 'down'
910
+ register_command 'down', repeat: true, unsafe: false do |arg|
769
911
  request_tc [:frame, :down]
912
+ end
770
913
 
771
914
  ### Evaluate
772
915
 
773
916
  # * `p <expr>`
774
917
  # * Evaluate like `p <expr>` on the current frame.
775
- when 'p'
776
- request_tc [:eval, :p, arg.to_s]
918
+ register_command 'p' do |arg|
919
+ request_eval :p, arg.to_s
920
+ end
777
921
 
778
922
  # * `pp <expr>`
779
923
  # * Evaluate like `pp <expr>` on the current frame.
780
- when 'pp'
781
- request_tc [:eval, :pp, arg.to_s]
924
+ register_command 'pp' do |arg|
925
+ request_eval :pp, arg.to_s
926
+ end
782
927
 
783
928
  # * `eval <expr>`
784
929
  # * Evaluate `<expr>` on the current frame.
785
- when 'eval', 'call'
930
+ register_command 'eval', 'call' do |arg|
786
931
  if arg == nil || arg.empty?
787
932
  show_help 'eval'
788
933
  @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
789
- return :retry
934
+ :retry
790
935
  else
791
- request_tc [:eval, :call, arg]
936
+ request_eval :call, arg
792
937
  end
938
+ end
793
939
 
794
940
  # * `irb`
795
941
  # * Invoke `irb` on the current frame.
796
- when 'irb'
942
+ register_command 'irb' do |arg|
797
943
  if @ui.remote?
798
- @ui.puts "not supported on the remote console."
799
- return :retry
944
+ @ui.puts "\nIRB is not supported on the remote console."
945
+ :retry
946
+ else
947
+ request_eval :irb, nil
800
948
  end
801
- request_tc [:eval, :irb]
802
-
803
- # don't repeat irb command
804
- @repl_prev_line = nil
949
+ end
805
950
 
806
951
  ### Trace
807
952
  # * `trace`
@@ -822,7 +967,7 @@ module DEBUGGER__
822
967
  # * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
823
968
  # * `trace off [line|call|pass]`
824
969
  # * Disable all tracers. If `<type>` is provided, disable specified type tracers.
825
- when 'trace'
970
+ register_command 'trace', postmortem: false, unsafe: false do |arg|
826
971
  if (re = /\s+into:\s*(.+)/) =~ arg
827
972
  into = $1
828
973
  arg.sub!(re, '')
@@ -840,22 +985,22 @@ module DEBUGGER__
840
985
  @ui.puts "* \##{i} #{t}"
841
986
  }
842
987
  @ui.puts
843
- return :retry
988
+ :retry
844
989
 
845
990
  when /\Aline\z/
846
991
  add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
847
- return :retry
992
+ :retry
848
993
 
849
994
  when /\Acall\z/
850
995
  add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
851
- return :retry
996
+ :retry
852
997
 
853
998
  when /\Aexception\z/
854
999
  add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
855
- return :retry
1000
+ :retry
856
1001
 
857
1002
  when /\Aobject\s+(.+)/
858
- request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}]
1003
+ request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]
859
1004
 
860
1005
  when /\Aoff\s+(\d+)\z/
861
1006
  if t = @tracers.values[$1.to_i]
@@ -864,7 +1009,7 @@ module DEBUGGER__
864
1009
  else
865
1010
  @ui.puts "Unmatched: #{$1}"
866
1011
  end
867
- return :retry
1012
+ :retry
868
1013
 
869
1014
  when /\Aoff(\s+(line|call|exception|object))?\z/
870
1015
  @tracers.values.each{|t|
@@ -873,12 +1018,13 @@ module DEBUGGER__
873
1018
  @ui.puts "Disable #{t.to_s}"
874
1019
  end
875
1020
  }
876
- return :retry
1021
+ :retry
877
1022
 
878
1023
  else
879
1024
  @ui.puts "Unknown trace option: #{arg.inspect}"
880
- return :retry
1025
+ :retry
881
1026
  end
1027
+ end
882
1028
 
883
1029
  # Record
884
1030
  # * `record`
@@ -890,14 +1036,15 @@ module DEBUGGER__
890
1036
  # * `s[tep]` does stepping forward with the last log.
891
1037
  # * `step reset`
892
1038
  # * Stop replay .
893
- when 'record'
1039
+ register_command 'record', postmortem: false, unsafe: false do |arg|
894
1040
  case arg
895
1041
  when nil, 'on', 'off'
896
1042
  request_tc [:record, arg&.to_sym]
897
1043
  else
898
1044
  @ui.puts "unknown command: #{arg}"
899
- return :retry
1045
+ :retry
900
1046
  end
1047
+ end
901
1048
 
902
1049
  ### Thread control
903
1050
 
@@ -905,7 +1052,7 @@ module DEBUGGER__
905
1052
  # * Show all threads.
906
1053
  # * `th[read] <thnum>`
907
1054
  # * Switch thread specified by `<thnum>`.
908
- when 'th', 'thread'
1055
+ register_command 'th', 'thread', unsafe: false do |arg|
909
1056
  case arg
910
1057
  when nil, 'list', 'l'
911
1058
  thread_list
@@ -914,7 +1061,8 @@ module DEBUGGER__
914
1061
  else
915
1062
  @ui.puts "unknown thread command: #{arg}"
916
1063
  end
917
- return :retry
1064
+ :retry
1065
+ end
918
1066
 
919
1067
  ### Configuration
920
1068
  # * `config`
@@ -927,13 +1075,14 @@ module DEBUGGER__
927
1075
  # * Append `<val>` to `<name>` if it is an array.
928
1076
  # * `config unset <name>`
929
1077
  # * Set <name> to default.
930
- when 'config'
1078
+ register_command 'config', unsafe: false do |arg|
931
1079
  config_command arg
932
- return :retry
1080
+ :retry
1081
+ end
933
1082
 
934
1083
  # * `source <file>`
935
1084
  # * Evaluate lines in `<file>` as debug commands.
936
- when 'source'
1085
+ register_command 'source' do |arg|
937
1086
  if arg
938
1087
  begin
939
1088
  cmds = File.readlines(path = File.expand_path(arg))
@@ -944,7 +1093,8 @@ module DEBUGGER__
944
1093
  else
945
1094
  show_help 'source'
946
1095
  end
947
- return :retry
1096
+ :retry
1097
+ end
948
1098
 
949
1099
  # * `open`
950
1100
  # * open debuggee port on UNIX domain socket and wait for attaching.
@@ -955,26 +1105,28 @@ module DEBUGGER__
955
1105
  # * open debuggee port for VSCode and launch VSCode if available.
956
1106
  # * `open chrome`
957
1107
  # * open debuggee port for Chrome and wait for attaching.
958
- when 'open'
1108
+ register_command 'open' do |arg|
959
1109
  case arg&.downcase
960
1110
  when '', nil
961
- repl_open
962
- when 'vscode'
963
- repl_open_vscode
964
- when /\A(.+):(\d+)\z/
965
- repl_open_tcp $1, $2.to_i
1111
+ ::DEBUGGER__.open nonstop: true
966
1112
  when /\A(\d+)z/
967
- repl_open_tcp nil, $1.to_i
1113
+ ::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true
1114
+ when /\A(.+):(\d+)\z/
1115
+ ::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true
968
1116
  when 'tcp'
969
- repl_open_tcp CONFIG[:host], (CONFIG[:port] || 0)
1117
+ ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
1118
+ when 'vscode'
1119
+ CONFIG[:open] = 'vscode'
1120
+ ::DEBUGGER__.open nonstop: true
970
1121
  when 'chrome', 'cdp'
971
- CONFIG[:open_frontend] = 'chrome'
972
- repl_open_tcp CONFIG[:host], (CONFIG[:port] || 0)
1122
+ CONFIG[:open] = 'chrome'
1123
+ ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
973
1124
  else
974
1125
  raise "Unknown arg: #{arg}"
975
1126
  end
976
1127
 
977
- return :retry
1128
+ :retry
1129
+ end
978
1130
 
979
1131
  ### Help
980
1132
 
@@ -982,26 +1134,38 @@ module DEBUGGER__
982
1134
  # * Show help for all commands.
983
1135
  # * `h[elp] <command>`
984
1136
  # * Show help for the given command.
985
- when 'h', 'help', '?'
1137
+ register_command 'h', 'help', '?', unsafe: false do |arg|
986
1138
  show_help arg
987
- return :retry
1139
+ :retry
1140
+ end
1141
+ end
988
1142
 
989
- ### END
1143
+ def process_command line
1144
+ if line.empty?
1145
+ if @repl_prev_line
1146
+ line = @repl_prev_line
1147
+ else
1148
+ return :retry
1149
+ end
1150
+ else
1151
+ @repl_prev_line = line
1152
+ end
1153
+
1154
+ /([^\s]+)(?:\s+(.+))?/ =~ line
1155
+ cmd_name, cmd_arg = $1, $2
1156
+
1157
+ if cmd = @commands[cmd_name]
1158
+ check_postmortem if !cmd.postmortem
1159
+ check_unsafe if cmd.unsafe
1160
+ cancel_auto_continue if cmd.cancel_auto_continue
1161
+ @repl_prev_line = nil if !cmd.repeat
1162
+
1163
+ cmd.block.call(cmd_arg)
990
1164
  else
991
- request_tc [:eval, :pp, line]
992
- =begin
993
1165
  @repl_prev_line = nil
994
- @ui.puts "unknown command: #{line}"
995
- begin
996
- require 'did_you_mean'
997
- spell_checker = DidYouMean::SpellChecker.new(dictionary: DEBUGGER__.commands)
998
- correction = spell_checker.correct(line.split(/\s/).first || '')
999
- @ui.puts "Did you mean? #{correction.join(' or ')}" unless correction.empty?
1000
- rescue LoadError
1001
- # Don't use D
1002
- end
1003
- return :retry
1004
- =end
1166
+ check_unsafe
1167
+
1168
+ request_eval :pp, line
1005
1169
  end
1006
1170
 
1007
1171
  rescue Interrupt
@@ -1017,31 +1181,12 @@ module DEBUGGER__
1017
1181
  return :retry
1018
1182
  end
1019
1183
 
1020
- def repl_open_setup
1021
- @tp_thread_begin.disable
1022
- @ui.activate self
1023
- if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
1024
- thc.mark_as_management
1184
+ def step_command type, arg
1185
+ if type == :until
1186
+ leave_subsession [:step, type, arg]
1187
+ return
1025
1188
  end
1026
- @tp_thread_begin.enable
1027
- end
1028
-
1029
- def repl_open_tcp host, port, **kw
1030
- DEBUGGER__.open_tcp host: host, port: port, nonstop: true, **kw
1031
- repl_open_setup
1032
- end
1033
-
1034
- def repl_open
1035
- DEBUGGER__.open nonstop: true
1036
- repl_open_setup
1037
- end
1038
-
1039
- def repl_open_vscode
1040
- CONFIG[:open_frontend] = 'vscode'
1041
- repl_open
1042
- end
1043
1189
 
1044
- def step_command type, arg
1045
1190
  case arg
1046
1191
  when nil, /\A\d+\z/
1047
1192
  if type == :in && @tc.recorder&.replaying?
@@ -1049,12 +1194,14 @@ module DEBUGGER__
1049
1194
  else
1050
1195
  leave_subsession [:step, type, arg&.to_i]
1051
1196
  end
1052
- when /\Aback\z/, /\Areset\z/
1197
+ when /\A(back)\z/, /\A(back)\s+(\d+)\z/, /\A(reset)\z/
1053
1198
  if type != :in
1054
1199
  @ui.puts "only `step #{arg}` is supported."
1055
1200
  :retry
1056
1201
  else
1057
- request_tc [:step, arg.to_sym]
1202
+ type = $1.to_sym
1203
+ iter = $2&.to_i
1204
+ request_tc [:step, type, iter]
1058
1205
  end
1059
1206
  else
1060
1207
  @ui.puts "Unknown option: #{arg}"
@@ -1248,8 +1395,6 @@ module DEBUGGER__
1248
1395
 
1249
1396
  def add_bp bp
1250
1397
  # don't repeat commands that add breakpoints
1251
- @repl_prev_line = nil
1252
-
1253
1398
  if @bps.has_key? bp.key
1254
1399
  if bp.duplicable?
1255
1400
  bp
@@ -1281,7 +1426,7 @@ module DEBUGGER__
1281
1426
 
1282
1427
  BREAK_KEYWORDS = %w(if: do: pre: path:).freeze
1283
1428
 
1284
- def parse_break arg
1429
+ private def parse_break type, arg
1285
1430
  mode = :sig
1286
1431
  expr = Hash.new{|h, k| h[k] = []}
1287
1432
  arg.split(' ').each{|w|
@@ -1298,13 +1443,18 @@ module DEBUGGER__
1298
1443
  expr[:path] = Regexp.compile($1)
1299
1444
  end
1300
1445
 
1446
+ if expr[:do] || expr[:pre]
1447
+ check_unsafe
1448
+ expr[:cmd] = [type, expr[:pre], expr[:do]]
1449
+ end
1450
+
1301
1451
  expr
1302
1452
  end
1303
1453
 
1304
1454
  def repl_add_breakpoint arg
1305
- expr = parse_break arg.strip
1455
+ expr = parse_break 'break', arg.strip
1306
1456
  cond = expr[:if]
1307
- cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1457
+ cmd = expr[:cmd]
1308
1458
  path = expr[:path]
1309
1459
 
1310
1460
  case expr[:sig]
@@ -1325,9 +1475,9 @@ module DEBUGGER__
1325
1475
  end
1326
1476
 
1327
1477
  def repl_add_catch_breakpoint arg
1328
- expr = parse_break arg.strip
1478
+ expr = parse_break 'catch', arg.strip
1329
1479
  cond = expr[:if]
1330
- cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1480
+ cmd = expr[:cmd]
1331
1481
  path = expr[:path]
1332
1482
 
1333
1483
  bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
@@ -1335,9 +1485,9 @@ module DEBUGGER__
1335
1485
  end
1336
1486
 
1337
1487
  def repl_add_watch_breakpoint arg
1338
- expr = parse_break arg.strip
1488
+ expr = parse_break 'watch', arg.strip
1339
1489
  cond = expr[:if]
1340
- cmd = ['watch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1490
+ cmd = expr[:cmd]
1341
1491
  path = Regexp.compile(expr[:path]) if expr[:path]
1342
1492
 
1343
1493
  request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
@@ -1374,7 +1524,7 @@ module DEBUGGER__
1374
1524
  def clear_line_breakpoints path
1375
1525
  path = resolve_path(path)
1376
1526
  clear_breakpoints do |k, bp|
1377
- bp.is_a?(LineBreakpoint) && DEBUGGER__.compare_path(k.first, path)
1527
+ bp.is_a?(LineBreakpoint) && bp.path_is?(path)
1378
1528
  end
1379
1529
  rescue Errno::ENOENT
1380
1530
  # just ignore
@@ -1398,9 +1548,7 @@ module DEBUGGER__
1398
1548
  # tracers
1399
1549
 
1400
1550
  def add_tracer tracer
1401
- # don't repeat commands that add tracers
1402
- @repl_prev_line = nil
1403
- if @tracers.has_key? tracer.key
1551
+ if @tracers[tracer.key]&.enabled?
1404
1552
  tracer.disable
1405
1553
  @ui.puts "Duplicated tracer: #{tracer}"
1406
1554
  else
@@ -1557,10 +1705,11 @@ module DEBUGGER__
1557
1705
  end
1558
1706
 
1559
1707
  private def enter_subsession
1708
+ @subsession_id += 1
1560
1709
  if !@subsession_stack.empty?
1561
- DEBUGGER__.info "Enter subsession (nested #{@subsession_stack.size})"
1710
+ DEBUGGER__.debug{ "Enter subsession (nested #{@subsession_stack.size})" }
1562
1711
  else
1563
- DEBUGGER__.info "Enter subsession"
1712
+ DEBUGGER__.debug{ "Enter subsession" }
1564
1713
  stop_all_threads
1565
1714
  @process_group.lock
1566
1715
  end
@@ -1573,11 +1722,11 @@ module DEBUGGER__
1573
1722
  @subsession_stack.pop
1574
1723
 
1575
1724
  if @subsession_stack.empty?
1576
- DEBUGGER__.info "Leave subsession"
1725
+ DEBUGGER__.debug{ "Leave subsession" }
1577
1726
  @process_group.unlock
1578
1727
  restart_all_threads
1579
1728
  else
1580
- DEBUGGER__.info "Leave subsession (nested #{@subsession_stack.size})"
1729
+ DEBUGGER__.debug{ "Leave subsession (nested #{@subsession_stack.size})" }
1581
1730
  end
1582
1731
 
1583
1732
  request_tc type if type
@@ -1599,25 +1748,30 @@ module DEBUGGER__
1599
1748
  file_path, reloaded = @sr.add(iseq, src)
1600
1749
  @ui.event :load, file_path, reloaded
1601
1750
 
1602
- pending_line_breakpoints = @bps.find_all do |key, bp|
1603
- LineBreakpoint === bp && !bp.iseq
1604
- end
1751
+ # check breakpoints
1752
+ if file_path
1753
+ @bps.find_all do |_key, bp|
1754
+ LineBreakpoint === bp && bp.path_is?(file_path) && (iseq.first_lineno..iseq.last_line).cover?(bp.line)
1755
+ end.each do |_key, bp|
1756
+ if !bp.iseq
1757
+ bp.try_activate iseq
1758
+ elsif reloaded
1759
+ @bps.delete bp.key # to allow duplicate
1605
1760
 
1606
- pending_line_breakpoints.each do |_key, bp|
1607
- if DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
1608
- bp.try_activate iseq
1609
- end
1610
- end
1761
+ # When we delete a breakpoint from the @bps hash, we also need to deactivate it or else its tracepoint event
1762
+ # will continue to be enabled and we'll suspend on ghost breakpoints
1763
+ bp.delete
1611
1764
 
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)
1765
+ nbp = LineBreakpoint.copy(bp, iseq)
1618
1766
  add_bp nbp
1619
1767
  end
1620
1768
  end
1769
+ else # !file_path => file_path is not existing
1770
+ @bps.find_all do |_key, bp|
1771
+ LineBreakpoint === bp && !bp.iseq && DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
1772
+ end.each do |_key, bp|
1773
+ bp.try_activate iseq
1774
+ end
1621
1775
  end
1622
1776
  end
1623
1777
 
@@ -1716,6 +1870,12 @@ module DEBUGGER__
1716
1870
  end
1717
1871
  end
1718
1872
 
1873
+ def check_unsafe
1874
+ if @unsafe_context
1875
+ raise RuntimeError, "#{@repl_prev_line.dump} is not allowed on unsafe context."
1876
+ end
1877
+ end
1878
+
1719
1879
  def enter_postmortem_session exc
1720
1880
  return unless exc.instance_variable_defined? :@__debugger_postmortem_frames
1721
1881
 
@@ -1795,6 +1955,17 @@ module DEBUGGER__
1795
1955
  end
1796
1956
  end
1797
1957
 
1958
+ def set_no_sigint_hook old, new
1959
+ return unless old != new
1960
+ return unless @ui.respond_to? :activate_sigint
1961
+
1962
+ if old # no -> yes
1963
+ @ui.activate_sigint
1964
+ else
1965
+ @ui.deactivate_sigint
1966
+ end
1967
+ end
1968
+
1798
1969
  def save_int_trap cmd
1799
1970
  prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd
1800
1971
  prev
@@ -1838,6 +2009,13 @@ module DEBUGGER__
1838
2009
  def after_fork_parent
1839
2010
  @ui.after_fork_parent
1840
2011
  end
2012
+
2013
+ # experimental API
2014
+ def extend_feature session: nil, thread_client: nil, ui: nil
2015
+ Session.include session if session
2016
+ ThreadClient.include thread_client if thread_client
2017
+ @ui.extend ui if ui
2018
+ end
1841
2019
  end
1842
2020
 
1843
2021
  class ProcessGroup
@@ -1886,9 +2064,11 @@ module DEBUGGER__
1886
2064
 
1887
2065
  def after_fork child: true
1888
2066
  if child || !@lock_file
1889
- @m = Mutex.new
1890
- @lock_level = 0
1891
- @lock_file = open(@lock_tempfile.path, 'w')
2067
+ @m = Mutex.new unless @m
2068
+ @m.synchronize do
2069
+ @lock_level = 0
2070
+ @lock_file = open(@lock_tempfile.path, 'w')
2071
+ end
1892
2072
  end
1893
2073
  end
1894
2074
 
@@ -1897,7 +2077,7 @@ module DEBUGGER__
1897
2077
  end
1898
2078
 
1899
2079
  def locked?
1900
- # DEBUGGER__.info "locked? #{@lock_level}"
2080
+ # DEBUGGER__.debug{ "locked? #{@lock_level}" }
1901
2081
  @lock_level > 0
1902
2082
  end
1903
2083
 
@@ -1982,6 +2162,10 @@ module DEBUGGER__
1982
2162
  end
1983
2163
  end
1984
2164
 
2165
+ def ignore_output_on_suspend?
2166
+ false
2167
+ end
2168
+
1985
2169
  def flush
1986
2170
  end
1987
2171
  end
@@ -2006,6 +2190,7 @@ module DEBUGGER__
2006
2190
  case loc.absolute_path
2007
2191
  when dir_prefix
2008
2192
  when %r{rubygems/core_ext/kernel_require\.rb}
2193
+ when %r{bundled_gems\.rb}
2009
2194
  else
2010
2195
  return loc if loc.absolute_path
2011
2196
  end
@@ -2018,19 +2203,22 @@ module DEBUGGER__
2018
2203
  def self.start nonstop: false, **kw
2019
2204
  CONFIG.set_config(**kw)
2020
2205
 
2021
- unless defined? SESSION
2022
- require_relative 'local'
2023
- initialize_session{ UI_LocalConsole.new }
2206
+ if CONFIG[:open]
2207
+ open nonstop: nonstop, **kw
2208
+ else
2209
+ unless defined? SESSION
2210
+ require_relative 'local'
2211
+ initialize_session{ UI_LocalConsole.new }
2212
+ end
2213
+ setup_initial_suspend unless nonstop
2024
2214
  end
2025
-
2026
- setup_initial_suspend unless nonstop
2027
2215
  end
2028
2216
 
2029
2217
  def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
2030
2218
  CONFIG.set_config(**kw)
2031
2219
  require_relative 'server'
2032
2220
 
2033
- if port || CONFIG[:open_frontend] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
2221
+ if port || CONFIG[:open] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
2034
2222
  open_tcp host: host, port: (port || 0), nonstop: nonstop
2035
2223
  else
2036
2224
  open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
@@ -2092,6 +2280,18 @@ module DEBUGGER__
2092
2280
  end
2093
2281
  end
2094
2282
 
2283
+ # Exiting control
2284
+
2285
+ class << self
2286
+ def skip_all
2287
+ @skip_all = true
2288
+ end
2289
+
2290
+ def skip?
2291
+ @skip_all
2292
+ end
2293
+ end
2294
+
2095
2295
  def self.load_rc
2096
2296
  [[File.expand_path('~/.rdbgrc'), true],
2097
2297
  [File.expand_path('~/.rdbgrc.rb'), true],
@@ -2120,7 +2320,7 @@ module DEBUGGER__
2120
2320
  end
2121
2321
 
2122
2322
  # Inspector
2123
-
2323
+
2124
2324
  SHORT_INSPECT_LENGTH = 40
2125
2325
 
2126
2326
  class LimitedPP
@@ -2191,6 +2391,7 @@ module DEBUGGER__
2191
2391
  def self.log level, msg
2192
2392
  if check_loglevel level
2193
2393
  @logfile = STDERR unless defined? @logfile
2394
+ return if @logfile.closed?
2194
2395
 
2195
2396
  if defined? SESSION
2196
2397
  pi = SESSION.process_info
@@ -2222,7 +2423,7 @@ module DEBUGGER__
2222
2423
  # depend on the file system. So this check is only roughly estimation.
2223
2424
 
2224
2425
  def self.compare_path(a, b)
2225
- a.downcase == b.downcase
2426
+ a&.downcase == b&.downcase
2226
2427
  end
2227
2428
  else
2228
2429
  def self.compare_path(a, b)
@@ -2273,8 +2474,24 @@ module DEBUGGER__
2273
2474
  end
2274
2475
  end
2275
2476
 
2276
- private def __fork_setup_for_debugger
2277
- fork_mode = CONFIG[:fork_mode]
2477
+ module DaemonInterceptor
2478
+ def daemon(*args)
2479
+ return super unless defined?(SESSION) && SESSION.active?
2480
+
2481
+ _, child_hook = __fork_setup_for_debugger(:child)
2482
+
2483
+ unless SESSION.remote?
2484
+ DEBUGGER__.warn "Can't debug the code after Process.daemon locally. Use the remote debugging feature."
2485
+ end
2486
+
2487
+ super.tap do
2488
+ child_hook.call
2489
+ end
2490
+ end
2491
+ end
2492
+
2493
+ private def __fork_setup_for_debugger fork_mode = nil
2494
+ fork_mode ||= CONFIG[:fork_mode]
2278
2495
 
2279
2496
  if fork_mode == :both && CONFIG[:parent_on_fork]
2280
2497
  fork_mode = :parent
@@ -2289,19 +2506,19 @@ module DEBUGGER__
2289
2506
  # Do nothing
2290
2507
  }
2291
2508
  child_hook = -> {
2292
- DEBUGGER__.warn "Detaching after fork from child process #{Process.pid}"
2509
+ DEBUGGER__.info "Detaching after fork from child process #{Process.pid}"
2293
2510
  SESSION.deactivate
2294
2511
  }
2295
2512
  when :child
2296
2513
  SESSION.before_fork false
2297
2514
 
2298
2515
  parent_hook = -> child_pid {
2299
- DEBUGGER__.warn "Detaching after fork from parent process #{Process.pid}"
2516
+ DEBUGGER__.info "Detaching after fork from parent process #{Process.pid}"
2300
2517
  SESSION.after_fork_parent
2301
2518
  SESSION.deactivate
2302
2519
  }
2303
2520
  child_hook = -> {
2304
- DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2521
+ DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2305
2522
  SESSION.activate on_fork: true
2306
2523
  }
2307
2524
  when :both
@@ -2312,7 +2529,7 @@ module DEBUGGER__
2312
2529
  SESSION.after_fork_parent
2313
2530
  }
2314
2531
  child_hook = -> {
2315
- DEBUGGER__.warn "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2532
+ DEBUGGER__.info "Attaching after process #{parent_pid} fork to child process #{Process.pid}"
2316
2533
  SESSION.process_group.after_fork child: true
2317
2534
  SESSION.activate on_fork: true
2318
2535
  }
@@ -2324,7 +2541,17 @@ module DEBUGGER__
2324
2541
 
2325
2542
  module TrapInterceptor
2326
2543
  def trap sig, *command, &command_proc
2327
- case sig&.to_sym
2544
+ sym =
2545
+ case sig
2546
+ when String
2547
+ sig.to_sym
2548
+ when Integer
2549
+ Signal.signame(sig)&.to_sym
2550
+ else
2551
+ sig
2552
+ end
2553
+
2554
+ case sym
2328
2555
  when :INT, :SIGINT
2329
2556
  if defined?(SESSION) && SESSION.active? && SESSION.intercept_trap_sigint?
2330
2557
  return SESSION.save_int_trap(command.empty? ? command_proc : command.first)
@@ -2339,6 +2566,7 @@ module DEBUGGER__
2339
2566
  module ::Process
2340
2567
  class << self
2341
2568
  prepend ForkInterceptor
2569
+ prepend DaemonInterceptor
2342
2570
  end
2343
2571
  end
2344
2572
 
@@ -2374,6 +2602,7 @@ module DEBUGGER__
2374
2602
  module ::Process
2375
2603
  class << self
2376
2604
  prepend ForkInterceptor
2605
+ prepend DaemonInterceptor
2377
2606
  end
2378
2607
  end
2379
2608
  end
@@ -2390,10 +2619,17 @@ module Kernel
2390
2619
  return if !defined?(::DEBUGGER__::SESSION) || !::DEBUGGER__::SESSION.active?
2391
2620
 
2392
2621
  if pre || (do_expr = binding.local_variable_get(:do))
2393
- cmds = ['binding.break', pre, do_expr]
2622
+ cmds = ['#debugger', pre, do_expr]
2394
2623
  end
2395
2624
 
2396
- loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
2625
+ if ::DEBUGGER__::SESSION.in_subsession?
2626
+ if cmds
2627
+ commands = [*cmds[1], *cmds[2]].map{|c| c.split(';;').join("\n")}
2628
+ ::DEBUGGER__::SESSION.add_preset_commands cmds[0], commands, kick: false, continue: false
2629
+ end
2630
+ else
2631
+ loc = caller_locations(up_level, 1).first; ::DEBUGGER__.add_line_breakpoint loc.path, loc.lineno + 1, oneshot: true, command: cmds
2632
+ end
2397
2633
  self
2398
2634
  end
2399
2635