debug 1.7.2 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -274,198 +274,225 @@ module DEBUGGER__
274
274
  retry
275
275
  end
276
276
 
277
+ def load_extensions req
278
+ if exts = req.dig('arguments', 'rdbgExtensions')
279
+ exts.each{|ext|
280
+ require_relative "dap_custom/#{File.basename(ext)}"
281
+ }
282
+ end
283
+
284
+ if scripts = req.dig('arguments', 'rdbgInitialScripts')
285
+ scripts.each do |script|
286
+ begin
287
+ eval(script)
288
+ rescue Exception => e
289
+ puts e.message
290
+ puts e.backtrace.inspect
291
+ end
292
+ end
293
+ end
294
+ end
295
+
277
296
  def process
278
297
  while req = recv_request
279
- raise "not a request: #{req.inspect}" unless req['type'] == 'request'
280
- args = req.dig('arguments')
298
+ process_request(req)
299
+ end
300
+ ensure
301
+ send_event :terminated unless @sock.closed?
302
+ end
281
303
 
282
- case req['command']
304
+ def process_request req
305
+ raise "not a request: #{req.inspect}" unless req['type'] == 'request'
306
+ args = req.dig('arguments')
283
307
 
284
- ## boot/configuration
285
- when 'launch'
286
- send_response req
287
- # `launch` runs on debuggee on the same file system
288
- UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap') || true
289
- @nonstop = true
308
+ case req['command']
290
309
 
291
- when 'attach'
292
- send_response req
293
- UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
310
+ ## boot/configuration
311
+ when 'launch'
312
+ send_response req
313
+ # `launch` runs on debuggee on the same file system
314
+ UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap') || true
315
+ @nonstop = true
294
316
 
295
- if req.dig('arguments', 'nonstop') == true
296
- @nonstop = true
297
- else
298
- @nonstop = false
299
- end
317
+ load_extensions req
300
318
 
301
- when 'configurationDone'
302
- send_response req
319
+ when 'attach'
320
+ send_response req
321
+ UI_DAP.local_fs_map_set req.dig('arguments', 'localfs') || req.dig('arguments', 'localfsMap')
303
322
 
304
- if @nonstop
305
- @q_msg << 'continue'
306
- else
307
- if SESSION.in_subsession?
308
- send_event 'stopped', reason: 'pause',
309
- threadId: 1, # maybe ...
310
- allThreadsStopped: true
311
- end
312
- end
323
+ if req.dig('arguments', 'nonstop') == true
324
+ @nonstop = true
325
+ else
326
+ @nonstop = false
327
+ end
313
328
 
314
- when 'setBreakpoints'
315
- req_path = args.dig('source', 'path')
316
- path = UI_DAP.local_to_remote_path(req_path)
317
- if path
318
- SESSION.clear_line_breakpoints path
319
-
320
- bps = []
321
- args['breakpoints'].each{|bp|
322
- line = bp['line']
323
- if cond = bp['condition']
324
- bps << SESSION.add_line_breakpoint(path, line, cond: cond)
325
- else
326
- bps << SESSION.add_line_breakpoint(path, line)
327
- end
328
- }
329
- send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
330
- else
331
- send_response req, success: false, message: "#{req_path} is not available"
332
- end
329
+ load_extensions req
333
330
 
334
- when 'setFunctionBreakpoints'
335
- send_response req
331
+ when 'configurationDone'
332
+ send_response req
336
333
 
337
- when 'setExceptionBreakpoints'
338
- process_filter = ->(filter_id, cond = nil) {
339
- bp =
340
- case filter_id
341
- when 'any'
342
- SESSION.add_catch_breakpoint 'Exception', cond: cond
343
- when 'RuntimeError'
344
- SESSION.add_catch_breakpoint 'RuntimeError', cond: cond
345
- else
346
- nil
347
- end
348
- {
349
- verified: !bp.nil?,
350
- message: bp.inspect,
351
- }
352
- }
334
+ if @nonstop
335
+ @q_msg << 'continue'
336
+ else
337
+ if SESSION.in_subsession?
338
+ send_event 'stopped', reason: 'pause',
339
+ threadId: 1, # maybe ...
340
+ allThreadsStopped: true
341
+ end
342
+ end
353
343
 
354
- SESSION.clear_catch_breakpoints 'Exception', 'RuntimeError'
344
+ when 'setBreakpoints'
345
+ req_path = args.dig('source', 'path')
346
+ path = UI_DAP.local_to_remote_path(req_path)
347
+ if path
348
+ SESSION.clear_line_breakpoints path
349
+
350
+ bps = []
351
+ args['breakpoints'].each{|bp|
352
+ line = bp['line']
353
+ if cond = bp['condition']
354
+ bps << SESSION.add_line_breakpoint(path, line, cond: cond)
355
+ else
356
+ bps << SESSION.add_line_breakpoint(path, line)
357
+ end
358
+ }
359
+ send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
360
+ else
361
+ send_response req, success: false, message: "#{req_path} is not available"
362
+ end
355
363
 
356
- filters = args.fetch('filters').map {|filter_id|
357
- process_filter.call(filter_id)
364
+ when 'setFunctionBreakpoints'
365
+ send_response req
366
+
367
+ when 'setExceptionBreakpoints'
368
+ process_filter = ->(filter_id, cond = nil) {
369
+ bp =
370
+ case filter_id
371
+ when 'any'
372
+ SESSION.add_catch_breakpoint 'Exception', cond: cond
373
+ when 'RuntimeError'
374
+ SESSION.add_catch_breakpoint 'RuntimeError', cond: cond
375
+ else
376
+ nil
377
+ end
378
+ {
379
+ verified: !bp.nil?,
380
+ message: bp.inspect,
358
381
  }
382
+ }
383
+
384
+ SESSION.clear_catch_breakpoints 'Exception', 'RuntimeError'
359
385
 
360
- filters += args.fetch('filterOptions', {}).map{|bp_info|
361
- process_filter.call(bp_info['filterId'], bp_info['condition'])
386
+ filters = args.fetch('filters').map {|filter_id|
387
+ process_filter.call(filter_id)
362
388
  }
363
389
 
364
- send_response req, breakpoints: filters
390
+ filters += args.fetch('filterOptions', {}).map{|bp_info|
391
+ process_filter.call(bp_info['filterId'], bp_info['condition'])
392
+ }
365
393
 
366
- when 'disconnect'
367
- terminate = args.fetch("terminateDebuggee", false)
394
+ send_response req, breakpoints: filters
368
395
 
369
- SESSION.clear_all_breakpoints
370
- send_response req
396
+ when 'disconnect'
397
+ terminate = args.fetch("terminateDebuggee", false)
371
398
 
372
- if SESSION.in_subsession?
373
- if terminate
374
- @q_msg << 'kill!'
375
- else
376
- @q_msg << 'continue'
377
- end
378
- else
379
- if terminate
380
- @q_msg << 'kill!'
381
- pause
382
- end
383
- end
399
+ SESSION.clear_all_breakpoints
400
+ send_response req
384
401
 
385
- ## control
386
- when 'continue'
387
- @q_msg << 'c'
388
- send_response req, allThreadsContinued: true
389
- when 'next'
390
- begin
391
- @session.check_postmortem
392
- @q_msg << 'n'
393
- send_response req
394
- rescue PostmortemError
395
- send_response req,
396
- success: false, message: 'postmortem mode',
397
- result: "'Next' is not supported while postmortem mode"
398
- end
399
- when 'stepIn'
400
- begin
401
- @session.check_postmortem
402
- @q_msg << 's'
403
- send_response req
404
- rescue PostmortemError
405
- send_response req,
406
- success: false, message: 'postmortem mode',
407
- result: "'stepIn' is not supported while postmortem mode"
402
+ if SESSION.in_subsession?
403
+ if terminate
404
+ @q_msg << 'kill!'
405
+ else
406
+ @q_msg << 'continue'
408
407
  end
409
- when 'stepOut'
410
- begin
411
- @session.check_postmortem
412
- @q_msg << 'fin'
413
- send_response req
414
- rescue PostmortemError
415
- send_response req,
416
- success: false, message: 'postmortem mode',
417
- result: "'stepOut' is not supported while postmortem mode"
408
+ else
409
+ if terminate
410
+ @q_msg << 'kill!'
411
+ pause
418
412
  end
419
- when 'terminate'
413
+ end
414
+
415
+ ## control
416
+ when 'continue'
417
+ @q_msg << 'c'
418
+ send_response req, allThreadsContinued: true
419
+ when 'next'
420
+ begin
421
+ @session.check_postmortem
422
+ @q_msg << 'n'
420
423
  send_response req
421
- exit
422
- when 'pause'
424
+ rescue PostmortemError
425
+ send_response req,
426
+ success: false, message: 'postmortem mode',
427
+ result: "'Next' is not supported while postmortem mode"
428
+ end
429
+ when 'stepIn'
430
+ begin
431
+ @session.check_postmortem
432
+ @q_msg << 's'
423
433
  send_response req
424
- Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid)
425
- when 'reverseContinue'
434
+ rescue PostmortemError
426
435
  send_response req,
427
- success: false, message: 'cancelled',
428
- result: "Reverse Continue is not supported. Only \"Step back\" is supported."
429
- when 'stepBack'
430
- @q_msg << req
436
+ success: false, message: 'postmortem mode',
437
+ result: "'stepIn' is not supported while postmortem mode"
438
+ end
439
+ when 'stepOut'
440
+ begin
441
+ @session.check_postmortem
442
+ @q_msg << 'fin'
443
+ send_response req
444
+ rescue PostmortemError
445
+ send_response req,
446
+ success: false, message: 'postmortem mode',
447
+ result: "'stepOut' is not supported while postmortem mode"
448
+ end
449
+ when 'terminate'
450
+ send_response req
451
+ exit
452
+ when 'pause'
453
+ send_response req
454
+ Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid)
455
+ when 'reverseContinue'
456
+ send_response req,
457
+ success: false, message: 'cancelled',
458
+ result: "Reverse Continue is not supported. Only \"Step back\" is supported."
459
+ when 'stepBack'
460
+ @q_msg << req
431
461
 
432
- ## query
433
- when 'threads'
434
- send_response req, threads: SESSION.managed_thread_clients.map{|tc|
435
- { id: tc.id,
436
- name: tc.name,
437
- }
462
+ ## query
463
+ when 'threads'
464
+ send_response req, threads: SESSION.managed_thread_clients.map{|tc|
465
+ { id: tc.id,
466
+ name: tc.name,
438
467
  }
468
+ }
439
469
 
440
- when 'evaluate'
441
- expr = req.dig('arguments', 'expression')
442
- if /\A\s*,(.+)\z/ =~ expr
443
- dbg_expr = $1.strip
444
- dbg_expr.split(';;') { |cmd| @q_msg << cmd }
470
+ when 'evaluate'
471
+ expr = req.dig('arguments', 'expression')
472
+ if /\A\s*,(.+)\z/ =~ expr
473
+ dbg_expr = $1.strip
474
+ dbg_expr.split(';;') { |cmd| @q_msg << cmd }
445
475
 
446
- send_response req,
447
- result: "(rdbg:command) #{dbg_expr}",
448
- variablesReference: 0
449
- else
450
- @q_msg << req
451
- end
452
- when 'stackTrace',
453
- 'scopes',
454
- 'variables',
455
- 'source',
456
- 'completions'
476
+ send_response req,
477
+ result: "(rdbg:command) #{dbg_expr}",
478
+ variablesReference: 0
479
+ else
457
480
  @q_msg << req
481
+ end
482
+ when 'stackTrace',
483
+ 'scopes',
484
+ 'variables',
485
+ 'source',
486
+ 'completions'
487
+ @q_msg << req
458
488
 
489
+ else
490
+ if respond_to? mid = "custom_dap_request_#{req['command']}"
491
+ __send__ mid, req
459
492
  else
460
- if respond_to? mid = "custom_dap_request_#{req['command']}"
461
- __send__ mid, req
462
- else
463
- raise "Unknown request: #{req.inspect}"
464
- end
493
+ raise "Unknown request: #{req.inspect}"
465
494
  end
466
495
  end
467
- ensure
468
- send_event :terminated unless @sock.closed?
469
496
  end
470
497
 
471
498
  ## called by the SESSION thread
@@ -625,6 +652,7 @@ module DEBUGGER__
625
652
  expr = req.dig('arguments', 'expression')
626
653
 
627
654
  if find_waiting_tc(tid)
655
+ restart_all_threads
628
656
  request_tc [:dap, :evaluate, req, fid, expr, context]
629
657
  else
630
658
  fail_response req
@@ -701,6 +729,7 @@ module DEBUGGER__
701
729
  register_vars result[:variables], tid
702
730
  @ui.respond req, result
703
731
  when :evaluate
732
+ stop_all_threads
704
733
  message = result.delete :message
705
734
  if message
706
735
  @ui.respond req, success: false, message: message
@@ -759,7 +788,9 @@ module DEBUGGER__
759
788
 
760
789
  def dap_eval b, expr, _context, prompt: '(repl_eval)'
761
790
  begin
762
- b.eval(expr.to_s, prompt)
791
+ tp_allow_reentry do
792
+ b.eval(expr.to_s, prompt)
793
+ end
763
794
  rescue Exception => e
764
795
  e
765
796
  end
@@ -867,7 +898,7 @@ module DEBUGGER__
867
898
  }
868
899
  when String
869
900
  vars = [
870
- variable('#lengthddsfsd', obj.length),
901
+ variable('#length', obj.length),
871
902
  variable('#encoding', obj.encoding),
872
903
  ]
873
904
  printed_str = value_inspect(obj)
@@ -1014,7 +1045,7 @@ module DEBUGGER__
1014
1045
  klass = M_CLASS.bind_call(obj)
1015
1046
 
1016
1047
  begin
1017
- klass.name || klass.to_s
1048
+ M_NAME.bind_call(klass) || klass.to_s
1018
1049
  rescue Exception => e
1019
1050
  "<Error: #{e.message} (#{e.backtrace.first}>"
1020
1051
  end
data/lib/debug/session.rb CHANGED
@@ -82,7 +82,7 @@ class RubyVM::InstructionSequence
82
82
  def first_line
83
83
  self.to_a[4][:code_location][0]
84
84
  end unless method_defined?(:first_line)
85
- end
85
+ end if defined?(RubyVM::InstructionSequence)
86
86
 
87
87
  module DEBUGGER__
88
88
  PresetCommands = Struct.new(:commands, :source, :auto_continue)
@@ -133,7 +133,7 @@ module DEBUGGER__
133
133
  @commands = {}
134
134
  @unsafe_context = false
135
135
 
136
- @has_keep_script_lines = RubyVM.respond_to? :keep_script_lines
136
+ @has_keep_script_lines = defined?(RubyVM.keep_script_lines)
137
137
 
138
138
  @tp_load_script = TracePoint.new(:script_compiled){|tp|
139
139
  eval_script = tp.eval_script unless @has_keep_script_lines
@@ -202,6 +202,11 @@ module DEBUGGER__
202
202
  end
203
203
  @tp_thread_end.enable
204
204
 
205
+ if CONFIG[:irb_console] && !CONFIG[:open]
206
+ require_relative "irb_integration"
207
+ thc.activate_irb_integration
208
+ end
209
+
205
210
  # session start
206
211
  q << true
207
212
  session_server_main
@@ -256,6 +261,15 @@ module DEBUGGER__
256
261
  @tc << req
257
262
  end
258
263
 
264
+ def request_tc_with_restarted_threads(req)
265
+ restart_all_threads
266
+ request_tc(req)
267
+ end
268
+
269
+ def request_eval type, src
270
+ request_tc_with_restarted_threads [:eval, type, src]
271
+ end
272
+
259
273
  def process_event evt
260
274
  # variable `@internal_info` is only used for test
261
275
  tc, output, ev, @internal_info, *ev_args = evt
@@ -314,7 +328,7 @@ module DEBUGGER__
314
328
  if @displays.empty?
315
329
  wait_command_loop
316
330
  else
317
- request_tc [:eval, :display, @displays]
331
+ request_eval :display, @displays
318
332
  end
319
333
  when :result
320
334
  raise "[BUG] not in subsession" if @subsession_stack.empty?
@@ -329,6 +343,7 @@ module DEBUGGER__
329
343
  end
330
344
  end
331
345
 
346
+ stop_all_threads
332
347
  when :method_breakpoint, :watch_breakpoint
333
348
  bp = ev_args[1]
334
349
  if bp
@@ -342,6 +357,7 @@ module DEBUGGER__
342
357
  obj_inspect = ev_args[2]
343
358
  opt = ev_args[3]
344
359
  add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
360
+ stop_all_threads
345
361
  else
346
362
  stop_all_threads
347
363
  end
@@ -685,15 +701,15 @@ module DEBUGGER__
685
701
  register_command 'bt', 'backtrace', unsafe: false do |arg|
686
702
  case arg
687
703
  when /\A(\d+)\z/
688
- request_tc [:show, :backtrace, arg.to_i, nil]
704
+ request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
689
705
  when /\A\/(.*)\/\z/
690
706
  pattern = $1
691
- request_tc [:show, :backtrace, nil, Regexp.compile(pattern)]
707
+ request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
692
708
  when /\A(\d+)\s+\/(.*)\/\z/
693
709
  max, pattern = $1, $2
694
- request_tc [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
710
+ request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
695
711
  else
696
- request_tc [:show, :backtrace, nil, nil]
712
+ request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
697
713
  end
698
714
  end
699
715
 
@@ -810,15 +826,15 @@ module DEBUGGER__
810
826
 
811
827
  case sub
812
828
  when nil
813
- request_tc [:show, :default, pat] # something useful
829
+ request_tc_with_restarted_threads [:show, :default, pat] # something useful
814
830
  when :locals
815
- request_tc [:show, :locals, pat]
831
+ request_tc_with_restarted_threads [:show, :locals, pat]
816
832
  when :ivars
817
- request_tc [:show, :ivars, pat, opt]
833
+ request_tc_with_restarted_threads [:show, :ivars, pat, opt]
818
834
  when :consts
819
- request_tc [:show, :consts, pat, opt]
835
+ request_tc_with_restarted_threads [:show, :consts, pat, opt]
820
836
  when :globals
821
- request_tc [:show, :globals, pat]
837
+ request_tc_with_restarted_threads [:show, :globals, pat]
822
838
  when :threads
823
839
  thread_list
824
840
  :retry
@@ -838,7 +854,7 @@ module DEBUGGER__
838
854
  # * Show you available methods and instance variables of the given object.
839
855
  # * If the object is a class/module, it also lists its constants.
840
856
  register_command 'outline', 'o', 'ls', unsafe: false do |arg|
841
- request_tc [:show, :outline, arg]
857
+ request_tc_with_restarted_threads [:show, :outline, arg]
842
858
  end
843
859
 
844
860
  # * `display`
@@ -848,9 +864,9 @@ module DEBUGGER__
848
864
  register_command 'display', postmortem: false do |arg|
849
865
  if arg && !arg.empty?
850
866
  @displays << arg
851
- request_tc [:eval, :try_display, @displays]
867
+ request_eval :try_display, @displays
852
868
  else
853
- request_tc [:eval, :display, @displays]
869
+ request_eval :display, @displays
854
870
  end
855
871
  end
856
872
 
@@ -864,7 +880,7 @@ module DEBUGGER__
864
880
  if @displays[n = $1.to_i]
865
881
  @displays.delete_at n
866
882
  end
867
- request_tc [:eval, :display, @displays]
883
+ request_eval :display, @displays
868
884
  when nil
869
885
  if ask "clear all?", 'N'
870
886
  @displays.clear
@@ -925,10 +941,11 @@ module DEBUGGER__
925
941
  # * Invoke `irb` on the current frame.
926
942
  register_command 'irb' do |arg|
927
943
  if @ui.remote?
928
- @ui.puts "not supported on the remote console."
944
+ @ui.puts "\nIRB is not supported on the remote console."
929
945
  :retry
946
+ else
947
+ request_eval :irb, nil
930
948
  end
931
- request_eval :irb, nil
932
949
  end
933
950
 
934
951
  ### Trace
@@ -983,7 +1000,7 @@ module DEBUGGER__
983
1000
  :retry
984
1001
 
985
1002
  when /\Aobject\s+(.+)/
986
- request_tc [:trace, :object, $1.strip, {pattern: pattern, into: into}]
1003
+ request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]
987
1004
 
988
1005
  when /\Aoff\s+(\d+)\z/
989
1006
  if t = @tracers.values[$1.to_i]
@@ -1164,11 +1181,6 @@ module DEBUGGER__
1164
1181
  return :retry
1165
1182
  end
1166
1183
 
1167
- def request_eval type, src
1168
- restart_all_threads
1169
- request_tc [:eval, type, src]
1170
- end
1171
-
1172
1184
  def step_command type, arg
1173
1185
  if type == :until
1174
1186
  leave_subsession [:step, type, arg]
@@ -1739,15 +1751,19 @@ module DEBUGGER__
1739
1751
  # check breakpoints
1740
1752
  if file_path
1741
1753
  @bps.find_all do |_key, bp|
1742
- LineBreakpoint === bp && bp.path_is?(file_path)
1754
+ LineBreakpoint === bp && bp.path_is?(file_path) && (iseq.first_lineno..iseq.last_line).cover?(bp.line)
1743
1755
  end.each do |_key, bp|
1744
1756
  if !bp.iseq
1745
1757
  bp.try_activate iseq
1746
1758
  elsif reloaded
1747
1759
  @bps.delete bp.key # to allow duplicate
1748
- if nbp = LineBreakpoint.copy(bp, iseq)
1749
- add_bp nbp
1750
- end
1760
+
1761
+ # When we delete a breakpoint from the @bps hash, we also need to deactivate it or else its tracepoint event
1762
+ # will continue to be enabled and we'll suspend on ghost breakpoints
1763
+ bp.delete
1764
+
1765
+ nbp = LineBreakpoint.copy(bp, iseq)
1766
+ add_bp nbp
1751
1767
  end
1752
1768
  end
1753
1769
  else # !file_path => file_path is not existing
@@ -1993,6 +2009,13 @@ module DEBUGGER__
1993
2009
  def after_fork_parent
1994
2010
  @ui.after_fork_parent
1995
2011
  end
2012
+
2013
+ # experimental API
2014
+ def extend_feature session: nil, thread_client: nil, ui: nil
2015
+ Session.include session if session
2016
+ ThreadClient.include thread_client if thread_client
2017
+ @ui.extend ui if ui
2018
+ end
1996
2019
  end
1997
2020
 
1998
2021
  class ProcessGroup
@@ -2167,6 +2190,7 @@ module DEBUGGER__
2167
2190
  case loc.absolute_path
2168
2191
  when dir_prefix
2169
2192
  when %r{rubygems/core_ext/kernel_require\.rb}
2193
+ when %r{bundled_gems\.rb}
2170
2194
  else
2171
2195
  return loc if loc.absolute_path
2172
2196
  end
@@ -2517,7 +2541,17 @@ module DEBUGGER__
2517
2541
 
2518
2542
  module TrapInterceptor
2519
2543
  def trap sig, *command, &command_proc
2520
- case sig&.to_sym
2544
+ sym =
2545
+ case sig
2546
+ when String
2547
+ sig.to_sym
2548
+ when Integer
2549
+ Signal.signame(sig)&.to_sym
2550
+ else
2551
+ sig
2552
+ end
2553
+
2554
+ case sym
2521
2555
  when :INT, :SIGINT
2522
2556
  if defined?(SESSION) && SESSION.active? && SESSION.intercept_trap_sigint?
2523
2557
  return SESSION.save_int_trap(command.empty? ? command_proc : command.first)
@@ -22,7 +22,7 @@ module DEBUGGER__
22
22
  end
23
23
  end
24
24
 
25
- if RubyVM.respond_to? :keep_script_lines
25
+ if defined?(RubyVM.keep_script_lines)
26
26
  # Ruby 3.1 and later
27
27
  RubyVM.keep_script_lines = true
28
28
  require 'objspace'