Skip to content

Commit ad633be

Browse files
jhawthorntenderlovebyroot
committed
Fix generic_ivar_set_shape_ivptr for table rebuild
[Bug #21438] Previously GC could trigger a table rebuild of the generic ivar 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. Also free after insert in generic_ivar_set_shape_ivptr Previously we were performing a realloc and then inserting the new value into the table. If the table was flagged as requiring a rebuild, this could trigger GC work and marking within that GC could access the ivptr freed by realloc. Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org> Co-authored-by: Jean Boussier <byroot@ruby-lang.org>
1 parent 8908cb0 commit ad633be

File tree

3 files changed

+65
-55
lines changed

3 files changed

+65
-55
lines changed

st.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,7 +1482,16 @@ st_update(st_table *tab, st_data_t key,
14821482
value = entry->record;
14831483
}
14841484
old_key = key;
1485+
1486+
unsigned int rebuilds_num = tab->rebuilds_num;
1487+
14851488
retval = (*func)(&key, &value, arg, existing);
1489+
1490+
// We need to make sure that the callback didn't cause a table rebuild
1491+
// Ideally we would make sure no operations happened
1492+
assert(rebuilds_num == tab->rebuilds_num);
1493+
(void)rebuilds_num;
1494+
14861495
switch (retval) {
14871496
case ST_CONTINUE:
14881497
if (! existing) {

test/ruby/test_variable.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,20 +393,38 @@ def initialize
393393
@a = 1
394394
@b = 2
395395
@c = 3
396+
@d = 4
397+
@e = 5
398+
@f = 6
399+
@g = 7
400+
@h = 8
396401
end
397402

398403
def ivars
399-
[@a, @b, @c]
404+
[@a, @b, @c, @d, @e, @f, @g, @h]
400405
end
401406
end
402407

403408
def test_external_ivars
404409
3.times{
405410
# check inline cache for external ivar access
406-
assert_equal [1, 2, 3], ExIvar.new.ivars
411+
assert_equal [1, 2, 3, 4, 5, 6, 7, 8], ExIvar.new.ivars
407412
}
408413
end
409414

415+
def test_exivar_resize_with_compaction_stress
416+
objs = 10_000.times.map do
417+
ExIvar.new
418+
end
419+
EnvUtil.under_gc_compact_stress do
420+
10.times do
421+
x = ExIvar.new
422+
x.instance_variable_set(:@resize, 1)
423+
x
424+
end
425+
end
426+
end
427+
410428
def test_local_variables_with_kwarg
411429
bug11674 = '[ruby-core:71437] [Bug #11674]'
412430
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: 36 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,22 +1162,6 @@ gen_ivtbl_bytes(size_t n)
11621162
return offsetof(struct gen_ivtbl, as.shape.ivptr) + n * sizeof(VALUE);
11631163
}
11641164

1165-
static struct gen_ivtbl *
1166-
gen_ivtbl_resize(struct gen_ivtbl *old, uint32_t n)
1167-
{
1168-
RUBY_ASSERT(n > 0);
1169-
1170-
uint32_t len = old ? old->as.shape.numiv : 0;
1171-
struct gen_ivtbl *ivtbl = xrealloc(old, gen_ivtbl_bytes(n));
1172-
1173-
ivtbl->as.shape.numiv = n;
1174-
for (; len < n; len++) {
1175-
ivtbl->as.shape.ivptr[len] = Qundef;
1176-
}
1177-
1178-
return ivtbl;
1179-
}
1180-
11811165
void
11821166
rb_mark_generic_ivar(VALUE obj)
11831167
{
@@ -1632,51 +1616,50 @@ struct gen_ivar_lookup_ensure_size {
16321616
bool resize;
16331617
};
16341618

1635-
static int
1636-
generic_ivar_lookup_ensure_size(st_data_t *k, st_data_t *v, st_data_t u, int existing)
1637-
{
1638-
ASSERT_vm_locking();
1639-
1640-
struct gen_ivar_lookup_ensure_size *ivar_lookup = (struct gen_ivar_lookup_ensure_size *)u;
1641-
struct gen_ivtbl *ivtbl = existing ? (struct gen_ivtbl *)*v : NULL;
1642-
1643-
if (!existing || ivar_lookup->resize) {
1644-
if (existing) {
1645-
RUBY_ASSERT(ivar_lookup->shape->type == SHAPE_IVAR);
1646-
RUBY_ASSERT(rb_shape_get_shape_by_id(ivar_lookup->shape->parent_id)->capacity < ivar_lookup->shape->capacity);
1647-
}
1648-
else {
1649-
FL_SET_RAW((VALUE)*k, FL_EXIVAR);
1650-
}
1651-
1652-
ivtbl = gen_ivtbl_resize(ivtbl, ivar_lookup->shape->capacity);
1653-
*v = (st_data_t)ivtbl;
1654-
}
1655-
1656-
RUBY_ASSERT(FL_TEST((VALUE)*k, FL_EXIVAR));
1657-
1658-
ivar_lookup->ivtbl = ivtbl;
1659-
if (ivar_lookup->shape) {
1660-
#if SHAPE_IN_BASIC_FLAGS
1661-
rb_shape_set_shape(ivar_lookup->obj, ivar_lookup->shape);
1662-
#else
1663-
ivtbl->shape_id = rb_shape_id(ivar_lookup->shape);
1664-
#endif
1665-
}
1666-
1667-
return ST_CONTINUE;
1668-
}
1669-
16701619
static VALUE *
16711620
generic_ivar_set_shape_ivptr(VALUE obj, void *data)
16721621
{
16731622
RUBY_ASSERT(!rb_shape_obj_too_complex(obj));
16741623

16751624
struct gen_ivar_lookup_ensure_size *ivar_lookup = data;
16761625

1626+
// We can't use st_update, since when resizing the fields table GC can
1627+
// happen, which will modify the st_table and may rebuild it
16771628
RB_VM_LOCK_ENTER();
16781629
{
1679-
st_update(generic_ivtbl(obj, ivar_lookup->id, false), (st_data_t)obj, generic_ivar_lookup_ensure_size, (st_data_t)ivar_lookup);
1630+
struct gen_ivtbl *ivtbl = NULL;
1631+
st_table *tbl = generic_ivtbl(obj, ivar_lookup->id, false);
1632+
int existing = st_lookup(tbl, (st_data_t)obj, (st_data_t *)&ivtbl);
1633+
1634+
if (!existing || ivar_lookup->resize) {
1635+
uint32_t new_capa = ivar_lookup->shape->capacity;
1636+
uint32_t old_capa = rb_shape_get_shape_by_id(ivar_lookup->shape->parent_id)->capacity;
1637+
1638+
if (existing) {
1639+
RUBY_ASSERT(ivar_lookup->shape->type == SHAPE_IVAR);
1640+
RUBY_ASSERT(old_capa < new_capa);
1641+
RUBY_ASSERT(ivtbl);
1642+
} else {
1643+
RUBY_ASSERT(!ivtbl);
1644+
RUBY_ASSERT(old_capa == 0);
1645+
}
1646+
RUBY_ASSERT(new_capa > 0);
1647+
1648+
struct gen_ivtbl *old_ivtbl = ivtbl;
1649+
ivtbl = xmalloc(gen_ivtbl_bytes(new_capa));
1650+
if (old_ivtbl) {
1651+
memcpy(ivtbl, old_ivtbl, gen_ivtbl_bytes(old_capa));
1652+
}
1653+
st_insert(tbl, (st_data_t)obj, (st_data_t)ivtbl);
1654+
if (old_ivtbl) {
1655+
xfree(old_ivtbl);
1656+
}
1657+
}
1658+
1659+
ivar_lookup->ivtbl = ivtbl;
1660+
if (ivar_lookup->shape) {
1661+
rb_shape_set_shape(ivar_lookup->obj, ivar_lookup->shape);
1662+
}
16801663
}
16811664
RB_VM_LOCK_LEAVE();
16821665

@@ -2134,7 +2117,7 @@ rb_copy_generic_ivar(VALUE clone, VALUE obj)
21342117
new_ivtbl->as.complex.table = st_copy(obj_ivtbl->as.complex.table);
21352118
}
21362119
else {
2137-
new_ivtbl = gen_ivtbl_resize(0, obj_ivtbl->as.shape.numiv);
2120+
new_ivtbl = xmalloc(gen_ivtbl_bytes(obj_ivtbl->as.shape.numiv));
21382121

21392122
for (uint32_t i=0; i<obj_ivtbl->as.shape.numiv; i++) {
21402123
RB_OBJ_WRITE(clone, &new_ivtbl->as.shape.ivptr[i], obj_ivtbl->as.shape.ivptr[i]);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy