debug 1.4.0 → 1.9.2
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 +210 -6
- data/Gemfile +2 -0
- data/LICENSE.txt +0 -0
- data/README.md +161 -85
- data/Rakefile +33 -10
- data/TODO.md +8 -8
- data/debug.gemspec +9 -7
- data/exe/rdbg +23 -4
- data/ext/debug/debug.c +111 -21
- data/ext/debug/extconf.rb +23 -0
- data/ext/debug/iseq_collector.c +2 -0
- data/lib/debug/abbrev_command.rb +77 -0
- data/lib/debug/breakpoint.rb +102 -74
- data/lib/debug/client.rb +46 -12
- data/lib/debug/color.rb +0 -0
- data/lib/debug/config.rb +129 -36
- data/lib/debug/console.rb +46 -40
- data/lib/debug/dap_custom/traceInspector.rb +336 -0
- data/lib/debug/frame_info.rb +40 -25
- data/lib/debug/irb_integration.rb +37 -0
- data/lib/debug/local.rb +17 -11
- data/lib/debug/open.rb +0 -0
- data/lib/debug/open_nonstop.rb +0 -0
- data/lib/debug/prelude.rb +3 -2
- data/lib/debug/server.rb +126 -56
- data/lib/debug/server_cdp.rb +673 -248
- data/lib/debug/server_dap.rb +497 -261
- data/lib/debug/session.rb +899 -441
- data/lib/debug/source_repository.rb +122 -49
- data/lib/debug/start.rb +1 -1
- data/lib/debug/thread_client.rb +460 -155
- data/lib/debug/tracer.rb +10 -16
- data/lib/debug/version.rb +1 -1
- data/lib/debug.rb +7 -2
- data/misc/README.md.erb +106 -56
- metadata +14 -24
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- data/.github/ISSUE_TEMPLATE/custom.md +0 -10
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- data/.github/pull_request_template.md +0 -9
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -30
- data/bin/setup +0 -8
- data/lib/debug/bp.vim +0 -68
data/lib/debug/thread_client.rb
CHANGED
@@ -5,32 +5,65 @@ require 'pp'
|
|
5
5
|
|
6
6
|
require_relative 'color'
|
7
7
|
|
8
|
+
class ::Thread
|
9
|
+
attr_accessor :debug_thread_client
|
10
|
+
end
|
11
|
+
|
8
12
|
module DEBUGGER__
|
13
|
+
M_INSTANCE_VARIABLES = method(:instance_variables).unbind
|
14
|
+
M_INSTANCE_VARIABLE_GET = method(:instance_variable_get).unbind
|
15
|
+
M_CLASS = method(:class).unbind
|
16
|
+
M_SINGLETON_CLASS = method(:singleton_class).unbind
|
17
|
+
M_KIND_OF_P = method(:kind_of?).unbind
|
18
|
+
M_RESPOND_TO_P = method(:respond_to?).unbind
|
19
|
+
M_METHOD = method(:method).unbind
|
20
|
+
M_OBJECT_ID = method(:object_id).unbind
|
21
|
+
M_NAME = method(:name).unbind
|
22
|
+
|
9
23
|
module SkipPathHelper
|
10
24
|
def skip_path?(path)
|
25
|
+
!path ||
|
26
|
+
DEBUGGER__.skip? ||
|
27
|
+
ThreadClient.current.management? ||
|
28
|
+
skip_internal_path?(path) ||
|
29
|
+
skip_config_skip_path?(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def skip_config_skip_path?(path)
|
11
33
|
(skip_paths = CONFIG[:skip_path]) && skip_paths.any?{|skip_path| path.match?(skip_path)}
|
12
34
|
end
|
13
35
|
|
36
|
+
def skip_internal_path?(path)
|
37
|
+
path.start_with?(__dir__) || path.delete_prefix('!eval:').start_with?('<internal:')
|
38
|
+
end
|
39
|
+
|
14
40
|
def skip_location?(loc)
|
15
41
|
loc_path = loc.absolute_path || "!eval:#{loc.path}"
|
16
42
|
skip_path?(loc_path)
|
17
43
|
end
|
18
44
|
end
|
19
45
|
|
46
|
+
module GlobalVariablesHelper
|
47
|
+
SKIP_GLOBAL_LIST = %i[$= $KCODE $-K $SAFE].freeze
|
48
|
+
def safe_global_variables
|
49
|
+
global_variables.reject{|name| SKIP_GLOBAL_LIST.include? name }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
20
53
|
class ThreadClient
|
21
54
|
def self.current
|
22
|
-
|
23
|
-
thc
|
24
|
-
else
|
25
|
-
thc = SESSION.get_thread_client
|
26
|
-
Thread.current[:DEBUGGER__ThreadClient] = thc
|
27
|
-
end
|
55
|
+
Thread.current.debug_thread_client ||= SESSION.get_thread_client
|
28
56
|
end
|
29
57
|
|
30
58
|
include Color
|
31
59
|
include SkipPathHelper
|
60
|
+
include GlobalVariablesHelper
|
61
|
+
|
62
|
+
attr_reader :thread, :id, :recorder, :check_bp_fulfillment_map
|
32
63
|
|
33
|
-
|
64
|
+
def location
|
65
|
+
current_frame&.location
|
66
|
+
end
|
34
67
|
|
35
68
|
def assemble_arguments(args)
|
36
69
|
args.map do |arg|
|
@@ -42,7 +75,8 @@ module DEBUGGER__
|
|
42
75
|
call_identifier_str =
|
43
76
|
case frame.frame_type
|
44
77
|
when :block
|
45
|
-
level, block_loc
|
78
|
+
level, block_loc = frame.block_identifier
|
79
|
+
args = frame.parameters_info
|
46
80
|
|
47
81
|
if !args.empty?
|
48
82
|
args_str = " {|#{assemble_arguments(args)}|}"
|
@@ -50,7 +84,8 @@ module DEBUGGER__
|
|
50
84
|
|
51
85
|
"#{colorize_blue("block")}#{args_str} in #{colorize_blue(block_loc + level)}"
|
52
86
|
when :method
|
53
|
-
ci
|
87
|
+
ci = frame.method_identifier
|
88
|
+
args = frame.parameters_info
|
54
89
|
|
55
90
|
if !args.empty?
|
56
91
|
args_str = "(#{assemble_arguments(args)})"
|
@@ -87,6 +122,9 @@ module DEBUGGER__
|
|
87
122
|
@obj_map = {} # { object_id => obj } for CDP
|
88
123
|
@recorder = nil
|
89
124
|
@mode = :waiting
|
125
|
+
@current_frame_index = 0
|
126
|
+
# every thread should maintain its own CheckBreakpoint fulfillment state
|
127
|
+
@check_bp_fulfillment_map = {} # { check_bp => boolean }
|
90
128
|
set_mode :running
|
91
129
|
thr.instance_variable_set(:@__thread_client_id, id)
|
92
130
|
|
@@ -106,6 +144,7 @@ module DEBUGGER__
|
|
106
144
|
end
|
107
145
|
|
108
146
|
def set_mode mode
|
147
|
+
debug_mode(@mode, mode)
|
109
148
|
# STDERR.puts "#{@mode} => #{mode} @ #{caller.inspect}"
|
110
149
|
# pp caller
|
111
150
|
|
@@ -149,14 +188,7 @@ module DEBUGGER__
|
|
149
188
|
end
|
150
189
|
|
151
190
|
def to_s
|
152
|
-
|
153
|
-
|
154
|
-
if loc
|
155
|
-
str = "(#{@thread.name || @thread.status})@#{loc}"
|
156
|
-
else
|
157
|
-
str = "(#{@thread.name || @thread.status})@#{@thread.to_s}"
|
158
|
-
end
|
159
|
-
|
191
|
+
str = "(#{@thread.name || @thread.status})@#{current_frame&.location || @thread.to_s}"
|
160
192
|
str += " (not under control)" unless self.waiting?
|
161
193
|
str
|
162
194
|
end
|
@@ -176,6 +208,7 @@ module DEBUGGER__
|
|
176
208
|
end
|
177
209
|
|
178
210
|
def << req
|
211
|
+
debug_cmd(req)
|
179
212
|
@q_cmd << req
|
180
213
|
end
|
181
214
|
|
@@ -186,6 +219,7 @@ module DEBUGGER__
|
|
186
219
|
end
|
187
220
|
|
188
221
|
def event! ev, *args
|
222
|
+
debug_event(ev, args)
|
189
223
|
@q_evt << [self, @output, ev, generate_info, *args]
|
190
224
|
@output = []
|
191
225
|
end
|
@@ -231,6 +265,7 @@ module DEBUGGER__
|
|
231
265
|
|
232
266
|
def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil
|
233
267
|
return if management?
|
268
|
+
debug_suspend(event)
|
234
269
|
|
235
270
|
@current_frame_index = 0
|
236
271
|
|
@@ -246,7 +281,6 @@ module DEBUGGER__
|
|
246
281
|
|
247
282
|
cf = @target_frames.first
|
248
283
|
if cf
|
249
|
-
@location = cf.location
|
250
284
|
case event
|
251
285
|
when :return, :b_return, :c_return
|
252
286
|
cf.has_return_value = true
|
@@ -265,8 +299,10 @@ module DEBUGGER__
|
|
265
299
|
end
|
266
300
|
|
267
301
|
if event != :pause
|
268
|
-
|
269
|
-
|
302
|
+
unless bp&.skip_src
|
303
|
+
show_src
|
304
|
+
show_frames CONFIG[:show_frames]
|
305
|
+
end
|
270
306
|
|
271
307
|
set_mode :waiting
|
272
308
|
|
@@ -302,11 +338,15 @@ module DEBUGGER__
|
|
302
338
|
@step_tp.disable if @step_tp
|
303
339
|
|
304
340
|
thread = Thread.current
|
341
|
+
subsession_id = SESSION.subsession_id
|
305
342
|
|
306
343
|
if SUPPORT_TARGET_THREAD
|
307
344
|
@step_tp = TracePoint.new(*events){|tp|
|
308
|
-
|
309
|
-
|
345
|
+
if SESSION.stop_stepping? tp.path, tp.lineno, subsession_id
|
346
|
+
tp.disable
|
347
|
+
next
|
348
|
+
end
|
349
|
+
next if !yield(tp)
|
310
350
|
next if tp.path.start_with?(__dir__)
|
311
351
|
next if tp.path.start_with?('<internal:trace_point>')
|
312
352
|
next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
|
@@ -321,8 +361,11 @@ module DEBUGGER__
|
|
321
361
|
else
|
322
362
|
@step_tp = TracePoint.new(*events){|tp|
|
323
363
|
next if thread != Thread.current
|
324
|
-
|
325
|
-
|
364
|
+
if SESSION.stop_stepping? tp.path, tp.lineno, subsession_id
|
365
|
+
tp.disable
|
366
|
+
next
|
367
|
+
end
|
368
|
+
next if !yield(tp)
|
326
369
|
next if tp.path.start_with?(__dir__)
|
327
370
|
next if tp.path.start_with?('<internal:trace_point>')
|
328
371
|
next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
|
@@ -339,9 +382,49 @@ module DEBUGGER__
|
|
339
382
|
|
340
383
|
## cmd helpers
|
341
384
|
|
342
|
-
|
343
|
-
|
344
|
-
|
385
|
+
if TracePoint.respond_to? :allow_reentry
|
386
|
+
def tp_allow_reentry
|
387
|
+
TracePoint.allow_reentry do
|
388
|
+
yield
|
389
|
+
end
|
390
|
+
rescue RuntimeError => e
|
391
|
+
# on the postmortem mode, it is not stopped in TracePoint
|
392
|
+
if e.message == 'No need to allow reentrance.'
|
393
|
+
yield
|
394
|
+
else
|
395
|
+
raise
|
396
|
+
end
|
397
|
+
end
|
398
|
+
else
|
399
|
+
def tp_allow_reentry
|
400
|
+
yield
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def frame_eval_core src, b, binding_location: false
|
405
|
+
saved_target_frames = @target_frames
|
406
|
+
saved_current_frame_index = @current_frame_index
|
407
|
+
|
408
|
+
if b
|
409
|
+
file, lineno = b.source_location
|
410
|
+
|
411
|
+
tp_allow_reentry do
|
412
|
+
if binding_location
|
413
|
+
b.eval(src, file, lineno)
|
414
|
+
else
|
415
|
+
b.eval(src, "(rdbg)/#{file}")
|
416
|
+
end
|
417
|
+
end
|
418
|
+
else
|
419
|
+
frame_self = current_frame.self
|
420
|
+
|
421
|
+
tp_allow_reentry do
|
422
|
+
frame_self.instance_eval(src)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
ensure
|
426
|
+
@target_frames = saved_target_frames
|
427
|
+
@current_frame_index = saved_current_frame_index
|
345
428
|
end
|
346
429
|
|
347
430
|
SPECIAL_LOCAL_VARS = [
|
@@ -349,25 +432,22 @@ module DEBUGGER__
|
|
349
432
|
[:return_value, "_return"],
|
350
433
|
]
|
351
434
|
|
352
|
-
def frame_eval src, re_raise: false
|
435
|
+
def frame_eval src, re_raise: false, binding_location: false
|
353
436
|
@success_last_eval = false
|
354
437
|
|
355
|
-
b = current_frame
|
438
|
+
b = current_frame&.eval_binding || TOPLEVEL_BINDING
|
356
439
|
|
357
440
|
special_local_variables current_frame do |name, var|
|
358
441
|
b.local_variable_set(name, var) if /\%/ !~ name
|
359
442
|
end
|
360
443
|
|
361
|
-
result =
|
362
|
-
|
363
|
-
b.eval(src, "(rdbg)/#{f}")
|
364
|
-
else
|
365
|
-
frame_self = current_frame.self
|
366
|
-
instance_eval_for_cmethod(frame_self, src)
|
367
|
-
end
|
444
|
+
result = frame_eval_core(src, b, binding_location: binding_location)
|
445
|
+
|
368
446
|
@success_last_eval = true
|
369
447
|
result
|
370
448
|
|
449
|
+
rescue SystemExit
|
450
|
+
raise
|
371
451
|
rescue Exception => e
|
372
452
|
return yield(e) if block_given?
|
373
453
|
|
@@ -380,50 +460,46 @@ module DEBUGGER__
|
|
380
460
|
raise if re_raise
|
381
461
|
end
|
382
462
|
|
383
|
-
def
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
if file_lines = frame.file_lines
|
391
|
-
frame_line = frame.location.lineno - 1
|
463
|
+
def get_src(frame,
|
464
|
+
max_lines:,
|
465
|
+
start_line: nil,
|
466
|
+
end_line: nil,
|
467
|
+
dir: +1)
|
468
|
+
if file_lines = frame.file_lines
|
469
|
+
frame_line = frame.location.lineno - 1
|
392
470
|
|
471
|
+
if CONFIG[:no_lineno]
|
472
|
+
lines = file_lines
|
473
|
+
else
|
393
474
|
lines = file_lines.map.with_index do |e, i|
|
394
475
|
cur = i == frame_line ? '=>' : ' '
|
395
476
|
line = colorize_dim('%4d|' % (i+1))
|
396
477
|
"#{cur}#{line} #{e}"
|
397
478
|
end
|
479
|
+
end
|
398
480
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
else
|
404
|
-
end_line = frame.show_line - max_lines
|
405
|
-
start_line = [end_line - max_lines, 0].max
|
406
|
-
end
|
481
|
+
unless start_line
|
482
|
+
if frame.show_line
|
483
|
+
if dir > 0
|
484
|
+
start_line = frame.show_line
|
407
485
|
else
|
408
|
-
|
486
|
+
end_line = frame.show_line - max_lines
|
487
|
+
start_line = [end_line - max_lines, 0].max
|
409
488
|
end
|
489
|
+
else
|
490
|
+
start_line = [frame_line - max_lines/2, 0].max
|
410
491
|
end
|
492
|
+
end
|
411
493
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
if update_line
|
417
|
-
frame.show_line = end_line
|
418
|
-
end
|
494
|
+
unless end_line
|
495
|
+
end_line = [start_line + max_lines, lines.size].min
|
496
|
+
end
|
419
497
|
|
420
|
-
|
421
|
-
|
422
|
-
puts lines[start_line ... end_line]
|
423
|
-
end
|
424
|
-
else # no file lines
|
425
|
-
puts "# No sourcefile available for #{frame.path}"
|
498
|
+
if start_line != end_line && max_lines
|
499
|
+
[start_line, end_line, lines]
|
426
500
|
end
|
501
|
+
else # no file lines
|
502
|
+
nil
|
427
503
|
end
|
428
504
|
rescue Exception => e
|
429
505
|
p e
|
@@ -431,14 +507,63 @@ module DEBUGGER__
|
|
431
507
|
exit!
|
432
508
|
end
|
433
509
|
|
510
|
+
def show_src(frame_index: @current_frame_index, update_line: false, ignore_show_line: false, max_lines: CONFIG[:show_src_lines], **options)
|
511
|
+
if frame = get_frame(frame_index)
|
512
|
+
begin
|
513
|
+
if ignore_show_line
|
514
|
+
prev_show_line = frame.show_line
|
515
|
+
frame.show_line = nil
|
516
|
+
end
|
517
|
+
|
518
|
+
start_line, end_line, lines = *get_src(frame, max_lines: max_lines, **options)
|
519
|
+
|
520
|
+
if start_line
|
521
|
+
if update_line
|
522
|
+
frame.show_line = end_line
|
523
|
+
end
|
524
|
+
|
525
|
+
puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
|
526
|
+
puts lines[start_line...end_line]
|
527
|
+
else
|
528
|
+
puts "# No sourcefile available for #{frame.path}"
|
529
|
+
end
|
530
|
+
ensure
|
531
|
+
frame.show_line = prev_show_line if prev_show_line
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
434
536
|
def current_frame
|
537
|
+
get_frame(@current_frame_index)
|
538
|
+
end
|
539
|
+
|
540
|
+
def get_frame(index)
|
435
541
|
if @target_frames
|
436
|
-
@target_frames[
|
542
|
+
@target_frames[index]
|
437
543
|
else
|
438
544
|
nil
|
439
545
|
end
|
440
546
|
end
|
441
547
|
|
548
|
+
def collect_locals(frame)
|
549
|
+
locals = []
|
550
|
+
|
551
|
+
if s = frame&.self
|
552
|
+
locals << ["%self", s]
|
553
|
+
end
|
554
|
+
special_local_variables frame do |name, val|
|
555
|
+
locals << [name, val]
|
556
|
+
end
|
557
|
+
|
558
|
+
if vars = frame&.local_variables
|
559
|
+
vars.each{|var, val|
|
560
|
+
locals << [var, val]
|
561
|
+
}
|
562
|
+
end
|
563
|
+
|
564
|
+
locals
|
565
|
+
end
|
566
|
+
|
442
567
|
## cmd: show
|
443
568
|
|
444
569
|
def special_local_variables frame
|
@@ -450,62 +575,86 @@ module DEBUGGER__
|
|
450
575
|
end
|
451
576
|
|
452
577
|
def show_locals pat
|
453
|
-
|
454
|
-
puts_variable_info
|
455
|
-
end
|
456
|
-
special_local_variables current_frame do |name, val|
|
457
|
-
puts_variable_info name, val, pat
|
578
|
+
collect_locals(current_frame).each do |var, val|
|
579
|
+
puts_variable_info(var, val, pat)
|
458
580
|
end
|
581
|
+
end
|
459
582
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
583
|
+
def show_ivars pat, expr = nil
|
584
|
+
if expr && !expr.empty?
|
585
|
+
_self = frame_eval(expr);
|
586
|
+
elsif _self = current_frame&.self
|
587
|
+
else
|
588
|
+
_self = nil
|
464
589
|
end
|
465
|
-
end
|
466
590
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
value = s.instance_variable_get(iv)
|
591
|
+
if _self
|
592
|
+
M_INSTANCE_VARIABLES.bind_call(_self).sort.each{|iv|
|
593
|
+
value = M_INSTANCE_VARIABLE_GET.bind_call(_self, iv)
|
471
594
|
puts_variable_info iv, value, pat
|
472
595
|
}
|
473
596
|
end
|
474
597
|
end
|
475
598
|
|
476
|
-
def
|
477
|
-
|
599
|
+
def iter_consts c, names = {}
|
600
|
+
c.constants(false).sort.each{|name|
|
601
|
+
next if names.has_key? name
|
602
|
+
names[name] = nil
|
603
|
+
begin
|
604
|
+
value = c.const_get(name)
|
605
|
+
rescue Exception => e
|
606
|
+
value = e
|
607
|
+
end
|
608
|
+
yield name, value
|
609
|
+
}
|
610
|
+
end
|
611
|
+
|
612
|
+
def get_consts expr = nil, only_self: false, &block
|
613
|
+
if expr && !expr.empty?
|
614
|
+
begin
|
615
|
+
_self = frame_eval(expr, re_raise: true)
|
616
|
+
rescue Exception
|
617
|
+
# ignore
|
618
|
+
else
|
619
|
+
if M_KIND_OF_P.bind_call(_self, Module)
|
620
|
+
iter_consts _self, &block
|
621
|
+
return
|
622
|
+
else
|
623
|
+
puts "#{_self.inspect} (by #{expr}) is not a Module."
|
624
|
+
end
|
625
|
+
end
|
626
|
+
elsif _self = current_frame&.self
|
478
627
|
cs = {}
|
479
|
-
if
|
480
|
-
cs[
|
628
|
+
if M_KIND_OF_P.bind_call(_self, Module)
|
629
|
+
cs[_self] = :self
|
481
630
|
else
|
482
|
-
|
483
|
-
cs[
|
631
|
+
_self = M_CLASS.bind_call(_self)
|
632
|
+
cs[_self] = :self unless only_self
|
484
633
|
end
|
485
634
|
|
486
635
|
unless only_self
|
487
|
-
|
636
|
+
_self.ancestors.each{|c| break if c == Object; cs[c] = :ancestors}
|
488
637
|
if b = current_frame&.binding
|
489
|
-
b.eval('Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c}
|
638
|
+
b.eval('::Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c}
|
490
639
|
end
|
491
640
|
end
|
492
641
|
|
493
642
|
names = {}
|
494
643
|
|
495
644
|
cs.each{|c, _|
|
496
|
-
c
|
497
|
-
next if names.has_key? name
|
498
|
-
names[name] = nil
|
499
|
-
value = c.const_get(name)
|
500
|
-
puts_variable_info name, value, pat
|
501
|
-
}
|
645
|
+
iter_consts c, names, &block
|
502
646
|
}
|
503
647
|
end
|
504
648
|
end
|
505
649
|
|
506
|
-
|
650
|
+
def show_consts pat, expr = nil, only_self: false
|
651
|
+
get_consts expr, only_self: only_self do |name, value|
|
652
|
+
puts_variable_info name, value, pat
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
507
656
|
def show_globals pat
|
508
|
-
|
657
|
+
safe_global_variables.sort.each{|name|
|
509
658
|
next if SKIP_GLOBAL_LIST.include? name
|
510
659
|
|
511
660
|
value = eval(name.to_s)
|
@@ -517,7 +666,7 @@ module DEBUGGER__
|
|
517
666
|
return if pat && pat !~ label
|
518
667
|
|
519
668
|
begin
|
520
|
-
inspected = obj
|
669
|
+
inspected = DEBUGGER__.safe_inspect(obj)
|
521
670
|
rescue Exception => e
|
522
671
|
inspected = e.inspect
|
523
672
|
end
|
@@ -526,28 +675,32 @@ module DEBUGGER__
|
|
526
675
|
w = SESSION::width
|
527
676
|
|
528
677
|
if mono_info.length >= w
|
529
|
-
|
678
|
+
maximum_value_width = w - "#{label} = ".length
|
679
|
+
valstr = truncate(inspected, width: maximum_value_width)
|
530
680
|
else
|
531
681
|
valstr = colored_inspect(obj, width: 2 ** 30)
|
532
682
|
valstr = inspected if valstr.lines.size > 1
|
533
|
-
info = "#{colorize_cyan(label)} = #{valstr}"
|
534
683
|
end
|
535
684
|
|
685
|
+
info = "#{colorize_cyan(label)} = #{valstr}"
|
686
|
+
|
536
687
|
puts info
|
537
688
|
end
|
538
689
|
|
539
690
|
def truncate(string, width:)
|
540
|
-
|
541
|
-
|
542
|
-
|
691
|
+
if string.start_with?("#<")
|
692
|
+
string[0 .. (width-5)] + '...>'
|
693
|
+
else
|
694
|
+
string[0 .. (width-4)] + '...'
|
695
|
+
end
|
543
696
|
end
|
544
697
|
|
545
698
|
### cmd: show edit
|
546
699
|
|
547
700
|
def show_by_editor path = nil
|
548
701
|
unless path
|
549
|
-
if
|
550
|
-
path =
|
702
|
+
if current_frame
|
703
|
+
path = current_frame.path
|
551
704
|
else
|
552
705
|
return # can't get path
|
553
706
|
end
|
@@ -557,7 +710,8 @@ module DEBUGGER__
|
|
557
710
|
if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
|
558
711
|
puts "command: #{editor}"
|
559
712
|
puts " path: #{path}"
|
560
|
-
|
713
|
+
require 'shellwords'
|
714
|
+
system(*Shellwords.split(editor), path)
|
561
715
|
else
|
562
716
|
puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
|
563
717
|
end
|
@@ -572,15 +726,11 @@ module DEBUGGER__
|
|
572
726
|
if @target_frames && (max ||= @target_frames.size) > 0
|
573
727
|
frames = []
|
574
728
|
@target_frames.each_with_index{|f, i|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
when Regexp
|
581
|
-
f.location_str.match?(pat)
|
582
|
-
end
|
583
|
-
}
|
729
|
+
# we need to use FrameInfo#matchable_location because #location_str is for display
|
730
|
+
# and it may change based on configs (e.g. use_short_path)
|
731
|
+
next if pattern && !(f.name.match?(pattern) || f.matchable_location.match?(pattern))
|
732
|
+
# avoid using skip_path? because we still want to display internal frames
|
733
|
+
next if skip_config_skip_path?(f.matchable_location)
|
584
734
|
|
585
735
|
frames << [i, f]
|
586
736
|
}
|
@@ -617,18 +767,25 @@ module DEBUGGER__
|
|
617
767
|
o = Output.new(@output)
|
618
768
|
|
619
769
|
locals = current_frame&.local_variables
|
620
|
-
klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
|
621
770
|
|
622
|
-
|
771
|
+
klass = M_CLASS.bind_call(obj)
|
772
|
+
klass = obj if Class == klass || Module == klass
|
773
|
+
|
774
|
+
o.dump("constants", obj.constants) if M_RESPOND_TO_P.bind_call(obj, :constants)
|
623
775
|
outline_method(o, klass, obj)
|
624
|
-
o.dump("instance variables", obj
|
776
|
+
o.dump("instance variables", M_INSTANCE_VARIABLES.bind_call(obj))
|
625
777
|
o.dump("class variables", klass.class_variables)
|
626
778
|
o.dump("locals", locals.keys) if locals
|
627
779
|
end
|
628
780
|
end
|
629
781
|
|
630
782
|
def outline_method(o, klass, obj)
|
631
|
-
|
783
|
+
begin
|
784
|
+
singleton_class = M_SINGLETON_CLASS.bind_call(obj)
|
785
|
+
rescue TypeError
|
786
|
+
singleton_class = nil
|
787
|
+
end
|
788
|
+
|
632
789
|
maps = class_method_map((singleton_class || klass).ancestors)
|
633
790
|
maps.each do |mod, methods|
|
634
791
|
name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods"
|
@@ -648,16 +805,48 @@ module DEBUGGER__
|
|
648
805
|
|
649
806
|
## cmd: breakpoint
|
650
807
|
|
808
|
+
# TODO: support non-ASCII Constant name
|
809
|
+
def constant_name? name
|
810
|
+
case name
|
811
|
+
when /\A::\b/
|
812
|
+
constant_name? $~.post_match
|
813
|
+
when /\A[A-Z]\w*/
|
814
|
+
post = $~.post_match
|
815
|
+
if post.empty?
|
816
|
+
true
|
817
|
+
else
|
818
|
+
constant_name? post
|
819
|
+
end
|
820
|
+
else
|
821
|
+
false
|
822
|
+
end
|
823
|
+
end
|
824
|
+
|
651
825
|
def make_breakpoint args
|
652
826
|
case args.first
|
653
827
|
when :method
|
654
828
|
klass_name, op, method_name, cond, cmd, path = args[1..]
|
655
|
-
bp = MethodBreakpoint.new(current_frame
|
829
|
+
bp = MethodBreakpoint.new(current_frame&.eval_binding || TOPLEVEL_BINDING, klass_name, op, method_name, cond: cond, command: cmd, path: path)
|
656
830
|
begin
|
657
831
|
bp.enable
|
832
|
+
rescue NameError => e
|
833
|
+
if bp.klass
|
834
|
+
puts "Unknown method name: \"#{e.name}\""
|
835
|
+
else
|
836
|
+
# klass_name can not be evaluated
|
837
|
+
if constant_name? klass_name
|
838
|
+
puts "Unknown constant name: \"#{e.name}\""
|
839
|
+
else
|
840
|
+
# only Class name is allowed
|
841
|
+
puts "Not a constant name: \"#{klass_name}\""
|
842
|
+
bp = nil
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
Session.activate_method_added_trackers if bp
|
658
847
|
rescue Exception => e
|
659
|
-
puts e.
|
660
|
-
|
848
|
+
puts e.inspect
|
849
|
+
bp = nil
|
661
850
|
end
|
662
851
|
|
663
852
|
bp
|
@@ -672,8 +861,18 @@ module DEBUGGER__
|
|
672
861
|
class SuspendReplay < Exception
|
673
862
|
end
|
674
863
|
|
864
|
+
if ::Fiber.respond_to?(:blocking)
|
865
|
+
private def fiber_blocking
|
866
|
+
::Fiber.blocking{yield}
|
867
|
+
end
|
868
|
+
else
|
869
|
+
private def fiber_blocking
|
870
|
+
yield
|
871
|
+
end
|
872
|
+
end
|
873
|
+
|
675
874
|
def wait_next_action
|
676
|
-
wait_next_action_
|
875
|
+
fiber_blocking{wait_next_action_}
|
677
876
|
rescue SuspendReplay
|
678
877
|
replay_suspend
|
679
878
|
end
|
@@ -693,6 +892,7 @@ module DEBUGGER__
|
|
693
892
|
set_mode :waiting if !waiting?
|
694
893
|
cmds = @q_cmd.pop
|
695
894
|
# pp [self, cmds: cmds]
|
895
|
+
|
696
896
|
break unless cmds
|
697
897
|
ensure
|
698
898
|
set_mode :running
|
@@ -710,8 +910,9 @@ module DEBUGGER__
|
|
710
910
|
|
711
911
|
case step_type
|
712
912
|
when :in
|
913
|
+
iter = iter || 1
|
713
914
|
if @recorder&.replaying?
|
714
|
-
@recorder.step_forward
|
915
|
+
@recorder.step_forward iter
|
715
916
|
raise SuspendReplay
|
716
917
|
else
|
717
918
|
step_tp iter do
|
@@ -724,6 +925,7 @@ module DEBUGGER__
|
|
724
925
|
frame = @target_frames.first
|
725
926
|
path = frame.location.absolute_path || "!eval:#{frame.path}"
|
726
927
|
line = frame.location.lineno
|
928
|
+
label = frame.location.base_label
|
727
929
|
|
728
930
|
if frame.iseq
|
729
931
|
frame.iseq.traceable_lines_norec(lines = {})
|
@@ -735,35 +937,89 @@ module DEBUGGER__
|
|
735
937
|
|
736
938
|
depth = @target_frames.first.frame_depth
|
737
939
|
|
738
|
-
step_tp iter do
|
940
|
+
step_tp iter do |tp|
|
739
941
|
loc = caller_locations(2, 1).first
|
740
942
|
loc_path = loc.absolute_path || "!eval:#{loc.path}"
|
943
|
+
loc_label = loc.base_label
|
944
|
+
loc_depth = DEBUGGER__.frame_depth - 3
|
741
945
|
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
946
|
+
case
|
947
|
+
when loc_depth == depth && loc_label == label
|
948
|
+
true
|
949
|
+
when loc_depth < depth
|
950
|
+
# lower stack depth
|
951
|
+
true
|
952
|
+
when (next_line &&
|
953
|
+
loc_path == path &&
|
954
|
+
(loc_lineno = loc.lineno) > line &&
|
955
|
+
loc_lineno <= next_line)
|
956
|
+
# different frame (maybe block) but the line is before next_line
|
957
|
+
true
|
958
|
+
end
|
749
959
|
end
|
750
960
|
break
|
751
961
|
|
752
962
|
when :finish
|
753
963
|
finish_frames = (iter || 1) - 1
|
754
|
-
|
964
|
+
frame = @target_frames.first
|
965
|
+
goal_depth = frame.frame_depth - finish_frames - (frame.has_return_value ? 1 : 0)
|
755
966
|
|
756
967
|
step_tp nil, [:return, :b_return] do
|
757
968
|
DEBUGGER__.frame_depth - 3 <= goal_depth ? true : false
|
758
969
|
end
|
759
970
|
break
|
760
971
|
|
972
|
+
when :until
|
973
|
+
location = iter&.strip
|
974
|
+
frame = @target_frames.first
|
975
|
+
depth = frame.frame_depth - (frame.has_return_value ? 1 : 0)
|
976
|
+
target_location_label = frame.location.base_label
|
977
|
+
|
978
|
+
case location
|
979
|
+
when nil, /\A(?:(.+):)?(\d+)\z/
|
980
|
+
no_loc = !location
|
981
|
+
file = $1 || frame.location.path
|
982
|
+
line = ($2 || frame.location.lineno + 1).to_i
|
983
|
+
|
984
|
+
step_tp nil, [:line, :return] do |tp|
|
985
|
+
if tp.event == :line
|
986
|
+
next false if no_loc && depth < DEBUGGER__.frame_depth - 3
|
987
|
+
next false unless tp.path.end_with?(file)
|
988
|
+
next false unless tp.lineno >= line
|
989
|
+
true
|
990
|
+
else
|
991
|
+
true if depth >= DEBUGGER__.frame_depth - 3 &&
|
992
|
+
caller_locations(2, 1).first.base_label == target_location_label
|
993
|
+
# TODO: imcomplete condition
|
994
|
+
end
|
995
|
+
end
|
996
|
+
else
|
997
|
+
pat = location
|
998
|
+
if /\A\/(.+)\/\z/ =~ pat
|
999
|
+
pat = Regexp.new($1)
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
step_tp nil, [:call, :c_call, :return] do |tp|
|
1003
|
+
case tp.event
|
1004
|
+
when :call, :c_call
|
1005
|
+
true if pat === tp.callee_id.to_s
|
1006
|
+
else # :return, :b_return
|
1007
|
+
true if depth >= DEBUGGER__.frame_depth - 3 &&
|
1008
|
+
caller_locations(2, 1).first.base_label == target_location_label
|
1009
|
+
# TODO: imcomplete condition
|
1010
|
+
end
|
1011
|
+
end
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
break
|
1015
|
+
|
761
1016
|
when :back
|
1017
|
+
iter = iter || 1
|
762
1018
|
if @recorder&.can_step_back?
|
763
1019
|
unless @recorder.backup_frames
|
764
1020
|
@recorder.backup_frames = @target_frames
|
765
1021
|
end
|
766
|
-
@recorder.step_back
|
1022
|
+
@recorder.step_back iter
|
767
1023
|
raise SuspendReplay
|
768
1024
|
else
|
769
1025
|
puts "Can not step back more."
|
@@ -800,13 +1056,6 @@ module DEBUGGER__
|
|
800
1056
|
end
|
801
1057
|
when :call
|
802
1058
|
result = frame_eval(eval_src)
|
803
|
-
when :irb
|
804
|
-
begin
|
805
|
-
result = frame_eval('binding.irb')
|
806
|
-
ensure
|
807
|
-
# workaround: https://github.com/ruby/debug/issues/308
|
808
|
-
Reline.prompt_proc = nil if defined? Reline
|
809
|
-
end
|
810
1059
|
when :display, :try_display
|
811
1060
|
failed_results = []
|
812
1061
|
eval_src.each_with_index{|src, i|
|
@@ -853,6 +1102,7 @@ module DEBUGGER__
|
|
853
1102
|
else
|
854
1103
|
raise "unsupported frame operation: #{arg.inspect}"
|
855
1104
|
end
|
1105
|
+
|
856
1106
|
event! :result, nil
|
857
1107
|
|
858
1108
|
when :show
|
@@ -866,6 +1116,10 @@ module DEBUGGER__
|
|
866
1116
|
when :list
|
867
1117
|
show_src(update_line: true, **(args.first || {}))
|
868
1118
|
|
1119
|
+
when :whereami
|
1120
|
+
show_src ignore_show_line: true
|
1121
|
+
show_frames CONFIG[:show_frames]
|
1122
|
+
|
869
1123
|
when :edit
|
870
1124
|
show_by_editor(args.first)
|
871
1125
|
|
@@ -881,11 +1135,13 @@ module DEBUGGER__
|
|
881
1135
|
|
882
1136
|
when :ivars
|
883
1137
|
pat = args.shift
|
884
|
-
|
1138
|
+
expr = args.shift
|
1139
|
+
show_ivars pat, expr
|
885
1140
|
|
886
1141
|
when :consts
|
887
1142
|
pat = args.shift
|
888
|
-
|
1143
|
+
expr = args.shift
|
1144
|
+
show_consts pat, expr
|
889
1145
|
|
890
1146
|
when :globals
|
891
1147
|
pat = args.shift
|
@@ -929,7 +1185,7 @@ module DEBUGGER__
|
|
929
1185
|
begin
|
930
1186
|
obj = frame_eval args.shift, re_raise: true
|
931
1187
|
opt = args.shift
|
932
|
-
obj_inspect = obj
|
1188
|
+
obj_inspect = DEBUGGER__.safe_inspect(obj)
|
933
1189
|
|
934
1190
|
width = 50
|
935
1191
|
|
@@ -937,7 +1193,7 @@ module DEBUGGER__
|
|
937
1193
|
obj_inspect = truncate(obj_inspect, width: width)
|
938
1194
|
end
|
939
1195
|
|
940
|
-
event! :result, :trace_pass, obj
|
1196
|
+
event! :result, :trace_pass, M_OBJECT_ID.bind_call(obj), obj_inspect, opt
|
941
1197
|
rescue => e
|
942
1198
|
puts e.message
|
943
1199
|
event! :result, nil
|
@@ -954,8 +1210,8 @@ module DEBUGGER__
|
|
954
1210
|
# enable recording
|
955
1211
|
if !@recorder
|
956
1212
|
@recorder = Recorder.new
|
957
|
-
@recorder.enable
|
958
1213
|
end
|
1214
|
+
@recorder.enable
|
959
1215
|
when :off
|
960
1216
|
if @recorder&.enabled?
|
961
1217
|
@recorder.disable
|
@@ -971,6 +1227,8 @@ module DEBUGGER__
|
|
971
1227
|
end
|
972
1228
|
event! :result, nil
|
973
1229
|
|
1230
|
+
when :quit
|
1231
|
+
sleep # wait for SystemExit
|
974
1232
|
when :dap
|
975
1233
|
process_dap args
|
976
1234
|
when :cdp
|
@@ -983,8 +1241,45 @@ module DEBUGGER__
|
|
983
1241
|
rescue SuspendReplay, SystemExit, Interrupt
|
984
1242
|
raise
|
985
1243
|
rescue Exception => e
|
986
|
-
|
1244
|
+
STDERR.puts e.cause.inspect
|
1245
|
+
STDERR.puts e.inspect
|
1246
|
+
Thread.list.each{|th|
|
1247
|
+
STDERR.puts "@@@ #{th}"
|
1248
|
+
th.backtrace.each{|b|
|
1249
|
+
STDERR.puts " > #{b}"
|
1250
|
+
}
|
1251
|
+
}
|
1252
|
+
p ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
|
987
1253
|
raise
|
1254
|
+
ensure
|
1255
|
+
@returning = false
|
1256
|
+
end
|
1257
|
+
|
1258
|
+
def debug_event(ev, args)
|
1259
|
+
DEBUGGER__.debug{
|
1260
|
+
args = args.map { |arg| DEBUGGER__.safe_inspect(arg) }
|
1261
|
+
"#{inspect} sends Event { type: #{ev.inspect}, args: #{args} } to Session"
|
1262
|
+
}
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
def debug_mode(old_mode, new_mode)
|
1266
|
+
DEBUGGER__.debug{
|
1267
|
+
"#{inspect} changes mode (#{old_mode} -> #{new_mode})"
|
1268
|
+
}
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
def debug_cmd(cmds)
|
1272
|
+
DEBUGGER__.debug{
|
1273
|
+
cmd, *args = *cmds
|
1274
|
+
args = args.map { |arg| DEBUGGER__.safe_inspect(arg) }
|
1275
|
+
"#{inspect} receives Cmd { type: #{cmd.inspect}, args: #{args} } from Session"
|
1276
|
+
}
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
def debug_suspend(event)
|
1280
|
+
DEBUGGER__.debug{
|
1281
|
+
"#{inspect} is suspended for #{event.inspect}"
|
1282
|
+
}
|
988
1283
|
end
|
989
1284
|
|
990
1285
|
class Recorder
|
@@ -1001,8 +1296,8 @@ module DEBUGGER__
|
|
1001
1296
|
|
1002
1297
|
@tp_recorder ||= TracePoint.new(:line){|tp|
|
1003
1298
|
next unless Thread.current == thread
|
1004
|
-
|
1005
|
-
next if tp.path
|
1299
|
+
# can't be replaced by skip_location
|
1300
|
+
next if skip_internal_path?(tp.path)
|
1006
1301
|
loc = caller_locations(1, 1).first
|
1007
1302
|
next if skip_location?(loc)
|
1008
1303
|
|
@@ -1016,10 +1311,14 @@ module DEBUGGER__
|
|
1016
1311
|
frame._callee = b.eval('__callee__')
|
1017
1312
|
end
|
1018
1313
|
}
|
1019
|
-
|
1314
|
+
append(frames)
|
1020
1315
|
}
|
1021
1316
|
end
|
1022
1317
|
|
1318
|
+
def append frames
|
1319
|
+
@log << frames
|
1320
|
+
end
|
1321
|
+
|
1023
1322
|
def enable
|
1024
1323
|
unless @tp_recorder.enabled?
|
1025
1324
|
@log.clear
|
@@ -1038,12 +1337,18 @@ module DEBUGGER__
|
|
1038
1337
|
@tp_recorder.enabled?
|
1039
1338
|
end
|
1040
1339
|
|
1041
|
-
def step_back
|
1042
|
-
@index +=
|
1340
|
+
def step_back iter
|
1341
|
+
@index += iter
|
1342
|
+
if @index > @log.size
|
1343
|
+
@index = @log.size
|
1344
|
+
end
|
1043
1345
|
end
|
1044
1346
|
|
1045
|
-
def step_forward
|
1046
|
-
@index -=
|
1347
|
+
def step_forward iter
|
1348
|
+
@index -= iter
|
1349
|
+
if @index < 0
|
1350
|
+
@index = 0
|
1351
|
+
end
|
1047
1352
|
end
|
1048
1353
|
|
1049
1354
|
def step_reset
|