diff options
| author | Jean Boussier <jean.boussier@gmail.com> | 2026-03-29 08:47:36 +0200 |
|---|---|---|
| committer | Jean Boussier <jean.boussier@gmail.com> | 2026-04-11 07:57:07 +0200 |
| commit | c0d86a0103de7130943d54b4a290b76ec7e0c135 (patch) | |
| tree | fe7e9c3bfa860e0fac692bf06233c18ceb20fbd6 | |
| parent | 044a43f42b6170488d0f3ab509a2a4d564eb8a0c (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.c | 25 | ||||
| -rw-r--r-- | test/ruby/test_box.rb | 31 |
2 files changed, 45 insertions, 11 deletions
@@ -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 |
