require_relative 'byebug.so'
require_relative 'byebug/version'
require_relative 'byebug/context'
require_relative 'byebug/processor'
require_relative 'byebug/remote'
require 'stringio'
require 'linecache19'
module Byebug
self.handler = CommandProcessor.new
# List of files byebug will ignore while debugging
IGNORED_FILES = Dir[Pathname.new(__FILE__) + "../**/*.rb"].map {
|f| File.expand_path(f) }
# Default options to Byebug.start
DEFAULT_START_SETTINGS = {
init: true, # Set $0 and save ARGV?
post_mortem: false, # post-mortem debugging on uncaught exception?
tracing: nil # Byebug.tracing? value. true/false resets
} unless defined?(DEFAULT_START_SETTINGS)
# Configuration file used for startup commands. Default value is .byebugrc
INITFILE = '.byebugrc' unless defined?(INITFILE)
class << self
attr_accessor :last_exception
Byebug.last_exception = nil
# gdb-style annotation mode. Used in GNU Emacs interface
attr_accessor :annotate
def source_reload
Object.send(:remove_const, "SCRIPT_LINES__") if
Object.const_defined?("SCRIPT_LINES__")
Object.const_set("SCRIPT_LINES__", {})
LineCache::clear_file_cache
end
# Get line +line_number+ from file named +filename+.
# @return "\n" if there was a problem. Leaking blanks are stripped off.
def line_at(filename, line_number)
@@autoreload = nil unless defined?(@@autoreload)
line = LineCache::getline filename, line_number, @@autoreload
return "\n" unless line
return "#{line.gsub(/^\s+/, '').chomp}"
end
alias stop remove_tracepoints
# @param [String] file
# @param [Fixnum] line
# @param [String] expr
def add_breakpoint(file, line, expr=nil)
breakpoint = Breakpoint.new(file, line, expr)
breakpoints << breakpoint
breakpoint
end
def remove_breakpoint(id)
Breakpoint.remove breakpoints, id
end
def interface=(value)
handler.interface = value
end
#
# Byebug.start(options) -> bool
# Byebug.start(options) { ... } -> obj
#
# If it's called without a block, it returns +true+ unless byebug was
# already started.
#
# If a block is given, it starts byebug and yields block. After the block is
# executed it stops byebug with Byebug.stop method. Inside the block you
# will probably want to have a call to Byebug.byebug. For example:
#
# Byebug.start { byebug; foo } # Stop inside of foo
#
# Also, byebug only allows one invocation of byebug at a time; nested
# Byebug.start's have no effect and you can't use this inside byebug itself.
#
# <i>Note that if you want to stop byebug, you must call Byebug.stop as
# many times as you called Byebug.start method.</i>
#
# +options+ is a hash used to set various debugging options.
# :init - true if you want to save ARGV and some other variables to
# make a byebug restart possible. Only the first time :init
# is set to true the values will get set. Since ARGV is
# saved, you should make sure it hasn't been changed before
# the (first) call.
# :post_mortem - true if you want to enter post-mortem debugging on an
# uncaught exception. Once post-mortem debugging is set, it
# can't be unset.
#
def start(options={}, &block)
options = Byebug::DEFAULT_START_SETTINGS.merge(options)
if options[:init]
Byebug.const_set('ARGV', ARGV.clone) unless defined? Byebug::ARGV
Byebug.const_set('PROG_SCRIPT', $0) unless defined? Byebug::PROG_SCRIPT
Byebug.const_set('INITIAL_DIR', Dir.pwd) unless defined? Byebug::INITIAL_DIR
end
Byebug.tracing = options[:tracing] unless options[:tracing].nil?
if Byebug.started?
retval = block && block.call(self)
else
retval = Byebug._start(&block)
end
post_mortem if options[:post_mortem]
return retval
end
##
# Runs normal byebug initialization scripts.
#
# Reads and executes the commands from init file (if any) in the current
# working directory. This is only done if the current directory is
# different from your home directory. Thus, you can have more than one init
# file, one generic in your home directory, and another, specific to the
# program you are debugging, in the directory where you invoke byebug.
#
def run_init_script(out = handler.interface)
cwd_script = File.expand_path(File.join(".", INITFILE))
run_script(cwd_script, out) if File.exists?(cwd_script)
home_script = File.expand_path(File.join(ENV['HOME'].to_s, INITFILE))
if File.exists?(home_script) and cwd_script != home_script
run_script(home_script, out)
end
end
##
# Runs a script file
#
def run_script(file, out = handler.interface, verbose=false)
interface = ScriptInterface.new(File.expand_path(file), out)
processor = ControlCommandProcessor.new(interface)
processor.process_commands(verbose)
end
##
# Activates the post-mortem mode. There are two ways of using it:
#
# == Global post-mortem mode
# By calling Byebug.post_mortem method without a block, you install an
# at_exit hook that intercepts any exception not handled by your script
# and enables post-mortem mode.
#
# == Local post-mortem mode
#
# If you know that a particular block of code raises an exception you can
# enable post-mortem mode by wrapping this block with Byebug.post_mortem,
# e.g.
#
# def offender
# raise 'error'
# end
# Byebug.post_mortem do
# ...
# offender
# ...
# end
def post_mortem
if block_given?
old_post_mortem = self.post_mortem?
begin
self.post_mortem = true
yield
rescue Exception => exp
handle_post_mortem(exp)
raise
ensure
self.post_mortem = old_post_mortem
end
else
return if self.post_mortem?
self.post_mortem = true
debug_at_exit do
handle_post_mortem($!) if post_mortem?
end
end
end
def handle_post_mortem(exp)
return if !exp || !exp.__debug_context ||
exp.__debug_context.stack_size == 0
orig_tracing = Byebug.tracing?
Byebug.tracing = false
Byebug.last_exception = exp
handler.at_line(exp.__debug_context, exp.__debug_file, exp.__debug_line)
ensure
Byebug.tracing = orig_tracing
end
private :handle_post_mortem
end
end
class Exception
attr_reader :__debug_file, :__debug_line, :__debug_binding, :__debug_context
end
class Module
#
# Wraps the +meth+ method with Byebug.start {...} block.
#
def debug_method(meth)
old_meth = "__debugee_#{meth}"
old_meth = "#{$1}_set" if old_meth =~ /^(.+)=$/
alias_method old_meth.to_sym, meth
class_eval <<-EOD
def #{meth}(*args, &block)
Byebug.start do
byebug 2
#{old_meth}(*args, &block)
end
end
EOD
end
#
# Wraps the +meth+ method with Byebug.post_mortem {...} block.
#
def post_mortem_method(meth)
old_meth = "__postmortem_#{meth}"
old_meth = "#{$1}_set" if old_meth =~ /^(.+)=$/
alias_method old_meth.to_sym, meth
class_eval <<-EOD
def #{meth}(*args, &block)
Byebug.start do |dbg|
dbg.post_mortem do
#{old_meth}(*args, &block)
end
end
end
EOD
end
end
module Kernel
##
# Enters byebug after _steps_into_ line events and _steps_out_ return events
# occur. Before entering byebug startup, the init script is read.
#
def byebug(steps_into = 1, steps_out = 2)
Byebug.start
Byebug.run_init_script(StringIO.new)
Byebug.context.stop_return steps_out if steps_out >= 1
Byebug.context.step_into steps_into if steps_into >= 0
end
end