diff options
author | Jeremy Evans <code@jeremyevans.net> | 2025-08-31 03:24:25 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-08-30 11:24:25 -0700 |
commit | 5c7dfe85a1dc49334e2828791f0ade42eee662db (patch) | |
tree | 6df4cea1d0df4a839f893e2620c37c42c770ad9c | |
parent | dd4e7801f3f2b7e258016b8e22c6d8870516166b (diff) |
Initialize class dup/clone before calling initialize_dup/initialize_clone
Previously, you could override the class initialize_dup/initialize_clone
method and the class hierarchy would not be set correctly inside the
method before calling super.
This removes Module#initialize_copy, and instead makes Object#dup/clone
call the underlying C function (rb_mod_init_copy) before calling the
appropriate initialize_dup/initialize_clone method.
This results in the following fixes:
* The appropriate initialize_dup method is called (dup on a class
will respect superclass initialize_dup).
* Inside class initialize_dup/initialize_clone/initialize_copy,
class ancestor hierarchy is correct.
* Calling singleton_class inside initialize_dup no longer raises
a TypeError later in dup.
* Calling singleton_class.ancestors inside initialize_dup no
longer results in missing ancestors.
Fixes [Bug #21538]
-rw-r--r-- | object.c | 3 | ||||
-rw-r--r-- | test/ruby/test_class.rb | 40 | ||||
-rw-r--r-- | test/ruby/test_module.rb | 8 |
3 files changed, 41 insertions, 10 deletions
@@ -409,7 +409,7 @@ init_copy(VALUE dest, VALUE obj) break; case T_CLASS: case T_MODULE: - // noop: handled in class.c: rb_mod_init_copy + rb_mod_init_copy(dest, obj); break; case T_OBJECT: rb_obj_copy_ivar(dest, obj); @@ -4571,7 +4571,6 @@ InitVM_Object(void) rb_define_method(rb_cModule, "<=", rb_class_inherited_p, 1); rb_define_method(rb_cModule, ">", rb_mod_gt, 1); rb_define_method(rb_cModule, ">=", rb_mod_ge, 1); - rb_define_method(rb_cModule, "initialize_copy", rb_mod_init_copy, 1); /* in class.c */ rb_define_method(rb_cModule, "to_s", rb_mod_to_s, 0); rb_define_alias(rb_cModule, "inspect", "to_s"); rb_define_method(rb_cModule, "included_modules", rb_mod_included_modules, 0); /* in class.c */ diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 74541bba3f..f40817e7a1 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -259,6 +259,46 @@ class TestClass < Test::Unit::TestCase assert_raise(TypeError) { BasicObject.dup } end + def test_class_hierarchy_inside_initialize_dup_bug_21538 + ancestors = sc_ancestors = nil + b = Class.new + b.define_singleton_method(:initialize_dup) do |x| + ancestors = self.ancestors + sc_ancestors = singleton_class.ancestors + super(x) + end + + a = Class.new(b) + + c = a.dup + + expected_ancestors = [c, b, *Object.ancestors] + expected_sc_ancestors = [c.singleton_class, b.singleton_class, *Object.singleton_class.ancestors] + assert_equal expected_ancestors, ancestors + assert_equal expected_sc_ancestors, sc_ancestors + assert_equal expected_ancestors, c.ancestors + assert_equal expected_sc_ancestors, c.singleton_class.ancestors + end + + def test_class_hierarchy_inside_initialize_clone_bug_21538 + ancestors = sc_ancestors = nil + a = Class.new + a.define_singleton_method(:initialize_clone) do |x| + ancestors = self.ancestors + sc_ancestors = singleton_class.ancestors + super(x) + end + + c = a.clone + + expected_ancestors = [c, *Object.ancestors] + expected_sc_ancestors = [c.singleton_class, *Object.singleton_class.ancestors] + assert_equal expected_ancestors, ancestors + assert_equal expected_sc_ancestors, sc_ancestors + assert_equal expected_ancestors, c.ancestors + assert_equal expected_sc_ancestors, c.singleton_class.ancestors + end + def test_singleton_class assert_raise(TypeError) { 1.extend(Module.new) } assert_raise(TypeError) { 1.0.extend(Module.new) } diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 35476967d7..5805ad7d9b 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -418,9 +418,6 @@ class TestModule < Test::Unit::TestCase instance = klass.new assert_equal(:first, instance.foo) new_mod = Module.new { define_method(:foo) { :second } } - assert_raise(TypeError) do - mod.send(:initialize_copy, new_mod) - end 4.times { GC.start } assert_equal(:first, instance.foo) # [BUG] unreachable end @@ -435,11 +432,6 @@ class TestModule < Test::Unit::TestCase assert_equal([:x], m.instance_methods) assert_equal([:@x], m.instance_variables) assert_equal([:X], m.constants) - assert_raise(TypeError) do - m.module_eval do - initialize_copy(Module.new) - end - end m = Class.new(Module) do def initialize_copy(other) |