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>
This commit is contained in:
eileencodes 2023-02-08 15:27:28 -05:00 коммит произвёл Aaron Patterson
Родитель 0ddf29f4d1
Коммит b9e6580135
2 изменённых файлов: 37 добавлений и 0 удалений

31
class.c
Просмотреть файл

@ -404,6 +404,27 @@ class_init_copy_check(VALUE clone, VALUE orig)
}
}
struct cvc_table_copy_ctx {
VALUE clone;
struct rb_id_table * new_table;
};
static enum rb_id_table_iterator_result
cvc_table_copy(ID id, VALUE val, void *data) {
struct cvc_table_copy_ctx *ctx = (struct cvc_table_copy_ctx *)data;
struct rb_cvar_class_tbl_entry * orig_entry;
orig_entry = (struct rb_cvar_class_tbl_entry *)val;
struct rb_cvar_class_tbl_entry *ent;
ent = ALLOC(struct rb_cvar_class_tbl_entry);
ent->class_value = ctx->clone;
ent->global_cvar_state = orig_entry->global_cvar_state;
rb_id_table_insert(ctx->new_table, id, (VALUE)ent);
return ID_TABLE_CONTINUE;
}
static void
copy_tables(VALUE clone, VALUE orig)
{
@ -411,6 +432,16 @@ copy_tables(VALUE clone, VALUE orig)
rb_free_const_table(RCLASS_CONST_TBL(clone));
RCLASS_CONST_TBL(clone) = 0;
}
if (RCLASS_CVC_TBL(orig)) {
struct rb_id_table *rb_cvc_tbl = RCLASS_CVC_TBL(orig);
struct rb_id_table *rb_cvc_tbl_dup = rb_id_table_create(rb_id_table_size(rb_cvc_tbl));
struct cvc_table_copy_ctx ctx;
ctx.clone = clone;
ctx.new_table = rb_cvc_tbl_dup;
rb_id_table_foreach(rb_cvc_tbl, cvc_table_copy, &ctx);
RCLASS_CVC_TBL(clone) = rb_cvc_tbl_dup;
}
RCLASS_M_TBL(clone) = 0;
if (!RB_TYPE_P(clone, T_ICLASS)) {
st_data_t id;

Просмотреть файл

@ -33,6 +33,12 @@ class TestVariable < Test::Unit::TestCase
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)