ruby/lib/irb/cmd/debug.rb
Stan Lo 7f8f62c93b [ruby/irb] Support seamless integration with ruby/debug
(https://github.com/ruby/irb/pull/575)

* Support native integration with ruby/debug

* Prevent using multi-irb and activating debugger at the same time

Multi-irb makes a few assumptions:

- IRB will manage all threads that host sub-irb sessions
- All IRB sessions will be run on the threads created by IRB itself

However, when using the debugger these assumptions are broken:

- `debug` will freeze ALL threads when it suspends the session (e.g. when
  hitting a breakpoint, or performing step-debugging).
- Since the irb-debug integration runs IRB as the debugger's interface,
  it will be run on the debugger's thread, which is not managed by IRB.

So we should prevent the 2 features from being used at the same time.
To do that, we check if the other feature is already activated when
executing the commands that would activate the other feature.

d8fb3246be
2023-08-13 18:30:34 +00:00

80 lines
2.5 KiB
Ruby

require_relative "nop"
require_relative "../debug"
module IRB
# :stopdoc:
module ExtendCommand
class Debug < Nop
category "Debugging"
description "Start the debugger of debug.gem."
BINDING_IRB_FRAME_REGEXPS = [
'<internal:prelude>',
binding.method(:irb).source_location.first,
].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ }
def execute(pre_cmds: nil, do_cmds: nil)
if irb_context.with_debugger
# If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger.
if cmd = pre_cmds || do_cmds
throw :IRB_EXIT, cmd
else
puts "IRB is already running with a debug session."
return
end
else
# If IRB is not running with a debug session yet, then:
# 1. Check if the debugging command is run from a `binding.irb` call.
# 2. If so, try setting up the debug gem.
# 3. Insert a debug breakpoint at `Irb#debug_break` with the intended command.
# 4. Exit the current Irb#run call via `throw :IRB_EXIT`.
# 5. `Irb#debug_break` will be called and trigger the breakpoint, which will run the intended command.
unless binding_irb?
puts "`debug` command is only available when IRB is started with binding.irb"
return
end
if IRB.respond_to?(:JobManager)
warn "Can't start the debugger when IRB is running in a multi-IRB session."
return
end
unless IRB::Debug.setup(irb_context.irb)
puts <<~MSG
You need to install the debug gem before using this command.
If you use `bundle exec`, please add `gem "debug"` into your Gemfile.
MSG
return
end
IRB::Debug.insert_debug_break(pre_cmds: pre_cmds, do_cmds: do_cmds)
# exit current Irb#run call
throw :IRB_EXIT
end
end
private
def binding_irb?
caller.any? do |frame|
BINDING_IRB_FRAME_REGEXPS.any? do |regexp|
frame.match?(regexp)
end
end
end
end
class DebugCommand < Debug
def self.category
"Debugging"
end
def self.description
command_name = self.name.split("::").last.downcase
"Start the debugger of debug.gem and run its `#{command_name}` command."
end
end
end
end