Skip to content

Commit 5342d91

Browse files
jhawthorntenderlovebyroot
committed
Fix generic_ivar_set_shape_field for table rebuild
[Bug #21438] Previously GC could trigger a table rebuild of the generic fields st_table in the middle of calling the st_update callback. This could cause entries to be reallocated or rearranged and the update to be for the wrong entry. This commit adds an assertion to make that case easier to detect, and replaces the st_update with a separate st_lookup and st_insert. Co-authored-by: Aaron Patterson <[email protected]> Co-authored-by: Jean Boussier <[email protected]>
1 parent 74cdf87 commit 5342d91

File tree

3 files changed

+42
-29
lines changed

3 files changed

+42
-29
lines changed

st.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,7 +1495,16 @@ st_update(st_table *tab, st_data_t key,
14951495
value = entry->record;
14961496
}
14971497
old_key = key;
1498+
1499+
unsigned int rebuilds_num = tab->rebuilds_num;
1500+
14981501
retval = (*func)(&key, &value, arg, existing);
1502+
1503+
// We need to make sure that the callback didn't cause a table rebuild
1504+
// Ideally we would make sure no operations happened
1505+
assert(rebuilds_num == tab->rebuilds_num);
1506+
(void)rebuilds_num;
1507+
14991508
switch (retval) {
15001509
case ST_CONTINUE:
15011510
if (! existing) {

test/ruby/test_variable.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,19 @@ def test_external_ivars
407407
}
408408
end
409409

410+
def test_exivar_resize_with_compaction_stress
411+
objs = 10_000.times.map do
412+
ExIvar.new
413+
end
414+
EnvUtil.under_gc_compact_stress do
415+
10.times do
416+
x = ExIvar.new
417+
x.instance_variable_set(:@resize, 1)
418+
x
419+
end
420+
end
421+
end
422+
410423
def test_local_variables_with_kwarg
411424
bug11674 = '[ruby-core:71437] [Bug #11674]'
412425
v = with_kwargs_11(v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8,v9:9,v10:10,v11:11)

variable.c

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,43 +1823,34 @@ struct gen_fields_lookup_ensure_size {
18231823
bool resize;
18241824
};
18251825

1826-
static int
1827-
generic_fields_lookup_ensure_size(st_data_t *k, st_data_t *v, st_data_t u, int existing)
1828-
{
1829-
ASSERT_vm_locking();
1830-
1831-
struct gen_fields_lookup_ensure_size *fields_lookup = (struct gen_fields_lookup_ensure_size *)u;
1832-
struct gen_fields_tbl *fields_tbl = existing ? (struct gen_fields_tbl *)*v : NULL;
1833-
1834-
if (!existing || fields_lookup->resize) {
1835-
if (existing) {
1836-
RUBY_ASSERT(RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_OBJ_ID));
1837-
RUBY_ASSERT(RSHAPE_CAPACITY(RSHAPE_PARENT(fields_lookup->shape_id)) < RSHAPE_CAPACITY(fields_lookup->shape_id));
1838-
}
1839-
1840-
fields_tbl = gen_fields_tbl_resize(fields_tbl, RSHAPE_CAPACITY(fields_lookup->shape_id));
1841-
*v = (st_data_t)fields_tbl;
1842-
}
1843-
1844-
fields_lookup->fields_tbl = fields_tbl;
1845-
if (fields_lookup->shape_id) {
1846-
rb_obj_set_shape_id(fields_lookup->obj, fields_lookup->shape_id);
1847-
}
1848-
1849-
RUBY_ASSERT(rb_obj_exivar_p((VALUE)*k));
1850-
1851-
return ST_CONTINUE;
1852-
}
1853-
18541826
static VALUE *
18551827
generic_ivar_set_shape_fields(VALUE obj, void *data)
18561828
{
18571829
RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj));
18581830

18591831
struct gen_fields_lookup_ensure_size *fields_lookup = data;
18601832

1833+
// We can't use st_update, since when resizing the fields table GC can
1834+
// happen, which will modify the st_table and may rebuild it
18611835
RB_VM_LOCKING() {
1862-
st_update(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, generic_fields_lookup_ensure_size, (st_data_t)fields_lookup);
1836+
struct gen_fields_tbl *fields_tbl = NULL;
1837+
st_table *tbl = generic_fields_tbl(obj, fields_lookup->id, false);
1838+
int existing = st_lookup(tbl, (st_data_t)obj, (st_data_t *)&fields_tbl);
1839+
1840+
if (!existing || fields_lookup->resize) {
1841+
if (existing) {
1842+
RUBY_ASSERT(RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_OBJ_ID));
1843+
RUBY_ASSERT(RSHAPE_CAPACITY(RSHAPE_PARENT(fields_lookup->shape_id)) < RSHAPE_CAPACITY(fields_lookup->shape_id));
1844+
}
1845+
1846+
fields_tbl = gen_fields_tbl_resize(fields_tbl, RSHAPE_CAPACITY(fields_lookup->shape_id));
1847+
st_insert(tbl, (st_data_t)obj, (st_data_t)fields_tbl);
1848+
}
1849+
1850+
fields_lookup->fields_tbl = fields_tbl;
1851+
if (fields_lookup->shape_id) {
1852+
rb_obj_set_shape_id(fields_lookup->obj, fields_lookup->shape_id);
1853+
}
18631854
}
18641855

18651856
return fields_lookup->fields_tbl->as.shape.fields;

0 commit comments

Comments
 (0)