debug 1.0.0.beta4 → 1.0.0.beta8

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.
@@ -1,55 +1,77 @@
1
- require 'irb/color' # IRB::Color.colorize_code
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'color'
2
4
 
3
5
  module DEBUGGER__
4
6
  class SourceRepository
7
+ SrcInfo = Struct.new(:src, :colored)
8
+
5
9
  def initialize
6
- @files = {} # filename => [src, iseq]
7
- @color_files = {}
10
+ @files = {} # filename => SrcInfo
8
11
  end
9
12
 
10
13
  def add iseq, src
11
- path = iseq.absolute_path
12
- path = '-e' if iseq.path == '-e'
13
- add_path path, src: src
14
+ if (path = iseq.absolute_path) && File.exist?(path)
15
+ add_path path
16
+ elsif src
17
+ add_iseq iseq, src
18
+ end
14
19
  end
15
20
 
16
- def add_path path, src: nil
17
- case
18
- when src
19
- if File.file?(path)
20
- path = '(eval)' + path
21
- src = nil
22
- end
23
- when path == '-e'
24
- when path
25
- begin
26
- src = File.read(path)
27
- rescue SystemCallError
28
- end
29
- else
30
- src = nil
21
+ def all_iseq iseq, rs = []
22
+ rs << iseq
23
+ iseq.each_child{|ci|
24
+ all_iseq(ci, rs)
25
+ }
26
+ rs
27
+ end
28
+
29
+ private def add_iseq iseq, src
30
+ line = iseq.first_line
31
+ if line > 1
32
+ src = ("\n" * (line - 1)) + src
31
33
  end
34
+ si = SrcInfo.new(src.lines)
32
35
 
33
- if src
36
+ all_iseq(iseq).each{|e|
37
+ e.instance_variable_set(:@debugger_si, si)
38
+ e.freeze
39
+ }
40
+ end
41
+
42
+ private def add_path path
43
+ begin
44
+ src = File.read(path)
34
45
  src = src.gsub("\r\n", "\n") # CRLF -> LF
35
- @files[path] = src.lines
46
+ @files[path] = SrcInfo.new(src.lines)
47
+ rescue SystemCallError
36
48
  end
37
49
  end
38
50
 
39
- def get path
40
- if @files.has_key? path
51
+ private def get_si iseq
52
+ return unless iseq
53
+
54
+ if iseq.instance_variable_defined?(:@debugger_si)
55
+ iseq.instance_variable_get(:@debugger_si)
56
+ elsif @files.has_key?(path = iseq.absolute_path)
41
57
  @files[path]
42
- else
43
- add_path path
58
+ elsif path
59
+ add_path(path)
44
60
  end
45
61
  end
46
62
 
47
- def get_colored path
48
- if src_lines = @color_files[path]
49
- return src_lines
50
- else
51
- if src_lines = get(path)
52
- @color_files[path] = IRB::Color.colorize_code(src_lines.join).lines
63
+ def get iseq
64
+ if si = get_si(iseq)
65
+ si.src
66
+ end
67
+ end
68
+
69
+ include Color
70
+
71
+ def get_colored iseq
72
+ if si = get_si(iseq)
73
+ si.colored || begin
74
+ si.colored = colorize_code(si.src.join).lines
53
75
  end
54
76
  end
55
77
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'session'
4
+ return unless defined?(DEBUGGER__)
5
+ DEBUGGER__.start
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'objspace'
2
4
  require 'pp'
3
- require 'irb/color'
5
+
4
6
  require_relative 'frame_info'
7
+ require_relative 'color'
5
8
 
6
9
  module DEBUGGER__
7
10
  class ThreadClient
@@ -12,23 +15,46 @@ module DEBUGGER__
12
15
  end
13
16
  end
14
17
 
18
+ include Color
19
+
15
20
  attr_reader :location, :thread, :mode, :id
16
21
 
17
- def colorize str, color
18
- if CONFIG[:use_colorize]
19
- IRB::Color.colorize str, color
20
- else
21
- str
22
- end
22
+ def assemble_arguments(args)
23
+ args.map do |arg|
24
+ "#{colorize_cyan(arg[:name])}=#{arg[:value]}"
25
+ end.join(", ")
23
26
  end
24
27
 
25
28
  def default_frame_formatter frame
26
- call_identifier_str = colorize(frame.call_identifier_str, [:BLUE, :BOLD])
27
- location_str = colorize(frame.location_str, [:YELLOW])
29
+ call_identifier_str =
30
+ case frame.frame_type
31
+ when :block
32
+ level, block_loc, args = frame.block_identifier
33
+
34
+ if !args.empty?
35
+ args_str = " {|#{assemble_arguments(args)}|}"
36
+ end
37
+
38
+ "#{colorize_blue("block")}#{args_str} in #{colorize_blue(block_loc + level)}"
39
+ when :method
40
+ ci, args = frame.method_identifier
41
+
42
+ if !args.empty?
43
+ args_str = "(#{assemble_arguments(args)})"
44
+ end
45
+
46
+ "#{colorize_blue(ci)}#{args_str}"
47
+ when :c
48
+ colorize_blue(frame.c_identifier)
49
+ when :other
50
+ colorize_blue(frame.other_identifier)
51
+ end
52
+
53
+ location_str = colorize(frame.location_str, [:GREEN])
28
54
  result = "#{call_identifier_str} at #{location_str}"
29
55
 
30
56
  if return_str = frame.return_str
31
- return_str = colorize(frame.return_str, [:MAGENTA])
57
+ return_str = colorize(frame.return_str, [:MAGENTA, :BOLD])
32
58
  result += " #=> #{return_str}"
33
59
  end
34
60
 
@@ -38,14 +64,20 @@ module DEBUGGER__
38
64
  def initialize id, q_evt, q_cmd, thr = Thread.current
39
65
  @id = id
40
66
  @thread = thr
67
+ @target_frames = nil
41
68
  @q_evt = q_evt
42
69
  @q_cmd = q_cmd
43
70
  @step_tp = nil
44
71
  @output = []
45
- @src_lines_on_stop = (::DEBUGGER__::CONFIG[:show_src_lines] || 10).to_i
46
- @show_frames_on_stop = (::DEBUGGER__::CONFIG[:show_frames] || 2).to_i
47
72
  @frame_formatter = method(:default_frame_formatter)
73
+ @var_map = {} # { thread_local_var_id => obj } for DAP
48
74
  set_mode nil
75
+
76
+ ::DEBUGGER__.info("Thread \##{@id} is created.")
77
+ end
78
+
79
+ def name
80
+ "##{@id} #{@thread.name || @thread.backtrace.last}"
49
81
  end
50
82
 
51
83
  def close
@@ -71,8 +103,14 @@ module DEBUGGER__
71
103
  @q_cmd << req
72
104
  end
73
105
 
106
+ def generate_info
107
+ return unless current_frame
108
+
109
+ { location: current_frame.location_str, line: current_frame.location.lineno }
110
+ end
111
+
74
112
  def event! ev, *args
75
- @q_evt << [self, @output, ev, *args]
113
+ @q_evt << [self, @output, ev, generate_info, *args]
76
114
  @output = []
77
115
  end
78
116
 
@@ -100,6 +138,11 @@ module DEBUGGER__
100
138
  wait_next_action
101
139
  end
102
140
 
141
+ def on_init name
142
+ event! :init, name
143
+ wait_next_action
144
+ end
145
+
103
146
  def on_breakpoint tp, bp
104
147
  on_suspend tp.event, tp, bp: bp
105
148
  end
@@ -116,11 +159,16 @@ module DEBUGGER__
116
159
  cf.has_return_value = true
117
160
  cf.return_value = tp.return_value
118
161
  end
162
+
163
+ if CatchBreakpoint === bp
164
+ cf.has_raised_exception = true
165
+ cf.raised_exception = bp.last_exc
166
+ end
119
167
  end
120
168
 
121
169
  if event != :pause
122
- show_src max_lines: @src_lines_on_stop
123
- show_frames @show_frames_on_stop
170
+ show_src max_lines: (::DEBUGGER__::CONFIG[:show_src_lines] || 10)
171
+ show_frames ::DEBUGGER__::CONFIG[:show_frames] || 2
124
172
 
125
173
  if bp
126
174
  event! :suspend, :breakpoint, bp.key
@@ -187,7 +235,6 @@ module DEBUGGER__
187
235
  start_line: nil,
188
236
  end_line: nil,
189
237
  dir: +1)
190
-
191
238
  if @target_frames && frame = @target_frames[frame_index]
192
239
  if file_lines = frame.file_lines
193
240
  frame_line = frame.location.lineno - 1
@@ -229,6 +276,10 @@ module DEBUGGER__
229
276
  puts "# No sourcefile available for #{frame.path}"
230
277
  end
231
278
  end
279
+ rescue Exception => e
280
+ p e
281
+ pp e.backtrace
282
+ exit!
232
283
  end
233
284
 
234
285
  def show_by_editor path = nil
@@ -253,16 +304,39 @@ module DEBUGGER__
253
304
  end
254
305
  end
255
306
 
307
+ def puts_variable_info label, obj
308
+ info = "#{colorize_cyan(label)} => #{colored_inspect(obj)}".lines
309
+ w = SESSION.width
310
+ max_inspect_lines = CONFIG[:show_inspect_lines] || 10
311
+
312
+ if (max_inspect_lines > 0 && (info.size > max_inspect_lines)) || info.any?{|l| l.size > w}
313
+ info = "#{colorize_cyan(label)} => #{colored_inspect(obj, no_color: true)}".lines
314
+ if max_inspect_lines > 0 && info.size > max_inspect_lines
315
+ info = info.first(max_inspect_lines - 2) +
316
+ ["...(#{info.size - (max_inspect_lines - 1)} lines)\n" + info.last]
317
+ end
318
+ info.map!{|l|
319
+ l.length > w ? l[0..(w-4)] + '...' : l
320
+ }
321
+ end
322
+
323
+ puts info
324
+ end
325
+
256
326
  def show_locals
257
327
  if s = current_frame&.self
258
- puts " %self => #{s}"
328
+ puts_variable_info '%self', s
259
329
  end
260
330
  if current_frame&.has_return_value
261
- puts " %return => #{current_frame.return_value}"
331
+ puts_variable_info '%return', current_frame.return_value
332
+ end
333
+ if current_frame&.has_raised_exception
334
+ puts_variable_info "%raised", current_frame.raised_exception
262
335
  end
263
336
  if b = current_frame&.binding
264
337
  b.local_variables.each{|loc|
265
- puts " #{loc} => #{b.local_variable_get(loc).inspect}"
338
+ value = b.local_variable_get(loc)
339
+ puts_variable_info loc, value
266
340
  }
267
341
  end
268
342
  end
@@ -270,22 +344,27 @@ module DEBUGGER__
270
344
  def show_ivars
271
345
  if s = current_frame&.self
272
346
  s.instance_variables.each{|iv|
273
- puts " #{iv} => #{s.instance_variable_get(iv)}"
347
+ value = s.instance_variable_get(iv)
348
+ puts_variable_info iv, value
274
349
  }
275
350
  end
276
351
  end
277
352
 
353
+ def instance_eval_for_cmethod frame_self, src
354
+ frame_self.instance_eval(src)
355
+ end
356
+
278
357
  def frame_eval src, re_raise: false
279
358
  begin
280
359
  @success_last_eval = false
281
360
 
282
361
  b = current_frame.binding
283
362
  result = if b
284
- f, l = b.source_location
363
+ f, _l = b.source_location
285
364
  b.eval(src, "(rdbg)/#{f}")
286
365
  else
287
366
  frame_self = current_frame.self
288
- frame_self.instance_eval(src)
367
+ instance_eval_for_cmethod(frame_self, src)
289
368
  end
290
369
  @success_last_eval = true
291
370
  result
@@ -303,21 +382,35 @@ module DEBUGGER__
303
382
  end
304
383
  end
305
384
 
306
- def frame_str(i)
385
+ def frame_str(i, frame: @target_frames[i])
307
386
  cur_str = (@current_frame_index == i ? '=>' : ' ')
308
387
  prefix = "#{cur_str}##{i}"
309
- frame = @target_frames[i]
310
388
  frame_string = @frame_formatter.call(frame)
311
389
  "#{prefix}\t#{frame_string}"
312
390
  end
313
391
 
314
- def show_frames max = (@target_frames || []).size
315
- if max > 0 && frames = @target_frames
316
- size = @target_frames.size
317
- max += 1 if size == max + 1
392
+ def show_frames max = nil, pattern = nil
393
+ if @target_frames && (max ||= @target_frames.size) > 0
394
+ frames = []
395
+ @target_frames.each_with_index{|f, i|
396
+ next if pattern && !(f.name.match?(pattern) || f.location_str.match?(pattern))
397
+ next if CONFIG[:skip_path] && CONFIG[:skip_path].any?{|pat|
398
+ case pat
399
+ when String
400
+ f.location_str.start_with?(pat)
401
+ when Regexp
402
+ f.location_str.match?(pat)
403
+ end
404
+ }
405
+
406
+ frames << [i, f]
407
+ }
408
+
409
+ size = frames.size
318
410
  max.times{|i|
319
- break if i >= size
320
- puts frame_str(i)
411
+ break unless frames[i]
412
+ index, frame = frames[i]
413
+ puts frame_str(index, frame: frame)
321
414
  }
322
415
  puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size
323
416
  end
@@ -345,18 +438,22 @@ module DEBUGGER__
345
438
  end
346
439
  end
347
440
 
348
- def add_breakpoint args
441
+ def make_breakpoint args
349
442
  case args.first
350
443
  when :method
351
- klass_name, op, method_name, cond = args[1..]
352
- bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond)
444
+ klass_name, op, method_name, cond, cmd = args[1..]
445
+ bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond, command: cmd)
353
446
  begin
354
447
  bp.enable
355
448
  rescue Exception => e
356
449
  puts e.message
357
450
  ::DEBUGGER__::METHOD_ADDED_TRACKER.enable
358
451
  end
359
- event! :result, :method_breakpoint, bp
452
+
453
+ bp
454
+ when :watch
455
+ ivar, object, result = args[1..]
456
+ WatchIVarBreakpoint.new(ivar, object, result)
360
457
  else
361
458
  raise "unknown breakpoint: #{args}"
362
459
  end
@@ -369,6 +466,8 @@ module DEBUGGER__
369
466
  def wait_next_action
370
467
  set_mode :wait_next_action
371
468
 
469
+ SESSION.check_forked
470
+
372
471
  while cmds = @q_cmd.pop
373
472
  # pp [self, cmds: cmds]
374
473
 
@@ -386,20 +485,28 @@ module DEBUGGER__
386
485
  frame = @target_frames.first
387
486
  path = frame.location.absolute_path || "!eval:#{frame.path}"
388
487
  line = frame.location.lineno
389
- frame.iseq.traceable_lines_norec(lines = {})
390
- next_line = lines.keys.bsearch{|e| e > line}
391
- if !next_line && (last_line = frame.iseq.last_line) > line
392
- next_line = last_line
488
+
489
+ if frame.iseq
490
+ frame.iseq.traceable_lines_norec(lines = {})
491
+ next_line = lines.keys.bsearch{|e| e > line}
492
+ if !next_line && (last_line = frame.iseq.last_line) > line
493
+ next_line = last_line
494
+ end
393
495
  end
496
+
394
497
  depth = @target_frames.first.frame_depth
395
498
 
396
499
  step_tp{
397
500
  loc = caller_locations(2, 1).first
398
501
  loc_path = loc.absolute_path || "!eval:#{loc.path}"
399
502
 
503
+ # same stack depth
504
+ (DEBUGGER__.frame_depth - 3 <= depth) ||
505
+
506
+ # different frame
400
507
  (next_line && loc_path == path &&
401
- (loc_lineno = loc.lineno) > line && loc_lineno <= next_line) ||
402
- (DEBUGGER__.frame_depth - 3 < depth)
508
+ (loc_lineno = loc.lineno) > line &&
509
+ loc_lineno <= next_line)
403
510
  }
404
511
  when :finish
405
512
  depth = @target_frames.first.frame_depth
@@ -414,20 +521,16 @@ module DEBUGGER__
414
521
  when :eval
415
522
  eval_type, eval_src = *args
416
523
 
417
- case eval_type
418
- when :display, :try_display
419
- else
420
- result = frame_eval(eval_src)
421
- end
422
524
  result_type = nil
423
525
 
424
526
  case eval_type
425
527
  when :p
528
+ result = frame_eval(eval_src)
426
529
  puts "=> " + result.inspect
427
530
  when :pp
531
+ result = frame_eval(eval_src)
428
532
  puts "=> "
429
- PP.pp(result, out = ''.dup)
430
- puts out
533
+ puts color_pp(result, SESSION.width)
431
534
  when :call
432
535
  result = frame_eval(eval_src)
433
536
  when :display, :try_display
@@ -442,14 +545,6 @@ module DEBUGGER__
442
545
 
443
546
  result_type = eval_type
444
547
  result = failed_results
445
- when :watch
446
- if @success_last_eval
447
- puts "#{eval_src} = #{result}"
448
- result = WatchExprBreakpoint.new(eval_src, result)
449
- result_type = :watch
450
- else
451
- result = nil
452
- end
453
548
  else
454
549
  raise "unknown error option: #{args.inspect}"
455
550
  end
@@ -490,7 +585,8 @@ module DEBUGGER__
490
585
 
491
586
  case type
492
587
  when :backtrace
493
- show_frames
588
+ max_lines, pattern = *args
589
+ show_frames max_lines, pattern
494
590
 
495
591
  when :list
496
592
  show_src(update_line: true, **(args.first || {}))
@@ -512,9 +608,30 @@ module DEBUGGER__
512
608
  end
513
609
 
514
610
  event! :result, nil
515
-
516
611
  when :breakpoint
517
- add_breakpoint args
612
+ case args[0]
613
+ when :method
614
+ bp = make_breakpoint args
615
+ event! :result, :method_breakpoint, bp
616
+ when :watch
617
+ ivar = args[1]
618
+ result = frame_eval(ivar)
619
+
620
+ if @success_last_eval
621
+ object =
622
+ if b = current_frame.binding
623
+ b.receiver
624
+ else
625
+ current_frame.self
626
+ end
627
+ bp = make_breakpoint [:watch, ivar, object, result]
628
+ event! :result, :watch_breakpoint, bp
629
+ else
630
+ event! :result, nil
631
+ end
632
+ end
633
+ when :dap
634
+ process_dap args
518
635
  else
519
636
  raise [cmd, *args].inspect
520
637
  end
@@ -523,7 +640,7 @@ module DEBUGGER__
523
640
  rescue SystemExit
524
641
  raise
525
642
  rescue Exception => e
526
- pp [__FILE__, __LINE__, e, e.backtrace]
643
+ pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
527
644
  raise
528
645
  ensure
529
646
  set_mode nil