Project

General

Profile

Actions

Bug #19394

closed

cvars in instance of cloned class point to source class's cvars even after class_variable_set on clone

Added by jamescdavis (James Davis) about 1 year ago. Updated 9 months ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-darwin21]
[ruby-core:112146]

Description

This unexpected change in behavior happens between Ruby 3.0.x and 3.1.x. In Ruby >= 3.1, when a class with a cvar is cloned (or duped), the cvar in instances of the cloned class continues to point to the source class’s cvar after the clone has its cvar updated with class_variable_set. In Ruby < 3.1, the cloned class instance points to the updated cvar, as expected.

It seems likely that this is a bug in the cvar cache introduced in Ruby 3.1.

Repro:

class Foo
  @@bar = 'bar'

  def print_bar
    puts "#{self.class.name} (from instance): #{@@bar} #{@@bar.object_id}"
  end
end

foo_bar = Foo.class_variable_get(:@@bar)
puts "Foo (class_variable_get): #{foo_bar} #{foo_bar.object_id}"

Foo.new.print_bar

FooClone = Foo.clone

FooClone.class_variable_set(:@@bar, 'bar_clone')

foo_clone_bar = FooClone.class_variable_get(:@@bar)
puts "FooClone (class_variable_get): #{foo_clone_bar} #{foo_clone_bar.object_id}"

FooClone.new.print_bar

Ruby 3.0.5:

Foo (class_variable_get): bar 60
Foo (from instance): bar 60
FooClone (class_variable_get): bar_clone 80
FooClone (from instance): bar_clone 80

Ruby 3.1.3, 3.2.0:

Foo (class_variable_get): bar 60
Foo (from instance): bar 60
FooClone (class_variable_get): bar_clone 80
FooClone (from instance): bar 60

Something similar happens when there are multiple clones and a cvar that the source class does not have defined is set on the clones. In this case, the cvars in instances of the clones all point to the first clone’s cvar.

Repro:

class Foo
  def print_bar
    puts "#{self.class.name} (from instance): #{@@bar} #{@@bar.object_id}"
  end
end

Foo1 = Foo.clone
Foo2 = Foo.clone
Foo3 = Foo.clone

Foo1.class_variable_set(:@@bar, 'bar1')
Foo2.class_variable_set(:@@bar, 'bar2')
Foo3.class_variable_set(:@@bar, 'bar3')

foo1_bar = Foo1.class_variable_get(:@@bar)
foo2_bar = Foo2.class_variable_get(:@@bar)
foo3_bar = Foo3.class_variable_get(:@@bar)

puts "Foo1 (class_variable_get): #{foo1_bar} #{foo1_bar.object_id}"
puts "Foo2 (class_variable_get): #{foo2_bar} #{foo2_bar.object_id}"
puts "Foo3 (class_variable_get): #{foo3_bar} #{foo3_bar.object_id}"

Foo1.new.print_bar
Foo2.new.print_bar
Foo3.new.print_bar

Ruby 3.0.5:

Foo1 (class_variable_get): bar1 60
Foo2 (class_variable_get): bar2 80
Foo3 (class_variable_get): bar3 100
Foo1 (from instance): bar1 60
Foo2 (from instance): bar2 80
Foo3 (from instance): bar3 100

Ruby 3.1.3, 3.2.0:

Foo1 (class_variable_get): bar1 60
Foo2 (class_variable_get): bar2 80
Foo3 (class_variable_get): bar3 100
Foo1 (from instance): bar1 60
Foo2 (from instance): bar1 60
Foo3 (from instance): bar1 60

Related issues 1 (0 open1 closed)

Related to Ruby master - Feature #17763: Implement cache for cvarsClosedActions
Actions

Also available in: Atom PDF

Like0
Like0Like1Like1Like0Like0Like0Like0Like0Like0