class DEBUGGER__::ThreadClient - debug: Ruby Standard Library Documentation

# class DEBUGGER__::ThreadClient

### Constants

MAX_LENGTH
SPECIAL_LOCAL_VARS
SUPPORT_TARGET_THREAD

### Attributes

check_bp_fulfillment_map[R]
id[R]
recorder[R]
thread[R]

### Public Class Methods

current() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 54
    def self.current
      Thread.current.debug_thread_client ||= SESSION.get_thread_client
    end

new(id, q_evt, q_cmd, thr = Thread.current) click to toggle source

    # File debug-1.10.0/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

### Public Instance Methods

<<(req) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 210
    def << req
      debug_cmd(req)
      @q_cmd << req
    end

activate_irb_integration() click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 796
    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

close() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 178
    def close
      @q_cmd.close
    end

collect_locals(frame) click to toggle source

    # File debug-1.10.0/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) click to toggle source

TODO: support non-ASCII Constant name

    # File debug-1.10.0/lib/debug/thread_client.rb, line 809
    def constant_name? name
      case name
      when /\A::\b/
        constant_name? $~.post_match
      when /\A[A-Z]\w*/
        post = $~.post_match
        if post.empty?
          true
        else
          constant_name? post
        end
      else
        false
      end
    end

current_frame() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 536
    def current_frame
      get_frame(@current_frame_index)
    end

dap_eval(b, expr, _context, prompt: '(repl_eval)') click to toggle source

    # File debug-1.10.0/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() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 134
    def deactivate
      @step_tp.disable if @step_tp
    end

debug_cmd(cmds) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 1279
    def debug_suspend(event)
      DEBUGGER__.debug{
        "#{inspect} is suspended for #{event.inspect}"
      }
    end

default_frame_formatter(frame) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/lib/debug/server_cdp.rb, line 1216
    def evaluate_result r
      v = variable nil, r
      v[:value]
    end

event!(ev, *args) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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() { || ... } click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 865
            def fiber_blocking
      ::Fiber.blocking{yield}
    end

frame_eval(src, re_raise: false, binding_location: false) { |e| ... } click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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: @target_frames[i]) click to toggle source

    # File debug-1.10.0/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() click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 182
    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

internalProperty(name, obj) click to toggle source

    # File debug-1.10.0/lib/debug/server_cdp.rb, line 1221
    def internalProperty name, obj
      v = variable name, obj
      v.delete :configurable
      v.delete :enumerable
      v
    end

iter_consts(c, names = {}) { |name, value| ... } click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 599
    def iter_consts c, names = {}
      c.constants(false).sort.each{|name|
        next if names.has_key? name
        names[name] = nil
        begin
          value = c.const_get(name)
        rescue Exception => e
          value = e
        end
        yield name, value
      }
    end

location() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 64
    def location
      current_frame&.location
    end

make_breakpoint(args) click to toggle source

    # File debug-1.10.0/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

management?() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 138
    def management?
      @is_management
    end

mark_as_management() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 142
    def mark_as_management
      @is_management = true
    end

name() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 174
    def name
      "##{@id} #{@thread.name || @thread.backtrace.last}"
    end

on_breakpoint(tp, bp) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 250
    def on_breakpoint tp, bp
      suspend tp.event, tp, bp: bp
    end

on_init(name) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 242
    def on_init name
      wait_reply [:init, name]
    end

on_load(iseq, eval_src) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 238
    def on_load iseq, eval_src
      wait_reply [:load, iseq, eval_src]
    end

on_pause() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 262
    def on_pause
      suspend :pause
    end

on_trace(trace_id, msg) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 246
    def on_trace trace_id, msg
      wait_reply [:trace, trace_id, msg]
    end

on_trap(sig) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 254
    def on_trap sig
      if waiting?
        # raise Interrupt
      else
        suspend :trap, sig: sig
      end
    end

outline_method(o, klass, obj) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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]
        message = 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 (message = "Error: Not defined global variable: #{expr.inspect}")
            when /(\A((::[A-Z]|[A-Z])\w*)+)/
              unless result = search_const(b, $1)
                message = "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
                  message = "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
            message = "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: 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) click to toggle source

    # File debug-1.10.0/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)
        message = 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
                message = "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 (message = "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)
                message = "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
                  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! :protocol_result, :evaluate, req, message: 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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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 = '') click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 196
    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

puts_variable_info(label, obj, pat) click to toggle source

    # File debug-1.10.0/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() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 323
    def replay_suspend
      # @recorder.current_position
      suspend :replay, replay_frames: @recorder.current_frame
    end

running?() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 166
    def running?
      @mode == :running
    end

search_const(b, expr) click to toggle source

    # File debug-1.10.0/lib/debug/server_cdp.rb, line 1195
    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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

cmd: show edit

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 748
    def show_frame i=0
      puts frame_str(i)
    end

show_frames(max = nil, pattern = nil) click to toggle source

cmd: show frames

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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) click to toggle source

cmd: show outline

    # File debug-1.10.0/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: CONFIG[:show_src_lines], **options) click to toggle source

    # File debug-1.10.0/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], **options)
      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, **options)

          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) { |name, send| ... } click to toggle source

cmd: show

    # File debug-1.10.0/lib/debug/thread_client.rb, line 569
    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

step_tp(iter, events = [:line, :b_return, :return]) { |tp| ... } click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/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() click to toggle source

    # File debug-1.10.0/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() { || ... } click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 386
    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.message == 'No need to allow reentrance.'
        yield
      else
        raise
      end
    end

truncate(string, width:) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/lib/debug/server_dap.rb, line 1044
    def type_name obj
      klass = M_CLASS.bind_call(obj)

      begin
        M_NAME.bind_call(klass) || klass.to_s
      rescue Exception => e
        "<Error: #{e.message} (#{e.backtrace.first}>"
      end
    end

value_inspect(obj, short: true) click to toggle source

    # File debug-1.10.0/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) click to toggle source

    # File debug-1.10.0/lib/debug/server_cdp.rb, line 1274
    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) click to toggle source

    # File debug-1.10.0/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() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 874
    def wait_next_action
      fiber_blocking{wait_next_action_}
    rescue SuspendReplay
      replay_suspend
    end

wait_next_action_() click to toggle source

    # File debug-1.10.0/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&.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.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&.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 :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

wait_reply(event_arg) click to toggle source

events

    # File debug-1.10.0/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

waiting?() click to toggle source

    # File debug-1.10.0/lib/debug/thread_client.rb, line 170
    def waiting?
      @mode == :waiting
    end