Fix id2ref table build when GC in progress

Previously, if GC was in progress when we're initially building the
id2ref table, it could see the empty table and then crash when trying to
remove ids from it. This commit fixes the bug by only publishing the
table after GC is done.

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
This commit is contained in:
John Hawthorn 2025-08-08 16:31:29 -07:00
parent 07878ebe78
commit d80c03d22a
2 changed files with 20 additions and 2 deletions

7
gc.c
View file

@ -1975,14 +1975,17 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id)
// GC Must not trigger while we build the table, otherwise if we end
// up freeing an object that had an ID, we might try to delete it from
// the table even though it wasn't inserted yet.
id2ref_tbl = st_init_table(&object_id_hash_type);
id2ref_value = TypedData_Wrap_Struct(0, &id2ref_tbl_type, id2ref_tbl);
st_table *tmp_id2ref_tbl = st_init_table(&object_id_hash_type);
VALUE tmp_id2ref_value = TypedData_Wrap_Struct(0, &id2ref_tbl_type, tmp_id2ref_tbl);
// build_id2ref_i will most certainly malloc, which could trigger GC and sweep
// objects we just added to the table.
// By calling rb_gc_disable() we also save having to handle potentially garbage objects.
bool gc_disabled = RTEST(rb_gc_disable());
{
id2ref_tbl = tmp_id2ref_tbl;
id2ref_value = tmp_id2ref_value;
rb_gc_impl_each_object(objspace, build_id2ref_i, (void *)id2ref_tbl);
}
if (!gc_disabled) rb_gc_enable();

View file

@ -284,6 +284,21 @@ End
end;
end
def test_id2ref_table_build
assert_separately([], <<-End)
10.times do
Object.new.object_id
end
GC.start(immediate_mark: false)
obj = Object.new
EnvUtil.suppress_warning do
assert_equal obj, ObjectSpace._id2ref(obj.object_id)
end
End
end
def test_each_object_singleton_class
assert_separately([], <<-End)
class C