mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00

This fixes cases where exceptions raised using Thread#raise are swallowed by finalizers and not delivered to the running thread. This could cause issues with finalizers that rely on pending interrupts, but that case is expected to be rarer. Fixes [Bug #13876] Fixes [Bug #15507] Co-authored-by: Koichi Sasada <ko1@atdot.net>
275 lines
6.4 KiB
Ruby
275 lines
6.4 KiB
Ruby
# frozen_string_literal: false
|
|
require 'test/unit'
|
|
|
|
class TestObjectSpace < Test::Unit::TestCase
|
|
def self.deftest_id2ref(obj)
|
|
/:(\d+)/ =~ caller[0]
|
|
file = $`
|
|
line = $1.to_i
|
|
code = <<"End"
|
|
define_method("test_id2ref_#{line}") {\
|
|
o = ObjectSpace._id2ref(obj.object_id);\
|
|
assert_same(obj, o, "didn't round trip: \#{obj.inspect}");\
|
|
}
|
|
End
|
|
eval code, binding, file, line
|
|
end
|
|
|
|
deftest_id2ref(-0x4000000000000001)
|
|
deftest_id2ref(-0x4000000000000000)
|
|
deftest_id2ref(-0x40000001)
|
|
deftest_id2ref(-0x40000000)
|
|
deftest_id2ref(-1)
|
|
deftest_id2ref(0)
|
|
deftest_id2ref(1)
|
|
deftest_id2ref(0x3fffffff)
|
|
deftest_id2ref(0x40000000)
|
|
deftest_id2ref(0x3fffffffffffffff)
|
|
deftest_id2ref(0x4000000000000000)
|
|
deftest_id2ref(:a)
|
|
deftest_id2ref(:abcdefghijilkjl)
|
|
deftest_id2ref(:==)
|
|
deftest_id2ref(Object.new)
|
|
deftest_id2ref(self)
|
|
deftest_id2ref(true)
|
|
deftest_id2ref(false)
|
|
deftest_id2ref(nil)
|
|
|
|
def test_id2ref_liveness
|
|
assert_normal_exit <<-EOS
|
|
ids = []
|
|
10.times{
|
|
1_000.times{
|
|
ids << 'hello'.object_id
|
|
}
|
|
objs = ids.map{|id|
|
|
begin
|
|
ObjectSpace._id2ref(id)
|
|
rescue RangeError
|
|
nil
|
|
end
|
|
}
|
|
GC.start
|
|
objs.each{|e| e.inspect}
|
|
}
|
|
EOS
|
|
end
|
|
|
|
def test_id2ref_invalid_argument
|
|
msg = /no implicit conversion/
|
|
assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(nil)}
|
|
assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(false)}
|
|
assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(true)}
|
|
assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(:a)}
|
|
assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref("0")}
|
|
assert_raise_with_message(TypeError, msg) {ObjectSpace._id2ref(Object.new)}
|
|
end
|
|
|
|
def test_count_objects
|
|
h = {}
|
|
ObjectSpace.count_objects(h)
|
|
assert_kind_of(Hash, h)
|
|
assert_empty(h.keys.delete_if {|x| x.is_a?(Symbol) || x.is_a?(Integer) })
|
|
assert_empty(h.values.delete_if {|x| x.is_a?(Integer) })
|
|
|
|
h = ObjectSpace.count_objects
|
|
assert_kind_of(Hash, h)
|
|
assert_empty(h.keys.delete_if {|x| x.is_a?(Symbol) || x.is_a?(Integer) })
|
|
assert_empty(h.values.delete_if {|x| x.is_a?(Integer) })
|
|
|
|
assert_raise(TypeError) { ObjectSpace.count_objects(1) }
|
|
|
|
h0 = {:T_FOO=>1000}
|
|
h = ObjectSpace.count_objects(h0)
|
|
assert_same(h0, h)
|
|
assert_equal(0, h0[:T_FOO])
|
|
end
|
|
|
|
def test_finalizer
|
|
assert_in_out_err(["-e", <<-END], "", %w(:ok :ok :ok :ok), [])
|
|
a = []
|
|
ObjectSpace.define_finalizer(a) { p :ok }
|
|
b = a.dup
|
|
ObjectSpace.define_finalizer(a) { p :ok }
|
|
!b
|
|
END
|
|
assert_raise(ArgumentError) { ObjectSpace.define_finalizer([], Object.new) }
|
|
|
|
code = proc do |priv|
|
|
<<-"CODE"
|
|
fin = Object.new
|
|
class << fin
|
|
#{priv}def call(id)
|
|
puts "finalized"
|
|
end
|
|
end
|
|
ObjectSpace.define_finalizer([], fin)
|
|
CODE
|
|
end
|
|
assert_in_out_err([], code[""], ["finalized"])
|
|
assert_in_out_err([], code["private "], ["finalized"])
|
|
c = EnvUtil.labeled_class("C\u{3042}").new
|
|
o = Object.new
|
|
assert_raise_with_message(ArgumentError, /C\u{3042}/) {
|
|
ObjectSpace.define_finalizer(o, c)
|
|
}
|
|
end
|
|
|
|
def test_finalizer_with_super
|
|
assert_in_out_err(["-e", <<-END], "", %w(:ok), [])
|
|
class A
|
|
def foo
|
|
end
|
|
end
|
|
|
|
class B < A
|
|
def foo
|
|
1.times { super }
|
|
end
|
|
end
|
|
|
|
class C
|
|
module M
|
|
end
|
|
|
|
FINALIZER = proc do
|
|
M.module_eval(__FILE__, "", __LINE__) do
|
|
end
|
|
end
|
|
|
|
def define_finalizer
|
|
ObjectSpace.define_finalizer(self, FINALIZER)
|
|
end
|
|
end
|
|
|
|
class D
|
|
def foo
|
|
B.new.foo
|
|
end
|
|
end
|
|
|
|
C::M.singleton_class.send :define_method, :module_eval do |src, id, line|
|
|
end
|
|
|
|
GC.stress = true
|
|
10.times do
|
|
C.new.define_finalizer
|
|
D.new.foo
|
|
end
|
|
|
|
p :ok
|
|
END
|
|
end
|
|
|
|
def test_exception_in_finalizer
|
|
assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", [], /finalizing \(RuntimeError\)/)
|
|
begin;
|
|
ObjectSpace.define_finalizer(Object.new) {raise "finalizing"}
|
|
end;
|
|
end
|
|
|
|
def test_finalizer_thread_raise
|
|
GC.disable
|
|
fzer = proc do |id|
|
|
sleep 0.2
|
|
end
|
|
2.times do
|
|
o = Object.new
|
|
ObjectSpace.define_finalizer(o, fzer)
|
|
end
|
|
|
|
my_error = Class.new(RuntimeError)
|
|
begin
|
|
main_th = Thread.current
|
|
Thread.new do
|
|
sleep 0.1
|
|
main_th.raise(my_error)
|
|
end
|
|
GC.start
|
|
puts "After GC"
|
|
sleep(10)
|
|
assert(false)
|
|
rescue my_error
|
|
end
|
|
end
|
|
|
|
def test_each_object
|
|
klass = Class.new
|
|
new_obj = klass.new
|
|
|
|
found = []
|
|
count = ObjectSpace.each_object(klass) do |obj|
|
|
found << obj
|
|
end
|
|
assert_equal(1, count)
|
|
assert_equal(1, found.size)
|
|
assert_same(new_obj, found[0])
|
|
end
|
|
|
|
def test_each_object_enumerator
|
|
klass = Class.new
|
|
new_obj = klass.new
|
|
|
|
found = []
|
|
counter = ObjectSpace.each_object(klass)
|
|
assert_equal(1, counter.each {|obj| found << obj})
|
|
assert_equal(1, found.size)
|
|
assert_same(new_obj, found[0])
|
|
end
|
|
|
|
def test_each_object_no_gabage
|
|
assert_separately([], <<-End)
|
|
GC.disable
|
|
eval('begin; 1.times{}; rescue; ensure; end')
|
|
arys = []
|
|
ObjectSpace.each_object(Array){|ary|
|
|
arys << ary
|
|
}
|
|
GC.enable
|
|
arys.each{|ary|
|
|
begin
|
|
assert_equal(String, ary.inspect.class) # should not cause SEGV
|
|
rescue RuntimeError
|
|
# rescue "can't modify frozen File" error.
|
|
end
|
|
}
|
|
End
|
|
end
|
|
|
|
def test_each_object_recursive_key
|
|
assert_normal_exit(<<-'end;', '[ruby-core:66742] [Bug #10579]')
|
|
h = {["foo"]=>nil}
|
|
p Thread.current[:__recursive_key__]
|
|
end;
|
|
end
|
|
|
|
def test_each_object_singleton_class
|
|
assert_separately([], <<-End)
|
|
class C
|
|
class << self
|
|
$c = self
|
|
end
|
|
end
|
|
|
|
exist = false
|
|
ObjectSpace.each_object(Class){|o|
|
|
exist = true if $c == o
|
|
}
|
|
assert(exist, 'Bug #11360')
|
|
End
|
|
|
|
klass = Class.new
|
|
instance = klass.new
|
|
sclass = instance.singleton_class
|
|
meta = klass.singleton_class
|
|
assert_kind_of(meta, sclass)
|
|
assert_include(ObjectSpace.each_object(meta).to_a, sclass)
|
|
end
|
|
|
|
def test_each_object_with_allocation
|
|
assert_normal_exit(<<-End)
|
|
list = []
|
|
ObjectSpace.each_object { |o| list << Object.new }
|
|
End
|
|
end
|
|
end
|