diff options
Diffstat (limited to 'test/ruby/test_marshal.rb')
| -rw-r--r-- | test/ruby/test_marshal.rb | 1018 |
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 |
