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.
@@ -18,7 +18,7 @@ module DEBUGGER__
18
18
  end
19
19
 
20
20
  at_exit do
21
- CONFIG[:skip_path] = [//] # skip all
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
- @local_fs = false
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
- def self.local_fs
76
- @local_fs
91
+ nil
92
+ end
77
93
  end
78
94
 
79
- def self.local_fs_set
80
- @local_fs = true
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
- UI_DAP.local_fs_set if self.kind_of?(UI_UnixDomainServer)
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
- #supportsCondition: true,
152
+ supportsCondition: true,
104
153
  #conditionDescription: '',
105
154
  },
106
155
  {
107
156
  filter: 'RuntimeError',
108
157
  label: 'rescue RuntimeError',
109
- default: true,
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
- kw[:seq] = @seq += 1
157
- str = JSON.dump(kw)
158
- @sock.write "Content-Length: #{str.bytesize}\r\n\r\n#{str}"
159
- show_protocol '<', str
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
- @is_attach = false
225
- UI_DAP.local_fs_set if req.dig('arguments', 'localfs')
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
- Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid)
229
- @is_attach = true
230
- UI_DAP.local_fs_set if req.dig('arguments', 'localfs')
231
- when 'setBreakpoints'
232
- path = args.dig('source', 'path')
233
- SESSION.clear_line_breakpoints path
234
-
235
- bps = []
236
- args['breakpoints'].each{|bp|
237
- line = bp['line']
238
- if cond = bp['condition']
239
- bps << SESSION.add_line_breakpoint(path, line, cond: cond)
240
- else
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
- send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
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
- case filter_id
250
- when 'any'
251
- bp = SESSION.add_catch_breakpoint 'Exception'
252
- when 'RuntimeError'
253
- bp = SESSION.add_catch_breakpoint 'RuntimeError'
254
- else
255
- bp = nil
256
- end
257
- {
258
- verified: bp ? true : false,
259
- message: bp.inspect,
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
- filters = args.fetch('filters').map {|filter_id|
264
- process_filter.call(filter_id)
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
- filters += args.fetch('filterOptions', {}).map{|bp_info|
268
- process_filter.call(bp_info.dig('filterId'))
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
- when 'configurationDone'
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
- if args.fetch("terminateDebuggee", false)
284
- @q_msg << 'kill!'
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
- @q_msg << 'continue'
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
- @tc << [:step, :back]
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
- tc << [:dap, :backtrace, req]
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
- tc << [:dap, :scopes, req, fid]
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
- tc << [:dap, :scope, req, fid]
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
- tc << [:dap, :variable, req, vid]
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
- tc << [:dap, :evaluate, req, fid, expr, context]
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
- tc << [:dap, :completions, req, fid, text]
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.with_index{|fi, i|
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'), i]
548
- if fi[:source] && src = fi[:source][:sourceReference]
549
- src_id = @src_map.size + 1
550
- @src_map[src_id] = src
551
- fi[:source][:sourceReference] = src_id
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
- event! :dap_result, :backtrace, req, {
611
- stackFrames: @target_frames.map{|frame|
612
- path = frame.realpath || frame.path
613
- source_name = path ? File.basename(path) : frame.location.to_s
614
-
615
- if !UI_DAP.local_fs || !(path && File.exist?(path))
616
- ref = frame.file_lines
617
- end
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
- # id: ??? # filled by SESSION
621
- name: frame.name,
622
- line: frame.location.lineno,
623
- column: 1,
624
- source: {
625
- name: source_name,
626
- path: path,
627
- sourceReference: ref,
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(DEBUGGER__.safe_inspect(k), v,)
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.instance_variables.map{|iv|
709
- variable(iv, obj.instance_variable_get(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.class)
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.instance_variable_get(expr)
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.respond_to? expr, include_all: true
759
- result = b.receiver.method(expr)
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
- phrase += " (variable:#{DEBUGGER__.safe_inspect(v)})"
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] = DEBUGGER__.safe_inspect(r)
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.instance_variables.size
945
+ ivnum = M_INSTANCE_VARIABLES.bind_call(obj).size
840
946
 
841
947
  { name: name,
842
- value: DEBUGGER__.safe_inspect(obj),
843
- type: obj.class.name || obj.class.to_s,
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,