# File debug-1.4.0/lib/debug/thread_client.rb, line 20
def self.current
if thc = Thread.current[:DEBUGGER__ThreadClient]
thc
else
thc = SESSION.get_thread_client
Thread.current[:DEBUGGER__ThreadClient] = thc
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 75
def initialize id, q_evt, q_cmd, thr = Thread.current
@is_management = false
@id = id
@thread = thr
@target_frames = nil
@q_evt = q_evt
@q_cmd = q_cmd
@step_tp = nil
@output = []
@frame_formatter = method(:default_frame_formatter)
@var_map = {} # { thread_local_var_id => obj } for DAP
@obj_map = {} # { object_id => obj } for CDP
@recorder = nil
@mode = :waiting
set_mode :running
thr.instance_variable_set(:@__thread_client_id, id)
::DEBUGGER__.info("Thread \##{@id} is created.")
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 177
def << req
@q_cmd << req
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 34
def assemble_arguments(args)
args.map do |arg|
"#{colorize_cyan(arg[:name])}=#{arg[:value]}"
end.join(", ")
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 638
def class_method_map(classes)
dumped = Array.new
classes.reject { |mod| mod >= Object }.map do |mod|
methods = mod.public_instance_methods(false).select do |m|
dumped.push(m) unless dumped.include?(m)
end
[mod, methods]
end.reverse
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 138
def close
@q_cmd.close
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 433
def current_frame
if @target_frames
@target_frames[@current_frame_index]
else
nil
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 95
def deactivate
@step_tp.disable if @step_tp
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 40
def default_frame_formatter frame
call_identifier_str =
case frame.frame_type
when :block
level, block_loc, args = frame.block_identifier
if !args.empty?
args_str = " {|#{assemble_arguments(args)}|}"
end
"#{colorize_blue("block")}#{args_str} in #{colorize_blue(block_loc + level)}"
when :method
ci, args = frame.method_identifier
if !args.empty?
args_str = "(#{assemble_arguments(args)})"
end
"#{colorize_blue(ci)}#{args_str}"
when :c
colorize_blue(frame.c_identifier)
when :other
colorize_blue(frame.other_identifier)
end
location_str = colorize(frame.location_str, [:GREEN])
result = "#{call_identifier_str} at #{location_str}"
if return_str = frame.return_str
result += " #=> #{colorize_magenta(return_str)}"
end
result
end
# File debug-1.4.0/lib/debug/server_cdp.rb, line 859
def evaluate_result r
v = variable nil, r
v[:value]
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 187
def event! ev, *args
@q_evt << [self, @output, ev, generate_info, *args]
@output = []
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 351
def frame_eval src, re_raise: false
@success_last_eval = false
b = current_frame.eval_binding
special_local_variables current_frame do |name, var|
b.local_variable_set(name, var) if /\%/ !~ name
end
result = if b
f, _l = b.source_location
b.eval(src, "(rdbg)/#{f}")
else
frame_self = current_frame.self
instance_eval_for_cmethod(frame_self, src)
end
@success_last_eval = true
result
rescue Exception => e
return yield(e) if block_given?
puts "eval error: #{e}"
e.backtrace_locations&.each do |loc|
break if loc.path == __FILE__
puts " #{loc}"
end
raise if re_raise
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 601
def frame_str(i, frame: @target_frames[i])
cur_str = (@current_frame_index == i ? '=>' : ' ')
prefix = "#{cur_str}##{i}"
frame_string = @frame_formatter.call(frame)
"#{prefix}\t#{frame_string}"
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 181
def generate_info
return unless current_frame
{ location: current_frame.location_str, line: current_frame.location.lineno }
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 142
def inspect
if bt = @thread.backtrace
"#<DBG:TC #{self.id}:#{@mode}@#{bt[-1]}>"
else # bt can be nil
"#<DBG:TC #{self.id}:#{@mode}>"
end
end
this method is extracted to hide #frame_eval's local variables from C method eval's binding
# File debug-1.4.0/lib/debug/thread_client.rb, line 342
def instance_eval_for_cmethod frame_self, src
frame_self.instance_eval(src)
end
cmd: breakpoint
# File debug-1.4.0/lib/debug/thread_client.rb, line 650
def make_breakpoint args
case args.first
when :method
klass_name, op, method_name, cond, cmd, path = args[1..]
bp = MethodBreakpoint.new(current_frame.eval_binding, klass_name, op, method_name, cond: cond, command: cmd, path: path)
begin
bp.enable
rescue Exception => e
puts e.message
::DEBUGGER__::METHOD_ADDED_TRACKER.enable
end
bp
when :watch
ivar, object, result, cond, command, path = args[1..]
WatchIVarBreakpoint.new(ivar, object, result, cond: cond, command: command, path: path)
else
raise "unknown breakpoint: #{args}"
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 99
def management?
@is_management
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 103
def mark_as_management
@is_management = true
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 134
def name
"##{@id} #{@thread.name || @thread.backtrace.last}"
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 215
def on_breakpoint tp, bp
suspend tp.event, tp, bp: bp
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 207
def on_init name
wait_reply [:init, name]
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 203
def on_load iseq, eval_src
wait_reply [:load, iseq, eval_src]
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 227
def on_pause
suspend :pause
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 211
def on_trace trace_id, msg
wait_reply [:trace, trace_id, msg]
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 219
def on_trap sig
if waiting?
# raise Interrupt
else
suspend :trap, sig: sig
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 629
def outline_method(o, klass, obj)
singleton_class = begin obj.singleton_class; rescue TypeError; nil end
maps = class_method_map((singleton_class || klass).ancestors)
maps.each do |mod, methods|
name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods"
o.dump(name, methods)
end
end
# File debug-1.4.0/lib/debug/server_cdp.rb, line 640
def process_cdp args
type = args.shift
req = args.shift
case type
when :backtrace
exception = nil
result = {
reason: 'other',
callFrames: @target_frames.map.with_index{|frame, i|
exception = frame.raised_exception if frame == current_frame && frame.has_raised_exception
path = frame.realpath || frame.path
if path.match /<internal:(.*)>/
path = $1
end
{
callFrameId: SecureRandom.hex(16),
functionName: frame.name,
functionLocation: {
scriptId: path,
lineNumber: 0
},
location: {
scriptId: path,
lineNumber: frame.location.lineno - 1 # The line number is 0-based.
},
url: "http://debuggee#{path}",
scopeChain: [
{
type: 'local',
object: {
type: 'object',
objectId: rand.to_s
}
},
{
type: 'script',
object: {
type: 'object',
objectId: rand.to_s
}
},
{
type: 'global',
object: {
type: 'object',
objectId: rand.to_s
}
}
],
this: {
type: 'object'
}
}
}
}
if exception
result[:data] = evaluate_result exception
result[:reason] = 'exception'
end
event! :cdp_result, :backtrace, req, result
when :evaluate
res = {}
fid, expr = args
frame = @target_frames[fid]
message = nil
if frame && (b = frame.binding)
b = b.dup
special_local_variables current_frame do |name, var|
b.local_variable_set(name, var) if /\%/ !~name
end
result = nil
case req.dig('params', 'objectGroup')
when 'popover'
case expr
# Chrome doesn't read instance variables
when /\A\$\S/
global_variables.each{|gvar|
if gvar.to_s == expr
result = eval(gvar.to_s)
break false
end
} and (message = "Error: Not defined global variable: #{expr.inspect}")
when /(\A[A-Z][a-zA-Z]*)/
unless result = search_const(b, $1)
message = "Error: Not defined constant: #{expr.inspect}"
end
else
begin
# try to check local variables
b.local_variable_defined?(expr) or raise NameError
result = b.local_variable_get(expr)
rescue NameError
# try to check method
if b.receiver.respond_to? expr, include_all: true
result = b.receiver.method(expr)
else
message = "Error: Can not evaluate: #{expr.inspect}"
end
end
end
else
begin
orig_stdout = $stdout
$stdout = StringIO.new
result = current_frame.binding.eval(expr.to_s, '(DEBUG CONSOLE)')
rescue Exception => e
result = e
b = result.backtrace.map{|e| " #{e}\n"}
line = b.first.match('.*:(\d+):in .*')[1].to_i
res[:exceptionDetails] = {
exceptionId: 1,
text: 'Uncaught',
lineNumber: line - 1,
columnNumber: 0,
exception: evaluate_result(result),
}
ensure
output = $stdout.string
$stdout = orig_stdout
end
end
else
result = Exception.new("Error: Can not evaluate on this frame")
end
res[:result] = evaluate_result(result)
event! :cdp_result, :evaluate, req, message: message, response: res, output: output
when :scope
fid = args.shift
frame = @target_frames[fid]
if b = frame.binding
vars = b.local_variables.map{|name|
v = b.local_variable_get(name)
variable(name, v)
}
special_local_variables frame do |name, val|
vars.unshift variable(name, val)
end
vars.unshift variable('%self', b.receiver)
elsif lvars = frame.local_variables
vars = lvars.map{|var, val|
variable(var, val)
}
else
vars = [variable('%self', frame.self)]
special_local_variables frame do |name, val|
vars.unshift variable(name, val)
end
end
event! :cdp_result, :scope, req, vars
when :properties
oid = args.shift
result = []
prop = []
if obj = @obj_map[oid]
case obj
when Array
result = obj.map.with_index{|o, i|
variable i.to_s, o
}
when Hash
result = obj.map{|k, v|
variable(k, v)
}
when Struct
result = obj.members.map{|m|
variable(m, obj[m])
}
when String
prop = [
property('#length', obj.length),
property('#encoding', obj.encoding)
]
when Class, Module
result = obj.instance_variables.map{|iv|
variable(iv, obj.instance_variable_get(iv))
}
prop = [property('%ancestors', obj.ancestors[1..])]
when Range
prop = [
property('#begin', obj.begin),
property('#end', obj.end),
]
end
result += obj.instance_variables.map{|iv|
variable(iv, obj.instance_variable_get(iv))
}
prop += [property('#class', obj.class)]
end
event! :cdp_result, :properties, req, result: result, internalProperties: prop
end
end
# File debug-1.4.0/lib/debug/server_dap.rb, line 587
def process_dap args
# pp tc: self, args: args
type = args.shift
req = args.shift
case type
when :backtrace
event! :dap_result, :backtrace, req, {
stackFrames: @target_frames.map{|frame|
path = frame.realpath || frame.path
ref = frame.file_lines unless path && File.exist?(path)
{
# id: ??? # filled by SESSION
name: frame.name,
line: frame.location.lineno,
column: 1,
source: {
name: File.basename(frame.path),
path: path,
sourceReference: ref,
},
}
}
}
when :scopes
fid = args.shift
frame = @target_frames[fid]
lnum =
if frame.binding
frame.binding.local_variables.size
elsif vars = frame.local_variables
vars.size
else
0
end
event! :dap_result, :scopes, req, scopes: [{
name: 'Local variables',
presentationHint: 'locals',
# variablesReference: N, # filled by SESSION
namedVariables: lnum,
indexedVariables: 0,
expensive: false,
}, {
name: 'Global variables',
presentationHint: 'globals',
variablesReference: 1, # GLOBAL
namedVariables: global_variables.size,
indexedVariables: 0,
expensive: false,
}]
when :scope
fid = args.shift
frame = @target_frames[fid]
if b = frame.binding
vars = b.local_variables.map{|name|
v = b.local_variable_get(name)
variable(name, v)
}
special_local_variables frame do |name, val|
vars.unshift variable(name, val)
end
vars.unshift variable('%self', b.receiver)
elsif lvars = frame.local_variables
vars = lvars.map{|var, val|
variable(var, val)
}
else
vars = [variable('%self', frame.self)]
special_local_variables frame do |name, val|
vars.push variable(name, val)
end
end
event! :dap_result, :scope, req, variables: vars, tid: self.id
when :variable
vid = args.shift
obj = @var_map[vid]
if obj
case req.dig('arguments', 'filter')
when 'indexed'
start = req.dig('arguments', 'start') || 0
count = req.dig('arguments', 'count') || obj.size
vars = (start ... (start + count)).map{|i|
variable(i.to_s, obj[i])
}
else
vars = []
case obj
when Hash
vars = obj.map{|k, v|
variable(DEBUGGER__.safe_inspect(k), v,)
}
when Struct
vars = obj.members.map{|m|
variable(m, obj[m])
}
when String
vars = [
variable('#length', obj.length),
variable('#encoding', obj.encoding)
]
when Class, Module
vars = obj.instance_variables.map{|iv|
variable(iv, obj.instance_variable_get(iv))
}
vars.unshift variable('%ancestors', obj.ancestors[1..])
when Range
vars = [
variable('#begin', obj.begin),
variable('#end', obj.end),
]
end
vars += obj.instance_variables.map{|iv|
variable(iv, obj.instance_variable_get(iv))
}
vars.unshift variable('#class', obj.class)
end
end
event! :dap_result, :variable, req, variables: (vars || []), tid: self.id
when :evaluate
fid, expr, context = args
frame = @target_frames[fid]
message = nil
if frame && (b = frame.binding)
b = b.dup
special_local_variables current_frame do |name, var|
b.local_variable_set(name, var) if /\%/ !~ name
end
case context
when 'repl', 'watch'
begin
result = b.eval(expr.to_s, '(DEBUG CONSOLE)')
rescue Exception => e
result = e
end
when 'hover'
case expr
when /\A\@\S/
begin
(r = b.receiver).instance_variable_defined?(expr) or raise(NameError)
result = r.instance_variable_get(expr)
rescue NameError
message = "Error: Not defined instance variable: #{expr.inspect}"
end
when /\A\$\S/
global_variables.each{|gvar|
if gvar.to_s == expr
result = eval(gvar.to_s)
break false
end
} and (message = "Error: Not defined global variable: #{expr.inspect}")
when /\A[A-Z]/
unless result = search_const(b, expr)
message = "Error: Not defined constants: #{expr.inspect}"
end
else
begin
# try to check local variables
b.local_variable_defined?(expr) or raise NameError
result = b.local_variable_get(expr)
rescue NameError
# try to check method
if b.receiver.respond_to? expr, include_all: true
result = b.receiver.method(expr)
else
message = "Error: Can not evaluate: #{expr.inspect}"
end
end
end
else
message = "Error: unknown context: #{context}"
end
else
result = 'Error: Can not evaluate on this frame'
end
event! :dap_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result)
when :completions
fid, text = args
frame = @target_frames[fid]
if (b = frame&.binding) && word = text&.split(/[\s\{]/)&.last
words = IRB::InputCompletor::retrieve_completion_data(word, bind: b).compact
end
event! :dap_result, :completions, req, targets: (words || []).map{|phrase|
if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase
w = $1
else
w = phrase
end
begin
if b&.local_variable_defined?(w)
v = b.local_variable_get(w)
phrase += " (variable:#{DEBUGGER__.safe_inspect(v)})"
end
rescue NameError
end
{
label: phrase,
text: w,
}
}
else
raise "Unknown req: #{args.inspect}"
end
end
# File debug-1.4.0/lib/debug/server_cdp.rb, line 864
def property name, obj
v = variable name, obj
v.delete :configurable
v.delete :enumerable
v
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 163
def puts str = ''
if @recorder&.replaying?
prefix = colorize_dim("[replay] ")
end
case str
when nil
@output << "\n"
when Array
str.each{|s| puts s}
else
@output << "#{prefix}#{str.chomp}\n"
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 515
def puts_variable_info label, obj, pat
return if pat && pat !~ label
begin
inspected = obj.inspect
rescue Exception => e
inspected = e.inspect
end
mono_info = "#{label} = #{inspected}"
w = SESSION::width
if mono_info.length >= w
info = truncate(mono_info, width: w)
else
valstr = colored_inspect(obj, width: 2 ** 30)
valstr = inspected if valstr.lines.size > 1
info = "#{colorize_cyan(label)} = #{valstr}"
end
puts info
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 286
def replay_suspend
# @recorder.current_position
suspend :replay, replay_frames: @recorder.current_frame
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 126
def running?
@mode == :running
end
# File debug-1.4.0/lib/debug/server_cdp.rb, line 842
def search_const b, expr
cs = expr.split('::')
[Object, *b.eval('Module.nesting')].reverse_each{|mod|
if cs.all?{|c|
if mod.const_defined?(c)
mod = mod.const_get(c)
else
false
end
}
# if-body
return mod
end
}
false
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 107
def set_mode mode
# STDERR.puts "#{@mode} => #{mode} @ #{caller.inspect}"
# pp caller
# mode transition check
case mode
when :running
raise "#{mode} is given, but #{mode}" unless self.waiting?
when :waiting
# TODO: there is waiting -> waiting
# raise "#{mode} is given, but #{mode}" unless self.running?
else
raise "unknown mode: #{mode}"
end
# DEBUGGER__.warn "#{@mode} => #{mode} @ #{self.inspect}"
@mode = mode
end
cmd: show edit
# File debug-1.4.0/lib/debug/thread_client.rb, line 546
def show_by_editor path = nil
unless path
if @target_frames && frame = @target_frames[@current_frame_index]
path = frame.path
else
return # can't get path
end
end
if File.exist?(path)
if editor = (ENV['RUBY_DEBUG_EDITOR'] || ENV['EDITOR'])
puts "command: #{editor}"
puts " path: #{path}"
system(editor, path)
else
puts "can not find editor setting: ENV['RUBY_DEBUG_EDITOR'] or ENV['EDITOR']"
end
else
puts "Can not find file: #{path}"
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 475
def show_consts pat, only_self: false
if s = current_frame&.self
cs = {}
if s.kind_of? Module
cs[s] = :self
else
s = s.class
cs[s] = :self unless only_self
end
unless only_self
s.ancestors.each{|c| break if c == Object; cs[c] = :ancestors}
if b = current_frame&.binding
b.eval('Module.nesting').each{|c| cs[c] = :nesting unless cs.has_key? c}
end
end
names = {}
cs.each{|c, _|
c.constants(false).sort.each{|name|
next if names.has_key? name
names[name] = nil
value = c.const_get(name)
puts_variable_info name, value, pat
}
}
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 597
def show_frame i=0
puts frame_str(i)
end
cmd: show frames
# File debug-1.4.0/lib/debug/thread_client.rb, line 570
def show_frames max = nil, pattern = nil
if @target_frames && (max ||= @target_frames.size) > 0
frames = []
@target_frames.each_with_index{|f, i|
next if pattern && !(f.name.match?(pattern) || f.location_str.match?(pattern))
next if CONFIG[:skip_path] && CONFIG[:skip_path].any?{|pat|
case pat
when String
f.location_str.start_with?(pat)
when Regexp
f.location_str.match?(pat)
end
}
frames << [i, f]
}
size = frames.size
max.times{|i|
break unless frames[i]
index, frame = frames[i]
puts frame_str(index, frame: frame)
}
puts " # and #{size - max} frames (use `bt' command for all frames)" if max < size
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 506
def show_globals pat
global_variables.sort.each{|name|
next if SKIP_GLOBAL_LIST.include? name
value = eval(name.to_s)
puts_variable_info name, value, pat
}
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 466
def show_ivars pat
if s = current_frame&.self
s.instance_variables.sort.each{|iv|
value = s.instance_variable_get(iv)
puts_variable_info iv, value, pat
}
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 451
def show_locals pat
if s = current_frame&.self
puts_variable_info '%self', s, pat
end
special_local_variables current_frame do |name, val|
puts_variable_info name, val, pat
end
if vars = current_frame&.local_variables
vars.each{|var, val|
puts_variable_info var, val, pat
}
end
end
cmd: show outline
# File debug-1.4.0/lib/debug/thread_client.rb, line 610
def show_outline expr
begin
obj = frame_eval(expr, re_raise: true)
rescue Exception
# ignore
else
o = Output.new(@output)
locals = current_frame&.local_variables
klass = (obj.class == Class || obj.class == Module ? obj : obj.class)
o.dump("constants", obj.constants) if obj.respond_to?(:constants)
outline_method(o, klass, obj)
o.dump("instance variables", obj.instance_variables)
o.dump("class variables", klass.class_variables)
o.dump("locals", locals.keys) if locals
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 382
def show_src(frame_index: @current_frame_index,
update_line: false,
max_lines: CONFIG[:show_src_lines] || 10,
start_line: nil,
end_line: nil,
dir: +1)
if @target_frames && frame = @target_frames[frame_index]
if file_lines = frame.file_lines
frame_line = frame.location.lineno - 1
lines = file_lines.map.with_index do |e, i|
cur = i == frame_line ? '=>' : ' '
line = colorize_dim('%4d|' % (i+1))
"#{cur}#{line} #{e}"
end
unless start_line
if frame.show_line
if dir > 0
start_line = frame.show_line
else
end_line = frame.show_line - max_lines
start_line = [end_line - max_lines, 0].max
end
else
start_line = [frame_line - max_lines/2, 0].max
end
end
unless end_line
end_line = [start_line + max_lines, lines.size].min
end
if update_line
frame.show_line = end_line
end
if start_line != end_line && max_lines
puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1
puts lines[start_line ... end_line]
end
else # no file lines
puts "# No sourcefile available for #{frame.path}"
end
end
rescue Exception => e
p e
pp e.backtrace
exit!
end
cmd: show
# File debug-1.4.0/lib/debug/thread_client.rb, line 443
def special_local_variables frame
SPECIAL_LOCAL_VARS.each do |mid, name|
next unless frame&.send("has_#{mid}")
name = name.sub('_', '%') if frame.eval_binding.local_variable_defined?(name)
yield name, frame.send(mid)
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 300
def step_tp iter, events = [:line, :b_return, :return]
@step_tp.disable if @step_tp
thread = Thread.current
if SUPPORT_TARGET_THREAD
@step_tp = TracePoint.new(*events){|tp|
next if SESSION.break_at? tp.path, tp.lineno
next if !yield(tp.event)
next if tp.path.start_with?(__dir__)
next if tp.path.start_with?('<internal:trace_point>')
next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
loc = caller_locations(1, 1).first
next if skip_location?(loc)
next if iter && (iter -= 1) > 0
tp.disable
suspend tp.event, tp
}
@step_tp.enable(target_thread: thread)
else
@step_tp = TracePoint.new(*events){|tp|
next if thread != Thread.current
next if SESSION.break_at? tp.path, tp.lineno
next if !yield(tp.event)
next if tp.path.start_with?(__dir__)
next if tp.path.start_with?('<internal:trace_point>')
next unless File.exist?(tp.path) if CONFIG[:skip_nosrc]
loc = caller_locations(1, 1).first
next if skip_location?(loc)
next if iter && (iter -= 1) > 0
tp.disable
suspend tp.event, tp
}
@step_tp.enable
end
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 231
def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil
return if management?
@current_frame_index = 0
case
when postmortem_frames
@target_frames = postmortem_frames
@postmortem = true
when replay_frames
@target_frames = replay_frames
else
@target_frames = DEBUGGER__.capture_frames(__dir__)
end
cf = @target_frames.first
if cf
@location = cf.location
case event
when :return, :b_return, :c_return
cf.has_return_value = true
cf.return_value = tp.return_value
end
if CatchBreakpoint === bp
cf.has_raised_exception = true
cf.raised_exception = bp.last_exc
end
if postmortem_exc
cf.has_raised_exception = true
cf.raised_exception = postmortem_exc
end
end
if event != :pause
show_src
show_frames CONFIG[:show_frames] || 2
set_mode :waiting
if bp
event! :suspend, :breakpoint, bp.key
elsif sig
event! :suspend, :trap, sig
else
event! :suspend, event
end
else
set_mode :waiting
end
wait_next_action
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 150
def to_s
loc = current_frame&.location
if loc
str = "(#{@thread.name || @thread.status})@#{loc}"
else
str = "(#{@thread.name || @thread.status})@#{@thread.to_s}"
end
str += " (not under control)" unless self.waiting?
str
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 538
def truncate(string, width))
str = string[0 .. (width-4)] + '...'
str += ">" if str.start_with?("#<")
str
end
# File debug-1.4.0/lib/debug/server_cdp.rb, line 895
def variable name, obj
case obj
when Array
variable_ name, obj, 'object', description: "Array(#{obj.size})", subtype: 'array'
when Hash
variable_ name, obj, 'object', description: "Hash(#{obj.size})", subtype: 'map'
when String
variable_ name, obj, 'string', description: obj
when Class, Module, Struct, Range, Time, Method
variable_ name, obj, 'object'
when TrueClass, FalseClass
variable_ name, obj, 'boolean'
when Symbol
variable_ name, obj, 'symbol'
when Integer, Float
variable_ name, obj, 'number'
when Exception
bt = nil
if log = obj.backtrace
bt = log.map{|e| " #{e}\n"}.join
end
variable_ name, obj, 'object', description: "#{obj.inspect}\n#{bt}", subtype: 'error'
else
variable_ name, obj, 'undefined'
end
end
# File debug-1.4.0/lib/debug/server_cdp.rb, line 871
def variable_ name, obj, type, description: obj.inspect, subtype: nil
oid = rand.to_s
@obj_map[oid] = obj
prop = {
name: name,
value: {
type: type,
description: description,
value: obj,
objectId: oid
},
configurable: true, # TODO: Change these parts because
enumerable: true # they are not necessarily `true`.
}
if type == 'object'
v = prop[:value]
v.delete :value
v[:subtype] = subtype if subtype
v[:className] = obj.class
end
prop
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 674
def wait_next_action
wait_next_action_
rescue SuspendReplay
replay_suspend
end
# File debug-1.4.0/lib/debug/thread_client.rb, line 680
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
if @recorder&.replaying?
@recorder.step_forward
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
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
loc = caller_locations(2, 1).first
loc_path = loc.absolute_path || "!eval:#{loc.path}"
# same stack depth
(DEBUGGER__.frame_depth - 3 <= depth) ||
# different frame
(next_line && loc_path == path &&
(loc_lineno = loc.lineno) > line &&
loc_lineno <= next_line)
end
break
when :finish
finish_frames = (iter || 1) - 1
goal_depth = @target_frames.first.frame_depth - finish_frames
step_tp nil, [:return, :b_return] do
DEBUGGER__.frame_depth - 3 <= goal_depth ? true : false
end
break
when :back
if @recorder&.can_step_back?
unless @recorder.backup_frames
@recorder.backup_frames = @target_frames
end
@recorder.step_back
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
begin
result = frame_eval('binding.irb')
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 :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
show_ivars pat
when :consts
pat = args.shift
show_consts pat
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 = obj.inspect
width = 50
if obj_inspect.length >= width
obj_inspect = truncate(obj_inspect, width: width)
end
event! :result, :trace_pass, obj.object_id, 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
@recorder.enable
end
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 :dap
process_dap args
when :cdp
process_cdp args
else
raise [cmd, *args].inspect
end
end
rescue SuspendReplay, SystemExit, Interrupt
raise
rescue Exception => e
pp ["DEBUGGER Exception: #{__FILE__}:#{__LINE__}", e, e.backtrace]
raise
end