summaryrefslogtreecommitdiff
path: root/test/ruby/test_marshal.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_marshal.rb')
-rw-r--r--test/ruby/test_marshal.rb1018
1 files changed, 980 insertions, 38 deletions
diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb
index d40c9da4d4..48a67e1dc5 100644
--- a/test/ruby/test_marshal.rb
+++ b/test/ruby/test_marshal.rb
@@ -1,28 +1,24 @@
+# frozen_string_literal: false
require 'test/unit'
-dir = File.dirname(File.expand_path(__FILE__))
-orgpath = $:.dup
-begin
- $:.push(dir)
- require 'marshaltestlib'
-ensure
- $:.replace(orgpath)
-end
+require_relative 'marshaltestlib'
class TestMarshal < Test::Unit::TestCase
include MarshalTestLib
+ def setup
+ @verbose = $VERBOSE
+ end
+
+ def teardown
+ $VERBOSE = @verbose
+ end
+
def encode(o)
- stress, GC.stress = GC.stress, true
Marshal.dump(o)
- ensure
- GC.stress = stress
end
def decode(s)
- stress, GC.stress = GC.stress, true
Marshal.load(s)
- ensure
- GC.stress = stress
end
def fact(n)
@@ -35,27 +31,79 @@ class TestMarshal < Test::Unit::TestCase
return f
end
- StrClone=String.clone;
-
def test_marshal
- $x = [1,2,3,[4,5,"foo"],{1=>"bar"},2.5,fact(30)]
- $y = Marshal.dump($x)
- assert_equal($x, Marshal.load($y))
+ a = [1, 2, 3, 2**32, 2**64, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)]
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ [[1,2,3,4], [81, 2, 118, 3146]].each { |w,x,y,z|
+ obj = (x.to_f + y.to_f / z.to_f) * Math.exp(w.to_f / (x.to_f + y.to_f / z.to_f))
+ assert_equal obj, Marshal.load(Marshal.dump(obj))
+ }
+
+ bug3659 = '[ruby-dev:41936]'
+ [1.0, 10.0, 100.0, 110.0].each {|x|
+ assert_equal(x, Marshal.load(Marshal.dump(x)), bug3659)
+ }
+ end
+
+ def test_marshal_integers
+ a = []
+ [-2, -1, 0, 1, 2].each do |i|
+ 0.upto(65).map do |exp|
+ a << 2**exp + i
+ end
+ end
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ a = [2**32, []]*2
+ assert_equal a, Marshal.load(Marshal.dump(a))
+
+ a = [2**32, 2**32, []]*2
+ assert_equal a, Marshal.load(Marshal.dump(a))
+ end
+
+ def test_marshal_small_bignum_backref
+ assert_equal [2**32, 2**32], Marshal.load("\x04\b[\al+\b\x00\x00\x00\x00\x01\x00@\x06")
+ end
+ StrClone = String.clone
+ def test_marshal_cloned_class
assert_instance_of(StrClone, Marshal.load(Marshal.dump(StrClone.new("abc"))))
+ end
- [[1,2,3,4], [81, 2, 118, 3146]].each { |w,x,y,z|
- a = (x.to_f + y.to_f / z.to_f) * Math.exp(w.to_f / (x.to_f + y.to_f / z.to_f))
- ma = Marshal.dump(a)
- b = Marshal.load(ma)
- assert_equal(a, b)
+ def test_inconsistent_struct
+ TestMarshal.const_set :StructOrNot, Struct.new(:a)
+ s = Marshal.dump(StructOrNot.new(1))
+ TestMarshal.instance_eval { remove_const :StructOrNot }
+ TestMarshal.const_set :StructOrNot, Class.new
+ assert_raise(TypeError, "[ruby-dev:31709]") { Marshal.load(s) }
+ ensure
+ TestMarshal.instance_eval { remove_const :StructOrNot }
+ end
+
+ def test_struct_invalid_members
+ TestMarshal.const_set :StructInvalidMembers, Struct.new(:a)
+ assert_raise(TypeError, "[ruby-dev:31759]") {
+ Marshal.load("\004\bIc&TestMarshal::StructInvalidMembers\006:\020__members__\"\bfoo")
+ TestMarshal::StructInvalidMembers.members
}
+ ensure
+ TestMarshal.instance_eval { remove_const :StructInvalidMembers }
+ end
+
+ def test_load_range_as_struct
+ assert_raise(TypeError, 'GH-6832') do
+ # Can be obtained with:
+ # $ ruby -e 'Range = Struct.new(:a, :b, :c); p Marshal.dump(Range.new(nil, nil, nil))'
+ Marshal.load("\x04\bS:\nRange\b:\x06a0:\x06b0:\x06c0")
+ end
end
class C
def initialize(str)
@str = str
end
+ attr_reader :str
def _dump(limit)
@str
end
@@ -65,16 +113,233 @@ class TestMarshal < Test::Unit::TestCase
end
def test_too_long_string
- (data = Marshal.dump(C.new("a")))[-2, 1] = "\003\377\377\377"
- e = assert_raise(ArgumentError, "[ruby-dev:32054]") {
+ data = Marshal.dump(C.new("a".force_encoding("ascii-8bit")))
+ data[-2, 1] = "\003\377\377\377"
+ assert_raise_with_message(ArgumentError, "marshal data too short", "[ruby-dev:32054]") {
Marshal.load(data)
}
- assert_equal("marshal data too short", e.message)
+ end
+
+
+ def test_userdef_encoding
+ s1 = "\xa4\xa4".force_encoding("euc-jp")
+ o1 = C.new(s1)
+ m = Marshal.dump(o1)
+ o2 = Marshal.load(m)
+ s2 = o2.str
+ assert_equal(s1, s2)
+ end
+
+ def test_pipe
+ o1 = C.new("a" * 10000)
+
+ IO.pipe do |r, w|
+ th = Thread.new {Marshal.dump(o1, w)}
+ o2 = Marshal.load(r)
+ th.join
+ assert_equal(o1.str, o2.str)
+ end
+
+ IO.pipe do |r, w|
+ th = Thread.new {Marshal.dump(o1, w, 2)}
+ o2 = Marshal.load(r)
+ th.join
+ assert_equal(o1.str, o2.str)
+ end
+
+ assert_raise(TypeError) { Marshal.dump("foo", Object.new) }
+ assert_raise(TypeError) { Marshal.load(Object.new) }
+ end
+
+ def test_limit
+ assert_equal([[[]]], Marshal.load(Marshal.dump([[[]]], 3)))
+ assert_raise(ArgumentError) { Marshal.dump([[[]]], 2) }
+ assert_nothing_raised(ArgumentError, '[ruby-core:24100]') { Marshal.dump("\u3042", 1) }
+ end
+
+ def test_userdef_invalid
+ o = C.new(nil)
+ assert_raise(TypeError) { Marshal.dump(o) }
+ end
+
+ def test_class
+ o = class << Object.new; self; end
+ assert_raise(TypeError) { Marshal.dump(o) }
+ assert_equal(Object, Marshal.load(Marshal.dump(Object)))
+ assert_equal(Enumerable, Marshal.load(Marshal.dump(Enumerable)))
+ end
+
+ class C2
+ def initialize(ary)
+ @ary = ary
+ end
+ def _dump(s)
+ @ary.clear
+ "foo"
+ end
+ end
+
+ def test_modify_array_during_dump
+ a = []
+ o = C2.new(a)
+ a << o << nil
+ assert_raise(RuntimeError) { Marshal.dump(a) }
+ end
+
+ def test_change_class_name
+ self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3)
+ eval("class C3; def _dump(s); 'foo'; end; end")
+ m = Marshal.dump(C3.new)
+ assert_raise(TypeError) { Marshal.load(m) }
+ self.class.__send__(:remove_const, :C3)
+ eval("C3 = nil")
+ assert_raise(TypeError) { Marshal.load(m) }
+ ensure
+ self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3)
+ end
+
+ def test_change_struct
+ self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3)
+ eval("C3 = Struct.new(:foo, :bar)")
+ m = Marshal.dump(C3.new("FOO", "BAR"))
+ self.class.__send__(:remove_const, :C3)
+ eval("C3 = Struct.new(:foo)")
+ assert_raise(TypeError) { Marshal.load(m) }
+ self.class.__send__(:remove_const, :C3)
+ eval("C3 = Struct.new(:foo, :baz)")
+ assert_raise(TypeError) { Marshal.load(m) }
+ ensure
+ self.class.__send__(:remove_const, :C3) if self.class.const_defined?(:C3)
+ end
+
+ class C4
+ def initialize(gc)
+ @gc = gc
+ end
+ def _dump(s)
+ GC.start if @gc
+ "foo"
+ end
+ end
+
+ def test_gc
+ assert_nothing_raised do
+ Marshal.dump((0..1000).map {|x| C4.new(x % 50 == 25) })
+ end
+ end
+
+ def test_symbol2
+ [:ruby, :"\u{7d05}\u{7389}"].each do |sym|
+ assert_equal(sym, Marshal.load(Marshal.dump(sym)), '[ruby-core:24788]')
+ end
+ bug2548 = '[ruby-core:27375]'
+ ary = [:$1, nil]
+ assert_equal(ary, Marshal.load(Marshal.dump(ary)), bug2548)
+ end
+
+ def test_symlink
+ assert_include(Marshal.dump([:a, :a]), ';')
+ end
+
+ def test_symlink_in_ivar
+ bug10991 = '[ruby-core:68587] [Bug #10991]'
+ sym = Marshal.load("\x04\x08" +
+ "I" ":\x0bKernel" +
+ ("\x06" +
+ ("I" ":\x07@a" +
+ ("\x06" ":\x07@b" "e;\x0""o:\x0bObject""\x0")) +
+ "0"))
+ assert_equal(:Kernel, sym, bug10991)
+ end
+
+ ClassUTF8 = eval("class R\u{e9}sum\u{e9}; self; end")
+
+ iso_8859_1 = Encoding::ISO_8859_1
+
+ structISO8859_1 = Struct.new("r\xe9sum\xe9".force_encoding(iso_8859_1).intern)
+ const_set("R\xe9sum\xe9".force_encoding(iso_8859_1), structISO8859_1)
+ structISO8859_1.name
+ StructISO8859_1 = structISO8859_1
+ classISO8859_1 = Class.new do
+ attr_accessor "r\xe9sum\xe9".force_encoding(iso_8859_1)
+ eval("def initialize(x) @r\xe9sum\xe9 = x; end".force_encoding(iso_8859_1))
+ end
+ const_set("R\xe9sum\xe92".force_encoding(iso_8859_1), classISO8859_1)
+ classISO8859_1.name
+ ClassISO8859_1 = classISO8859_1
+
+ moduleUTF8 = const_set("C\u{30af 30e9 30b9}", Module.new)
+ moduleUTF8.name
+ ModuleUTF8 = moduleUTF8
+
+ def test_nonascii_class_instance
+ a = ClassUTF8.new
+ assert_instance_of(ClassUTF8, Marshal.load(Marshal.dump(a)), '[ruby-core:24790]')
+
+ bug1932 = '[ruby-core:24882]'
+
+ a = StructISO8859_1.new(10)
+ assert_nothing_raised(bug1932) do
+ assert_equal(a, Marshal.load(Marshal.dump(a)), bug1932)
+ end
+ a.__send__("#{StructISO8859_1.members[0]}=", a)
+ assert_nothing_raised(bug1932) do
+ assert_equal(a, Marshal.load(Marshal.dump(a)), bug1932)
+ end
+
+ a = ClassISO8859_1.new(10)
+ assert_nothing_raised(bug1932) do
+ b = Marshal.load(Marshal.dump(a))
+ assert_equal(ClassISO8859_1, b.class, bug1932)
+ assert_equal(a.instance_variables, b.instance_variables, bug1932)
+ a.instance_variables.each do |i|
+ assert_equal(a.instance_variable_get(i), b.instance_variable_get(i), bug1932)
+ end
+ end
+ a.__send__(a.methods(true).grep(/=\z/)[0], a)
+ assert_nothing_raised(bug1932) do
+ b = Marshal.load(Marshal.dump(a))
+ assert_equal(ClassISO8859_1, b.class, bug1932)
+ assert_equal(a.instance_variables, b.instance_variables, bug1932)
+ assert_equal(b, b.instance_variable_get(a.instance_variables[0]), bug1932)
+ end
+ end
+
+ def test_nonascii_class_module
+ assert_same(ClassUTF8, Marshal.load(Marshal.dump(ClassUTF8)))
+ assert_same(ClassISO8859_1, Marshal.load(Marshal.dump(ClassISO8859_1)))
+ assert_same(ModuleUTF8, Marshal.load(Marshal.dump(ModuleUTF8)))
+ end
+
+ def test_regexp2
+ assert_equal(/\\u/, Marshal.load("\004\b/\b\\\\u\000"))
+ assert_equal(/u/, Marshal.load("\004\b/\a\\u\000"))
+ assert_raise(FrozenError) { Marshal.load("\x04\bI/\x06u\x00\a:\x06EF:\t@fooi/") }
+
+ bug2109 = '[ruby-core:25625]'
+ a = "\x82\xa0".force_encoding(Encoding::Windows_31J)
+ b = "\x82\xa2".force_encoding(Encoding::Windows_31J)
+ c = [/#{a}/, /#{b}/]
+ assert_equal(c, Marshal.load(Marshal.dump(c)), bug2109)
+
+ assert_nothing_raised(ArgumentError, '[ruby-dev:40386]') do
+ re = IO.pipe do |r, w|
+ w.write("\x04\bI/\x00\x00\x06:\rencoding\"\rUS-ASCII")
+ # Marshal.load would not overread and block
+ Marshal.load(r)
+ end
+ assert_equal(//, re)
+ end
end
class DumpTest
def marshal_dump
- loop { Thread.pass }
+ @@block.call(:marshal_dump)
+ end
+
+ def dump_each(&block)
+ @@block = block
+ Marshal.dump(self)
end
end
@@ -83,29 +348,706 @@ class TestMarshal < Test::Unit::TestCase
nil
end
def marshal_load(obj)
- loop { Thread.pass }
+ @@block.call(:marshal_load)
+ end
+ def self.load_each(m, &block)
+ @@block = block
+ Marshal.load(m)
end
end
def test_context_switch
o = DumpTest.new
- Thread.new { Marshal.dump(o) }
+ e = o.enum_for(:dump_each)
+ assert_equal(:marshal_dump, e.next)
GC.start
assert(true, '[ruby-dev:39425]')
+ assert_raise(StopIteration) {e.next}
o = LoadTest.new
m = Marshal.dump(o)
- Thread.new { Marshal.load(m) }
+ e = LoadTest.enum_for(:load_each, m)
+ assert_equal(:marshal_load, e.next)
GC.start
assert(true, '[ruby-dev:39425]')
+ assert_raise(StopIteration) {e.next}
+ end
+
+ def test_dump_buffer
+ bug2390 = '[ruby-dev:39744]'
+ w = ""
+ def w.write(str)
+ self << str.to_s
+ end
+ Marshal.dump(Object.new, w)
+ assert_not_empty(w, bug2390)
+ end
+
+ class C5
+ def marshal_dump
+ "foo"
+ end
+ def marshal_load(foo)
+ @foo = foo
+ end
+ def initialize(x)
+ @x = x
+ end
+ end
+ def test_marshal_dump
+ c = C5.new("bar")
+ s = Marshal.dump(c)
+ d = Marshal.load(s)
+ assert_equal("foo", d.instance_variable_get(:@foo))
+ assert_equal(false, d.instance_variable_defined?(:@x))
+ end
+
+ class C6
+ def initialize
+ @stdin = STDIN
+ end
+ attr_reader :stdin
+ def marshal_dump
+ 1
+ end
+ def marshal_load(x)
+ @stdin = STDIN
+ end
+ end
+ def test_marshal_dump_extra_iv
+ o = C6.new
+ m = nil
+ assert_nothing_raised("[ruby-dev:21475] [ruby-dev:39845]") {
+ m = Marshal.dump(o)
+ }
+ o2 = Marshal.load(m)
+ assert_equal(STDIN, o2.stdin)
+ end
+
+ def test_marshal_string_encoding
+ o1 = ["foo".force_encoding("EUC-JP")] + [ "bar" ] * 2
+ m = Marshal.dump(o1)
+ o2 = Marshal.load(m)
+ assert_equal(o1, o2, "[ruby-dev:40388]")
+ end
+
+ def test_marshal_regexp_encoding
+ o1 = [Regexp.new("r1".force_encoding("EUC-JP"))] + ["r2"] * 2
+ m = Marshal.dump(o1)
+ o2 = Marshal.load(m)
+ assert_equal(o1, o2, "[ruby-dev:40416]")
+ end
+
+ def test_marshal_encoding_encoding
+ o1 = [Encoding.find("EUC-JP")] + ["r2"] * 2
+ m = Marshal.dump(o1)
+ o2 = Marshal.load(m)
+ assert_equal(o1, o2)
+ end
+
+ def test_marshal_symbol_ascii8bit
+ bug6209 = '[ruby-core:43762]'
+ o1 = "\xff".force_encoding("ASCII-8BIT").intern
+ m = Marshal.dump(o1)
+ o2 = nil
+ assert_nothing_raised(EncodingError, bug6209) {o2 = Marshal.load(m)}
+ assert_equal(o1, o2, bug6209)
+ end
+
+ class PrivateClass
+ def initialize(foo)
+ @foo = foo
+ end
+ attr_reader :foo
+ end
+ private_constant :PrivateClass
+
+ def test_marshal_private_class
+ o1 = PrivateClass.new("test")
+ o2 = Marshal.load(Marshal.dump(o1))
+ assert_equal(o1.class, o2.class)
+ assert_equal(o1.foo, o2.foo)
+ end
+
+ class TooComplex
+ def initialize
+ @marshal_complex = 1
+ end
+ end
+
+ def test_complex_shape_object_id_not_dumped
+ if defined?(RubyVM::Shape::SHAPE_MAX_VARIATIONS)
+ assert_equal 8, RubyVM::Shape::SHAPE_MAX_VARIATIONS
+ end
+ 8.times do |i|
+ TooComplex.new.instance_variable_set("@TestObjectIdTooComplex#{i}", 1)
+ end
+ obj = TooComplex.new
+ ivar = "@a#{rand(10_000).to_s.rjust(5, '0')}"
+ obj.instance_variable_set(ivar, 1)
+
+ if defined?(RubyVM::Shape)
+ assert_predicate(RubyVM::Shape.of(obj), :complex?)
+ end
+ obj.object_id
+ assert_equal "\x04\bo:\x1CTestMarshal::TooComplex\a:\x15@marshal_complexi\x06:\f#{ivar}i\x06".b, Marshal.dump(obj)
+ end
+
+ def test_marshal_complex
+ assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x05")}
+ assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x06i\x00")}
+ assert_equal(Complex(1, 2), Marshal.load("\x04\bU:\fComplex[\ai\x06i\a"))
+ assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\bi\x00i\x00i\x00")}
+ end
+
+ def test_marshal_rational
+ assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\x05")}
+ assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\x06i\x00")}
+ assert_equal(Rational(1, 2), Marshal.load("\x04\bU:\rRational[\ai\x06i\a"))
+ assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\bi\x00i\x00i\x00")}
+ end
+
+ def test_marshal_flonum_reference
+ bug7348 = '[ruby-core:49323]'
+ e = []
+ ary = [ [2.0, e], [e] ]
+ assert_equal(ary, Marshal.load(Marshal.dump(ary)), bug7348)
+ end
+
+ class TestClass
+ end
+
+ module TestModule
+ end
+
+ class Bug7627 < Struct.new(:bar)
+ attr_accessor :foo
+
+ def marshal_dump; 'dump'; end # fake dump data
+ def marshal_load(*); end # do nothing
+ end
+
+ def test_marshal_dump_struct_ivar
+ bug7627 = '[ruby-core:51163]'
+ obj = Bug7627.new
+ obj.foo = '[Bug #7627]'
+
+ dump = Marshal.dump(obj)
+ loaded = Marshal.load(dump)
+
+ assert_equal(obj, loaded, bug7627)
+ assert_nil(loaded.foo, bug7627)
+ end
+
+ class LoadData
+ attr_reader :data
+ def initialize(data)
+ @data = data
+ end
+ alias marshal_dump data
+ alias marshal_load initialize
+ end
+
+ class Bug8276 < LoadData
+ def initialize(*)
+ super
+ freeze
+ end
+ alias marshal_load initialize
+ end
+
+ class FrozenData < LoadData
+ def marshal_load(data)
+ super
+ data.instance_variables.each do |iv|
+ instance_variable_set(iv, data.instance_variable_get(iv))
+ end
+ freeze
+ end
end
- def test_taint
- x = Object.new
- x.taint
- s = Marshal.dump(x)
- assert_equal(true, s.tainted?)
- y = Marshal.load(s)
- assert_equal(true, y.tainted?)
+ def test_marshal_dump_excess_encoding
+ bug8276 = '[ruby-core:54334] [Bug #8276]'
+ t = Bug8276.new(bug8276)
+ s = Marshal.dump(t)
+ assert_nothing_raised(RuntimeError, bug8276) {s = Marshal.load(s)}
+ assert_equal(t.data, s.data, bug8276)
+ end
+
+ def test_marshal_dump_ivar
+ s = "data with ivar"
+ s.instance_variable_set(:@t, 42)
+ t = Bug8276.new(s)
+ s = Marshal.dump(t)
+ assert_raise(FrozenError) {Marshal.load(s)}
+ end
+
+ def test_marshal_load_ivar
+ s = "data with ivar"
+ s.instance_variable_set(:@t, 42)
+ hook = ->(v) {
+ if LoadData === v
+ assert_send([v, :instance_variable_defined?, :@t], v.class.name)
+ assert_equal(42, v.instance_variable_get(:@t), v.class.name)
+ end
+ v
+ }
+ [LoadData, FrozenData].each do |klass|
+ t = klass.new(s)
+ d = Marshal.dump(t)
+ v = assert_nothing_raised(RuntimeError) {break Marshal.load(d, hook)}
+ assert_send([v, :instance_variable_defined?, :@t], klass.name)
+ assert_equal(42, v.instance_variable_get(:@t), klass.name)
+ end
+ end
+
+ def test_class_ivar
+ assert_raise(TypeError) {Marshal.load("\x04\x08Ic\x1bTestMarshal::TestClass\x06:\x0e@ivar_bug\"\x08bug")}
+ assert_raise(TypeError) {Marshal.load("\x04\x08IM\x1bTestMarshal::TestClass\x06:\x0e@ivar_bug\"\x08bug")}
+ assert_not_operator(TestClass, :instance_variable_defined?, :@ivar_bug)
+
+ assert_raise(TypeError) {Marshal.load("\x04\x08[\x07c\x1bTestMarshal::TestClassI@\x06\x06:\x0e@ivar_bug\"\x08bug")}
+ assert_not_operator(TestClass, :instance_variable_defined?, :@ivar_bug)
+ end
+
+ def test_module_ivar
+ assert_raise(TypeError) {Marshal.load("\x04\x08Im\x1cTestMarshal::TestModule\x06:\x0e@ivar_bug\"\x08bug")}
+ assert_raise(TypeError) {Marshal.load("\x04\x08IM\x1cTestMarshal::TestModule\x06:\x0e@ivar_bug\"\x08bug")}
+ assert_not_operator(TestModule, :instance_variable_defined?, :@ivar_bug)
+
+ assert_raise(TypeError) {Marshal.load("\x04\x08[\x07m\x1cTestMarshal::TestModuleI@\x06\x06:\x0e@ivar_bug\"\x08bug")}
+ assert_not_operator(TestModule, :instance_variable_defined?, :@ivar_bug)
+ end
+
+ class TestForRespondToFalse
+ def respond_to?(a, priv = false)
+ false
+ end
+ end
+
+ def test_marshal_respond_to_arity
+ assert_nothing_raised(ArgumentError, '[Bug #7722]') do
+ Marshal.dump(TestForRespondToFalse.new)
+ end
+ end
+
+ def test_packed_string
+ packed = ["foo"].pack("p")
+ bare = "".force_encoding(Encoding::ASCII_8BIT) << packed
+ assert_equal(Marshal.dump(bare), Marshal.dump(packed))
+ end
+
+ class Bug9523
+ attr_reader :cc
+ def marshal_dump
+ callcc {|c| @cc = c }
+ nil
+ end
+ def marshal_load(v)
+ end
+ end
+
+ def test_continuation
+ EnvUtil.suppress_warning {require "continuation"}
+ omit 'requires callcc support' unless respond_to?(:callcc)
+
+ c = Bug9523.new
+ assert_raise_with_message(RuntimeError, /Marshal\.dump reentered at marshal_dump/) do
+ Marshal.dump(c)
+ GC.start
+ 1000.times {"x"*1000}
+ GC.start
+ c.cc.call
+ end
+ end
+
+ def test_undumpable_message
+ c = Module.new {break module_eval("class IO\u{26a1} < IO;self;end")}
+ assert_raise_with_message(TypeError, /IO\u{26a1}/) {
+ Marshal.dump(c.new(0, autoclose: false))
+ }
+ end
+
+ def test_undumpable_data
+ c = Module.new {break module_eval("class T\u{23F0 23F3}<Time;undef _dump;self;end")}
+ assert_raise_with_message(TypeError, /T\u{23F0 23F3}/) {
+ Marshal.dump(c.new)
+ }
+ end
+
+ def test_unloadable_data
+ name = "Unloadable\u{23F0 23F3}"
+ c = eval("class #{name} < Time;;self;end")
+ c.class_eval {
+ alias _dump_data _dump
+ undef _dump
+ }
+ d = Marshal.dump(c.new)
+ assert_raise_with_message(TypeError, /Unloadable\u{23F0 23F3}/) {
+ Marshal.load(d)
+ }
+
+ ensure
+ self.class.class_eval do
+ remove_const name
+ end if c
+ end
+
+ def test_unloadable_userdef
+ name = "Userdef\u{23F0 23F3}"
+ c = eval("class #{name} < Time;self;end")
+ class << c
+ undef _load
+ end
+ d = Marshal.dump(c.new)
+ assert_raise_with_message(TypeError, /Userdef\u{23F0 23F3}/) {
+ Marshal.load(d)
+ }
+
+ ensure
+ self.class.class_eval do
+ remove_const name
+ end if c
+ end
+
+ def test_recursive_userdef
+ t = Time.utc(0)
+ t.instance_eval {@v = t}
+ assert_raise_with_message(RuntimeError, /recursive\b.*\b_dump/) do
+ Marshal.dump(t)
+ end
+ end
+
+ def test_unloadable_usrmarshal
+ c = eval("class UsrMarshal\u{23F0 23F3}<Time;self;end")
+ c.class_eval {
+ alias marshal_dump _dump
+ }
+ d = Marshal.dump(c.new)
+ assert_raise_with_message(TypeError, /UsrMarshal\u{23F0 23F3}/) {
+ Marshal.load(d)
+ }
+ end
+
+ def test_no_internal_ids
+ opt = %w[--disable=gems]
+ args = [opt, 'Marshal.dump("",STDOUT)', true, true]
+ kw = {encoding: Encoding::ASCII_8BIT}
+ out, err, status = EnvUtil.invoke_ruby(*args, **kw)
+ assert_empty(err)
+ assert_predicate(status, :success?)
+ expected = out
+
+ opt << "--enable=frozen-string-literal"
+ opt << "--debug=frozen-string-literal"
+ out, err, status = EnvUtil.invoke_ruby(*args, **kw)
+ assert_empty(err)
+ assert_predicate(status, :success?)
+ assert_equal(expected, out)
+ end
+
+ def test_marshal_honor_post_proc_value_for_link
+ str = 'x' # for link
+ obj = [str, str]
+ assert_equal(['X', 'X'], Marshal.load(Marshal.dump(obj), ->(v) { v == str ? v.upcase : v }))
+ end
+
+ def test_marshal_proc_string_encoding
+ string = "foo"
+ payload = Marshal.dump(string)
+ Marshal.load(payload, ->(v) {
+ if v.is_a?(String)
+ assert_equal(string, v)
+ assert_equal(string.encoding, v.encoding)
+ end
+ v
+ })
+ end
+
+ def test_marshal_proc_freeze
+ object = { foo: [42, "bar"] }
+ assert_equal object, Marshal.load(Marshal.dump(object), :freeze.to_proc)
+ end
+
+ def test_marshal_load_extended_class_crash
+ assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
+ begin;
+ assert_raise_with_message(ArgumentError, /undefined/) do
+ Marshal.load("\x04\be:\x0F\x00omparableo:\vObject\x00")
+ end
+ end;
+ end
+
+ def test_marshal_load_r_prepare_reference_crash
+ crash = "\x04\bI/\x05\x00\x06:\x06E{\x06@\x05T"
+
+ opt = %w[--disable=gems]
+ assert_separately(opt, <<-RUBY)
+ assert_raise_with_message(ArgumentError, /bad link/) do
+ Marshal.load(#{crash.dump})
+ end
+ RUBY
+ end
+
+ MethodMissingWithoutRespondTo = Struct.new(:wrapped_object) do
+ undef respond_to?
+ def method_missing(*args, &block)
+ wrapped_object.public_send(*args, &block)
+ end
+ def respond_to_missing?(name, private = false)
+ wrapped_object.respond_to?(name, false)
+ end
+ end
+
+ def test_method_missing_without_respond_to
+ bug12353 = "[ruby-core:75377] [Bug #12353]: try method_missing if" \
+ " respond_to? is undefined"
+ obj = MethodMissingWithoutRespondTo.new("foo")
+ dump = assert_nothing_raised(NoMethodError, bug12353) do
+ Marshal.dump(obj)
+ end
+ assert_equal(obj, Marshal.load(dump))
+ end
+
+ class Bug12974
+ def marshal_dump
+ dup
+ end
+ end
+
+ def test_marshal_dump_recursion
+ assert_raise_with_message(RuntimeError, /same class instance/) do
+ Marshal.dump(Bug12974.new)
+ end
+ end
+
+ Bug14314 = Struct.new(:foo, keyword_init: true)
+
+ def test_marshal_keyword_init_struct
+ obj = Bug14314.new(foo: 42)
+ assert_equal obj, Marshal.load(Marshal.dump(obj))
+ end
+
+ class Bug15968
+ attr_accessor :bar, :baz
+
+ def initialize
+ self.bar = Bar.new(self)
+ end
+
+ class Bar
+ attr_accessor :foo
+
+ def initialize(foo)
+ self.foo = foo
+ end
+
+ def marshal_dump
+ if self.foo.baz
+ self.foo.remove_instance_variable(:@baz)
+ else
+ self.foo.baz = :problem
+ end
+ {foo: self.foo}
+ end
+
+ def marshal_load(data)
+ self.foo = data[:foo]
+ end
+ end
+ end
+
+ def test_marshal_dump_adding_instance_variable
+ obj = Bug15968.new
+ loaded = Marshal.load(Marshal.dump(obj))
+ assert_nil loaded.baz
+ end
+
+ def test_marshal_dump_removing_instance_variable
+ obj = Bug15968.new
+ obj.baz = :Bug15968
+ loaded = Marshal.load(Marshal.dump(obj))
+ assert_equal :Bug15968, loaded.baz
+ end
+
+ ruby2_keywords def ruby2_keywords_hash(*a)
+ a.last
+ end
+
+ def ruby2_keywords_test(key: 1)
+ key
+ end
+
+ def test_marshal_with_ruby2_keywords_hash
+ flagged_hash = ruby2_keywords_hash(key: 42)
+ data = Marshal.dump(flagged_hash)
+ hash = Marshal.load(data)
+ assert_equal(42, ruby2_keywords_test(*[hash]))
+
+ hash2 = Marshal.load(data.sub(/\x06K(?=T\z)/, "\x08KEY"))
+ assert_raise(ArgumentError, /\(given 1, expected 0\)/) {
+ ruby2_keywords_test(*[hash2])
+ }
+ hash2 = Marshal.load(data.sub(/:\x06K(?=T\z)/, "I\\&\x06:\x0dencoding\"\x0aUTF-7"))
+ assert_raise(ArgumentError, /\(given 1, expected 0\)/) {
+ ruby2_keywords_test(*[hash2])
+ }
+ end
+
+ def test_invalid_byte_sequence_symbol
+ data = Marshal.dump(:K)
+ data = data.sub(/:\x06K/, "I\\&\x06:\x0dencoding\"\x0dUTF-16LE")
+ assert_raise(ArgumentError, /UTF-16LE: "\\x4B"/) {
+ Marshal.load(data)
+ }
+ end
+
+ def exception_test
+ raise
+ end
+
+ def test_marshal_exception
+ begin
+ exception_test
+ rescue => e
+ e2 = Marshal.load(Marshal.dump(e))
+ assert_equal(e.message, e2.message)
+ assert_equal(e.backtrace, e2.backtrace)
+ assert_nil(e2.backtrace_locations) # temporal
+ end
+ end
+
+ def nameerror_test
+ unknown_method
+ end
+
+ def test_marshal_nameerror
+ begin
+ nameerror_test
+ rescue NameError => e
+ e2 = Marshal.load(Marshal.dump(e))
+ assert_equal(e.message.lines.first.chomp, e2.message.lines.first)
+ assert_equal(e.name, e2.name)
+ assert_equal(e.backtrace, e2.backtrace)
+ assert_nil(e2.backtrace_locations) # temporal
+ end
+ end
+
+ def test_load_overread
+ input = Struct.new(:bytes, :used) do
+ def initialize
+ super("\x04\x08[\x07".bytes, false)
+ end
+
+ def getbyte
+ bytes.shift
+ end
+
+ def read(_len, _outbuf = nil)
+ return nil if used
+ self.used = true
+ "0" * (1024 * 128)
+ end
+ end.new
+
+ assert_equal([nil, nil], Marshal.load(input))
+ end
+
+ def test_bignum_len_overflow
+ assert_raise(ArgumentError) do
+ Marshal.load("\x04\x08l+\x04\x00\x00\x00\x40")
+ end
+ assert_raise(ArgumentError) do
+ Marshal.load("\x04\x08l+\xfc\x00\x00\x00\x80")
+ end
+ end
+
+ def test_bignum_invalid_sign
+ assert_raise(ArgumentError) do
+ Marshal.load("\x04\bl?")
+ end
+ end
+
+ class TestMarshalFreezeProc < Test::Unit::TestCase
+ include MarshalTestLib
+
+ def encode(o)
+ Marshal.dump(o)
+ end
+
+ def decode(s)
+ Marshal.load(s, :freeze.to_proc)
+ end
+ end
+
+ def _test_hash_compared_by_identity(h)
+ h.compare_by_identity
+ h["a" + "0"] = 1
+ h["a" + "0"] = 2
+ h = Marshal.load(Marshal.dump(h))
+ assert_predicate(h, :compare_by_identity?)
+ a = h.to_a
+ assert_equal([["a0", 1], ["a0", 2]], a.sort)
+ assert_not_same(a[1][0], a[0][0])
+ end
+
+ def test_hash_compared_by_identity
+ _test_hash_compared_by_identity(Hash.new)
+ end
+
+ def test_hash_default_compared_by_identity
+ _test_hash_compared_by_identity(Hash.new(true))
+ end
+
+ class TestMarshalFreeze < Test::Unit::TestCase
+ include MarshalTestLib
+
+ def encode(o)
+ Marshal.dump(o)
+ end
+
+ def decode(s)
+ Marshal.load(s, freeze: true)
+ end
+
+ def test_return_objects_are_frozen
+ source = ["foo", {}, /foo/, 1..2]
+ objects = decode(encode(source))
+ assert_equal source, objects
+ assert_predicate objects, :frozen?
+ objects.each do |obj|
+ assert_predicate obj, :frozen?
+ end
+ end
+
+ def test_proc_returned_object_are_not_frozen
+ source = ["foo", {}, 1..2]
+ objects = Marshal.load(encode(source), ->(o) { o.dup }, freeze: true)
+ assert_equal source, objects
+ refute_predicate objects, :frozen?
+ objects.each do |obj|
+ refute_predicate obj, :frozen?
+ end
+ end
+
+ def test_modules_and_classes_are_not_frozen
+ _objects = Marshal.load(encode([Object, Kernel]), freeze: true)
+ refute_predicate Object, :frozen?
+ refute_predicate Kernel, :frozen?
+ end
+
+ def test_linked_strings_are_frozen
+ str = "test"
+ str.instance_variable_set(:@self, str)
+ source = [str, str]
+
+ objects = Marshal.load(encode(source), freeze: true)
+ assert_predicate objects[0], :frozen?
+ assert_predicate objects[1], :frozen?
+ assert_same objects[0], objects[1]
+ assert_same objects[0], objects[0].instance_variable_get(:@self)
+ assert_same objects[1], objects[1].instance_variable_get(:@self)
+ assert_same objects[0].instance_variable_get(:@self), objects[1].instance_variable_get(:@self)
+ end
end
end