def wait_next_action_
# assertions
raise "@mode is #{@mode}" if !waiting?
unless SESSION.active?
pp caller
set_mode :running
return
end
while true
begin
set_mode :waiting if !waiting?
cmds = @q_cmd.pop
# pp [self, cmds: cmds]
break unless cmds
ensure
set_mode :running
end
cmd, *args = *cmds
case cmd
when :continue
break
when :step
step_type = args[0]
iter = args[1]
case step_type
when :in
iter = iter || 1
if @recorder&.replaying?
@recorder.step_forward iter
raise SuspendReplay
else
step_tp iter do
true
end
break
end
when :next
frame = @target_frames.first
path = frame.location.absolute_path || "!eval:#{frame.path}"
line = frame.location.lineno
label = frame.location.base_label
if frame.iseq
frame.iseq.traceable_lines_norec(lines = {})
next_line = lines.keys.bsearch{|e| e > line}
if !next_line && (last_line = frame.iseq.last_line) > line
next_line = last_line
end
end
depth = @target_frames.first.frame_depth
step_tp iter do |tp|
loc = caller_locations(2, 1).first
loc_path = loc.absolute_path || "!eval:#{loc.path}"
loc_label = loc.base_label
loc_depth = DEBUGGER__.frame_depth - 3
case
when loc_depth == depth && loc_label == label
true
when loc_depth < depth
# lower stack depth
true
when (next_line &&
loc_path == path &&
(loc_lineno = loc.lineno) > line &&
loc_lineno <= next_line)
# different frame (maybe block) but the line is before next_line
true
end
end
break
when :finish
finish_frames = (iter || 1) - 1
frame = @target_frames.first
goal_depth = frame.frame_depth - finish_frames - (frame.has_return_value ? 1 : 0)
step_tp nil, [:return, :b_return] do
DEBUGGER__.frame_depth - 3 <= goal_depth ? true : false
end
break
when :until
location = iter&.strip
frame = @target_frames.first
depth = frame.frame_depth - (frame.has_return_value ? 1 : 0)
target_location_label = frame.location.base_label
case location
when nil, /\A(?:(.+):)?(\d+)\z/
no_loc = !location
file = $1 || frame.location.path
line = ($2 || frame.location.lineno + 1).to_i
step_tp nil, [:line, :return] do |tp|
if tp.event == :line
next false if no_loc && depth < DEBUGGER__.frame_depth - 3
next false unless tp.path.end_with?(file)
next false unless tp.lineno >= line
true
else
true if depth >= DEBUGGER__.frame_depth - 3 &&
caller_locations(2, 1).first.label == target_location_label
# TODO: imcomplete condition
end
end
else
pat = location
if /\A\/(.+)\/\z/ =~ pat
pat = Regexp.new($1)
end
step_tp nil, [:call, :c_call, :return] do |tp|
case tp.event
when :call, :c_call
true if pat === tp.callee_id.to_s
else # :return, :b_return
true if depth >= DEBUGGER__.frame_depth - 3 &&
caller_locations(2, 1).first.label == target_location_label
# TODO: imcomplete condition
end
end
end
break
when :back
iter = iter || 1
if @recorder&.can_step_back?
unless @recorder.backup_frames
@recorder.backup_frames = @target_frames
end
@recorder.step_back iter
raise SuspendReplay
else
puts "Can not step back more."
event! :result, nil
end
when :reset
if @recorder&.replaying?
@recorder.step_reset
raise SuspendReplay
end
else
raise "unknown: #{type}"
end
when :eval
eval_type, eval_src = *args
result_type = nil
case eval_type
when :p
result = frame_eval(eval_src)
puts "=> " + color_pp(result, 2 ** 30)
if alloc_path = ObjectSpace.allocation_sourcefile(result)
puts "allocated at #{alloc_path}:#{ObjectSpace.allocation_sourceline(result)}"
end
when :pp
result = frame_eval(eval_src)
puts color_pp(result, SESSION.width)
if alloc_path = ObjectSpace.allocation_sourcefile(result)
puts "allocated at #{alloc_path}:#{ObjectSpace.allocation_sourceline(result)}"
end
when :call
result = frame_eval(eval_src)
when :irb
require 'irb' # prelude's binding.irb doesn't have show_code option
begin
result = frame_eval('binding.irb(show_code: false)', binding_location: true)
ensure
# workaround: https://github.com/ruby/debug/issues/308
Reline.prompt_proc = nil if defined? Reline
end
when :display, :try_display
failed_results = []
eval_src.each_with_index{|src, i|
result = frame_eval(src){|e|
failed_results << [i, e.message]
"<error: #{e.message}>"
}
puts "#{i}: #{src} = #{result}"
}
result_type = eval_type
result = failed_results
else
raise "unknown error option: #{args.inspect}"
end
event! :result, result_type, result
when :frame
type, arg = *args
case type
when :up
if @current_frame_index + 1 < @target_frames.size
@current_frame_index += 1
show_src max_lines: 1
show_frame(@current_frame_index)
end
when :down
if @current_frame_index > 0
@current_frame_index -= 1
show_src max_lines: 1
show_frame(@current_frame_index)
end
when :set
if arg
index = arg.to_i
if index >= 0 && index < @target_frames.size
@current_frame_index = index
else
puts "out of frame index: #{index}"
end
end
show_src max_lines: 1
show_frame(@current_frame_index)
else
raise "unsupported frame operation: #{arg.inspect}"
end
event! :result, nil
when :show
type = args.shift
case type
when :backtrace
max_lines, pattern = *args
show_frames max_lines, pattern
when :list
show_src(update_line: true, **(args.first || {}))
when :whereami
show_src ignore_show_line: true
show_frames CONFIG[:show_frames]
when :edit
show_by_editor(args.first)
when :default
pat = args.shift
show_locals pat
show_ivars pat
show_consts pat, only_self: true
when :locals
pat = args.shift
show_locals pat
when :ivars
pat = args.shift
expr = args.shift
show_ivars pat, expr
when :consts
pat = args.shift
expr = args.shift
show_consts pat, expr
when :globals
pat = args.shift
show_globals pat
when :outline
show_outline args.first || 'self'
else
raise "unknown show param: " + [type, *args].inspect
end
event! :result, nil
when :breakpoint
case args[0]
when :method
bp = make_breakpoint args
event! :result, :method_breakpoint, bp
when :watch
ivar, cond, command, path = args[1..]
result = frame_eval(ivar)
if @success_last_eval
object =
if b = current_frame.binding
b.receiver
else
current_frame.self
end
bp = make_breakpoint [:watch, ivar, object, result, cond, command, path]
event! :result, :watch_breakpoint, bp
else
event! :result, nil
end
end
when :trace
case args.shift
when :object
begin
obj = frame_eval args.shift, re_raise: true
opt = args.shift
obj_inspect = DEBUGGER__.safe_inspect(obj)
width = 50
if obj_inspect.length >= width
obj_inspect = truncate(obj_inspect, width: width)
end
event! :result, :trace_pass, M_OBJECT_ID.bind_call(obj), obj_inspect, opt
rescue => e
puts e.message
event! :result, nil
end
else
raise "unreachable"
end
when :record
case args[0]
when nil
# ok
when :on
# enable recording
if !@recorder
@recorder = Recorder.new
end
@recorder.enable
when :off
if @recorder&.enabled?
@recorder.disable
end
else
raise "unknown: #{args.inspect}"
end
if @recorder&.enabled?
puts "Recorder for #{Thread.current}: on (#{@recorder.log.size} records)"
else
puts "Recorder for #{Thread.current}: off"
end
event! :result, nil
when :quit
sleep # wait for SystemExit
when :dap
process_dap args
when :cdp
process_cdp args
else
raise [cmd, *args].inspect
end
end
rescue SuspendReplay, SystemExit, Interrupt
raise
rescue Exception => e
STDERR.puts e.cause.inspect
STDERR.puts e.inspect
Thread.list.each{|th|
STDERR.puts "@@@ #{th}"
th.backtrace.each{|b|
STDERR.puts " > #{b}"
}
}
p ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
raise
ensure
@returning = false
end