From a38eb05773bf19236b8c09613bb63df8e12e732d Mon Sep 17 00:00:00 2001 From: Max Horn Date: Wed, 26 Aug 2026 21:54:08 +0200 Subject: [PATCH] julia_gc: accept image types in mark validation The VALIDATE_MARKING check in JMark (and the corresponding check in MarkJuliaObjSafe) requires the type of a marked object to be a live pool allocation, tested via jl_gc_internal_obj_base_ptr. Since GAP.jl became precompilable, its foreign types (GapObj, SmallBag, LargeBag) are restored from a package image via jl_reinit_foreign_type; they live in image memory, jl_gc_internal_obj_base_ptr returns NULL for them, and the first collection aborts. The same holds for the types of arbitrary Julia objects wrapped by JuliaInterface, which mostly live in the system image. Accept type objects with the in_image header bit as valid. This was investigated and fixed with the assistance of Claude Code (diagnosis, patch and testing by the AI, reviewed by a human). Co-authored-by: Claude Fable 5 --- src/julia_gc.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/julia_gc.c b/src/julia_gc.c index f04d51e918..fb0afa4b8a 100644 --- a/src/julia_gc.c +++ b/src/julia_gc.c @@ -279,16 +279,28 @@ static inline int JMarkTyped(jl_ptls_t ptls, void * obj, jl_datatype_t * ty) return jl_gc_mark_queue_obj(ptls, (jl_value_t *)obj); } +// Check that `obj` is still allocated and not on a free list already, +// by verifying that its type is a valid datatype object. A datatype is +// either a pool object, or lives in a system or package image (e.g. the +// types of GAP.jl bags after loading it from a precompiled image), as +// indicated by the `in_image` bit in its header. +static inline int ValidTypeOfMarkedObj(void * obj) +{ + jl_value_t * ty = jl_typeof(obj); + // for a freed pool object, `ty` is the freelist link: NULL or a + // pointer into a pool page; check before dereferencing it + if (ty == NULL) + return 0; + if (jl_gc_internal_obj_base_ptr(ty) != ty && + !jl_astaggedvalue(ty)->bits.in_image) + return 0; + return jl_typeis(ty, jl_datatype_type); +} + static inline int JMark(jl_ptls_t ptls, void * obj) { #ifdef VALIDATE_MARKING - // Validate that `obj` is still allocated and not on a - // free list already. We verify this by checking that the - // type is a pool object of type `jl_datatype_type`. - jl_value_t * ty = jl_typeof(obj); - if (jl_gc_internal_obj_base_ptr(ty) != ty) - abort(); - if (!jl_typeis(ty, jl_datatype_type)) + if (!ValidTypeOfMarkedObj(obj)) abort(); #endif return jl_gc_mark_queue_obj(ptls, (jl_value_t *)obj); @@ -299,13 +311,7 @@ void MarkJuliaObjSafe(void * obj, void * ref) { if (!obj) return; - // Validate that `obj` is still allocated and not on a - // free list already. We verify this by checking that the - // type is a pool object of type `jl_datatype_type`. - jl_value_t * ty = jl_typeof(obj); - if (jl_gc_internal_obj_base_ptr(ty) != ty) - return; - if (!jl_typeis(ty, jl_datatype_type)) + if (!ValidTypeOfMarkedObj(obj)) return; if (jl_gc_mark_queue_obj(((MarkData *)ref)->ptls, (jl_value_t *)obj)) ((MarkData *)ref)->youngRef++;