class BasicObject def __marshal__(ms, strip_ivars = false) out = ms.serialize_extended_object self out << "o" cls = Rubinius::Type.object_class self name = Rubinius::Type.module_inspect cls out << ms.serialize(name.to_sym) out << ms.serialize_instance_variables_suffix(self, true, strip_ivars) end end class Class def __marshal__(ms) if Rubinius::Type.singleton_class_object(self) raise TypeError, "singleton class can't be dumped" elsif name.nil? || name.empty? raise TypeError, "can't dump anonymous module #{self}" end "c#{ms.serialize_integer(name.length)}#{name}" end end class Module def __marshal__(ms) raise TypeError, "can't dump anonymous module #{self}" if name.nil? || name.empty? "m#{ms.serialize_integer(name.length)}#{name}" end end class Float def __marshal__(ms) if nan? str = "nan" elsif zero? str = (1.0 / self) < 0 ? '-0' : '0' elsif infinite? str = self < 0 ? "-inf" : "inf" else s, decimal, sign, digits = dtoa if decimal < -3 or decimal > digits str = s.insert(1, ".") << "e#{decimal - 1}" elsif decimal > 0 str = s[0, decimal] digits -= decimal str << ".#{s[decimal, digits]}" if digits > 0 else str = "0." str << "0" * -decimal if decimal != 0 str << s[0, digits] end end sl = str.length if sign == 1 ss = "-" sl += 1 end Rubinius::Type.binary_string("f#{ms.serialize_integer(sl)}#{ss}#{str}") end end class Exception def __marshal__(ms) out = ms.serialize_extended_object self out << "o" cls = Rubinius::Type.object_class self name = Rubinius::Type.module_inspect cls out << ms.serialize(name.to_sym) out << ms.serialize_fixnum(2) out << ms.serialize(:mesg) out << ms.serialize(@reason_message) out << ms.serialize(:bt) out << ms.serialize(backtrace) out end end class Time def __custom_marshal__(ms) out = Rubinius::Type.binary_string("") # Order matters. extra_values = {} extra_values[:offset] = gmt_offset unless gmt? extra_values[:zone] = zone if nsec > 0 # MRI serializes nanoseconds as a Rational using an # obscure and implementation-dependent method. # To keep compatibility we can just put nanoseconds # in the numerator and set the denominator to 1. extra_values[:nano_num] = nsec extra_values[:nano_den] = 1 end ivars = ms.serializable_instance_variables(self, false) out << Rubinius::Type.binary_string("I") out << Rubinius::Type.binary_string("u#{ms.serialize(self.class.name.to_sym)}") str = _dump out << ms.serialize_integer(str.length) + str count = ivars.size + extra_values.size out << ms.serialize_integer(count) ivars.each do |ivar| sym = ivar.to_sym val = __instance_variable_get__(sym) out << ms.serialize(sym) out << ms.serialize(val) end extra_values.each_pair do |key, value| out << ms.serialize(key) out << ms.serialize(value) end out end end module Marshal class State def serialize_encoding?(obj) enc = Rubinius::Type.object_encoding(obj) enc && enc != Encoding::BINARY end def serialize_encoding(obj) case enc = Rubinius::Type.object_encoding(obj) when Encoding::US_ASCII :E.__marshal__(self) + false.__marshal__(self) when Encoding::UTF_8 :E.__marshal__(self) + true.__marshal__(self) else :encoding.__marshal__(self) + serialize_string(enc.name) end end def set_object_encoding(obj, enc) case obj when String obj.force_encoding enc when Regexp obj.source.force_encoding enc when Symbol # TODO end end def set_instance_variables(obj) construct_integer.times do ivar = get_symbol value = construct case ivar when :E if value set_object_encoding obj, Encoding::UTF_8 else set_object_encoding obj, Encoding::US_ASCII end next when :encoding if enc = Encoding.find(value) set_object_encoding obj, enc next end end obj.__instance_variable_set__ prepare_ivar(ivar), value end end def construct_string obj = get_byte_sequence Rubinius::Unsafe.set_class(obj, get_user_class) if @user_class set_object_encoding(obj, Encoding::ASCII_8BIT) store_unique_object obj end end end class Range def __marshal__(ms) super(ms, true) end end class NilClass def __marshal__(ms) Rubinius::Type.binary_string("0") end end class TrueClass def __marshal__(ms) Rubinius::Type.binary_string("T") end end class FalseClass def __marshal__(ms) Rubinius::Type.binary_string("F") end end class Symbol def __marshal__(ms) if idx = ms.find_symlink(self) Rubinius::Type.binary_string(";#{ms.serialize_integer(idx)}") else ms.add_symlink self ms.serialize_symbol(self) end end end class String def __marshal__(ms) out = ms.serialize_instance_variables_prefix(self) out << ms.serialize_extended_object(self) out << ms.serialize_user_class(self, String) out << ms.serialize_string(self) out << ms.serialize_instance_variables_suffix(self) out end end class Fixnum def __marshal__(ms) ms.serialize_integer(self, "i") end end class Bignum def __marshal__(ms) ms.serialize_bignum(self) end end class Regexp def __marshal__(ms) str = self.source out = ms.serialize_instance_variables_prefix(self) out << ms.serialize_extended_object(self) out << ms.serialize_user_class(self, Regexp) out << "/" out << ms.serialize_integer(str.length) + str out << (options & Regexp::OPTION_MASK).chr out << ms.serialize_instance_variables_suffix(self) out end end class Struct def __marshal__(ms) exclude = _attrs.map { |a| "@#{a}".to_sym } out = ms.serialize_instance_variables_prefix(self, exclude) out << ms.serialize_extended_object(self) out << "S" out << ms.serialize(self.class.name.to_sym) out << ms.serialize_integer(self.length) self.each_pair do |name, value| out << ms.serialize(name) out << ms.serialize(value) end out << ms.serialize_instance_variables_suffix(self, false, false, exclude) out end end class Array def __marshal__(ms) out = ms.serialize_instance_variables_prefix(self) out << ms.serialize_extended_object(self) out << ms.serialize_user_class(self, Array) out << "[" out << ms.serialize_integer(self.length) unless empty? each do |element| out << ms.serialize(element) end end out << ms.serialize_instance_variables_suffix(self) out end end class Hash def __marshal__(ms) raise TypeError, "can't dump hash with default proc" if default_proc excluded_ivars = %w[ @head @tail @size @previous @next @key @key_hash @value @key_hash @entries @bmp @entries @level @state @trie @compare_by_identity @default @default_proc ].map { |a| a.to_sym } out = ms.serialize_instance_variables_prefix(self, excluded_ivars) out << ms.serialize_extended_object(self) out << ms.serialize_user_class(self, Hash) out << (self.default ? "}" : "{") out << ms.serialize_integer(length) unless empty? each_pair do |key, val| out << ms.serialize(key) out << ms.serialize(val) end end out << (self.default ? ms.serialize(self.default) : '') out << ms.serialize_instance_variables_suffix(self, false, false, excluded_ivars) out end end class Time def self.__construct__(ms, data, ivar_index, has_ivar) obj = _load(data) ms.store_unique_object obj if ivar_index and has_ivar[ivar_index] ms.set_instance_variables obj has_ivar[ivar_index] = false end nano_num = obj.instance_variable_get(:@nano_num) nano_den = obj.instance_variable_get(:@nano_den) if nano_num && nano_den obj.send(:nsec=, Rational(nano_num, nano_den).to_i) end obj end end module Unmarshalable def __marshal__(ms) raise TypeError, "marshaling is undefined for class #{self.class}" end end class Method include Unmarshalable end class Proc include Unmarshalable end class IO include Unmarshalable end class MatchData include Unmarshalable end module Marshal MAJOR_VERSION = 4 MINOR_VERSION = 8 VERSION_STRING = "\x04\x08" # Here only for reference TYPE_NIL = ?0 TYPE_TRUE = ?T TYPE_FALSE = ?F TYPE_FIXNUM = ?i TYPE_EXTENDED = ?e TYPE_UCLASS = ?C TYPE_OBJECT = ?o TYPE_DATA = ?d # no specs TYPE_USERDEF = ?u TYPE_USRMARSHAL = ?U TYPE_FLOAT = ?f TYPE_BIGNUM = ?l TYPE_STRING = ?" TYPE_REGEXP = ?/ TYPE_ARRAY = ?[ TYPE_HASH = ?{ TYPE_HASH_DEF = ?} TYPE_STRUCT = ?S TYPE_MODULE_OLD = ?M # no specs TYPE_CLASS = ?c TYPE_MODULE = ?m TYPE_SYMBOL = ?: TYPE_SYMLINK = ?; TYPE_IVAR = ?I TYPE_LINK = ?@ class State def initialize(stream, depth, proc) # shared @links = Rubinius::LookupTable.new @symlinks = Rubinius::LookupTable.new @symbols = [] @objects = [] # dumping @depth = depth # loading if stream @stream = stream else @stream = nil end if stream @consumed = 2 else @consumed = 0 end @modules = nil @has_ivar = [] @proc = proc @call = true end private :initialize def const_lookup(name, type = nil) mod = Object parts = String(name).split '::' parts.each do |part| unless Rubinius::Type.const_exists?(mod, part) raise ArgumentError, "undefined class/module #{name}" end mod = Rubinius::Type.const_get(mod, part, false) end if type and not mod.instance_of? type raise ArgumentError, "#{name} does not refer to a #{type}" end mod end def add_non_immediate_object(obj) return if Rubinius::Type.object_kind_of? obj, ImmediateValue add_object(obj) end def add_object(obj) sz = @objects.size @objects[sz] = obj @links[obj.__id__] = sz end def add_symlink(obj) sz = @symlinks.size @symbols[sz] = obj @symlinks[obj.__id__] = sz end def call(obj) @proc.call obj if @proc and @call end def construct(ivar_index = nil, call_proc = true) type = consume_byte() obj = case type when 48 # ?0 nil when 84 # ?T true when 70 # ?F false when 99 # ?c construct_class when 109 # ?m construct_module when 77 # ?M construct_old_module when 105 # ?i construct_integer when 108 # ?l construct_bignum when 102 # ?f construct_float when 58 # ?: construct_symbol when 34 # ?" construct_string when 47 # ?/ construct_regexp when 91 # ?[ construct_array when 123 # ?{ construct_hash when 125 # ?} construct_hash_def when 83 # ?S construct_struct when 111 # ?o construct_object when 117 # ?u construct_user_defined ivar_index when 85 # ?U construct_user_marshal when 100 # ?d construct_data when 64 # ?@ num = construct_integer begin obj = @objects.fetch(num) return obj rescue IndexError raise ArgumentError, "dump format error (unlinked)" end when 59 # ?; num = construct_integer sym = @symbols[num] raise ArgumentError, "bad symbol" unless sym return sym when 101 # ?e @modules ||= [] name = get_symbol @modules << const_lookup(name, Module) obj = construct nil, false extend_object obj obj when 67 # ?C name = get_symbol @user_class = name construct nil, false when 73 # ?I ivar_index = @has_ivar.length @has_ivar.push true obj = construct ivar_index, false set_instance_variables obj if @has_ivar.pop obj else raise ArgumentError, "load error, unknown type #{type}" end call obj if @proc and call_proc Rubinius::Type.infect obj, @stream unless obj.frozen? obj end def construct_class obj = const_lookup(get_byte_sequence.to_sym, Class) store_unique_object obj obj end def construct_module obj = const_lookup(get_byte_sequence.to_sym, Module) store_unique_object obj obj end def construct_old_module obj = const_lookup(get_byte_sequence.to_sym) store_unique_object obj obj end def construct_array obj = [] store_unique_object obj if @user_class cls = get_user_class() if cls < Array Rubinius::Unsafe.set_class obj, cls else # This is what MRI does, it's weird. return cls.allocate end end construct_integer.times do |i| obj.__append__ construct end obj end def construct_bignum sign = consume_byte() == 45 ? -1 : 1 # ?- size = construct_integer * 2 result = 0 data = consume size (0...size).each do |exp| result += (data.getbyte(exp) * 2**(exp*8)) end obj = result * sign add_object obj obj end def construct_data name = get_symbol klass = const_lookup name, Class store_unique_object klass obj = klass.allocate # TODO ensure obj is a wrapped C pointer (T_DATA in MRI-land) store_unique_object obj unless Rubinius::Type.object_respond_to? obj, :_load_data raise TypeError, "class #{name} needs to have instance method `_load_data'" end obj._load_data construct obj end def construct_float s = get_byte_sequence if s == "nan" obj = 0.0 / 0.0 elsif s == "inf" obj = 1.0 / 0.0 elsif s == "-inf" obj = 1.0 / -0.0 else obj = s.to_f end store_unique_object obj obj end def construct_hash obj = @user_class ? get_user_class.allocate : {} store_unique_object obj construct_integer.times do original_modules = @modules @modules = nil key = construct val = construct @modules = original_modules # Use __store__ (an alias for []=) to get around subclass overrides obj.__store__ key, val end obj end def construct_hash_def obj = @user_class ? get_user_class.allocate : {} store_unique_object obj construct_integer.times do key = construct val = construct obj[key] = val end obj.default = construct obj end def construct_integer c = consume_byte() # The format appears to be a simple integer compression format # # The 0-123 cases are easy, and use one byte # We've read c as unsigned char in a way, but we need to honor # the sign bit. We do that by simply comparing with the +128 values return 0 if c == 0 return c - 5 if 4 < c and c < 128 # negative, but checked known it's instead in 2's complement return c - 251 if 252 > c and c > 127 # otherwise c (now in the 1 to 4 range) indicates how many # bytes to read to construct the value. # # Because we're operating on a small number of possible values, # it's cleaner to just unroll the calculate of each case c when 1 consume_byte when 2 consume_byte | (consume_byte << 8) when 3 consume_byte | (consume_byte << 8) | (consume_byte << 16) when 4 consume_byte | (consume_byte << 8) | (consume_byte << 16) | (consume_byte << 24) when 255 # -1 consume_byte - 256 when 254 # -2 (consume_byte | (consume_byte << 8)) - 65536 when 253 # -3 (consume_byte | (consume_byte << 8) | (consume_byte << 16)) - 16777216 # 2 ** 24 when 252 # -4 (consume_byte | (consume_byte << 8) | (consume_byte << 16) | (consume_byte << 24)) - 4294967296 else raise "Invalid integer size: #{c}" end end def construct_object name = get_symbol klass = const_lookup name, Class obj = klass.allocate raise TypeError, 'dump format error' unless Object === obj store_unique_object obj if Rubinius::Type.object_kind_of? obj, Exception set_exception_variables obj else set_instance_variables obj end obj end def construct_regexp s = get_byte_sequence if @user_class obj = get_user_class.new s, consume_byte else obj = Regexp.new s, consume_byte end store_unique_object obj end def construct_struct symbols = [] values = [] name = get_symbol store_unique_object name klass = const_lookup name, Class members = klass.members obj = klass.allocate store_unique_object obj construct_integer.times do |i| slot = get_symbol unless members[i].intern == slot raise TypeError, "struct %s is not compatible (%p for %p)" % [klass, slot, members[i]] end obj.instance_variable_set "@#{slot}", construct end obj end def construct_symbol obj = get_byte_sequence.to_sym store_unique_object obj obj end def construct_user_defined(ivar_index) name = get_symbol klass = const_lookup name, Class data = get_byte_sequence if Rubinius::Type.object_respond_to? klass, :__construct__ return klass.__construct__(self, data, ivar_index, @has_ivar) end if ivar_index and @has_ivar[ivar_index] set_instance_variables data @has_ivar[ivar_index] = false end obj = nil Rubinius.privately do obj = klass._load data end add_object obj obj end def construct_user_marshal name = get_symbol store_unique_object name klass = const_lookup name, Class obj = klass.allocate extend_object obj if @modules unless Rubinius::Type.object_respond_to_marshal_load? obj raise TypeError, "instance of #{klass} needs to have method `marshal_load'" end store_unique_object obj data = construct Rubinius.privately do obj.marshal_load data end obj end def extend_object(obj) obj.__extend__(@modules.pop) until @modules.empty? end def find_link(obj) @links[obj.__id__] end def find_symlink(obj) @symlinks[obj.__id__] end def get_byte_sequence size = construct_integer consume size end def get_user_class cls = const_lookup @user_class, Class @user_class = nil cls end def get_symbol type = consume_byte() case type when 58 # TYPE_SYMBOL @call = false obj = construct_symbol @call = true obj when 59 # TYPE_SYMLINK num = construct_integer @symbols[num] else raise ArgumentError, "expected TYPE_SYMBOL or TYPE_SYMLINK, got #{type.inspect}" end end def prepare_ivar(ivar) ivar.to_s =~ /\A@/ ? ivar : "@#{ivar}".to_sym end def serialize(obj) raise ArgumentError, "exceed depth limit" if @depth == 0 # How much depth we have left. @depth -= 1; if link = find_link(obj) str = Rubinius::Type.binary_string("@#{serialize_integer(link)}") else add_non_immediate_object obj # ORDER MATTERS. if Rubinius::Type.object_respond_to_marshal_dump? obj str = serialize_user_marshal obj elsif Rubinius::Type.object_respond_to__dump? obj str = serialize_user_defined obj else str = obj.__marshal__ self end end @depth += 1 Rubinius::Type.infect(str, obj) end def serialize_extended_object(obj) str = '' if mods = Rubinius.extended_modules(obj) mods.each do |mod| str << "e#{serialize(mod.name.to_sym)}" end end Rubinius::Type.binary_string(str) end def serializable_instance_variables(obj, exclude_ivars) ivars = Rubinius.invoke_primitive :object_instance_variables, obj ivars -= exclude_ivars if exclude_ivars ivars end def serialize_instance_variables_prefix(obj, exclude_ivars = false) ivars = serializable_instance_variables(obj, exclude_ivars) Rubinius::Type.binary_string(!ivars.empty? || serialize_encoding?(obj) ? "I" : "") end def serialize_instance_variables_suffix(obj, force=false, strip_ivars=false, exclude_ivars=false) ivars = serializable_instance_variables(obj, exclude_ivars) unless force or !ivars.empty? or serialize_encoding?(obj) return Rubinius::Type.binary_string("") end count = ivars.size if serialize_encoding?(obj) str = serialize_integer(count + 1) str << serialize_encoding(obj) else str = serialize_integer(count) end ivars.each do |ivar| sym = ivar.to_sym val = obj.__instance_variable_get__(sym) if strip_ivars str << serialize(ivar.to_s[1..-1].to_sym) else str << serialize(sym) end str << serialize(val) end Rubinius::Type.binary_string(str) end def serialize_integer(n, prefix = nil) if (!Rubinius::L64 && n.is_a?(Fixnum)) || ((n >> 31) == 0 or (n >> 31) == -1) Rubinius::Type.binary_string(prefix.to_s + serialize_fixnum(n)) else serialize_bignum(n) end end def serialize_fixnum(n) if n == 0 s = n.chr elsif n > 0 and n < 123 s = (n + 5).chr elsif n < 0 and n > -124 s = (256 + (n - 5)).chr else s = "\0" cnt = 0 4.times do s << (n & 0xff).chr n >>= 8 cnt += 1 break if n == 0 or n == -1 end s[0] = (n < 0 ? 256 - cnt : cnt).chr end Rubinius::Type.binary_string(s) end def serialize_bignum(n) str = (n < 0 ? 'l-' : 'l+') cnt = 0 num = n.abs while num != 0 str << (num & 0xff).chr num >>= 8 cnt += 1 end if cnt % 2 == 1 str << "\0" cnt += 1 end Rubinius::Type.binary_string(str[0..1] + serialize_fixnum(cnt / 2) + str[2..-1]) end def serialize_symbol(obj) str = obj.to_s mf = "I" unless str.ascii_only? if mf if Rubinius::Type.object_encoding(obj).equal? Encoding::BINARY me = serialize_integer(0) elsif serialize_encoding?(obj) me = serialize_integer(1) + serialize_encoding(obj.encoding) end end mi = serialize_integer(str.bytesize) s = Rubinius::Type.binary_string str Rubinius::Type.binary_string("#{mf}:#{mi}#{s}#{me}") end def serialize_string(str) output = Rubinius::Type.binary_string("\"#{serialize_integer(str.bytesize)}") output + Rubinius::Type.binary_string(str.dup) end def serialize_user_class(obj, cls) if obj.class != cls Rubinius::Type.binary_string("C#{serialize(obj.class.name.to_sym)}") else Rubinius::Type.binary_string('') end end def serialize_user_defined(obj) if Rubinius::Type.object_respond_to? obj, :__custom_marshal__ return obj.__custom_marshal__(self) end str = nil Rubinius.privately do str = obj._dump @depth end unless Rubinius::Type.object_kind_of? str, String raise TypeError, "_dump() must return string" end out = serialize_instance_variables_prefix(str) out << Rubinius::Type.binary_string("u#{serialize(obj.class.name.to_sym)}") out << serialize_integer(str.length) + str out << serialize_instance_variables_suffix(str) out end def serialize_user_marshal(obj) val = nil Rubinius.privately do val = obj.marshal_dump end add_non_immediate_object val cls = Rubinius::Type.object_class obj name = Rubinius::Type.module_inspect cls Rubinius::Type.binary_string("U#{serialize(name.to_sym)}#{val.__marshal__(self)}") end def store_unique_object(obj) if Symbol === obj add_symlink obj else add_non_immediate_object obj end obj end def set_exception_variables(obj) construct_integer.times do ivar = get_symbol value = construct case ivar when :bt obj.__instance_variable_set__ :@custom_backtrace, value when :mesg obj.__instance_variable_set__ :@reason_message, value end end end end class IOState < State def consume(bytes) @stream.read(bytes) end def consume_byte b = @stream.getbyte raise EOFError unless b b end end class StringState < State def initialize(stream, depth, prc) super stream, depth, prc if @stream @byte_array = stream.data end end private :initialize def consume(bytes) raise ArgumentError, "marshal data too short" if @consumed > @stream.bytesize data = @stream.byteslice @consumed, bytes @consumed += bytes data end def consume_byte raise ArgumentError, "marshal data too short" if @consumed >= @stream.bytesize data = @byte_array.get_byte @consumed @consumed += 1 return data end end def self.dump(obj, an_io=nil, limit=nil) unless limit if Rubinius::Type.object_kind_of? an_io, Fixnum limit = an_io an_io = nil else limit = -1 end end depth = Rubinius::Type.coerce_to limit, Fixnum, :to_int ms = State.new nil, depth, nil if an_io if !Rubinius::Type.object_respond_to? an_io, :write raise TypeError, "output must respond to write" end if Rubinius::Type.object_respond_to? an_io, :binmode an_io.binmode end end str = Rubinius::Type.binary_string(VERSION_STRING) + ms.serialize(obj) if an_io an_io.write(str) return an_io end return str end def self.load(obj, prc = nil) if Rubinius::Type.object_respond_to? obj, :to_str data = obj.to_s major = data.getbyte 0 minor = data.getbyte 1 ms = StringState.new data, nil, prc elsif Rubinius::Type.object_respond_to? obj, :read and Rubinius::Type.object_respond_to? obj, :getc ms = IOState.new obj, nil, prc major = ms.consume_byte minor = ms.consume_byte else raise TypeError, "instance of IO needed" end if major != MAJOR_VERSION or minor > MINOR_VERSION raise TypeError, "incompatible marshal file format (can't be read)\n\tformat version #{MAJOR_VERSION}.#{MINOR_VERSION} required; #{major.inspect}.#{minor.inspect} given" end ms.construct end class << self alias_method :restore, :load end end