Fix use-after-free in WeakKeyMap#clear
[ruby.git] / test / ruby / test_weakkeymap.rb
blob91c1538076f9f444fe682b9a97a74deed9777da1
1 # frozen_string_literal: false
2 require 'test/unit'
4 class TestWeakKeyMap < Test::Unit::TestCase
5   def setup
6     @wm = ObjectSpace::WeakKeyMap.new
7   end
9   def test_map
10     x = Object.new
11     k = "foo"
12     @wm[k] = x
13     assert_same(x, @wm[k])
14     assert_same(x, @wm["FOO".downcase])
15   end
17   def test_aset_const
18     x = Object.new
19     assert_raise(ArgumentError) { @wm[true] = x }
20     assert_raise(ArgumentError) { @wm[false] = x }
21     assert_raise(ArgumentError) { @wm[nil] = x }
22     assert_raise(ArgumentError) { @wm[42] = x }
23     assert_raise(ArgumentError) { @wm[2**128] = x }
24     assert_raise(ArgumentError) { @wm[1.23] = x }
25     assert_raise(ArgumentError) { @wm[:foo] = x }
26     assert_raise(ArgumentError) { @wm["foo#{rand}".to_sym] = x }
27   end
29   def test_getkey
30     k = "foo"
31     @wm[k] = true
32     assert_same(k, @wm.getkey("FOO".downcase))
33   end
35   def test_key?
36     assert_weak_include(:key?, "foo")
37     assert_not_send([@wm, :key?, "bar"])
38   end
40   def test_delete
41     k1 = "foo"
42     x1 = Object.new
43     @wm[k1] = x1
44     assert_equal x1, @wm[k1]
45     assert_equal x1, @wm.delete(k1)
46     assert_nil @wm[k1]
47     assert_nil @wm.delete(k1)
49     fallback =  @wm.delete(k1) do |key|
50       assert_equal k1, key
51       42
52     end
53     assert_equal 42, fallback
54   end
56   def test_clear
57     k = "foo"
58     @wm[k] = true
59     assert @wm[k]
60     assert_same @wm, @wm.clear
61     refute @wm[k]
62   end
64   def test_clear_bug_20691
65     assert_normal_exit(<<~RUBY)
66       map = ObjectSpace::WeakKeyMap.new
68       1_000.times do
69         1_000.times do
70           map[Object.new] = nil
71         end
73         map.clear
74       end
75     RUBY
76   end
78   def test_inspect
79     x = Object.new
80     k = Object.new
81     @wm[k] = x
82     assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect)
84     1000.times do |i|
85       @wm[i.to_s] = Object.new
86       @wm.inspect
87     end
88     assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect)
89   end
91   def test_no_hash_method
92     k = BasicObject.new
93     assert_raise NoMethodError do
94       @wm[k] = 42
95     end
96   end
98   def test_frozen_object
99     o = Object.new.freeze
100     assert_nothing_raised(FrozenError) {@wm[o] = 'foo'}
101     assert_nothing_raised(FrozenError) {@wm['foo'] = o}
102   end
104   def test_inconsistent_hash_key_memory_leak
105     assert_no_memory_leak [], '', <<~RUBY
106       class BadHash
107         def initialize
108           @hash = 0
109         end
111         def hash
112           @hash += 1
113         end
114       end
116       k = BadHash.new
117       wm = ObjectSpace::WeakKeyMap.new
119       100_000.times do |i|
120         wm[k] = i
121       end
122     RUBY
123   end
125   def test_compaction
126     omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
128     assert_separately(%w(-robjspace), <<-'end;')
129       wm = ObjectSpace::WeakKeyMap.new
130       key = Object.new
131       val = Object.new
132       wm[key] = val
134       GC.verify_compaction_references(expand_heap: true, toward: :empty)
136       assert_equal(val, wm[key])
137     end;
138   end
140   def test_gc_compact_stress
141     omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077
142     EnvUtil.under_gc_compact_stress { ObjectSpace::WeakKeyMap.new }
143   end
145   private
147   def assert_weak_include(m, k, n = 100)
148     if n > 0
149       return assert_weak_include(m, k, n-1)
150     end
151     1.times do
152       x = Object.new
153       @wm[k] = x
154       assert_send([@wm, m, k])
155       assert_send([@wm, m, "FOO".downcase])
156       x = Object.new
157     end
158   end