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]
This commit is contained in:
Jeremy Evans 2025-08-31 03:24:25 +09:00 committed by GitHub
parent dd4e7801f3
commit 5c7dfe85a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 41 additions and 10 deletions

View File

@ -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 */

View File

@ -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) }

View File

@ -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)