debug 1.3.4 → 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 +39 -6
- data/README.md +34 -7
- data/bin/gentest +12 -4
- data/ext/debug/debug.c +4 -1
- data/lib/debug/breakpoint.rb +61 -11
- data/lib/debug/client.rb +57 -16
- data/lib/debug/color.rb +29 -19
- 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 +45 -77
- data/lib/debug/server_cdp.rb +590 -93
- data/lib/debug/server_dap.rb +231 -53
- data/lib/debug/session.rb +114 -52
- 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 +12 -4
- 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
|
@@ -229,7 +286,7 @@ module DEBUGGER__
|
|
229
286
|
rescue PostmortemError
|
230
287
|
send_response req,
|
231
288
|
success: false, message: 'postmortem mode',
|
232
|
-
result: "'Next' is not supported while
|
289
|
+
result: "'Next' is not supported while postmortem mode"
|
233
290
|
end
|
234
291
|
when 'stepIn'
|
235
292
|
begin
|
@@ -239,7 +296,7 @@ module DEBUGGER__
|
|
239
296
|
rescue PostmortemError
|
240
297
|
send_response req,
|
241
298
|
success: false, message: 'postmortem mode',
|
242
|
-
result: "'stepIn' is not supported while
|
299
|
+
result: "'stepIn' is not supported while postmortem mode"
|
243
300
|
end
|
244
301
|
when 'stepOut'
|
245
302
|
begin
|
@@ -249,7 +306,7 @@ module DEBUGGER__
|
|
249
306
|
rescue PostmortemError
|
250
307
|
send_response req,
|
251
308
|
success: false, message: 'postmortem mode',
|
252
|
-
result: "'stepOut' is not supported while
|
309
|
+
result: "'stepOut' is not supported while postmortem mode"
|
253
310
|
end
|
254
311
|
when 'terminate'
|
255
312
|
send_response req
|
@@ -276,7 +333,8 @@ module DEBUGGER__
|
|
276
333
|
'scopes',
|
277
334
|
'variables',
|
278
335
|
'evaluate',
|
279
|
-
'source'
|
336
|
+
'source',
|
337
|
+
'completions'
|
280
338
|
@q_msg << req
|
281
339
|
|
282
340
|
else
|
@@ -382,7 +440,6 @@ module DEBUGGER__
|
|
382
440
|
case ref[0]
|
383
441
|
when :globals
|
384
442
|
vars = global_variables.map do |name|
|
385
|
-
File.write('/tmp/x', "#{name}\n")
|
386
443
|
gv = 'Not implemented yet...'
|
387
444
|
{
|
388
445
|
name: name,
|
@@ -423,11 +480,13 @@ module DEBUGGER__
|
|
423
480
|
end
|
424
481
|
when 'evaluate'
|
425
482
|
frame_id = req.dig('arguments', 'frameId')
|
483
|
+
context = req.dig('arguments', 'context')
|
484
|
+
|
426
485
|
if @frame_map[frame_id]
|
427
486
|
tid, fid = @frame_map[frame_id]
|
428
487
|
expr = req.dig('arguments', 'expression')
|
429
488
|
if tc = find_waiting_tc(tid)
|
430
|
-
tc << [:dap, :evaluate, req, fid, expr]
|
489
|
+
tc << [:dap, :evaluate, req, fid, expr, context]
|
431
490
|
else
|
432
491
|
fail_response req
|
433
492
|
end
|
@@ -442,6 +501,21 @@ module DEBUGGER__
|
|
442
501
|
fail_response req, message: 'not found...'
|
443
502
|
end
|
444
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
|
445
519
|
else
|
446
520
|
raise "Unknown DAP request: #{req.inspect}"
|
447
521
|
end
|
@@ -479,8 +553,15 @@ module DEBUGGER__
|
|
479
553
|
register_vars result[:variables], tid
|
480
554
|
@ui.respond req, result
|
481
555
|
when :evaluate
|
482
|
-
|
483
|
-
|
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
|
484
565
|
@ui.respond req, result
|
485
566
|
else
|
486
567
|
raise "unsupported: #{args.inspect}"
|
@@ -565,8 +646,9 @@ module DEBUGGER__
|
|
565
646
|
v = b.local_variable_get(name)
|
566
647
|
variable(name, v)
|
567
648
|
}
|
568
|
-
|
569
|
-
|
649
|
+
special_local_variables frame do |name, val|
|
650
|
+
vars.unshift variable(name, val)
|
651
|
+
end
|
570
652
|
vars.unshift variable('%self', b.receiver)
|
571
653
|
elsif lvars = frame.local_variables
|
572
654
|
vars = lvars.map{|var, val|
|
@@ -574,8 +656,9 @@ module DEBUGGER__
|
|
574
656
|
}
|
575
657
|
else
|
576
658
|
vars = [variable('%self', frame.self)]
|
577
|
-
|
578
|
-
|
659
|
+
special_local_variables frame do |name, val|
|
660
|
+
vars.push variable(name, val)
|
661
|
+
end
|
579
662
|
end
|
580
663
|
event! :dap_result, :scope, req, variables: vars, tid: self.id
|
581
664
|
|
@@ -596,7 +679,7 @@ module DEBUGGER__
|
|
596
679
|
case obj
|
597
680
|
when Hash
|
598
681
|
vars = obj.map{|k, v|
|
599
|
-
variable(DEBUGGER__.
|
682
|
+
variable(DEBUGGER__.safe_inspect(k), v,)
|
600
683
|
}
|
601
684
|
when Struct
|
602
685
|
vars = obj.members.map{|m|
|
@@ -628,32 +711,127 @@ module DEBUGGER__
|
|
628
711
|
event! :dap_result, :variable, req, variables: (vars || []), tid: self.id
|
629
712
|
|
630
713
|
when :evaluate
|
631
|
-
fid, expr = args
|
714
|
+
fid, expr, context = args
|
632
715
|
frame = @target_frames[fid]
|
716
|
+
message = nil
|
633
717
|
|
634
718
|
if frame && (b = frame.binding)
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
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}"
|
639
768
|
end
|
640
769
|
else
|
641
|
-
result = '
|
770
|
+
result = 'Error: Can not evaluate on this frame'
|
771
|
+
end
|
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
|
642
781
|
end
|
643
|
-
|
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
|
+
|
644
804
|
else
|
645
805
|
raise "Unknown req: #{args.inspect}"
|
646
806
|
end
|
647
807
|
end
|
648
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
|
+
|
649
826
|
def evaluate_result r
|
650
827
|
v = variable nil, r
|
651
|
-
v.delete
|
652
|
-
v
|
828
|
+
v.delete :name
|
829
|
+
v.delete :value
|
830
|
+
v[:result] = DEBUGGER__.safe_inspect(r)
|
653
831
|
v
|
654
832
|
end
|
655
833
|
|
656
|
-
def variable_ name, obj, indexedVariables: 0, namedVariables: 0
|
834
|
+
def variable_ name, obj, indexedVariables: 0, namedVariables: 0
|
657
835
|
if indexedVariables > 0 || namedVariables > 0
|
658
836
|
vid = @var_map.size + 1
|
659
837
|
@var_map[vid] = obj
|
@@ -664,7 +842,7 @@ module DEBUGGER__
|
|
664
842
|
ivnum = obj.instance_variables.size
|
665
843
|
|
666
844
|
{ name: name,
|
667
|
-
value: DEBUGGER__.
|
845
|
+
value: DEBUGGER__.safe_inspect(obj),
|
668
846
|
type: obj.class.name || obj.class.to_s,
|
669
847
|
variablesReference: vid,
|
670
848
|
indexedVariables: indexedVariables,
|
@@ -679,7 +857,7 @@ module DEBUGGER__
|
|
679
857
|
when Hash
|
680
858
|
variable_ name, obj, namedVariables: obj.size
|
681
859
|
when String
|
682
|
-
variable_ name, obj,
|
860
|
+
variable_ name, obj, namedVariables: 3 # #to_str, #length, #encoding
|
683
861
|
when Struct
|
684
862
|
variable_ name, obj, namedVariables: obj.size
|
685
863
|
when Class, Module
|