class Gem::SafeMarshal::Visitors::ToRuby

Constants

COMPAT_CLASSES

Public Class Methods

new(permitted_classes:, permitted_symbols:, permitted_ivars:) click to toggle 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) click to toggle source
Calls superclass method
# 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

Private Instance Methods

call_method(receiver, method, *args) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 350
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() click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 358
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) click to toggle 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) click to toggle 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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 345
def register_object(o)
  @objects << o
  o
end
resolve_class(n) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 274
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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 301
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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 322
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) click to toggle 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
  until idx == size
    push_stack idx
    array << visit(elements[idx])
    idx += 1
  end

  array
end
visit_Gem_SafeMarshal_Elements_Bignum(b) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 234
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(_) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 213
def visit_Gem_SafeMarshal_Elements_False(_)
  false
end
visit_Gem_SafeMarshal_Elements_Float(f) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 221
def visit_Gem_SafeMarshal_Elements_Float(f)
  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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 149
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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 162
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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 201
def visit_Gem_SafeMarshal_Elements_Integer(i)
  i.int
end
visit_Gem_SafeMarshal_Elements_Nil(_) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 205
def visit_Gem_SafeMarshal_Elements_Nil(_)
  nil
end
visit_Gem_SafeMarshal_Elements_Object(o) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 169
def visit_Gem_SafeMarshal_Elements_Object(o)
  register_object(resolve_class(o.name).allocate)
end
visit_Gem_SafeMarshal_Elements_String(s) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 217
def visit_Gem_SafeMarshal_Elements_String(s)
  register_object(+s.str)
end
visit_Gem_SafeMarshal_Elements_Symbol(s) click to toggle 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_True(_) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 209
def visit_Gem_SafeMarshal_Elements_True(_)
  true
end
visit_Gem_SafeMarshal_Elements_UserClass(r) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 250
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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 181
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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 185
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) click to toggle 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

      marshal_string = "\x04\bIu:\tTime".b
      marshal_string.concat(s.size + 5)
      marshal_string << s
      marshal_string.concat(internal.size + 5)

      internal.each do |k, v|
        marshal_string.concat(":")
        marshal_string.concat(k.size + 5)
        marshal_string.concat(k.to_s)
        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) click to toggle source
# File lib/rubygems/safe_marshal/visitors/to_ruby.rb, line 309
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