Class: DEBUGGER__::ThreadClient
Relationships & Source Files | |
Namespace Children | |
Classes:
| |
Exceptions:
| |
Super Chains via Extension / Inclusion / Inheritance | |
Instance Chain:
|
|
Inherits: | Object |
Defined in: | lib/debug/server_cdp.rb, lib/debug/irb_integration.rb, lib/debug/server_dap.rb, lib/debug/thread_client.rb |
Constant Summary
-
MAX_LENGTH =
# File 'lib/debug/server_dap.rb', line 776180
-
SPECIAL_LOCAL_VARS =
# File 'lib/debug/thread_client.rb', line 430[ [:raised_exception, "_raised"], [:return_value, "_return"], ]
GlobalVariablesHelper
- Included
Class Method Summary
Instance Attribute Summary
- #check_bp_fulfillment_map readonly
- #id readonly
- #management? ⇒ Boolean readonly
- #recorder readonly
- #running? ⇒ Boolean readonly
- #thread readonly
- #waiting? ⇒ Boolean readonly
Instance Method Summary
- #<<(req)
- #activate_irb_integration
- #assemble_arguments(args)
- #class_method_map(classes)
- #close
- #collect_locals(frame)
-
#constant_name?(name) ⇒ Boolean
TODO: support non-ASCII Constant name.
- #current_frame
- #dap_eval(b, expr, _context, prompt: '(repl_eval)')
- #deactivate
- #debug_cmd(cmds)
- #debug_event(ev, args)
- #debug_mode(old_mode, new_mode)
- #debug_suspend(event)
- #default_frame_formatter(frame)
-
#evaluate_result(r)
See additional method definition at file lib/debug/server_cdp.rb line 1216.
- #event!(ev, *args)
- #exceptionDetails(exc, text)
- #frame_eval(src, re_raise: false, binding_location: false)
- #frame_eval_core(src, b, binding_location: false)
- #frame_str(i, frame: )
- #generate_info
- #get_consts(expr = nil, only_self: false, &block)
- #get_frame(index)
- #get_src(frame, max_lines:, start_line: nil, end_line: nil, dir: +1)
- #inspect
- #internalProperty(name, obj)
- #iter_consts(c, names = {})
- #location
- #make_breakpoint(args)
- #mark_as_management
- #name
- #on_breakpoint(tp, bp)
- #on_init(name)
- #on_load(iseq, eval_src)
- #on_pause
- #on_trace(trace_id, msg)
- #on_trap(sig)
- #outline_method(o, klass, obj)
- #preview(name, obj)
- #preview_(value, hash, overflow)
- #process_cdp(args)
- #process_dap(args)
- #propertyDescriptor(name, obj)
- #propertyDescriptor_(name, obj, type, description: nil, subtype: nil)
- #puts(str = '')
- #puts_variable_info(label, obj, pat)
- #replay_suspend
-
#search_const(b, expr)
See additional method definition at file lib/debug/server_cdp.rb line 1195.
- #set_mode(mode)
-
#show_by_editor(path = nil)
cmd: show edit.
- #show_consts(pat, expr = nil, only_self: false)
- #show_frame(i = 0)
-
#show_frames(max = nil, pattern = nil)
cmd: show frames.
- #show_globals(pat)
- #show_ivars(pat, expr = nil)
- #show_locals(pat)
-
#show_outline(expr)
cmd: show outline.
- #show_src(frame_index: @current_frame_index, update_line: false, ignore_show_line: false, max_lines: , **options)
-
#special_local_variables(frame)
cmd: show.
- #step_tp(iter, events = [:line, :b_return, :return])
- #suspend(event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil)
- #to_s
-
#tp_allow_reentry
See additional method definition at line 386.
- #truncate(string, width:)
- #type_name(obj)
- #value_inspect(obj, short: true)
-
#variable(name, obj)
See additional method definition at file lib/debug/server_cdp.rb line 1274.
- #variable_(name, obj, indexedVariables: 0, namedVariables: 0)
- #wait_next_action
- #wait_next_action_
-
#wait_reply(event_arg)
events.
- #fiber_blocking private
GlobalVariablesHelper
- Included
SkipPathHelper
- Included
Color
- Included
#color_pp | See additional method definition at line 50. |
#colored_inspect, | |
#colorize | See additional method definition at line 36. |
#colorize_blue, | |
#colorize_code | See additional method definition at line 79. |
#colorize_cyan, #colorize_dim, #colorize_magenta, | |
#irb_colorize | See additional method definition at line 27. |
#with_inspection_error_guard |
Constructor Details
.new(id, q_evt, q_cmd, thr = Thread.current) ⇒ ThreadClient
# File 'lib/debug/thread_client.rb', line 111
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 @current_frame_index = 0 # every thread should maintain its own CheckBreakpoint fulfillment state @check_bp_fulfillment_map = {} # { check_bp => boolean } set_mode :running thr.instance_variable_set(:@__thread_client_id, id) ::DEBUGGER__.info("Thread \##{@id} is created.") end
Class Method Details
.current
[ GitHub ]# File 'lib/debug/thread_client.rb', line 54
def self.current Thread.current.debug_thread_client ||= SESSION.get_thread_client end
Instance Attribute Details
#check_bp_fulfillment_map (readonly)
[ GitHub ]#id (readonly)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 62
attr_reader :thread, :id, :recorder, :check_bp_fulfillment_map
#management? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/debug/thread_client.rb', line 138
def management? @is_management end
#recorder (readonly)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 62
attr_reader :thread, :id, :recorder, :check_bp_fulfillment_map
#running? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/debug/thread_client.rb', line 166
def running? @mode == :running end
#thread (readonly)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 62
attr_reader :thread, :id, :recorder, :check_bp_fulfillment_map
#waiting? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/debug/thread_client.rb', line 170
def waiting? @mode == :waiting end
Instance Method Details
#<<(req)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 210
def << req debug_cmd(req) @q_cmd << req end
#activate_irb_integration
[ GitHub ]# File 'lib/debug/irb_integration.rb', line 18
def activate_irb_integration IRB.setup(location, argv: []) workspace = IRB::WorkSpace.new(current_frame&.binding || TOPLEVEL_BINDING) irb = IRB::Irb.new(workspace) IRB.conf[:MAIN_CONTEXT] = irb.context IRB::Debug.setup(irb) IRB::Context.prepend(IrbPatch) end
#assemble_arguments(args)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 68
def assemble_arguments(args) args.map do |arg| "#{colorize_cyan(arg[:name])}=#{arg[:value]}" end.join(", ") end
#class_method_map(classes)
[ GitHub ]#close
[ GitHub ]# File 'lib/debug/thread_client.rb', line 178
def close @q_cmd.close end
#collect_locals(frame)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 548
def collect_locals(frame) locals = [] if s = frame&.self locals << ["%self", s] end special_local_variables frame do |name, val| locals << [name, val] end if vars = frame&.local_variables vars.each{|var, val| locals << [var, val] } end locals end
#constant_name?(name) ⇒ Boolean
TODO: support non-ASCII Constant name
#current_frame
[ GitHub ]# File 'lib/debug/thread_client.rb', line 536
def current_frame get_frame(@current_frame_index) end
#dap_eval(b, expr, _context, prompt: '(repl_eval)')
[ GitHub ]# File 'lib/debug/server_dap.rb', line 789
def dap_eval b, expr, _context, prompt: '(repl_eval)' begin tp_allow_reentry do b.eval(expr.to_s, prompt) end rescue Exception => e e end end
#deactivate
[ GitHub ]# File 'lib/debug/thread_client.rb', line 134
def deactivate @step_tp.disable if @step_tp end
#debug_cmd(cmds)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 1271
def debug_cmd(cmds) DEBUGGER__.debug{ cmd, *args = *cmds args = args.map { |arg| DEBUGGER__.safe_inspect(arg) } "#{inspect} receives Cmd { type: #{cmd.inspect}, args: #{args} } from Session" } end
#debug_event(ev, args)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 1258
def debug_event(ev, args) DEBUGGER__.debug{ args = args.map { |arg| DEBUGGER__.safe_inspect(arg) } "#{inspect} sends Event { type: #{ev.inspect}, args: #{args} } to Session" } end
#debug_mode(old_mode, new_mode)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 1265
def debug_mode(old_mode, new_mode) DEBUGGER__.debug{ "#{inspect} changes mode (#{old_mode} -> #{new_mode})" } end
#debug_suspend(event)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 1279
def debug_suspend(event) DEBUGGER__.debug{ "#{inspect} is suspended for #{event.inspect}" } end
#default_frame_formatter(frame)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 74
def default_frame_formatter frame call_identifier_str = case frame.frame_type when :block level, block_loc = frame.block_identifier args = frame.parameters_info if !args.empty? args_str = " {|#{assemble_arguments(args)}|}" end "#{colorize_blue("block")}#{args_str} in #{colorize_blue(block_loc + level)}" when :method ci = frame.method_identifier args = frame.parameters_info 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
#evaluate_result(r)
See additional method definition at file lib/debug/server_cdp.rb line 1216.
# File 'lib/debug/server_dap.rb', line 1040
def evaluate_result r v = variable nil, r v[:value] end
#event!(ev, *args)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 221
def event! ev, *args debug_event(ev, args) @q_evt << [self, @output, ev, generate_info, *args] @output = [] end
#exceptionDetails(exc, text)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 1164
def exceptionDetails exc, text frames = [ { columnNumber: 0, functionName: 'eval', lineNumber: 0, url: '' } ] exc.backtrace_locations&.each do |loc| break if loc.path == __FILE__ path = loc.absolute_path || loc.path frames << { columnNumber: 0, functionName: loc.base_label, lineNumber: loc.lineno - 1, url: path } end { exceptionId: 1, text: text, lineNumber: 0, columnNumber: 0, exception: evaluate_result(exc), stackTrace: { callFrames: frames } } end
#fiber_blocking (private)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 869
private def fiber_blocking yield end
#frame_eval(src, re_raise: false, binding_location: false)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 435
def frame_eval src, re_raise: false, binding_location: false @success_last_eval = false b = current_frame&.eval_binding || TOPLEVEL_BINDING special_local_variables current_frame do |name, var| b.local_variable_set(name, var) if /\%/ !~ name end result = frame_eval_core(src, b, binding_location: binding_location) @success_last_eval = true result rescue SystemExit raise 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
#frame_eval_core(src, b, binding_location: false)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 404
def frame_eval_core src, b, binding_location: false saved_target_frames = @target_frames saved_current_frame_index = @current_frame_index if b file, lineno = b.source_location tp_allow_reentry do if binding_location b.eval(src, file, lineno) else b.eval(src, "(rdbg)/#{file}") end end else frame_self = current_frame.self tp_allow_reentry do frame_self.instance_eval(src) end end ensure @target_frames = saved_target_frames @current_frame_index = saved_current_frame_index end
#frame_str(i, frame: )
[ GitHub ]# File 'lib/debug/thread_client.rb', line 752
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
#generate_info
[ GitHub ]# File 'lib/debug/thread_client.rb', line 215
def generate_info return unless current_frame { location: current_frame.location_str, line: current_frame.location.lineno } end
#get_consts(expr = nil, only_self: false, &block)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 612
def get_consts expr = nil, only_self: false, &block if expr && !expr.empty? begin _self = frame_eval(expr, re_raise: true) rescue Exception # ignore else if M_KIND_OF_P.bind_call(_self, Module) iter_consts _self, &block return else puts "#{_self.inspect} (by #{expr}) is not a Module." end end elsif _self = current_frame&.self cs = {} if M_KIND_OF_P.bind_call(_self, Module) cs[_self] = :self else _self = M_CLASS.bind_call(_self) cs[_self] = :self unless only_self end unless only_self _self.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, _| iter_consts c, names, &block } end end
#get_frame(index)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 540
def get_frame(index) if @target_frames @target_frames[index] else nil end end
#get_src(frame, max_lines:, start_line: nil, end_line: nil, dir: +1)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 463
def get_src(frame, max_lines:, start_line: nil, end_line: nil, dir: +1) if file_lines = frame.file_lines frame_line = frame.location.lineno - 1 if CONFIG[:no_lineno] lines = file_lines else lines = file_lines.map.with_index do |e, i| cur = i == frame_line ? '=>' : ' ' line = colorize_dim('%4d|' % (i+1)) "#{cur}#{line} #{e}" end 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 start_line != end_line && max_lines [start_line, end_line, lines] end else # no file lines nil end rescue Exception => e p e pp e.backtrace exit! end
#inspect
[ GitHub ]#internalProperty(name, obj)
[ GitHub ]#iter_consts(c, names = {})
[ GitHub ]#location
[ GitHub ]# File 'lib/debug/thread_client.rb', line 64
def location current_frame&.location end
#make_breakpoint(args)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 825
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 || TOPLEVEL_BINDING, klass_name, op, method_name, cond: cond, command: cmd, path: path) begin bp.enable rescue NameError => e if bp.klass puts "Unknown method name: \"#{e.name}\"" else # klass_name can not be evaluated if constant_name? klass_name puts "Unknown constant name: \"#{e.name}\"" else # only Class name is allowed puts "Not a constant name: \"#{klass_name}\"" bp = nil end end Session.activate_method_added_trackers if bp rescue Exception => e puts e.inspect bp = nil 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
#mark_as_management
[ GitHub ]# File 'lib/debug/thread_client.rb', line 142
def mark_as_management @is_management = true end
#name
[ GitHub ]# File 'lib/debug/thread_client.rb', line 174
def name "##{@id} #{@thread.name || @thread.backtrace.last}" end
#on_breakpoint(tp, bp)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 250
def on_breakpoint tp, bp suspend tp.event, tp, bp: bp end
#on_init(name)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 242
def on_init name wait_reply [:init, name] end
#on_load(iseq, eval_src)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 238
def on_load iseq, eval_src wait_reply [:load, iseq, eval_src] end
#on_pause
[ GitHub ]# File 'lib/debug/thread_client.rb', line 262
def on_pause suspend :pause end
#on_trace(trace_id, msg)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 246
def on_trace trace_id, msg wait_reply [:trace, trace_id, msg] end
#on_trap(sig)
[ GitHub ]#outline_method(o, klass, obj)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 782
def outline_method(o, klass, obj) begin singleton_class = M_SINGLETON_CLASS.bind_call(obj) rescue TypeError singleton_class = 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
#preview(name, obj)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 1296
def preview name, obj case obj when Array pd = propertyDescriptor name, obj overflow = false if obj.size > 100 obj = obj[0..99] overflow = true end hash = obj.each_with_index.to_h{|o, i| [i.to_s, o]} preview_ pd[:value], hash, overflow when Hash pd = propertyDescriptor name, obj overflow = false if obj.size > 100 obj = obj.to_a[0..99].to_h overflow = true end preview_ pd[:value], obj, overflow else nil end end
#preview_(value, hash, overflow)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 1253
def preview_ value, hash, overflow # The reason for not using "map" method is to prevent the object overriding it from causing bugs. # https://github.com/ruby/debug/issues/781 props = [] hash.each{|k, v| pd = propertyDescriptor k, v props << { name: pd[:name], type: pd[:value][:type], value: pd[:value][:description] } } { type: value[:type], subtype: value[:subtype], description: value[:description], overflow: overflow, properties: props } end
#process_cdp(args)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 961
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 frame.iseq.nil? lineno = 0 else lineno = frame.iseq.first_line - 1 end { callFrameId: SecureRandom.hex(16), functionName: frame.name, functionLocation: { # scriptId: N, # filled by SESSION lineNumber: lineno }, location: { # scriptId: N, # filled by SESSION lineNumber: frame.location.lineno - 1 # The line number is 0-based. }, url: 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! :protocol_result, :backtrace, req, result when :evaluate res = {} fid, expr, group = args frame = @target_frames[fid] = nil if frame && (b = frame.eval_binding) special_local_variables frame do |name, var| b.local_variable_set(name, var) if /\%/ !~name end result = nil case group when 'popover' case expr # Chrome doesn't read instance variables when /\A\$\S/ safe_global_variables.each{|gvar| if gvar.to_s == expr result = eval(gvar.to_s) break false end } and ( = "Error: Not defined global variable: #{expr.inspect}") when /(\A((::[A-Z]|[A-Z])\w*)+)/ unless result = search_const(b, $1) = "Error: Not defined constant: #{expr.inspect}" end else begin result = b.local_variable_get(expr) rescue NameError # try to check method if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true) result = M_METHOD.bind_call(b.receiver, expr) else = "Error: Can not evaluate: #{expr.inspect}" end end end when 'console', 'watch-group' begin orig_stdout = $stdout $stdout = StringIO.new result = b.eval(expr.to_s, '(DEBUG CONSOLE)') rescue Exception => e result = e res[:exceptionDetails] = exceptionDetails(e, 'Uncaught') ensure output = $stdout.string $stdout = orig_stdout end else = "Error: unknown objectGroup: #{group}" end else result = Exception.new("Error: Can not evaluate on this frame") end res[:result] = evaluate_result(result) event! :protocol_result, :evaluate, req, 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! :protocol_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 = [ internalProperty('#length', obj.length), internalProperty('#encoding', obj.encoding) ] when Class, Module result = obj.instance_variables.map{|iv| variable(iv, obj.instance_variable_get(iv)) } prop = [internalProperty('%ancestors', obj.ancestors[1..])] when Range prop = [ internalProperty('#begin', obj.begin), internalProperty('#end', obj.end), ] end result += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv| variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv)) } prop += [internalProperty('#class', M_CLASS.bind_call(obj))] end event! :protocol_result, :properties, req, result: result, internalProperties: prop when :exception oid = args.shift exc = nil if obj = @obj_map[oid] exc = exceptionDetails obj, obj.to_s end event! :protocol_result, :exception, req, exceptionDetails: exc end end
#process_dap(args)
[ GitHub ]# File 'lib/debug/server_dap.rb', line 799
def process_dap args # pp tc: self, args: args type = args.shift req = args.shift case type when :backtrace start_frame = req.dig('arguments', 'startFrame') || 0 levels = req.dig('arguments', 'levels') || 1_000 frames = [] @target_frames.each_with_index do |frame, i| next if i < start_frame path = frame.realpath || frame.path next if skip_path?(path) && !SESSION.stop_stepping?(path, frame.location.lineno) break if (levels -= 1) < 0 source_name = path ? File.basename(path) : frame.location.to_s if (path && File.exist?(path)) && (local_path = UI_DAP.remote_to_local_path(path)) # ok else ref = frame.file_lines end frames << { id: i, # id is refilled by SESSION name: frame.name, line: frame.location.lineno, column: 1, source: { name: source_name, path: (local_path || path), sourceReference: ref, }, } end event! :protocol_result, :backtrace, req, { stackFrames: frames, totalFrames: @target_frames.size, } when :scopes fid = args.shift frame = get_frame(fid) lnum = if frame.binding frame.binding.local_variables.size elsif vars = frame.local_variables vars.size else 0 end event! :protocol_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: safe_global_variables.size, indexedVariables: 0, expensive: false, }] when :scope fid = args.shift frame = get_frame(fid) vars = collect_locals(frame).map do |var, val| variable(var, val) end event! :protocol_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(value_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), ] printed_str = value_inspect(obj) vars << variable('#dump', NaiveString.new(obj)) if printed_str.end_with?('...') when Class, Module vars << variable('%ancestors', obj.ancestors[1..]) when Range vars = [ variable('#begin', obj.begin), variable('#end', obj.end), ] end unless NaiveString === obj vars += M_INSTANCE_VARIABLES.bind_call(obj).sort.map{|iv| variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv)) } vars.unshift variable('#class', M_CLASS.bind_call(obj)) end end end event! :protocol_result, :variable, req, variables: (vars || []), tid: self.id when :evaluate fid, expr, context = args frame = get_frame(fid) = nil if frame && (b = frame.eval_binding) special_local_variables frame do |name, var| b.local_variable_set(name, var) if /\%/ !~ name end case context when 'repl', 'watch' result = dap_eval b, expr, context, prompt: '(DEBUG CONSOLE)' when 'hover' case expr when /\A\@\S/ begin result = M_INSTANCE_VARIABLE_GET.bind_call(b.receiver, expr) rescue NameError = "Error: Not defined instance variable: #{expr.inspect}" end when /\A\$\S/ safe_global_variables.each{|gvar| if gvar.to_s == expr result = eval(gvar.to_s) break false end } and ( = "Error: Not defined global variable: #{expr.inspect}") when /\Aself$/ result = b.receiver when /(\A((::[A-Z]|[A-Z])\w*)+)/ unless result = search_const(b, $1) = "Error: Not defined constants: #{expr.inspect}" end else begin result = b.local_variable_get(expr) rescue NameError # try to check method if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true) result = M_METHOD.bind_call(b.receiver, expr) else = "Error: Can not evaluate: #{expr.inspect}" end end end else = "Error: unknown context: #{context}" end else result = 'Error: Can not evaluate on this frame' end event! :protocol_result, :evaluate, req, message: , tid: self.id, **evaluate_result(result) when :completions fid, text = args frame = get_frame(fid) if (b = frame&.binding) && word = text&.split(/[\s\{]/)&.last words = IRB::InputCompletor::retrieve_completion_data(word, bind: b).compact end event! :protocol_result, :completions, req, targets: (words || []).map{|phrase| detail = nil if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase w = $1 else w = phrase end begin v = b.local_variable_get(w) detail ="(variable: #{value_inspect(v)})" rescue NameError end { label: phrase, text: w, detail: detail, } } else if respond_to? mid = "custom_dap_request_#{type}" __send__ mid, req else raise "Unknown request: #{args.inspect}" end end end
#propertyDescriptor(name, obj)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 1320
def propertyDescriptor name, obj case obj when Array propertyDescriptor_ name, obj, 'object', subtype: 'array' when Hash propertyDescriptor_ name, obj, 'object', subtype: 'map' when String propertyDescriptor_ name, obj, 'string', description: obj when TrueClass, FalseClass propertyDescriptor_ name, obj, 'boolean' when Symbol propertyDescriptor_ name, obj, 'symbol' when Integer, Float propertyDescriptor_ name, obj, 'number' when Exception bt = '' if log = obj.backtrace_locations log.each do |loc| break if loc.path == __FILE__ bt += " #{loc}\n" end end propertyDescriptor_ name, obj, 'object', description: "#{obj.inspect}\n#{bt}", subtype: 'error' else propertyDescriptor_ name, obj, 'object' end end
#propertyDescriptor_(name, obj, type, description: nil, subtype: nil)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 1228
def propertyDescriptor_ name, obj, type, description: nil, subtype: nil description = DEBUGGER__.safe_inspect(obj, short: true) if description.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] = (klass = M_CLASS.bind_call(obj)).name || klass.to_s end prop end
#puts(str = '')
[ GitHub ]# File 'lib/debug/thread_client.rb', line 196
def puts str = '' if @recorder&. 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
#puts_variable_info(label, obj, pat)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 665
def puts_variable_info label, obj, pat return if pat && pat !~ label begin inspected = DEBUGGER__.safe_inspect(obj) rescue Exception => e inspected = e.inspect end mono_info = "#{label} = #{inspected}" w = SESSION::width if mono_info.length >= w maximum_value_width = w - "#{label} = ".length valstr = truncate(inspected, width: maximum_value_width) else valstr = colored_inspect(obj, width: 2 ** 30) valstr = inspected if valstr.lines.size > 1 end info = "#{colorize_cyan(label)} = #{valstr}" puts info end
#replay_suspend
[ GitHub ]# File 'lib/debug/thread_client.rb', line 323
def replay_suspend # @recorder.current_position suspend :replay, replay_frames: @recorder.current_frame end
#search_const(b, expr)
See additional method definition at file lib/debug/server_cdp.rb line 1195.
# File 'lib/debug/server_dap.rb', line 1019
def search_const b, expr cs = expr.delete_prefix('::').split('::') [Object, *b.eval('::Module.nesting')].reverse_each{|mod| if cs.all?{|c| if mod.const_defined?(c) begin mod = mod.const_get(c) rescue Exception false end else false end } # if-body return mod end } false end
#set_mode(mode)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 146
def set_mode mode debug_mode(@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
#show_by_editor(path = nil)
cmd: show edit
# File 'lib/debug/thread_client.rb', line 700
def show_by_editor path = nil unless path if current_frame path = current_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}" require 'shellwords' system(*Shellwords.split(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
#show_consts(pat, expr = nil, only_self: false)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 650
def show_consts pat, expr = nil, only_self: false get_consts expr, only_self: only_self do |name, value| puts_variable_info name, value, pat end end
#show_frame(i = 0)
[ GitHub ]#show_frames(max = nil, pattern = nil)
cmd: show frames
# File 'lib/debug/thread_client.rb', line 725
def show_frames max = nil, pattern = nil if @target_frames && (max ||= @target_frames.size) > 0 frames = [] @target_frames.each_with_index{|f, i| # we need to use FrameInfo#matchable_location because #location_str is for display # and it may change based on configs (e.g. use_short_path) next if pattern && !(f.name.match?(pattern) || f.matchable_location.match?(pattern)) # avoid using skip_path? because we still want to display internal frames next if skip_config_skip_path?(f.matchable_location) 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
#show_globals(pat)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 656
def show_globals pat safe_global_variables.sort.each{|name| next if SKIP_GLOBAL_LIST.include? name value = eval(name.to_s) puts_variable_info name, value, pat } end
#show_ivars(pat, expr = nil)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 583
def show_ivars pat, expr = nil if expr && !expr.empty? _self = frame_eval(expr); elsif _self = current_frame&.self else _self = nil end if _self M_INSTANCE_VARIABLES.bind_call(_self).sort.each{|iv| value = M_INSTANCE_VARIABLE_GET.bind_call(_self, iv) puts_variable_info iv, value, pat } end end
#show_locals(pat)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 577
def show_locals pat collect_locals(current_frame).each do |var, val| puts_variable_info(var, val, pat) end end
#show_outline(expr)
cmd: show outline
# File 'lib/debug/thread_client.rb', line 761
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 = M_CLASS.bind_call(obj) klass = obj if Class == klass || Module == klass o.dump("constants", obj.constants) if M_RESPOND_TO_P.bind_call(obj, :constants) outline_method(o, klass, obj) o.dump("instance variables", M_INSTANCE_VARIABLES.bind_call(obj)) o.dump("class variables", klass.class_variables) o.dump("locals", locals.keys) if locals end end
#show_src(frame_index: @current_frame_index, update_line: false, ignore_show_line: false, max_lines: , **options)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 510
def show_src(frame_index: @current_frame_index, update_line: false, ignore_show_line: false, max_lines: CONFIG[:show_src_lines], ** ) if frame = get_frame(frame_index) begin if ignore_show_line prev_show_line = frame.show_line frame.show_line = nil end start_line, end_line, lines = *get_src(frame, max_lines: max_lines, ** ) if start_line if update_line frame.show_line = end_line end puts "[#{start_line+1}, #{end_line}] in #{frame.pretty_path}" if !update_line && max_lines != 1 puts lines[start_line...end_line] else puts "# No sourcefile available for #{frame.path}" end ensure frame.show_line = prev_show_line if prev_show_line end end end
#special_local_variables(frame)
cmd: show
#step_tp(iter, events = [:line, :b_return, :return])
[ GitHub ]# File 'lib/debug/thread_client.rb', line 337
def step_tp iter, events = [:line, :b_return, :return] @step_tp.disable if @step_tp thread = Thread.current subsession_id = SESSION.subsession_id if SUPPORT_TARGET_THREAD @step_tp = TracePoint.new(*events){|tp| if SESSION.stop_stepping? tp.path, tp.lineno, subsession_id tp.disable next end next if !yield(tp) 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 if SESSION.stop_stepping? tp.path, tp.lineno, subsession_id tp.disable next end next if !yield(tp) 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
#suspend(event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 266
def suspend event, tp = nil, bp: nil, sig: nil, postmortem_frames: nil, replay_frames: nil, postmortem_exc: nil return if management? debug_suspend(event) @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 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 unless bp&.skip_src show_src show_frames CONFIG[:show_frames] end 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
#to_s
[ GitHub ]# File 'lib/debug/thread_client.rb', line 190
def to_s str = "(#{@thread.name || @thread.status})@#{current_frame&.location || @thread.to_s}" str += " (not under control)" unless self.waiting? str end
#tp_allow_reentry
See additional method definition at line 386.
# File 'lib/debug/thread_client.rb', line 399
def tp_allow_reentry TracePoint.allow_reentry do yield end rescue RuntimeError => e # on the postmortem mode, it is not stopped in TracePoint if e. == 'No need to allow reentrance.' yield else raise end end
#truncate(string, width:)
[ GitHub ]# File 'lib/debug/thread_client.rb', line 690
def truncate(string, width:) if string.start_with?("#<") string[0 .. (width-5)] + '...>' else string[0 .. (width-4)] + '...' end end
#type_name(obj)
[ GitHub ]#value_inspect(obj, short: true)
[ GitHub ]# File 'lib/debug/server_dap.rb', line 778
def value_inspect obj, short: true # TODO: max length should be configuarable? str = DEBUGGER__.safe_inspect obj, short: short, max_length: MAX_LENGTH if str.encoding == Encoding::UTF_8 str.scrub else str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) end end
#variable(name, obj)
See additional method definition at file lib/debug/server_cdp.rb line 1274.
# File 'lib/debug/server_dap.rb', line 1089
def variable name, obj pd = propertyDescriptor name, obj case obj when Array pd[:value][:preview] = preview name, obj obj.each_with_index{|item, idx| if valuePreview = preview(idx.to_s, item) pd[:value][:preview][:properties][idx][:valuePreview] = valuePreview end } when Hash pd[:value][:preview] = preview name, obj obj.each_with_index{|item, idx| key, val = item if valuePreview = preview(key, val) pd[:value][:preview][:properties][idx][:valuePreview] = valuePreview end } end pd end
#variable_(name, obj, indexedVariables: 0, namedVariables: 0)
[ GitHub ]# File 'lib/debug/server_dap.rb', line 1054
def variable_ name, obj, indexedVariables: 0, namedVariables: 0 if indexedVariables > 0 || namedVariables > 0 vid = @var_map.size + 1 @var_map[vid] = obj else vid = 0 end namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size if NaiveString === obj str = obj.str.dump vid = indexedVariables = namedVariables = 0 else str = value_inspect(obj) end if name { name: name, value: str, type: type_name(obj), variablesReference: vid, indexedVariables: indexedVariables, namedVariables: namedVariables, } else { result: str, type: type_name(obj), variablesReference: vid, indexedVariables: indexedVariables, namedVariables: namedVariables, } end end
#wait_next_action
[ GitHub ]# File 'lib/debug/thread_client.rb', line 874
def wait_next_action fiber_blocking{wait_next_action_} rescue SuspendReplay replay_suspend end
#wait_next_action_
[ GitHub ]# File 'lib/debug/thread_client.rb', line 880
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&. @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.base_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.base_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&. @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 :display, :try_display failed_results = [] eval_src.each_with_index{|src, i| result = frame_eval(src){|e| failed_results << [i, e. ] "<error: #{e.}>" } 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. 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
#wait_reply(event_arg)
events
# File 'lib/debug/thread_client.rb', line 229
def wait_reply event_arg return if management? set_mode :waiting event!(*event_arg) wait_next_action end