From b9e6580135d20cf3fff2e872b3e54c08e96468cb Mon Sep 17 00:00:00 2001 From: eileencodes Date: Wed, 8 Feb 2023 15:27:28 -0500 Subject: [PATCH] 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 --- class.c | 31 +++++++++++++++++++++++++++++++ test/ruby/test_variable.rb | 6 ++++++ 2 files changed, 37 insertions(+) diff --git a/class.c b/class.c index 333a9d78e5..cf1731c1ab 100644 --- a/class.c +++ b/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; diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index 195f5c94fb..b7892f0cdc 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -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)