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

# class DEBUGGER__::Session

### Constants

BREAK_KEYWORDS
INTERNAL_ERROR
INVALID_PARAMS
METHOD_ADDED_TRACKERS

### Attributes

intercepted_sigint_cmd[R]
process_group[R]
subsession_id[R]

### Public Class Methods

activate_method_added_trackers() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1847
    def self.activate_method_added_trackers
      METHOD_ADDED_TRACKERS.each do |m, tp|
        tp.enable(target: m) unless tp.enabled?
      rescue ArgumentError
        DEBUGGER__.warn "Methods defined under #{m.owner} can not track by the debugger."
      end
    end

create_method_added_tracker(mod, method_added_id, method_accessor = :method) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1840
    def self.create_method_added_tracker mod, method_added_id, method_accessor = :method
      m = mod.__send__(method_accessor, method_added_id)
      METHOD_ADDED_TRACKERS[m] = TracePoint.new(:call) do |tp|
        SESSION.method_added tp
      end
    end

deactivate_method_added_trackers() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1855
    def self.deactivate_method_added_trackers
      METHOD_ADDED_TRACKERS.each do |m, tp|
        tp.disable if tp.enabled?
      end
    end

new() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 98
    def initialize
      @ui = nil
      @sr = SourceRepository.new
      @bps = {} # bp.key => bp
                #   [file, line] => LineBreakpoint
                #   "Error" => CatchBreakpoint
                #   "Foo#bar" => MethodBreakpoint
                #   [:watch, ivar] => WatchIVarBreakpoint
                #   [:check, expr] => CheckBreakpoint
      #
      @tracers = {}
      @th_clients = {} # {Thread => ThreadClient}
      @q_evt = Queue.new
      @displays = []
      @tc = nil
      @tc_id = 0
      @preset_command = nil
      @postmortem_hook = nil
      @postmortem = false
      @intercept_trap_sigint = false
      @intercepted_sigint_cmd = 'DEFAULT'
      @process_group = ProcessGroup.new
      @subsession_stack = []
      @subsession_id = 0

      @frame_map = {} # for DAP: {id => [threadId, frame_depth]} and CDP: {id => frame_depth}
      @var_map   = {1 => [:globals], } # {id => ...} for DAP
      @src_map   = {} # {id => src}

      @scr_id_map = {} # for CDP
      @obj_map = {} # { object_id => ... } for CDP

      @tp_thread_begin = nil
      @tp_thread_end = nil

      @commands = {}
      @unsafe_context = false

      @has_keep_script_lines = defined?(RubyVM.keep_script_lines)

      @tp_load_script = TracePoint.new(:script_compiled){|tp|
        eval_script = tp.eval_script unless @has_keep_script_lines
        ThreadClient.current.on_load tp.instruction_sequence, eval_script
      }
      @tp_load_script.enable

      @thread_stopper = thread_stopper
      self.postmortem = CONFIG[:postmortem]

      register_default_command
    end

### Public Instance Methods

activate(ui = nil, on_fork: false) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 168
    def activate ui = nil, on_fork: false
      @ui = ui if ui

      @tp_thread_begin&.disable
      @tp_thread_end&.disable
      @tp_thread_begin = nil
      @tp_thread_end = nil
      @ui.activate self, on_fork: on_fork

      q = Queue.new
      first_q = Queue.new
      @session_server = Thread.new do
        # make sure `@session_server` is assigned
        first_q.pop; first_q = nil

        Thread.current.name = 'DEBUGGER__::SESSION@server'
        Thread.current.abort_on_exception = true

        # Thread management
        setup_threads
        thc = get_thread_client Thread.current
        thc.mark_as_management

        if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
          thc.mark_as_management
        end

        @tp_thread_begin = TracePoint.new(:thread_begin) do |tp|
          get_thread_client
        end
        @tp_thread_begin.enable

        @tp_thread_end = TracePoint.new(:thread_end) do |tp|
          @th_clients.delete(Thread.current)
        end
        @tp_thread_end.enable

        # session start
        q << true
        session_server_main
      end
      first_q << :ok

      q.pop

      # For activating irb:rdbg with startup config like `RUBY_DEBUG_IRB_CONSOLE=1`
      # Because in that case the `Config#if_updated` callback would not be triggered
      if CONFIG[:irb_console] && !CONFIG[:open]
        activate_irb_integration
      end
    end

activate_irb_integration() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1881
    def activate_irb_integration
      require_relative "irb_integration"
      thc = get_thread_client(@session_server)
      thc.activate_irb_integration
    end

active?() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 150
    def active?
      !@q_evt.closed?
    end

add_bp(bp) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1398
    def add_bp bp
      # don't repeat commands that add breakpoints
      if @bps.has_key? bp.key
        if bp.duplicable?
          bp
        else
          @ui.puts "duplicated breakpoint: #{bp}"
          bp.disable
          nil
        end
      else
        @bps[bp.key] = bp
      end
    end

add_catch_breakpoint(pat, cond: nil) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1498
    def add_catch_breakpoint pat, cond: nil
      bp = CatchBreakpoint.new(pat, cond: cond)
      add_bp bp
    end

add_check_breakpoint(cond, path, command) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1503
    def add_check_breakpoint cond, path, command
      bp = CheckBreakpoint.new(cond: cond, path: path, command: command)
      add_bp bp
    end

add_iseq_breakpoint(iseq, **kw) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1545
    def add_iseq_breakpoint iseq, **kw
      bp = ISeqBreakpoint.new(iseq, [:line], **kw)
      add_bp bp
    end

add_line_breakpoint(file, line, **kw) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1508
    def add_line_breakpoint file, line, **kw
      file = resolve_path(file)
      bp = LineBreakpoint.new(file, line, **kw)

      add_bp bp
    rescue Errno::ENOENT => e
      @ui.puts e.message
    end

add_preset_commands(name, cmds, kick: true, continue: true) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 374
    def add_preset_commands name, cmds, kick: true, continue: true
      cs = cmds.map{|c|
        c.each_line.map{|line|
          line = line.strip.gsub(/\A\s*\#.*/, '').strip
          line unless line.empty?
        }.compact
      }.flatten.compact

      if @preset_command && !@preset_command.commands.empty?
        @preset_command.commands += cs
      else
        @preset_command = PresetCommands.new(cs, name, continue)
      end

      ThreadClient.current.on_init name if kick
    end

add_tracer(tracer) click to toggle source

tracers

    # File debug-1.10.0/lib/debug/session.rb, line 1552
    def add_tracer tracer
      if @tracers[tracer.key]&.enabled?
        tracer.disable
        @ui.puts "Duplicated tracer: #{tracer}"
      else
        @tracers[tracer.key] = tracer
        @ui.puts "Enable #{tracer}"
      end
    end

after_fork_parent() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 2017
    def after_fork_parent
      @ui.after_fork_parent
    end

ask(msg, default = 'Y') click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1340
    def ask msg, default = 'Y'
      opts = '[y/n]'.tr(default.downcase, default)
      input = @ui.ask("#{msg} #{opts} ")
      input = default if input.empty?
      case input
      when 'y', 'Y'
        true
      else
        false
      end
    end

before_fork(need_lock = true) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 2011
    def before_fork need_lock = true
      if need_lock
        @process_group.multi_process!
      end
    end

bp_index(specific_bp_key) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1375
    def bp_index specific_bp_key
      iterate_bps do |key, bp, i|
        if key == specific_bp_key
          return [bp, i]
        end
      end
      nil
    end

cancel_auto_continue() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1288
    def cancel_auto_continue
      if @preset_command&.auto_continue
        @preset_command.auto_continue = false
      end
    end

capture_exception_frames(*exclude_path) { || ... } click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1897
    def capture_exception_frames *exclude_path
      postmortem_hook = TracePoint.new(:raise){|tp|
        exc = tp.raised_exception
        frames = DEBUGGER__.capture_frames(__dir__)

        exclude_path.each{|ex|
          if Regexp === ex
            frames.delete_if{|e| ex =~ e.path}
          else
            frames.delete_if{|e| e.path.start_with? ex.to_s}
          end
        }
        exc.instance_variable_set(:@__debugger_postmortem_frames, frames)
      }
      postmortem_hook.enable

      begin
        yield
        nil
      rescue Exception => e
        if e.instance_variable_defined? :@__debugger_postmortem_frames
          e
        else
          raise
        end
      ensure
        postmortem_hook.disable
      end
    end

check_postmortem() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1869
    def check_postmortem
      if @postmortem
        raise PostmortemError, "Can not use this command on postmortem mode."
      end
    end

check_unsafe() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1875
    def check_unsafe
      if @unsafe_context
        raise RuntimeError, "#{@repl_prev_line.dump} is not allowed on unsafe context."
      end
    end

clean_bps() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1392
    def clean_bps
      @bps.delete_if{|_k, bp|
        bp.deleted?
      }
    end

clear_all_breakpoints() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1541
    def clear_all_breakpoints
      clear_breakpoints{true}
    end

clear_breakpoints(&condition) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1517
    def clear_breakpoints(&condition)
      @bps.delete_if do |k, bp|
        if condition.call(k, bp)
          bp.delete
          true
        end
      end
    end

clear_catch_breakpoints(*exception_names) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1535
    def clear_catch_breakpoints *exception_names
      clear_breakpoints do |k, bp|
        bp.is_a?(CatchBreakpoint) && exception_names.include?(k[1])
      end
    end

clear_line_breakpoints(path) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1526
    def clear_line_breakpoints path
      path = resolve_path(path)
      clear_breakpoints do |k, bp|
        bp.is_a?(LineBreakpoint) && bp.path_is?(path)
      end
    rescue Errno::ENOENT
      # just ignore
    end

config_command(arg) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1254
    def config_command arg
      case arg
      when nil
        CONFIG_SET.each do |k, _|
          config_show k
        end

      when /\Aunset\s+(.+)\z/
        if CONFIG_SET[key = $1.to_sym]
          CONFIG[key] = nil
        end
        config_show key

      when /\A(\w+)\s*=\s*(.+)\z/
        config_set $1, $2

      when /\A\s*set\s+(\w+)\s+(.+)\z/
        config_set $1, $2

      when /\A(\w+)\s*<<\s*(.+)\z/
        config_set $1, $2, append: true

      when /\A\s*append\s+(\w+)\s+(.+)\z/
        config_set $1, $2, append: true

      when /\A(\w+)\z/
        config_show $1

      else
        @ui.puts "Can not parse parameters: #{arg}"
      end
    end

config_set(key, val, append: false) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1238
    def config_set key, val, append: false
      if CONFIG_SET[key = key.to_sym]
        begin
          if append
            CONFIG.append_config(key, val)
          else
            CONFIG[key] = val
          end
        rescue => e
          @ui.puts e.message
        end
      end

      config_show key
    end

config_show(key) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1214
    def config_show key
      key = key.to_sym
      config_detail = CONFIG_SET[key]

      if config_detail
        v = CONFIG[key]
        kv = "#{key} = #{v.inspect}"
        desc = config_detail[1]

        if config_default = config_detail[3]
          desc += " (default: #{config_default})"
        end

        line = "%-34s \# %s" % [kv, desc]
        if line.size > SESSION.width
          @ui.puts "\# #{desc}\n#{kv}"
        else
          @ui.puts line
        end
      else
        @ui.puts "Unknown configuration: #{key}. 'config' shows all configurations."
      end
    end

deactivate() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 220
    def deactivate
      get_thread_client.deactivate
      @thread_stopper.disable
      @tp_load_script.disable
      @tp_thread_begin.disable
      @tp_thread_end.disable
      @bps.each_value{|bp| bp.disable}
      @th_clients.each_value{|thc| thc.close}
      @tracers.values.each{|t| t.disable}
      @q_evt.close
      @ui&.deactivate
      @ui = nil
    end

deactivate_irb_integration() click to toggle source

    # File debug-1.10.0/lib/debug/irb_integration.rb, line 29
    def deactivate_irb_integration
      Reline.completion_proc = nil
      Reline.output_modifier_proc = nil
      Reline.autocompletion = false
      Reline.dig_perfect_match_proc = nil
      reset_ui UI_LocalConsole.new
    end

delete_bp(arg = nil) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1413
    def delete_bp arg = nil
      case arg
      when nil
        @bps.each{|key, bp| bp.delete}
        @bps.clear
      else
        del_bp = nil
        iterate_bps{|key, bp, i| del_bp = bp if i == arg}
        if del_bp
          del_bp.delete
          @bps.delete del_bp.key
          return [arg, del_bp]
        end
      end
    end

enter_postmortem_session(exc) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1887
    def enter_postmortem_session exc
      return unless exc.instance_variable_defined? :@__debugger_postmortem_frames

      frames = exc.instance_variable_get(:@__debugger_postmortem_frames)
      @postmortem = true
      ThreadClient.current.suspend :postmortem, postmortem_frames: frames, postmortem_exc: exc
    ensure
      @postmortem = false
    end

extend_feature(session: nil, thread_client: nil, ui: nil) click to toggle source

experimental API

    # File debug-1.10.0/lib/debug/session.rb, line 2022
    def extend_feature session: nil, thread_client: nil, ui: nil
      Session.include session if session
      ThreadClient.include thread_client if thread_client
      @ui.extend ui if ui
    end

fail_response(req, **result) click to toggle source

    # File debug-1.10.0/lib/debug/server_cdp.rb, line 722
    def fail_response req, **result
      @ui.respond_fail req, **result
      return :retry
    end

find_waiting_tc(id) click to toggle source

    # File debug-1.10.0/lib/debug/server_dap.rb, line 557
    def find_waiting_tc id
      @th_clients.each{|th, tc|
        return tc if tc.id == id && tc.waiting?
      }
      return nil
    end

get_thread_client(th = Thread.current) click to toggle source

can be called by other threads

    # File debug-1.10.0/lib/debug/session.rb, line 1652
    def get_thread_client th = Thread.current
      if @th_clients.has_key? th
        @th_clients[th]
      else
        if Thread.current == @session_server
          create_thread_client th
        else
          ask_thread_client th
        end
      end
    end

get_type(obj) click to toggle source

FIXME: unify this method with `ThreadClient#propertyDescriptor`.

    # File debug-1.10.0/lib/debug/server_cdp.rb, line 701
    def get_type obj
      case obj
      when Array
        ['object', 'array']
      when Hash
        ['object', 'map']
      when String
        ['string']
      when TrueClass, FalseClass
        ['boolean']
      when Symbol
        ['symbol']
      when Integer, Float
        ['number']
      when Exception
        ['object', 'error']
      else
        ['object']
      end
    end

in_subsession?() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1741
    def in_subsession?
      !@subsession_stack.empty?
    end

inspect() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 399
    def inspect
      "DEBUGGER__::SESSION"
    end

intercept_trap_sigint(flag) { || ... } click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1986
    def intercept_trap_sigint flag, &b
      prev = @intercept_trap_sigint
      @intercept_trap_sigint = flag
      yield
    ensure
      @intercept_trap_sigint = prev
    end

intercept_trap_sigint?() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1982
    def intercept_trap_sigint?
      @intercept_trap_sigint
    end

intercept_trap_sigint_end() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1999
    def intercept_trap_sigint_end
      @intercept_trap_sigint = false
      prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, nil
      prev
    end

intercept_trap_sigint_start(prev) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1994
    def intercept_trap_sigint_start prev
      @intercept_trap_sigint = true
      @intercepted_sigint_cmd = prev
    end

iterate_bps() { |key, bp, i| ... } click to toggle source

breakpoint management

    # File debug-1.10.0/lib/debug/session.rb, line 1354
    def iterate_bps
      deleted_bps = []
      i = 0
      @bps.each{|key, bp|
        if !bp.deleted?
          yield key, bp, i
          i += 1
        else
          deleted_bps << bp
        end
      }
    ensure
      deleted_bps.each{|bp| @bps.delete bp}
    end

managed_thread_clients() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1596
    def managed_thread_clients
      thcs, _unmanaged_ths = update_thread_list
      thcs
    end

method_added(tp) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1798
    def method_added tp
      b = tp.binding

      if var_name = b.local_variables.first
        mid = b.local_variable_get(var_name)
        resolved = true

        @bps.each{|k, bp|
          case bp
          when MethodBreakpoint
            if bp.method.nil?
              if bp.sig_method_name == mid.to_s
                bp.try_enable(added: true)
              end
            end

            resolved = false if !bp.enabled?
          end
        }

        if resolved
          Session.deactivate_method_added_trackers
        end

        case mid
        when :method_added, :singleton_method_added
          Session.create_method_added_tracker(tp.self, mid)
          Session.activate_method_added_trackers unless resolved
        end
      end
    end

on_load(iseq, src) click to toggle source

event

    # File debug-1.10.0/lib/debug/session.rb, line 1747
    def on_load iseq, src
      DEBUGGER__.info "Load #{iseq.absolute_path || iseq.path}"

      file_path, reloaded = @sr.add(iseq, src)
      @ui.event :load, file_path, reloaded

      # check breakpoints
      if file_path
        @bps.find_all do |_key, bp|
          LineBreakpoint === bp && bp.path_is?(file_path) && (iseq.first_lineno..iseq.last_line).cover?(bp.line)
        end.each do |_key, bp|
          if !bp.iseq
            bp.try_activate iseq
          elsif reloaded
            @bps.delete bp.key # to allow duplicate

            # When we delete a breakpoint from the @bps hash, we also need to deactivate it or else its tracepoint event
            # will continue to be enabled and we'll suspend on ghost breakpoints
            bp.delete

            nbp = LineBreakpoint.copy(bp, iseq)
            add_bp nbp
          end
        end
      else # !file_path => file_path is not existing
        @bps.find_all do |_key, bp|
          LineBreakpoint === bp && !bp.iseq && DEBUGGER__.compare_path(bp.path, (iseq.absolute_path || iseq.path))
        end.each do |_key, bp|
          bp.try_activate iseq
        end
      end
    end

on_thread_begin(th) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1627
    def on_thread_begin th
      if @th_clients.has_key? th
        # TODO: NG?
      else
        create_thread_client th
      end
    end

pop_event() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 249
    def pop_event
      @q_evt.pop
    end

postmortem=(is_enable) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1927
    def postmortem=(is_enable)
      if is_enable
        unless @postmortem_hook
          @postmortem_hook = TracePoint.new(:raise){|tp|
            exc = tp.raised_exception
            frames = DEBUGGER__.capture_frames(__dir__)
            exc.instance_variable_set(:@__debugger_postmortem_frames, frames)
          }
          at_exit{
            @postmortem_hook.disable
            if CONFIG[:postmortem] && (exc = $!) != nil
              exc = exc.cause while exc.cause

              begin
                @ui.puts "Enter postmortem mode with #{exc.inspect}"
                @ui.puts exc.backtrace.map{|e| '  ' + e}
                @ui.puts "\n"

                enter_postmortem_session exc
              rescue SystemExit
                exit!
              rescue Exception => e
                @ui = STDERR unless @ui
                @ui.puts "Error while postmortem console: #{e.inspect}"
              end
            end
          }
        end

        if !@postmortem_hook.enabled?
          @postmortem_hook.enable
        end
      else
        if @postmortem_hook && @postmortem_hook.enabled?
          @postmortem_hook.disable
        end
      end
    end

process_command(line) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1145
    def process_command line
      if line.empty?
        if @repl_prev_line
          line = @repl_prev_line
        else
          return :retry
        end
      else
        @repl_prev_line = line
      end

      /([^\s]+)(?:\s+(.+))?/ =~ line
      cmd_name, cmd_arg = $1, $2

      if cmd = @commands[cmd_name]
        check_postmortem      if !cmd.postmortem
        check_unsafe          if cmd.unsafe
        cancel_auto_continue  if cmd.cancel_auto_continue
        @repl_prev_line = nil if !cmd.repeat

        cmd.block.call(cmd_arg)
      else
        @repl_prev_line = nil
        check_unsafe

        request_eval :pp, line
      end

    rescue Interrupt
      return :retry
    rescue SystemExit
      raise
    rescue PostmortemError => e
      @ui.puts e.message
      return :retry
    rescue Exception => e
      @ui.puts "[REPL ERROR] #{e.inspect}"
      @ui.puts e.backtrace.map{|e| '  ' + e}
      return :retry
    end

process_event(evt) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 274
    def process_event evt
      # variable `@internal_info` is only used for test
      tc, output, ev, @internal_info, *ev_args = evt

      output.each{|str| @ui.puts str} if ev != :suspend

      # special event, tc is nil
      # and we don't want to set @tc to the newly created thread's ThreadClient
      if ev == :thread_begin
        th = ev_args.shift
        q = ev_args.shift
        on_thread_begin th
        q << true

        return
      end

      @tc = tc

      case ev
      when :init
        enter_subsession
        wait_command_loop
      when :load
        iseq, src = ev_args
        on_load iseq, src
        request_tc :continue

      when :trace
        trace_id, msg = ev_args
        if t = @tracers.values.find{|t| t.object_id == trace_id}
          t.puts msg
        end
        request_tc :continue

      when :suspend
        enter_subsession if ev_args.first != :replay
        output.each{|str| @ui.puts str} unless @ui.ignore_output_on_suspend?

        case ev_args.first
        when :breakpoint
          bp, i = bp_index ev_args[1]
          clean_bps unless bp
          @ui.event :suspend_bp, i, bp, @tc.id
        when :trap
          @ui.event :suspend_trap, sig = ev_args[1], @tc.id

          if sig == :SIGINT && (@intercepted_sigint_cmd.kind_of?(Proc) || @intercepted_sigint_cmd.kind_of?(String))
            @ui.puts "#{@intercepted_sigint_cmd.inspect} is registered as SIGINT handler."
            @ui.puts "`sigint` command execute it."
          end
        else
          @ui.event :suspended, @tc.id
        end

        if @displays.empty?
          wait_command_loop
        else
          request_eval :display, @displays
        end
      when :result
        raise "[BUG] not in subsession" if @subsession_stack.empty?

        case ev_args.first
        when :try_display
          failed_results = ev_args[1]
          if failed_results.size > 0
            i, _msg = failed_results.last
            if i+1 == @displays.size
              @ui.puts "canceled: #{@displays.pop}"
            end
          end

          stop_all_threads
        when :method_breakpoint, :watch_breakpoint
          bp = ev_args[1]
          if bp
            add_bp(bp)
            show_bps bp
          else
            # can't make a bp
          end
        when :trace_pass
          obj_id = ev_args[1]
          obj_inspect = ev_args[2]
          opt = ev_args[3]
          add_tracer ObjectTracer.new(@ui, obj_id, obj_inspect, **opt)
          stop_all_threads
        else
          stop_all_threads
        end

        wait_command_loop

      when :protocol_result
        process_protocol_result ev_args
        wait_command_loop
      end
    end

process_info() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 2005
    def process_info
      if @process_group.multi?
        "#{$0}\##{Process.pid}"
      end
    end

process_protocol_request(req) click to toggle source

    # File debug-1.10.0/lib/debug/server_cdp.rb, line 730
    def process_protocol_request req
      case req['method']
      when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.enable'
        request_tc [:cdp, :backtrace, req]
      when 'Debugger.evaluateOnCallFrame'
        frame_id = req.dig('params', 'callFrameId')
        group = req.dig('params', 'objectGroup')
        if fid = @frame_map[frame_id]
          expr = req.dig('params', 'expression')
          request_tc [:cdp, :evaluate, req, fid, expr, group]
        else
          fail_response req,
                        code: INVALID_PARAMS,
                        message: "'callFrameId' is an invalid"
        end
      when 'Runtime.getProperties', 'Runtime.getExceptionDetails'
        oid = req.dig('params', 'objectId') || req.dig('params', 'errorObjectId')
        if ref = @obj_map[oid]
          case ref[0]
          when 'local'
            frame_id = ref[1]
            fid = @frame_map[frame_id]
            request_tc [:cdp, :scope, req, fid]
          when 'global'
            vars = safe_global_variables.sort.map do |name|
              begin
                gv = eval(name.to_s)
              rescue Errno::ENOENT
                gv = nil
              end
              prop = {
                name: name,
                value: {
                  description: gv.inspect
                },
                configurable: true,
                enumerable: true
              }
              type, subtype = get_type(gv)
              prop[:value][:type] = type
              prop[:value][:subtype] = subtype if subtype
              prop
            end

            @ui.respond req, result: vars
            return :retry
          when 'properties'
            request_tc [:cdp, :properties, req, oid]
          when 'exception'
            request_tc [:cdp, :exception, req, oid]
          when 'script'
            # TODO: Support script and global types
            @ui.respond req, result: []
            return :retry
          else
            raise "Unknown type: #{ref.inspect}"
          end
        else
          fail_response req,
                        code: INVALID_PARAMS,
                        message: "'objectId' is an invalid"
        end
      when 'Debugger.getScriptSource'
        s_id = req.dig('params', 'scriptId')
        if src = @src_map[s_id]
          @ui.respond req, scriptSource: src
        else
          fail_response req,
                        code: INVALID_PARAMS,
                        message: "'scriptId' is an invalid"
        end
        return :retry
      when 'Debugger.getPossibleBreakpoints'
        s_id = req.dig('params', 'start', 'scriptId')
        if src = @src_map[s_id]
          lineno = req.dig('params', 'start', 'lineNumber')
          end_line = src.lines.count
          lineno = end_line  if lineno > end_line
          @ui.respond req,
                      locations: [{
                        scriptId: s_id,
                        lineNumber: lineno
                      }]
        else
          fail_response req,
                        code: INVALID_PARAMS,
                        message: "'scriptId' is an invalid"
        end
        return :retry
      when 'Debugger.setBreakpointByUrl'
        path = req.dig('params', 'scriptId')
        if s_id = @scr_id_map[path]
          lineno = req.dig('params', 'lineNumber')
          b_id = req.dig('params', 'breakpointId')
          @ui.respond req,
                      breakpointId: b_id,
                      locations: [{
                          scriptId: s_id,
                          lineNumber: lineno
                      }]
        else
          fail_response req,
                        code: INTERNAL_ERROR,
                        message: 'The target script is not found...'
        end
        return :retry
      end
    end

process_protocol_result(args) click to toggle source

    # File debug-1.10.0/lib/debug/server_cdp.rb, line 839
    def process_protocol_result args
      type, req, result = args

      case type
      when :backtrace
        result[:callFrames].each.with_index do |frame, i|
          frame_id = frame[:callFrameId]
          @frame_map[frame_id] = i
          path = frame[:url]
          unless s_id = @scr_id_map[path]
            s_id = (@scr_id_map.size + 1).to_s
            @scr_id_map[path] = s_id
            lineno = 0
            src = ''
            if path && File.exist?(path)
              src = File.read(path)
              @src_map[s_id] = src
              lineno = src.lines.count
            end
            @ui.fire_event 'Debugger.scriptParsed',
                          scriptId: s_id,
                          url: path,
                          startLine: 0,
                          startColumn: 0,
                          endLine: lineno,
                          endColumn: 0,
                          executionContextId: 1,
                          hash: src.hash.inspect
          end
          frame[:location][:scriptId] = s_id
          frame[:functionLocation][:scriptId] = s_id

          frame[:scopeChain].each {|s|
            oid = s.dig(:object, :objectId)
            @obj_map[oid] = [s[:type], frame_id]
          }
        end

        if oid = result.dig(:data, :objectId)
          @obj_map[oid] = ['properties']
        end
        @ui.fire_event 'Debugger.paused', **result
      when :evaluate
        message = result.delete :message
        if message
          fail_response req,
                        code: INVALID_PARAMS,
                        message: message
        else
          src = req.dig('params', 'expression')
          s_id = (@src_map.size + 1).to_s
          @src_map[s_id] = src
          lineno = src.lines.count
          @ui.fire_event 'Debugger.scriptParsed',
                            scriptId: s_id,
                            url: '',
                            startLine: 0,
                            startColumn: 0,
                            endLine: lineno,
                            endColumn: 0,
                            executionContextId: 1,
                            hash: src.hash.inspect
          if exc = result.dig(:response, :exceptionDetails)
            exc[:stackTrace][:callFrames].each{|frame|
              if frame[:url].empty?
                frame[:scriptId] = s_id
              else
                path = frame[:url]
                unless s_id = @scr_id_map[path]
                  s_id = (@scr_id_map.size + 1).to_s
                  @scr_id_map[path] = s_id
                end
                frame[:scriptId] = s_id
              end
            }
            if oid = exc[:exception][:objectId]
              @obj_map[oid] = ['exception']
            end
          end
          rs = result.dig(:response, :result)
          [rs].each{|obj|
            if oid = obj[:objectId]
              @obj_map[oid] = ['properties']
            end
          }
          @ui.respond req, **result[:response]

          out = result[:output]
          if out && !out.empty?
            @ui.fire_event 'Runtime.consoleAPICalled',
                            type: 'log',
                            args: [
                              type: out.class,
                              value: out
                            ],
                            executionContextId: 1, # Change this number if something goes wrong.
                            timestamp: Time.now.to_f
          end
        end
      when :scope
        result.each{|obj|
          if oid = obj.dig(:value, :objectId)
            @obj_map[oid] = ['properties']
          end
        }
        @ui.respond req, result: result
      when :properties
        result.each_value{|v|
          v.each{|obj|
            if oid = obj.dig(:value, :objectId)
              @obj_map[oid] = ['properties']
            end
          }
        }
        @ui.respond req, **result
      when :exception
        @ui.respond req, **result
      end
    end

prompt() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 417
    def prompt
      if @postmortem
        '(rdbg:postmortem) '
      elsif @process_group.multi?
        "(rdbg@#{process_info}) "
      else
        '(rdbg) '
      end
    end

register_default_command() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 468
    def register_default_command
      ### Control flow

      # * `s[tep]`
      #   * Step in. Resume the program until next breakable point.
      # * `s[tep] <n>`
      #   * Step in, resume the program at `<n>`th breakable point.
      register_command 's', 'step',
                       repeat: true,
                       cancel_auto_continue: true,
                       postmortem: false do |arg|
        step_command :in, arg
      end

      # * `n[ext]`
      #   * Step over. Resume the program until next line.
      # * `n[ext] <n>`
      #   * Step over, same as `step <n>`.
      register_command 'n', 'next',
                       repeat: true,
                       cancel_auto_continue: true,
                       postmortem: false do |arg|
        step_command :next, arg
      end

      # * `fin[ish]`
      #   * Finish this frame. Resume the program until the current frame is finished.
      # * `fin[ish] <n>`
      #   * Finish `<n>`th frames.
      register_command 'fin', 'finish',
                       repeat: true,
                       cancel_auto_continue: true,
                       postmortem: false do |arg|
        if arg&.to_i == 0
          raise 'finish command with 0 does not make sense.'
        end

        step_command :finish, arg
      end

      # * `u[ntil]`
      #   * Similar to `next` command, but only stop later lines or the end of the current frame.
      #   * Similar to gdb's `advance` command.
      # * `u[ntil] <[file:]line>`
      #   * Run til the program reaches given location or the end of the current frame.
      # * `u[ntil] <name>`
      #   * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
      register_command 'u', 'until',
                       repeat: true,
                       cancel_auto_continue: true,
                       postmortem: false do |arg|

        step_command :until, arg
      end

      # * `c` or `cont` or `continue`
      #   * Resume the program.
      register_command 'c', 'cont', 'continue',
                       repeat: true,
                       cancel_auto_continue: true do |arg|
        leave_subsession :continue
      end

      # * `q[uit]` or `Ctrl-D`
      #   * Finish debugger (with the debuggee process on non-remote debugging).
      register_command 'q', 'quit' do |arg|
        if ask 'Really quit?'
          @ui.quit arg.to_i do
            request_tc :quit
          end
          leave_subsession :continue
        else
          next :retry
        end
      end

      # * `q[uit]!`
      #   * Same as q[uit] but without the confirmation prompt.
      register_command 'q!', 'quit!', unsafe: false do |arg|
        @ui.quit arg.to_i do
          request_tc :quit
        end
        leave_subsession :continue
      end

      # * `kill`
      #   * Stop the debuggee process with `Kernel#exit!`.
      register_command 'kill' do |arg|
        if ask 'Really kill?'
          exit! (arg || 1).to_i
        else
          next :retry
        end
      end

      # * `kill!`
      #   * Same as kill but without the confirmation prompt.
      register_command 'kill!', unsafe: false do |arg|
        exit! (arg || 1).to_i
      end

      # * `sigint`
      #   * Execute SIGINT handler registered by the debuggee.
      #   * Note that this command should be used just after stop by `SIGINT`.
      register_command 'sigint' do
        begin
          case cmd = @intercepted_sigint_cmd
          when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
            # ignore
          when String
            eval(cmd)
          when Proc
            cmd.call
          end

          leave_subsession :continue

        rescue Exception => e
          @ui.puts "Exception: #{e}"
          @ui.puts e.backtrace.map{|line| "  #{e}"}
          next :retry
        end
      end

      ### Breakpoint

      # * `b[reak]`
      #   * Show all breakpoints.
      # * `b[reak] <line>`
      #   * Set breakpoint on `<line>` at the current frame's file.
      # * `b[reak] <file>:<line>` or `<file> <line>`
      #   * Set breakpoint on `<file>:<line>`.
      # * `b[reak] <class>#<name>`
      #    * Set breakpoint on the method `<class>#<name>`.
      # * `b[reak] <expr>.<name>`
      #    * Set breakpoint on the method `<expr>.<name>`.
      # * `b[reak] ... if: <expr>`
      #   * break if `<expr>` is true at specified location.
      # * `b[reak] ... pre: <command>`
      #   * break and run `<command>` before stopping.
      # * `b[reak] ... do: <command>`
      #   * break and run `<command>`, and continue.
      # * `b[reak] ... path: <path>`
      #   * break if the path matches to `<path>`. `<path>` can be a regexp with `/regexp/`.
      # * `b[reak] if: <expr>`
      #   * break if: `<expr>` is true at any lines.
      #   * Note that this feature is super slow.
      register_command 'b', 'break', postmortem: false, unsafe: false do |arg|
        if arg == nil
          show_bps
          next :retry
        else
          case bp = repl_add_breakpoint(arg)
          when :noretry
          when nil
            next :retry
          else
            show_bps bp
            next :retry
          end
        end
      end

      # * `catch <Error>`
      #   * Set breakpoint on raising `<Error>`.
      # * `catch ... if: <expr>`
      #   * stops only if `<expr>` is true as well.
      # * `catch ... pre: <command>`
      #   * runs `<command>` before stopping.
      # * `catch ... do: <command>`
      #   * stops and run `<command>`, and continue.
      # * `catch ... path: <path>`
      #   * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
      register_command 'catch', postmortem: false, unsafe: false do |arg|
        if arg
          bp = repl_add_catch_breakpoint arg
          show_bps bp if bp
        else
          show_bps
        end

        :retry
      end

      # * `watch @ivar`
      #   * Stop the execution when the result of current scope's `@ivar` is changed.
      #   * Note that this feature is super slow.
      # * `watch ... if: <expr>`
      #   * stops only if `<expr>` is true as well.
      # * `watch ... pre: <command>`
      #   * runs `<command>` before stopping.
      # * `watch ... do: <command>`
      #   * stops and run `<command>`, and continue.
      # * `watch ... path: <path>`
      #   * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
      register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg|
        if arg && arg.match?(/\A@\w+/)
          repl_add_watch_breakpoint(arg)
        else
          show_bps
          :retry
        end
      end

      # * `del[ete]`
      #   * delete all breakpoints.
      # * `del[ete] <bpnum>`
      #   * delete specified breakpoint.
      register_command 'del', 'delete', postmortem: false, unsafe: false do |arg|
        case arg
        when nil
          show_bps
          if ask "Remove all breakpoints?", 'N'
            delete_bp
          end
        when /\d+/
          bp = delete_bp arg.to_i
        else
          nil
        end
        @ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
        :retry
      end

      ### Information

      # * `bt` or `backtrace`
      #   * Show backtrace (frame) information.
      # * `bt <num>` or `backtrace <num>`
      #   * Only shows first `<num>` frames.
      # * `bt /regexp/` or `backtrace /regexp/`
      #   * Only shows frames with method name or location info that matches `/regexp/`.
      # * `bt <num> /regexp/` or `backtrace <num> /regexp/`
      #   * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
      register_command 'bt', 'backtrace', unsafe: false do |arg|
        case arg
        when /\A(\d+)\z/
          request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
        when /\A\/(.*)\/\z/
          pattern = $1
          request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
        when /\A(\d+)\s+\/(.*)\/\z/
          max, pattern = $1, $2
          request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
        else
          request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
        end
      end

      # * `l[ist]`
      #   * Show current frame's source code.
      #   * Next `list` command shows the successor lines.
      # * `l[ist] -`
      #   * Show predecessor lines as opposed to the `list` command.
      # * `l[ist] <start>` or `l[ist] <start>-<end>`
      #   * Show current frame's source code from the line <start> to <end> if given.
      register_command 'l', 'list', repeat: true, unsafe: false do |arg|
        case arg ? arg.strip : nil
        when /\A(\d+)\z/
          request_tc [:show, :list, {start_line: arg.to_i - 1}]
        when /\A-\z/
          request_tc [:show, :list, {dir: -1}]
        when /\A(\d+)-(\d+)\z/
          request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
        when nil
          request_tc [:show, :list]
        else
          @ui.puts "Can not handle list argument: #{arg}"
          :retry
        end
      end

      # * `whereami`
      #   * Show the current frame with source code.
      register_command 'whereami', unsafe: false do
        request_tc [:show, :whereami]
      end

      # * `edit`
      #   * Open the current file on the editor (use `EDITOR` environment variable).
      #   * Note that edited file will not be reloaded.
      # * `edit <file>`
      #   * Open <file> on the editor.
      register_command 'edit' do |arg|
        if @ui.remote?
          @ui.puts "not supported on the remote console."
          next :retry
        end

        begin
          arg = resolve_path(arg) if arg
        rescue Errno::ENOENT
          @ui.puts "not found: #{arg}"
          next :retry
        end

        request_tc [:show, :edit, arg]
      end

      info_subcommands = nil
      info_subcommands_abbrev = nil

      # * `i[nfo]`
      #   * Show information about current frame (local/instance variables and defined constants).
      # * `i[nfo]` <subcommand>
      #   * `info` has the following sub-commands.
      #   * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'.
      # * `i[nfo] l or locals or local_variables`
      #   * Show information about the current frame (local variables)
      #   * It includes `self` as `%self` and a return value as `_return`.
      # * `i[nfo] i or ivars or instance_variables`
      #   * Show information about instance variables about `self`.
      #   * `info ivars <expr>` shows the instance variables of the result of `<expr>`.
      # * `i[nfo] c or consts or constants`
      #   * Show information about accessible constants except toplevel constants.
      #   * `info consts <expr>` shows the constants of a class/module of the result of `<expr>`
      # * `i[nfo] g or globals or global_variables`
      #   * Show information about global variables
      # * `i[nfo] th or threads`
      #   * Show all threads (same as `th[read]`).
      # * `i[nfo] b or breakpoints or w or watchpoints`
      #   * Show all breakpoints and watchpoints.
      # * `i[nfo] ... /regexp/`
      #   * Filter the output with `/regexp/`.
      register_command 'i', 'info', unsafe: false do |arg|
        if /\/(.+)\/\z/ =~ arg
          pat = Regexp.compile($1)
          sub = $~.pre_match.strip
        else
          sub = arg
        end

        if /\A(.+?)\b(.+)/ =~ sub
          sub = $1
          opt = $2.strip
          opt = nil if opt.empty?
        end

        if sub && !info_subcommands
          info_subcommands = {
            locals: %w[ locals local_variables ],
            ivars:  %w[ ivars instance_variables ],
            consts: %w[ consts constants ],
            globals:%w[ globals global_variables ],
            threads:%w[ threads ],
            breaks: %w[ breakpoints ],
            watchs: %w[ watchpoints ],
          }

          require_relative 'abbrev_command'
          info_subcommands_abbrev = AbbrevCommand.new(info_subcommands)
        end

        if sub
          sub = info_subcommands_abbrev.search sub, :unknown do |candidates|
            # note: unreached now
            @ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}"
          end
        end

        case sub
        when nil
          request_tc_with_restarted_threads [:show, :default, pat] # something useful
        when :locals
          request_tc_with_restarted_threads [:show, :locals, pat]
        when :ivars
          request_tc_with_restarted_threads [:show, :ivars, pat, opt]
        when :consts
          request_tc_with_restarted_threads [:show, :consts, pat, opt]
        when :globals
          request_tc_with_restarted_threads [:show, :globals, pat]
        when :threads
          thread_list
          :retry
        when :breaks, :watchs
          show_bps
          :retry
        else
          @ui.puts "unrecognized argument for info command: #{arg}"
          show_help 'info'
          :retry
        end
      end

      # * `o[utline]` or `ls`
      #   * Show you available methods, constants, local variables, and instance variables in the current scope.
      # * `o[utline] <expr>` or `ls <expr>`
      #   * Show you available methods and instance variables of the given object.
      #   * If the object is a class/module, it also lists its constants.
      register_command 'outline', 'o', 'ls', unsafe: false do |arg|
        request_tc_with_restarted_threads [:show, :outline, arg]
      end

      # * `display`
      #   * Show display setting.
      # * `display <expr>`
      #   * Show the result of `<expr>` at every suspended timing.
      register_command 'display', postmortem: false do |arg|
        if arg && !arg.empty?
          @displays << arg
          request_eval :try_display, @displays
        else
          request_eval :display, @displays
        end
      end

      # * `undisplay`
      #   * Remove all display settings.
      # * `undisplay <displaynum>`
      #   * Remove a specified display setting.
      register_command 'undisplay', postmortem: false, unsafe: false do |arg|
        case arg
        when /(\d+)/
          if @displays[n = $1.to_i]
            @displays.delete_at n
          end
          request_eval :display, @displays
        when nil
          if ask "clear all?", 'N'
            @displays.clear
          end
          :retry
        end
      end

      ### Frame control

      # * `f[rame]`
      #   * Show the current frame.
      # * `f[rame] <framenum>`
      #   * Specify a current frame. Evaluation are run on specified frame.
      register_command 'frame', 'f', unsafe: false do |arg|
        request_tc [:frame, :set, arg]
      end

      # * `up`
      #   * Specify the upper frame.
      register_command 'up', repeat: true, unsafe: false do |arg|
        request_tc [:frame, :up]
      end

      # * `down`
      #   * Specify the lower frame.
      register_command 'down', repeat: true, unsafe: false do |arg|
        request_tc [:frame, :down]
      end

      ### Evaluate

      # * `p <expr>`
      #   * Evaluate like `p <expr>` on the current frame.
      register_command 'p' do |arg|
        request_eval :p, arg.to_s
      end

      # * `pp <expr>`
      #   * Evaluate like `pp <expr>` on the current frame.
      register_command 'pp' do |arg|
        request_eval :pp, arg.to_s
      end

      # * `eval <expr>`
      #   * Evaluate `<expr>` on the current frame.
      register_command 'eval', 'call' do |arg|
        if arg == nil || arg.empty?
          show_help 'eval'
          @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
          :retry
        else
          request_eval :call, arg
        end
      end

      # * `irb`
      #   * Activate and switch to `irb:rdbg` console
      register_command 'irb' do |arg|
        if @ui.remote?
          @ui.puts "\nIRB is not supported on the remote console."
        else
          config_set :irb_console, true
        end

        :retry
      end

      ### Trace
      # * `trace`
      #   * Show available tracers list.
      # * `trace line`
      #   * Add a line tracer. It indicates line events.
      # * `trace call`
      #   * Add a call tracer. It indicate call/return events.
      # * `trace exception`
      #   * Add an exception tracer. It indicates raising exceptions.
      # * `trace object <expr>`
      #   * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
      # * `trace ... /regexp/`
      #   * Indicates only matched events to `/regexp/`.
      # * `trace ... into: <file>`
      #   * Save trace information into: `<file>`.
      # * `trace off <num>`
      #   * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
      # * `trace off [line|call|pass]`
      #   * Disable all tracers. If `<type>` is provided, disable specified type tracers.
      register_command 'trace', postmortem: false, unsafe: false do |arg|
        if (re = /\s+into:\s*(.+)/) =~ arg
          into = $1
          arg.sub!(re, '')
        end

        if (re = /\s\/(.+)\/\z/) =~ arg
          pattern = $1
          arg.sub!(re, '')
        end

        case arg
        when nil
          @ui.puts 'Tracers:'
          @tracers.values.each_with_index{|t, i|
            @ui.puts "* \##{i} #{t}"
          }
          @ui.puts
          :retry

        when /\Aline\z/
          add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
          :retry

        when /\Acall\z/
          add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
          :retry

        when /\Aexception\z/
          add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
          :retry

        when /\Aobject\s+(.+)/
          request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]

        when /\Aoff\s+(\d+)\z/
          if t = @tracers.values[$1.to_i]
            t.disable
            @ui.puts "Disable #{t.to_s}"
          else
            @ui.puts "Unmatched: #{$1}"
          end
          :retry

        when /\Aoff(\s+(line|call|exception|object))?\z/
          @tracers.values.each{|t|
            if $2.nil? || t.type == $2
              t.disable
              @ui.puts "Disable #{t.to_s}"
            end
          }
          :retry

        else
          @ui.puts "Unknown trace option: #{arg.inspect}"
          :retry
        end
      end

      # Record
      # * `record`
      #   * Show recording status.
      # * `record [on|off]`
      #   * Start/Stop recording.
      # * `step back`
      #   * Start replay. Step back with the last execution log.
      #   * `s[tep]` does stepping forward with the last log.
      # * `step reset`
      #   * Stop replay .
      register_command 'record', postmortem: false, unsafe: false do |arg|
        case arg
        when nil, 'on', 'off'
          request_tc [:record, arg&.to_sym]
        else
          @ui.puts "unknown command: #{arg}"
          :retry
        end
      end

      ### Thread control

      # * `th[read]`
      #   * Show all threads.
      # * `th[read] <thnum>`
      #   * Switch thread specified by `<thnum>`.
      register_command 'th', 'thread', unsafe: false do |arg|
        case arg
        when nil, 'list', 'l'
          thread_list
        when /(\d+)/
          switch_thread $1.to_i
        else
          @ui.puts "unknown thread command: #{arg}"
        end
        :retry
      end

      ### Configuration
      # * `config`
      #   * Show all configuration with description.
      # * `config <name>`
      #   * Show current configuration of <name>.
      # * `config set <name> <val>` or `config <name> = <val>`
      #   * Set <name> to <val>.
      # * `config append <name> <val>` or `config <name> << <val>`
      #   * Append `<val>` to `<name>` if it is an array.
      # * `config unset <name>`
      #   * Set <name> to default.
      register_command 'config', unsafe: false do |arg|
        config_command arg
        :retry
      end

      # * `source <file>`
      #   * Evaluate lines in `<file>` as debug commands.
      register_command 'source' do |arg|
        if arg
          begin
            cmds = File.readlines(path = File.expand_path(arg))
            add_preset_commands path, cmds, kick: true, continue: false
          rescue Errno::ENOENT
            @ui.puts "File not found: #{arg}"
          end
        else
          show_help 'source'
        end
        :retry
      end

      # * `open`
      #   * open debuggee port on UNIX domain socket and wait for attaching.
      #   * Note that `open` command is EXPERIMENTAL.
      # * `open [<host>:]<port>`
      #   * open debuggee port on TCP/IP with given `[<host>:]<port>` and wait for attaching.
      # * `open vscode`
      #   * open debuggee port for VSCode and launch VSCode if available.
      # * `open chrome`
      #   * open debuggee port for Chrome and wait for attaching.
      register_command 'open' do |arg|
        case arg&.downcase
        when '', nil
          ::DEBUGGER__.open nonstop: true
        when /\A(\d+)z/
          ::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true
        when /\A(.+):(\d+)\z/
          ::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true
        when 'tcp'
          ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
        when 'vscode'
          CONFIG[:open] = 'vscode'
          ::DEBUGGER__.open nonstop: true
        when 'chrome', 'cdp'
          CONFIG[:open] = 'chrome'
          ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
        else
          raise "Unknown arg: #{arg}"
        end

        :retry
      end

      ### Help

      # * `h[elp]`
      #   * Show help for all commands.
      # * `h[elp] <command>`
      #   * Show help for the given command.
      register_command 'h', 'help', '?', unsafe: false do |arg|
        show_help arg
        :retry
      end
    end

register_var(v, tid) click to toggle source

    # File debug-1.10.0/lib/debug/server_dap.rb, line 752
    def register_var v, tid
      if (tl_vid = v[:variablesReference]) > 0
        vid = @var_map.size + 1
        @var_map[vid] = [:variable, tid, tl_vid]
        v[:variablesReference] = vid
      end
    end

register_vars(vars, tid) click to toggle source

    # File debug-1.10.0/lib/debug/server_dap.rb, line 760
    def register_vars vars, tid
      raise tid.inspect unless tid.kind_of?(Integer)
      vars.each{|v|
        register_var v, tid
      }
    end

rehash_bps() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1384
    def rehash_bps
      bps = @bps.values
      @bps.clear
      bps.each{|bp|
        add_bp bp
      }
    end

remote?() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 154
    def remote?
      @ui.remote?
    end

repl_add_breakpoint(arg) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1456
    def repl_add_breakpoint arg
      expr = parse_break 'break', arg.strip
      cond = expr[:if]
      cmd  = expr[:cmd]
      path = expr[:path]

      case expr[:sig]
      when /\A(\d+)\z/
        add_line_breakpoint @tc.location.path, $1.to_i, cond: cond, command: cmd
      when /\A(.+)[:\s+](\d+)\z/
        add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
      when /\A(.+)([\.\#])(.+)\z/
        request_tc [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
        return :noretry
      when nil
        add_check_breakpoint cond, path, cmd
      else
        @ui.puts "Unknown breakpoint format: #{arg}"
        @ui.puts
        show_help 'b'
      end
    end

repl_add_catch_breakpoint(arg) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1479
    def repl_add_catch_breakpoint arg
      expr = parse_break 'catch', arg.strip
      cond = expr[:if]
      cmd  = expr[:cmd]
      path = expr[:path]

      bp = CatchBreakpoint.new(expr[:sig], cond: cond, command: cmd, path: path)
      add_bp bp
    end

repl_add_watch_breakpoint(arg) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1489
    def repl_add_watch_breakpoint arg
      expr = parse_break 'watch', arg.strip
      cond = expr[:if]
      cmd  = expr[:cmd]
      path = Regexp.compile(expr[:path]) if expr[:path]

      request_tc [:breakpoint, :watch, expr[:sig], cond, cmd, path]
    end

request_eval(type, src) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 270
    def request_eval type, src
      request_tc_with_restarted_threads [:eval, type, src]
    end

request_tc(req) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 261
    def request_tc(req)
      @tc << req
    end

request_tc_with_restarted_threads(req) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 265
    def request_tc_with_restarted_threads(req)
      restart_all_threads
      request_tc(req)
    end

reset_ui(ui) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 234
    def reset_ui ui
      @ui.deactivate
      @ui = ui

      # activate new ui
      @tp_thread_begin.disable
      @tp_thread_end.disable
      @ui.activate self
      if @ui.respond_to?(:reader_thread) && thc = get_thread_client(@ui.reader_thread)
        thc.mark_as_management
      end
      @tp_thread_begin.enable
      @tp_thread_end.enable
    end

resolve_path(file) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1780
    def resolve_path file
      File.realpath(File.expand_path(file))
    rescue Errno::ENOENT
      case file
      when '-e', '-'
        return file
      else
        $LOAD_PATH.each do |lp|
          libpath = File.join(lp, file)
          return File.realpath(libpath)
        rescue Errno::ENOENT
          # next
        end
      end

      raise
    end

save_int_trap(cmd) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1977
    def save_int_trap cmd
      prev, @intercepted_sigint_cmd = @intercepted_sigint_cmd, cmd
      prev
    end

session_server_main() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 253
    def session_server_main
      while evt = pop_event
        process_event evt
      end
    ensure
      deactivate
    end

set_no_sigint_hook(old, new) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1966
    def set_no_sigint_hook old, new
      return unless old != new
      return unless @ui.respond_to? :activate_sigint

      if old # no -> yes
        @ui.activate_sigint
      else
        @ui.deactivate_sigint
      end
    end

setup_threads() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1614
    def setup_threads
      prev_clients = @th_clients
      @th_clients = {}

      Thread.list.each{|th|
        if tc = prev_clients[th]
          @th_clients[th] = tc
        else
          create_thread_client(th)
        end
      }
    end

show_bps(specific_bp = nil) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1369
    def show_bps specific_bp = nil
      iterate_bps do |key, bp, i|
        @ui.puts "#%d %s" % [i, bp.to_s] if !specific_bp || bp == specific_bp
      end
    end

show_help(arg = nil) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1294
    def show_help arg = nil
      instructions = (DEBUGGER__.commands.keys + DEBUGGER__.commands.values).uniq
      print_instructions = proc do |desc|
        desc.split("\n").each do |line|
          next if line.start_with?(" ") # workaround for step back
          formatted_line = line.gsub(/[\[\]\*]/, "").strip
          instructions.each do |inst|
            if formatted_line.start_with?("`#{inst}")
              desc.sub!(line, colorize(line, [:CYAN, :BOLD]))
            end
          end
        end
        @ui.puts desc
      end

      print_category = proc do |cat|
        @ui.puts "\n"
        @ui.puts colorize("### #{cat}", [:GREEN, :BOLD])
        @ui.puts "\n"
      end

      DEBUGGER__.helps.each { |cat, cs|
        # categories
        if arg.nil?
          print_category.call(cat)
        else
          cs.each { |ws, _|
            if ws.include?(arg)
              print_category.call(cat)
              break
            end
          }
        end

        # instructions
        cs.each { |ws, desc|
          if arg.nil? || ws.include?(arg)
            print_instructions.call(desc.dup)
            return if arg
          end
        }
      }

      @ui.puts "not found: #{arg}" if arg
    end

source(iseq) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 391
    def source iseq
      if !CONFIG[:no_color]
        @sr.get_colored(iseq)
      else
        @sr.get(iseq)
      end
    end

step_command(type, arg) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1186
    def step_command type, arg
      if type == :until
        leave_subsession [:step, type, arg]
        return
      end

      case arg
      when nil, /\A\d+\z/
        if type == :in && @tc.recorder&.replaying?
          request_tc [:step, type, arg&.to_i]
        else
          leave_subsession [:step, type, arg&.to_i]
        end
      when /\A(back)\z/, /\A(back)\s+(\d+)\z/, /\A(reset)\z/
        if type != :in
          @ui.puts "only `step #{arg}` is supported."
          :retry
        else
          type = $1.to_sym
          iter = $2&.to_i
          request_tc [:step, type, iter]
        end
      else
        @ui.puts "Unknown option: #{arg}"
        :retry
      end
    end

stop_stepping?(file, line, subsession_id = nil) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 158
    def stop_stepping? file, line, subsession_id = nil
      if @bps.has_key? [file, line]
        true
      elsif subsession_id && @subsession_id != subsession_id
        true
      else
        false
      end
    end

switch_thread(n) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1601
    def switch_thread n
      thcs, _unmanaged_ths = update_thread_list

      if tc = thcs[n]
        if tc.waiting?
          @tc = tc
        else
          @ui.puts "#{tc.thread} is not controllable yet."
        end
      end
      thread_list
    end

thread_list() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1582
    def thread_list
      thcs, unmanaged_ths = update_thread_list
      thcs.each_with_index{|thc, i|
        @ui.puts "#{@tc == thc ? "--> " : "    "}\##{i} #{thc}"
      }

      if !unmanaged_ths.empty?
        @ui.puts "The following threads are not managed yet by the debugger:"
        unmanaged_ths.each{|th|
          @ui.puts "     " + th.to_s
        }
      end
    end

update_thread_list() click to toggle source

threads

    # File debug-1.10.0/lib/debug/session.rb, line 1564
    def update_thread_list
      list = Thread.list
      thcs = []
      unmanaged = []

      list.each{|th|
        if thc = @th_clients[th]
          if !thc.management?
            thcs << thc
          end
        else
          unmanaged << th
        end
      }

      return thcs.sort_by{|thc| thc.id}, unmanaged
    end

wait_command() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 427
    def wait_command
      if @preset_command
        if @preset_command.commands.empty?
          if @preset_command.auto_continue
            @preset_command = nil

            leave_subsession :continue
            return
          else
            @preset_command = nil
            return :retry
          end
        else
          line = @preset_command.commands.shift
          @ui.puts "(rdbg:#{@preset_command.source}) #{line}"
        end
      else
        @ui.puts "INTERNAL_INFO: #{JSON.generate(@internal_info)}" if ENV['RUBY_DEBUG_TEST_UI'] == 'terminal'
        line = @ui.readline prompt
      end

      case line
      when String
        process_command line
      when Hash
        process_protocol_request line # defined in server.rb
      else
        raise "unexpected input: #{line.inspect}"
      end
    end

wait_command_loop() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 403
    def wait_command_loop
      loop do
        case wait_command
        when :retry
          # nothing
        else
          break
        end
      rescue Interrupt
        @ui.puts "\n^C"
        retry
      end
    end

width() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1865
    def width
      @ui.width
    end

### Private Instance Methods

ask_thread_client(th) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1641
            def ask_thread_client th
      # TODO: Ractor support
      q2 = Queue.new
      # tc, output, ev, @internal_info, *ev_args = evt
      @q_evt << [nil, [], :thread_begin, nil, th, q2]
      q2.pop

      @th_clients[th] or raise "unexpected error"
    end

create_thread_client(th) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1635
            def create_thread_client th
      # TODO: Ractor support
      raise "Only session_server can create thread_client" unless Thread.current == @session_server
      @th_clients[th] = ThreadClient.new((@tc_id += 1), @q_evt, Queue.new, th)
    end

enter_subsession() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1709
            def enter_subsession
      @subsession_id += 1
      if !@subsession_stack.empty?
        DEBUGGER__.debug{ "Enter subsession (nested #{@subsession_stack.size})" }
      else
        DEBUGGER__.debug{ "Enter subsession" }
        stop_all_threads
        @process_group.lock
      end

      @subsession_stack << true
    end

leave_subsession(type) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1722
            def leave_subsession type
      raise '[BUG] leave_subsession: not entered' if @subsession_stack.empty?
      @subsession_stack.pop

      if @subsession_stack.empty?
        DEBUGGER__.debug{ "Leave subsession" }
        @process_group.unlock
        restart_all_threads
      else
        DEBUGGER__.debug{ "Leave subsession (nested #{@subsession_stack.size})" }
      end

      request_tc type if type
      @tc = nil
    rescue Exception => e
      STDERR.puts PP.pp([e, e.backtrace], ''.dup)
      raise
    end

parse_break(type, arg) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1431
            def parse_break type, arg
      mode = :sig
      expr = Hash.new{|h, k| h[k] = []}
      arg.split(' ').each{|w|
        if BREAK_KEYWORDS.any?{|pat| w == pat}
          mode = w[0..-2].to_sym
        else
          expr[mode] << w
        end
      }
      expr.default_proc = nil
      expr = expr.transform_values{|v| v.join(' ')}

      if (path = expr[:path]) && path =~ /\A\/(.*)\/\z/
        expr[:path] = Regexp.compile($1)
      end

      if expr[:do] || expr[:pre]
        check_unsafe
        expr[:cmd] = [type, expr[:pre], expr[:do]]
      end

      expr
    end

register_command(*names, repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true, &b) click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 458
            def register_command *names,
                                 repeat: false, unsafe: true, cancel_auto_continue: false, postmortem: true,
                                 &b
      cmd = SessionCommand.new(b, repeat, unsafe, cancel_auto_continue, postmortem)

      names.each{|name|
        @commands[name] = cmd
      }
    end

restart_all_threads() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1699
            def restart_all_threads
      stopper = @thread_stopper
      stopper.disable if stopper.enabled?

      waiting_thread_clients.each{|tc|
        next if @tc == tc
        tc << :continue
      }
    end

running_thread_clients_count() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1664
            def running_thread_clients_count
      @th_clients.count{|th, tc|
        next if tc.management?
        next unless tc.running?
        true
      }
    end

stop_all_threads() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1692
            def stop_all_threads
      return if running_thread_clients_count == 0

      stopper = @thread_stopper
      stopper.enable unless stopper.enabled?
    end

thread_stopper() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1680
            def thread_stopper
      TracePoint.new(:line) do
        # run on each thread
        tc = ThreadClient.current
        next if tc.management?
        next unless tc.running?
        next if tc == @tc

        tc.on_pause
      end
    end

waiting_thread_clients() click to toggle source

    # File debug-1.10.0/lib/debug/session.rb, line 1672
            def waiting_thread_clients
      @th_clients.map{|th, tc|
        next if tc.management?
        next unless tc.waiting?
        tc
      }.compact
    end