summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakashi Kokubun <takashikkbn@gmail.com>2026-05-11 13:54:55 -0700
committerTakashi Kokubun <takashikkbn@gmail.com>2026-05-11 13:54:55 -0700
commit8539f0b386e2e42f8fe5ac12a2fd9e84872d8c7c (patch)
tree11eccee9cbf04ac5f408b75a49a232b53029923d
parentb18acb27c3f1b1ae26bbae6e53e94e246dc0e973 (diff)
merge revision(s) c0d86a0103de7130943d54b4a290b76ec7e0c135,47e061277ac194a36659510bcf4f3190bde629a6: [Backport #21952]
class.c: rb_class_duplicate_classext also dup content of cvc_tbl [Bug #21952] Shallow copying the table result in the same memory being shared between multiple box, causing double free when one of the box is garbage collected. --- class.c: Make cvc_tbl a managed object [Bug #21952] Solves the double-free or use after-free concern with boxes. Now entries can safely be used for copy-on-write. Also is likely necessary to make it save to read cvar from secondary ractors, as allowed since: ab32c0e690b805cdaaf264ad4c3421696c588204
-rw-r--r--class.c69
-rw-r--r--debug_counter.h1
-rw-r--r--ext/objspace/objspace.c1
-rw-r--r--gc.c51
-rw-r--r--id_table.c79
-rw-r--r--id_table.h10
-rw-r--r--imemo.c13
-rw-r--r--internal/class.h17
-rw-r--r--internal/imemo.h1
-rw-r--r--iseq.c4
-rw-r--r--test/ruby/test_box.rb31
-rw-r--r--variable.c29
-rw-r--r--vm_insnhelper.c7
-rw-r--r--yjit/src/cruby_bindings.inc.rs2
-rw-r--r--zjit/src/cruby_bindings.inc.rs2
15 files changed, 196 insertions, 121 deletions
diff --git a/class.c b/class.c
index c9fbe14be2..de87ddffda 100644
--- a/class.c
+++ b/class.c
@@ -84,13 +84,6 @@ static void rb_class_remove_from_super_subclasses(VALUE klass);
static void rb_class_remove_from_module_subclasses(VALUE klass);
static void rb_class_classext_free_subclasses(rb_classext_t *ext);
-static enum rb_id_table_iterator_result
-cvar_table_free_i(VALUE value, void *ctx)
-{
- xfree((void *)value);
- return ID_TABLE_CONTINUE;
-}
-
rb_classext_t *
rb_class_unlink_classext(VALUE klass, const rb_box_t *box)
{
@@ -113,11 +106,6 @@ rb_class_classext_free(VALUE klass, rb_classext_t *ext, bool is_prime)
rb_free_const_table(tbl);
}
- if ((tbl = RCLASSEXT_CVC_TBL(ext)) != NULL) {
- rb_id_table_foreach_values(tbl, cvar_table_free_i, NULL);
- rb_id_table_free(tbl);
- }
-
if (is_prime) {
rb_class_remove_from_super_subclasses(klass);
rb_class_classext_free_subclasses(ext);
@@ -222,14 +210,6 @@ struct duplicate_id_tbl_data {
};
static enum rb_id_table_iterator_result
-duplicate_classext_id_table_i(ID key, VALUE value, void *data)
-{
- struct rb_id_table *tbl = (struct rb_id_table *)data;
- rb_id_table_insert(tbl, key, value);
- return ID_TABLE_CONTINUE;
-}
-
-static enum rb_id_table_iterator_result
duplicate_classext_m_tbl_i(ID key, VALUE value, void *data)
{
struct duplicate_id_tbl_data *arg = (struct duplicate_id_tbl_data *)data;
@@ -257,22 +237,6 @@ duplicate_classext_m_tbl(struct rb_id_table *orig, VALUE klass, bool init_missin
return tbl;
}
-static struct rb_id_table *
-duplicate_classext_id_table(struct rb_id_table *orig, bool init_missing)
-{
- struct rb_id_table *tbl;
-
- if (!orig) {
- if (init_missing)
- return rb_id_table_create(0);
- else
- return NULL;
- }
- tbl = rb_id_table_create(rb_id_table_size(orig));
- rb_id_table_foreach(orig, duplicate_classext_id_table_i, tbl);
- return tbl;
-}
-
static rb_const_entry_t *
duplicate_classext_const_entry(rb_const_entry_t *src, VALUE klass)
{
@@ -406,7 +370,14 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_box_t *bo
* RCLASSEXT_CC_TBL(copy) = NULL
*/
- RCLASSEXT_CVC_TBL(ext) = duplicate_classext_id_table(RCLASSEXT_CVC_TBL(orig), dup_iclass);
+ VALUE cvc_table = RCLASSEXT_CVC_TBL(orig);
+ if (cvc_table) {
+ cvc_table = rb_marked_id_table_dup(cvc_table);
+ }
+ else if (dup_iclass) {
+ cvc_table = rb_marked_id_table_new(2);
+ }
+ RB_OBJ_WRITE(klass, &RCLASSEXT_CVC_TBL(ext), cvc_table);
// Subclasses/back-pointers are only in the prime classext.
@@ -964,9 +935,15 @@ class_init_copy_check(VALUE clone, VALUE orig)
struct cvc_table_copy_ctx {
VALUE clone;
- struct rb_id_table * new_table;
+ VALUE new_table;
};
+static struct rb_cvar_class_tbl_entry *
+cvc_table_entry_alloc(void)
+{
+ return (struct rb_cvar_class_tbl_entry *)SHAREABLE_IMEMO_NEW(struct rb_cvar_class_tbl_entry, imemo_cvar_entry, 0);
+}
+
static enum rb_id_table_iterator_result
cvc_table_copy(ID id, VALUE val, void *data)
{
@@ -976,13 +953,11 @@ cvc_table_copy(ID id, VALUE val, void *data)
struct rb_cvar_class_tbl_entry *ent;
- ent = ALLOC(struct rb_cvar_class_tbl_entry);
- ent->class_value = ctx->clone;
- ent->cref = orig_entry->cref;
+ ent = cvc_table_entry_alloc();
+ RB_OBJ_WRITE((VALUE)ent, &ent->class_value, ctx->clone);
+ RB_OBJ_WRITE(ctx->clone, &ent->cref, orig_entry->cref);
ent->global_cvar_state = orig_entry->global_cvar_state;
- rb_id_table_insert(ctx->new_table, id, (VALUE)ent);
-
- RB_OBJ_WRITTEN(ctx->clone, Qundef, ent->cref);
+ rb_marked_id_table_insert(ctx->new_table, id, (VALUE)ent);
return ID_TABLE_CONTINUE;
}
@@ -995,13 +970,13 @@ copy_tables(VALUE clone, VALUE orig)
RCLASS_WRITE_CONST_TBL(clone, 0, false);
}
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));
+ VALUE rb_cvc_tbl = RCLASS_CVC_TBL(orig);
+ VALUE rb_cvc_tbl_dup = rb_marked_id_table_new(rb_marked_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);
+ rb_marked_id_table_foreach(rb_cvc_tbl, cvc_table_copy, &ctx);
RCLASS_WRITE_CVC_TBL(clone, rb_cvc_tbl_dup);
}
rb_id_table_free(RCLASS_M_TBL(clone));
diff --git a/debug_counter.h b/debug_counter.h
index c8d8ed8f11..723e9e1249 100644
--- a/debug_counter.h
+++ b/debug_counter.h
@@ -305,6 +305,7 @@ RB_DEBUG_COUNTER(obj_imemo_ment)
RB_DEBUG_COUNTER(obj_imemo_iseq)
RB_DEBUG_COUNTER(obj_imemo_env)
RB_DEBUG_COUNTER(obj_imemo_tmpbuf)
+RB_DEBUG_COUNTER(obj_imemo_cvar_entry)
RB_DEBUG_COUNTER(obj_imemo_cref)
RB_DEBUG_COUNTER(obj_imemo_svar)
RB_DEBUG_COUNTER(obj_imemo_throw_data)
diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c
index 8b3045fda3..248647f0ed 100644
--- a/ext/objspace/objspace.c
+++ b/ext/objspace/objspace.c
@@ -500,6 +500,7 @@ count_imemo_objects(int argc, VALUE *argv, VALUE self)
INIT_IMEMO_TYPE_ID(imemo_ment);
INIT_IMEMO_TYPE_ID(imemo_iseq);
INIT_IMEMO_TYPE_ID(imemo_tmpbuf);
+ INIT_IMEMO_TYPE_ID(imemo_cvar_entry);
INIT_IMEMO_TYPE_ID(imemo_callinfo);
INIT_IMEMO_TYPE_ID(imemo_callcache);
INIT_IMEMO_TYPE_ID(imemo_constcache);
diff --git a/gc.c b/gc.c
index 0afba4f22c..22c817ec18 100644
--- a/gc.c
+++ b/gc.c
@@ -2270,9 +2270,6 @@ classext_memsize(rb_classext_t *ext, bool prime, VALUE box_value, void *arg)
if (RCLASSEXT_M_TBL(ext)) {
s += rb_id_table_memsize(RCLASSEXT_M_TBL(ext));
}
- if (RCLASSEXT_CVC_TBL(ext)) {
- s += rb_id_table_memsize(RCLASSEXT_CVC_TBL(ext));
- }
if (RCLASSEXT_CONST_TBL(ext)) {
s += rb_id_table_memsize(RCLASSEXT_CONST_TBL(ext));
}
@@ -2818,26 +2815,6 @@ mark_const_tbl(rb_objspace_t *objspace, struct rb_id_table *tbl)
rb_id_table_foreach_values(tbl, mark_const_entry_i, objspace);
}
-static enum rb_id_table_iterator_result
-mark_cvc_tbl_i(VALUE cvc_entry, void *objspace)
-{
- struct rb_cvar_class_tbl_entry *entry;
-
- entry = (struct rb_cvar_class_tbl_entry *)cvc_entry;
-
- RUBY_ASSERT(entry->cref == 0 || (BUILTIN_TYPE((VALUE)entry->cref) == T_IMEMO && IMEMO_TYPE_P(entry->cref, imemo_cref)));
- gc_mark_internal((VALUE)entry->cref);
-
- return ID_TABLE_CONTINUE;
-}
-
-static void
-mark_cvc_tbl(rb_objspace_t *objspace, struct rb_id_table *tbl)
-{
- if (!tbl) return;
- rb_id_table_foreach_values(tbl, mark_cvc_tbl_i, objspace);
-}
-
#if STACK_GROW_DIRECTION < 0
#define GET_STACK_BOUNDS(start, end, appendix) ((start) = STACK_END, (end) = STACK_START)
#elif STACK_GROW_DIRECTION > 0
@@ -3082,6 +3059,7 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE box_value, void *a
if (!rb_gc_checking_shareable()) {
// unshareable
gc_mark_internal(RCLASSEXT_FIELDS_OBJ(ext));
+ gc_mark_internal(RCLASSEXT_CVC_TBL(ext));
}
if (!RCLASSEXT_SHARED_CONST_TBL(ext) && RCLASSEXT_CONST_TBL(ext)) {
@@ -3089,7 +3067,6 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE box_value, void *a
}
mark_m_tbl(objspace, RCLASSEXT_CALLABLE_M_TBL(ext));
gc_mark_internal(RCLASSEXT_CC_TBL(ext));
- mark_cvc_tbl(objspace, RCLASSEXT_CVC_TBL(ext));
gc_mark_internal(RCLASSEXT_CLASSPATH(ext));
}
@@ -3716,29 +3693,6 @@ update_m_tbl(void *objspace, struct rb_id_table *tbl)
}
static enum rb_id_table_iterator_result
-update_cvc_tbl_i(VALUE cvc_entry, void *objspace)
-{
- struct rb_cvar_class_tbl_entry *entry;
-
- entry = (struct rb_cvar_class_tbl_entry *)cvc_entry;
-
- if (entry->cref) {
- TYPED_UPDATE_IF_MOVED(objspace, rb_cref_t *, entry->cref);
- }
-
- entry->class_value = gc_location_internal(objspace, entry->class_value);
-
- return ID_TABLE_CONTINUE;
-}
-
-static void
-update_cvc_tbl(void *objspace, struct rb_id_table *tbl)
-{
- if (!tbl) return;
- rb_id_table_foreach_values(tbl, update_cvc_tbl_i, objspace);
-}
-
-static enum rb_id_table_iterator_result
update_const_tbl_i(VALUE value, void *objspace)
{
rb_const_entry_t *ce = (rb_const_entry_t *)value;
@@ -3812,7 +3766,7 @@ update_classext(rb_classext_t *ext, bool is_prime, VALUE box_value, void *arg)
update_const_tbl(objspace, RCLASSEXT_CONST_TBL(ext));
}
UPDATE_IF_MOVED(objspace, RCLASSEXT_CC_TBL(ext));
- update_cvc_tbl(objspace, RCLASSEXT_CVC_TBL(ext));
+ UPDATE_IF_MOVED(objspace, RCLASSEXT_CVC_TBL(ext));
update_superclasses(objspace, ext);
update_subclasses(objspace, ext);
@@ -3831,6 +3785,7 @@ update_iclass_classext(rb_classext_t *ext, bool is_prime, VALUE box_value, void
update_m_tbl(objspace, RCLASSEXT_M_TBL(ext));
update_m_tbl(objspace, RCLASSEXT_CALLABLE_M_TBL(ext));
UPDATE_IF_MOVED(objspace, RCLASSEXT_CC_TBL(ext));
+ UPDATE_IF_MOVED(objspace, RCLASSEXT_CVC_TBL(ext));
update_subclasses(objspace, ext);
update_classext_values(objspace, ext, true);
diff --git a/id_table.c b/id_table.c
index cece14c389..0bcdfaf408 100644
--- a/id_table.c
+++ b/id_table.c
@@ -438,3 +438,82 @@ rb_managed_id_table_delete(VALUE table, ID id)
{
return rb_id_table_delete(managed_id_table_ptr(table), id);
}
+
+static enum rb_id_table_iterator_result
+marked_id_table_mark_i(VALUE val, void *data)
+{
+ rb_gc_mark_movable(val);
+ return ID_TABLE_CONTINUE;
+}
+
+static void
+marked_id_table_mark(void *ptr)
+{
+ struct rb_id_table *tbl = (struct rb_id_table *)ptr;
+ rb_id_table_foreach_values(tbl, marked_id_table_mark_i, NULL);
+}
+
+static enum rb_id_table_iterator_result
+marked_id_table_compact_check_i(VALUE value, void *data)
+{
+ if (rb_gc_location(value) != value) {
+ return ID_TABLE_REPLACE;
+ }
+ return ID_TABLE_CONTINUE;
+}
+
+static enum rb_id_table_iterator_result
+marked_id_table_compact_replace_i(VALUE *value, void *data, int existing)
+{
+ *value = rb_gc_location(*value);
+ return ID_TABLE_CONTINUE;
+}
+
+static void
+marked_id_table_compact(void *ptr)
+{
+ struct rb_id_table *tbl = (struct rb_id_table *)ptr;
+ rb_id_table_foreach_values_with_replace(tbl, marked_id_table_compact_check_i, marked_id_table_compact_replace_i, NULL);
+}
+
+const rb_data_type_t rb_marked_id_table_type = {
+ .wrap_struct_name = "VM/marked_id_table",
+ .function = {
+ .dmark = marked_id_table_mark,
+ .dfree = managed_id_table_free,
+ .dsize = managed_id_table_memsize,
+ .dcompact = marked_id_table_compact,
+ },
+ .parent = &rb_managed_id_table_type,
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
+};
+
+VALUE
+rb_marked_id_table_new(size_t capa)
+{
+ return rb_managed_id_table_create(&rb_marked_id_table_type, capa);
+}
+
+int
+rb_marked_id_table_insert(VALUE table, ID id, VALUE val)
+{
+ int result = rb_managed_id_table_insert(table, id, val);
+ RB_OBJ_WRITTEN(table, Qundef, val);
+ return result;
+}
+
+static enum rb_id_table_iterator_result
+marked_id_table_dup_i(VALUE val, void *data)
+{
+ VALUE new_table = (VALUE)data;
+ RB_OBJ_WRITTEN(new_table, Qundef, val);
+ return ID_TABLE_CONTINUE;
+}
+
+VALUE
+rb_marked_id_table_dup(VALUE old_table)
+{
+ VALUE new_table = rb_managed_id_table_dup(old_table);
+ rb_managed_id_table_foreach_values(new_table, marked_id_table_dup_i, (void *)new_table);
+ return new_table;
+}
diff --git a/id_table.h b/id_table.h
index 0c8cd343ee..fde1a0a2c0 100644
--- a/id_table.h
+++ b/id_table.h
@@ -47,6 +47,16 @@ int rb_managed_id_table_delete(VALUE table, ID id);
extern const rb_data_type_t rb_managed_id_table_type;
+VALUE rb_marked_id_table_new(size_t capa);
+int rb_marked_id_table_insert(VALUE table, ID id, VALUE val);
+VALUE rb_marked_id_table_dup(VALUE table);
+
+// alisases
+#define rb_marked_id_table_size rb_managed_id_table_size
+#define rb_marked_id_table_lookup rb_managed_id_table_lookup
+#define rb_marked_id_table_foreach rb_managed_id_table_foreach
+#define rb_marked_id_table_foreach_values rb_managed_id_table_foreach_values
+
RUBY_SYMBOL_EXPORT_BEGIN
size_t rb_id_table_size(const struct rb_id_table *tbl);
RUBY_SYMBOL_EXPORT_END
diff --git a/imemo.c b/imemo.c
index 42f6615a5e..5b7e017f9b 100644
--- a/imemo.c
+++ b/imemo.c
@@ -29,6 +29,7 @@ rb_imemo_name(enum imemo_type type)
IMEMO_NAME(svar);
IMEMO_NAME(throw_data);
IMEMO_NAME(tmpbuf);
+ IMEMO_NAME(cvar_entry);
IMEMO_NAME(fields);
#undef IMEMO_NAME
}
@@ -247,6 +248,8 @@ rb_imemo_memsize(VALUE obj)
size += ((rb_imemo_tmpbuf_t *)obj)->cnt * sizeof(VALUE);
break;
+ case imemo_cvar_entry:
+ break;
case imemo_fields:
if (FL_TEST_RAW(obj, OBJ_FIELD_HEAP)) {
if (rb_shape_obj_too_complex_p(obj)) {
@@ -495,6 +498,12 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating)
break;
}
+ case imemo_cvar_entry: {
+ struct rb_cvar_class_tbl_entry *ent = (struct rb_cvar_class_tbl_entry *)obj;
+ rb_gc_mark_and_move(&ent->class_value);
+ rb_gc_mark_and_move((VALUE *)&ent->cref);
+ break;
+ }
case imemo_fields: {
rb_gc_mark_and_move((VALUE *)&RBASIC(obj)->klass);
@@ -624,6 +633,10 @@ rb_imemo_free(VALUE obj)
RB_DEBUG_COUNTER_INC(obj_imemo_tmpbuf);
break;
+ case imemo_cvar_entry:
+ RB_DEBUG_COUNTER_INC(obj_imemo_cvar_entry);
+
+ break;
case imemo_fields:
imemo_fields_free(IMEMO_OBJ_FIELDS(obj));
RB_DEBUG_COUNTER_INC(obj_imemo_fields);
diff --git a/internal/class.h b/internal/class.h
index 164081b569..b2c6b45416 100644
--- a/internal/class.h
+++ b/internal/class.h
@@ -35,9 +35,10 @@ struct rb_subclass_entry {
typedef struct rb_subclass_entry rb_subclass_entry_t;
struct rb_cvar_class_tbl_entry {
+ VALUE imemo_flags;
uint32_t index;
rb_serial_t global_cvar_state;
- const rb_cref_t * cref;
+ const rb_cref_t *cref;
VALUE class_value;
};
@@ -49,7 +50,7 @@ struct rb_classext_struct {
struct rb_id_table *const_tbl;
struct rb_id_table *callable_m_tbl;
VALUE cc_tbl; /* { ID => { cme, [cc1, cc2, ...] }, ... } */
- struct rb_id_table *cvc_tbl;
+ VALUE cvc_tbl;
VALUE *superclasses;
/**
* The head of the subclasses linked list. This is a dummy entry (klass == 0)
@@ -226,8 +227,8 @@ static inline void RCLASS_SET_CONST_TBL(VALUE klass, struct rb_id_table *table,
static inline void RCLASS_WRITE_CONST_TBL(VALUE klass, struct rb_id_table *table, bool shared);
static inline void RCLASS_WRITE_CALLABLE_M_TBL(VALUE klass, struct rb_id_table *table);
static inline void RCLASS_WRITE_CC_TBL(VALUE klass, VALUE table);
-static inline void RCLASS_SET_CVC_TBL(VALUE klass, struct rb_id_table *table);
-static inline void RCLASS_WRITE_CVC_TBL(VALUE klass, struct rb_id_table *table);
+static inline void RCLASS_SET_CVC_TBL(VALUE klass, VALUE table);
+static inline void RCLASS_WRITE_CVC_TBL(VALUE klass, VALUE table);
static inline void RCLASS_WRITE_SUPERCLASSES(VALUE klass, size_t depth, VALUE *superclasses, bool with_self);
static inline void RCLASS_SET_SUBCLASSES(VALUE klass, rb_subclass_entry_t *head);
@@ -598,15 +599,15 @@ RCLASS_WRITE_CC_TBL(VALUE klass, VALUE table)
}
static inline void
-RCLASS_SET_CVC_TBL(VALUE klass, struct rb_id_table *table)
+RCLASS_SET_CVC_TBL(VALUE klass, VALUE table)
{
- RCLASSEXT_CVC_TBL(RCLASS_EXT_PRIME(klass)) = table;
+ RB_OBJ_ATOMIC_WRITE(klass, &RCLASSEXT_CVC_TBL(RCLASS_EXT_PRIME(klass)), table);
}
static inline void
-RCLASS_WRITE_CVC_TBL(VALUE klass, struct rb_id_table *table)
+RCLASS_WRITE_CVC_TBL(VALUE klass, VALUE table)
{
- RCLASSEXT_CVC_TBL(RCLASS_EXT_WRITABLE(klass)) = table;
+ RB_OBJ_ATOMIC_WRITE(klass, &RCLASSEXT_CVC_TBL(RCLASS_EXT_WRITABLE(klass)), table);
}
static inline void
diff --git a/internal/imemo.h b/internal/imemo.h
index f8bda26f0b..aeaf636a50 100644
--- a/internal/imemo.h
+++ b/internal/imemo.h
@@ -37,6 +37,7 @@ enum imemo_type {
imemo_ment = 6,
imemo_iseq = 7,
imemo_tmpbuf = 8,
+ imemo_cvar_entry = 9,
imemo_callinfo = 10,
imemo_callcache = 11,
imemo_constcache = 12,
diff --git a/iseq.c b/iseq.c
index 27aec9f0f7..dc55cbf4d2 100644
--- a/iseq.c
+++ b/iseq.c
@@ -298,9 +298,7 @@ rb_iseq_mark_and_move_each_body_value(const rb_iseq_t *iseq, VALUE *original_ise
for (unsigned int i = 0; i < body->icvarc_size; i++, is_entries++) {
ICVARC icvarc = (ICVARC)is_entries;
if (icvarc->entry) {
- RUBY_ASSERT(!RB_TYPE_P(icvarc->entry->class_value, T_NONE));
-
- rb_gc_mark_and_move(&icvarc->entry->class_value);
+ rb_gc_mark_and_move((VALUE *)&icvarc->entry);
}
}
diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb
index 313a013c07..d087f25b4d 100644
--- a/test/ruby/test_box.rb
+++ b/test/ruby/test_box.rb
@@ -154,6 +154,37 @@ class TestBox < Test::Unit::TestCase
assert Ruby::Box.current.inspect.include?("main")
end
+ def test_class_variables
+ # [Bug #21952]
+ assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
+ begin;
+ Ruby::Box.root.eval(<<~RUBY)
+ module M
+ @@x = 1
+ end
+
+ class A
+ include M
+ end
+
+ class B < A
+ end
+ RUBY
+
+ code = <<~REPRO
+ class ::B
+ @@x += 1
+ end
+ REPRO
+
+ b1 = Ruby::Box.new
+ assert_equal 2, b1.eval(code)
+
+ b2 = Ruby::Box.new
+ assert_equal 2, b2.eval(code)
+ end;
+ end
+
def test_autoload_in_box
setup_box
diff --git a/variable.c b/variable.c
index 04e2eaea52..aabe6b6c03 100644
--- a/variable.c
+++ b/variable.c
@@ -4278,22 +4278,29 @@ rb_cvar_set(VALUE klass, ID id, VALUE val)
bool new_cvar = rb_class_ivar_set(target, id, val);
- struct rb_id_table *rb_cvc_tbl = RCLASS_WRITABLE_CVC_TBL(target);
-
- if (!rb_cvc_tbl) {
- rb_cvc_tbl = rb_id_table_create(2);
- RCLASS_WRITE_CVC_TBL(target, rb_cvc_tbl);
- }
+ VALUE cvc_tbl = RCLASS_WRITABLE_CVC_TBL(target);
struct rb_cvar_class_tbl_entry *ent;
VALUE ent_data;
- if (!rb_id_table_lookup(rb_cvc_tbl, id, &ent_data)) {
- ent = ALLOC(struct rb_cvar_class_tbl_entry);
- ent->class_value = target;
+ if (!cvc_tbl || !rb_marked_id_table_lookup(cvc_tbl, id, &ent_data)) {
+ ent = (struct rb_cvar_class_tbl_entry *)SHAREABLE_IMEMO_NEW(struct rb_cvar_class_tbl_entry, imemo_cvar_entry, 0);
+ RB_OBJ_WRITE((VALUE)ent, &ent->class_value, target);
+ RB_OBJ_WRITE((VALUE)ent, &ent->cref, 0);
ent->global_cvar_state = GET_GLOBAL_CVAR_STATE();
- ent->cref = 0;
- rb_id_table_insert(rb_cvc_tbl, id, (VALUE)ent);
+
+ VALUE new_cvc_tbl = cvc_tbl;
+ if (!new_cvc_tbl) {
+ new_cvc_tbl = rb_marked_id_table_new(2);
+ }
+ else if (rb_multi_ractor_p()) {
+ new_cvc_tbl = rb_marked_id_table_new(rb_marked_id_table_size(cvc_tbl) + 1);
+ }
+
+ rb_marked_id_table_insert(new_cvc_tbl, id, (VALUE)ent);
+ if (new_cvc_tbl != cvc_tbl) {
+ RCLASS_WRITE_CVC_TBL(target, new_cvc_tbl);
+ }
RB_DEBUG_COUNTER_INC(cvar_inline_miss);
}
else {
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 2ad67461bb..b88873c0da 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -1604,24 +1604,23 @@ update_classvariable_cache(const rb_iseq_t *iseq, VALUE klass, ID id, const rb_c
defined_class = RBASIC(defined_class)->klass;
}
- struct rb_id_table *rb_cvc_tbl = RCLASS_CVC_TBL(defined_class);
+ VALUE rb_cvc_tbl = RCLASS_CVC_TBL(defined_class);
if (!rb_cvc_tbl) {
rb_bug("the cvc table should be set");
}
VALUE ent_data;
- if (!rb_id_table_lookup(rb_cvc_tbl, id, &ent_data)) {
+ if (!rb_marked_id_table_lookup(rb_cvc_tbl, id, &ent_data)) {
rb_bug("should have cvar cache entry");
}
struct rb_cvar_class_tbl_entry *ent = (void *)ent_data;
ent->global_cvar_state = GET_GLOBAL_CVAR_STATE();
- ent->cref = cref;
+ RB_OBJ_WRITE((VALUE)ent, &ent->cref, cref);
ic->entry = ent;
RUBY_ASSERT(BUILTIN_TYPE((VALUE)cref) == T_IMEMO && IMEMO_TYPE_P(cref, imemo_cref));
- RB_OBJ_WRITTEN(iseq, Qundef, ent->cref);
RB_OBJ_WRITTEN(iseq, Qundef, ent->class_value);
RB_OBJ_WRITTEN(ent->class_value, Qundef, ent->cref);
diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs
index 952cf88c20..586f9c1ebd 100644
--- a/yjit/src/cruby_bindings.inc.rs
+++ b/yjit/src/cruby_bindings.inc.rs
@@ -358,6 +358,7 @@ pub const imemo_memo: imemo_type = 5;
pub const imemo_ment: imemo_type = 6;
pub const imemo_iseq: imemo_type = 7;
pub const imemo_tmpbuf: imemo_type = 8;
+pub const imemo_cvar_entry: imemo_type = 9;
pub const imemo_callinfo: imemo_type = 10;
pub const imemo_callcache: imemo_type = 11;
pub const imemo_constcache: imemo_type = 12;
@@ -640,6 +641,7 @@ pub const SHAPE_ID_HAS_IVAR_MASK: shape_id_mask = 134742014;
pub type shape_id_mask = u32;
#[repr(C)]
pub struct rb_cvar_class_tbl_entry {
+ pub imemo_flags: VALUE,
pub index: u32,
pub global_cvar_state: rb_serial_t,
pub cref: *const rb_cref_t,
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs
index 2689ec30cf..e1f446b0af 100644
--- a/zjit/src/cruby_bindings.inc.rs
+++ b/zjit/src/cruby_bindings.inc.rs
@@ -425,6 +425,7 @@ pub const imemo_memo: imemo_type = 5;
pub const imemo_ment: imemo_type = 6;
pub const imemo_iseq: imemo_type = 7;
pub const imemo_tmpbuf: imemo_type = 8;
+pub const imemo_cvar_entry: imemo_type = 9;
pub const imemo_callinfo: imemo_type = 10;
pub const imemo_callcache: imemo_type = 11;
pub const imemo_constcache: imemo_type = 12;
@@ -1429,6 +1430,7 @@ pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 264241152;
pub type shape_id_fl_type = u32;
#[repr(C)]
pub struct rb_cvar_class_tbl_entry {
+ pub imemo_flags: VALUE,
pub index: u32,
pub global_cvar_state: rb_serial_t,
pub cref: *const rb_cref_t,