# class Gem::SafeMarshal::Visitors::ToRuby

### Constants

COMPAT_CLASSES

### Public Class Methods

new (permitted_classes:, permitted_symbols:, permitted_ivars:)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 8
    def initialize(permitted_classes:, permitted_symbols:, permitted_ivars:)
      @permitted_classes = permitted_classes
      @permitted_symbols = ["E"].concat(permitted_symbols).concat(permitted_classes)
      @permitted_ivars = permitted_ivars

      @objects = []
      @symbols = []
      @class_cache = {}

      @stack = ["root"]
      @stack_idx = 1
    end

### Public Instance Methods

visit (target)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 26
    def visit(target)
      stack_idx = @stack_idx
      super
    ensure
      @stack_idx = stack_idx - 1
    end

Calls superclass method

### Private Instance Methods

call_method (receiver, method, *args)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 357
    def call_method(receiver, method, *args)
      receiver.__send__(method, *args)
    rescue NoMethodError => e
      raise unless e.receiver == receiver

      raise MethodCallError, "Unable to call #{method.inspect} on #{receiver.inspect}, perhaps it is a class using marshal compat, which is not visible in ruby? #{e}"
    end

formatted_stack ()
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 365
    def formatted_stack
      formatted = []
      @stack[0, @stack_idx].each do |e|
        if e.is_a?(Integer)
          if formatted.last == "ivar_"
            formatted[-1] = "ivar_#{e}"
          else
            formatted << "[#{e}]"
          end
        else
          formatted << e
        end
      end
      formatted
    end

map_ivars (klass, ivars)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 63
    def map_ivars(klass, ivars)
      stack_idx = @stack_idx
      ivars.map.with_index do |(k, v), i|
        @stack_idx = stack_idx

        push_stack "ivar_"
        push_stack i
        k = resolve_ivar(klass, k)

        @stack_idx = stack_idx
        push_stack k

        next k, visit(v)
      end
    end

push_stack (element)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 35
    def push_stack(element)
      @stack[@stack_idx] = element
      @stack_idx += 1
    end

register_object (o)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 352
    def register_object(o)
      @objects << o
      o
    end

resolve_class (n)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 281
    def resolve_class(n)
      @class_cache[n] ||= begin
        to_s = resolve_symbol_name(n)
        raise UnpermittedClassError.new(name: to_s, stack: formatted_stack) unless @permitted_classes.include?(to_s)
        visit_symbol_type(n)
        begin
          ::Object.const_get(to_s)
        rescue NameError
          raise ArgumentError, "Undefined class #{to_s.inspect}"
        end
      end
    end

resolve_ivar (klass, name)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 308
    def resolve_ivar(klass, name)
      to_s = resolve_symbol_name(name)

      raise UnpermittedIvarError.new(symbol: to_s, klass: klass, stack: formatted_stack) unless @permitted_ivars.fetch(klass.name, [].freeze).include?(to_s)

      visit_symbol_type(name)
    end

resolve_symbol_name (element)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 329
    def resolve_symbol_name(element)
      case element
      when Elements::Symbol
        element.name
      when Elements::SymbolLink
        visit_Gem_SafeMarshal_Elements_SymbolLink(element).name
      else
        raise FormatError, "Expected symbol or symbol link, got #{element.inspect} @ #{formatted_stack.join(".")}"
      end
    end

visit_Gem_SafeMarshal_Elements_Array (a)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 40
    def visit_Gem_SafeMarshal_Elements_Array(a)
      array = register_object([])

      elements = a.elements
      size = elements.size
      idx = 0
      # not idiomatic, but there's a huge number of IMEMOs allocated here, so we avoid the block
      # because this is such a hot path when doing a bundle install with the full index
      while idx < size
        push_stack idx
        array << visit(elements[idx])
        idx += 1
      end

      array
    end

visit_Gem_SafeMarshal_Elements_Bignum (b)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 241
    def visit_Gem_SafeMarshal_Elements_Bignum(b)
      result = 0
      b.data.each_byte.with_index do |byte, exp|
        result += (byte * 2**(exp * 8))
      end

      case b.sign
      when 43 # ?+
        result
      when 45 # ?-
        -result
      else
        raise FormatError, "Unexpected sign for Bignum #{b.sign.chr.inspect} (#{b.sign})"
      end
    end

visit_Gem_SafeMarshal_Elements_False (_)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 218
    def visit_Gem_SafeMarshal_Elements_False(_)
      false
    end

visit_Gem_SafeMarshal_Elements_Float (f)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 226
    def visit_Gem_SafeMarshal_Elements_Float(f)
      register_object(
        case f.string
        when "inf"
          ::Float::INFINITY
        when "-inf"
          -::Float::INFINITY
        when "nan"
          ::Float::NAN
        else
          f.string.to_f
        end
      )
    end

visit_Gem_SafeMarshal_Elements_Hash (o)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 154
    def visit_Gem_SafeMarshal_Elements_Hash(o)
      hash = register_object({})

      o.pairs.each_with_index do |(k, v), i|
        push_stack i
        k = visit(k)
        push_stack k
        hash[k] = visit(v)
      end

      hash
    end

visit_Gem_SafeMarshal_Elements_HashWithDefaultValue (o)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 167
    def visit_Gem_SafeMarshal_Elements_HashWithDefaultValue(o)
      hash = visit_Gem_SafeMarshal_Elements_Hash(o)
      push_stack :default
      hash.default = visit(o.default)
      hash
    end

visit_Gem_SafeMarshal_Elements_Integer (i)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 206
    def visit_Gem_SafeMarshal_Elements_Integer(i)
      i.int
    end

visit_Gem_SafeMarshal_Elements_Nil (_)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 210
    def visit_Gem_SafeMarshal_Elements_Nil(_)
      nil
    end

visit_Gem_SafeMarshal_Elements_Object (o)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 174
    def visit_Gem_SafeMarshal_Elements_Object(o)
      register_object(resolve_class(o.name).allocate)
    end

visit_Gem_SafeMarshal_Elements_ObjectLink (o)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 178
    def visit_Gem_SafeMarshal_Elements_ObjectLink(o)
      @objects.fetch(o.offset)
    end

visit_Gem_SafeMarshal_Elements_String (s)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 222
    def visit_Gem_SafeMarshal_Elements_String(s)
      register_object(+s.str)
    end

visit_Gem_SafeMarshal_Elements_Symbol (s)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 57
    def visit_Gem_SafeMarshal_Elements_Symbol(s)
      name = s.name
      raise UnpermittedSymbolError.new(symbol: name, stack: formatted_stack) unless @permitted_symbols.include?(name)
      visit_symbol_type(s)
    end

visit_Gem_SafeMarshal_Elements_SymbolLink (o)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 182
    def visit_Gem_SafeMarshal_Elements_SymbolLink(o)
      @symbols.fetch(o.offset)
    end

visit_Gem_SafeMarshal_Elements_True (_)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 214
    def visit_Gem_SafeMarshal_Elements_True(_)
      true
    end

visit_Gem_SafeMarshal_Elements_UserClass (r)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 257
    def visit_Gem_SafeMarshal_Elements_UserClass(r)
      if resolve_class(r.name) == ::Hash && r.wrapped_object.is_a?(Elements::Hash)

        hash = register_object({}.compare_by_identity)

        o = r.wrapped_object
        o.pairs.each_with_index do |(k, v), i|
          push_stack i
          k = visit(k)
          push_stack k
          hash[k] = visit(v)
        end

        if o.is_a?(Elements::HashWithDefaultValue)
          push_stack :default
          hash.default = visit(o.default)
        end

        hash
      else
        raise UnsupportedError.new("Unsupported user class #{resolve_class(r.name)} in marshal stream", stack: formatted_stack)
      end
    end

visit_Gem_SafeMarshal_Elements_UserDefined (o)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 186
    def visit_Gem_SafeMarshal_Elements_UserDefined(o)
      register_object(call_method(resolve_class(o.name), :_load, o.binary_string))
    end

visit_Gem_SafeMarshal_Elements_UserMarshal (o)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 190
    def visit_Gem_SafeMarshal_Elements_UserMarshal(o)
      klass = resolve_class(o.name)
      compat = COMPAT_CLASSES.fetch(klass, nil)
      idx = @objects.size
      object = register_object(call_method(compat || klass, :allocate))

      push_stack :data
      ret = call_method(object, :marshal_load, visit(o.data))

      if compat
        object = @objects[idx] = ret
      end

      object
    end

visit_Gem_SafeMarshal_Elements_WithIvars (e)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 79
    def visit_Gem_SafeMarshal_Elements_WithIvars(e)
      object_offset = @objects.size
      push_stack "object"
      object = visit(e.object)
      ivars = map_ivars(object.class, e.ivars)

      case e.object
      when Elements::UserDefined
        if object.class == ::Time
          internal = []

          ivars.reject! do |k, v|
            case k
            when :offset, :zone, :nano_num, :nano_den, :submicro
              internal << [k, v]
              true
            else
              false
            end
          end

          s = e.object.binary_string
          # 122 is the largest integer that can be represented in marshal in a single byte
          raise TimeTooLargeError.new("binary string too large", stack: formatted_stack) if s.bytesize > 122

          marshal_string = "\x04\bIu:\tTime".b
          marshal_string.concat(s.bytesize + 5)
          marshal_string << s
          # internal is limited to 5, so no overflow is possible
          marshal_string.concat(internal.size + 5)

          internal.each do |k, v|
            k = k.name
            # ivar name can't be too large because only known ivars are in the internal ivars list
            marshal_string.concat(":")
            marshal_string.concat(k.bytesize + 5)
            marshal_string.concat(k)
            dumped = Marshal.dump(v)
            dumped[0, 2] = ""
            marshal_string.concat(dumped)
          end

          object = @objects[object_offset] = Marshal.load(marshal_string)
        end
      when Elements::String
        enc = nil

        ivars.reject! do |k, v|
          case k
          when :E
            case v
            when TrueClass
              enc = "UTF-8"
            when FalseClass
              enc = "US-ASCII"
            else
              raise FormatError, "Unexpected value for String :E #{v.inspect}"
            end
          when :encoding
            enc = v
          else
            next false
          end
          true
        end

        object.force_encoding(enc) if enc
      end

      ivars.each do |k, v|
        object.instance_variable_set k, v
      end
      object
    end

visit_symbol_type (element)
Source

    # File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 316
    def visit_symbol_type(element)
      case element
      when Elements::Symbol
        sym = element.name.to_sym
        @symbols << sym
        sym
      when Elements::SymbolLink
        visit_Gem_SafeMarshal_Elements_SymbolLink(element)
      end
    end