Skip to content

Commit c0d86a0

Browse files
committed
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.
1 parent 044a43f commit c0d86a0

2 files changed

Lines changed: 45 additions & 11 deletions

File tree

class.c

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,6 @@ struct duplicate_id_tbl_data {
226226
VALUE klass;
227227
};
228228

229-
static enum rb_id_table_iterator_result
230-
duplicate_classext_id_table_i(ID key, VALUE value, void *data)
231-
{
232-
struct rb_id_table *tbl = (struct rb_id_table *)data;
233-
rb_id_table_insert(tbl, key, value);
234-
return ID_TABLE_CONTINUE;
235-
}
236-
237229
static enum rb_id_table_iterator_result
238230
duplicate_classext_m_tbl_i(ID key, VALUE value, void *data)
239231
{
@@ -262,8 +254,19 @@ duplicate_classext_m_tbl(struct rb_id_table *orig, VALUE klass, bool init_missin
262254
return tbl;
263255
}
264256

257+
static enum rb_id_table_iterator_result
258+
duplicate_classext_cvc_tbl_i(ID key, VALUE value, void *data)
259+
{
260+
struct rb_id_table *tbl = (struct rb_id_table *)data;
261+
struct rb_cvar_class_tbl_entry *cvc_entry = (struct rb_cvar_class_tbl_entry *)value;
262+
struct rb_cvar_class_tbl_entry *copy = ALLOC(struct rb_cvar_class_tbl_entry);
263+
MEMCPY(copy, cvc_entry, struct rb_cvar_class_tbl_entry, 1);
264+
rb_id_table_insert(tbl, key, (VALUE)copy);
265+
return ID_TABLE_CONTINUE;
266+
}
267+
265268
static struct rb_id_table *
266-
duplicate_classext_id_table(struct rb_id_table *orig, bool init_missing)
269+
duplicate_classext_cvc_tbl(struct rb_id_table *orig, bool init_missing)
267270
{
268271
struct rb_id_table *tbl;
269272

@@ -274,7 +277,7 @@ duplicate_classext_id_table(struct rb_id_table *orig, bool init_missing)
274277
return NULL;
275278
}
276279
tbl = rb_id_table_create(rb_id_table_size(orig));
277-
rb_id_table_foreach(orig, duplicate_classext_id_table_i, tbl);
280+
rb_id_table_foreach(orig, duplicate_classext_cvc_tbl_i, tbl);
278281
return tbl;
279282
}
280283

@@ -411,7 +414,7 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_box_t *bo
411414
* RCLASSEXT_CC_TBL(copy) = NULL
412415
*/
413416

414-
RCLASSEXT_CVC_TBL(ext) = duplicate_classext_id_table(RCLASSEXT_CVC_TBL(orig), dup_iclass);
417+
RCLASSEXT_CVC_TBL(ext) = duplicate_classext_cvc_tbl(RCLASSEXT_CVC_TBL(orig), dup_iclass);
415418

416419
// Subclasses/back-pointers are only in the prime classext.
417420

test/ruby/test_box.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,37 @@ def test_raising_errors_in_require
155155
assert_include Ruby::Box.current.inspect, "main"
156156
end
157157

158+
def test_class_variables
159+
# [Bug #21952]
160+
assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true)
161+
begin;
162+
Ruby::Box.root.eval(<<~RUBY)
163+
module M
164+
@@x = 1
165+
end
166+
167+
class A
168+
include M
169+
end
170+
171+
class B < A
172+
end
173+
RUBY
174+
175+
code = <<~REPRO
176+
class ::B
177+
@@x += 1
178+
end
179+
REPRO
180+
181+
b1 = Ruby::Box.new
182+
assert_equal 2, b1.eval(code)
183+
184+
b2 = Ruby::Box.new
185+
assert_equal 2, b2.eval(code)
186+
end;
187+
end
188+
158189
def test_autoload_in_box
159190
setup_box
160191

0 commit comments

Comments
 (0)