debug 1.5.0 → 1.6.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/CONTRIBUTING.md +79 -11
- data/README.md +39 -16
- data/Rakefile +25 -7
- data/debug.gemspec +1 -1
- data/exe/rdbg +7 -3
- data/ext/debug/debug.c +0 -22
- data/ext/debug/extconf.rb +18 -7
- data/lib/debug/breakpoint.rb +68 -33
- data/lib/debug/client.rb +12 -2
- data/lib/debug/config.rb +55 -24
- data/lib/debug/console.rb +9 -3
- data/lib/debug/frame_info.rb +31 -24
- data/lib/debug/server.rb +44 -17
- data/lib/debug/server_cdp.rb +5 -5
- data/lib/debug/server_dap.rb +221 -115
- data/lib/debug/session.rb +227 -129
- data/lib/debug/source_repository.rb +13 -0
- data/lib/debug/thread_client.rb +161 -64
- data/lib/debug/tracer.rb +4 -3
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +28 -8
- metadata +5 -6
- data/lib/debug/bp.vim +0 -68
data/lib/debug/server_dap.rb
CHANGED
@@ -18,7 +18,7 @@ module DEBUGGER__
|
|
18
18
|
end
|
19
19
|
|
20
20
|
at_exit do
|
21
|
-
CONFIG
|
21
|
+
CONFIG.skip_all
|
22
22
|
FileUtils.rm_rf dir if tempdir
|
23
23
|
end
|
24
24
|
|
@@ -70,20 +70,69 @@ module DEBUGGER__
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
|
73
|
+
# true: all localfs
|
74
|
+
# Array: part of localfs
|
75
|
+
# nil: no localfs
|
76
|
+
@local_fs_map = nil
|
77
|
+
|
78
|
+
def self.remote_to_local_path path
|
79
|
+
case @local_fs_map
|
80
|
+
when nil
|
81
|
+
nil
|
82
|
+
when true
|
83
|
+
path
|
84
|
+
else # Array
|
85
|
+
@local_fs_map.each do |(remote_path_prefix, local_path_prefix)|
|
86
|
+
if path.start_with? remote_path_prefix
|
87
|
+
return path.sub(remote_path_prefix){ local_path_prefix }
|
88
|
+
end
|
89
|
+
end
|
74
90
|
|
75
|
-
|
76
|
-
|
91
|
+
nil
|
92
|
+
end
|
77
93
|
end
|
78
94
|
|
79
|
-
def self.
|
80
|
-
@
|
95
|
+
def self.local_to_remote_path path
|
96
|
+
case @local_fs_map
|
97
|
+
when nil
|
98
|
+
nil
|
99
|
+
when true
|
100
|
+
path
|
101
|
+
else # Array
|
102
|
+
@local_fs_map.each do |(remote_path_prefix, local_path_prefix)|
|
103
|
+
if path.start_with? local_path_prefix
|
104
|
+
return path.sub(local_path_prefix){ remote_path_prefix }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.local_fs_map_set map
|
113
|
+
return if @local_fs_map # already setup
|
114
|
+
|
115
|
+
case map
|
116
|
+
when String
|
117
|
+
@local_fs_map = map.split(',').map{|e| e.split(':').map{|path| path.delete_suffix('/') + '/'}}
|
118
|
+
when true
|
119
|
+
@local_fs_map = map
|
120
|
+
when nil
|
121
|
+
@local_fs_map = CONFIG[:local_fs_map]
|
122
|
+
end
|
81
123
|
end
|
82
124
|
|
83
125
|
def dap_setup bytes
|
84
126
|
CONFIG.set_config no_color: true
|
85
127
|
@seq = 0
|
86
|
-
|
128
|
+
|
129
|
+
case self
|
130
|
+
when UI_UnixDomainServer
|
131
|
+
UI_DAP.local_fs_map_set true
|
132
|
+
when UI_TcpServer
|
133
|
+
# TODO: loopback address can be used to connect other FS env, like Docker containers
|
134
|
+
# UI_DAP.local_fs_set if @local_addr.ipv4_loopback? || @local_addr.ipv6_loopback?
|
135
|
+
end
|
87
136
|
|
88
137
|
show_protocol :>, bytes
|
89
138
|
req = JSON.load(bytes)
|
@@ -100,14 +149,13 @@ module DEBUGGER__
|
|
100
149
|
{
|
101
150
|
filter: 'any',
|
102
151
|
label: 'rescue any exception',
|
103
|
-
|
152
|
+
supportsCondition: true,
|
104
153
|
#conditionDescription: '',
|
105
154
|
},
|
106
155
|
{
|
107
156
|
filter: 'RuntimeError',
|
108
157
|
label: 'rescue RuntimeError',
|
109
|
-
|
110
|
-
#supportsCondition: true,
|
158
|
+
supportsCondition: true,
|
111
159
|
#conditionDescription: '',
|
112
160
|
},
|
113
161
|
],
|
@@ -153,10 +201,12 @@ module DEBUGGER__
|
|
153
201
|
end
|
154
202
|
|
155
203
|
def send **kw
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
204
|
+
if sock = @sock
|
205
|
+
kw[:seq] = @seq += 1
|
206
|
+
str = JSON.dump(kw)
|
207
|
+
sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
|
208
|
+
show_protocol '<', str
|
209
|
+
end
|
160
210
|
end
|
161
211
|
|
162
212
|
def send_response req, success: true, message: nil, **kw
|
@@ -221,70 +271,97 @@ module DEBUGGER__
|
|
221
271
|
## boot/configuration
|
222
272
|
when 'launch'
|
223
273
|
send_response req
|
224
|
-
|
225
|
-
|
274
|
+
UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
|
275
|
+
@is_launch = true
|
276
|
+
|
226
277
|
when 'attach'
|
227
278
|
send_response req
|
228
|
-
|
229
|
-
@
|
230
|
-
|
231
|
-
when '
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
bps << SESSION.add_line_breakpoint(path, line)
|
279
|
+
UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
|
280
|
+
@is_launch = false
|
281
|
+
|
282
|
+
when 'configurationDone'
|
283
|
+
send_response req
|
284
|
+
|
285
|
+
if @is_launch
|
286
|
+
@q_msg << 'continue'
|
287
|
+
else
|
288
|
+
if SESSION.in_subsession?
|
289
|
+
send_event 'stopped', reason: 'pause',
|
290
|
+
threadId: 1, # maybe ...
|
291
|
+
allThreadsStopped: true
|
242
292
|
end
|
243
|
-
|
244
|
-
|
293
|
+
end
|
294
|
+
|
295
|
+
when 'setBreakpoints'
|
296
|
+
req_path = args.dig('source', 'path')
|
297
|
+
path = UI_DAP.local_to_remote_path(req_path)
|
298
|
+
|
299
|
+
if path
|
300
|
+
SESSION.clear_line_breakpoints path
|
301
|
+
|
302
|
+
bps = []
|
303
|
+
args['breakpoints'].each{|bp|
|
304
|
+
line = bp['line']
|
305
|
+
if cond = bp['condition']
|
306
|
+
bps << SESSION.add_line_breakpoint(path, line, cond: cond)
|
307
|
+
else
|
308
|
+
bps << SESSION.add_line_breakpoint(path, line)
|
309
|
+
end
|
310
|
+
}
|
311
|
+
send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
|
312
|
+
else
|
313
|
+
send_response req, success: false, message: "#{req_path} is not available"
|
314
|
+
end
|
315
|
+
|
245
316
|
when 'setFunctionBreakpoints'
|
246
317
|
send_response req
|
318
|
+
|
247
319
|
when 'setExceptionBreakpoints'
|
248
|
-
process_filter = ->(filter_id) {
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
320
|
+
process_filter = ->(filter_id, cond = nil) {
|
321
|
+
bp =
|
322
|
+
case filter_id
|
323
|
+
when 'any'
|
324
|
+
SESSION.add_catch_breakpoint 'Exception', cond: cond
|
325
|
+
when 'RuntimeError'
|
326
|
+
SESSION.add_catch_breakpoint 'RuntimeError', cond: cond
|
327
|
+
else
|
328
|
+
nil
|
329
|
+
end
|
330
|
+
{
|
331
|
+
verified: !bp.nil?,
|
332
|
+
message: bp.inspect,
|
333
|
+
}
|
260
334
|
}
|
261
|
-
}
|
262
335
|
|
263
|
-
|
264
|
-
|
265
|
-
|
336
|
+
SESSION.clear_catch_breakpoints 'Exception', 'RuntimeError'
|
337
|
+
|
338
|
+
filters = args.fetch('filters').map {|filter_id|
|
339
|
+
process_filter.call(filter_id)
|
340
|
+
}
|
266
341
|
|
267
|
-
|
268
|
-
process_filter.call(bp_info
|
342
|
+
filters += args.fetch('filterOptions', {}).map{|bp_info|
|
343
|
+
process_filter.call(bp_info['filterId'], bp_info['condition'])
|
269
344
|
}
|
270
345
|
|
271
346
|
send_response req, breakpoints: filters
|
272
|
-
|
273
|
-
send_response req
|
274
|
-
if defined?(@is_attach) && @is_attach
|
275
|
-
@q_msg << 'p'
|
276
|
-
send_event 'stopped', reason: 'pause',
|
277
|
-
threadId: 1,
|
278
|
-
allThreadsStopped: true
|
279
|
-
else
|
280
|
-
@q_msg << 'continue'
|
281
|
-
end
|
347
|
+
|
282
348
|
when 'disconnect'
|
283
|
-
|
284
|
-
|
349
|
+
terminate = args.fetch("terminateDebuggee", false)
|
350
|
+
|
351
|
+
if SESSION.in_subsession?
|
352
|
+
if terminate
|
353
|
+
@q_msg << 'kill!'
|
354
|
+
else
|
355
|
+
@q_msg << 'continue'
|
356
|
+
end
|
285
357
|
else
|
286
|
-
|
358
|
+
if terminate
|
359
|
+
@q_msg << 'kill!'
|
360
|
+
pause
|
361
|
+
end
|
287
362
|
end
|
363
|
+
|
364
|
+
SESSION.clear_all_breakpoints
|
288
365
|
send_response req
|
289
366
|
|
290
367
|
## control
|
@@ -360,14 +437,6 @@ module DEBUGGER__
|
|
360
437
|
|
361
438
|
## called by the SESSION thread
|
362
439
|
|
363
|
-
def readline prompt
|
364
|
-
@q_msg.pop || 'kill!'
|
365
|
-
end
|
366
|
-
|
367
|
-
def sock skip: false
|
368
|
-
yield $stderr
|
369
|
-
end
|
370
|
-
|
371
440
|
def respond req, res
|
372
441
|
send_response(req, **res)
|
373
442
|
end
|
@@ -379,6 +448,16 @@ module DEBUGGER__
|
|
379
448
|
|
380
449
|
def event type, *args
|
381
450
|
case type
|
451
|
+
when :load
|
452
|
+
file_path, reloaded = *args
|
453
|
+
|
454
|
+
if file_path
|
455
|
+
send_event 'loadedSource',
|
456
|
+
reason: (reloaded ? :changed : :new),
|
457
|
+
source: {
|
458
|
+
path: file_path,
|
459
|
+
}
|
460
|
+
end
|
382
461
|
when :suspend_bp
|
383
462
|
_i, bp, tid = *args
|
384
463
|
if bp.kind_of?(CatchBreakpoint)
|
@@ -425,15 +504,16 @@ module DEBUGGER__
|
|
425
504
|
case req['command']
|
426
505
|
when 'stepBack'
|
427
506
|
if @tc.recorder&.can_step_back?
|
428
|
-
|
507
|
+
request_tc [:step, :back]
|
429
508
|
else
|
430
509
|
fail_response req, message: 'cancelled'
|
431
510
|
end
|
432
511
|
|
433
512
|
when 'stackTrace'
|
434
513
|
tid = req.dig('arguments', 'threadId')
|
514
|
+
|
435
515
|
if tc = find_waiting_tc(tid)
|
436
|
-
|
516
|
+
request_tc [:dap, :backtrace, req]
|
437
517
|
else
|
438
518
|
fail_response req
|
439
519
|
end
|
@@ -442,7 +522,7 @@ module DEBUGGER__
|
|
442
522
|
if @frame_map[frame_id]
|
443
523
|
tid, fid = @frame_map[frame_id]
|
444
524
|
if tc = find_waiting_tc(tid)
|
445
|
-
|
525
|
+
request_tc [:dap, :scopes, req, fid]
|
446
526
|
else
|
447
527
|
fail_response req
|
448
528
|
end
|
@@ -474,7 +554,7 @@ module DEBUGGER__
|
|
474
554
|
tid, fid = @frame_map[frame_id]
|
475
555
|
|
476
556
|
if tc = find_waiting_tc(tid)
|
477
|
-
|
557
|
+
request_tc [:dap, :scope, req, fid]
|
478
558
|
else
|
479
559
|
fail_response req
|
480
560
|
end
|
@@ -483,7 +563,7 @@ module DEBUGGER__
|
|
483
563
|
tid, vid = ref[1], ref[2]
|
484
564
|
|
485
565
|
if tc = find_waiting_tc(tid)
|
486
|
-
|
566
|
+
request_tc [:dap, :variable, req, vid]
|
487
567
|
else
|
488
568
|
fail_response req
|
489
569
|
end
|
@@ -501,7 +581,7 @@ module DEBUGGER__
|
|
501
581
|
tid, fid = @frame_map[frame_id]
|
502
582
|
expr = req.dig('arguments', 'expression')
|
503
583
|
if tc = find_waiting_tc(tid)
|
504
|
-
|
584
|
+
request_tc [:dap, :evaluate, req, fid, expr, context]
|
505
585
|
else
|
506
586
|
fail_response req
|
507
587
|
end
|
@@ -527,7 +607,7 @@ module DEBUGGER__
|
|
527
607
|
if col = req.dig('arguments', 'column')
|
528
608
|
text = text.split(/\n/)[line.to_i - 1][0...(col.to_i - 1)]
|
529
609
|
end
|
530
|
-
|
610
|
+
request_tc [:dap, :completions, req, fid, text]
|
531
611
|
else
|
532
612
|
fail_response req
|
533
613
|
end
|
@@ -542,13 +622,18 @@ module DEBUGGER__
|
|
542
622
|
|
543
623
|
case type
|
544
624
|
when :backtrace
|
545
|
-
result[:stackFrames].each
|
625
|
+
result[:stackFrames].each{|fi|
|
626
|
+
frame_depth = fi[:id]
|
546
627
|
fi[:id] = id = @frame_map.size + 1
|
547
|
-
@frame_map[id] = [req.dig('arguments', 'threadId'),
|
548
|
-
if fi[:source]
|
549
|
-
|
550
|
-
|
551
|
-
|
628
|
+
@frame_map[id] = [req.dig('arguments', 'threadId'), frame_depth]
|
629
|
+
if fi[:source]
|
630
|
+
if src = fi[:source][:sourceReference]
|
631
|
+
src_id = @src_map.size + 1
|
632
|
+
@src_map[src_id] = src
|
633
|
+
fi[:source][:sourceReference] = src_id
|
634
|
+
else
|
635
|
+
fi[:source][:sourceReference] = 0
|
636
|
+
end
|
552
637
|
end
|
553
638
|
}
|
554
639
|
@ui.respond req, result
|
@@ -600,6 +685,11 @@ module DEBUGGER__
|
|
600
685
|
end
|
601
686
|
|
602
687
|
class ThreadClient
|
688
|
+
def value_inspect obj
|
689
|
+
# TODO: max length should be configuarable?
|
690
|
+
DEBUGGER__.safe_inspect obj, short: true, max_length: 4 * 1024
|
691
|
+
end
|
692
|
+
|
603
693
|
def process_dap args
|
604
694
|
# pp tc: self, args: args
|
605
695
|
type = args.shift
|
@@ -607,27 +697,38 @@ module DEBUGGER__
|
|
607
697
|
|
608
698
|
case type
|
609
699
|
when :backtrace
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
700
|
+
start_frame = req.dig('arguments', 'startFrame') || 0
|
701
|
+
levels = req.dig('arguments', 'levels') || 1_000
|
702
|
+
frames = []
|
703
|
+
@target_frames.each_with_index do |frame, i|
|
704
|
+
next if i < start_frame
|
705
|
+
break if (levels -= 1) < 0
|
706
|
+
|
707
|
+
path = frame.realpath || frame.path
|
708
|
+
source_name = path ? File.basename(path) : frame.location.to_s
|
709
|
+
|
710
|
+
if (path && File.exist?(path)) && (local_path = UI_DAP.remote_to_local_path(path))
|
711
|
+
# ok
|
712
|
+
else
|
713
|
+
ref = frame.file_lines
|
714
|
+
end
|
618
715
|
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
}
|
716
|
+
frames << {
|
717
|
+
id: i, # id is refilled by SESSION
|
718
|
+
name: frame.name,
|
719
|
+
line: frame.location.lineno,
|
720
|
+
column: 1,
|
721
|
+
source: {
|
722
|
+
name: source_name,
|
723
|
+
path: (local_path || path),
|
724
|
+
sourceReference: ref,
|
725
|
+
},
|
630
726
|
}
|
727
|
+
end
|
728
|
+
|
729
|
+
event! :dap_result, :backtrace, req, {
|
730
|
+
stackFrames: frames,
|
731
|
+
totalFrames: @target_frames.size,
|
631
732
|
}
|
632
733
|
when :scopes
|
633
734
|
fid = args.shift
|
@@ -682,7 +783,7 @@ module DEBUGGER__
|
|
682
783
|
case obj
|
683
784
|
when Hash
|
684
785
|
vars = obj.map{|k, v|
|
685
|
-
variable(
|
786
|
+
variable(value_inspect(k), v,)
|
686
787
|
}
|
687
788
|
when Struct
|
688
789
|
vars = obj.members.map{|m|
|
@@ -705,10 +806,10 @@ module DEBUGGER__
|
|
705
806
|
]
|
706
807
|
end
|
707
808
|
|
708
|
-
vars += obj.
|
709
|
-
variable(iv,
|
809
|
+
vars += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv|
|
810
|
+
variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
|
710
811
|
}
|
711
|
-
vars.unshift variable('#class', obj
|
812
|
+
vars.unshift variable('#class', M_CLASS.bind_call(obj))
|
712
813
|
end
|
713
814
|
end
|
714
815
|
event! :dap_result, :variable, req, variables: (vars || []), tid: self.id
|
@@ -735,7 +836,7 @@ module DEBUGGER__
|
|
735
836
|
case expr
|
736
837
|
when /\A\@\S/
|
737
838
|
begin
|
738
|
-
result = b.receiver
|
839
|
+
result = M_INSTANCE_VARIABLE_GET.bind_call(b.receiver, expr)
|
739
840
|
rescue NameError
|
740
841
|
message = "Error: Not defined instance variable: #{expr.inspect}"
|
741
842
|
end
|
@@ -746,6 +847,8 @@ module DEBUGGER__
|
|
746
847
|
break false
|
747
848
|
end
|
748
849
|
} and (message = "Error: Not defined global variable: #{expr.inspect}")
|
850
|
+
when /\Aself$/
|
851
|
+
result = b.receiver
|
749
852
|
when /(\A((::[A-Z]|[A-Z])\w*)+)/
|
750
853
|
unless result = search_const(b, $1)
|
751
854
|
message = "Error: Not defined constants: #{expr.inspect}"
|
@@ -755,8 +858,8 @@ module DEBUGGER__
|
|
755
858
|
result = b.local_variable_get(expr)
|
756
859
|
rescue NameError
|
757
860
|
# try to check method
|
758
|
-
if b.receiver
|
759
|
-
result = b.receiver
|
861
|
+
if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true)
|
862
|
+
result = M_METHOD.bind_call(b.receiver, expr)
|
760
863
|
else
|
761
864
|
message = "Error: Can not evaluate: #{expr.inspect}"
|
762
865
|
end
|
@@ -780,6 +883,8 @@ module DEBUGGER__
|
|
780
883
|
end
|
781
884
|
|
782
885
|
event! :dap_result, :completions, req, targets: (words || []).map{|phrase|
|
886
|
+
detail = nil
|
887
|
+
|
783
888
|
if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase
|
784
889
|
w = $1
|
785
890
|
else
|
@@ -788,13 +893,14 @@ module DEBUGGER__
|
|
788
893
|
|
789
894
|
begin
|
790
895
|
v = b.local_variable_get(w)
|
791
|
-
|
896
|
+
detail ="(variable: #{value_inspect(v)})"
|
792
897
|
rescue NameError
|
793
898
|
end
|
794
899
|
|
795
900
|
{
|
796
901
|
label: phrase,
|
797
902
|
text: w,
|
903
|
+
detail: detail,
|
798
904
|
}
|
799
905
|
}
|
800
906
|
|
@@ -824,7 +930,7 @@ module DEBUGGER__
|
|
824
930
|
v = variable nil, r
|
825
931
|
v.delete :name
|
826
932
|
v.delete :value
|
827
|
-
v[:result] =
|
933
|
+
v[:result] = value_inspect(r)
|
828
934
|
v
|
829
935
|
end
|
830
936
|
|
@@ -836,11 +942,11 @@ module DEBUGGER__
|
|
836
942
|
vid = 0
|
837
943
|
end
|
838
944
|
|
839
|
-
ivnum = obj.
|
945
|
+
ivnum = M_INSTANCE_VARIABLES.bind_call(obj).size
|
840
946
|
|
841
947
|
{ name: name,
|
842
|
-
value:
|
843
|
-
type: obj.
|
948
|
+
value: value_inspect(obj),
|
949
|
+
type: (klass = M_CLASS.bind_call(obj)).name || klass.to_s,
|
844
950
|
variablesReference: vid,
|
845
951
|
indexedVariables: indexedVariables,
|
846
952
|
namedVariables: namedVariables + ivnum,
|