123456789_123456789_123456789_123456789_123456789_

Class: Gem::SafeMarshal::Visitors::ToRuby

Relationships & Source Files
Namespace Children
Classes:
Exceptions:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, Visitor
Instance Chain:
self, Visitor
Inherits: Gem::SafeMarshal::Visitors::Visitor
Defined in: lib/rubygems/safe_marshal/visitors/to_ruby.rb

Constant Summary

Visitor - Inherited

DISPATCH

Class Method Summary

Instance Method Summary

Visitor - Inherited

Constructor Details

.new(permitted_classes:, permitted_symbols:, permitted_ivars:) ⇒ ToRuby

[ GitHub ]

  
# 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

Instance Method Details

#call_method(receiver, method, *args)

[ GitHub ]

  
# 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

[ GitHub ]

  
# 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

#inspect

This method is for internal use only.
[ GitHub ]

  
# File 'lib/rubygems/safe_marshal/visitors/to_ruby.rb', line 21

def inspect # :nodoc:
  format("#<%s permitted_classes: %p permitted_symbols: %p permitted_ivars: %p>",
    self.class, @permitted_classes, @permitted_symbols, @permitted_ivars)
end

#map_ivars(klass, ivars) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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)

[ GitHub ]

  
# File 'lib/rubygems/safe_marshal/visitors/to_ruby.rb', line 345

def register_object(o)
  @objects << o
  o
end

#resolve_class(n) (private)

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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)

See additional method definition at line 322.

[ GitHub ]

  
# File 'lib/rubygems/safe_marshal/visitors/to_ruby.rb', line 333

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(target)

[ GitHub ]

  
# 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

#visit_Gem_SafeMarshal_Elements_Array(a) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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(_) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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(_) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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(_) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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) (private)

[ GitHub ]

  
# 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)

[ GitHub ]

  
# 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