debug 1.0.0.beta2 → 1.0.0.beta7

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,27 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'color'
4
+
1
5
  module DEBUGGER__
2
6
  class SourceRepository
7
+ SrcInfo = Struct.new(:src, :colored)
8
+
3
9
  def initialize
4
- @files = {} # filename => [src, iseq]
10
+ @files = {} # filename => SrcInfo
5
11
  end
6
12
 
7
13
  def add iseq, src
8
- path = iseq.absolute_path
9
- path = '-e' if iseq.path == '-e'
14
+ if (path = iseq.absolute_path) && File.exist?(path)
15
+ add_path path
16
+ elsif src
17
+ add_iseq iseq, src
18
+ end
19
+ end
10
20
 
11
- case
12
- when path = iseq.absolute_path
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
33
+ end
34
+ si = SrcInfo.new(src.lines)
35
+
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
13
44
  src = File.read(path)
14
- when iseq.path == '-e'
15
- path = '-e'
16
- else
17
- src = nil
45
+ src = src.gsub("\r\n", "\n") # CRLF -> LF
46
+ @files[path] = SrcInfo.new(src.lines)
47
+ rescue SystemCallError
18
48
  end
49
+ end
50
+
51
+ private def get_si iseq
52
+ return unless iseq
19
53
 
20
- @files[path] = src.lines if src
54
+ if iseq.instance_variable_defined?(:@debugger_si)
55
+ iseq.instance_variable_get(:@debugger_si)
56
+ elsif @files.has_key?(path = iseq.absolute_path)
57
+ @files[path]
58
+ elsif path
59
+ add_path(path)
60
+ end
21
61
  end
22
62
 
23
- def get path
24
- @files[path]
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
75
+ end
76
+ end
25
77
  end
26
78
  end
27
79
  end
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'objspace'
2
4
  require 'pp'
3
5
 
6
+ require_relative 'frame_info'
7
+ require_relative 'color'
8
+
4
9
  module DEBUGGER__
5
10
  class ThreadClient
6
11
  def self.current
@@ -10,18 +15,73 @@ module DEBUGGER__
10
15
  end
11
16
  end
12
17
 
18
+ include Color
19
+
13
20
  attr_reader :location, :thread, :mode, :id
14
21
 
22
+ def assemble_arguments(args)
23
+ args.map do |arg|
24
+ "#{colorize_cyan(arg[:name])}=#{arg[:value]}"
25
+ end.join(", ")
26
+ end
27
+
28
+ def default_frame_formatter frame
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])
54
+ result = "#{call_identifier_str} at #{location_str}"
55
+
56
+ if return_str = frame.return_str
57
+ return_str = colorize(frame.return_str, [:MAGENTA, :BOLD])
58
+ result += " #=> #{return_str}"
59
+ end
60
+
61
+ result
62
+ end
63
+
15
64
  def initialize id, q_evt, q_cmd, thr = Thread.current
16
65
  @id = id
17
66
  @thread = thr
67
+ @target_frames = nil
18
68
  @q_evt = q_evt
19
69
  @q_cmd = q_cmd
20
70
  @step_tp = nil
21
71
  @output = []
22
- @src_lines_on_stop = (DEBUGGER__::CONFIG[:show_src_lines] || 10).to_i
23
- @show_frames_on_stop = (DEBUGGER__::CONFIG[:show_frames] || 2).to_i
72
+ @frame_formatter = method(:default_frame_formatter)
73
+ @var_map = {} # { thread_local_var_id => obj } for DAP
24
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}"
81
+ end
82
+
83
+ def close
84
+ @q_cmd.close
25
85
  end
26
86
 
27
87
  def inspect
@@ -43,8 +103,14 @@ module DEBUGGER__
43
103
  @q_cmd << req
44
104
  end
45
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
+
46
112
  def event! ev, *args
47
- @q_evt << [self, @output, ev, *args]
113
+ @q_evt << [self, @output, ev, generate_info, *args]
48
114
  @output = []
49
115
  end
50
116
 
@@ -72,6 +138,11 @@ module DEBUGGER__
72
138
  wait_next_action
73
139
  end
74
140
 
141
+ def on_init name
142
+ event! :init, name
143
+ wait_next_action
144
+ end
145
+
75
146
  def on_breakpoint tp, bp
76
147
  on_suspend tp.event, tp, bp: bp
77
148
  end
@@ -88,11 +159,16 @@ module DEBUGGER__
88
159
  cf.has_return_value = true
89
160
  cf.return_value = tp.return_value
90
161
  end
162
+
163
+ if CatchBreakpoint === bp
164
+ cf.has_raised_exception = true
165
+ cf.raised_exception = bp.last_exc
166
+ end
91
167
  end
92
168
 
93
169
  if event != :pause
94
- show_src max_lines: @src_lines_on_stop
95
- 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
96
172
 
97
173
  if bp
98
174
  event! :suspend, :breakpoint, bp.key
@@ -125,6 +201,8 @@ module DEBUGGER__
125
201
  next if SESSION.break? tp.path, tp.lineno
126
202
  next if !yield
127
203
  next if tp.path.start_with?(__dir__)
204
+ next unless File.exist?(tp.path) if ::DEBUGGER__::CONFIG[:skip_nosrc]
205
+
128
206
  tp.disable
129
207
  on_suspend tp.event, tp
130
208
  }
@@ -134,6 +212,8 @@ module DEBUGGER__
134
212
  next if thread != Thread.current
135
213
  next if SESSION.break? tp.path, tp.lineno
136
214
  next if !yield
215
+ next unless File.exist?(tp.path) if ::DEBUGGER__::CONFIG[:skip_nosrc]
216
+
137
217
  tp.disable
138
218
  on_suspend tp.event, tp
139
219
  }
@@ -149,23 +229,14 @@ module DEBUGGER__
149
229
  end
150
230
  end
151
231
 
152
- def file_lines path
153
- if (src_lines = SESSION.source(path))
154
- src_lines
155
- elsif File.exist?(path)
156
- File.readlines(path)
157
- end
158
- end
159
-
160
232
  def show_src(frame_index: @current_frame_index,
161
233
  update_line: false,
162
234
  max_lines: 10,
163
235
  start_line: nil,
164
236
  end_line: nil,
165
237
  dir: +1)
166
- #
167
238
  if @target_frames && frame = @target_frames[frame_index]
168
- if file_lines = file_lines(path = frame.location.path)
239
+ if file_lines = frame.file_lines
169
240
  frame_line = frame.location.lineno - 1
170
241
 
171
242
  lines = file_lines.map.with_index do |e, i|
@@ -197,18 +268,24 @@ module DEBUGGER__
197
268
  frame.show_line = end_line
198
269
  end
199
270
 
200
- if start_line != end_line
201
- puts "[#{start_line+1}, #{end_line}] in #{path}" unless update_line
271
+ if start_line != end_line && max_lines
272
+ puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
202
273
  puts lines[start_line ... end_line]
203
274
  end
275
+ else # no file lines
276
+ puts "# No sourcefile available for #{frame.path}"
204
277
  end
205
278
  end
279
+ rescue Exception => e
280
+ p e
281
+ pp e.backtrace
282
+ exit!
206
283
  end
207
284
 
208
285
  def show_by_editor path = nil
209
286
  unless path
210
287
  if @target_frames && frame = @target_frames[@current_frame_index]
211
- path = frame.location.path
288
+ path = frame.path
212
289
  else
213
290
  return # can't get path
214
291
  end
@@ -227,16 +304,39 @@ module DEBUGGER__
227
304
  end
228
305
  end
229
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
+
230
326
  def show_locals
231
327
  if s = current_frame&.self
232
- puts " %self => #{s}"
328
+ puts_variable_info '%self', s
233
329
  end
234
330
  if current_frame&.has_return_value
235
- 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
236
335
  end
237
336
  if b = current_frame&.binding
238
337
  b.local_variables.each{|loc|
239
- puts " #{loc} => #{b.local_variable_get(loc).inspect}"
338
+ value = b.local_variable_get(loc)
339
+ puts_variable_info loc, value
240
340
  }
241
341
  end
242
342
  end
@@ -244,22 +344,27 @@ module DEBUGGER__
244
344
  def show_ivars
245
345
  if s = current_frame&.self
246
346
  s.instance_variables.each{|iv|
247
- puts " #{iv} => #{s.instance_variable_get(iv)}"
347
+ value = s.instance_variable_get(iv)
348
+ puts_variable_info iv, value
248
349
  }
249
350
  end
250
351
  end
251
352
 
353
+ def instance_eval_for_cmethod frame_self, src
354
+ frame_self.instance_eval(src)
355
+ end
356
+
252
357
  def frame_eval src, re_raise: false
253
358
  begin
254
359
  @success_last_eval = false
255
360
 
256
361
  b = current_frame.binding
257
362
  result = if b
258
- f, l = b.source_location
363
+ f, _l = b.source_location
259
364
  b.eval(src, "(rdbg)/#{f}")
260
365
  else
261
366
  frame_self = current_frame.self
262
- frame_self.instance_eval(src)
367
+ instance_eval_for_cmethod(frame_self, src)
263
368
  end
264
369
  @success_last_eval = true
265
370
  result
@@ -277,81 +382,37 @@ module DEBUGGER__
277
382
  end
278
383
  end
279
384
 
280
- def parameters_info b, vars
281
- vars.map{|var|
282
- begin
283
- "#{var}=#{short_inspect(b.local_variable_get(var))}"
284
- rescue NameError, TypeError
285
- nil
286
- end
287
- }.compact.join(', ')
288
- end
289
-
290
- def klass_sig frame
291
- klass = frame.class
292
- if klass == frame.self.singleton_class
293
- "#{frame.self}."
294
- else
295
- "#{frame.class}#"
296
- end
297
- end
298
-
299
- SHORT_INSPECT_LENGTH = 40
300
-
301
- def short_inspect obj
302
- str = obj.inspect
303
- if str.length > SHORT_INSPECT_LENGTH
304
- str[0...SHORT_INSPECT_LENGTH] + '...'
305
- else
306
- str
307
- end
308
- end
309
-
310
- def frame_str i
311
- buff = ''.dup
312
- frame = @target_frames[i]
313
- b = frame.binding
314
-
315
- buff << (@current_frame_index == i ? '--> ' : ' ')
316
- if b
317
- buff << "##{i}\t#{frame.location}"
318
- else
319
- buff << "##{i}\t[C] #{frame.location}"
320
- end
321
-
322
- if b && (iseq = frame.iseq)
323
- if iseq.type == :block
324
- if (argc = iseq.argc) > 0
325
- args = parameters_info b, iseq.locals[0...argc]
326
- buff << " {|#{args}|}"
327
- end
328
- else
329
- if (callee = b.eval('__callee__', __FILE__, __LINE__)) && (argc = iseq.argc) > 0
330
- args = parameters_info b, iseq.locals[0...argc]
331
- ksig = klass_sig frame
332
- buff << " #{ksig}#{callee}(#{args})"
333
- end
334
- end
335
-
336
- if frame.has_return_value
337
- buff << " #=> #{short_inspect(frame.return_value)}"
338
- end
339
- else
340
- # p frame.self
341
- end
385
+ def frame_str(i, frame: @target_frames[i])
386
+ cur_str = (@current_frame_index == i ? '=>' : ' ')
387
+ prefix = "#{cur_str}##{i}"
388
+ frame_string = @frame_formatter.call(frame)
389
+ "#{prefix}\t#{frame_string}"
390
+ end
391
+
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
+ }
342
405
 
343
- buff
344
- end
406
+ frames << [i, f]
407
+ }
345
408
 
346
- def show_frames max = (@target_frames || []).size
347
- if max > 0 && frames = @target_frames
348
- size = @target_frames.size
349
- max += 1 if size == max + 1
409
+ size = frames.size
350
410
  max.times{|i|
351
- break if i >= size
352
- puts frame_str(i)
411
+ break unless frames[i]
412
+ index, frame = frames[i]
413
+ puts frame_str(index, frame: frame)
353
414
  }
354
- puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size
415
+ puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size
355
416
  end
356
417
  end
357
418
 
@@ -377,18 +438,22 @@ module DEBUGGER__
377
438
  end
378
439
  end
379
440
 
380
- def add_breakpoint args
441
+ def make_breakpoint args
381
442
  case args.first
382
443
  when :method
383
- klass_name, op, method_name, cond = args[1..]
384
- 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)
385
446
  begin
386
447
  bp.enable
387
448
  rescue Exception => e
388
449
  puts e.message
389
450
  ::DEBUGGER__::METHOD_ADDED_TRACKER.enable
390
451
  end
391
- event! :result, :method_breakpoint, bp
452
+
453
+ bp
454
+ when :watch
455
+ ivar, object, result = args[1..]
456
+ WatchIVarBreakpoint.new(ivar, object, result)
392
457
  else
393
458
  raise "unknown breakpoint: #{args}"
394
459
  end
@@ -401,6 +466,8 @@ module DEBUGGER__
401
466
  def wait_next_action
402
467
  set_mode :wait_next_action
403
468
 
469
+ SESSION.check_forked
470
+
404
471
  while cmds = @q_cmd.pop
405
472
  # pp [self, cmds: cmds]
406
473
 
@@ -416,22 +483,30 @@ module DEBUGGER__
416
483
  step_tp{true}
417
484
  when :next
418
485
  frame = @target_frames.first
419
- path = frame.location.absolute_path || "!eval:#{frame.location.path}"
486
+ path = frame.location.absolute_path || "!eval:#{frame.path}"
420
487
  line = frame.location.lineno
421
- frame.iseq.traceable_lines_norec(lines = {})
422
- next_line = lines.keys.bsearch{|e| e > line}
423
- if !next_line && (last_line = frame.iseq.last_line) > line
424
- 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
425
495
  end
496
+
426
497
  depth = @target_frames.first.frame_depth
427
498
 
428
499
  step_tp{
429
500
  loc = caller_locations(2, 1).first
430
501
  loc_path = loc.absolute_path || "!eval:#{loc.path}"
431
502
 
503
+ # same stack depth
504
+ (DEBUGGER__.frame_depth - 3 <= depth) ||
505
+
506
+ # different frame
432
507
  (next_line && loc_path == path &&
433
- (loc_lineno = loc.lineno) > line && loc_lineno <= next_line) ||
434
- (DEBUGGER__.frame_depth - 3 < depth)
508
+ (loc_lineno = loc.lineno) > line &&
509
+ loc_lineno <= next_line)
435
510
  }
436
511
  when :finish
437
512
  depth = @target_frames.first.frame_depth
@@ -446,20 +521,16 @@ module DEBUGGER__
446
521
  when :eval
447
522
  eval_type, eval_src = *args
448
523
 
449
- case eval_type
450
- when :display, :try_display
451
- else
452
- result = frame_eval(eval_src)
453
- end
454
524
  result_type = nil
455
525
 
456
526
  case eval_type
457
527
  when :p
528
+ result = frame_eval(eval_src)
458
529
  puts "=> " + result.inspect
459
530
  when :pp
531
+ result = frame_eval(eval_src)
460
532
  puts "=> "
461
- PP.pp(result, out = ''.dup)
462
- puts out
533
+ puts color_pp(result, SESSION.width)
463
534
  when :call
464
535
  result = frame_eval(eval_src)
465
536
  when :display, :try_display
@@ -474,14 +545,6 @@ module DEBUGGER__
474
545
 
475
546
  result_type = eval_type
476
547
  result = failed_results
477
- when :watch
478
- if @success_last_eval
479
- puts "#{eval_src} = #{result}"
480
- result = WatchExprBreakpoint.new(eval_src, result)
481
- result_type = :watch
482
- else
483
- result = nil
484
- end
485
548
  else
486
549
  raise "unknown error option: #{args.inspect}"
487
550
  end
@@ -492,7 +555,7 @@ module DEBUGGER__
492
555
  case type
493
556
  when :up
494
557
  if @current_frame_index + 1 < @target_frames.size
495
- @current_frame_index += 1
558
+ @current_frame_index += 1
496
559
  show_src max_lines: 1
497
560
  show_frame(@current_frame_index)
498
561
  end
@@ -522,7 +585,8 @@ module DEBUGGER__
522
585
 
523
586
  case type
524
587
  when :backtrace
525
- show_frames
588
+ max_lines, pattern = *args
589
+ show_frames max_lines, pattern
526
590
 
527
591
  when :list
528
592
  show_src(update_line: true, **(args.first || {}))
@@ -544,9 +608,30 @@ module DEBUGGER__
544
608
  end
545
609
 
546
610
  event! :result, nil
547
-
548
611
  when :breakpoint
549
- 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
550
635
  else
551
636
  raise [cmd, *args].inspect
552
637
  end
@@ -555,7 +640,7 @@ module DEBUGGER__
555
640
  rescue SystemExit
556
641
  raise
557
642
  rescue Exception => e
558
- pp [__FILE__, __LINE__, e, e.backtrace]
643
+ pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
559
644
  raise
560
645
  ensure
561
646
  set_mode nil