debug 1.4.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +195 -3
- data/Gemfile +1 -0
- data/README.md +55 -32
- data/Rakefile +28 -10
- data/debug.gemspec +7 -5
- data/exe/rdbg +7 -3
- data/ext/debug/debug.c +76 -14
- data/ext/debug/extconf.rb +22 -0
- data/lib/debug/breakpoint.rb +90 -66
- data/lib/debug/client.rb +21 -5
- data/lib/debug/config.rb +55 -24
- data/lib/debug/console.rb +43 -16
- data/lib/debug/frame_info.rb +32 -26
- data/lib/debug/local.rb +1 -1
- data/lib/debug/server.rb +105 -40
- data/lib/debug/server_cdp.rb +373 -152
- data/lib/debug/server_dap.rb +273 -170
- data/lib/debug/session.rb +450 -236
- data/lib/debug/source_repository.rb +104 -51
- data/lib/debug/thread_client.rb +252 -103
- data/lib/debug/tracer.rb +7 -12
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +34 -14
- metadata +6 -16
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- data/.github/ISSUE_TEMPLATE/custom.md +0 -10
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- data/.github/pull_request_template.md +0 -9
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -30
- data/bin/setup +0 -8
- data/lib/debug/bp.vim +0 -68
data/lib/debug/session.rb
CHANGED
@@ -7,7 +7,7 @@ return if ENV['RUBY_DEBUG_ENABLE'] == '0'
|
|
7
7
|
if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
8
8
|
trace_var(:$0) do |file|
|
9
9
|
trace_var(:$0, nil)
|
10
|
-
if /-r (#{__dir__}\S+)/ =~ ENV['RUBYOPT']
|
10
|
+
if /-r (#{Regexp.escape(__dir__)}\S+)/ =~ ENV['RUBYOPT']
|
11
11
|
lib = $1
|
12
12
|
$LOADED_FEATURES.delete_if{|path| path.start_with?(__dir__)}
|
13
13
|
ENV['RUBY_DEBUG_INITIAL_SUSPEND_PATH'] = file
|
@@ -19,6 +19,14 @@ if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
|
|
19
19
|
return
|
20
20
|
end
|
21
21
|
|
22
|
+
# restore RUBYOPT
|
23
|
+
if (added_opt = ENV['RUBY_DEBUG_ADDED_RUBYOPT']) &&
|
24
|
+
(rubyopt = ENV['RUBYOPT']) &&
|
25
|
+
rubyopt.start_with?(added_opt)
|
26
|
+
ENV['RUBYOPT'] = rubyopt.delete_prefix(rubyopt)
|
27
|
+
ENV['RUBY_DEBUG_ADDED_RUBYOPT'] = nil
|
28
|
+
end
|
29
|
+
|
22
30
|
require_relative 'frame_info'
|
23
31
|
require_relative 'config'
|
24
32
|
require_relative 'thread_client'
|
@@ -31,7 +39,8 @@ $LOADED_FEATURES << 'debug.rb'
|
|
31
39
|
$LOADED_FEATURES << File.expand_path(File.join(__dir__, '..', 'debug.rb'))
|
32
40
|
require 'debug' # invalidate the $LOADED_FEATURE cache
|
33
41
|
|
34
|
-
require 'json' if ENV['
|
42
|
+
require 'json' if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
|
43
|
+
require 'pp'
|
35
44
|
|
36
45
|
class RubyVM::InstructionSequence
|
37
46
|
def traceable_lines_norec lines
|
@@ -56,23 +65,22 @@ class RubyVM::InstructionSequence
|
|
56
65
|
|
57
66
|
def type
|
58
67
|
self.to_a[9]
|
59
|
-
end
|
68
|
+
end unless method_defined?(:type)
|
60
69
|
|
61
|
-
def
|
62
|
-
self.to_a
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
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)
|
68
76
|
|
69
77
|
def last_line
|
70
78
|
self.to_a[4][:code_location][2]
|
71
|
-
end
|
79
|
+
end unless method_defined?(:last_line)
|
72
80
|
|
73
81
|
def first_line
|
74
82
|
self.to_a[4][:code_location][0]
|
75
|
-
end
|
83
|
+
end unless method_defined?(:first_line)
|
76
84
|
end
|
77
85
|
|
78
86
|
module DEBUGGER__
|
@@ -82,8 +90,10 @@ module DEBUGGER__
|
|
82
90
|
class Session
|
83
91
|
attr_reader :intercepted_sigint_cmd, :process_group
|
84
92
|
|
85
|
-
|
86
|
-
|
93
|
+
include Color
|
94
|
+
|
95
|
+
def initialize
|
96
|
+
@ui = nil
|
87
97
|
@sr = SourceRepository.new
|
88
98
|
@bps = {} # bp.key => bp
|
89
99
|
# [file, line] => LineBreakpoint
|
@@ -104,13 +114,13 @@ module DEBUGGER__
|
|
104
114
|
@intercept_trap_sigint = false
|
105
115
|
@intercepted_sigint_cmd = 'DEFAULT'
|
106
116
|
@process_group = ProcessGroup.new
|
107
|
-
@
|
117
|
+
@subsession_stack = []
|
108
118
|
|
109
119
|
@frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
|
110
120
|
@var_map = {1 => [:globals], } # {id => ...} for DAP
|
111
121
|
@src_map = {} # {id => src}
|
112
122
|
|
113
|
-
@
|
123
|
+
@scr_id_map = {} # for CDP
|
114
124
|
@obj_map = {} # { object_id => ... } for CDP
|
115
125
|
|
116
126
|
@tp_thread_begin = nil
|
@@ -120,9 +130,6 @@ module DEBUGGER__
|
|
120
130
|
@tp_load_script.enable
|
121
131
|
|
122
132
|
@thread_stopper = thread_stopper
|
123
|
-
|
124
|
-
activate
|
125
|
-
|
126
133
|
self.postmortem = CONFIG[:postmortem]
|
127
134
|
end
|
128
135
|
|
@@ -134,15 +141,13 @@ module DEBUGGER__
|
|
134
141
|
@bps.has_key? [file, line]
|
135
142
|
end
|
136
143
|
|
137
|
-
def activate on_fork: false
|
144
|
+
def activate ui = nil, on_fork: false
|
145
|
+
@ui = ui if ui
|
146
|
+
|
138
147
|
@tp_thread_begin&.disable
|
139
148
|
@tp_thread_begin = nil
|
140
149
|
|
141
|
-
|
142
|
-
@ui.activate self, on_fork: true
|
143
|
-
else
|
144
|
-
@ui.activate self, on_fork: false
|
145
|
-
end
|
150
|
+
@ui.activate self, on_fork: on_fork
|
146
151
|
|
147
152
|
q = Queue.new
|
148
153
|
@session_server = Thread.new do
|
@@ -201,9 +206,14 @@ module DEBUGGER__
|
|
201
206
|
deactivate
|
202
207
|
end
|
203
208
|
|
209
|
+
def request_tc(req)
|
210
|
+
@tc << req
|
211
|
+
end
|
212
|
+
|
204
213
|
def process_event evt
|
205
214
|
# variable `@internal_info` is only used for test
|
206
|
-
tc, output, ev, @internal_info, *ev_args = evt
|
215
|
+
@tc, output, ev, @internal_info, *ev_args = evt
|
216
|
+
|
207
217
|
output.each{|str| @ui.puts str} if ev != :suspend
|
208
218
|
|
209
219
|
case ev
|
@@ -215,20 +225,19 @@ module DEBUGGER__
|
|
215
225
|
q << true
|
216
226
|
|
217
227
|
when :init
|
218
|
-
|
219
|
-
|
228
|
+
enter_subsession
|
229
|
+
wait_command_loop
|
220
230
|
when :load
|
221
231
|
iseq, src = ev_args
|
222
232
|
on_load iseq, src
|
223
|
-
|
224
|
-
tc << :continue
|
233
|
+
request_tc :continue
|
225
234
|
|
226
235
|
when :trace
|
227
236
|
trace_id, msg = ev_args
|
228
237
|
if t = @tracers.values.find{|t| t.object_id == trace_id}
|
229
238
|
t.puts msg
|
230
239
|
end
|
231
|
-
|
240
|
+
request_tc :continue
|
232
241
|
|
233
242
|
when :suspend
|
234
243
|
enter_subsession if ev_args.first != :replay
|
@@ -238,26 +247,25 @@ module DEBUGGER__
|
|
238
247
|
when :breakpoint
|
239
248
|
bp, i = bp_index ev_args[1]
|
240
249
|
clean_bps unless bp
|
241
|
-
@ui.event :suspend_bp, i, bp, tc.id
|
250
|
+
@ui.event :suspend_bp, i, bp, @tc.id
|
242
251
|
when :trap
|
243
|
-
@ui.event :suspend_trap, sig = ev_args[1], tc.id
|
252
|
+
@ui.event :suspend_trap, sig = ev_args[1], @tc.id
|
244
253
|
|
245
254
|
if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
|
246
255
|
@ui.puts "#{@intercepted_sigint_cmd.inspect} is registered as SIGINT handler."
|
247
256
|
@ui.puts "`sigint` command execute it."
|
248
257
|
end
|
249
258
|
else
|
250
|
-
@ui.event :suspended, tc.id
|
259
|
+
@ui.event :suspended, @tc.id
|
251
260
|
end
|
252
261
|
|
253
262
|
if @displays.empty?
|
254
|
-
wait_command_loop
|
263
|
+
wait_command_loop
|
255
264
|
else
|
256
|
-
|
265
|
+
request_tc [:eval, :display, @displays]
|
257
266
|
end
|
258
|
-
|
259
267
|
when :result
|
260
|
-
raise "[BUG] not in subsession"
|
268
|
+
raise "[BUG] not in subsession" if @subsession_stack.empty?
|
261
269
|
|
262
270
|
case ev_args.first
|
263
271
|
when :try_display
|
@@ -286,14 +294,14 @@ module DEBUGGER__
|
|
286
294
|
# ignore
|
287
295
|
end
|
288
296
|
|
289
|
-
wait_command_loop
|
297
|
+
wait_command_loop
|
290
298
|
|
291
299
|
when :dap_result
|
292
300
|
dap_event ev_args # server.rb
|
293
|
-
wait_command_loop
|
301
|
+
wait_command_loop
|
294
302
|
when :cdp_result
|
295
303
|
cdp_event ev_args
|
296
|
-
wait_command_loop
|
304
|
+
wait_command_loop
|
297
305
|
end
|
298
306
|
end
|
299
307
|
|
@@ -326,9 +334,7 @@ module DEBUGGER__
|
|
326
334
|
"DEBUGGER__::SESSION"
|
327
335
|
end
|
328
336
|
|
329
|
-
def wait_command_loop
|
330
|
-
@tc = tc
|
331
|
-
|
337
|
+
def wait_command_loop
|
332
338
|
loop do
|
333
339
|
case wait_command
|
334
340
|
when :retry
|
@@ -369,7 +375,7 @@ module DEBUGGER__
|
|
369
375
|
@ui.puts "(rdbg:#{@preset_command.source}) #{line}"
|
370
376
|
end
|
371
377
|
else
|
372
|
-
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['
|
378
|
+
@ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
|
373
379
|
line = @ui.readline prompt
|
374
380
|
end
|
375
381
|
|
@@ -510,8 +516,8 @@ module DEBUGGER__
|
|
510
516
|
# * break and run `<command>` before stopping.
|
511
517
|
# * `b[reak] ... do: <command>`
|
512
518
|
# * break and run `<command>`, and continue.
|
513
|
-
# * `b[reak] ... path: <
|
514
|
-
# * break if the
|
519
|
+
# * `b[reak] ... path: <path>`
|
520
|
+
# * break if the path matches to `<path>`. `<path>` can be a regexp with `/regexp/`.
|
515
521
|
# * `b[reak] if: <expr>`
|
516
522
|
# * break if: `<expr>` is true at any lines.
|
517
523
|
# * Note that this feature is super slow.
|
@@ -532,32 +538,6 @@ module DEBUGGER__
|
|
532
538
|
end
|
533
539
|
end
|
534
540
|
|
535
|
-
# skip
|
536
|
-
when 'bv'
|
537
|
-
check_postmortem
|
538
|
-
require 'json'
|
539
|
-
|
540
|
-
h = Hash.new{|h, k| h[k] = []}
|
541
|
-
@bps.each_value{|bp|
|
542
|
-
if LineBreakpoint === bp
|
543
|
-
h[bp.path] << {lnum: bp.line}
|
544
|
-
end
|
545
|
-
}
|
546
|
-
if h.empty?
|
547
|
-
# TODO: clean?
|
548
|
-
else
|
549
|
-
open(".rdb_breakpoints.json", 'w'){|f| JSON.dump(h, f)}
|
550
|
-
end
|
551
|
-
|
552
|
-
vimsrc = File.join(__dir__, 'bp.vim')
|
553
|
-
system("vim -R -S #{vimsrc} #{@tc.location.path}")
|
554
|
-
|
555
|
-
if File.exist?(".rdb_breakpoints.json")
|
556
|
-
pp JSON.load(File.read(".rdb_breakpoints.json"))
|
557
|
-
end
|
558
|
-
|
559
|
-
return :retry
|
560
|
-
|
561
541
|
# * `catch <Error>`
|
562
542
|
# * Set breakpoint on raising `<Error>`.
|
563
543
|
# * `catch ... if: <expr>`
|
@@ -566,8 +546,8 @@ module DEBUGGER__
|
|
566
546
|
# * runs `<command>` before stopping.
|
567
547
|
# * `catch ... do: <command>`
|
568
548
|
# * stops and run `<command>`, and continue.
|
569
|
-
# * `catch ... path: <
|
570
|
-
# * stops if the exception is raised from a path
|
549
|
+
# * `catch ... path: <path>`
|
550
|
+
# * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
|
571
551
|
when 'catch'
|
572
552
|
check_postmortem
|
573
553
|
|
@@ -588,8 +568,8 @@ module DEBUGGER__
|
|
588
568
|
# * runs `<command>` before stopping.
|
589
569
|
# * `watch ... do: <command>`
|
590
570
|
# * stops and run `<command>`, and continue.
|
591
|
-
# * `watch ... path: <
|
592
|
-
# * stops if the
|
571
|
+
# * `watch ... path: <path>`
|
572
|
+
# * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
|
593
573
|
when 'wat', 'watch'
|
594
574
|
check_postmortem
|
595
575
|
|
@@ -635,15 +615,15 @@ module DEBUGGER__
|
|
635
615
|
when 'bt', 'backtrace'
|
636
616
|
case arg
|
637
617
|
when /\A(\d+)\z/
|
638
|
-
|
618
|
+
request_tc [:show, :backtrace, arg.to_i, nil]
|
639
619
|
when /\A\/(.*)\/\z/
|
640
620
|
pattern = $1
|
641
|
-
|
621
|
+
request_tc [:show, :backtrace, nil, Regexp.compile(pattern)]
|
642
622
|
when /\A(\d+)\s+\/(.*)\/\z/
|
643
623
|
max, pattern = $1, $2
|
644
|
-
|
624
|
+
request_tc [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
|
645
625
|
else
|
646
|
-
|
626
|
+
request_tc [:show, :backtrace, nil, nil]
|
647
627
|
end
|
648
628
|
|
649
629
|
# * `l[ist]`
|
@@ -656,13 +636,13 @@ module DEBUGGER__
|
|
656
636
|
when 'l', 'list'
|
657
637
|
case arg ? arg.strip : nil
|
658
638
|
when /\A(\d+)\z/
|
659
|
-
|
639
|
+
request_tc [:show, :list, {start_line: arg.to_i - 1}]
|
660
640
|
when /\A-\z/
|
661
|
-
|
641
|
+
request_tc [:show, :list, {dir: -1}]
|
662
642
|
when /\A(\d+)-(\d+)\z/
|
663
|
-
|
643
|
+
request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
|
664
644
|
when nil
|
665
|
-
|
645
|
+
request_tc [:show, :list]
|
666
646
|
else
|
667
647
|
@ui.puts "Can not handle list argument: #{arg}"
|
668
648
|
return :retry
|
@@ -686,7 +666,7 @@ module DEBUGGER__
|
|
686
666
|
return :retry
|
687
667
|
end
|
688
668
|
|
689
|
-
|
669
|
+
request_tc [:show, :edit, arg]
|
690
670
|
|
691
671
|
# * `i[nfo]`
|
692
672
|
# * Show information about current frame (local/instance variables and defined constants).
|
@@ -699,8 +679,8 @@ module DEBUGGER__
|
|
699
679
|
# * Show information about accessible constants except toplevel constants.
|
700
680
|
# * `i[nfo] g[lobal[s]]`
|
701
681
|
# * Show information about global variables
|
702
|
-
# * `i[nfo] ...
|
703
|
-
# * Filter the output with
|
682
|
+
# * `i[nfo] ... /regexp/`
|
683
|
+
# * Filter the output with `/regexp/`.
|
704
684
|
# * `i[nfo] th[read[s]]`
|
705
685
|
# * Show all threads (same as `th[read]`).
|
706
686
|
when 'i', 'info'
|
@@ -713,15 +693,15 @@ module DEBUGGER__
|
|
713
693
|
|
714
694
|
case sub
|
715
695
|
when nil
|
716
|
-
|
696
|
+
request_tc [:show, :default, pat] # something useful
|
717
697
|
when 'l', /^locals?/
|
718
|
-
|
698
|
+
request_tc [:show, :locals, pat]
|
719
699
|
when 'i', /^ivars?/i, /^instance[_ ]variables?/i
|
720
|
-
|
700
|
+
request_tc [:show, :ivars, pat]
|
721
701
|
when 'c', /^consts?/i, /^constants?/i
|
722
|
-
|
702
|
+
request_tc [:show, :consts, pat]
|
723
703
|
when 'g', /^globals?/i, /^global[_ ]variables?/i
|
724
|
-
|
704
|
+
request_tc [:show, :globals, pat]
|
725
705
|
when 'th', /threads?/
|
726
706
|
thread_list
|
727
707
|
return :retry
|
@@ -737,7 +717,7 @@ module DEBUGGER__
|
|
737
717
|
# * Show you available methods and instance variables of the given object.
|
738
718
|
# * If the object is a class/module, it also lists its constants.
|
739
719
|
when 'outline', 'o', 'ls'
|
740
|
-
|
720
|
+
request_tc [:show, :outline, arg]
|
741
721
|
|
742
722
|
# * `display`
|
743
723
|
# * Show display setting.
|
@@ -746,9 +726,9 @@ module DEBUGGER__
|
|
746
726
|
when 'display'
|
747
727
|
if arg && !arg.empty?
|
748
728
|
@displays << arg
|
749
|
-
|
729
|
+
request_tc [:eval, :try_display, @displays]
|
750
730
|
else
|
751
|
-
|
731
|
+
request_tc [:eval, :display, @displays]
|
752
732
|
end
|
753
733
|
|
754
734
|
# * `undisplay`
|
@@ -761,7 +741,7 @@ module DEBUGGER__
|
|
761
741
|
if @displays[n = $1.to_i]
|
762
742
|
@displays.delete_at n
|
763
743
|
end
|
764
|
-
|
744
|
+
request_tc [:eval, :display, @displays]
|
765
745
|
when nil
|
766
746
|
if ask "clear all?", 'N'
|
767
747
|
@displays.clear
|
@@ -776,29 +756,29 @@ module DEBUGGER__
|
|
776
756
|
# * `f[rame] <framenum>`
|
777
757
|
# * Specify a current frame. Evaluation are run on specified frame.
|
778
758
|
when 'frame', 'f'
|
779
|
-
|
759
|
+
request_tc [:frame, :set, arg]
|
780
760
|
|
781
761
|
# * `up`
|
782
762
|
# * Specify the upper frame.
|
783
763
|
when 'up'
|
784
|
-
|
764
|
+
request_tc [:frame, :up]
|
785
765
|
|
786
766
|
# * `down`
|
787
767
|
# * Specify the lower frame.
|
788
768
|
when 'down'
|
789
|
-
|
769
|
+
request_tc [:frame, :down]
|
790
770
|
|
791
771
|
### Evaluate
|
792
772
|
|
793
773
|
# * `p <expr>`
|
794
774
|
# * Evaluate like `p <expr>` on the current frame.
|
795
775
|
when 'p'
|
796
|
-
|
776
|
+
request_tc [:eval, :p, arg.to_s]
|
797
777
|
|
798
778
|
# * `pp <expr>`
|
799
779
|
# * Evaluate like `pp <expr>` on the current frame.
|
800
780
|
when 'pp'
|
801
|
-
|
781
|
+
request_tc [:eval, :pp, arg.to_s]
|
802
782
|
|
803
783
|
# * `eval <expr>`
|
804
784
|
# * Evaluate `<expr>` on the current frame.
|
@@ -808,7 +788,7 @@ module DEBUGGER__
|
|
808
788
|
@ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
|
809
789
|
return :retry
|
810
790
|
else
|
811
|
-
|
791
|
+
request_tc [:eval, :call, arg]
|
812
792
|
end
|
813
793
|
|
814
794
|
# * `irb`
|
@@ -818,7 +798,7 @@ module DEBUGGER__
|
|
818
798
|
@ui.puts "not supported on the remote console."
|
819
799
|
return :retry
|
820
800
|
end
|
821
|
-
|
801
|
+
request_tc [:eval, :irb]
|
822
802
|
|
823
803
|
# don't repeat irb command
|
824
804
|
@repl_prev_line = nil
|
@@ -834,8 +814,8 @@ module DEBUGGER__
|
|
834
814
|
# * Add an exception tracer. It indicates raising exceptions.
|
835
815
|
# * `trace object <expr>`
|
836
816
|
# * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
|
837
|
-
# * `trace ...
|
838
|
-
# * Indicates only matched events to
|
817
|
+
# * `trace ... /regexp/`
|
818
|
+
# * Indicates only matched events to `/regexp/`.
|
839
819
|
# * `trace ... into: <file>`
|
840
820
|
# * Save trace information into: `<file>`.
|
841
821
|
# * `trace off <num>`
|
@@ -875,7 +855,7 @@ module DEBUGGER__
|
|
875
855
|
return :retry
|
876
856
|
|
877
857
|
when /\Aobject\s+(.+)/
|
878
|
-
|
858
|
+
request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}]
|
879
859
|
|
880
860
|
when /\Aoff\s+(\d+)\z/
|
881
861
|
if t = @tracers.values[$1.to_i]
|
@@ -913,7 +893,7 @@ module DEBUGGER__
|
|
913
893
|
when 'record'
|
914
894
|
case arg
|
915
895
|
when nil, 'on', 'off'
|
916
|
-
|
896
|
+
request_tc [:record, arg&.to_sym]
|
917
897
|
else
|
918
898
|
@ui.puts "unknown command: #{arg}"
|
919
899
|
return :retry
|
@@ -978,7 +958,7 @@ module DEBUGGER__
|
|
978
958
|
when 'open'
|
979
959
|
case arg&.downcase
|
980
960
|
when '', nil
|
981
|
-
|
961
|
+
repl_open
|
982
962
|
when 'vscode'
|
983
963
|
repl_open_vscode
|
984
964
|
when /\A(.+):(\d+)\z/
|
@@ -1003,16 +983,12 @@ module DEBUGGER__
|
|
1003
983
|
# * `h[elp] <command>`
|
1004
984
|
# * Show help for the given command.
|
1005
985
|
when 'h', 'help', '?'
|
1006
|
-
|
1007
|
-
show_help arg
|
1008
|
-
else
|
1009
|
-
@ui.puts DEBUGGER__.help
|
1010
|
-
end
|
986
|
+
show_help arg
|
1011
987
|
return :retry
|
1012
988
|
|
1013
989
|
### END
|
1014
990
|
else
|
1015
|
-
|
991
|
+
request_tc [:eval, :pp, line]
|
1016
992
|
=begin
|
1017
993
|
@repl_prev_line = nil
|
1018
994
|
@ui.puts "unknown command: #{line}"
|
@@ -1055,21 +1031,21 @@ module DEBUGGER__
|
|
1055
1031
|
repl_open_setup
|
1056
1032
|
end
|
1057
1033
|
|
1058
|
-
def
|
1059
|
-
DEBUGGER__.
|
1034
|
+
def repl_open
|
1035
|
+
DEBUGGER__.open nonstop: true
|
1060
1036
|
repl_open_setup
|
1061
1037
|
end
|
1062
1038
|
|
1063
1039
|
def repl_open_vscode
|
1064
1040
|
CONFIG[:open_frontend] = 'vscode'
|
1065
|
-
|
1041
|
+
repl_open
|
1066
1042
|
end
|
1067
1043
|
|
1068
1044
|
def step_command type, arg
|
1069
1045
|
case arg
|
1070
1046
|
when nil, /\A\d+\z/
|
1071
1047
|
if type == :in && @tc.recorder&.replaying?
|
1072
|
-
|
1048
|
+
request_tc [:step, type, arg&.to_i]
|
1073
1049
|
else
|
1074
1050
|
leave_subsession [:step, type, arg&.to_i]
|
1075
1051
|
end
|
@@ -1078,7 +1054,7 @@ module DEBUGGER__
|
|
1078
1054
|
@ui.puts "only `step #{arg}` is supported."
|
1079
1055
|
:retry
|
1080
1056
|
else
|
1081
|
-
|
1057
|
+
request_tc [:step, arg.to_sym]
|
1082
1058
|
end
|
1083
1059
|
else
|
1084
1060
|
@ui.puts "Unknown option: #{arg}"
|
@@ -1088,11 +1064,18 @@ module DEBUGGER__
|
|
1088
1064
|
|
1089
1065
|
def config_show key
|
1090
1066
|
key = key.to_sym
|
1091
|
-
|
1067
|
+
config_detail = CONFIG_SET[key]
|
1068
|
+
|
1069
|
+
if config_detail
|
1092
1070
|
v = CONFIG[key]
|
1093
|
-
kv = "#{key} = #{v.
|
1094
|
-
desc =
|
1095
|
-
|
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]
|
1096
1079
|
if line.size > SESSION.width
|
1097
1080
|
@ui.puts "\# #{desc}\n#{kv}"
|
1098
1081
|
else
|
@@ -1142,7 +1125,7 @@ module DEBUGGER__
|
|
1142
1125
|
config_set $1, $2, append: true
|
1143
1126
|
|
1144
1127
|
when /\A\s*append\s+(\w+)\s+(.+)\z/
|
1145
|
-
config_set $1, $2
|
1128
|
+
config_set $1, $2, append: true
|
1146
1129
|
|
1147
1130
|
when /\A(\w+)\z/
|
1148
1131
|
config_show $1
|
@@ -1159,16 +1142,50 @@ module DEBUGGER__
|
|
1159
1142
|
end
|
1160
1143
|
end
|
1161
1144
|
|
1162
|
-
def show_help arg
|
1163
|
-
DEBUGGER__.
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
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
|
1168
1184
|
end
|
1169
1185
|
}
|
1170
1186
|
}
|
1171
|
-
|
1187
|
+
|
1188
|
+
@ui.puts "not found: #{arg}" if arg
|
1172
1189
|
end
|
1173
1190
|
|
1174
1191
|
def ask msg, default = 'Y'
|
@@ -1234,9 +1251,12 @@ module DEBUGGER__
|
|
1234
1251
|
@repl_prev_line = nil
|
1235
1252
|
|
1236
1253
|
if @bps.has_key? bp.key
|
1237
|
-
|
1254
|
+
if bp.duplicable?
|
1255
|
+
bp
|
1256
|
+
else
|
1238
1257
|
@ui.puts "duplicated breakpoint: #{bp}"
|
1239
1258
|
bp.disable
|
1259
|
+
nil
|
1240
1260
|
end
|
1241
1261
|
else
|
1242
1262
|
@bps[bp.key] = bp
|
@@ -1272,14 +1292,20 @@ module DEBUGGER__
|
|
1272
1292
|
end
|
1273
1293
|
}
|
1274
1294
|
expr.default_proc = nil
|
1275
|
-
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
|
1276
1302
|
end
|
1277
1303
|
|
1278
1304
|
def repl_add_breakpoint arg
|
1279
1305
|
expr = parse_break arg.strip
|
1280
1306
|
cond = expr[:if]
|
1281
1307
|
cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1282
|
-
path =
|
1308
|
+
path = expr[:path]
|
1283
1309
|
|
1284
1310
|
case expr[:sig]
|
1285
1311
|
when /\A(\d+)\z/
|
@@ -1287,10 +1313,10 @@ module DEBUGGER__
|
|
1287
1313
|
when /\A(.+)[:\s+](\d+)\z/
|
1288
1314
|
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
|
1289
1315
|
when /\A(.+)([\.\#])(.+)\z/
|
1290
|
-
|
1316
|
+
request_tc [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
|
1291
1317
|
return :noretry
|
1292
1318
|
when nil
|
1293
|
-
add_check_breakpoint cond, path
|
1319
|
+
add_check_breakpoint cond, path, cmd
|
1294
1320
|
else
|
1295
1321
|
@ui.puts "Unknown breakpoint format: #{arg}"
|
1296
1322
|
@ui.puts
|
@@ -1302,7 +1328,7 @@ module DEBUGGER__
|
|
1302
1328
|
expr = parse_break arg.strip
|
1303
1329
|
cond = expr[:if]
|
1304
1330
|
cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1305
|
-
path =
|
1331
|
+
path = expr[:path]
|
1306
1332
|
|
1307
1333
|
bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
|
1308
1334
|
add_bp bp
|
@@ -1314,16 +1340,16 @@ module DEBUGGER__
|
|
1314
1340
|
cmd = ['watch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
|
1315
1341
|
path = Regexp.compile(expr[:path]) if expr[:path]
|
1316
1342
|
|
1317
|
-
|
1343
|
+
request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
|
1318
1344
|
end
|
1319
1345
|
|
1320
|
-
def add_catch_breakpoint pat
|
1321
|
-
bp = CatchBreakpoint.new(pat)
|
1346
|
+
def add_catch_breakpoint pat, cond: nil
|
1347
|
+
bp = CatchBreakpoint.new(pat, cond: cond)
|
1322
1348
|
add_bp bp
|
1323
1349
|
end
|
1324
1350
|
|
1325
|
-
def add_check_breakpoint
|
1326
|
-
bp = CheckBreakpoint.new(
|
1351
|
+
def add_check_breakpoint cond, path, command
|
1352
|
+
bp = CheckBreakpoint.new(cond: cond, path: path, command: command)
|
1327
1353
|
add_bp bp
|
1328
1354
|
end
|
1329
1355
|
|
@@ -1336,6 +1362,34 @@ module DEBUGGER__
|
|
1336
1362
|
@ui.puts e.message
|
1337
1363
|
end
|
1338
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
|
+
|
1339
1393
|
def add_iseq_breakpoint iseq, **kw
|
1340
1394
|
bp = ISeqBreakpoint.new(iseq, [:line], **kw)
|
1341
1395
|
add_bp bp
|
@@ -1503,42 +1557,66 @@ module DEBUGGER__
|
|
1503
1557
|
end
|
1504
1558
|
|
1505
1559
|
private def enter_subsession
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
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
|
1511
1569
|
end
|
1512
1570
|
|
1513
1571
|
private def leave_subsession type
|
1514
|
-
|
1515
|
-
@
|
1516
|
-
|
1517
|
-
|
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
|
1518
1584
|
@tc = nil
|
1519
|
-
@subsession = false
|
1520
1585
|
rescue Exception => e
|
1521
|
-
STDERR.puts [e, e.backtrace].
|
1586
|
+
STDERR.puts PP.pp([e, e.backtrace], ''.dup)
|
1522
1587
|
raise
|
1523
1588
|
end
|
1524
1589
|
|
1525
1590
|
def in_subsession?
|
1526
|
-
|
1591
|
+
!@subsession_stack.empty?
|
1527
1592
|
end
|
1528
1593
|
|
1529
1594
|
## event
|
1530
1595
|
|
1531
1596
|
def on_load iseq, src
|
1532
1597
|
DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"
|
1533
|
-
|
1598
|
+
|
1599
|
+
file_path, reloaded = @sr.add(iseq, src)
|
1600
|
+
@ui.event :load, file_path, reloaded
|
1534
1601
|
|
1535
1602
|
pending_line_breakpoints = @bps.find_all do |key, bp|
|
1536
1603
|
LineBreakpoint === bp && !bp.iseq
|
1537
1604
|
end
|
1538
1605
|
|
1539
1606
|
pending_line_breakpoints.each do |_key, bp|
|
1540
|
-
if bp.path
|
1541
|
-
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
|
1542
1620
|
end
|
1543
1621
|
end
|
1544
1622
|
end
|
@@ -1563,9 +1641,10 @@ module DEBUGGER__
|
|
1563
1641
|
|
1564
1642
|
def method_added tp
|
1565
1643
|
b = tp.binding
|
1644
|
+
|
1566
1645
|
if var_name = b.local_variables.first
|
1567
1646
|
mid = b.local_variable_get(var_name)
|
1568
|
-
|
1647
|
+
resolved = true
|
1569
1648
|
|
1570
1649
|
@bps.each{|k, bp|
|
1571
1650
|
case bp
|
@@ -1576,15 +1655,57 @@ module DEBUGGER__
|
|
1576
1655
|
end
|
1577
1656
|
end
|
1578
1657
|
|
1579
|
-
|
1658
|
+
resolved = false if !bp.enabled?
|
1580
1659
|
end
|
1581
1660
|
}
|
1582
|
-
|
1583
|
-
|
1661
|
+
|
1662
|
+
if resolved
|
1663
|
+
Session.deactivate_method_added_trackers
|
1584
1664
|
end
|
1665
|
+
|
1666
|
+
case mid
|
1667
|
+
when :method_added, :singleton_method_added
|
1668
|
+
Session.create_method_added_tracker(tp.self, mid)
|
1669
|
+
Session.activate_method_added_trackers unless resolved
|
1670
|
+
end
|
1671
|
+
end
|
1672
|
+
end
|
1673
|
+
|
1674
|
+
class ::Module
|
1675
|
+
undef method_added
|
1676
|
+
def method_added mid; end
|
1677
|
+
end
|
1678
|
+
|
1679
|
+
class ::BasicObject
|
1680
|
+
undef singleton_method_added
|
1681
|
+
def singleton_method_added mid; end
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
def self.create_method_added_tracker mod, method_added_id, method_accessor = :method
|
1685
|
+
m = mod.__send__(method_accessor, method_added_id)
|
1686
|
+
METHOD_ADDED_TRACKERS[m] = TracePoint.new(:call) do |tp|
|
1687
|
+
SESSION.method_added tp
|
1585
1688
|
end
|
1586
1689
|
end
|
1587
1690
|
|
1691
|
+
def self.activate_method_added_trackers
|
1692
|
+
METHOD_ADDED_TRACKERS.each do |m, tp|
|
1693
|
+
tp.enable(target: m) unless tp.enabled?
|
1694
|
+
rescue ArgumentError
|
1695
|
+
DEBUGGER__.warn "Methods defined under #{m.owner} can not track by the debugger."
|
1696
|
+
end
|
1697
|
+
end
|
1698
|
+
|
1699
|
+
def self.deactivate_method_added_trackers
|
1700
|
+
METHOD_ADDED_TRACKERS.each do |m, tp|
|
1701
|
+
tp.disable if tp.enabled?
|
1702
|
+
end
|
1703
|
+
end
|
1704
|
+
|
1705
|
+
METHOD_ADDED_TRACKERS = Hash.new
|
1706
|
+
create_method_added_tracker Module, :method_added, :instance_method
|
1707
|
+
create_method_added_tracker BasicObject, :singleton_method_added, :instance_method
|
1708
|
+
|
1588
1709
|
def width
|
1589
1710
|
@ui.width
|
1590
1711
|
end
|
@@ -1860,6 +1981,9 @@ module DEBUGGER__
|
|
1860
1981
|
puts "\nStop by #{args.first}"
|
1861
1982
|
end
|
1862
1983
|
end
|
1984
|
+
|
1985
|
+
def flush
|
1986
|
+
end
|
1863
1987
|
end
|
1864
1988
|
|
1865
1989
|
# manual configuration methods
|
@@ -1876,7 +2000,7 @@ module DEBUGGER__
|
|
1876
2000
|
# nil for -r
|
1877
2001
|
def self.require_location
|
1878
2002
|
locs = caller_locations
|
1879
|
-
dir_prefix = /#{__dir__}/
|
2003
|
+
dir_prefix = /#{Regexp.escape(__dir__)}/
|
1880
2004
|
|
1881
2005
|
locs.each do |loc|
|
1882
2006
|
case loc.absolute_path
|
@@ -1896,7 +2020,7 @@ module DEBUGGER__
|
|
1896
2020
|
|
1897
2021
|
unless defined? SESSION
|
1898
2022
|
require_relative 'local'
|
1899
|
-
initialize_session UI_LocalConsole.new
|
2023
|
+
initialize_session{ UI_LocalConsole.new }
|
1900
2024
|
end
|
1901
2025
|
|
1902
2026
|
setup_initial_suspend unless nonstop
|
@@ -1904,8 +2028,9 @@ module DEBUGGER__
|
|
1904
2028
|
|
1905
2029
|
def self.open host: nil, port: CONFIG[:port], sock_path: nil, sock_dir: nil, nonstop: false, **kw
|
1906
2030
|
CONFIG.set_config(**kw)
|
2031
|
+
require_relative 'server'
|
1907
2032
|
|
1908
|
-
if port || CONFIG[:open_frontend] == 'chrome'
|
2033
|
+
if port || CONFIG[:open_frontend] == 'chrome' || (!::Addrinfo.respond_to?(:unix))
|
1909
2034
|
open_tcp host: host, port: (port || 0), nonstop: nonstop
|
1910
2035
|
else
|
1911
2036
|
open_unix sock_path: sock_path, sock_dir: sock_dir, nonstop: nonstop
|
@@ -1919,7 +2044,7 @@ module DEBUGGER__
|
|
1919
2044
|
if defined? SESSION
|
1920
2045
|
SESSION.reset_ui UI_TcpServer.new(host: host, port: port)
|
1921
2046
|
else
|
1922
|
-
initialize_session UI_TcpServer.new(host: host, port: port)
|
2047
|
+
initialize_session{ UI_TcpServer.new(host: host, port: port) }
|
1923
2048
|
end
|
1924
2049
|
|
1925
2050
|
setup_initial_suspend unless nonstop
|
@@ -1932,7 +2057,7 @@ module DEBUGGER__
|
|
1932
2057
|
if defined? SESSION
|
1933
2058
|
SESSION.reset_ui UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
1934
2059
|
else
|
1935
|
-
initialize_session UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path)
|
2060
|
+
initialize_session{ UI_UnixDomainServer.new(sock_dir: sock_dir, sock_path: sock_path) }
|
1936
2061
|
end
|
1937
2062
|
|
1938
2063
|
setup_initial_suspend unless nonstop
|
@@ -1959,9 +2084,10 @@ module DEBUGGER__
|
|
1959
2084
|
end
|
1960
2085
|
|
1961
2086
|
class << self
|
1962
|
-
define_method :initialize_session do |
|
2087
|
+
define_method :initialize_session do |&init_ui|
|
1963
2088
|
DEBUGGER__.info "Session start (pid: #{Process.pid})"
|
1964
|
-
::DEBUGGER__.const_set(:SESSION, Session.new
|
2089
|
+
::DEBUGGER__.const_set(:SESSION, Session.new)
|
2090
|
+
SESSION.activate init_ui.call
|
1965
2091
|
load_rc
|
1966
2092
|
end
|
1967
2093
|
end
|
@@ -1993,34 +2119,53 @@ module DEBUGGER__
|
|
1993
2119
|
end
|
1994
2120
|
end
|
1995
2121
|
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
def singleton_method_added mid; end
|
2000
|
-
end
|
2122
|
+
# Inspector
|
2123
|
+
|
2124
|
+
SHORT_INSPECT_LENGTH = 40
|
2001
2125
|
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2126
|
+
class LimitedPP
|
2127
|
+
def self.pp(obj, max=80)
|
2128
|
+
out = self.new(max)
|
2129
|
+
catch out do
|
2130
|
+
PP.singleline_pp(obj, out)
|
2131
|
+
end
|
2132
|
+
out.buf
|
2007
2133
|
end
|
2008
|
-
end
|
2009
2134
|
|
2010
|
-
|
2135
|
+
attr_reader :buf
|
2011
2136
|
|
2012
|
-
|
2137
|
+
def initialize max
|
2138
|
+
@max = max
|
2139
|
+
@cnt = 0
|
2140
|
+
@buf = String.new
|
2141
|
+
end
|
2013
2142
|
|
2014
|
-
|
2015
|
-
|
2143
|
+
def <<(other)
|
2144
|
+
@buf << other
|
2145
|
+
|
2146
|
+
if @buf.size >= @max
|
2147
|
+
@buf = @buf[0..@max] + '...'
|
2148
|
+
throw self
|
2149
|
+
end
|
2150
|
+
end
|
2151
|
+
end
|
2016
2152
|
|
2017
|
-
|
2018
|
-
|
2153
|
+
def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
|
2154
|
+
if short
|
2155
|
+
LimitedPP.pp(obj, max_length)
|
2019
2156
|
else
|
2020
|
-
|
2157
|
+
obj.inspect
|
2158
|
+
end
|
2159
|
+
rescue NoMethodError => e
|
2160
|
+
klass, oid = M_CLASS.bind_call(obj), M_OBJECT_ID.bind_call(obj)
|
2161
|
+
if obj == (r = e.receiver)
|
2162
|
+
"<\##{klass.name}#{oid} does not have \#inspect>"
|
2163
|
+
else
|
2164
|
+
rklass, roid = M_CLASS.bind_call(r), M_OBJECT_ID.bind_call(r)
|
2165
|
+
"<\##{klass.name}:#{roid} contains <\##{rklass}:#{roid} and it does not have #inspect>"
|
2021
2166
|
end
|
2022
2167
|
rescue Exception => e
|
2023
|
-
|
2168
|
+
"<#inspect raises #{e.inspect}>"
|
2024
2169
|
end
|
2025
2170
|
|
2026
2171
|
def self.warn msg
|
@@ -2031,18 +2176,27 @@ module DEBUGGER__
|
|
2031
2176
|
log :INFO, msg
|
2032
2177
|
end
|
2033
2178
|
|
2034
|
-
def self.
|
2035
|
-
@logfile = STDERR unless defined? @logfile
|
2036
|
-
|
2179
|
+
def self.check_loglevel level
|
2037
2180
|
lv = LOG_LEVELS[level]
|
2038
|
-
config_lv = LOG_LEVELS[CONFIG[:log_level]
|
2181
|
+
config_lv = LOG_LEVELS[CONFIG[:log_level]]
|
2182
|
+
lv <= config_lv
|
2183
|
+
end
|
2039
2184
|
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2185
|
+
def self.debug(&b)
|
2186
|
+
if check_loglevel :DEBUG
|
2187
|
+
log :DEBUG, b.call
|
2043
2188
|
end
|
2189
|
+
end
|
2190
|
+
|
2191
|
+
def self.log level, msg
|
2192
|
+
if check_loglevel level
|
2193
|
+
@logfile = STDERR unless defined? @logfile
|
2194
|
+
|
2195
|
+
if defined? SESSION
|
2196
|
+
pi = SESSION.process_info
|
2197
|
+
process_info = pi ? "[#{pi}]" : nil
|
2198
|
+
end
|
2044
2199
|
|
2045
|
-
if lv <= config_lv
|
2046
2200
|
if level == :WARN
|
2047
2201
|
# :WARN on debugger is general information
|
2048
2202
|
@logfile.puts "DEBUGGER#{process_info}: #{msg}"
|
@@ -2062,17 +2216,69 @@ module DEBUGGER__
|
|
2062
2216
|
yield
|
2063
2217
|
end
|
2064
2218
|
|
2219
|
+
if File.identical?(__FILE__.upcase, __FILE__.downcase)
|
2220
|
+
# For case insensitive file system (like Windows)
|
2221
|
+
# Note that this check is not enough because case sensitive/insensitive is
|
2222
|
+
# depend on the file system. So this check is only roughly estimation.
|
2223
|
+
|
2224
|
+
def self.compare_path(a, b)
|
2225
|
+
a.downcase == b.downcase
|
2226
|
+
end
|
2227
|
+
else
|
2228
|
+
def self.compare_path(a, b)
|
2229
|
+
a == b
|
2230
|
+
end
|
2231
|
+
end
|
2232
|
+
|
2065
2233
|
module ForkInterceptor
|
2066
|
-
|
2067
|
-
|
2234
|
+
if Process.respond_to? :_fork
|
2235
|
+
def _fork
|
2236
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2068
2237
|
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2238
|
+
parent_hook, child_hook = __fork_setup_for_debugger
|
2239
|
+
|
2240
|
+
super.tap do |pid|
|
2241
|
+
if pid != 0
|
2242
|
+
# after fork: parent
|
2243
|
+
parent_hook.call pid
|
2244
|
+
else
|
2245
|
+
# after fork: child
|
2246
|
+
child_hook.call
|
2247
|
+
end
|
2248
|
+
end
|
2249
|
+
end
|
2250
|
+
else
|
2251
|
+
def fork(&given_block)
|
2252
|
+
return super unless defined?(SESSION) && SESSION.active?
|
2253
|
+
parent_hook, child_hook = __fork_setup_for_debugger
|
2254
|
+
|
2255
|
+
if given_block
|
2256
|
+
new_block = proc {
|
2257
|
+
# after fork: child
|
2258
|
+
child_hook.call
|
2259
|
+
given_block.call
|
2260
|
+
}
|
2261
|
+
super(&new_block).tap{|pid| parent_hook.call(pid)}
|
2072
2262
|
else
|
2073
|
-
|
2263
|
+
super.tap do |pid|
|
2264
|
+
if pid
|
2265
|
+
# after fork: parent
|
2266
|
+
parent_hook.call pid
|
2267
|
+
else
|
2268
|
+
# after fork: child
|
2269
|
+
child_hook.call
|
2270
|
+
end
|
2271
|
+
end
|
2074
2272
|
end
|
2075
2273
|
end
|
2274
|
+
end
|
2275
|
+
|
2276
|
+
private def __fork_setup_for_debugger
|
2277
|
+
fork_mode = CONFIG[:fork_mode]
|
2278
|
+
|
2279
|
+
if fork_mode == :both && CONFIG[:parent_on_fork]
|
2280
|
+
fork_mode = :parent
|
2281
|
+
end
|
2076
2282
|
|
2077
2283
|
parent_pid = Process.pid
|
2078
2284
|
|
@@ -2112,26 +2318,7 @@ module DEBUGGER__
|
|
2112
2318
|
}
|
2113
2319
|
end
|
2114
2320
|
|
2115
|
-
|
2116
|
-
new_block = proc {
|
2117
|
-
# after fork: child
|
2118
|
-
child_hook.call
|
2119
|
-
given_block.call
|
2120
|
-
}
|
2121
|
-
pid = super(&new_block)
|
2122
|
-
parent_hook.call(pid)
|
2123
|
-
pid
|
2124
|
-
else
|
2125
|
-
if pid = super
|
2126
|
-
# after fork: parent
|
2127
|
-
parent_hook.call pid
|
2128
|
-
else
|
2129
|
-
# after fork: child
|
2130
|
-
child_hook.call
|
2131
|
-
end
|
2132
|
-
|
2133
|
-
pid
|
2134
|
-
end
|
2321
|
+
return parent_hook, child_hook
|
2135
2322
|
end
|
2136
2323
|
end
|
2137
2324
|
|
@@ -2148,28 +2335,46 @@ module DEBUGGER__
|
|
2148
2335
|
end
|
2149
2336
|
end
|
2150
2337
|
|
2151
|
-
if
|
2338
|
+
if Process.respond_to? :_fork
|
2339
|
+
module ::Process
|
2340
|
+
class << self
|
2341
|
+
prepend ForkInterceptor
|
2342
|
+
end
|
2343
|
+
end
|
2344
|
+
|
2345
|
+
# trap
|
2152
2346
|
module ::Kernel
|
2153
|
-
prepend ForkInterceptor
|
2154
2347
|
prepend TrapInterceptor
|
2155
2348
|
end
|
2349
|
+
module ::Signal
|
2350
|
+
class << self
|
2351
|
+
prepend TrapInterceptor
|
2352
|
+
end
|
2353
|
+
end
|
2156
2354
|
else
|
2157
|
-
|
2158
|
-
|
2159
|
-
|
2355
|
+
if RUBY_VERSION >= '3.0.0'
|
2356
|
+
module ::Kernel
|
2357
|
+
prepend ForkInterceptor
|
2358
|
+
prepend TrapInterceptor
|
2359
|
+
end
|
2360
|
+
else
|
2361
|
+
class ::Object
|
2362
|
+
include ForkInterceptor
|
2363
|
+
include TrapInterceptor
|
2364
|
+
end
|
2160
2365
|
end
|
2161
|
-
end
|
2162
2366
|
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2367
|
+
module ::Kernel
|
2368
|
+
class << self
|
2369
|
+
prepend ForkInterceptor
|
2370
|
+
prepend TrapInterceptor
|
2371
|
+
end
|
2167
2372
|
end
|
2168
|
-
end
|
2169
2373
|
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2374
|
+
module ::Process
|
2375
|
+
class << self
|
2376
|
+
prepend ForkInterceptor
|
2377
|
+
end
|
2173
2378
|
end
|
2174
2379
|
end
|
2175
2380
|
|
@@ -2199,3 +2404,12 @@ class Binding
|
|
2199
2404
|
alias break debugger
|
2200
2405
|
alias b debugger
|
2201
2406
|
end
|
2407
|
+
|
2408
|
+
# for Ruby 2.6 compatibility
|
2409
|
+
unless method(:p).unbind.respond_to? :bind_call
|
2410
|
+
class UnboundMethod
|
2411
|
+
def bind_call(obj, *args)
|
2412
|
+
self.bind(obj).call(*args)
|
2413
|
+
end
|
2414
|
+
end
|
2415
|
+
end
|