Files
ruby/test/ruby/test_variable.rb
Eileen M. UchitelleandAaron Patterson cba152ff1f Backport cvar clone bug fix for 19379 to 3.1 (#7889)
* Copy cvar table on clone

When a class with a class variable is cloned we need to also copy the
cvar cache table from the original table to the clone. I found this bug
while working on fixing [Bug #19379]. While this does not fix that bug
directly it is still a required change to fix another bug revealed by
the fix in https://github.com/ruby/ruby/pull/7265

This needs to be backported to 3.2.x and 3.1.x.

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>

* Fix cvar caching when class is cloned

The class variable cache that was added in
https://github.com/ruby/ruby/pull/4544 changed the behavior of class
variables on cloned classes. As reported when a class is cloned AND a
class variable was set, and the class variable was read from the
original class, reading a class variable from the cloned class would
return the value from the original class.

This was happening because the IC (inline cache) is stored on the ISEQ
which is shared between the original and cloned class, therefore they
share the cache too.

To fix this we are now storing the `cref` in the cache so that we can
check if it's equal to the current `cref`. If it's different we don't
want to read from the cache. If it's the same we do. Cloned classes
don't share the same cref with their original class.

This will need to be backported to 3.1 in addition to 3.2 since the bug
exists in both versions.

We also added a marking function which was missing.

Fixes [Bug #19379]

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>

* Add missing write barrier

We were missing the write barrier for class_value to cref. This should
fix the segv we were seeing in http://ci.rvm.jp/logfiles/brlog.trunk-gc-asserts.20230601-165052

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>

---------

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
2023-07-25 11:05:27 +00:00

322 lines
7.5 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
class TestVariable < Test::Unit::TestCase
class Gods
@@rule = "Uranus"
def ruler0
@@rule
end
def self.ruler1 # <= per method definition style
@@rule
end
class << self # <= multiple method definition style
def ruler2
@@rule
end
end
end
module Olympians
@@rule ="Zeus"
def ruler3
@@rule
end
end
class Titans < Gods
@@rule = "Cronus" # modifies @@rule in Gods
include Olympians
def ruler4
@@rule
end
end
Athena = Gods.clone
def test_cloned_classes_copy_cvar_cache
assert_equal "Cronus", Athena.new.ruler0
end
def test_setting_class_variable_on_module_through_inheritance
mod = Module.new
mod.class_variable_set(:@@foo, 1)
mod.freeze
c = Class.new { include(mod) }
assert_raise(FrozenError) { c.class_variable_set(:@@foo, 2) }
assert_raise(FrozenError) { c.class_eval("@@foo = 2") }
assert_equal(1, c.class_variable_get(:@@foo))
end
Zeus = Gods.clone
def test_cloned_allows_setting_cvar
Zeus.class_variable_set(:@@rule, "Athena")
god = Gods.new.ruler0
zeus = Zeus.new.ruler0
assert_equal "Cronus", god
assert_equal "Athena", zeus
assert_not_equal god.object_id, zeus.object_id
end
def test_singleton_class_included_class_variable
c = Class.new
c.extend(Olympians)
assert_empty(c.singleton_class.class_variables)
assert_raise(NameError){ c.singleton_class.class_variable_get(:@@rule) }
c.class_variable_set(:@@foo, 1)
assert_equal([:@@foo], c.singleton_class.class_variables)
assert_equal(1, c.singleton_class.class_variable_get(:@@foo))
c = Class.new
c.extend(Olympians)
sc = Class.new(c)
assert_empty(sc.singleton_class.class_variables)
assert_raise(NameError){ sc.singleton_class.class_variable_get(:@@rule) }
c.class_variable_set(:@@foo, 1)
assert_equal([:@@foo], sc.singleton_class.class_variables)
assert_equal(1, sc.singleton_class.class_variable_get(:@@foo))
c = Class.new
o = c.new
o.extend(Olympians)
assert_equal([:@@rule], o.singleton_class.class_variables)
assert_equal("Zeus", o.singleton_class.class_variable_get(:@@rule))
c.class_variable_set(:@@foo, 1)
assert_equal([:@@foo, :@@rule], o.singleton_class.class_variables.sort)
assert_equal(1, o.singleton_class.class_variable_get(:@@foo))
end
def test_cvar_overtaken_by_parent_class
error = eval <<~EORB
class Parent
end
class Child < Parent
@@cvar = 1
def self.cvar
@@cvar
end
end
assert_equal 1, Child.cvar
class Parent
@@cvar = 2
end
assert_raise RuntimeError do
Child.cvar
end
EORB
assert_equal "class variable @@cvar of TestVariable::Child is overtaken by TestVariable::Parent", error.message
ensure
TestVariable.send(:remove_const, :Child) rescue nil
TestVariable.send(:remove_const, :Parent) rescue nil
end
def test_cvar_overtaken_by_module
error = eval <<~EORB
class ParentForModule
@@cvar = 1
def self.cvar
@@cvar
end
end
assert_equal 1, ParentForModule.cvar
module Mixin
@@cvar = 2
end
class ParentForModule
include Mixin
end
assert_raise RuntimeError do
ParentForModule.cvar
end
EORB
assert_equal "class variable @@cvar of TestVariable::ParentForModule is overtaken by TestVariable::Mixin", error.message
ensure
TestVariable.send(:remove_const, :Mixin) rescue nil
TestVariable.send(:remove_const, :ParentForModule) rescue nil
end
class IncludeRefinedModuleClassVariableNoWarning
module Mod
@@_test_include_refined_module_class_variable = true
end
module Mod2
refine Mod do
end
end
include Mod
def t
@@_test_include_refined_module_class_variable
end
end
def test_include_refined_module_class_variable
assert_warning('') do
IncludeRefinedModuleClassVariableNoWarning.new.t
end
end
def test_variable
assert_instance_of(Integer, $$)
# read-only variable
assert_raise(NameError) do
$$ = 5
end
assert_normal_exit("$*=0; $*", "[ruby-dev:36698]")
foobar = "foobar"
$_ = foobar
assert_equal(foobar, $_)
assert_equal("Cronus", Gods.new.ruler0)
assert_equal("Cronus", Gods.ruler1)
assert_equal("Cronus", Gods.ruler2)
assert_equal("Cronus", Titans.ruler1)
assert_equal("Cronus", Titans.ruler2)
atlas = Titans.new
assert_equal("Cronus", atlas.ruler0)
assert_equal("Zeus", atlas.ruler3)
assert_raise(RuntimeError) { atlas.ruler4 }
assert_nothing_raised do
class << Gods
defined?(@@rule) && @@rule
end
end
end
def test_local_variables
lvar = 1
assert_instance_of(Symbol, local_variables[0], "[ruby-dev:34008]")
lvar
end
def test_local_variables2
x = 1
proc do |y|
assert_equal([:x, :y], local_variables.sort)
end.call
x
end
def test_local_variables3
x = 1
proc do |y|
1.times do |z|
assert_equal([:x, :y, :z], local_variables.sort)
end
end.call
x
end
def test_shadowing_local_variables
bug9486 = '[ruby-core:60501] [Bug #9486]'
assert_equal([:x, :bug9486], tap {|x| break local_variables}, bug9486)
end
def test_shadowing_block_local_variables
bug9486 = '[ruby-core:60501] [Bug #9486]'
assert_equal([:x, :bug9486], tap {|;x| x = x; break local_variables}, bug9486)
end
def test_global_variables
gv = global_variables
assert_empty(gv.grep(/\A(?!\$)/))
assert_nil($~)
assert_not_include(gv, :$1)
/(\w)(\d)?(.)(.)(.)(.)(.)(.)(.)(.)(\d)?(.)/ =~ "globalglobalglobal"
assert_not_nil($~)
gv = global_variables - gv
assert_include(gv, :$1)
assert_not_include(gv, :$2)
assert_not_include(gv, :$11)
assert_include(gv, :$12)
end
def test_global_variable_0
assert_in_out_err(["-e", "$0='t'*1000;print $0"], "", /\At+\z/, [])
end
def test_global_variable_popped
assert_nothing_raised {
EnvUtil.suppress_warning {
eval("$foo; 1")
}
}
end
def test_constant_popped
assert_nothing_raised {
EnvUtil.suppress_warning {
eval("TestVariable::Gods; 1")
}
}
end
def test_special_constant_ivars
[ true, false, :symbol, "dsym#{rand(9999)}".to_sym, 1, 1.0 ].each do |v|
assert_empty v.instance_variables
msg = "can't modify frozen #{v.class}: #{v.inspect}"
assert_raise_with_message(FrozenError, msg) do
v.instance_variable_set(:@foo, :bar)
end
assert_nil EnvUtil.suppress_warning {v.instance_variable_get(:@foo)}
assert_not_send([v, :instance_variable_defined?, :@foo])
assert_raise_with_message(FrozenError, msg) do
v.remove_instance_variable(:@foo)
end
end
end
class ExIvar < Hash
def initialize
@a = 1
@b = 2
@c = 3
end
def ivars
[@a, @b, @c]
end
end
def test_external_ivars
3.times{
# check inline cache for external ivar access
assert_equal [1, 2, 3], ExIvar.new.ivars
}
end
def test_local_variables_with_kwarg
bug11674 = '[ruby-core:71437] [Bug #11674]'
v = with_kwargs_11(v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8,v9:9,v10:10,v11:11)
assert_equal(%i(v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11), v, bug11674)
end
private
def with_kwargs_11(v1:, v2:, v3:, v4:, v5:, v6:, v7:, v8:, v9:, v10:, v11:)
local_variables
end
end