debug 1.3.4 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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