debug 1.3.4 → 1.4.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.
data/lib/debug/session.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ return if ENV['RUBY_DEBUG_ENABLE'] == '0'
4
+
3
5
  # skip to load debugger for bundle exec
4
6
 
5
7
  if $0.end_with?('bin/bundle') && ARGV.first == 'exec'
@@ -99,17 +101,17 @@ module DEBUGGER__
99
101
  @preset_command = nil
100
102
  @postmortem_hook = nil
101
103
  @postmortem = false
102
- @thread_stopper = nil
103
104
  @intercept_trap_sigint = false
104
105
  @intercepted_sigint_cmd = 'DEFAULT'
105
106
  @process_group = ProcessGroup.new
106
107
  @subsession = nil
107
108
 
108
- @frame_map = {} # {id => [threadId, frame_depth]} for DAP
109
+ @frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
109
110
  @var_map = {1 => [:globals], } # {id => ...} for DAP
110
111
  @src_map = {} # {id => src}
111
112
 
112
113
  @script_paths = [File.absolute_path($0)] # for CDP
114
+ @obj_map = {} # { object_id => ... } for CDP
113
115
 
114
116
  @tp_thread_begin = nil
115
117
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
@@ -117,6 +119,8 @@ module DEBUGGER__
117
119
  }
118
120
  @tp_load_script.enable
119
121
 
122
+ @thread_stopper = thread_stopper
123
+
120
124
  activate
121
125
 
122
126
  self.postmortem = CONFIG[:postmortem]
@@ -147,15 +151,15 @@ module DEBUGGER__
147
151
 
148
152
  # Thread management
149
153
  setup_threads
150
- thc = thread_client Thread.current
151
- thc.is_management
154
+ thc = get_thread_client Thread.current
155
+ thc.mark_as_management
152
156
 
153
- if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
154
- thc.is_management
157
+ if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
158
+ thc.mark_as_management
155
159
  end
156
160
 
157
161
  @tp_thread_begin = TracePoint.new(:thread_begin) do |tp|
158
- thread_client
162
+ get_thread_client
159
163
  end
160
164
  @tp_thread_begin.enable
161
165
 
@@ -168,12 +172,12 @@ module DEBUGGER__
168
172
  end
169
173
 
170
174
  def deactivate
171
- thread_client.deactivate
172
- @thread_stopper.disable if @thread_stopper
175
+ get_thread_client.deactivate
176
+ @thread_stopper.disable
173
177
  @tp_load_script.disable
174
178
  @tp_thread_begin.disable
175
- @bps.each{|k, bp| bp.disable}
176
- @th_clients.each{|th, thc| thc.close}
179
+ @bps.each_value{|bp| bp.disable}
180
+ @th_clients.each_value{|thc| thc.close}
177
181
  @tracers.values.each{|t| t.disable}
178
182
  @q_evt.close
179
183
  @ui&.deactivate
@@ -233,12 +237,13 @@ module DEBUGGER__
233
237
  case ev_args.first
234
238
  when :breakpoint
235
239
  bp, i = bp_index ev_args[1]
240
+ clean_bps unless bp
236
241
  @ui.event :suspend_bp, i, bp, tc.id
237
242
  when :trap
238
243
  @ui.event :suspend_trap, sig = ev_args[1], tc.id
239
244
 
240
245
  if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
241
- @ui.puts "#{@intercepted_sigint_cmd.inspect} is registerred as SIGINT handler."
246
+ @ui.puts "#{@intercepted_sigint_cmd.inspect} is registered as SIGINT handler."
242
247
  @ui.puts "`sigint` command execute it."
243
248
  end
244
249
  else
@@ -418,10 +423,15 @@ module DEBUGGER__
418
423
  # * `fin[ish]`
419
424
  # * Finish this frame. Resume the program until the current frame is finished.
420
425
  # * `fin[ish] <n>`
421
- # * Finish frames, same as `step <n>`.
426
+ # * Finish `<n>`th frames.
422
427
  when 'fin', 'finish'
423
428
  cancel_auto_continue
424
429
  check_postmortem
430
+
431
+ if arg&.to_i == 0
432
+ raise 'finish command with 0 does not make sense.'
433
+ end
434
+
425
435
  step_command :finish, arg
426
436
 
427
437
  # * `c[ontinue]`
@@ -447,7 +457,7 @@ module DEBUGGER__
447
457
  leave_subsession nil
448
458
 
449
459
  # * `kill`
450
- # * Stop the debuggee process with `Kernal#exit!`.
460
+ # * Stop the debuggee process with `Kernel#exit!`.
451
461
  when 'kill'
452
462
  if ask 'Really kill?'
453
463
  exit! (arg || 1).to_i
@@ -461,7 +471,7 @@ module DEBUGGER__
461
471
  exit! (arg || 1).to_i
462
472
 
463
473
  # * `sigint`
464
- # * Execute SIGINT handler registerred by the debuggee.
474
+ # * Execute SIGINT handler registered by the debuggee.
465
475
  # * Note that this command should be used just after stop by `SIGINT`.
466
476
  when 'sigint'
467
477
  begin
@@ -500,6 +510,8 @@ module DEBUGGER__
500
510
  # * break and run `<command>` before stopping.
501
511
  # * `b[reak] ... do: <command>`
502
512
  # * break and run `<command>`, and continue.
513
+ # * `b[reak] ... path: <path_regexp>`
514
+ # * break if the triggering event's path matches <path_regexp>.
503
515
  # * `b[reak] if: <expr>`
504
516
  # * break if: `<expr>` is true at any lines.
505
517
  # * Note that this feature is super slow.
@@ -526,7 +538,7 @@ module DEBUGGER__
526
538
  require 'json'
527
539
 
528
540
  h = Hash.new{|h, k| h[k] = []}
529
- @bps.each{|key, bp|
541
+ @bps.each_value{|bp|
530
542
  if LineBreakpoint === bp
531
543
  h[bp.path] << {lnum: bp.line}
532
544
  end
@@ -548,6 +560,14 @@ module DEBUGGER__
548
560
 
549
561
  # * `catch <Error>`
550
562
  # * Set breakpoint on raising `<Error>`.
563
+ # * `catch ... if: <expr>`
564
+ # * stops only if `<expr>` is true as well.
565
+ # * `catch ... pre: <command>`
566
+ # * runs `<command>` before stopping.
567
+ # * `catch ... do: <command>`
568
+ # * stops and run `<command>`, and continue.
569
+ # * `catch ... path: <path_regexp>`
570
+ # * stops if the exception is raised from a path that matches <path_regexp>.
551
571
  when 'catch'
552
572
  check_postmortem
553
573
 
@@ -562,11 +582,19 @@ module DEBUGGER__
562
582
  # * `watch @ivar`
563
583
  # * Stop the execution when the result of current scope's `@ivar` is changed.
564
584
  # * Note that this feature is super slow.
585
+ # * `watch ... if: <expr>`
586
+ # * stops only if `<expr>` is true as well.
587
+ # * `watch ... pre: <command>`
588
+ # * runs `<command>` before stopping.
589
+ # * `watch ... do: <command>`
590
+ # * stops and run `<command>`, and continue.
591
+ # * `watch ... path: <path_regexp>`
592
+ # * stops if the triggering event's path matches <path_regexp>.
565
593
  when 'wat', 'watch'
566
594
  check_postmortem
567
595
 
568
596
  if arg && arg.match?(/\A@\w+/)
569
- @tc << [:breakpoint, :watch, arg]
597
+ repl_add_watch_breakpoint(arg)
570
598
  else
571
599
  show_bps
572
600
  return :retry
@@ -902,7 +930,7 @@ module DEBUGGER__
902
930
  when nil, 'list', 'l'
903
931
  thread_list
904
932
  when /(\d+)/
905
- thread_switch $1.to_i
933
+ switch_thread $1.to_i
906
934
  else
907
935
  @ui.puts "unknown thread command: #{arg}"
908
936
  end
@@ -1016,8 +1044,8 @@ module DEBUGGER__
1016
1044
  def repl_open_setup
1017
1045
  @tp_thread_begin.disable
1018
1046
  @ui.activate self
1019
- if @ui.respond_to?(:reader_thread) && thc = thread_client(@ui.reader_thread)
1020
- thc.is_management
1047
+ if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
1048
+ thc.mark_as_management
1021
1049
  end
1022
1050
  @tp_thread_begin.enable
1023
1051
  end
@@ -1195,6 +1223,12 @@ module DEBUGGER__
1195
1223
  }
1196
1224
  end
1197
1225
 
1226
+ def clean_bps
1227
+ @bps.delete_if{|_k, bp|
1228
+ bp.deleted?
1229
+ }
1230
+ end
1231
+
1198
1232
  def add_bp bp
1199
1233
  # don't repeat commands that add breakpoints
1200
1234
  @repl_prev_line = nil
@@ -1225,7 +1259,7 @@ module DEBUGGER__
1225
1259
  end
1226
1260
  end
1227
1261
 
1228
- BREAK_KEYWORDS = %w(if: do: pre:).freeze
1262
+ BREAK_KEYWORDS = %w(if: do: pre: path:).freeze
1229
1263
 
1230
1264
  def parse_break arg
1231
1265
  mode = :sig
@@ -1245,6 +1279,7 @@ module DEBUGGER__
1245
1279
  expr = parse_break arg.strip
1246
1280
  cond = expr[:if]
1247
1281
  cmd = ['break', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1282
+ path = Regexp.compile(expr[:path]) if expr[:path]
1248
1283
 
1249
1284
  case expr[:sig]
1250
1285
  when /\A(\d+)\z/
@@ -1252,10 +1287,10 @@ module DEBUGGER__
1252
1287
  when /\A(.+)[:\s+](\d+)\z/
1253
1288
  add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
1254
1289
  when /\A(.+)([\.\#])(.+)\z/
1255
- @tc << [:breakpoint, :method, $1, $2, $3, cond, cmd]
1290
+ @tc << [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
1256
1291
  return :noretry
1257
1292
  when nil
1258
- add_check_breakpoint cond
1293
+ add_check_breakpoint cond, path
1259
1294
  else
1260
1295
  @ui.puts "Unknown breakpoint format: #{arg}"
1261
1296
  @ui.puts
@@ -1267,18 +1302,28 @@ module DEBUGGER__
1267
1302
  expr = parse_break arg.strip
1268
1303
  cond = expr[:if]
1269
1304
  cmd = ['catch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1305
+ path = Regexp.compile(expr[:path]) if expr[:path]
1270
1306
 
1271
- bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd)
1307
+ bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
1272
1308
  add_bp bp
1273
1309
  end
1274
1310
 
1311
+ def repl_add_watch_breakpoint arg
1312
+ expr = parse_break arg.strip
1313
+ cond = expr[:if]
1314
+ cmd = ['watch', expr[:pre], expr[:do]] if expr[:pre] || expr[:do]
1315
+ path = Regexp.compile(expr[:path]) if expr[:path]
1316
+
1317
+ @tc << [:breakpoint, :watch, expr[:sig], cond, cmd, path]
1318
+ end
1319
+
1275
1320
  def add_catch_breakpoint pat
1276
1321
  bp = CatchBreakpoint.new(pat)
1277
1322
  add_bp bp
1278
1323
  end
1279
1324
 
1280
- def add_check_breakpoint expr
1281
- bp = CheckBreakpoint.new(expr)
1325
+ def add_check_breakpoint expr, path
1326
+ bp = CheckBreakpoint.new(expr, path)
1282
1327
  add_bp bp
1283
1328
  end
1284
1329
 
@@ -1291,6 +1336,11 @@ module DEBUGGER__
1291
1336
  @ui.puts e.message
1292
1337
  end
1293
1338
 
1339
+ def add_iseq_breakpoint iseq, **kw
1340
+ bp = ISeqBreakpoint.new(iseq, [:line], **kw)
1341
+ add_bp bp
1342
+ end
1343
+
1294
1344
  # tracers
1295
1345
 
1296
1346
  def add_tracer tracer
@@ -1344,7 +1394,7 @@ module DEBUGGER__
1344
1394
  thcs
1345
1395
  end
1346
1396
 
1347
- def thread_switch n
1397
+ def switch_thread n
1348
1398
  thcs, _unmanaged_ths = update_thread_list
1349
1399
 
1350
1400
  if tc = thcs[n]
@@ -1365,7 +1415,7 @@ module DEBUGGER__
1365
1415
  if tc = prev_clients[th]
1366
1416
  @th_clients[th] = tc
1367
1417
  else
1368
- thread_client_create(th)
1418
+ create_thread_client(th)
1369
1419
  end
1370
1420
  }
1371
1421
  end
@@ -1374,17 +1424,17 @@ module DEBUGGER__
1374
1424
  if @th_clients.has_key? th
1375
1425
  # TODO: NG?
1376
1426
  else
1377
- thread_client_create th
1427
+ create_thread_client th
1378
1428
  end
1379
1429
  end
1380
1430
 
1381
- private def thread_client_create th
1431
+ private def create_thread_client th
1382
1432
  # TODO: Ractor support
1383
1433
  raise "Only session_server can create thread_client" unless Thread.current == @session_server
1384
1434
  @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
1385
1435
  end
1386
1436
 
1387
- private def ask_thread_client th = Thread.current
1437
+ private def ask_thread_client th
1388
1438
  # TODO: Ractor support
1389
1439
  q2 = Queue.new
1390
1440
  # tc, output, ev, @internal_info, *ev_args = evt
@@ -1395,30 +1445,18 @@ module DEBUGGER__
1395
1445
  end
1396
1446
 
1397
1447
  # can be called by other threads
1398
- def thread_client th = Thread.current
1448
+ def get_thread_client th = Thread.current
1399
1449
  if @th_clients.has_key? th
1400
1450
  @th_clients[th]
1401
1451
  else
1402
1452
  if Thread.current == @session_server
1403
- thread_client_create th
1453
+ create_thread_client th
1404
1454
  else
1405
1455
  ask_thread_client th
1406
1456
  end
1407
1457
  end
1408
1458
  end
1409
1459
 
1410
- private def thread_stopper
1411
- @thread_stopper ||= TracePoint.new(:line) do
1412
- # run on each thread
1413
- tc = ThreadClient.current
1414
- next if tc.management?
1415
- next unless tc.running?
1416
- next if tc == @tc
1417
-
1418
- tc.on_pause
1419
- end
1420
- end
1421
-
1422
1460
  private def running_thread_clients_count
1423
1461
  @th_clients.count{|th, tc|
1424
1462
  next if tc.management?
@@ -1435,15 +1473,27 @@ module DEBUGGER__
1435
1473
  }.compact
1436
1474
  end
1437
1475
 
1476
+ private def thread_stopper
1477
+ TracePoint.new(:line) do
1478
+ # run on each thread
1479
+ tc = ThreadClient.current
1480
+ next if tc.management?
1481
+ next unless tc.running?
1482
+ next if tc == @tc
1483
+
1484
+ tc.on_pause
1485
+ end
1486
+ end
1487
+
1438
1488
  private def stop_all_threads
1439
1489
  return if running_thread_clients_count == 0
1440
1490
 
1441
- stopper = thread_stopper
1491
+ stopper = @thread_stopper
1442
1492
  stopper.enable unless stopper.enabled?
1443
1493
  end
1444
1494
 
1445
1495
  private def restart_all_threads
1446
- stopper = thread_stopper
1496
+ stopper = @thread_stopper
1447
1497
  stopper.disable if stopper.enabled?
1448
1498
 
1449
1499
  waiting_thread_clients.each{|tc|
@@ -1550,7 +1600,7 @@ module DEBUGGER__
1550
1600
 
1551
1601
  frames = exc.instance_variable_get(:@__debugger_postmortem_frames)
1552
1602
  @postmortem = true
1553
- ThreadClient.current.suspend :postmortem, postmortem_frames: frames
1603
+ ThreadClient.current.suspend :postmortem, postmortem_frames: frames, postmortem_exc: exc
1554
1604
  ensure
1555
1605
  @postmortem = false
1556
1606
  end
@@ -1822,7 +1872,7 @@ module DEBUGGER__
1822
1872
  ::DEBUGGER__::SESSION.add_catch_breakpoint pat
1823
1873
  end
1824
1874
 
1825
- # String for requring location
1875
+ # String for requiring location
1826
1876
  # nil for -r
1827
1877
  def self.require_location
1828
1878
  locs = caller_locations
@@ -1960,13 +2010,17 @@ module DEBUGGER__
1960
2010
  METHOD_ADDED_TRACKER = self.create_method_added_tracker
1961
2011
 
1962
2012
  SHORT_INSPECT_LENGTH = 40
1963
- def self.short_inspect obj, use_short = true
2013
+
2014
+ def self.safe_inspect obj, max_length: SHORT_INSPECT_LENGTH, short: false
1964
2015
  str = obj.inspect
1965
- if use_short && str.length > SHORT_INSPECT_LENGTH
1966
- str[0...SHORT_INSPECT_LENGTH] + '...'
2016
+
2017
+ if short && str.length > max_length
2018
+ str[0...max_length] + '...'
1967
2019
  else
1968
2020
  str
1969
2021
  end
2022
+ rescue Exception => e
2023
+ str = "<#inspect raises #{e.inspect}>"
1970
2024
  end
1971
2025
 
1972
2026
  def self.warn msg
@@ -2000,6 +2054,14 @@ module DEBUGGER__
2000
2054
  end
2001
2055
  end
2002
2056
 
2057
+ def self.step_in &b
2058
+ if defined?(SESSION) && SESSION.active?
2059
+ SESSION.add_iseq_breakpoint RubyVM::InstructionSequence.of(b), oneshot: true
2060
+ end
2061
+
2062
+ yield
2063
+ end
2064
+
2003
2065
  module ForkInterceptor
2004
2066
  def fork(&given_block)
2005
2067
  return super unless defined?(SESSION) && SESSION.active?
@@ -40,12 +40,10 @@ module DEBUGGER__
40
40
  end
41
41
 
42
42
  private def add_path path
43
- begin
44
- src = File.read(path)
45
- src = src.gsub("\r\n", "\n") # CRLF -> LF
46
- @files[path] = SrcInfo.new(src.lines)
47
- rescue SystemCallError
48
- end
43
+ src = File.read(path)
44
+ src = src.gsub("\r\n", "\n") # CRLF -> LF
45
+ @files[path] = SrcInfo.new(src.lines)
46
+ rescue SystemCallError
49
47
  end
50
48
 
51
49
  private def get_si iseq
@@ -22,7 +22,7 @@ module DEBUGGER__
22
22
  if thc = Thread.current[:DEBUGGER__ThreadClient]
23
23
  thc
24
24
  else
25
- thc = SESSION.thread_client
25
+ thc = SESSION.get_thread_client
26
26
  Thread.current[:DEBUGGER__ThreadClient] = thc
27
27
  end
28
28
  end
@@ -67,7 +67,7 @@ module DEBUGGER__
67
67
  result = "#{call_identifier_str} at #{location_str}"
68
68
 
69
69
  if return_str = frame.return_str
70
- result += " #=> #{colorize_magenta(frame.return_str)}"
70
+ result += " #=> #{colorize_magenta(return_str)}"
71
71
  end
72
72
 
73
73
  result
@@ -84,6 +84,7 @@ module DEBUGGER__
84
84
  @output = []
85
85
  @frame_formatter = method(:default_frame_formatter)
86
86
  @var_map = {} # { thread_local_var_id => obj } for DAP
87
+ @obj_map = {} # { object_id => obj } for CDP
87
88
  @recorder = nil
88
89
  @mode = :waiting
89
90
  set_mode :running
@@ -100,13 +101,13 @@ module DEBUGGER__
100
101
  @is_management
101
102
  end
102
103
 
103
- def is_management
104
+ def mark_as_management
104
105
  @is_management = true
105
106
  end
106
107
 
107
108
  def set_mode mode
108
109
  # STDERR.puts "#{@mode} => #{mode} @ #{caller.inspect}"
109
- #pp caller
110
+ # pp caller
110
111
 
111
112
  # mode transition check
112
113
  case mode
@@ -228,7 +229,7 @@ module DEBUGGER__
228
229
  suspend :pause
229
230
  end
230
231
 
231
- def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil
232
+ def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil
232
233
  return if management?
233
234
 
234
235
  @current_frame_index = 0
@@ -256,10 +257,15 @@ module DEBUGGER__
256
257
  cf.has_raised_exception = true
257
258
  cf.raised_exception = bp.last_exc
258
259
  end
260
+
261
+ if postmortem_exc
262
+ cf.has_raised_exception = true
263
+ cf.raised_exception = postmortem_exc
264
+ end
259
265
  end
260
266
 
261
267
  if event != :pause
262
- show_src max_lines: (CONFIG[:show_src_lines] || 10)
268
+ show_src
263
269
  show_frames CONFIG[:show_frames] || 2
264
270
 
265
271
  set_mode :waiting
@@ -292,15 +298,15 @@ module DEBUGGER__
292
298
  SUPPORT_TARGET_THREAD = false
293
299
  end
294
300
 
295
- def step_tp iter
301
+ def step_tp iter, events = [:line, :b_return, :return]
296
302
  @step_tp.disable if @step_tp
297
303
 
298
304
  thread = Thread.current
299
305
 
300
306
  if SUPPORT_TARGET_THREAD
301
- @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
307
+ @step_tp = TracePoint.new(*events){|tp|
302
308
  next if SESSION.break_at? tp.path, tp.lineno
303
- next if !yield
309
+ next if !yield(tp.event)
304
310
  next if tp.path.start_with?(__dir__)
305
311
  next if tp.path.start_with?('<internal:trace_point>')
306
312
  next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
@@ -313,10 +319,10 @@ module DEBUGGER__
313
319
  }
314
320
  @step_tp.enable(target_thread: thread)
315
321
  else
316
- @step_tp = TracePoint.new(:line, :b_return, :return){|tp|
322
+ @step_tp = TracePoint.new(*events){|tp|
317
323
  next if thread != Thread.current
318
324
  next if SESSION.break_at? tp.path, tp.lineno
319
- next if !yield
325
+ next if !yield(tp.event)
320
326
  next if tp.path.start_with?(__dir__)
321
327
  next if tp.path.start_with?('<internal:trace_point>')
322
328
  next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
@@ -338,38 +344,45 @@ module DEBUGGER__
338
344
  frame_self.instance_eval(src)
339
345
  end
340
346
 
347
+ SPECIAL_LOCAL_VARS = [
348
+ [:raised_exception, "_raised"],
349
+ [:return_value, "_return"],
350
+ ]
351
+
341
352
  def frame_eval src, re_raise: false
342
- begin
343
- @success_last_eval = false
353
+ @success_last_eval = false
344
354
 
345
- b = current_frame&.eval_binding || TOPLEVEL_BINDING
355
+ b = current_frame.eval_binding
346
356
 
347
- result = if b
348
- f, _l = b.source_location
349
- b.eval(src, "(rdbg)/#{f}")
350
- else
351
- frame_self = current_frame.self
352
- instance_eval_for_cmethod(frame_self, src)
353
- end
354
- @success_last_eval = true
355
- result
357
+ special_local_variables current_frame do |name, var|
358
+ b.local_variable_set(name, var) if /\%/ !~ name
359
+ end
356
360
 
357
- rescue Exception => e
358
- return yield(e) if block_given?
361
+ result = if b
362
+ f, _l = b.source_location
363
+ b.eval(src, "(rdbg)/#{f}")
364
+ else
365
+ frame_self = current_frame.self
366
+ instance_eval_for_cmethod(frame_self, src)
367
+ end
368
+ @success_last_eval = true
369
+ result
359
370
 
360
- puts "eval error: #{e}"
371
+ rescue Exception => e
372
+ return yield(e) if block_given?
361
373
 
362
- e.backtrace_locations&.each do |loc|
363
- break if loc.path == __FILE__
364
- puts " #{loc}"
365
- end
366
- raise if re_raise
374
+ puts "eval error: #{e}"
375
+
376
+ e.backtrace_locations&.each do |loc|
377
+ break if loc.path == __FILE__
378
+ puts " #{loc}"
367
379
  end
380
+ raise if re_raise
368
381
  end
369
382
 
370
383
  def show_src(frame_index: @current_frame_index,
371
384
  update_line: false,
372
- max_lines: 10,
385
+ max_lines: CONFIG[:show_src_lines] || 10,
373
386
  start_line: nil,
374
387
  end_line: nil,
375
388
  dir: +1)
@@ -428,15 +441,20 @@ module DEBUGGER__
428
441
 
429
442
  ## cmd: show
430
443
 
444
+ def special_local_variables frame
445
+ SPECIAL_LOCAL_VARS.each do |mid, name|
446
+ next unless frame&.send("has_#{mid}")
447
+ name = name.sub('_', '%') if frame.eval_binding.local_variable_defined?(name)
448
+ yield name, frame.send(mid)
449
+ end
450
+ end
451
+
431
452
  def show_locals pat
432
453
  if s = current_frame&.self
433
454
  puts_variable_info '%self', s, pat
434
455
  end
435
- if current_frame&.has_return_value
436
- puts_variable_info '%return', current_frame.return_value, pat
437
- end
438
- if current_frame&.has_raised_exception
439
- puts_variable_info "%raised", current_frame.raised_exception, pat
456
+ special_local_variables current_frame do |name, val|
457
+ puts_variable_info name, val, pat
440
458
  end
441
459
 
442
460
  if vars = current_frame&.local_variables
@@ -633,8 +651,8 @@ module DEBUGGER__
633
651
  def make_breakpoint args
634
652
  case args.first
635
653
  when :method
636
- klass_name, op, method_name, cond, cmd = args[1..]
637
- bp = MethodBreakpoint.new(current_frame.binding, klass_name, op, method_name, cond: cond, command: cmd)
654
+ klass_name, op, method_name, cond, cmd, path = args[1..]
655
+ bp = MethodBreakpoint.new(current_frame.eval_binding, klass_name, op, method_name, cond: cond, command: cmd, path: path)
638
656
  begin
639
657
  bp.enable
640
658
  rescue Exception => e
@@ -644,8 +662,8 @@ module DEBUGGER__
644
662
 
645
663
  bp
646
664
  when :watch
647
- ivar, object, result = args[1..]
648
- WatchIVarBreakpoint.new(ivar, object, result)
665
+ ivar, object, result, cond, command, path = args[1..]
666
+ WatchIVarBreakpoint.new(ivar, object, result, cond: cond, command: command, path: path)
649
667
  else
650
668
  raise "unknown breakpoint: #{args}"
651
669
  end
@@ -732,10 +750,11 @@ module DEBUGGER__
732
750
  break
733
751
 
734
752
  when :finish
735
- depth = @target_frames.first.frame_depth
736
- step_tp iter do
737
- # 3 is debugger's frame count
738
- DEBUGGER__.frame_depth - 3 < depth
753
+ finish_frames = (iter || 1) - 1
754
+ goal_depth = @target_frames.first.frame_depth - finish_frames
755
+
756
+ step_tp nil, [:return, :b_return] do
757
+ DEBUGGER__.frame_depth - 3 <= goal_depth ? true : false
739
758
  end
740
759
  break
741
760
 
@@ -887,7 +906,7 @@ module DEBUGGER__
887
906
  bp = make_breakpoint args
888
907
  event! :result, :method_breakpoint, bp
889
908
  when :watch
890
- ivar = args[1]
909
+ ivar, cond, command, path = args[1..]
891
910
  result = frame_eval(ivar)
892
911
 
893
912
  if @success_last_eval
@@ -897,7 +916,7 @@ module DEBUGGER__
897
916
  else
898
917
  current_frame.self
899
918
  end
900
- bp = make_breakpoint [:watch, ivar, object, result]
919
+ bp = make_breakpoint [:watch, ivar, object, result, cond, command, path]
901
920
  event! :result, :watch_breakpoint, bp
902
921
  else
903
922
  event! :result, nil
@@ -1067,7 +1086,7 @@ module DEBUGGER__
1067
1086
  end
1068
1087
  end
1069
1088
 
1070
- # copyed from irb
1089
+ # copied from irb
1071
1090
  class Output
1072
1091
  include Color
1073
1092