1require 'test/unit'
2require 'tempfile'
3require_relative 'marshaltestlib'
4
5class TestMarshal < Test::Unit::TestCase
6  include MarshalTestLib
7
8  def setup
9    @verbose = $VERBOSE
10    $VERBOSE = nil
11  end
12
13  def teardown
14    $VERBOSE = @verbose
15  end
16
17  def encode(o)
18    Marshal.dump(o)
19  end
20
21  def decode(s)
22    Marshal.load(s)
23  end
24
25  def fact(n)
26    return 1 if n == 0
27    f = 1
28    while n>0
29      f *= n
30      n -= 1
31    end
32    return f
33  end
34
35  def test_marshal
36    a = [1, 2, 3, [4,5,"foo"], {1=>"bar"}, 2.5, fact(30)]
37    assert_equal a, Marshal.load(Marshal.dump(a))
38
39    [[1,2,3,4], [81, 2, 118, 3146]].each { |w,x,y,z|
40      obj = (x.to_f + y.to_f / z.to_f) * Math.exp(w.to_f / (x.to_f + y.to_f / z.to_f))
41      assert_equal obj, Marshal.load(Marshal.dump(obj))
42    }
43
44    bug3659 = '[ruby-dev:41936]'
45    [1.0, 10.0, 100.0, 110.0].each {|x|
46      assert_equal(x, Marshal.load(Marshal.dump(x)), bug3659)
47    }
48  end
49
50  StrClone = String.clone
51  def test_marshal_cloned_class
52    assert_instance_of(StrClone, Marshal.load(Marshal.dump(StrClone.new("abc"))))
53  end
54
55  def test_inconsistent_struct
56    TestMarshal.const_set :StructOrNot, Struct.new(:a)
57    s = Marshal.dump(StructOrNot.new(1))
58    TestMarshal.instance_eval { remove_const :StructOrNot }
59    TestMarshal.const_set :StructOrNot, Class.new
60    assert_raise(TypeError, "[ruby-dev:31709]") { Marshal.load(s) }
61  end
62
63  def test_struct_invalid_members
64    TestMarshal.const_set :StructInvalidMembers, Struct.new(:a)
65    assert_raise(TypeError, "[ruby-dev:31759]") {
66      Marshal.load("\004\bIc&TestMarshal::StructInvalidMembers\006:\020__members__\"\bfoo")
67      TestMarshal::StructInvalidMembers.members
68    }
69  end
70
71  class C
72    def initialize(str)
73      @str = str
74    end
75    attr_reader :str
76    def _dump(limit)
77      @str
78    end
79    def self._load(s)
80      new(s)
81    end
82  end
83
84  def test_too_long_string
85    data = Marshal.dump(C.new("a".force_encoding("ascii-8bit")))
86    data[-2, 1] = "\003\377\377\377"
87    e = assert_raise(ArgumentError, "[ruby-dev:32054]") {
88      Marshal.load(data)
89    }
90    assert_equal("marshal data too short", e.message)
91  end
92
93
94  def test_userdef_encoding
95    s1 = "\xa4\xa4".force_encoding("euc-jp")
96    o1 = C.new(s1)
97    m = Marshal.dump(o1)
98    o2 = Marshal.load(m)
99    s2 = o2.str
100    assert_equal(s1, s2)
101  end
102
103  def test_pipe
104    o1 = C.new("a" * 10000)
105
106    o2 = IO.pipe do |r, w|
107      Thread.new {Marshal.dump(o1, w)}
108      Marshal.load(r)
109    end
110    assert_equal(o1.str, o2.str)
111
112    o2 = IO.pipe do |r, w|
113      Thread.new {Marshal.dump(o1, w, 2)}
114      Marshal.load(r)
115    end
116    assert_equal(o1.str, o2.str)
117
118    assert_raise(TypeError) { Marshal.dump("foo", Object.new) }
119    assert_raise(TypeError) { Marshal.load(Object.new) }
120  end
121
122  def test_limit
123    assert_equal([[[]]], Marshal.load(Marshal.dump([[[]]], 3)))
124    assert_raise(ArgumentError) { Marshal.dump([[[]]], 2) }
125    assert_nothing_raised(ArgumentError, '[ruby-core:24100]') { Marshal.dump("\u3042", 1) }
126  end
127
128  def test_userdef_invalid
129    o = C.new(nil)
130    assert_raise(TypeError) { Marshal.dump(o) }
131  end
132
133  def test_class
134    o = class << Object.new; self; end
135    assert_raise(TypeError) { Marshal.dump(o) }
136    assert_equal(Object, Marshal.load(Marshal.dump(Object)))
137    assert_equal(Enumerable, Marshal.load(Marshal.dump(Enumerable)))
138  end
139
140  class C2
141    def initialize(ary)
142      @ary = ary
143    end
144    def _dump(s)
145      @ary.clear
146      "foo"
147    end
148  end
149
150  def test_modify_array_during_dump
151    a = []
152    o = C2.new(a)
153    a << o << nil
154    assert_raise(RuntimeError) { Marshal.dump(a) }
155  end
156
157  def test_change_class_name
158    eval("class C3; def _dump(s); 'foo'; end; end")
159    m = Marshal.dump(C3.new)
160    assert_raise(TypeError) { Marshal.load(m) }
161    eval("C3 = nil")
162    assert_raise(TypeError) { Marshal.load(m) }
163  end
164
165  def test_change_struct
166    eval("C3 = Struct.new(:foo, :bar)")
167    m = Marshal.dump(C3.new("FOO", "BAR"))
168    eval("C3 = Struct.new(:foo)")
169    assert_raise(TypeError) { Marshal.load(m) }
170    eval("C3 = Struct.new(:foo, :baz)")
171    assert_raise(TypeError) { Marshal.load(m) }
172  end
173
174  class C4
175    def initialize(gc)
176      @gc = gc
177    end
178    def _dump(s)
179      GC.start if @gc
180      "foo"
181    end
182  end
183
184  def test_gc
185    assert_nothing_raised do
186      Marshal.dump((0..1000).map {|x| C4.new(x % 50 == 25) })
187    end
188  end
189
190  def test_taint_and_untrust
191    x = Object.new
192    x.taint
193    x.untrust
194    s = Marshal.dump(x)
195    assert_equal(true, s.tainted?)
196    assert_equal(true, s.untrusted?)
197    y = Marshal.load(s)
198    assert_equal(true, y.tainted?)
199    assert_equal(true, y.untrusted?)
200  end
201
202  def test_taint_and_untrust_each_object
203    x = Object.new
204    obj = [[x]]
205
206    # clean object causes crean stream
207    assert_equal(false, obj.tainted?)
208    assert_equal(false, obj.untrusted?)
209    assert_equal(false, obj.first.tainted?)
210    assert_equal(false, obj.first.untrusted?)
211    assert_equal(false, obj.first.first.tainted?)
212    assert_equal(false, obj.first.first.untrusted?)
213    s = Marshal.dump(obj)
214    assert_equal(false, s.tainted?)
215    assert_equal(false, s.untrusted?)
216
217    # tainted/untrusted object causes tainted/untrusted stream
218    x.taint
219    x.untrust
220    assert_equal(false, obj.tainted?)
221    assert_equal(false, obj.untrusted?)
222    assert_equal(false, obj.first.tainted?)
223    assert_equal(false, obj.first.untrusted?)
224    assert_equal(true, obj.first.first.tainted?)
225    assert_equal(true, obj.first.first.untrusted?)
226    t = Marshal.dump(obj)
227    assert_equal(true, t.tainted?)
228    assert_equal(true, t.untrusted?)
229
230    # clean stream causes clean objects
231    assert_equal(false, s.tainted?)
232    assert_equal(false, s.untrusted?)
233    y = Marshal.load(s)
234    assert_equal(false, y.tainted?)
235    assert_equal(false, y.untrusted?)
236    assert_equal(false, y.first.tainted?)
237    assert_equal(false, y.first.untrusted?)
238    assert_equal(false, y.first.first.tainted?)
239    assert_equal(false, y.first.first.untrusted?)
240
241    # tainted/untrusted stream causes tainted/untrusted objects
242    assert_equal(true, t.tainted?)
243    assert_equal(true, t.untrusted?)
244    y = Marshal.load(t)
245    assert_equal(true, y.tainted?)
246    assert_equal(true, y.untrusted?)
247    assert_equal(true, y.first.tainted?)
248    assert_equal(true, y.first.untrusted?)
249    assert_equal(true, y.first.first.tainted?)
250    assert_equal(true, y.first.first.untrusted?)
251
252    # same tests by different senario
253    s.taint
254    s.untrust
255    assert_equal(true, s.tainted?)
256    assert_equal(true, s.untrusted?)
257    y = Marshal.load(s)
258    assert_equal(true, y.tainted?)
259    assert_equal(true, y.untrusted?)
260    assert_equal(true, y.first.tainted?)
261    assert_equal(true, y.first.untrusted?)
262    assert_equal(true, y.first.first.tainted?)
263    assert_equal(true, y.first.first.untrusted?)
264  end
265
266  def test_symbol2
267    [:ruby, :"\u{7d05}\u{7389}"].each do |sym|
268      assert_equal(sym, Marshal.load(Marshal.dump(sym)), '[ruby-core:24788]')
269    end
270    bug2548 = '[ruby-core:27375]'
271    ary = [:$1, nil]
272    assert_equal(ary, Marshal.load(Marshal.dump(ary)), bug2548)
273  end
274
275  ClassUTF8 = eval("class R\u{e9}sum\u{e9}; self; end")
276
277  iso_8859_1 = Encoding::ISO_8859_1
278
279  structISO8859_1 = Struct.new("r\xe9sum\xe9".force_encoding(iso_8859_1).intern)
280  const_set("R\xe9sum\xe9".force_encoding(iso_8859_1), structISO8859_1)
281  structISO8859_1.name
282  StructISO8859_1 = structISO8859_1
283  classISO8859_1 = Class.new do
284    attr_accessor "r\xe9sum\xe9".force_encoding(iso_8859_1)
285    eval("def initialize(x) @r\xe9sum\xe9 = x; end".force_encoding(iso_8859_1))
286  end
287  const_set("R\xe9sum\xe92".force_encoding(iso_8859_1), classISO8859_1)
288  classISO8859_1.name
289  ClassISO8859_1 = classISO8859_1
290
291  def test_class_nonascii
292    a = ClassUTF8.new
293    assert_instance_of(ClassUTF8, Marshal.load(Marshal.dump(a)), '[ruby-core:24790]')
294
295    bug1932 = '[ruby-core:24882]'
296
297    a = StructISO8859_1.new(10)
298    assert_nothing_raised(bug1932) do
299      assert_equal(a, Marshal.load(Marshal.dump(a)), bug1932)
300    end
301    a.__send__("#{StructISO8859_1.members[0]}=", a)
302    assert_nothing_raised(bug1932) do
303      assert_equal(a, Marshal.load(Marshal.dump(a)), bug1932)
304    end
305
306    a = ClassISO8859_1.new(10)
307    assert_nothing_raised(bug1932) do
308      b = Marshal.load(Marshal.dump(a))
309      assert_equal(ClassISO8859_1, b.class, bug1932)
310      assert_equal(a.instance_variables, b.instance_variables, bug1932)
311      a.instance_variables.each do |i|
312        assert_equal(a.instance_variable_get(i), b.instance_variable_get(i), bug1932)
313      end
314    end
315    a.__send__(a.methods(true).grep(/=\z/)[0], a)
316    assert_nothing_raised(bug1932) do
317      b = Marshal.load(Marshal.dump(a))
318      assert_equal(ClassISO8859_1, b.class, bug1932)
319      assert_equal(a.instance_variables, b.instance_variables, bug1932)
320      assert_equal(b, b.instance_variable_get(a.instance_variables[0]), bug1932)
321    end
322  end
323
324  def test_regexp2
325    assert_equal(/\\u/, Marshal.load("\004\b/\b\\\\u\000"))
326    assert_equal(/u/, Marshal.load("\004\b/\a\\u\000"))
327    assert_equal(/u/, Marshal.load("\004\bI/\a\\u\000\006:\016@encoding\"\vEUC-JP"))
328
329    bug2109 = '[ruby-core:25625]'
330    a = "\x82\xa0".force_encoding(Encoding::Windows_31J)
331    b = "\x82\xa2".force_encoding(Encoding::Windows_31J)
332    c = [/#{a}/, /#{b}/]
333    assert_equal(c, Marshal.load(Marshal.dump(c)), bug2109)
334
335    assert_nothing_raised(ArgumentError, '[ruby-dev:40386]') do
336      re = Tempfile.open("marshal_regexp") do |f|
337        f.binmode.write("\x04\bI/\x00\x00\x06:\rencoding\"\rUS-ASCII")
338        f.close
339        re2 = Marshal.load(f.open.binmode)
340        f.close(true)
341        re2
342      end
343      assert_equal(//, re)
344    end
345  end
346
347  class DumpTest
348    def marshal_dump
349      @@block.call(:marshal_dump)
350    end
351
352    def dump_each(&block)
353      @@block = block
354      Marshal.dump(self)
355    end
356  end
357
358  class LoadTest
359    def marshal_dump
360      nil
361    end
362    def marshal_load(obj)
363      @@block.call(:marshal_load)
364    end
365    def self.load_each(m, &block)
366      @@block = block
367      Marshal.load(m)
368    end
369  end
370
371  def test_context_switch
372    o = DumpTest.new
373    e = o.enum_for(:dump_each)
374    assert_equal(:marshal_dump, e.next)
375    GC.start
376    assert(true, '[ruby-dev:39425]')
377    assert_raise(StopIteration) {e.next}
378
379    o = LoadTest.new
380    m = Marshal.dump(o)
381    e = LoadTest.enum_for(:load_each, m)
382    assert_equal(:marshal_load, e.next)
383    GC.start
384    assert(true, '[ruby-dev:39425]')
385    assert_raise(StopIteration) {e.next}
386  end
387
388  def test_dump_buffer
389    bug2390 = '[ruby-dev:39744]'
390    w = ""
391    def w.write(str)
392      self << str.to_s
393    end
394    Marshal.dump(Object.new, w)
395    assert_not_empty(w, bug2390)
396  end
397
398  class C5
399    def marshal_dump
400      "foo"
401    end
402    def marshal_load(foo)
403      @foo = foo
404    end
405    def initialize(x)
406      @x = x
407    end
408  end
409  def test_marshal_dump
410    c = C5.new("bar")
411    s = Marshal.dump(c)
412    d = Marshal.load(s)
413    assert_equal("foo", d.instance_variable_get(:@foo))
414    assert_equal(false, d.instance_variable_defined?(:@x))
415  end
416
417  class C6
418    def initialize
419      @stdin = STDIN
420    end
421    attr_reader :stdin
422    def marshal_dump
423      1
424    end
425    def marshal_load(x)
426      @stdin = STDIN
427    end
428  end
429  def test_marshal_dump_extra_iv
430    o = C6.new
431    m = nil
432    assert_nothing_raised("[ruby-dev:21475] [ruby-dev:39845]") {
433      m = Marshal.dump(o)
434    }
435    o2 = Marshal.load(m)
436    assert_equal(STDIN, o2.stdin)
437  end
438
439  def test_marshal_string_encoding
440    o1 = ["foo".force_encoding("EUC-JP")] + [ "bar" ] * 2
441    m = Marshal.dump(o1)
442    o2 = Marshal.load(m)
443    assert_equal(o1, o2, "[ruby-dev:40388]")
444  end
445
446  def test_marshal_regexp_encoding
447    o1 = [Regexp.new("r1".force_encoding("EUC-JP"))] + ["r2"] * 2
448    m = Marshal.dump(o1)
449    o2 = Marshal.load(m)
450    assert_equal(o1, o2, "[ruby-dev:40416]")
451  end
452
453  def test_marshal_encoding_encoding
454    o1 = [Encoding.find("EUC-JP")] + ["r2"] * 2
455    m = Marshal.dump(o1)
456    o2 = Marshal.load(m)
457    assert_equal(o1, o2)
458  end
459
460  def test_marshal_symbol_ascii8bit
461    bug6209 = '[ruby-core:43762]'
462    o1 = "\xff".force_encoding("ASCII-8BIT").intern
463    m = Marshal.dump(o1)
464    o2 = nil
465    assert_nothing_raised(EncodingError, bug6209) {o2 = Marshal.load(m)}
466    assert_equal(o1, o2, bug6209)
467  end
468
469  class PrivateClass
470    def initialize(foo)
471      @foo = foo
472    end
473    attr_reader :foo
474  end
475  private_constant :PrivateClass
476
477  def test_marshal_private_class
478    o1 = PrivateClass.new("test")
479    o2 = Marshal.load(Marshal.dump(o1))
480    assert_equal(o1.class, o2.class)
481    assert_equal(o1.foo, o2.foo)
482  end
483
484  def test_marshal_complex
485    assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x05")}
486    assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\x06i\x00")}
487    assert_equal(Complex(1, 2), Marshal.load("\x04\bU:\fComplex[\ai\x06i\a"))
488    assert_raise(ArgumentError){Marshal.load("\x04\bU:\fComplex[\bi\x00i\x00i\x00")}
489  end
490
491  def test_marshal_rational
492    assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\x05")}
493    assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\x06i\x00")}
494    assert_equal(Rational(1, 2), Marshal.load("\x04\bU:\rRational[\ai\x06i\a"))
495    assert_raise(ArgumentError){Marshal.load("\x04\bU:\rRational[\bi\x00i\x00i\x00")}
496  end
497
498  def test_marshal_flonum_reference
499    bug7348 = '[ruby-core:49323]'
500    e = []
501    ary = [ [2.0, e], [e] ]
502    assert_equal(ary, Marshal.load(Marshal.dump(ary)), bug7348)
503  end
504
505  class TestClass
506  end
507
508  module TestModule
509  end
510
511  def test_marshal_load_should_not_taint_classes
512    bug7325 = '[ruby-core:49198]'
513    for c in [TestClass, TestModule]
514      assert(!c.tainted?)
515      assert(!c.untrusted?)
516      c2 = Marshal.load(Marshal.dump(c).taint.untrust)
517      assert_same(c, c2)
518      assert(!c.tainted?, bug7325)
519      assert(!c.untrusted?, bug7325)
520    end
521  end
522
523  class Bug7627 < Struct.new(:bar)
524    attr_accessor :foo
525
526    def marshal_dump; 'dump'; end  # fake dump data
527    def marshal_load(*); end       # do nothing
528  end
529
530  def test_marshal_dump_struct_ivar
531    bug7627 = '[ruby-core:51163]'
532    obj = Bug7627.new
533    obj.foo = '[Bug #7627]'
534
535    dump   = Marshal.dump(obj)
536    loaded = Marshal.load(dump)
537
538    assert_equal(obj, loaded, bug7627)
539    assert_nil(loaded.foo, bug7627)
540  end
541
542  def test_class_ivar
543    assert_raise(TypeError) {Marshal.load("\x04\x08Ic\x1bTestMarshal::TestClass\x06:\x0e@ivar_bug\"\x08bug")}
544    assert_raise(TypeError) {Marshal.load("\x04\x08IM\x1bTestMarshal::TestClass\x06:\x0e@ivar_bug\"\x08bug")}
545    assert_not_operator(TestClass, :instance_variable_defined?, :@bug)
546  end
547
548  def test_module_ivar
549    assert_raise(TypeError) {Marshal.load("\x04\x08Im\x1cTestMarshal::TestModule\x06:\x0e@ivar_bug\"\x08bug")}
550    assert_raise(TypeError) {Marshal.load("\x04\x08IM\x1cTestMarshal::TestModule\x06:\x0e@ivar_bug\"\x08bug")}
551    assert_not_operator(TestModule, :instance_variable_defined?, :@bug)
552  end
553
554  class TestForRespondToFalse
555    def respond_to?(a)
556      false
557    end
558  end
559
560  def test_marshal_respond_to_arity
561    assert_nothing_raised(ArgumentError, '[Bug #7722]') do
562      Marshal.dump(TestForRespondToFalse.new)
563    end
564  end
565end
566