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