Project

General

Profile

Actions

Bug #21538

closed

initialize_dup not called when duping class/module

Added by chucke (Tiago Cardoso) about 1 month ago. Updated 13 days ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:122945]

Description

Not sure whether this is expected behaviour or not, but just leaving it here to start the debate on whether callbacks like initialize_dup are supposed to be called when a module or class is duped (the same happens with initialize_copy and initialize_clone btw):

class A
  def initialize_dup(_)
    puts "dup instance"
    super
  end
  def self.initialize_dup(_)
    puts "dup class"
    super
  end
end

A.new.dup #=> "dup instance"
A.dup #=> nothing

Updated by jeremyevans0 (Jeremy Evans) 14 days ago

The example given isn't a bug. initialize_dup is called on the new instance (in this example, the instance of Class), and dup does not copy singleton classes. If you define Class#inititialize_dup, it works as expected:

class A
  def initialize_dup(_)
    puts "dup instance"
    super
  end
end
class Class
  def initialize_dup(_)
    puts "dup class"
    super
  end
end

A.new.dup
# prints "dup instance"

A.dup
# prints "dup class"

dup is different than clone, because clone copies the singleton class, and therefore will pick up singleton methods:

class A
  def initialize_clone(_)
    puts "clone instance"
    super
  end
  def self.initialize_clone(_)
    puts "clone class"
    super
  end
end

A.new.clone
# prints "clone instance"

A.clone
# prints "clone class"

That being said, there are two definite bugs and another probable bug in Class#dup, as evidenced by this example:

class Class
  def initialize_dup(_)
    p ancestors
    p singleton_class.ancestors
    super
  end
end

class B
  def self.initialize_dup(_)
    puts "dup class"
    super
  end
end

class A < B
end

puts
p A.ancestors
p A.singleton_class.ancestors

puts
C = A.dup

puts
p C.ancestors
p C.singleton_class.ancestors

Output on Ruby 3.4 (with comments on bugs):

[A, B, Object, Kernel, BasicObject]
[#<Class:A>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

# Probable Bug: During initialize_dup, ancestors are missing,
# and therefore ancestor initialize_dup singleton method not called
[#<Class:0x00000d7e572e8ca8>]
[#<Class:#<Class:0x00000d7e572e8ca8>>, Class, Module, Object, Kernel, BasicObject]

# Definite Bug 1: after dup, singleton class ancestors are missing
[C, B, Object, Kernel, BasicObject]
[#<Class:C>, Class, Module, Object, Kernel, BasicObject]

Output on master branch:

[A, B, Object, Kernel, BasicObject]
[#<Class:A>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

# Definite Bug 2: calling singleton_class inside Class#initialize_dup
# results in TypeError
[#<Class:0x00000b7eeb88aca0>]
[#<Class:#<Class:0x00000b7eeb88aca0>>, Class, Module, Object, Kernel, BasicObject]
-:5:in 'Module#initialize_copy': already initialized class (TypeError)
        from -:5:in 'Kernel#initialize_dup'
        from -:5:in 'Class#initialize_dup'
        from -:25:in 'Kernel#dup'
        from -:25:in '<main>'

The reason I call the first bug probable and not definite is that it is at least defensible that ancestor setup occurs inside dup, but after the call to Class#initialize_dup. I still think it should be considered a bug and fixed. I'm guessing fixing definite bug 2 requires fixing the probable bug.

For clone, there is a similar issue of missing ancestors (but not singleton class ancestors) inside initialize_clone:

class B
  def self.initialize_clone(_)
    p ancestors
    p singleton_class.ancestors
    puts "clone class"
    super
  end
end

class A < B
end


puts
p A.ancestors
p A.singleton_class.ancestors

puts
C = A.clone

puts
p C.ancestors
p C.singleton_class.ancestors

Output:

[A, B, Object, Kernel, BasicObject]
[#<Class:A>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

# Probable bug: During initialize_clone, ancestors are missing
[#<Class:0x00000a047fffaab8>]
[#<Class:#<Class:0x00000a047fffaab8>>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
clone class

[C, B, Object, Kernel, BasicObject]
[#<Class:C>, #<Class:B>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

Actions #3

Updated by jeremyevans (Jeremy Evans) 13 days ago

  • Status changed from Open to Closed

Applied in changeset git|5c7dfe85a1dc49334e2828791f0ade42eee662db.


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]

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0