debug 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +9 -0
- data/CONTRIBUTING.md +43 -12
- data/README.md +38 -11
- data/bin/gentest +12 -4
- data/ext/debug/debug.c +5 -2
- data/lib/debug/breakpoint.rb +61 -11
- data/lib/debug/client.rb +57 -16
- data/lib/debug/color.rb +30 -20
- data/lib/debug/config.rb +6 -3
- data/lib/debug/console.rb +17 -3
- data/lib/debug/frame_info.rb +11 -16
- data/lib/debug/prelude.rb +2 -2
- data/lib/debug/server.rb +47 -77
- data/lib/debug/server_cdp.rb +605 -94
- data/lib/debug/server_dap.rb +256 -57
- data/lib/debug/session.rb +146 -54
- data/lib/debug/source_repository.rb +4 -6
- data/lib/debug/thread_client.rb +67 -48
- data/lib/debug/tracer.rb +1 -1
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +16 -8
- metadata +3 -2
data/lib/debug/server_dap.rb
CHANGED
|
@@ -1,10 +1,69 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'irb/completion'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'fileutils'
|
|
4
8
|
|
|
5
9
|
module DEBUGGER__
|
|
6
10
|
module UI_DAP
|
|
7
|
-
SHOW_PROTOCOL = ENV['RUBY_DEBUG_DAP_SHOW_PROTOCOL'] == '1'
|
|
11
|
+
SHOW_PROTOCOL = ENV['DEBUG_DAP_SHOW_PROTOCOL'] == '1' || ENV['RUBY_DEBUG_DAP_SHOW_PROTOCOL'] == '1'
|
|
12
|
+
|
|
13
|
+
def self.setup sock_path
|
|
14
|
+
dir = Dir.mktmpdir("ruby-debug-vscode-")
|
|
15
|
+
at_exit{
|
|
16
|
+
CONFIG[:skip_path] = [//] # skip all
|
|
17
|
+
FileUtils.rm_rf dir
|
|
18
|
+
}
|
|
19
|
+
Dir.chdir(dir) do
|
|
20
|
+
Dir.mkdir('.vscode')
|
|
21
|
+
open('README.rb', 'w'){|f|
|
|
22
|
+
f.puts <<~MSG
|
|
23
|
+
# Wait for starting the attaching to the Ruby process
|
|
24
|
+
# This file will be removed at the end of the debuggee process.
|
|
25
|
+
#
|
|
26
|
+
# Note that vscode-rdbg extension is needed. Please install if you don't have.
|
|
27
|
+
MSG
|
|
28
|
+
}
|
|
29
|
+
open('.vscode/launch.json', 'w'){|f|
|
|
30
|
+
f.puts JSON.pretty_generate({
|
|
31
|
+
version: '0.2.0',
|
|
32
|
+
configurations: [
|
|
33
|
+
{
|
|
34
|
+
type: "rdbg",
|
|
35
|
+
name: "Attach with rdbg",
|
|
36
|
+
request: "attach",
|
|
37
|
+
rdbgPath: File.expand_path('../../exe/rdbg', __dir__),
|
|
38
|
+
debugPort: sock_path,
|
|
39
|
+
autoAttach: true,
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
cmds = ['code', "#{dir}/", "#{dir}/README.rb"]
|
|
47
|
+
cmdline = cmds.join(' ')
|
|
48
|
+
ssh_cmdline = "code --remote ssh-remote+[SSH hostname] #{dir}/ #{dir}/README.rb"
|
|
49
|
+
|
|
50
|
+
STDERR.puts "Launching: #{cmdline}"
|
|
51
|
+
env = ENV.delete_if{|k, h| /RUBY/ =~ k}.to_h
|
|
52
|
+
|
|
53
|
+
unless system(env, *cmds)
|
|
54
|
+
DEBUGGER__.warn <<~MESSAGE
|
|
55
|
+
Can not invoke the command.
|
|
56
|
+
Use the command-line on your terminal (with modification if you need).
|
|
57
|
+
|
|
58
|
+
#{cmdline}
|
|
59
|
+
|
|
60
|
+
If your application is running on a SSH remote host, please try:
|
|
61
|
+
|
|
62
|
+
#{ssh_cmdline}
|
|
63
|
+
|
|
64
|
+
MESSAGE
|
|
65
|
+
end
|
|
66
|
+
end
|
|
8
67
|
|
|
9
68
|
def show_protocol dir, msg
|
|
10
69
|
if SHOW_PROTOCOL
|
|
@@ -44,11 +103,12 @@ module DEBUGGER__
|
|
|
44
103
|
],
|
|
45
104
|
supportsExceptionFilterOptions: true,
|
|
46
105
|
supportsStepBack: true,
|
|
106
|
+
supportsEvaluateForHovers: true,
|
|
107
|
+
supportsCompletionsRequest: true,
|
|
47
108
|
|
|
48
109
|
## Will be supported
|
|
49
110
|
# supportsExceptionOptions: true,
|
|
50
111
|
# supportsHitConditionalBreakpoints:
|
|
51
|
-
# supportsEvaluateForHovers:
|
|
52
112
|
# supportsSetVariable: true,
|
|
53
113
|
# supportSuspendDebuggee:
|
|
54
114
|
# supportsLogPoints:
|
|
@@ -58,7 +118,6 @@ module DEBUGGER__
|
|
|
58
118
|
|
|
59
119
|
## Possible?
|
|
60
120
|
# supportsRestartFrame:
|
|
61
|
-
# supportsCompletionsRequest:
|
|
62
121
|
# completionTriggerCharacters:
|
|
63
122
|
# supportsModulesRequest:
|
|
64
123
|
# additionalModuleColumns:
|
|
@@ -87,22 +146,22 @@ module DEBUGGER__
|
|
|
87
146
|
kw[:seq] = @seq += 1
|
|
88
147
|
str = JSON.dump(kw)
|
|
89
148
|
show_protocol '<', str
|
|
90
|
-
@sock.write "Content-Length: #{str.
|
|
149
|
+
@sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
|
|
91
150
|
end
|
|
92
151
|
|
|
93
|
-
def send_response req, success: true, **kw
|
|
152
|
+
def send_response req, success: true, message: nil, **kw
|
|
94
153
|
if kw.empty?
|
|
95
154
|
send type: 'response',
|
|
96
155
|
command: req['command'],
|
|
97
156
|
request_seq: req['seq'],
|
|
98
157
|
success: success,
|
|
99
|
-
message: success ? 'Success' : 'Failed'
|
|
158
|
+
message: message || (success ? 'Success' : 'Failed')
|
|
100
159
|
else
|
|
101
160
|
send type: 'response',
|
|
102
161
|
command: req['command'],
|
|
103
162
|
request_seq: req['seq'],
|
|
104
163
|
success: success,
|
|
105
|
-
message: success ? 'Success' : 'Failed',
|
|
164
|
+
message: message || (success ? 'Success' : 'Failed'),
|
|
106
165
|
body: kw
|
|
107
166
|
end
|
|
108
167
|
end
|
|
@@ -119,29 +178,27 @@ module DEBUGGER__
|
|
|
119
178
|
end
|
|
120
179
|
|
|
121
180
|
def recv_request
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
@
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
raise "unrecognized line: #{l} (#{l.size} bytes)"
|
|
140
|
-
end
|
|
181
|
+
r = IO.select([@sock])
|
|
182
|
+
|
|
183
|
+
@session.process_group.sync do
|
|
184
|
+
raise RetryBecauseCantRead unless IO.select([@sock], nil, nil, 0)
|
|
185
|
+
|
|
186
|
+
case header = @sock.gets
|
|
187
|
+
when /Content-Length: (\d+)/
|
|
188
|
+
b = @sock.read(2)
|
|
189
|
+
raise b.inspect unless b == "\r\n"
|
|
190
|
+
|
|
191
|
+
l = @sock.read(s = $1.to_i)
|
|
192
|
+
show_protocol :>, l
|
|
193
|
+
JSON.load(l)
|
|
194
|
+
when nil
|
|
195
|
+
nil
|
|
196
|
+
else
|
|
197
|
+
raise "unrecognized line: #{l} (#{l.size} bytes)"
|
|
141
198
|
end
|
|
142
|
-
rescue RetryBecauseCantRead
|
|
143
|
-
retry
|
|
144
199
|
end
|
|
200
|
+
rescue RetryBecauseCantRead
|
|
201
|
+
retry
|
|
145
202
|
end
|
|
146
203
|
|
|
147
204
|
def process
|
|
@@ -222,14 +279,35 @@ module DEBUGGER__
|
|
|
222
279
|
@q_msg << 'c'
|
|
223
280
|
send_response req, allThreadsContinued: true
|
|
224
281
|
when 'next'
|
|
225
|
-
|
|
226
|
-
|
|
282
|
+
begin
|
|
283
|
+
@session.check_postmortem
|
|
284
|
+
@q_msg << 'n'
|
|
285
|
+
send_response req
|
|
286
|
+
rescue PostmortemError
|
|
287
|
+
send_response req,
|
|
288
|
+
success: false, message: 'postmortem mode',
|
|
289
|
+
result: "'Next' is not supported while postmortem mode"
|
|
290
|
+
end
|
|
227
291
|
when 'stepIn'
|
|
228
|
-
|
|
229
|
-
|
|
292
|
+
begin
|
|
293
|
+
@session.check_postmortem
|
|
294
|
+
@q_msg << 's'
|
|
295
|
+
send_response req
|
|
296
|
+
rescue PostmortemError
|
|
297
|
+
send_response req,
|
|
298
|
+
success: false, message: 'postmortem mode',
|
|
299
|
+
result: "'stepIn' is not supported while postmortem mode"
|
|
300
|
+
end
|
|
230
301
|
when 'stepOut'
|
|
231
|
-
|
|
232
|
-
|
|
302
|
+
begin
|
|
303
|
+
@session.check_postmortem
|
|
304
|
+
@q_msg << 'fin'
|
|
305
|
+
send_response req
|
|
306
|
+
rescue PostmortemError
|
|
307
|
+
send_response req,
|
|
308
|
+
success: false, message: 'postmortem mode',
|
|
309
|
+
result: "'stepOut' is not supported while postmortem mode"
|
|
310
|
+
end
|
|
233
311
|
when 'terminate'
|
|
234
312
|
send_response req
|
|
235
313
|
exit
|
|
@@ -255,7 +333,8 @@ module DEBUGGER__
|
|
|
255
333
|
'scopes',
|
|
256
334
|
'variables',
|
|
257
335
|
'evaluate',
|
|
258
|
-
'source'
|
|
336
|
+
'source',
|
|
337
|
+
'completions'
|
|
259
338
|
@q_msg << req
|
|
260
339
|
|
|
261
340
|
else
|
|
@@ -361,7 +440,6 @@ module DEBUGGER__
|
|
|
361
440
|
case ref[0]
|
|
362
441
|
when :globals
|
|
363
442
|
vars = global_variables.map do |name|
|
|
364
|
-
File.write('/tmp/x', "#{name}\n")
|
|
365
443
|
gv = 'Not implemented yet...'
|
|
366
444
|
{
|
|
367
445
|
name: name,
|
|
@@ -402,11 +480,13 @@ module DEBUGGER__
|
|
|
402
480
|
end
|
|
403
481
|
when 'evaluate'
|
|
404
482
|
frame_id = req.dig('arguments', 'frameId')
|
|
483
|
+
context = req.dig('arguments', 'context')
|
|
484
|
+
|
|
405
485
|
if @frame_map[frame_id]
|
|
406
486
|
tid, fid = @frame_map[frame_id]
|
|
407
487
|
expr = req.dig('arguments', 'expression')
|
|
408
488
|
if tc = find_waiting_tc(tid)
|
|
409
|
-
tc << [:dap, :evaluate, req, fid, expr]
|
|
489
|
+
tc << [:dap, :evaluate, req, fid, expr, context]
|
|
410
490
|
else
|
|
411
491
|
fail_response req
|
|
412
492
|
end
|
|
@@ -421,6 +501,21 @@ module DEBUGGER__
|
|
|
421
501
|
fail_response req, message: 'not found...'
|
|
422
502
|
end
|
|
423
503
|
return :retry
|
|
504
|
+
|
|
505
|
+
when 'completions'
|
|
506
|
+
frame_id = req.dig('arguments', 'frameId')
|
|
507
|
+
tid, fid = @frame_map[frame_id]
|
|
508
|
+
|
|
509
|
+
if tc = find_waiting_tc(tid)
|
|
510
|
+
text = req.dig('arguments', 'text')
|
|
511
|
+
line = req.dig('arguments', 'line')
|
|
512
|
+
if col = req.dig('arguments', 'column')
|
|
513
|
+
text = text.split(/\n/)[line.to_i - 1][0...(col.to_i - 1)]
|
|
514
|
+
end
|
|
515
|
+
tc << [:dap, :completions, req, fid, text]
|
|
516
|
+
else
|
|
517
|
+
fail_response req
|
|
518
|
+
end
|
|
424
519
|
else
|
|
425
520
|
raise "Unknown DAP request: #{req.inspect}"
|
|
426
521
|
end
|
|
@@ -458,8 +553,15 @@ module DEBUGGER__
|
|
|
458
553
|
register_vars result[:variables], tid
|
|
459
554
|
@ui.respond req, result
|
|
460
555
|
when :evaluate
|
|
461
|
-
|
|
462
|
-
|
|
556
|
+
message = result.delete :message
|
|
557
|
+
if message
|
|
558
|
+
@ui.respond req, success: false, message: message
|
|
559
|
+
else
|
|
560
|
+
tid = result.delete :tid
|
|
561
|
+
register_var result, tid
|
|
562
|
+
@ui.respond req, result
|
|
563
|
+
end
|
|
564
|
+
when :completions
|
|
463
565
|
@ui.respond req, result
|
|
464
566
|
else
|
|
465
567
|
raise "unsupported: #{args.inspect}"
|
|
@@ -491,7 +593,7 @@ module DEBUGGER__
|
|
|
491
593
|
case type
|
|
492
594
|
when :backtrace
|
|
493
595
|
event! :dap_result, :backtrace, req, {
|
|
494
|
-
stackFrames: @target_frames.map
|
|
596
|
+
stackFrames: @target_frames.map{|frame|
|
|
495
597
|
path = frame.realpath || frame.path
|
|
496
598
|
ref = frame.file_lines unless path && File.exist?(path)
|
|
497
599
|
|
|
@@ -544,8 +646,9 @@ module DEBUGGER__
|
|
|
544
646
|
v = b.local_variable_get(name)
|
|
545
647
|
variable(name, v)
|
|
546
648
|
}
|
|
547
|
-
|
|
548
|
-
|
|
649
|
+
special_local_variables frame do |name, val|
|
|
650
|
+
vars.unshift variable(name, val)
|
|
651
|
+
end
|
|
549
652
|
vars.unshift variable('%self', b.receiver)
|
|
550
653
|
elsif lvars = frame.local_variables
|
|
551
654
|
vars = lvars.map{|var, val|
|
|
@@ -553,8 +656,9 @@ module DEBUGGER__
|
|
|
553
656
|
}
|
|
554
657
|
else
|
|
555
658
|
vars = [variable('%self', frame.self)]
|
|
556
|
-
|
|
557
|
-
|
|
659
|
+
special_local_variables frame do |name, val|
|
|
660
|
+
vars.push variable(name, val)
|
|
661
|
+
end
|
|
558
662
|
end
|
|
559
663
|
event! :dap_result, :scope, req, variables: vars, tid: self.id
|
|
560
664
|
|
|
@@ -575,7 +679,7 @@ module DEBUGGER__
|
|
|
575
679
|
case obj
|
|
576
680
|
when Hash
|
|
577
681
|
vars = obj.map{|k, v|
|
|
578
|
-
variable(DEBUGGER__.
|
|
682
|
+
variable(DEBUGGER__.safe_inspect(k), v,)
|
|
579
683
|
}
|
|
580
684
|
when Struct
|
|
581
685
|
vars = obj.members.map{|m|
|
|
@@ -607,32 +711,127 @@ module DEBUGGER__
|
|
|
607
711
|
event! :dap_result, :variable, req, variables: (vars || []), tid: self.id
|
|
608
712
|
|
|
609
713
|
when :evaluate
|
|
610
|
-
fid, expr = args
|
|
714
|
+
fid, expr, context = args
|
|
611
715
|
frame = @target_frames[fid]
|
|
716
|
+
message = nil
|
|
612
717
|
|
|
613
718
|
if frame && (b = frame.binding)
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
719
|
+
b = b.dup
|
|
720
|
+
special_local_variables current_frame do |name, var|
|
|
721
|
+
b.local_variable_set(name, var) if /\%/ !~ name
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
case context
|
|
725
|
+
when 'repl', 'watch'
|
|
726
|
+
begin
|
|
727
|
+
result = b.eval(expr.to_s, '(DEBUG CONSOLE)')
|
|
728
|
+
rescue Exception => e
|
|
729
|
+
result = e
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
when 'hover'
|
|
733
|
+
case expr
|
|
734
|
+
when /\A\@\S/
|
|
735
|
+
begin
|
|
736
|
+
(r = b.receiver).instance_variable_defined?(expr) or raise(NameError)
|
|
737
|
+
result = r.instance_variable_get(expr)
|
|
738
|
+
rescue NameError
|
|
739
|
+
message = "Error: Not defined instance variable: #{expr.inspect}"
|
|
740
|
+
end
|
|
741
|
+
when /\A\$\S/
|
|
742
|
+
global_variables.each{|gvar|
|
|
743
|
+
if gvar.to_s == expr
|
|
744
|
+
result = eval(gvar.to_s)
|
|
745
|
+
break false
|
|
746
|
+
end
|
|
747
|
+
} and (message = "Error: Not defined global variable: #{expr.inspect}")
|
|
748
|
+
when /\A[A-Z]/
|
|
749
|
+
unless result = search_const(b, expr)
|
|
750
|
+
message = "Error: Not defined constants: #{expr.inspect}"
|
|
751
|
+
end
|
|
752
|
+
else
|
|
753
|
+
begin
|
|
754
|
+
# try to check local variables
|
|
755
|
+
b.local_variable_defined?(expr) or raise NameError
|
|
756
|
+
result = b.local_variable_get(expr)
|
|
757
|
+
rescue NameError
|
|
758
|
+
# try to check method
|
|
759
|
+
if b.receiver.respond_to? expr, include_all: true
|
|
760
|
+
result = b.receiver.method(expr)
|
|
761
|
+
else
|
|
762
|
+
message = "Error: Can not evaluate: #{expr.inspect}"
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
end
|
|
766
|
+
else
|
|
767
|
+
message = "Error: unknown context: #{context}"
|
|
618
768
|
end
|
|
619
769
|
else
|
|
620
|
-
result = '
|
|
770
|
+
result = 'Error: Can not evaluate on this frame'
|
|
621
771
|
end
|
|
622
|
-
|
|
772
|
+
|
|
773
|
+
event! :dap_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result)
|
|
774
|
+
|
|
775
|
+
when :completions
|
|
776
|
+
fid, text = args
|
|
777
|
+
frame = @target_frames[fid]
|
|
778
|
+
|
|
779
|
+
if (b = frame&.binding) && word = text&.split(/[\s\{]/)&.last
|
|
780
|
+
words = IRB::InputCompletor::retrieve_completion_data(word, bind: b).compact
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
event! :dap_result, :completions, req, targets: (words || []).map{|phrase|
|
|
784
|
+
if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase
|
|
785
|
+
w = $1
|
|
786
|
+
else
|
|
787
|
+
w = phrase
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
begin
|
|
791
|
+
if b&.local_variable_defined?(w)
|
|
792
|
+
v = b.local_variable_get(w)
|
|
793
|
+
phrase += " (variable:#{DEBUGGER__.safe_inspect(v)})"
|
|
794
|
+
end
|
|
795
|
+
rescue NameError
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
{
|
|
799
|
+
label: phrase,
|
|
800
|
+
text: w,
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
623
804
|
else
|
|
624
805
|
raise "Unknown req: #{args.inspect}"
|
|
625
806
|
end
|
|
626
807
|
end
|
|
627
808
|
|
|
809
|
+
def search_const b, expr
|
|
810
|
+
cs = expr.split('::')
|
|
811
|
+
[Object, *b.eval('Module.nesting')].reverse_each{|mod|
|
|
812
|
+
if cs.all?{|c|
|
|
813
|
+
if mod.const_defined?(c)
|
|
814
|
+
mod = mod.const_get(c)
|
|
815
|
+
else
|
|
816
|
+
false
|
|
817
|
+
end
|
|
818
|
+
}
|
|
819
|
+
# if-body
|
|
820
|
+
return mod
|
|
821
|
+
end
|
|
822
|
+
}
|
|
823
|
+
false
|
|
824
|
+
end
|
|
825
|
+
|
|
628
826
|
def evaluate_result r
|
|
629
827
|
v = variable nil, r
|
|
630
|
-
v.delete
|
|
631
|
-
v
|
|
828
|
+
v.delete :name
|
|
829
|
+
v.delete :value
|
|
830
|
+
v[:result] = DEBUGGER__.safe_inspect(r)
|
|
632
831
|
v
|
|
633
832
|
end
|
|
634
833
|
|
|
635
|
-
def variable_ name, obj, indexedVariables: 0, namedVariables: 0
|
|
834
|
+
def variable_ name, obj, indexedVariables: 0, namedVariables: 0
|
|
636
835
|
if indexedVariables > 0 || namedVariables > 0
|
|
637
836
|
vid = @var_map.size + 1
|
|
638
837
|
@var_map[vid] = obj
|
|
@@ -643,7 +842,7 @@ module DEBUGGER__
|
|
|
643
842
|
ivnum = obj.instance_variables.size
|
|
644
843
|
|
|
645
844
|
{ name: name,
|
|
646
|
-
value: DEBUGGER__.
|
|
845
|
+
value: DEBUGGER__.safe_inspect(obj),
|
|
647
846
|
type: obj.class.name || obj.class.to_s,
|
|
648
847
|
variablesReference: vid,
|
|
649
848
|
indexedVariables: indexedVariables,
|
|
@@ -658,7 +857,7 @@ module DEBUGGER__
|
|
|
658
857
|
when Hash
|
|
659
858
|
variable_ name, obj, namedVariables: obj.size
|
|
660
859
|
when String
|
|
661
|
-
variable_ name, obj,
|
|
860
|
+
variable_ name, obj, namedVariables: 3 # #to_str, #length, #encoding
|
|
662
861
|
when Struct
|
|
663
862
|
variable_ name, obj, namedVariables: obj.size
|
|
664
863
|
when Class, Module
|