summaryrefslogtreecommitdiff
path: root/spec/ruby/core/marshal
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/marshal')
-rw-r--r--spec/ruby/core/marshal/dump_spec.rb638
-rw-r--r--spec/ruby/core/marshal/fixtures/classes.rb4
-rw-r--r--spec/ruby/core/marshal/fixtures/marshal_data.rb157
-rw-r--r--spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb12
-rw-r--r--spec/ruby/core/marshal/float_spec.rb2
-rw-r--r--spec/ruby/core/marshal/load_spec.rb1289
-rw-r--r--spec/ruby/core/marshal/restore_spec.rb5
-rw-r--r--spec/ruby/core/marshal/shared/load.rb900
8 files changed, 2012 insertions, 995 deletions
diff --git a/spec/ruby/core/marshal/dump_spec.rb b/spec/ruby/core/marshal/dump_spec.rb
index 8d6ba245af..9bbb7809af 100644
--- a/spec/ruby/core/marshal/dump_spec.rb
+++ b/spec/ruby/core/marshal/dump_spec.rb
@@ -1,5 +1,6 @@
-# -*- encoding: binary -*-
+# encoding: binary
require_relative '../../spec_helper'
+require_relative 'fixtures/classes'
require_relative 'fixtures/marshal_data'
describe "Marshal.dump" do
@@ -37,7 +38,7 @@ describe "Marshal.dump" do
].should be_computed_by(:dump)
end
- platform_is wordsize: 64 do
+ platform_is c_long_size: 64 do
it "dumps a positive Fixnum > 31 bits as a Bignum" do
Marshal.dump(2**31 + 1).should == "\x04\bl+\a\x01\x00\x00\x80"
end
@@ -46,6 +47,11 @@ describe "Marshal.dump" do
Marshal.dump(-2**31 - 1).should == "\x04\bl-\a\x01\x00\x00\x80"
end
end
+
+ it "does not use object links for objects repeatedly dumped" do
+ Marshal.dump([0, 0]).should == "\x04\b[\ai\x00i\x00"
+ Marshal.dump([2**16, 2**16]).should == "\x04\b[\ai\x03\x00\x00\x01i\x03\x00\x00\x01"
+ end
end
describe "with a Symbol" do
@@ -75,9 +81,28 @@ describe "Marshal.dump" do
end
it "dumps a binary encoded Symbol" do
- s = "\u2192".force_encoding("binary").to_sym
+ s = "\u2192".dup.force_encoding("binary").to_sym
Marshal.dump(s).should == "\x04\b:\b\xE2\x86\x92"
end
+
+ it "dumps multiple Symbols sharing the same encoding" do
+ # Note that the encoding is a link for the second Symbol
+ symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET"
+ symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T"
+ value = [
+ "€a".dup.force_encoding(Encoding::UTF_8).to_sym,
+ "€b".dup.force_encoding(Encoding::UTF_8).to_sym
+ ]
+ Marshal.dump(value).should == "\x04\b[\a#{symbol1}#{symbol2}"
+
+ value = [*value, value[0]]
+ Marshal.dump(value).should == "\x04\b[\b#{symbol1}#{symbol2};\x00"
+ end
+
+ it "uses symbol links for objects repeatedly dumped" do
+ symbol = :foo
+ Marshal.dump([symbol, symbol]).should == "\x04\b[\a:\bfoo;\x00" # ;\x00 is a link to the symbol object
+ end
end
describe "with an object responding to #marshal_dump" do
@@ -89,17 +114,54 @@ describe "Marshal.dump" do
UserMarshal.should_not_receive(:name)
Marshal.dump(UserMarshal.new)
end
+
+ it "raises TypeError if an Object is an instance of an anonymous class" do
+ -> { Marshal.dump(Class.new(UserMarshal).new) }.should.raise(TypeError, /can't dump anonymous class/)
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ obj = UserMarshal.new
+ Marshal.dump([obj, obj]).should == "\x04\b[\aU:\x10UserMarshal:\tdata@\x06" # @\x06 is a link to the object
+ end
+
+ it "adds instance variables of a dumped object after the object itself into the objects table" do
+ value = "<foo>"
+ obj = MarshalSpec::UserMarshalDumpWithIvar.new("string", value)
+
+ # expect a link to the object (@\x06, that means Integer 1) is smaller than a link
+ # to the instance variable value (@\t, that means Integer 4)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bU:)MarshalSpec::UserMarshalDumpWithIvarI[\x06\"\vstring\x06:\t@foo\"\n<foo>@\x06@\t"
+ end
end
describe "with an object responding to #_dump" do
- it "dumps the object returned by #marshal_dump" do
+ it "dumps the String returned by #_dump" do
Marshal.dump(UserDefined.new).should == "\004\bu:\020UserDefined\022\004\b[\a:\nstuff;\000"
end
+ it "dumps the String in non US-ASCII and non UTF-8 encoding" do
+ object = UserDefinedString.new("a".encode("windows-1251"))
+ Marshal.dump(object).should == "\x04\bIu:\x16UserDefinedString\x06a\x06:\rencoding\"\x11Windows-1251"
+ end
+
+ it "dumps the String in multibyte encoding" do
+ object = UserDefinedString.new("a".encode("utf-32le"))
+ Marshal.dump(object).should == "\x04\bIu:\x16UserDefinedString\ta\x00\x00\x00\x06:\rencoding\"\rUTF-32LE"
+ end
+
+ it "ignores overridden name method" do
+ obj = MarshalSpec::UserDefinedWithOverriddenName.new
+ Marshal.dump(obj).should == "\x04\bu:/MarshalSpec::UserDefinedWithOverriddenName\x12\x04\b[\a:\nstuff;\x00"
+ end
+
it "raises a TypeError if _dump returns a non-string" do
m = mock("marshaled")
m.should_receive(:_dump).and_return(0)
- -> { Marshal.dump(m) }.should raise_error(TypeError)
+ -> { Marshal.dump(m) }.should.raise(TypeError)
+ end
+
+ it "raises TypeError if an Object is an instance of an anonymous class" do
+ -> { Marshal.dump(Class.new(UserDefined).new) }.should.raise(TypeError, /can't dump anonymous class/)
end
it "favors marshal_dump over _dump" do
@@ -108,6 +170,48 @@ describe "Marshal.dump" do
m.should_not_receive(:_dump)
Marshal.dump(m)
end
+
+ it "indexes instance variables of a String returned by #_dump at first and then indexes the object itself" do
+ class MarshalSpec::M1::A
+ def _dump(level)
+ s = +"<dump>"
+ s.instance_variable_set(:@foo, "bar")
+ s
+ end
+ end
+
+ a = MarshalSpec::M1::A.new
+
+ # 0-based index of the object a = 2, that is encoded as \x07 and printed as "\a" character.
+ # Objects are serialized in the following order: Array, a, "bar".
+ # But they are indexed in different order: Array (index=0), "bar" (index=1), a (index=2)
+ # So the second occurenc of the object a is encoded as an index 2.
+ reference = "@\a"
+ Marshal.dump([a, a]).should == "\x04\b[\aIu:\x17MarshalSpec::M1::A\v<dump>\x06:\t@foo\"\bbar#{reference}"
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ obj = UserDefined.new
+ Marshal.dump([obj, obj]).should == "\x04\b[\au:\x10UserDefined\x12\x04\b[\a:\nstuff;\x00@\x06" # @\x06 is a link to the object
+ end
+
+ it "adds instance variables of a dumped String before the object itself into the objects table" do
+ value = "<foo>"
+ obj = MarshalSpec::UserDefinedDumpWithIVars.new(+"string", value)
+
+ # expect a link to the object (@\a, that means Integer 2) is greater than a link
+ # to the instance variable value (@\x06, that means Integer 1)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bIu:*MarshalSpec::UserDefinedDumpWithIVars\vstring\x06:\t@foo\"\n<foo>@\a@\x06"
+ end
+
+ describe "Core library classes with #_dump returning a String with instance variables" do
+ it "indexes instance variables and then a Time object itself" do
+ t = Time.utc(2022)
+ reference = "@\a"
+
+ Marshal.dump([t, t]).should == "\x04\b[\aIu:\tTime\r \x80\x1E\xC0\x00\x00\x00\x00\x06:\tzoneI\"\bUTC\x06:\x06EF#{reference}"
+ end
+ end
end
describe "with a Class" do
@@ -123,12 +227,28 @@ describe "Marshal.dump" do
Marshal.dump(UserDefined::Nested).should == "\004\bc\030UserDefined::Nested"
end
+ it "ignores overridden name method" do
+ Marshal.dump(MarshalSpec::ClassWithOverriddenName).should == "\x04\bc)MarshalSpec::ClassWithOverriddenName"
+ end
+
+ ruby_version_is "4.0" do
+ it "dumps a class with multibyte characters in name" do
+ source_object = eval("MarshalSpec::MultibyteぁあぃいClass".dup.force_encoding(Encoding::UTF_8))
+ Marshal.dump(source_object).should == "\x04\bIc,MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Class\x06:\x06ET"
+ Marshal.load(Marshal.dump(source_object)) == source_object
+ end
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ Marshal.dump([String, String]).should == "\x04\b[\ac\vString@\x06" # @\x06 is a link to the object
+ end
+
it "raises TypeError with an anonymous Class" do
- -> { Marshal.dump(Class.new) }.should raise_error(TypeError)
+ -> { Marshal.dump(Class.new) }.should.raise(TypeError, /can't dump anonymous class/)
end
it "raises TypeError with a singleton Class" do
- -> { Marshal.dump(class << self; self end) }.should raise_error(TypeError)
+ -> { Marshal.dump(class << self; self end) }.should.raise(TypeError)
end
end
@@ -137,8 +257,24 @@ describe "Marshal.dump" do
Marshal.dump(Marshal).should == "\004\bm\fMarshal"
end
+ it "ignores overridden name method" do
+ Marshal.dump(MarshalSpec::ModuleWithOverriddenName).should == "\x04\bc*MarshalSpec::ModuleWithOverriddenName"
+ end
+
+ ruby_version_is "4.0" do
+ it "dumps a module with multibyte characters in name" do
+ source_object = eval("MarshalSpec::MultibyteけげこごModule".dup.force_encoding(Encoding::UTF_8))
+ Marshal.dump(source_object).should == "\x04\bIm-MarshalSpec::Multibyte\xE3\x81\x91\xE3\x81\x92\xE3\x81\x93\xE3\x81\x94Module\x06:\x06ET"
+ Marshal.load(Marshal.dump(source_object)) == source_object
+ end
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ Marshal.dump([Marshal, Marshal]).should == "\x04\b[\am\fMarshal@\x06" # @\x06 is a link to the object
+ end
+
it "raises TypeError with an anonymous Module" do
- -> { Marshal.dump(Module.new) }.should raise_error(TypeError)
+ -> { Marshal.dump(Module.new) }.should.raise(TypeError, /can't dump anonymous module/)
end
end
@@ -155,6 +291,23 @@ describe "Marshal.dump" do
[Marshal, nan_value, "\004\bf\bnan"],
].should be_computed_by(:dump)
end
+
+ it "may or may not use object links for objects repeatedly dumped" do
+ # it's an MRI implementation detail - on x86 architecture object links
+ # aren't used for Float values but on amd64 - object links are used
+
+ dump = Marshal.dump([0.0, 0.0])
+ ["\x04\b[\af\x060@\x06", "\x04\b[\af\x060f\x060"].should.include?(dump)
+
+ # if object links aren't used - entries in the objects table are still
+ # occupied by Float values
+ if dump == "\x04\b[\af\x060f\x060"
+ s = "string"
+ # an index of "string" ("@\b") in the object table equals 3 (`"\b".ord - 5`),
+ # so `0.0, 0,0` elements occupied indices 1 and 2
+ Marshal.dump([0.0, 0.0, s, s]).should == "\x04\b[\tf\x060f\x060\"\vstring@\b"
+ end
+ end
end
describe "with a Bignum" do
@@ -171,15 +324,81 @@ describe "Marshal.dump" do
[Marshal, -2**64, "\004\bl-\n\000\000\000\000\000\000\000\000\001\000"],
].should be_computed_by(:dump)
end
+
+ it "uses object links for objects repeatedly dumped" do
+ n = 2**64
+ Marshal.dump([n, n]).should == "\x04\b[\al+\n\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00@\x06" # @\x06 is a link to the object
+ end
+
+ it "increases the object links counter" do
+ obj = Object.new
+ object_1_link = "\x06" # representing of (0-based) index=1 (by adding 5 for small Integers)
+ object_2_link = "\x07" # representing of index=2
+
+ # objects: Array, Object, Object
+ Marshal.dump([obj, obj]).should == "\x04\b[\ao:\vObject\x00@#{object_1_link}"
+
+ # objects: Array, Bignum, Object, Object
+ Marshal.dump([2**64, obj, obj]).should == "\x04\b[\bl+\n\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00o:\vObject\x00@#{object_2_link}"
+ Marshal.dump([2**48, obj, obj]).should == "\x04\b[\bl+\t\x00\x00\x00\x00\x00\x00\x01\x00o:\vObject\x00@#{object_2_link}"
+ Marshal.dump([2**32, obj, obj]).should == "\x04\b[\bl+\b\x00\x00\x00\x00\x01\x00o:\vObject\x00@#{object_2_link}"
+ end
+ end
+
+ describe "with a Rational" do
+ it "dumps a Rational" do
+ Marshal.dump(Rational(2, 3)).should == "\x04\bU:\rRational[\ai\ai\b"
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ r = Rational(2, 3)
+ Marshal.dump([r, r]).should == "\x04\b[\aU:\rRational[\ai\ai\b@\x06" # @\x06 is a link to the object
+ end
+ end
+
+ describe "with a Complex" do
+ it "dumps a Complex" do
+ Marshal.dump(Complex(2, 3)).should == "\x04\bU:\fComplex[\ai\ai\b"
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ c = Complex(2, 3)
+ Marshal.dump([c, c]).should == "\x04\b[\aU:\fComplex[\ai\ai\b@\x06" # @\x06 is a link to the object
+ end
+ end
+
+ describe "with a Data" do
+ it "dumps a Data" do
+ Marshal.dump(MarshalSpec::DataSpec::Measure.new(100, 'km')).should == "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm"
+ end
+
+ it "dumps an extended Data" do
+ obj = MarshalSpec::DataSpec::MeasureExtended.new(100, "km")
+ Marshal.dump(obj).should == "\x04\bS:+MarshalSpec::DataSpec::MeasureExtended\a:\vamountii:\tunit\"\akm"
+ end
+
+ it "ignores overridden name method" do
+ obj = MarshalSpec::DataSpec::MeasureWithOverriddenName.new(100, "km")
+ Marshal.dump(obj).should == "\x04\bS:5MarshalSpec::DataSpec::MeasureWithOverriddenName\a:\vamountii:\tunit\"\akm"
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ d = MarshalSpec::DataSpec::Measure.new(100, 'km')
+ Marshal.dump([d, d]).should == "\x04\b[\aS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm@\x06" # @\x06 is a link to the object
+ end
+
+ it "raises TypeError with an anonymous Struct" do
+ -> { Marshal.dump(Data.define(:a).new(1)) }.should.raise(TypeError, /can't dump anonymous class/)
+ end
end
describe "with a String" do
it "dumps a blank String" do
- Marshal.dump("".force_encoding("binary")).should == "\004\b\"\000"
+ Marshal.dump("".dup.force_encoding("binary")).should == "\004\b\"\000"
end
it "dumps a short String" do
- Marshal.dump("short".force_encoding("binary")).should == "\004\b\"\012short"
+ Marshal.dump("short".dup.force_encoding("binary")).should == "\004\b\"\012short"
end
it "dumps a long String" do
@@ -187,7 +406,7 @@ describe "Marshal.dump" do
end
it "dumps a String extended with a Module" do
- Marshal.dump("".extend(Meths).force_encoding("binary")).should == "\004\be:\nMeths\"\000"
+ Marshal.dump("".dup.extend(Meths).force_encoding("binary")).should == "\004\be:\nMeths\"\000"
end
it "dumps a String subclass" do
@@ -198,24 +417,29 @@ describe "Marshal.dump" do
Marshal.dump(UserString.new.extend(Meths).force_encoding("binary")).should == "\004\be:\nMethsC:\017UserString\"\000"
end
+ it "ignores overridden name method when dumps a String subclass" do
+ obj = MarshalSpec::StringWithOverriddenName.new
+ Marshal.dump(obj).should == "\x04\bC:*MarshalSpec::StringWithOverriddenName\"\x00"
+ end
+
it "dumps a String with instance variables" do
- str = ""
+ str = +""
str.instance_variable_set("@foo", "bar")
Marshal.dump(str.force_encoding("binary")).should == "\x04\bI\"\x00\x06:\t@foo\"\bbar"
end
it "dumps a US-ASCII String" do
- str = "abc".force_encoding("us-ascii")
+ str = "abc".dup.force_encoding("us-ascii")
Marshal.dump(str).should == "\x04\bI\"\babc\x06:\x06EF"
end
it "dumps a UTF-8 String" do
- str = "\x6d\xc3\xb6\x68\x72\x65".force_encoding("utf-8")
+ str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8")
Marshal.dump(str).should == "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET"
end
it "dumps a String in another encoding" do
- str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".force_encoding("utf-16le")
+ str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le")
result = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE"
Marshal.dump(str).should == result
end
@@ -223,6 +447,21 @@ describe "Marshal.dump" do
it "dumps multiple strings using symlinks for the :E (encoding) symbol" do
Marshal.dump(["".encode("us-ascii"), "".encode("utf-8")]).should == "\x04\b[\aI\"\x00\x06:\x06EFI\"\x00\x06;\x00T"
end
+
+ it "uses object links for objects repeatedly dumped" do
+ s = "string"
+ Marshal.dump([s, s]).should == "\x04\b[\a\"\vstring@\x06" # @\x06 is a link to the object
+ end
+
+ it "adds instance variables after the object itself into the objects table" do
+ obj = +"string"
+ value = "<foo>"
+ obj.instance_variable_set :@foo, value
+
+ # expect a link to the object (@\x06, that means Integer 1) is smaller than a link
+ # to the instance variable value (@\a, that means Integer 2)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bI\"\vstring\x06:\t@foo\"\n<foo>@\x06@\a"
+ end
end
describe "with a Regexp" do
@@ -234,14 +473,26 @@ describe "Marshal.dump" do
Marshal.dump(//im).should == "\x04\bI/\x00\x05\x06:\x06EF"
end
- it "dumps a Regexp with instance variables" do
- o = Regexp.new("")
+ it "dumps a Regexp subclass with instance variables" do
+ o = UserRegexp.new("")
o.instance_variable_set(:@ivar, :ivar)
- Marshal.dump(o).should == "\x04\bI/\x00\x00\a:\x06EF:\n@ivar:\tivar"
+ Marshal.dump(o).should == "\x04\bIC:\x0FUserRegexp/\x00\x00\a:\x06EF:\n@ivar:\tivar"
+ end
+
+ it "dumps an extended Regexp subclass" do
+ Marshal.dump(UserRegexp.new("").extend(Meths)).should == "\x04\bIe:\nMethsC:\x0FUserRegexp/\x00\x00\x06:\x06EF"
end
- it "dumps an extended Regexp" do
- Marshal.dump(Regexp.new("").extend(Meths)).should == "\x04\bIe:\nMeths/\x00\x00\x06:\x06EF"
+ ruby_version_is ""..."4.1" do
+ it "dumps a Regexp with instance variables" do
+ o = Regexp.new("")
+ o.instance_variable_set(:@ivar, :ivar)
+ Marshal.dump(o).should == "\x04\bI/\x00\x00\a:\x06EF:\n@ivar:\tivar"
+ end
+
+ it "dumps an extended Regexp" do
+ Marshal.dump(Regexp.new("").extend(Meths)).should == "\x04\bIe:\nMeths/\x00\x00\x06:\x06EF"
+ end
end
it "dumps a Regexp subclass" do
@@ -249,18 +500,51 @@ describe "Marshal.dump" do
end
it "dumps a binary Regexp" do
- o = Regexp.new("".force_encoding("binary"), Regexp::FIXEDENCODING)
+ o = Regexp.new("".dup.force_encoding("binary"), Regexp::FIXEDENCODING)
Marshal.dump(o).should == "\x04\b/\x00\x10"
end
+ it "dumps an ascii-compatible Regexp" do
+ o = Regexp.new("a".encode("us-ascii"), Regexp::FIXEDENCODING)
+ Marshal.dump(o).should == "\x04\bI/\x06a\x10\x06:\x06EF"
+
+ o = Regexp.new("a".encode("us-ascii"))
+ Marshal.dump(o).should == "\x04\bI/\x06a\x00\x06:\x06EF"
+
+ o = Regexp.new("a".encode("windows-1251"), Regexp::FIXEDENCODING)
+ Marshal.dump(o).should == "\x04\bI/\x06a\x10\x06:\rencoding\"\x11Windows-1251"
+
+ o = Regexp.new("a".encode("windows-1251"))
+ Marshal.dump(o).should == "\x04\bI/\x06a\x00\x06:\x06EF"
+ end
+
it "dumps a UTF-8 Regexp" do
- o = Regexp.new("".force_encoding("utf-8"), Regexp::FIXEDENCODING)
+ o = Regexp.new("".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING)
Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\x06ET"
+
+ o = Regexp.new("a".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING)
+ Marshal.dump(o).should == "\x04\bI/\x06a\x10\x06:\x06ET"
+
+ o = Regexp.new("\u3042".dup.force_encoding("utf-8"), Regexp::FIXEDENCODING)
+ Marshal.dump(o).should == "\x04\bI/\b\xE3\x81\x82\x10\x06:\x06ET"
end
it "dumps a Regexp in another encoding" do
- o = Regexp.new("".force_encoding("utf-16le"), Regexp::FIXEDENCODING)
+ o = Regexp.new("".dup.force_encoding("utf-16le"), Regexp::FIXEDENCODING)
Marshal.dump(o).should == "\x04\bI/\x00\x10\x06:\rencoding\"\rUTF-16LE"
+
+ o = Regexp.new("a".encode("utf-16le"), Regexp::FIXEDENCODING)
+ Marshal.dump(o).should == "\x04\bI/\aa\x00\x10\x06:\rencoding\"\rUTF-16LE"
+ end
+
+ it "ignores overridden name method when dumps a Regexp subclass" do
+ obj = MarshalSpec::RegexpWithOverriddenName.new("")
+ Marshal.dump(obj).should == "\x04\bIC:*MarshalSpec::RegexpWithOverriddenName/\x00\x00\x06:\x06EF"
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ r = /\A.\Z/
+ Marshal.dump([r, r]).should == "\x04\b[\aI/\n\\A.\\Z\x00\x06:\x06EF@\x06" # @\x06 is a link to the object
end
end
@@ -292,6 +576,26 @@ describe "Marshal.dump" do
it "dumps an extended Array" do
Marshal.dump([].extend(Meths)).should == "\004\be:\nMeths[\000"
end
+
+ it "ignores overridden name method when dumps an Array subclass" do
+ obj = MarshalSpec::ArrayWithOverriddenName.new
+ Marshal.dump(obj).should == "\x04\bC:)MarshalSpec::ArrayWithOverriddenName[\x00"
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ a = [1]
+ Marshal.dump([a, a]).should == "\x04\b[\a[\x06i\x06@\x06" # @\x06 is a link to the object
+ end
+
+ it "adds instance variables after the object itself into the objects table" do
+ obj = []
+ value = "<foo>"
+ obj.instance_variable_set :@foo, value
+
+ # expect a link to the object (@\x06, that means Integer 1) is smaller than a link
+ # to the instance variable value (@\a, that means Integer 2)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bI[\x00\x06:\t@foo\"\n<foo>@\x06@\a"
+ end
end
describe "with a Hash" do
@@ -299,6 +603,10 @@ describe "Marshal.dump" do
Marshal.dump({}).should == "\004\b{\000"
end
+ it "dumps a non-empty Hash" do
+ Marshal.dump({a: 1}).should == "\x04\b{\x06:\x06ai\x06"
+ end
+
it "dumps a Hash subclass" do
Marshal.dump(UserHash.new).should == "\004\bC:\rUserHash{\000"
end
@@ -307,8 +615,22 @@ describe "Marshal.dump" do
Marshal.dump(Hash.new(1)).should == "\004\b}\000i\006"
end
+ it "dumps a Hash with compare_by_identity" do
+ h = {}
+ h.compare_by_identity
+
+ Marshal.dump(h).should == "\004\bC:\tHash{\x00"
+ end
+
+ it "dumps a Hash subclass with compare_by_identity" do
+ h = UserHash.new
+ h.compare_by_identity
+
+ Marshal.dump(h).should == "\x04\bC:\rUserHashC:\tHash{\x00"
+ end
+
it "raises a TypeError with hash having default proc" do
- -> { Marshal.dump(Hash.new {}) }.should raise_error(TypeError)
+ -> { Marshal.dump(Hash.new {}) }.should.raise(TypeError, "can't dump hash with default proc")
end
it "dumps a Hash with instance variables" do
@@ -324,6 +646,26 @@ describe "Marshal.dump" do
it "dumps an Hash subclass with a parameter to initialize" do
Marshal.dump(UserHashInitParams.new(1)).should == "\004\bIC:\027UserHashInitParams{\000\006:\a@ai\006"
end
+
+ it "ignores overridden name method when dumps a Hash subclass" do
+ obj = MarshalSpec::HashWithOverriddenName.new
+ Marshal.dump(obj).should == "\x04\bC:(MarshalSpec::HashWithOverriddenName{\x00"
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ h = {a: 1}
+ Marshal.dump([h, h]).should == "\x04\b[\a{\x06:\x06ai\x06@\x06" # @\x06 is a link to the object
+ end
+
+ it "adds instance variables after the object itself into the objects table" do
+ obj = {}
+ value = "<foo>"
+ obj.instance_variable_set :@foo, value
+
+ # expect a link to the object (@\x06, that means Integer 1) is smaller than a link
+ # to the instance variable value (@\a, that means Integer 2)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bI{\x00\x06:\t@foo\"\n<foo>@\x06@\a"
+ end
end
describe "with a Struct" do
@@ -343,10 +685,39 @@ describe "Marshal.dump" do
end
it "dumps an extended Struct" do
- st = Struct.new("Extended", :a, :b).new
- Marshal.dump(st.extend(Meths)).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
+ obj = Struct.new("Extended", :a, :b).new.extend(Meths)
+ Marshal.dump(obj).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
+
+ s = 'hi'
+ obj.a = [:a, s]
+ obj.b = [:Meths, s]
+ Marshal.dump(obj).should == "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
Struct.send(:remove_const, :Extended)
end
+
+ it "ignores overridden name method" do
+ obj = MarshalSpec::StructWithOverriddenName.new("member")
+ Marshal.dump(obj).should == "\x04\bS:*MarshalSpec::StructWithOverriddenName\x06:\x06a\"\vmember"
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ s = Struct::Pyramid.new
+ Marshal.dump([s, s]).should == "\x04\b[\aS:\x14Struct::Pyramid\x00@\x06" # @\x06 is a link to the object
+ end
+
+ it "raises TypeError with an anonymous Struct" do
+ -> { Marshal.dump(Struct.new(:a).new(1)) }.should.raise(TypeError, /can't dump anonymous class/)
+ end
+
+ it "adds instance variables after the object itself into the objects table" do
+ obj = Struct::Pyramid.new
+ value = "<foo>"
+ obj.instance_variable_set :@foo, value
+
+ # expect a link to the object (@\x06, that means Integer 1) is smaller than a link
+ # to the instance variable value (@\a, that means Integer 2)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bIS:\x14Struct::Pyramid\x00\x06:\t@foo\"\n<foo>@\x06@\a"
+ end
end
describe "with an Object" do
@@ -366,7 +737,7 @@ describe "Marshal.dump" do
it "dumps an Object with a non-US-ASCII instance variable" do
obj = Object.new
- ivar = "@é".force_encoding(Encoding::UTF_8).to_sym
+ ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym
obj.instance_variable_set(ivar, 1)
Marshal.dump(obj).should == "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06"
end
@@ -378,48 +749,105 @@ describe "Marshal.dump" do
Marshal.dump(obj).should == "\004\bo:\x0BObject\x00"
end
- it "dumps an Object if it has a singleton class but no singleton methods" do
+ it "dumps an Object if it has a singleton class but no singleton methods and no singleton instance variables" do
obj = Object.new
obj.singleton_class
Marshal.dump(obj).should == "\004\bo:\x0BObject\x00"
end
- it "raises if an Object has a singleton class and singleton methods" do
+ it "ignores overridden name method" do
+ obj = MarshalSpec::ClassWithOverriddenName.new
+ Marshal.dump(obj).should == "\x04\bo:)MarshalSpec::ClassWithOverriddenName\x00"
+ end
+
+ it "raises TypeError if an Object has a singleton class and singleton methods" do
obj = Object.new
def obj.foo; end
-> {
Marshal.dump(obj)
- }.should raise_error(TypeError, "singleton can't be dumped")
+ }.should.raise(TypeError, "singleton can't be dumped")
+ end
+
+ it "raises TypeError if an Object has a singleton class and singleton instance variables" do
+ obj = Object.new
+ class << obj
+ @v = 1
+ end
+
+ -> {
+ Marshal.dump(obj)
+ }.should.raise(TypeError, "singleton can't be dumped")
+ end
+
+ it "raises TypeError if an Object is an instance of an anonymous class" do
+ anonymous_class = Class.new
+ obj = anonymous_class.new
+
+ -> { Marshal.dump(obj) }.should.raise(TypeError, /can't dump anonymous class/)
+ end
+
+ it "raises TypeError if an Object extends an anonymous module" do
+ anonymous_module = Module.new
+ obj = Object.new
+ obj.extend(anonymous_module)
+
+ -> { Marshal.dump(obj) }.should.raise(TypeError, /can't dump anonymous class/)
end
it "dumps a BasicObject subclass if it defines respond_to?" do
obj = MarshalSpec::BasicObjectSubWithRespondToFalse.new
Marshal.dump(obj).should == "\x04\bo:2MarshalSpec::BasicObjectSubWithRespondToFalse\x00"
end
+
+ it "dumps without marshaling any attached finalizer" do
+ obj = Object.new
+ finalizer = Object.new
+ def finalizer.noop(_)
+ end
+ ObjectSpace.define_finalizer(obj, finalizer.method(:noop))
+ Marshal.load(Marshal.dump(obj)).class.should == Object
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ obj = Object.new
+ Marshal.dump([obj, obj]).should == "\x04\b[\ao:\vObject\x00@\x06" # @\x06 is a link to the object
+ end
+
+ it "adds instance variables after the object itself into the objects table" do
+ obj = Object.new
+ value = "<foo>"
+ obj.instance_variable_set :@foo, value
+
+ # expect a link to the object (@\x06, that means Integer 1) is smaller than a link
+ # to the instance variable value (@\a, that means Integer 2)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bo:\vObject\x06:\t@foo\"\n<foo>@\x06@\a"
+ end
end
describe "with a Range" do
- it "dumps a Range inclusive of end (with indeterminant order)" do
+ it "dumps a Range inclusive of end" do
dump = Marshal.dump(1..2)
+ dump.should == "\x04\bo:\nRange\b:\texclF:\nbegini\x06:\bendi\a"
+
load = Marshal.load(dump)
load.should == (1..2)
end
- it "dumps a Range exclusive of end (with indeterminant order)" do
+ it "dumps a Range exclusive of end" do
dump = Marshal.dump(1...2)
+ dump.should == "\x04\bo:\nRange\b:\texclT:\nbegini\x06:\bendi\a"
+
load = Marshal.load(dump)
load.should == (1...2)
end
- ruby_version_is ""..."3.0" do
- it "dumps a Range with extra instance variables" do
- range = (1...3)
- range.instance_variable_set :@foo, 42
- dump = Marshal.dump(range)
- load = Marshal.load(dump)
- load.should == range
- load.instance_variable_get(:@foo).should == 42
- end
+ it "uses object links for objects repeatedly dumped" do
+ r = 1..2
+ Marshal.dump([r, r]).should == "\x04\b[\ao:\nRange\b:\texclF:\nbegini\x06:\bendi\a@\x06" # @\x06 is a link to the object
+ end
+
+ it "raises TypeError with an anonymous Range subclass" do
+ -> { Marshal.dump(Class.new(Range).new(1, 2)) }.should.raise(TypeError, /can't dump anonymous class/)
end
end
@@ -449,7 +877,7 @@ describe "Marshal.dump" do
base = "\x04\bIu:\tTime\r#{@t_dump}\a"
offset = ":\voffseti\x020*"
zone = ":\tzoneI\"\bAST\x06:\x06EF" # Last is 'F' (US-ASCII)
- [ "#{base}#{offset}#{zone}", "#{base}#{zone}#{offset}" ].should include(dump)
+ [ "#{base}#{offset}#{zone}", "#{base}#{zone}#{offset}" ].should.include?(dump)
end
end
@@ -458,6 +886,43 @@ describe "Marshal.dump" do
zone = ":\tzoneI\"\bUTC\x06:\x06EF" # Last is 'F' (US-ASCII)
dump.should == "\x04\bIu:\tTime\r#{@utc_dump}\x06#{zone}"
end
+
+ it "ignores overridden name method" do
+ obj = MarshalSpec::TimeWithOverriddenName.new
+ Marshal.dump(obj).should.include?("MarshalSpec::TimeWithOverriddenName")
+ end
+
+ ruby_version_is "4.0" do
+ it "dumps a Time subclass with multibyte characters in name" do
+ source_object = eval("MarshalSpec::MultibyteぁあぃいTime".dup.force_encoding(Encoding::UTF_8))
+ Marshal.dump(source_object).should == "\x04\bIc+MarshalSpec::Multibyte\xE3\x81\x81\xE3\x81\x82\xE3\x81\x83\xE3\x81\x84Time\x06:\x06ET"
+ Marshal.load(Marshal.dump(source_object)) == source_object
+ end
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ # order of the offset and zone instance variables is a subject to change
+ # and may be different on different CRuby versions
+ base = Regexp.quote("\x04\b[\aIu:\tTime\r\xF5\xEF\e\x80\x00\x00\x00\x00\a")
+ offset = Regexp.quote(":\voffseti\x020*:\tzoneI\"\bAST\x06:\x06EF")
+ zone = Regexp.quote(":\tzoneI\"\bAST\x06:\x06EF:\voffseti\x020*")
+ instance_variables = /#{offset}|#{zone}/
+ Marshal.dump([@t, @t]).should =~ /\A#{base}#{instance_variables}@\a\Z/ # @\a is a link to the object
+ end
+
+ it "adds instance variables before the object itself into the objects table" do
+ obj = @utc
+ value = "<foo>"
+ obj.instance_variable_set :@foo, value
+
+ # expect a link to the object (@\b, that means Integer 3) is greater than a link
+ # to the instance variable value (@\x06, that means Integer 1)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bIu:\tTime\r \x00\x1C\xC0\x00\x00\x00\x00\a:\t@foo\"\n<foo>:\tzoneI\"\bUTC\x06:\x06EF@\b@\x06"
+ end
+
+ it "raises TypeError with an anonymous Time subclass" do
+ -> { Marshal.dump(Class.new(Time).now) }.should.raise(TypeError)
+ end
end
describe "with an Exception" do
@@ -489,15 +954,47 @@ describe "Marshal.dump" do
begin
raise RuntimeError, "the consequence"
rescue RuntimeError => e
- e.cause.should equal(cause)
+ e.cause.should.equal?(cause)
exc = e
end
end
reloaded = Marshal.load(Marshal.dump(exc))
- reloaded.cause.should be_an_instance_of(StandardError)
+ reloaded.cause.should.instance_of?(StandardError)
reloaded.cause.message.should == "the cause"
end
+
+ # NoMethodError uses an exception formatter on TruffleRuby and computes a message lazily
+ it "dumps the message for the raised NoMethodError exception" do
+ begin
+ "".foo
+ rescue => e
+ end
+
+ Marshal.dump(e).should =~ /undefined method [`']foo' for ("":String|an instance of String)/
+ end
+
+ it "uses object links for objects repeatedly dumped" do
+ e = Exception.new
+ Marshal.dump([e, e]).should == "\x04\b[\ao:\x0EException\a:\tmesg0:\abt0@\x06" # @\x\a is a link to the object
+ end
+
+ it "adds instance variables after the object itself into the objects table" do
+ obj = Exception.new
+ value = "<foo>"
+ obj.instance_variable_set :@foo, value
+
+ # expect a link to the object (@\x06, that means Integer 1) is smaller than a link
+ # to the instance variable value (@\a, that means Integer 2)
+ Marshal.dump([obj, obj, value]).should == "\x04\b[\bo:\x0EException\b:\tmesg0:\abt0:\t@foo\"\n<foo>@\x06@\a"
+ end
+
+ it "raises TypeError if an Object is an instance of an anonymous class" do
+ anonymous_class = Class.new(Exception)
+ obj = anonymous_class.new
+
+ -> { Marshal.dump(obj) }.should.raise(TypeError, /can't dump anonymous class/)
+ end
end
it "dumps subsequent appearances of a symbol as a link" do
@@ -517,10 +1014,10 @@ describe "Marshal.dump" do
it "raises an ArgumentError when the recursion limit is exceeded" do
h = {'one' => {'two' => {'three' => 0}}}
- -> { Marshal.dump(h, 3) }.should raise_error(ArgumentError)
- -> { Marshal.dump([h], 4) }.should raise_error(ArgumentError)
- -> { Marshal.dump([], 0) }.should raise_error(ArgumentError)
- -> { Marshal.dump([[[]]], 1) }.should raise_error(ArgumentError)
+ -> { Marshal.dump(h, 3) }.should.raise(ArgumentError)
+ -> { Marshal.dump([h], 4) }.should.raise(ArgumentError)
+ -> { Marshal.dump([], 0) }.should.raise(ArgumentError)
+ -> { Marshal.dump([[[]]], 1) }.should.raise(ArgumentError)
end
it "ignores the recursion limit if the limit is negative" do
@@ -530,7 +1027,6 @@ describe "Marshal.dump" do
end
describe "when passed an IO" do
-
it "writes the serialized data to the IO-Object" do
(obj = mock('test')).should_receive(:write).at_least(1)
Marshal.dump("test", obj)
@@ -543,7 +1039,7 @@ describe "Marshal.dump" do
it "raises an Error when the IO-Object does not respond to #write" do
obj = mock('test')
- -> { Marshal.dump("test", obj) }.should raise_error(TypeError)
+ -> { Marshal.dump("test", obj) }.should.raise(TypeError)
end
@@ -553,62 +1049,34 @@ describe "Marshal.dump" do
obj.should_receive(:binmode).at_least(1)
Marshal.dump("test", obj)
end
-
-
end
describe "when passed a StringIO" do
it "should raise an error" do
require "stringio"
- -> { Marshal.dump(StringIO.new) }.should raise_error(TypeError)
+ -> { Marshal.dump(StringIO.new) }.should.raise(TypeError)
end
end
it "raises a TypeError if marshalling a Method instance" do
- -> { Marshal.dump(Marshal.method(:dump)) }.should raise_error(TypeError)
+ -> { Marshal.dump(Marshal.method(:dump)) }.should.raise(TypeError)
end
it "raises a TypeError if marshalling a Proc" do
- -> { Marshal.dump(proc {}) }.should raise_error(TypeError)
+ -> { Marshal.dump(proc {}) }.should.raise(TypeError)
end
it "raises a TypeError if dumping a IO/File instance" do
- -> { Marshal.dump(STDIN) }.should raise_error(TypeError)
- -> { File.open(__FILE__) { |f| Marshal.dump(f) } }.should raise_error(TypeError)
+ -> { Marshal.dump(STDIN) }.should.raise(TypeError)
+ -> { File.open(__FILE__) { |f| Marshal.dump(f) } }.should.raise(TypeError)
end
it "raises a TypeError if dumping a MatchData instance" do
- -> { Marshal.dump(/(.)/.match("foo")) }.should raise_error(TypeError)
+ -> { Marshal.dump(/(.)/.match("foo")) }.should.raise(TypeError)
end
it "raises a TypeError if dumping a Mutex instance" do
m = Mutex.new
- -> { Marshal.dump(m) }.should raise_error(TypeError)
- end
-
- ruby_version_is ''...'2.7' do
- it "returns an untainted string if object is untainted" do
- Marshal.dump(Object.new).tainted?.should be_false
- end
-
- it "returns a tainted string if object is tainted" do
- Marshal.dump(Object.new.taint).tainted?.should be_true
- end
-
- it "returns a tainted string if nested object is tainted" do
- Marshal.dump([[Object.new.taint]]).tainted?.should be_true
- end
-
- it "returns a trusted string if object is trusted" do
- Marshal.dump(Object.new).untrusted?.should be_false
- end
-
- it "returns an untrusted string if object is untrusted" do
- Marshal.dump(Object.new.untrust).untrusted?.should be_true
- end
-
- it "returns an untrusted string if nested object is untrusted" do
- Marshal.dump([[Object.new.untrust]]).untrusted?.should be_true
- end
+ -> { Marshal.dump(m) }.should.raise(TypeError)
end
end
diff --git a/spec/ruby/core/marshal/fixtures/classes.rb b/spec/ruby/core/marshal/fixtures/classes.rb
new file mode 100644
index 0000000000..7c81c64927
--- /dev/null
+++ b/spec/ruby/core/marshal/fixtures/classes.rb
@@ -0,0 +1,4 @@
+module MarshalSpec
+ # empty modules
+ module M1 end
+end
diff --git a/spec/ruby/core/marshal/fixtures/marshal_data.rb b/spec/ruby/core/marshal/fixtures/marshal_data.rb
index bf58f4888d..c16d9e4bb6 100644
--- a/spec/ruby/core/marshal/fixtures/marshal_data.rb
+++ b/spec/ruby/core/marshal/fixtures/marshal_data.rb
@@ -1,4 +1,7 @@
-# -*- encoding: binary -*-
+# encoding: binary
+
+require_relative 'marshal_multibyte_data'
+
class UserDefined
class Nested
def ==(other)
@@ -38,7 +41,7 @@ class UserDefinedWithIvar
attr_reader :a, :b, :c
def initialize
- @a = 'stuff'
+ @a = +'stuff'
@a.instance_variable_set :@foo, :UserDefinedWithIvar
@b = 'more'
@c = @b
@@ -78,12 +81,47 @@ class UserDefinedImmediate
end
end
+class UserDefinedString
+ attr_reader :string
+
+ def initialize(string)
+ @string = string
+ end
+
+ def _dump(depth)
+ @string
+ end
+
+ def self._load(data)
+ new(data)
+ end
+end
+
+module MarshalSpec
+ class UserDefinedDumpWithIVars
+ attr_reader :string
+
+ def initialize(string, ivar_value)
+ @string = string
+ @string.instance_variable_set(:@foo, ivar_value)
+ end
+
+ def _dump(depth)
+ @string
+ end
+
+ def self._load(data)
+ new(data)
+ end
+ end
+end
+
class UserPreviouslyDefinedWithInitializedIvar
attr_accessor :field1, :field2
end
class UserMarshal
- attr_reader :data
+ attr_accessor :data
def initialize
@data = 'stuff'
@@ -120,6 +158,32 @@ class UserMarshalWithIvar
end
end
+module MarshalSpec
+ class UserMarshalDumpWithIvar
+ attr_reader :data
+
+ def initialize(data, ivar_value)
+ @data = data
+ @ivar_value = ivar_value
+ end
+
+ def marshal_dump
+ obj = [data]
+ obj.instance_variable_set(:@foo, @ivar_value)
+ obj
+ end
+
+ def marshal_load(o)
+ @data = o[0]
+ end
+
+ def ==(other)
+ self.class === other and
+ @data = other.data
+ end
+ end
+end
+
class UserArray < Array
end
@@ -167,12 +231,17 @@ module MarshalSpec
end
end
+ StructToDump = Struct.new(:a, :b)
+
class BasicObjectSubWithRespondToFalse < BasicObject
def respond_to?(method_name, include_all=false)
false
end
end
+ module ModuleToExtendBy
+ end
+
def self.random_data
randomizer = Random.new(42)
1000.times{randomizer.rand} # Make sure we exhaust his first state of 624 random words
@@ -192,6 +261,70 @@ module MarshalSpec
set_swapped_class(nil)
end
+ class ClassWithOverriddenName
+ def self.name
+ "Foo"
+ end
+ end
+
+ class ModuleWithOverriddenName
+ def self.name
+ "Foo"
+ end
+ end
+
+ class TimeWithOverriddenName < Time
+ def self.name
+ "Foo"
+ end
+ end
+
+ class StructWithOverriddenName < Struct.new(:a)
+ def self.name
+ "Foo"
+ end
+ end
+
+ class UserDefinedWithOverriddenName < UserDefined
+ def self.name
+ "Foo"
+ end
+ end
+
+ class StringWithOverriddenName < String
+ def self.name
+ "Foo"
+ end
+ end
+
+ class ArrayWithOverriddenName < Array
+ def self.name
+ "Foo"
+ end
+ end
+
+ class HashWithOverriddenName < Hash
+ def self.name
+ "Foo"
+ end
+ end
+
+ class RegexpWithOverriddenName < Regexp
+ def self.name
+ "Foo"
+ end
+ end
+
+ class ObjectWithFreezeRaisingException < Object
+ def freeze
+ raise
+ end
+ end
+
+ class ObjectWithoutFreeze < Object
+ undef freeze
+ end
+
DATA = {
"nil" => [nil, "\004\b0"],
"1..2" => [(1..2),
@@ -217,7 +350,7 @@ module MarshalSpec
"\004\b\"\012small"],
"String big" => ['big' * 100,
"\004\b\"\002,\001#{'big' * 100}"],
- "String extended" => [''.extend(Meths), # TODO: check for module on load
+ "String extended" => [''.dup.extend(Meths), # TODO: check for module on load
"\004\be:\nMeths\"\000"],
"String subclass" => [UserString.new,
"\004\bC:\017UserString\"\000"],
@@ -324,7 +457,7 @@ module MarshalSpec
"\x04\bI\"\nsmall\x06:\x06EF"],
"String big" => ['big' * 100,
"\x04\bI\"\x02,\x01bigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbigbig\x06:\x06EF"],
- "String extended" => [''.extend(Meths), # TODO: check for module on load
+ "String extended" => [''.dup.extend(Meths), # TODO: check for module on load
"\x04\bIe:\nMeths\"\x00\x06:\x06EF"],
"String subclass" => [UserString.new,
"\004\bC:\017UserString\"\000"],
@@ -398,6 +531,20 @@ module MarshalSpec
"\004\bS:\024Struct::Pyramid\000"],
"Random" => random_data,
}
+
+ module DataSpec
+ Measure = Data.define(:amount, :unit)
+ Empty = Data.define
+
+ MeasureExtended = Class.new(Measure)
+ MeasureExtended.extend(Enumerable)
+
+ class MeasureWithOverriddenName < Measure
+ def self.name
+ "Foo"
+ end
+ end
+ end
end
class ArraySub < Array
diff --git a/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb b/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb
new file mode 100644
index 0000000000..98a0d43392
--- /dev/null
+++ b/spec/ruby/core/marshal/fixtures/marshal_multibyte_data.rb
@@ -0,0 +1,12 @@
+# -*- encoding: utf-8 -*-
+
+module MarshalSpec
+ class MultibyteぁあぃいClass
+ end
+
+ module MultibyteけげこごModule
+ end
+
+ class MultibyteぁあぃいTime < Time
+ end
+end
diff --git a/spec/ruby/core/marshal/float_spec.rb b/spec/ruby/core/marshal/float_spec.rb
index 5793bbd564..5b2d68d6e1 100644
--- a/spec/ruby/core/marshal/float_spec.rb
+++ b/spec/ruby/core/marshal/float_spec.rb
@@ -40,7 +40,7 @@ end
describe "Marshal.load with Float" do
it "loads NaN" do
- Marshal.load("\004\bf\bnan").should be_nan
+ Marshal.load("\004\bf\bnan").should.nan?
end
it "loads +Infinity" do
diff --git a/spec/ruby/core/marshal/load_spec.rb b/spec/ruby/core/marshal/load_spec.rb
index a5bdfbf520..f5a05f8e52 100644
--- a/spec/ruby/core/marshal/load_spec.rb
+++ b/spec/ruby/core/marshal/load_spec.rb
@@ -1,6 +1,1291 @@
+# encoding: binary
require_relative '../../spec_helper'
-require_relative 'shared/load'
+require_relative 'fixtures/marshal_data'
describe "Marshal.load" do
- it_behaves_like :marshal_load, :load
+ before :all do
+ @num_self_class = 1
+ end
+
+ it "raises an ArgumentError when the dumped data is truncated" do
+ obj = {first: 1, second: 2, third: 3}
+ -> { Marshal.load(Marshal.dump(obj)[0, 5]) }.should.raise(ArgumentError, "marshal data too short")
+ end
+
+ it "raises an ArgumentError when the argument is empty String" do
+ -> { Marshal.load("") }.should.raise(ArgumentError, "marshal data too short")
+ end
+
+ it "raises an ArgumentError when the dumped class is missing" do
+ Object.send(:const_set, :KaBoom, Class.new)
+ kaboom = Marshal.dump(KaBoom.new)
+ Object.send(:remove_const, :KaBoom)
+
+ -> { Marshal.load(kaboom) }.should.raise(ArgumentError)
+ end
+
+ describe "when called with freeze: true" do
+ it "returns frozen strings" do
+ string = Marshal.load(Marshal.dump("foo"), freeze: true)
+ string.should == "foo"
+ string.should.frozen?
+
+ utf8_string = "foo".encode(Encoding::UTF_8)
+ string = Marshal.load(Marshal.dump(utf8_string), freeze: true)
+ string.should == utf8_string
+ string.should.frozen?
+ end
+
+ it "returns frozen arrays" do
+ array = Marshal.load(Marshal.dump([1, 2, 3]), freeze: true)
+ array.should == [1, 2, 3]
+ array.should.frozen?
+ end
+
+ it "returns frozen hashes" do
+ hash = Marshal.load(Marshal.dump({foo: 42}), freeze: true)
+ hash.should == {foo: 42}
+ hash.should.frozen?
+ end
+
+ it "returns frozen regexps" do
+ regexp = Marshal.load(Marshal.dump(/foo/), freeze: true)
+ regexp.should == /foo/
+ regexp.should.frozen?
+ end
+
+ it "returns frozen structs" do
+ struct = Marshal.load(Marshal.dump(MarshalSpec::StructToDump.new(1, 2)), freeze: true)
+ struct.should == MarshalSpec::StructToDump.new(1, 2)
+ struct.should.frozen?
+ end
+
+ it "returns frozen objects" do
+ source_object = Object.new
+
+ object = Marshal.load(Marshal.dump(source_object), freeze: true)
+ object.should.frozen?
+ end
+
+ describe "deep freezing" do
+ it "returns hashes with frozen keys and values" do
+ key = Object.new
+ value = Object.new
+ source_object = {key => value}
+
+ hash = Marshal.load(Marshal.dump(source_object), freeze: true)
+ hash.size.should == 1
+ hash.keys[0].should.frozen?
+ hash.values[0].should.frozen?
+ end
+
+ it "returns arrays with frozen elements" do
+ object = Object.new
+ source_object = [object]
+
+ array = Marshal.load(Marshal.dump(source_object), freeze: true)
+ array.size.should == 1
+ array[0].should.frozen?
+ end
+
+ it "returns structs with frozen members" do
+ object1 = Object.new
+ object2 = Object.new
+ source_object = MarshalSpec::StructToDump.new(object1, object2)
+
+ struct = Marshal.load(Marshal.dump(source_object), freeze: true)
+ struct.a.should.frozen?
+ struct.b.should.frozen?
+ end
+
+ it "returns objects with frozen instance variables" do
+ source_object = Object.new
+ instance_variable = Object.new
+ source_object.instance_variable_set(:@a, instance_variable)
+
+ object = Marshal.load(Marshal.dump(source_object), freeze: true)
+ object.instance_variable_get(:@a).should != nil
+ object.instance_variable_get(:@a).should.frozen?
+ end
+
+ it "deduplicates frozen strings" do
+ source_object = ["foo" + "bar", "foobar"]
+ object = Marshal.load(Marshal.dump(source_object), freeze: true)
+
+ object[0].should.equal?(object[1])
+ end
+ end
+
+ it "does not freeze modules" do
+ object = Marshal.load(Marshal.dump(Kernel), freeze: true)
+ object.should_not.frozen?
+ Kernel.should_not.frozen?
+ end
+
+ it "does not freeze classes" do
+ object = Marshal.load(Marshal.dump(Object), freeze: true)
+ object.should_not.frozen?
+ Object.should_not.frozen?
+ end
+
+ it "does freeze extended objects" do
+ object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", freeze: true)
+ object.should.frozen?
+ end
+
+ it "does freeze extended objects with instance variables" do
+ object = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x06:\n@ivarT", freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns frozen object having #_dump method" do
+ object = Marshal.load(Marshal.dump(UserDefined.new), freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns frozen object responding to #marshal_dump and #marshal_load" do
+ object = Marshal.load(Marshal.dump(UserMarshal.new), freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns frozen object extended by a module" do
+ object = Object.new
+ object.extend(MarshalSpec::ModuleToExtendBy)
+
+ object = Marshal.load(Marshal.dump(object), freeze: true)
+ object.should.frozen?
+ end
+
+ it "does not call freeze method" do
+ object = MarshalSpec::ObjectWithFreezeRaisingException.new
+ object = Marshal.load(Marshal.dump(object), freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns frozen object even if object does not respond to freeze method" do
+ object = MarshalSpec::ObjectWithoutFreeze.new
+ object = Marshal.load(Marshal.dump(object), freeze: true)
+ object.should.frozen?
+ end
+
+ it "returns a frozen object when is an instance of String/Array/Regexp/Hash subclass and has instance variables" do
+ source_object = UserString.new
+ source_object.instance_variable_set(:@foo, "bar")
+
+ object = Marshal.load(Marshal.dump(source_object), freeze: true)
+ object.should.frozen?
+ end
+
+ describe "when called with a proc" do
+ it "call the proc with frozen objects" do
+ arr = []
+ s = +'hi'
+ s.instance_variable_set(:@foo, 5)
+ st = Struct.new("Brittle", :a).new
+ st.instance_variable_set(:@clue, 'none')
+ st.a = 0.0
+ h = Hash.new('def')
+ h['nine'] = 9
+ a = [:a, :b, :c]
+ a.instance_variable_set(:@two, 2)
+ obj = [s, 10, s, s, st, a]
+ obj.instance_variable_set(:@zoo, 'ant')
+ proc = Proc.new { |o| arr << o; o}
+
+ Marshal.load(
+ "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F",
+ proc,
+ freeze: true,
+ )
+
+ arr.should == [
+ false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st,
+ :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]],
+ ]
+
+ arr.each do |v|
+ v.should.frozen?
+ end
+
+ Struct.send(:remove_const, :Brittle)
+ end
+
+ it "does not freeze the object returned by the proc" do
+ string = Marshal.load(Marshal.dump("foo"), proc { |o| o.upcase }, freeze: true)
+ string.should == "FOO"
+ string.should_not.frozen?
+ end
+ end
+ end
+
+ describe "when called with a proc" do
+ it "call the proc with fully initialized strings" do
+ utf8_string = "foo".encode(Encoding::UTF_8)
+ Marshal.load(Marshal.dump(utf8_string), proc { |arg|
+ if arg.is_a?(String)
+ arg.should == utf8_string
+ arg.encoding.should == Encoding::UTF_8
+ end
+ arg
+ })
+ end
+
+ it "no longer mutate the object after it was passed to the proc" do
+ string = Marshal.load(Marshal.dump("foo"), :freeze.to_proc)
+ string.should.frozen?
+ end
+
+ it "call the proc with extended objects" do
+ objs = []
+ obj = Marshal.load("\x04\be:\x0FEnumerableo:\vObject\x00", Proc.new { |o| objs << o; o })
+ objs.should == [obj]
+ end
+
+ it "returns the value of the proc" do
+ Marshal.load(Marshal.dump([1,2]), proc { [3,4] }).should == [3,4]
+ end
+
+ it "calls the proc for recursively visited data" do
+ a = [1]
+ a << a
+ ret = []
+ Marshal.load(Marshal.dump(a), proc { |arg| ret << arg.inspect; arg })
+ ret[0].should == 1.inspect
+ ret[1].should == a.inspect
+ ret.size.should == 2
+ end
+
+ it "loads an Array with proc" do
+ arr = []
+ s = +'hi'
+ s.instance_variable_set(:@foo, 5)
+ st = Struct.new("Brittle", :a).new
+ st.instance_variable_set(:@clue, 'none')
+ st.a = 0.0
+ h = Hash.new('def')
+ h['nine'] = 9
+ a = [:a, :b, :c]
+ a.instance_variable_set(:@two, 2)
+ obj = [s, 10, s, s, st, a]
+ obj.instance_variable_set(:@zoo, 'ant')
+ proc = Proc.new { |o| arr << o.dup; o}
+
+ Marshal.load("\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc)
+
+ arr.should == [
+ false, 5, "hi", 10, "hi", "hi", 0.0, false, "none", st,
+ :b, :c, 2, a, false, "ant", ["hi", 10, "hi", "hi", st, [:a, :b, :c]],
+ ]
+ Struct.send(:remove_const, :Brittle)
+ end
+ end
+
+ describe "when called with nil for the proc argument" do
+ it "behaves as if no proc argument was passed" do
+ a = [1]
+ a << a
+ b = Marshal.load(Marshal.dump(a), nil)
+ b.should == a
+ end
+ end
+
+ describe "when called on objects with custom _dump methods" do
+ it "does not set instance variables of an object with user-defined _dump/_load" do
+ # this string represents: <#UserPreviouslyDefinedWithInitializedIvar @field2=7 @field1=6>
+ dump_str = "\004\bu:-UserPreviouslyDefinedWithInitializedIvar\a:\f@field2i\f:\f@field1i\v"
+
+ UserPreviouslyDefinedWithInitializedIvar.should_receive(:_load).and_return(UserPreviouslyDefinedWithInitializedIvar.new)
+ marshaled_obj = Marshal.load(dump_str)
+
+ marshaled_obj.should.instance_of?(UserPreviouslyDefinedWithInitializedIvar)
+ marshaled_obj.field1.should == nil
+ marshaled_obj.field2.should == nil
+ end
+
+ it "loads the String in non US-ASCII and non UTF-8 encoding" do
+ source_object = UserDefinedString.new("a".encode("windows-1251"))
+ object = Marshal.load(Marshal.dump(source_object))
+ object.string.should == "a".encode("windows-1251")
+ end
+
+ it "loads the String in multibyte encoding" do
+ source_object = UserDefinedString.new("a".encode("utf-32le"))
+ object = Marshal.load(Marshal.dump(source_object))
+ object.string.should == "a".encode("utf-32le")
+ end
+
+ describe "that returns an immediate value" do
+ it "loads an array containing an instance of the object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: [<#UserDefinedImmediate A>, <#String "string">, <#String "string">]
+ marshaled_obj = Marshal.load("\004\b[\bu:\031UserDefinedImmediate\000\"\vstring@\a")
+
+ marshaled_obj.should == [nil, str, str]
+ end
+
+ it "loads any structure with multiple references to the same object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: {a: <#UserDefinedImmediate A>, b: <#UserDefinedImmediate A>, c: <#String "string">, d: <#String "string">}
+ hash_dump = "\x04\b{\t:\x06aIu:\x19UserDefinedImmediate\x00\x06:\x06ET:\x06b@\x06:\x06cI\"\vstring\x06;\aT:\x06d@\a"
+
+ marshaled_obj = Marshal.load(hash_dump)
+ marshaled_obj.should == {a: nil, b: nil, c: str, d: str}
+
+ # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate A>, <#String "string">, <#String "string">]
+ array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ET@\x06I\"\vstring\x06;\x06T@\a"
+
+ marshaled_obj = Marshal.load(array_dump)
+ marshaled_obj.should == [nil, nil, str, str]
+ end
+
+ it "loads an array containing references to multiple instances of the object, followed by multiple instances of another object" do
+ str = "string"
+
+ # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate B>, <#String "string">, <#String "string">]
+ array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ETIu;\x00\x00\x06;\x06TI\"\vstring\x06;\x06T@\b"
+
+ marshaled_obj = Marshal.load(array_dump)
+ marshaled_obj.should == [nil, nil, str, str]
+ end
+ end
+ end
+
+ it "loads an array containing objects having _dump method, and with proc" do
+ arr = []
+ myproc = Proc.new { |o| arr << o.dup; o }
+ o1 = UserDefined.new;
+ o2 = UserDefinedWithIvar.new
+ obj = [o1, o2, o1, o2]
+
+ Marshal.load("\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc)
+
+ arr[0].should == o1
+ arr[1].should == o2
+ arr[2].should == obj
+ arr.size.should == 3
+ end
+
+ it "loads an array containing objects having marshal_dump method, and with proc" do
+ arr = []
+ proc = Proc.new { |o| arr << o.dup; o }
+ o1 = UserMarshal.new
+ o2 = UserMarshalWithIvar.new
+
+ Marshal.load("\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc)
+
+ arr[0].should == 'stuff'
+ arr[1].should == o1
+ arr[2].should == 'my data'
+ arr[3].should == ['my data']
+ arr[4].should == o2
+ arr[5].should == [o1, o2, o1, o2]
+
+ arr.size.should == 6
+ end
+
+ it "assigns classes to nested subclasses of Array correctly" do
+ arr = ArraySub.new(ArraySub.new)
+ arr_dump = Marshal.dump(arr)
+ Marshal.load(arr_dump).class.should == ArraySub
+ end
+
+ it "loads subclasses of Array with overridden << and push correctly" do
+ arr = ArraySubPush.new
+ arr[0] = '1'
+ arr_dump = Marshal.dump(arr)
+ Marshal.load(arr_dump).should == arr
+ end
+
+ it "raises a TypeError with bad Marshal version" do
+ marshal_data = +'\xff\xff'
+ marshal_data[0] = (Marshal::MAJOR_VERSION).chr
+ marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr
+
+ -> { Marshal.load(marshal_data) }.should.raise(TypeError)
+
+ marshal_data = +'\xff\xff'
+ marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr
+ marshal_data[1] = (Marshal::MINOR_VERSION).chr
+
+ -> { Marshal.load(marshal_data) }.should.raise(TypeError)
+ end
+
+ it "raises EOFError on loading an empty file" do
+ temp_file = tmp("marshal.rubyspec.tmp.#{Process.pid}")
+ file = File.new(temp_file, "w+")
+ begin
+ -> { Marshal.load(file) }.should.raise(EOFError)
+ ensure
+ file.close
+ rm_r temp_file
+ end
+ end
+
+ # Note: Ruby 1.9 should be compatible with older marshal format
+ MarshalSpec::DATA.each do |description, (object, marshal, attributes)|
+ it "loads a #{description}" do
+ Marshal.load(marshal).should == object
+ end
+ end
+
+ MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)|
+ it "loads a #{description}" do
+ Marshal.load(marshal).should == object
+ end
+ end
+
+ describe "for an Array" do
+ it "loads an array containing the same objects" do
+ s = 'oh'
+ b = 'hi'
+ r = //
+ d = [b, :no, s, :go]
+ c = String
+ f = 1.0
+
+ o1 = UserMarshalWithIvar.new; o2 = UserMarshal.new
+
+ obj = [:so, 'hello', 100, :so, :so, d, :so, o2, :so, :no, o2,
+ :go, c, nil, Struct::Pyramid.new, f, :go, :no, s, b, r,
+ :so, 'huh', o1, true, b, b, 99, r, b, s, :so, f, c, :no, o1, d]
+
+ Marshal.load("\004\b[*:\aso\"\nhelloii;\000;\000[\t\"\ahi:\ano\"\aoh:\ago;\000U:\020UserMarshal\"\nstuff;\000;\006@\n;\ac\vString0S:\024Struct::Pyramid\000f\0061;\a;\006@\t@\b/\000\000;\000\"\bhuhU:\030UserMarshalWithIvar[\006\"\fmy dataT@\b@\bih@\017@\b@\t;\000@\016@\f;\006@\021@\a").should ==
+ obj
+ end
+
+ it "loads an array having ivar" do
+ s = +'well'
+ s.instance_variable_set(:@foo, 10)
+ obj = ['5', s, 'hi'].extend(Meths, MethsMore)
+ obj.instance_variable_set(:@mix, s)
+ new_obj = Marshal.load("\004\bI[\b\"\0065I\"\twell\006:\t@fooi\017\"\ahi\006:\t@mix@\a")
+ new_obj.should == obj
+ new_obj.instance_variable_get(:@mix).should.equal? new_obj[1]
+ new_obj[1].instance_variable_get(:@foo).should == 10
+ end
+
+ it "loads an extended Array object containing a user-marshaled object" do
+ obj = [UserMarshal.new, UserMarshal.new].extend(Meths)
+ dump = "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT"
+ new_obj = Marshal.load(dump)
+
+ new_obj.should == obj
+ obj_ancestors = class << obj; ancestors[1..-1]; end
+ new_obj_ancestors = class << new_obj; ancestors[1..-1]; end
+ obj_ancestors.should == new_obj_ancestors
+ end
+ end
+
+ describe "for a Hash" do
+ it "loads an extended_user_hash with a parameter to initialize" do
+ obj = UserHashInitParams.new(:abc).extend(Meths)
+
+ new_obj = Marshal.load("\004\bIe:\nMethsC:\027UserHashInitParams{\000\006:\a@a:\babc")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == Meths
+ new_obj_metaclass_ancestors[@num_self_class+1].should == UserHashInitParams
+ end
+
+ it "loads an extended hash object containing a user-marshaled object" do
+ obj = {a: UserMarshal.new}.extend(Meths)
+
+ new_obj = Marshal.load("\004\be:\nMeths{\006:\006aU:\020UserMarshal\"\nstuff")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == Meths
+ new_obj_metaclass_ancestors[@num_self_class+1].should == Hash
+ end
+
+ it "preserves hash ivars when hash contains a string having ivar" do
+ s = +'string'
+ s.instance_variable_set :@string_ivar, 'string ivar'
+ h = { key: s }
+ h.instance_variable_set :@hash_ivar, 'hash ivar'
+
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar'
+ unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar'
+ end
+
+ it "preserves compare_by_identity behaviour" do
+ h = { a: 1 }
+ h.compare_by_identity
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should.compare_by_identity?
+
+ h = { a: 1 }
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should_not.compare_by_identity?
+ end
+
+ it "preserves compare_by_identity behaviour for a Hash subclass" do
+ h = UserHash.new({ a: 1 })
+ h.compare_by_identity
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should.compare_by_identity?
+
+ h = UserHash.new({ a: 1 })
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should_not.compare_by_identity?
+ end
+
+ it "allocates an instance of the proper class when Hash subclass with compare_by_identity behaviour" do
+ h = UserHash.new({ a: 1 })
+ h.compare_by_identity
+
+ unmarshalled = Marshal.load(Marshal.dump(h))
+ unmarshalled.should.kind_of?(UserHash)
+ end
+ end
+
+ describe "for a Symbol" do
+ it "loads a Symbol" do
+ sym = Marshal.load("\004\b:\vsymbol")
+ sym.should == :symbol
+ sym.encoding.should == Encoding::US_ASCII
+ end
+
+ it "loads a big Symbol" do
+ sym = ('big' * 100).to_sym
+ Marshal.load("\004\b:\002,\001#{'big' * 100}").should == sym
+ end
+
+ it "loads an encoded Symbol" do
+ s = "\u2192"
+
+ sym = Marshal.load("\x04\bI:\b\xE2\x86\x92\x06:\x06ET")
+ sym.should == s.encode("utf-8").to_sym
+ sym.encoding.should == Encoding::UTF_8
+
+ sym = Marshal.load("\x04\bI:\t\xFE\xFF!\x92\x06:\rencoding\"\vUTF-16")
+ sym.should == s.encode("utf-16").to_sym
+ sym.encoding.should == Encoding::UTF_16
+
+ sym = Marshal.load("\x04\bI:\a\x92!\x06:\rencoding\"\rUTF-16LE")
+ sym.should == s.encode("utf-16le").to_sym
+ sym.encoding.should == Encoding::UTF_16LE
+
+ sym = Marshal.load("\x04\bI:\a!\x92\x06:\rencoding\"\rUTF-16BE")
+ sym.should == s.encode("utf-16be").to_sym
+ sym.encoding.should == Encoding::UTF_16BE
+
+ sym = Marshal.load("\x04\bI:\a\xA2\xAA\x06:\rencoding\"\vEUC-JP")
+ sym.should == s.encode("euc-jp").to_sym
+ sym.encoding.should == Encoding::EUC_JP
+
+ sym = Marshal.load("\x04\bI:\a\x81\xA8\x06:\rencoding\"\x10Windows-31J")
+ sym.should == s.encode("sjis").to_sym
+ sym.encoding.should == Encoding::SJIS
+ end
+
+ it "loads a binary encoded Symbol" do
+ s = "\u2192".dup.force_encoding("binary").to_sym
+ sym = Marshal.load("\x04\b:\b\xE2\x86\x92")
+ sym.should == s
+ sym.encoding.should == Encoding::BINARY
+ end
+
+ it "loads multiple Symbols sharing the same encoding" do
+ # Note that the encoding is a link for the second Symbol
+ symbol1 = "I:\t\xE2\x82\xACa\x06:\x06ET"
+ symbol2 = "I:\t\xE2\x82\xACb\x06;\x06T"
+ dump = "\x04\b[\a#{symbol1}#{symbol2}"
+ value = Marshal.load(dump)
+ value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8]
+ expected = [
+ "€a".dup.force_encoding(Encoding::UTF_8).to_sym,
+ "€b".dup.force_encoding(Encoding::UTF_8).to_sym
+ ]
+ value.should == expected
+
+ value = Marshal.load("\x04\b[\b#{symbol1}#{symbol2};\x00")
+ value.map(&:encoding).should == [Encoding::UTF_8, Encoding::UTF_8, Encoding::UTF_8]
+ value.should == [*expected, expected[0]]
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before symbol characters end" do
+ Marshal.dump(:hello).should == "\x04\b:\nhello"
+
+ -> {
+ Marshal.load("\x04\b:\nhel")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a String" do
+ it "loads a string having ivar with ref to self" do
+ obj = +'hi'
+ obj.instance_variable_set(:@self, obj)
+ Marshal.load("\004\bI\"\ahi\006:\n@self@\000").should == obj
+ end
+
+ it "loads a string through StringIO stream" do
+ require 'stringio'
+ obj = "This is a string which should be unmarshalled through StringIO stream!"
+ Marshal.load(StringIO.new(Marshal.dump(obj))).should == obj
+ end
+
+ it "sets binmode if it is loading through StringIO stream" do
+ io = StringIO.new("\004\b:\vsymbol")
+ def io.binmode; raise "binmode"; end
+ -> { Marshal.load(io) }.should.raise(RuntimeError, "binmode")
+ end
+
+ it "loads a string with an ivar" do
+ str = Marshal.load("\x04\bI\"\x00\x06:\t@fooI\"\bbar\x06:\x06EF")
+ str.instance_variable_get("@foo").should == "bar"
+ end
+
+ it "loads a String subclass with custom constructor" do
+ str = Marshal.load("\x04\bC: UserCustomConstructorString\"\x00")
+ str.should.instance_of?(UserCustomConstructorString)
+ end
+
+ it "loads a US-ASCII String" do
+ str = "abc".dup.force_encoding("us-ascii")
+ data = "\x04\bI\"\babc\x06:\x06EF"
+ result = Marshal.load(data)
+ result.should == str
+ result.encoding.should.equal?(Encoding::US_ASCII)
+ end
+
+ it "loads a UTF-8 String" do
+ str = "\x6d\xc3\xb6\x68\x72\x65".dup.force_encoding("utf-8")
+ data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET"
+ result = Marshal.load(data)
+ result.should == str
+ result.encoding.should.equal?(Encoding::UTF_8)
+ end
+
+ it "loads a String in another encoding" do
+ str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".dup.force_encoding("utf-16le")
+ data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE"
+ result = Marshal.load(data)
+ result.should == str
+ result.encoding.should.equal?(Encoding::UTF_16LE)
+ end
+
+ it "loads a String as BINARY if no encoding is specified at the end" do
+ str = "\xC3\xB8".dup.force_encoding("BINARY")
+ data = "\x04\b\"\a\xC3\xB8".dup.force_encoding("UTF-8")
+ result = Marshal.load(data)
+ result.encoding.should == Encoding::BINARY
+ result.should == str
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before string characters end" do
+ Marshal.dump("hello").should == "\x04\b\"\nhello"
+
+ -> {
+ Marshal.load("\x04\b\"\nhel")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a Struct" do
+ it "loads a extended_struct having fields with same objects" do
+ s = 'hi'
+ obj = Struct.new("Extended", :a, :b).new.extend(Meths)
+ dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a0:\006b0"
+ Marshal.load(dump).should == obj
+
+ obj.a = [:a, s]
+ obj.b = [:Meths, s]
+ dump = "\004\be:\nMethsS:\025Struct::Extended\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
+ Marshal.load(dump).should == obj
+ Struct.send(:remove_const, :Extended)
+ end
+
+ it "loads a struct having ivar" do
+ obj = Struct.new("Thick").new
+ obj.instance_variable_set(:@foo, 5)
+ reloaded = Marshal.load("\004\bIS:\022Struct::Thick\000\006:\t@fooi\n")
+ reloaded.should == obj
+ reloaded.instance_variable_get(:@foo).should == 5
+ Struct.send(:remove_const, :Thick)
+ end
+
+ it "loads a struct having fields" do
+ obj = Struct.new("Ure1", :a, :b).new
+ Marshal.load("\004\bS:\021Struct::Ure1\a:\006a0:\006b0").should == obj
+ Struct.send(:remove_const, :Ure1)
+ end
+
+ it "does not call initialize on the unmarshaled struct" do
+ threadlocal_key = MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY
+
+ s = MarshalSpec::StructWithUserInitialize.new('foo')
+ Thread.current[threadlocal_key].should == ['foo']
+ s.a.should == 'foo'
+
+ Thread.current[threadlocal_key] = nil
+
+ dumped = Marshal.dump(s)
+ loaded = Marshal.load(dumped)
+
+ Thread.current[threadlocal_key].should == nil
+ loaded.a.should == 'foo'
+ end
+ end
+
+ describe "for a Data" do
+ it "loads a Data" do
+ obj = MarshalSpec::DataSpec::Measure.new(100, 'km')
+ dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm"
+ Marshal.dump(obj).should == dumped
+
+ Marshal.load(dumped).should == obj
+ end
+
+ it "loads an extended Data" do
+ obj = MarshalSpec::DataSpec::MeasureExtended.new(100, "km")
+ dumped = "\x04\bS:+MarshalSpec::DataSpec::MeasureExtended\a:\vamountii:\tunit\"\akm"
+ Marshal.dump(obj).should == dumped
+
+ Marshal.load(dumped).should == obj
+ end
+
+ it "returns a frozen object" do
+ obj = MarshalSpec::DataSpec::Measure.new(100, 'km')
+ dumped = "\x04\bS:#MarshalSpec::DataSpec::Measure\a:\vamountii:\tunit\"\akm"
+ Marshal.dump(obj).should == dumped
+
+ Marshal.load(dumped).should.frozen?
+ end
+ end
+
+ describe "for an Exception" do
+ it "loads a marshalled exception with no message" do
+ obj = Exception.new
+ loaded = Marshal.load("\004\bo:\016Exception\a:\abt0:\tmesg0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesg0:\abt0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads a marshalled exception with a message" do
+ obj = Exception.new("foo")
+ loaded = Marshal.load("\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads a marshalled exception with a backtrace" do
+ obj = Exception.new("foo")
+ obj.set_backtrace(["foo/bar.rb:10"])
+ loaded = Marshal.load("\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ loaded = Marshal.load("\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF")
+ loaded.message.should == obj.message
+ loaded.backtrace.should == obj.backtrace
+ end
+
+ it "loads an marshalled exception with ivars" do
+ s = 'hi'
+ arr = [:so, :so, s, s]
+ obj = Exception.new("foo")
+ obj.instance_variable_set :@arr, arr
+
+ loaded = Marshal.load("\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\t@arr[\t:\aso;\t\"\ahi@\b")
+ new_arr = loaded.instance_variable_get :@arr
+
+ loaded.message.should == obj.message
+ new_arr.should == arr
+ end
+ end
+
+ describe "for an Object" do
+ it "loads an object" do
+ Marshal.load("\004\bo:\vObject\000").should.is_a?(Object)
+ end
+
+ it "loads an extended Object" do
+ obj = Object.new.extend(Meths)
+
+ new_obj = Marshal.load("\004\be:\nMethso:\vObject\000")
+
+ new_obj.class.should == obj.class
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 2].should == [Meths, Object]
+ end
+
+ it "loads an object having ivar" do
+ s = 'hi'
+ arr = [:so, :so, s, s]
+ obj = Object.new
+ obj.instance_variable_set :@str, arr
+
+ new_obj = Marshal.load("\004\bo:\vObject\006:\t@str[\t:\aso;\a\"\ahi@\a")
+ new_str = new_obj.instance_variable_get :@str
+
+ new_str.should == arr
+ end
+
+ it "loads an Object with a non-US-ASCII instance variable" do
+ ivar = "@é".dup.force_encoding(Encoding::UTF_8).to_sym
+ obj = Marshal.load("\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06")
+ obj.instance_variables.should == [ivar]
+ obj.instance_variables[0].encoding.should == Encoding::UTF_8
+ obj.instance_variable_get(ivar).should == 1
+ end
+
+ it "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class" do
+ -> do
+ Marshal.load("\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd")
+ end.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before class name end" do
+ Marshal.dump(Object.new).should == "\x04\bo:\vObject\x00"
+
+ -> {
+ Marshal.load("\x04\bo:\vObj")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for an object responding to #marshal_dump and #marshal_load" do
+ it "loads a user-marshaled object" do
+ obj = UserMarshal.new
+ obj.data = :data
+ value = [obj, :data]
+ dump = Marshal.dump(value)
+ dump.should == "\x04\b[\aU:\x10UserMarshal:\tdata;\x06"
+ reloaded = Marshal.load(dump)
+ reloaded.should == value
+ end
+ end
+
+ describe "for a user object" do
+ it "loads a user-marshaled extended object" do
+ obj = UserMarshal.new.extend(Meths)
+
+ new_obj = Marshal.load("\004\bU:\020UserMarshal\"\nstuff")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class].should == UserMarshal
+ end
+
+ it "loads a UserObject" do
+ Marshal.load("\004\bo:\017UserObject\000").should.is_a?(UserObject)
+ end
+
+ describe "that extends a core type other than Object or BasicObject" do
+ after :each do
+ MarshalSpec.reset_swapped_class
+ end
+
+ it "raises ArgumentError if the resulting class does not extend the same type" do
+ MarshalSpec.set_swapped_class(Class.new(Hash))
+ data = Marshal.dump(MarshalSpec::SwappedClass.new)
+
+ MarshalSpec.set_swapped_class(Class.new(Array))
+ -> { Marshal.load(data) }.should.raise(ArgumentError)
+
+ MarshalSpec.set_swapped_class(Class.new)
+ -> { Marshal.load(data) }.should.raise(ArgumentError)
+ end
+ end
+ end
+
+ describe "for a Regexp" do
+ ruby_version_is "4.1" do
+ it "raises FrozenError for an extended Regexp" do
+ -> {
+ Marshal.load("\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
+ }.should.raise(FrozenError)
+ end
+
+ it "raises FrozenError when regexp has instance variables" do
+ -> {
+ Marshal.load("\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
+ }.should.raise(FrozenError)
+ end
+ end
+
+ ruby_version_is ""..."4.1" do
+ it "loads an extended Regexp" do
+ obj = /[a-z]/.dup.extend(Meths, MethsMore)
+ new_obj = Marshal.load("\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
+
+ new_obj.should == obj
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 3].should ==
+ [Meths, MethsMore, Regexp]
+ end
+
+ it "restore the regexp instance variables" do
+ obj = Regexp.new("hello")
+ obj.instance_variable_set(:@regexp_ivar, [42])
+
+ new_obj = Marshal.load("\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
+ new_obj.instance_variables.should == [:@regexp_ivar]
+ new_obj.instance_variable_get(:@regexp_ivar).should == [42]
+ end
+ end
+
+ it "loads a Regexp subclass instance variables" do
+ obj = UserRegexp.new('abc')
+ obj.instance_variable_set(:@noise, 'much')
+
+ new_obj = Marshal.load(Marshal.dump(obj))
+
+ new_obj.should == obj
+ new_obj.instance_variable_get(:@noise).should == 'much'
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 2].should ==
+ [UserRegexp, Regexp]
+ end
+
+ it "loads a Regexp subclass instance variables when it is extended with a module" do
+ obj = UserRegexp.new('').extend(Meths)
+ obj.instance_variable_set(:@noise, 'much')
+
+ new_obj = Marshal.load("\004\bIe:\nMethsC:\017UserRegexp/\000\000\006:\v@noise\"\tmuch")
+
+ new_obj.should == obj
+ new_obj.instance_variable_get(:@noise).should == 'much'
+ new_obj_metaclass_ancestors = class << new_obj; ancestors; end
+ new_obj_metaclass_ancestors[@num_self_class, 3].should ==
+ [Meths, UserRegexp, Regexp]
+ end
+
+ it "preserves Regexp encoding" do
+ source_object = Regexp.new("a".encode("utf-32le"))
+ regexp = Marshal.load(Marshal.dump(source_object))
+
+ regexp.encoding.should == Encoding::UTF_32LE
+ regexp.source.should == "a".encode("utf-32le")
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before source string end" do
+ Marshal.dump(/hello world/).should == "\x04\bI/\x10hello world\x00\x06:\x06EF"
+
+ -> {
+ Marshal.load("\x04\bI/\x10hel")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a Float" do
+ it "loads a Float NaN" do
+ obj = 0.0 / 0.0
+ Marshal.load("\004\bf\bnan").to_s.should == obj.to_s
+ end
+
+ it "loads a Float 1.3" do
+ Marshal.load("\004\bf\v1.3\000\314\315").should == 1.3
+ end
+
+ it "loads a Float -5.1867345e-22" do
+ obj = -5.1867345e-22
+ Marshal.load("\004\bf\037-5.1867345000000008e-22\000\203_").should be_close(obj, 1e-30)
+ end
+
+ it "loads a Float 1.1867345e+22" do
+ obj = 1.1867345e+22
+ Marshal.load("\004\bf\0361.1867344999999999e+22\000\344@").should == obj
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before float string representation end" do
+ Marshal.dump(1.3).should == "\x04\bf\b1.3"
+
+ -> {
+ Marshal.load("\004\bf\v1")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for an Integer" do
+ it "loads 0" do
+ Marshal.load("\004\bi\000").should == 0
+ Marshal.load("\004\bi\005").should == 0
+ end
+
+ it "loads an Integer 8" do
+ Marshal.load("\004\bi\r" ).should == 8
+ end
+
+ it "loads and Integer -8" do
+ Marshal.load("\004\bi\363" ).should == -8
+ end
+
+ it "loads an Integer 1234" do
+ Marshal.load("\004\bi\002\322\004").should == 1234
+ end
+
+ it "loads an Integer -1234" do
+ Marshal.load("\004\bi\376.\373").should == -1234
+ end
+
+ it "loads an Integer 4611686018427387903" do
+ Marshal.load("\004\bl+\t\377\377\377\377\377\377\377?").should == 4611686018427387903
+ end
+
+ it "loads an Integer -4611686018427387903" do
+ Marshal.load("\004\bl-\t\377\377\377\377\377\377\377?").should == -4611686018427387903
+ end
+
+ it "loads an Integer 2361183241434822606847" do
+ Marshal.load("\004\bl+\n\377\377\377\377\377\377\377\377\177\000").should == 2361183241434822606847
+ end
+
+ it "loads an Integer -2361183241434822606847" do
+ Marshal.load("\004\bl-\n\377\377\377\377\377\377\377\377\177\000").should == -2361183241434822606847
+ end
+
+ it "raises ArgumentError if the input is too short" do
+ ["\004\bi",
+ "\004\bi\001",
+ "\004\bi\002",
+ "\004\bi\002\0",
+ "\004\bi\003",
+ "\004\bi\003\0",
+ "\004\bi\003\0\0",
+ "\004\bi\004",
+ "\004\bi\004\0",
+ "\004\bi\004\0\0",
+ "\004\bi\004\0\0\0"].each do |invalid|
+ -> { Marshal.load(invalid) }.should.raise(ArgumentError)
+ end
+ end
+
+ if 0.size == 8 # for platforms like x86_64
+ it "roundtrips 4611686018427387903 from dump/load correctly" do
+ Marshal.load(Marshal.dump(4611686018427387903)).should == 4611686018427387903
+ end
+ end
+ end
+
+ describe "for a Rational" do
+ it "loads" do
+ r = Marshal.load(Marshal.dump(Rational(1, 3)))
+ r.should == Rational(1, 3)
+ r.should.frozen?
+ end
+ end
+
+ describe "for a Complex" do
+ it "loads" do
+ c = Marshal.load(Marshal.dump(Complex(4, 3)))
+ c.should == Complex(4, 3)
+ c.should.frozen?
+ end
+ end
+
+ describe "for a Bignum" do
+ platform_is c_long_size: 64 do
+ context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do
+ it "dumps a Fixnum" do
+ val = Marshal.load("\004\bl+\ab:wU")
+ val.should == 1433877090
+ val.class.should == Integer
+ end
+
+ it "dumps an array containing multiple references to the Bignum as an array of Fixnum" do
+ arr = Marshal.load("\004\b[\al+\a\223BwU@\006")
+ arr.should == [1433879187, 1433879187]
+ arr.each { |v| v.class.should == Integer }
+ end
+ end
+ end
+ end
+
+ describe "for a Time" do
+ it "loads" do
+ Marshal.load(Marshal.dump(Time.at(1))).should == Time.at(1)
+ end
+
+ it "loads serialized instance variables" do
+ t = Time.new
+ t.instance_variable_set(:@foo, 'bar')
+
+ Marshal.load(Marshal.dump(t)).instance_variable_get(:@foo).should == 'bar'
+ end
+
+ it "loads Time objects stored as links" do
+ t = Time.new
+
+ t1, t2 = Marshal.load(Marshal.dump([t, t]))
+ t1.should.equal? t2
+ end
+
+ it "keeps the local zone" do
+ with_timezone 'AST', 3 do
+ t = Time.local(2012, 1, 1)
+ Marshal.load(Marshal.dump(t)).zone.should == t.zone
+ end
+ end
+
+ it "keeps UTC zone" do
+ t = Time.now.utc
+ t2 = Marshal.load(Marshal.dump(t))
+ t2.should.utc?
+ end
+
+ it "keeps the zone" do
+ t = nil
+
+ with_timezone 'AST', 4 do
+ t = Time.local(2012, 1, 1)
+ end
+
+ with_timezone 'EET', -2 do
+ Marshal.load(Marshal.dump(t)).zone.should == 'AST'
+ end
+ end
+
+ it "keeps utc offset" do
+ t = Time.new(2007,11,1,15,25,0, "+09:00")
+ t2 = Marshal.load(Marshal.dump(t))
+ t2.utc_offset.should == 32400
+ end
+
+ it "keeps nanoseconds" do
+ t = Time.now
+ Marshal.load(Marshal.dump(t)).nsec.should == t.nsec
+ end
+
+ it "does not add any additional instance variable" do
+ t = Time.now
+ t2 = Marshal.load(Marshal.dump(t))
+ t2.instance_variables.should.empty?
+ end
+ end
+
+ describe "for nil" do
+ it "loads" do
+ Marshal.load("\x04\b0").should == nil
+ end
+ end
+
+ describe "for true" do
+ it "loads" do
+ Marshal.load("\x04\bT").should == true
+ end
+ end
+
+ describe "for false" do
+ it "loads" do
+ Marshal.load("\x04\bF").should == false
+ end
+ end
+
+ describe "for a Class" do
+ it "loads" do
+ Marshal.load("\x04\bc\vString").should == String
+ end
+
+ it "raises ArgumentError if given the name of a non-Module" do
+ -> { Marshal.load("\x04\bc\vKernel") }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError if given a nonexistent class" do
+ -> { Marshal.load("\x04\bc\vStrung") }.should.raise(ArgumentError)
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before class name end" do
+ Marshal.dump(String).should == "\x04\bc\vString"
+
+ -> {
+ Marshal.load("\x04\bc\vStr")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a Module" do
+ it "loads a module" do
+ Marshal.load("\x04\bm\vKernel").should == Kernel
+ end
+
+ it "raises ArgumentError if given the name of a non-Class" do
+ -> { Marshal.load("\x04\bm\vString") }.should.raise(ArgumentError)
+ end
+
+ it "loads an old module" do
+ Marshal.load("\x04\bM\vKernel").should == Kernel
+ end
+
+ it "raises ArgumentError when end of byte sequence reached before module name end" do
+ Marshal.dump(Kernel).should == "\x04\bm\vKernel"
+
+ -> {
+ Marshal.load("\x04\bm\vKer")
+ }.should.raise(ArgumentError, "marshal data too short")
+ end
+ end
+
+ describe "for a wrapped C pointer" do
+ it "loads" do
+ class DumpableDir < Dir
+ def _dump_data
+ path
+ end
+ def _load_data path
+ initialize(path)
+ end
+ end
+
+ data = "\x04\bd:\x10DumpableDirI\"\x06.\x06:\x06ET"
+
+ dir = Marshal.load(data)
+ begin
+ dir.path.should == '.'
+ ensure
+ dir.close
+ end
+ end
+
+ it "raises TypeError when the local class is missing _load_data" do
+ class UnloadableDumpableDir < Dir
+ def _dump_data
+ path
+ end
+ # no _load_data
+ end
+
+ data = "\x04\bd:\x1AUnloadableDumpableDirI\"\x06.\x06:\x06ET"
+
+ -> { Marshal.load(data) }.should.raise(TypeError)
+ end
+
+ it "raises ArgumentError when the local class is a regular object" do
+ data = "\004\bd:\020UserDefined\0"
+
+ -> { Marshal.load(data) }.should.raise(ArgumentError)
+ end
+ end
+
+ describe "when a class does not exist in the namespace" do
+ before :each do
+ NamespaceTest.send(:const_set, :SameName, Class.new)
+ @data = Marshal.dump(NamespaceTest::SameName.new)
+ NamespaceTest.send(:remove_const, :SameName)
+ end
+
+ it "raises an ArgumentError" do
+ message = "undefined class/module NamespaceTest::SameName"
+ -> { Marshal.load(@data) }.should.raise(ArgumentError, message)
+ end
+ end
+
+ it "raises an ArgumentError with full constant name when the dumped constant is missing" do
+ NamespaceTest.send(:const_set, :KaBoom, Class.new)
+ @data = Marshal.dump(NamespaceTest::KaBoom.new)
+ NamespaceTest.send(:remove_const, :KaBoom)
+
+ -> { Marshal.load(@data) }.should.raise(ArgumentError, /NamespaceTest::KaBoom/)
+ end
end
diff --git a/spec/ruby/core/marshal/restore_spec.rb b/spec/ruby/core/marshal/restore_spec.rb
index 7e75d7dea6..3135f881d4 100644
--- a/spec/ruby/core/marshal/restore_spec.rb
+++ b/spec/ruby/core/marshal/restore_spec.rb
@@ -1,6 +1,7 @@
require_relative '../../spec_helper'
-require_relative 'shared/load'
describe "Marshal.restore" do
- it_behaves_like :marshal_load, :restore
+ it "is an alias of Marshal.load" do
+ Marshal.method(:restore).should == Marshal.method(:load)
+ end
end
diff --git a/spec/ruby/core/marshal/shared/load.rb b/spec/ruby/core/marshal/shared/load.rb
deleted file mode 100644
index 4e940c80a8..0000000000
--- a/spec/ruby/core/marshal/shared/load.rb
+++ /dev/null
@@ -1,900 +0,0 @@
-# -*- encoding: binary -*-
-require_relative '../fixtures/marshal_data'
-
-describe :marshal_load, shared: true do
- before :all do
- @num_self_class = 1
- end
-
- it "raises an ArgumentError when the dumped data is truncated" do
- obj = {first: 1, second: 2, third: 3}
- -> { Marshal.send(@method, Marshal.dump(obj)[0, 5]) }.should raise_error(ArgumentError)
- end
-
- it "raises an ArgumentError when the dumped class is missing" do
- Object.send(:const_set, :KaBoom, Class.new)
- kaboom = Marshal.dump(KaBoom.new)
- Object.send(:remove_const, :KaBoom)
-
- -> { Marshal.send(@method, kaboom) }.should raise_error(ArgumentError)
- end
-
- describe "when called with a proc" do
- it "returns the value of the proc" do
- Marshal.send(@method, Marshal.dump([1,2]), proc { [3,4] }).should == [3,4]
- end
-
- it "calls the proc for recursively visited data" do
- a = [1]
- a << a
- ret = []
- Marshal.send(@method, Marshal.dump(a), proc { |arg| ret << arg; arg })
- ret.first.should == 1
- ret[1].should == [1,a]
- ret[2].should == a
- ret.size.should == 3
- end
-
- it "loads an Array with proc" do
- arr = []
- s = 'hi'
- s.instance_variable_set(:@foo, 5)
- st = Struct.new("Brittle", :a).new
- st.instance_variable_set(:@clue, 'none')
- st.a = 0.0
- h = Hash.new('def')
- h['nine'] = 9
- a = [:a, :b, :c]
- a.instance_variable_set(:@two, 2)
- obj = [s, 10, s, s, st, a]
- obj.instance_variable_set(:@zoo, 'ant')
- proc = Proc.new { |o| arr << o; o}
-
- Marshal.send(@method, "\x04\bI[\vI\"\ahi\a:\x06EF:\t@fooi\ni\x0F@\x06@\x06IS:\x14Struct::Brittle\x06:\x06af\x060\x06:\n@clueI\"\tnone\x06;\x00FI[\b;\b:\x06b:\x06c\x06:\t@twoi\a\x06:\t@zooI\"\bant\x06;\x00F", proc)
-
- arr.should == ["hi", false, 5, 10, "hi", "hi", 0.0, st, "none", false,
- :b, :c, a, 2, ["hi", 10, "hi", "hi", st, [:a, :b, :c]], "ant", false]
-
- Struct.send(:remove_const, :Brittle)
- end
- end
-
- describe "when called with nil for the proc argument" do
- it "behaves as if no proc argument was passed" do
- a = [1]
- a << a
- b = Marshal.send(@method, Marshal.dump(a), nil)
- b.should == a
- end
- end
-
- describe "when called on objects with custom _dump methods" do
- it "does not set instance variables of an object with user-defined _dump/_load" do
- # this string represents: <#UserPreviouslyDefinedWithInitializedIvar @field2=7 @field1=6>
- dump_str = "\004\bu:-UserPreviouslyDefinedWithInitializedIvar\a:\f@field2i\f:\f@field1i\v"
-
- UserPreviouslyDefinedWithInitializedIvar.should_receive(:_load).and_return(UserPreviouslyDefinedWithInitializedIvar.new)
- marshaled_obj = Marshal.send(@method, dump_str)
-
- marshaled_obj.should be_an_instance_of(UserPreviouslyDefinedWithInitializedIvar)
- marshaled_obj.field1.should be_nil
- marshaled_obj.field2.should be_nil
- end
-
- describe "that return an immediate value" do
- it "loads an array containing an instance of the object, followed by multiple instances of another object" do
- str = "string"
-
- # this string represents: [<#UserDefinedImmediate A>, <#String "string">, <#String "string">]
- marshaled_obj = Marshal.send(@method, "\004\b[\bu:\031UserDefinedImmediate\000\"\vstring@\a")
-
- marshaled_obj.should == [nil, str, str]
- end
-
- it "loads any structure with multiple references to the same object, followed by multiple instances of another object" do
- str = "string"
-
- # this string represents: {a: <#UserDefinedImmediate A>, b: <#UserDefinedImmediate A>, c: <#String "string">, d: <#String "string">}
- hash_dump = "\x04\b{\t:\x06aIu:\x19UserDefinedImmediate\x00\x06:\x06ET:\x06b@\x06:\x06cI\"\vstring\x06;\aT:\x06d@\a"
-
- marshaled_obj = Marshal.send(@method, hash_dump)
- marshaled_obj.should == {a: nil, b: nil, c: str, d: str}
-
- # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate A>, <#String "string">, <#String "string">]
- array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ET@\x06I\"\vstring\x06;\x06T@\a"
-
- marshaled_obj = Marshal.send(@method, array_dump)
- marshaled_obj.should == [nil, nil, str, str]
- end
-
- it "loads an array containing references to multiple instances of the object, followed by multiple instances of another object" do
- str = "string"
-
- # this string represents: [<#UserDefinedImmediate A>, <#UserDefinedImmediate B>, <#String "string">, <#String "string">]
- array_dump = "\x04\b[\tIu:\x19UserDefinedImmediate\x00\x06:\x06ETIu;\x00\x00\x06;\x06TI\"\vstring\x06;\x06T@\b"
-
- marshaled_obj = Marshal.send(@method, array_dump)
- marshaled_obj.should == [nil, nil, str, str]
- end
- end
- end
-
- it "loads an array containing objects having _dump method, and with proc" do
- arr = []
- myproc = Proc.new { |o| arr << o; o }
- o1 = UserDefined.new;
- o2 = UserDefinedWithIvar.new
- obj = [o1, o2, o1, o2]
-
- Marshal.send(@method, "\x04\b[\tu:\x10UserDefined\x18\x04\b[\aI\"\nstuff\x06:\x06EF@\x06u:\x18UserDefinedWithIvar>\x04\b[\bI\"\nstuff\a:\x06EF:\t@foo:\x18UserDefinedWithIvarI\"\tmore\x06;\x00F@\a@\x06@\a", myproc)
-
- arr.should == [o1, o2, o1, o2, obj]
- end
-
- it "loads an array containing objects having marshal_dump method, and with proc" do
- arr = []
- proc = Proc.new { |o| arr << o; o }
- o1 = UserMarshal.new
- o2 = UserMarshalWithIvar.new
- obj = [o1, o2, o1, o2]
-
- Marshal.send(@method, "\004\b[\tU:\020UserMarshal\"\nstuffU:\030UserMarshalWithIvar[\006\"\fmy data@\006@\b", proc)
-
- arr.should == ['stuff', o1, 'my data', ['my data'], o2, o1, o2, obj]
- end
-
- it "assigns classes to nested subclasses of Array correctly" do
- arr = ArraySub.new(ArraySub.new)
- arr_dump = Marshal.dump(arr)
- Marshal.send(@method, arr_dump).class.should == ArraySub
- end
-
- it "loads subclasses of Array with overridden << and push correctly" do
- arr = ArraySubPush.new
- arr[0] = '1'
- arr_dump = Marshal.dump(arr)
- Marshal.send(@method, arr_dump).should == arr
- end
-
- it "raises a TypeError with bad Marshal version" do
- marshal_data = '\xff\xff'
- marshal_data[0] = (Marshal::MAJOR_VERSION).chr
- marshal_data[1] = (Marshal::MINOR_VERSION + 1).chr
-
- -> { Marshal.send(@method, marshal_data) }.should raise_error(TypeError)
-
- marshal_data = '\xff\xff'
- marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr
- marshal_data[1] = (Marshal::MINOR_VERSION).chr
-
- -> { Marshal.send(@method, marshal_data) }.should raise_error(TypeError)
- end
-
- it "raises EOFError on loading an empty file" do
- temp_file = tmp("marshal.rubyspec.tmp.#{Process.pid}")
- file = File.new(temp_file, "w+")
- begin
- -> { Marshal.send(@method, file) }.should raise_error(EOFError)
- ensure
- file.close
- rm_r temp_file
- end
- end
-
- ruby_version_is ''...'2.7' do
- it "returns an untainted object if source is untainted" do
- x = Object.new
- y = Marshal.send(@method, Marshal.dump(x))
- y.tainted?.should be_false
- end
-
- describe "when source is tainted" do
- it "returns a tainted object" do
- x = Object.new
- x.taint
- s = Marshal.dump(x)
- y = Marshal.send(@method, s)
- y.tainted?.should be_true
-
- # note that round-trip via Marshal does not preserve
- # the taintedness at each level of the nested structure
- y = Marshal.send(@method, Marshal.dump([[x]]))
- y.tainted?.should be_true
- y.first.tainted?.should be_true
- y.first.first.tainted?.should be_true
- end
-
- it "does not taint Symbols" do
- x = [:x]
- y = Marshal.send(@method, Marshal.dump(x).taint)
- y.tainted?.should be_true
- y.first.tainted?.should be_false
- end
-
- it "does not taint Fixnums" do
- x = [1]
- y = Marshal.send(@method, Marshal.dump(x).taint)
- y.tainted?.should be_true
- y.first.tainted?.should be_false
- end
-
- it "does not taint Bignums" do
- x = [bignum_value]
- y = Marshal.send(@method, Marshal.dump(x).taint)
- y.tainted?.should be_true
- y.first.tainted?.should be_false
- end
-
- it "does not taint Floats" do
- x = [1.2]
- y = Marshal.send(@method, Marshal.dump(x).taint)
- y.tainted?.should be_true
- y.first.tainted?.should be_false
- end
- end
-
- it "preserves taintedness of nested structure" do
- x = Object.new
- a = [[x]]
- x.taint
- y = Marshal.send(@method, Marshal.dump(a))
- y.tainted?.should be_true
- y.first.tainted?.should be_true
- y.first.first.tainted?.should be_true
- end
-
- it "returns a trusted object if source is trusted" do
- x = Object.new
- y = Marshal.send(@method, Marshal.dump(x))
- y.untrusted?.should be_false
- end
-
- it "returns an untrusted object if source is untrusted" do
- x = Object.new
- x.untrust
- y = Marshal.send(@method, Marshal.dump(x))
- y.untrusted?.should be_true
-
- # note that round-trip via Marshal does not preserve
- # the untrustedness at each level of the nested structure
- y = Marshal.send(@method, Marshal.dump([[x]]))
- y.untrusted?.should be_true
- y.first.untrusted?.should be_true
- y.first.first.untrusted?.should be_true
- end
- end
-
- # Note: Ruby 1.9 should be compatible with older marshal format
- MarshalSpec::DATA.each do |description, (object, marshal, attributes)|
- it "loads a #{description}" do
- Marshal.send(@method, marshal).should == object
- end
- end
-
- MarshalSpec::DATA_19.each do |description, (object, marshal, attributes)|
- it "loads a #{description}" do
- Marshal.send(@method, marshal).should == object
- end
- end
-
- describe "for an Array" do
- it "loads an array containing the same objects" do
- s = 'oh'
- b = 'hi'
- r = //
- d = [b, :no, s, :go]
- c = String
- f = 1.0
-
- o1 = UserMarshalWithIvar.new; o2 = UserMarshal.new
-
- obj = [:so, 'hello', 100, :so, :so, d, :so, o2, :so, :no, o2,
- :go, c, nil, Struct::Pyramid.new, f, :go, :no, s, b, r,
- :so, 'huh', o1, true, b, b, 99, r, b, s, :so, f, c, :no, o1, d]
-
- Marshal.send(@method, "\004\b[*:\aso\"\nhelloii;\000;\000[\t\"\ahi:\ano\"\aoh:\ago;\000U:\020UserMarshal\"\nstuff;\000;\006@\n;\ac\vString0S:\024Struct::Pyramid\000f\0061;\a;\006@\t@\b/\000\000;\000\"\bhuhU:\030UserMarshalWithIvar[\006\"\fmy dataT@\b@\bih@\017@\b@\t;\000@\016@\f;\006@\021@\a").should ==
- obj
- end
-
- it "loads an array having ivar" do
- s = 'well'
- s.instance_variable_set(:@foo, 10)
- obj = ['5', s, 'hi'].extend(Meths, MethsMore)
- obj.instance_variable_set(:@mix, s)
- new_obj = Marshal.send(@method, "\004\bI[\b\"\0065I\"\twell\006:\t@fooi\017\"\ahi\006:\t@mix@\a")
- new_obj.should == obj
- new_obj.instance_variable_get(:@mix).should equal new_obj[1]
- new_obj[1].instance_variable_get(:@foo).should == 10
- end
-
- it "loads an extended Array object containing a user-marshaled object" do
- obj = [UserMarshal.new, UserMarshal.new].extend(Meths)
- new_obj = Marshal.send(@method, "\x04\be:\nMeths[\ao:\x10UserMarshal\x06:\n@dataI\"\nstuff\x06:\x06ETo;\x06\x06;\aI\"\nstuff\x06;\bT")
-
- new_obj.should == obj
- obj_ancestors = class << obj; ancestors[1..-1]; end
- new_obj_ancestors = class << new_obj; ancestors[1..-1]; end
- obj_ancestors.should == new_obj_ancestors
- end
- end
-
- describe "for a Hash" do
- it "loads an extended_user_hash with a parameter to initialize" do
- obj = UserHashInitParams.new(:abc).extend(Meths)
-
- new_obj = Marshal.send(@method, "\004\bIe:\nMethsC:\027UserHashInitParams{\000\006:\a@a:\babc")
-
- new_obj.should == obj
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class].should == Meths
- new_obj_metaclass_ancestors[@num_self_class+1].should == UserHashInitParams
- end
-
- it "loads an extended hash object containing a user-marshaled object" do
- obj = {a: UserMarshal.new}.extend(Meths)
-
- new_obj = Marshal.send(@method, "\004\be:\nMeths{\006:\006aU:\020UserMarshal\"\nstuff")
-
- new_obj.should == obj
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class].should == Meths
- new_obj_metaclass_ancestors[@num_self_class+1].should == Hash
- end
-
- it "preserves hash ivars when hash contains a string having ivar" do
- s = 'string'
- s.instance_variable_set :@string_ivar, 'string ivar'
- h = { key: s }
- h.instance_variable_set :@hash_ivar, 'hash ivar'
-
- unmarshalled = Marshal.send(@method, Marshal.dump(h))
- unmarshalled.instance_variable_get(:@hash_ivar).should == 'hash ivar'
- unmarshalled[:key].instance_variable_get(:@string_ivar).should == 'string ivar'
- end
- end
-
- describe "for a Symbol" do
- it "loads a Symbol" do
- sym = Marshal.send(@method, "\004\b:\vsymbol")
- sym.should == :symbol
- sym.encoding.should == Encoding::US_ASCII
- end
-
- it "loads a big Symbol" do
- sym = ('big' * 100).to_sym
- Marshal.send(@method, "\004\b:\002,\001#{'big' * 100}").should == sym
- end
-
- it "loads an encoded Symbol" do
- s = "\u2192"
-
- sym = Marshal.send(@method, "\x04\bI:\b\xE2\x86\x92\x06:\x06ET")
- sym.should == s.encode("utf-8").to_sym
- sym.encoding.should == Encoding::UTF_8
-
- sym = Marshal.send(@method, "\x04\bI:\t\xFE\xFF!\x92\x06:\rencoding\"\vUTF-16")
- sym.should == s.encode("utf-16").to_sym
- sym.encoding.should == Encoding::UTF_16
-
- sym = Marshal.send(@method, "\x04\bI:\a\x92!\x06:\rencoding\"\rUTF-16LE")
- sym.should == s.encode("utf-16le").to_sym
- sym.encoding.should == Encoding::UTF_16LE
-
- sym = Marshal.send(@method, "\x04\bI:\a!\x92\x06:\rencoding\"\rUTF-16BE")
- sym.should == s.encode("utf-16be").to_sym
- sym.encoding.should == Encoding::UTF_16BE
-
- sym = Marshal.send(@method, "\x04\bI:\a\xA2\xAA\x06:\rencoding\"\vEUC-JP")
- sym.should == s.encode("euc-jp").to_sym
- sym.encoding.should == Encoding::EUC_JP
-
- sym = Marshal.send(@method, "\x04\bI:\a\x81\xA8\x06:\rencoding\"\x10Windows-31J")
- sym.should == s.encode("sjis").to_sym
- sym.encoding.should == Encoding::SJIS
- end
-
- it "loads a binary encoded Symbol" do
- s = "\u2192".force_encoding("binary").to_sym
- sym = Marshal.send(@method, "\x04\b:\b\xE2\x86\x92")
- sym.should == s
- sym.encoding.should == Encoding::BINARY
- end
- end
-
- describe "for a String" do
- it "loads a string having ivar with ref to self" do
- obj = 'hi'
- obj.instance_variable_set(:@self, obj)
- Marshal.send(@method, "\004\bI\"\ahi\006:\n@self@\000").should == obj
- end
-
- it "loads a string through StringIO stream" do
- require 'stringio'
- obj = "This is a string which should be unmarshalled through StringIO stream!"
- Marshal.send(@method, StringIO.new(Marshal.dump(obj))).should == obj
- end
-
- it "loads a string with an ivar" do
- str = Marshal.send(@method, "\x04\bI\"\x00\x06:\t@fooI\"\bbar\x06:\x06EF")
- str.instance_variable_get("@foo").should == "bar"
- end
-
- it "loads a String subclass with custom constructor" do
- str = Marshal.send(@method, "\x04\bC: UserCustomConstructorString\"\x00")
- str.should be_an_instance_of(UserCustomConstructorString)
- end
-
- it "loads a US-ASCII String" do
- str = "abc".force_encoding("us-ascii")
- data = "\x04\bI\"\babc\x06:\x06EF"
- result = Marshal.send(@method, data)
- result.should == str
- result.encoding.should equal(Encoding::US_ASCII)
- end
-
- it "loads a UTF-8 String" do
- str = "\x6d\xc3\xb6\x68\x72\x65".force_encoding("utf-8")
- data = "\x04\bI\"\vm\xC3\xB6hre\x06:\x06ET"
- result = Marshal.send(@method, data)
- result.should == str
- result.encoding.should equal(Encoding::UTF_8)
- end
-
- it "loads a String in another encoding" do
- str = "\x6d\x00\xf6\x00\x68\x00\x72\x00\x65\x00".force_encoding("utf-16le")
- data = "\x04\bI\"\x0Fm\x00\xF6\x00h\x00r\x00e\x00\x06:\rencoding\"\rUTF-16LE"
- result = Marshal.send(@method, data)
- result.should == str
- result.encoding.should equal(Encoding::UTF_16LE)
- end
-
- it "loads a String as BINARY if no encoding is specified at the end" do
- str = "\xC3\xB8".force_encoding("BINARY")
- data = "\x04\b\"\a\xC3\xB8".force_encoding("UTF-8")
- result = Marshal.send(@method, data)
- result.encoding.should == Encoding::BINARY
- result.should == str
- end
- end
-
- describe "for a Struct" do
- it "loads a extended_struct having fields with same objects" do
- s = 'hi'
- obj = Struct.new("Ure2", :a, :b).new.extend(Meths)
- obj.a = [:a, s]
- obj.b = [:Meths, s]
-
- Marshal.send(@method,
- "\004\be:\nMethsS:\021Struct::Ure2\a:\006a[\a;\a\"\ahi:\006b[\a;\000@\a"
- ).should == obj
- Struct.send(:remove_const, :Ure2)
- end
-
- it "loads a struct having ivar" do
- obj = Struct.new("Thick").new
- obj.instance_variable_set(:@foo, 5)
- Marshal.send(@method, "\004\bIS:\022Struct::Thick\000\006:\t@fooi\n").should == obj
- Struct.send(:remove_const, :Thick)
- end
-
- it "loads a struct having fields" do
- obj = Struct.new("Ure1", :a, :b).new
- Marshal.send(@method, "\004\bS:\021Struct::Ure1\a:\006a0:\006b0").should == obj
- Struct.send(:remove_const, :Ure1)
- end
-
- it "does not call initialize on the unmarshaled struct" do
- threadlocal_key = MarshalSpec::StructWithUserInitialize::THREADLOCAL_KEY
-
- s = MarshalSpec::StructWithUserInitialize.new('foo')
- Thread.current[threadlocal_key].should == ['foo']
- s.a.should == 'foo'
-
- Thread.current[threadlocal_key] = nil
-
- dumped = Marshal.dump(s)
- loaded = Marshal.send(@method, dumped)
-
- Thread.current[threadlocal_key].should == nil
- loaded.a.should == 'foo'
- end
- end
-
- describe "for an Exception" do
- it "loads a marshalled exception with no message" do
- obj = Exception.new
- loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg0")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesg0:\abt0")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- end
-
- it "loads a marshalled exception with a message" do
- obj = Exception.new("foo")
- loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt0:\tmesg\"\bfoo")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt0")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- end
-
- it "loads a marshalled exception with a backtrace" do
- obj = Exception.new("foo")
- obj.set_backtrace(["foo/bar.rb:10"])
- loaded = Marshal.send(@method, "\004\bo:\016Exception\a:\abt[\006\"\022foo/bar.rb:10:\tmesg\"\bfoo")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- loaded = Marshal.send(@method, "\x04\bo:\x0EException\a:\tmesgI\"\bfoo\x06:\x06EF:\abt[\x06I\"\x12foo/bar.rb:10\x06;\aF")
- loaded.message.should == obj.message
- loaded.backtrace.should == obj.backtrace
- end
-
- it "loads an marshalled exception with ivars" do
- s = 'hi'
- arr = [:so, :so, s, s]
- obj = Exception.new("foo")
- obj.instance_variable_set :@arr, arr
-
- loaded = Marshal.send(@method, "\x04\bo:\x0EException\b:\tmesg\"\bfoo:\abt0:\t@arr[\t:\aso;\t\"\ahi@\b")
- new_arr = loaded.instance_variable_get :@arr
-
- loaded.message.should == obj.message
- new_arr.should == arr
- end
- end
-
- describe "for an Object" do
- it "loads an object" do
- Marshal.send(@method, "\004\bo:\vObject\000").should be_kind_of(Object)
- end
-
- it "loads an extended Object" do
- obj = Object.new.extend(Meths)
-
- new_obj = Marshal.send(@method, "\004\be:\nMethso:\vObject\000")
-
- new_obj.class.should == obj.class
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class, 2].should == [Meths, Object]
- end
-
- it "loads an object having ivar" do
- s = 'hi'
- arr = [:so, :so, s, s]
- obj = Object.new
- obj.instance_variable_set :@str, arr
-
- new_obj = Marshal.send(@method, "\004\bo:\vObject\006:\t@str[\t:\aso;\a\"\ahi@\a")
- new_str = new_obj.instance_variable_get :@str
-
- new_str.should == arr
- end
-
- it "loads an Object with a non-US-ASCII instance variable" do
- ivar = "@é".force_encoding(Encoding::UTF_8).to_sym
- obj = Marshal.send(@method, "\x04\bo:\vObject\x06I:\b@\xC3\xA9\x06:\x06ETi\x06")
- obj.instance_variables.should == [ivar]
- obj.instance_variables[0].encoding.should == Encoding::UTF_8
- obj.instance_variable_get(ivar).should == 1
- end
-
- it "raises ArgumentError if the object from an 'o' stream is not dumpable as 'o' type user class" do
- -> do
- Marshal.send(@method, "\x04\bo:\tFile\001\001:\001\005@path\"\x10/etc/passwd")
- end.should raise_error(ArgumentError)
- end
- end
-
- describe "for a user object" do
- it "loads a user-marshaled extended object" do
- obj = UserMarshal.new.extend(Meths)
-
- new_obj = Marshal.send(@method, "\004\bU:\020UserMarshal\"\nstuff")
-
- new_obj.should == obj
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class].should == UserMarshal
- end
-
- it "loads a UserObject" do
- Marshal.send(@method, "\004\bo:\017UserObject\000").should be_kind_of(UserObject)
- end
-
- describe "that extends a core type other than Object or BasicObject" do
- after :each do
- MarshalSpec.reset_swapped_class
- end
-
- it "raises ArgumentError if the resulting class does not extend the same type" do
- MarshalSpec.set_swapped_class(Class.new(Hash))
- data = Marshal.dump(MarshalSpec::SwappedClass.new)
-
- MarshalSpec.set_swapped_class(Class.new(Array))
- -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
-
- MarshalSpec.set_swapped_class(Class.new)
- -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
- end
- end
- end
-
- describe "for a Regexp" do
- it "loads an extended Regexp" do
- obj = /[a-z]/.dup.extend(Meths, MethsMore)
- new_obj = Marshal.send(@method, "\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
-
- new_obj.should == obj
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class, 3].should ==
- [Meths, MethsMore, Regexp]
- end
-
- it "loads a extended_user_regexp having ivar" do
- obj = UserRegexp.new('').extend(Meths)
- obj.instance_variable_set(:@noise, 'much')
-
- new_obj = Marshal.send(@method, "\004\bIe:\nMethsC:\017UserRegexp/\000\000\006:\v@noise\"\tmuch")
-
- new_obj.should == obj
- new_obj.instance_variable_get(:@noise).should == 'much'
- new_obj_metaclass_ancestors = class << new_obj; ancestors; end
- new_obj_metaclass_ancestors[@num_self_class, 3].should ==
- [Meths, UserRegexp, Regexp]
- end
- end
-
- describe "for a Float" do
- it "loads a Float NaN" do
- obj = 0.0 / 0.0
- Marshal.send(@method, "\004\bf\bnan").to_s.should == obj.to_s
- end
-
- it "loads a Float 1.3" do
- Marshal.send(@method, "\004\bf\v1.3\000\314\315").should == 1.3
- end
-
- it "loads a Float -5.1867345e-22" do
- obj = -5.1867345e-22
- Marshal.send(@method, "\004\bf\037-5.1867345000000008e-22\000\203_").should be_close(obj, 1e-30)
- end
-
- it "loads a Float 1.1867345e+22" do
- obj = 1.1867345e+22
- Marshal.send(@method, "\004\bf\0361.1867344999999999e+22\000\344@").should == obj
- end
- end
-
- describe "for an Integer" do
- it "loads 0" do
- Marshal.send(@method, "\004\bi\000").should == 0
- Marshal.send(@method, "\004\bi\005").should == 0
- end
-
- it "loads an Integer 8" do
- Marshal.send(@method, "\004\bi\r" ).should == 8
- end
-
- it "loads and Integer -8" do
- Marshal.send(@method, "\004\bi\363" ).should == -8
- end
-
- it "loads an Integer 1234" do
- Marshal.send(@method, "\004\bi\002\322\004").should == 1234
- end
-
- it "loads an Integer -1234" do
- Marshal.send(@method, "\004\bi\376.\373").should == -1234
- end
-
- it "loads an Integer 4611686018427387903" do
- Marshal.send(@method, "\004\bl+\t\377\377\377\377\377\377\377?").should == 4611686018427387903
- end
-
- it "loads an Integer -4611686018427387903" do
- Marshal.send(@method, "\004\bl-\t\377\377\377\377\377\377\377?").should == -4611686018427387903
- end
-
- it "loads an Integer 2361183241434822606847" do
- Marshal.send(@method, "\004\bl+\n\377\377\377\377\377\377\377\377\177\000").should == 2361183241434822606847
- end
-
- it "loads an Integer -2361183241434822606847" do
- Marshal.send(@method, "\004\bl-\n\377\377\377\377\377\377\377\377\177\000").should == -2361183241434822606847
- end
-
- it "raises ArgumentError if the input is too short" do
- ["\004\bi",
- "\004\bi\001",
- "\004\bi\002",
- "\004\bi\002\0",
- "\004\bi\003",
- "\004\bi\003\0",
- "\004\bi\003\0\0",
- "\004\bi\004",
- "\004\bi\004\0",
- "\004\bi\004\0\0",
- "\004\bi\004\0\0\0"].each do |invalid|
- -> { Marshal.send(@method, invalid) }.should raise_error(ArgumentError)
- end
- end
-
- if 0.size == 8 # for platforms like x86_64
- it "roundtrips 4611686018427387903 from dump/load correctly" do
- Marshal.send(@method, Marshal.dump(4611686018427387903)).should == 4611686018427387903
- end
- end
- end
-
- describe "for a Rational" do
- it "loads" do
- Marshal.send(@method, Marshal.dump(Rational(1, 3))).should == Rational(1, 3)
- end
- end
-
- describe "for a Complex" do
- it "loads" do
- Marshal.send(@method, Marshal.dump(Complex(4, 3))).should == Complex(4, 3)
- end
- end
-
- describe "for a Bignum" do
- platform_is wordsize: 64 do
- context "that is Bignum on 32-bit platforms but Fixnum on 64-bit" do
- it "dumps a Fixnum" do
- val = Marshal.send(@method, "\004\bl+\ab:wU")
- val.should == 1433877090
- val.class.should == Integer
- end
-
- it "dumps an array containing multiple references to the Bignum as an array of Fixnum" do
- arr = Marshal.send(@method, "\004\b[\al+\a\223BwU@\006")
- arr.should == [1433879187, 1433879187]
- arr.each { |v| v.class.should == Integer }
- end
- end
- end
- end
-
- describe "for a Time" do
- it "loads" do
- Marshal.send(@method, Marshal.dump(Time.at(1))).should == Time.at(1)
- end
-
- it "loads serialized instance variables" do
- t = Time.new
- t.instance_variable_set(:@foo, 'bar')
-
- Marshal.send(@method, Marshal.dump(t)).instance_variable_get(:@foo).should == 'bar'
- end
-
- it "loads Time objects stored as links" do
- t = Time.new
-
- t1, t2 = Marshal.send(@method, Marshal.dump([t, t]))
- t1.should equal t2
- end
-
- it "loads the zone" do
- with_timezone 'AST', 3 do
- t = Time.local(2012, 1, 1)
- Marshal.send(@method, Marshal.dump(t)).zone.should == t.zone
- end
- end
-
- it "loads nanoseconds" do
- t = Time.now
- Marshal.send(@method, Marshal.dump(t)).nsec.should == t.nsec
- end
- end
-
- describe "for nil" do
- it "loads" do
- Marshal.send(@method, "\x04\b0").should be_nil
- end
- end
-
- describe "for true" do
- it "loads" do
- Marshal.send(@method, "\x04\bT").should be_true
- end
- end
-
- describe "for false" do
- it "loads" do
- Marshal.send(@method, "\x04\bF").should be_false
- end
- end
-
- describe "for a Class" do
- it "loads" do
- Marshal.send(@method, "\x04\bc\vString").should == String
- end
-
- it "raises ArgumentError if given the name of a non-Module" do
- -> { Marshal.send(@method, "\x04\bc\vKernel") }.should raise_error(ArgumentError)
- end
-
- it "raises ArgumentError if given a nonexistent class" do
- -> { Marshal.send(@method, "\x04\bc\vStrung") }.should raise_error(ArgumentError)
- end
- end
-
- describe "for a Module" do
- it "loads a module" do
- Marshal.send(@method, "\x04\bm\vKernel").should == Kernel
- end
-
- it "raises ArgumentError if given the name of a non-Class" do
- -> { Marshal.send(@method, "\x04\bm\vString") }.should raise_error(ArgumentError)
- end
-
- it "loads an old module" do
- Marshal.send(@method, "\x04\bM\vKernel").should == Kernel
- end
- end
-
- describe "for a wrapped C pointer" do
- it "loads" do
- class DumpableDir < Dir
- def _dump_data
- path
- end
- def _load_data path
- initialize(path)
- end
- end
-
- data = "\x04\bd:\x10DumpableDirI\"\x06.\x06:\x06ET"
-
- dir = Marshal.send(@method, data)
- begin
- dir.path.should == '.'
- ensure
- dir.close
- end
- end
-
- it "raises TypeError when the local class is missing _load_data" do
- class UnloadableDumpableDir < Dir
- def _dump_data
- path
- end
- # no _load_data
- end
-
- data = "\x04\bd:\x1AUnloadableDumpableDirI\"\x06.\x06:\x06ET"
-
- -> { Marshal.send(@method, data) }.should raise_error(TypeError)
- end
-
- it "raises ArgumentError when the local class is a regular object" do
- data = "\004\bd:\020UserDefined\0"
-
- -> { Marshal.send(@method, data) }.should raise_error(ArgumentError)
- end
- end
-
- describe "when a class does not exist in the namespace" do
- before :each do
- NamespaceTest.send(:const_set, :SameName, Class.new)
- @data = Marshal.dump(NamespaceTest::SameName.new)
- NamespaceTest.send(:remove_const, :SameName)
- end
-
- it "raises an ArgumentError" do
- message = "undefined class/module NamespaceTest::SameName"
- -> { Marshal.send(@method, @data) }.should raise_error(ArgumentError, message)
- end
- end
-
- it "raises an ArgumentError with full constant name when the dumped constant is missing" do
- NamespaceTest.send(:const_set, :KaBoom, Class.new)
- @data = Marshal.dump(NamespaceTest::KaBoom.new)
- NamespaceTest.send(:remove_const, :KaBoom)
-
- -> { Marshal.send(@method, @data) }.should raise_error(ArgumentError, /NamespaceTest::KaBoom/)
- end
-end