summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJean Boussier <jean.boussier@gmail.com>2026-03-29 08:47:36 +0200
committerJean Boussier <jean.boussier@gmail.com>2026-04-11 07:57:07 +0200
commitc0d86a0103de7130943d54b4a290b76ec7e0c135 (patch)
treefe7e9c3bfa860e0fac692bf06233c18ceb20fbd6
parent044a43f42b6170488d0f3ab509a2a4d564eb8a0c (diff)
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.
-rw-r--r--class.c25
-rw-r--r--test/ruby/test_box.rb31
2 files changed, 45 insertions, 11 deletions
diff --git a/class.c b/class.c
index cd07846173..e52a541c6e 100644
--- a/class.c
+++ b/class.c
@@ -227,14 +227,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;
@@ -262,8 +254,19 @@ duplicate_classext_m_tbl(struct rb_id_table *orig, VALUE klass, bool init_missin
return tbl;
}
+static enum rb_id_table_iterator_result
+duplicate_classext_cvc_tbl_i(ID key, VALUE value, void *data)
+{
+ struct rb_id_table *tbl = (struct rb_id_table *)data;
+ struct rb_cvar_class_tbl_entry *cvc_entry = (struct rb_cvar_class_tbl_entry *)value;
+ struct rb_cvar_class_tbl_entry *copy = ALLOC(struct rb_cvar_class_tbl_entry);
+ MEMCPY(copy, cvc_entry, struct rb_cvar_class_tbl_entry, 1);
+ rb_id_table_insert(tbl, key, (VALUE)copy);
+ return ID_TABLE_CONTINUE;
+}
+
static struct rb_id_table *
-duplicate_classext_id_table(struct rb_id_table *orig, bool init_missing)
+duplicate_classext_cvc_tbl(struct rb_id_table *orig, bool init_missing)
{
struct rb_id_table *tbl;
@@ -274,7 +277,7 @@ duplicate_classext_id_table(struct rb_id_table *orig, bool init_missing)
return NULL;
}
tbl = rb_id_table_create(rb_id_table_size(orig));
- rb_id_table_foreach(orig, duplicate_classext_id_table_i, tbl);
+ rb_id_table_foreach(orig, duplicate_classext_cvc_tbl_i, tbl);
return tbl;
}
@@ -411,7 +414,7 @@ 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);
+ RCLASSEXT_CVC_TBL(ext) = duplicate_classext_cvc_tbl(RCLASSEXT_CVC_TBL(orig), dup_iclass);
// Subclasses/back-pointers are only in the prime classext.
diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb
index 5da29b4971..c6a2c5423e 100644
--- a/test/ruby/test_box.rb
+++ b/test/ruby/test_box.rb
@@ -155,6 +155,37 @@ class TestBox < Test::Unit::TestCase
assert_include Ruby::Box.current.inspect, "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