From 99d5f19eef1f2a5604bc9f14f9d9c70d11f259e0 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 16 Jun 2026 14:59:42 +0200 Subject: [PATCH 1/8] [ruby/json] fast_float_parser.h: handle `__has_builtin` not being defined https://github.com/ruby/json/commit/97dda65096 --- ext/json/vendor/fast_float_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/vendor/fast_float_parser.h b/ext/json/vendor/fast_float_parser.h index 9e4a6691360d43..833c1462e9d1a7 100644 --- a/ext/json/vendor/fast_float_parser.h +++ b/ext/json/vendor/fast_float_parser.h @@ -730,7 +730,7 @@ ffc_nlz_int64(uint64_t x) unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; -#elif __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) +#elif defined(__has_builtin) && __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) if (x == 0) { return 64; } From 0384a278280135456e702b29b51b348d87337f5f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 16 Jun 2026 15:42:48 +0200 Subject: [PATCH 2/8] [ruby/json] fast_float_parser.h: fix GCC 8 support https://github.com/ruby/json/commit/cdad7dfb26 --- ext/json/vendor/fast_float_parser.h | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/ext/json/vendor/fast_float_parser.h b/ext/json/vendor/fast_float_parser.h index 833c1462e9d1a7..6e43b254c814ec 100644 --- a/ext/json/vendor/fast_float_parser.h +++ b/ext/json/vendor/fast_float_parser.h @@ -720,7 +720,10 @@ static inline double ffp_bits2double(uint64_t bits) { static inline unsigned int ffc_nlz_int64(uint64_t x) { -#if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) +#if defined(HAVE_BUILTIN___BUILTIN_CLZLL) && !(defined(__sun) && defined(__sparc)) + return (unsigned int)__builtin_clzll((unsigned long long)x); + +#elif defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) return (unsigned int)__lzcnt64(x); #elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U64) @@ -730,21 +733,6 @@ ffc_nlz_int64(uint64_t x) unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; -#elif defined(__has_builtin) && __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) - if (x == 0) { - return 64; - } - else if (sizeof(long) * CHAR_BIT == 64) { - return (unsigned int)__builtin_clzl((unsigned long)x); - } - else if (sizeof(long long) * CHAR_BIT == 64) { - return (unsigned int)__builtin_clzll((unsigned long long)x); - } - else { - /* :FIXME: Is there a way to make this branch a compile-time error? */ - __builtin_unreachable(); - } - #else uint64_t y; unsigned int n = 64; From 78495ca41572efad625e683280c654bb46da95af Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 16 Jun 2026 14:53:31 +0200 Subject: [PATCH 3/8] imemo_tmp_buffer: skip marking when useless In many place ALLOC_V / ALLOCV_N is used as a safer `alloca`, and to behave like stack memory, `ALLOCV` does scan its buffer for references using `rb_gc_mark_locations`. The problem is that it's quite slow, and in many cases, the temporary buffer is known not to contain any references. e.g. `ALLOCV_N(uint32_t, buf0, len)` can't possibly contain references, as the element size is too small. --- imemo.c | 12 ++++-------- include/ruby/internal/memory.h | 29 +++-------------------------- internal/imemo.h | 4 ++-- process.c | 2 +- 4 files changed, 10 insertions(+), 37 deletions(-) diff --git a/imemo.c b/imemo.c index 796e078c89565f..1c9020593b2380 100644 --- a/imemo.c +++ b/imemo.c @@ -57,6 +57,7 @@ rb_imemo_tmpbuf_new(void) rb_gc_register_pinning_obj((VALUE)obj); + obj->marked = false; obj->ptr = NULL; obj->size = 0; @@ -64,7 +65,7 @@ rb_imemo_tmpbuf_new(void) } void * -rb_alloc_tmp_buffer(volatile VALUE *store, long len) +rb_alloc_tmp_buffer(volatile VALUE *store, long len, bool marked) { if (len < 0) { rb_raise(rb_eArgError, "negative buffer size (or size too big)"); @@ -75,18 +76,13 @@ rb_alloc_tmp_buffer(volatile VALUE *store, long len) rb_imemo_tmpbuf_t *tmpbuf = (rb_imemo_tmpbuf_t *)rb_imemo_tmpbuf_new(); *store = (VALUE)tmpbuf; void *ptr = ruby_xmalloc(len); + tmpbuf->marked = marked; tmpbuf->ptr = ptr; tmpbuf->size = len; return ptr; } -void * -rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt) -{ - return rb_alloc_tmp_buffer(store, (long)size); -} - void rb_free_tmp_buffer(volatile VALUE *store) { @@ -556,7 +552,7 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) case imemo_tmpbuf: { const rb_imemo_tmpbuf_t *m = (const rb_imemo_tmpbuf_t *)obj; - if (!reference_updating) { + if (m->marked && !reference_updating) { rb_gc_mark_locations(m->ptr, m->ptr + (m->size / sizeof(VALUE))); } diff --git a/include/ruby/internal/memory.h b/include/ruby/internal/memory.h index eb9c1d252580f7..2ebb92f051736c 100644 --- a/include/ruby/internal/memory.h +++ b/include/ruby/internal/memory.h @@ -304,7 +304,7 @@ typedef uint128_t DSIZE_T; #define RB_ALLOCV(v, n) \ ((n) < RUBY_ALLOCV_LIMIT ? \ ((v) = 0, alloca(n)) : \ - rb_alloc_tmp_buffer(&(v), (n))) + rb_alloc_tmp_buffer(&(v), (n), true)) /** * Allocates a memory region, possibly on stack. If the given size exceeds @@ -438,30 +438,7 @@ RBIMPL_ATTR_NONNULL(()) * @return Allocated `len` bytes array. * @post `store` holds the corresponding tmp buffer object. */ -void *rb_alloc_tmp_buffer(volatile VALUE *store, long len); - -RBIMPL_ATTR_RESTRICT() -RBIMPL_ATTR_RETURNS_NONNULL() -RBIMPL_ATTR_ALLOC_SIZE((2,3)) -RBIMPL_ATTR_NONNULL(()) -/** - * @private - * - * This is an implementation detail of #RB_ALLOCV_N(). People don't use this - * directly. - * - * @param[out] store Pointer to a variable. - * @param[in] len Requested number of bytes to allocate. - * @param[in] count Number of elements in an array. - * @return Allocated `len` bytes array. - * @post `store` holds the corresponding tmp buffer object. - * - * @internal - * - * Although the meaning of `count` variable is clear, @shyouhei doesn't - * understand its needs. - */ -void *rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t len,size_t count); +void *rb_alloc_tmp_buffer(volatile VALUE *store, long len, bool mark); /** * @private @@ -741,7 +718,7 @@ static inline void * rb_alloc_tmp_buffer2(volatile VALUE *store, long count, size_t elsize) { const size_t total_size = rbimpl_size_mul_or_raise(RBIMPL_CAST((size_t)count), elsize); - return rb_alloc_tmp_buffer(store, (long)total_size); + return rb_alloc_tmp_buffer(store, (long)total_size, elsize >= sizeof(VALUE)); } RBIMPL_SYMBOL_EXPORT_BEGIN() diff --git a/internal/imemo.h b/internal/imemo.h index e8a5f0fc8eb66e..7562794506f3d6 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -97,6 +97,7 @@ struct rb_imemo_tmpbuf_struct { VALUE flags; VALUE *ptr; /* malloc'ed buffer */ size_t size; /* buffer size in bytes */ + bool marked; /* whether the buffer may contain object references */ }; struct rb_imemo_cdhash { @@ -142,7 +143,6 @@ struct MEMO { typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif VALUE rb_imemo_new(enum imemo_type type, VALUE v0, size_t size, bool is_shareable); -VALUE rb_imemo_tmpbuf_new(void); struct MEMO *rb_imemo_memo_new(VALUE a, VALUE b, long c); struct MEMO *rb_imemo_memo_new_value(VALUE a, VALUE b, VALUE c); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); @@ -212,7 +212,7 @@ rb_imemo_tmpbuf_new_from_an_RString(VALUE str) StringValue(str); len = RSTRING_LEN(str); - rb_alloc_tmp_buffer(&imemo, len); + rb_alloc_tmp_buffer(&imemo, len, false); memcpy(RB_IMEMO_TMPBUF_PTR(imemo), RSTRING_PTR(str), len); return imemo; } diff --git a/process.c b/process.c index e443ccbe49af13..ac44167c05f2ca 100644 --- a/process.c +++ b/process.c @@ -2726,7 +2726,7 @@ open_func(void *ptr) static void rb_execarg_allocate_dup2_tmpbuf(struct rb_execarg *eargp, long len) { - rb_alloc_tmp_buffer(&eargp->dup2_tmpbuf, run_exec_dup2_tmpbuf_size(len)); + rb_alloc_tmp_buffer(&eargp->dup2_tmpbuf, run_exec_dup2_tmpbuf_size(len), false); } static VALUE From 6d4d8cba5a12f3ac4aa60a9a4217e22e487d0fff Mon Sep 17 00:00:00 2001 From: Daichi Kamiyama <32436625+dak2@users.noreply.github.com> Date: Wed, 17 Jun 2026 01:33:22 +0900 Subject: [PATCH 4/8] ZJIT: Add tests for FixnumAdd/FixnumSub overflow side exit (#17360) Add `test_opt_plus_overflow` and `test_opt_minus_overflow`, verifying that `Fixnum + Fixnum` and `Fixnum - Fixnum` side exit on overflow, seems like test_opt_mult_overflow. --- zjit/src/codegen_tests.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index 99d5f905166c9e..6dc607e399de31 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -2102,6 +2102,38 @@ fn test_opt_mult_overflow() { "), @"[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]"); } +#[test] +fn test_opt_plus_overflow() { + assert_snapshot!(inspect(" + def test(a, b) + a + b + end + test(1, 2) # profile opt_plus + + r1 = test(2, 3) + r2 = test(4611686018427387903, 1) # FIXNUM_MAX + 1 overflows + r3 = test(-4611686018427387904, -1) # FIXNUM_MIN - 1 overflows + + [r1, r2, r3] + "), @"[5, 4611686018427387904, -4611686018427387905]"); +} + +#[test] +fn test_opt_minus_overflow() { + assert_snapshot!(inspect(" + def test(a, b) + a - b + end + test(6, 4) # profile opt_minus + + r1 = test(6, 4) + r2 = test(4611686018427387903, -1) # FIXNUM_MAX - (-1) overflows + r3 = test(-4611686018427387904, 1) # FIXNUM_MIN - 1 overflows + + [r1, r2, r3] + "), @"[2, 4611686018427387904, -4611686018427387905]"); +} + #[test] fn test_opt_eq() { eval(" From 1326e744231876f70da1e08bcdffac10fc51e1a1 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 3 Jun 2026 15:15:47 -0700 Subject: [PATCH 5/8] Simplify imemo_callcache reference update In the past this used an ad-hoc weakref implementation that required checking whether the referenced object was about to be swept. We replaced this with the actual weakref implementation from the GC so these VALUEs should always be either live or Qundef. --- imemo.c | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/imemo.c b/imemo.c index 1c9020593b2380..9154f6bc6ac280 100644 --- a/imemo.c +++ b/imemo.c @@ -332,12 +332,6 @@ rb_imemo_memsize(VALUE obj) * mark * ========================================================================= */ -static bool -moved_or_living_object_strictly_p(VALUE obj) -{ - return !SPECIAL_CONST_P(obj) && (!rb_objspace_garbage_object_p(obj) || BUILTIN_TYPE(obj) == T_MOVED); -} - static void mark_and_move_method_entry(rb_method_entry_t *ment, bool reference_updating) { @@ -420,17 +414,12 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) */ } else if (reference_updating) { - if (moved_or_living_object_strictly_p((VALUE)cc->cme_)) { - *((VALUE *)&cc->klass) = rb_gc_location(cc->klass); - *((struct rb_callable_method_entry_struct **)&cc->cme_) = - (struct rb_callable_method_entry_struct *)rb_gc_location((VALUE)cc->cme_); + *((VALUE *)&cc->klass) = rb_gc_location(cc->klass); + *((struct rb_callable_method_entry_struct **)&cc->cme_) = + (struct rb_callable_method_entry_struct *)rb_gc_location((VALUE)cc->cme_); - RUBY_ASSERT(RB_TYPE_P(cc->klass, T_CLASS) || RB_TYPE_P(cc->klass, T_ICLASS)); - RUBY_ASSERT(IMEMO_TYPE_P((VALUE)cc->cme_, imemo_ment)); - } - else { - vm_cc_invalidate(cc); - } + RUBY_ASSERT(RB_TYPE_P(cc->klass, T_CLASS) || RB_TYPE_P(cc->klass, T_ICLASS)); + RUBY_ASSERT(IMEMO_TYPE_P((VALUE)cc->cme_, imemo_ment)); } else { RUBY_ASSERT(RB_TYPE_P(cc->klass, T_CLASS) || RB_TYPE_P(cc->klass, T_ICLASS)); From 6b915ecb1dd16c5233a90fad2382a054aaf70ff5 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 3 Jun 2026 23:40:54 -0700 Subject: [PATCH 6/8] Stop using rb_gc_pointer_to_heap_p --- gc.c | 8 -------- gc/default/default.c | 24 +++++++++++++++++++++++- internal/gc.h | 1 - vm_method.c | 1 - 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/gc.c b/gc.c index b385ed31332916..ece52789f71fbe 100644 --- a/gc.c +++ b/gc.c @@ -2038,12 +2038,6 @@ rb_objspace_garbage_object_p(VALUE obj) return !SPECIAL_CONST_P(obj) && rb_gc_impl_garbage_object_p(rb_gc_get_objspace(), obj); } -bool -rb_gc_pointer_to_heap_p(VALUE obj) -{ - return rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj); -} - #define OBJ_ID_INCREMENT (RUBY_IMMEDIATE_MASK + 1) #define LAST_OBJECT_ID() (object_id_counter * OBJ_ID_INCREMENT) static VALUE id2ref_value = 0; @@ -3139,8 +3133,6 @@ gc_location_internal(void *objspace, VALUE value) return value; } - GC_ASSERT(rb_gc_impl_pointer_to_heap_p(objspace, (void *)value)); - return rb_gc_impl_location(objspace, value); } diff --git a/gc/default/default.c b/gc/default/default.c index 66b4c593eac06a..3e00f908cb24cd 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -2804,7 +2804,27 @@ is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr) bool rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr) { - return is_pointer_to_heap(objspace_ptr, ptr); + rb_objspace_t *objspace = objspace_ptr; + + /* Whether ptr refers to a live object. is_pointer_to_heap is the + * address-only check; T_NONE, T_MOVED, and T_ZOMBIE slots are valid heap + * addresses but not live objects. */ + if (!is_pointer_to_heap(objspace, ptr)) return false; + + VALUE obj = (VALUE)ptr; + bool live = false; + asan_unpoisoning_object(obj) { + switch (BUILTIN_TYPE(obj)) { + case T_NONE: + case T_MOVED: + case T_ZOMBIE: + break; + default: + live = true; + break; + } + } + return live; } #define ZOMBIE_OBJ_KEPT_FLAGS (FL_FINALIZE) @@ -4369,6 +4389,8 @@ rb_gc_impl_location(void *objspace_ptr, VALUE value) { VALUE destination; + GC_ASSERT(is_pointer_to_heap(objspace_ptr, (void *)value)); + asan_unpoisoning_object(value) { if (BUILTIN_TYPE(value) == T_MOVED) { destination = (VALUE)RMOVED(value)->destination; diff --git a/internal/gc.h b/internal/gc.h index e21fb892676f61..8e85381f090f35 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -229,7 +229,6 @@ void rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), v void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *data); int rb_objspace_internal_object_p(VALUE obj); int rb_objspace_garbage_object_p(VALUE obj); -bool rb_gc_pointer_to_heap_p(VALUE obj); void rb_gc_declare_weak_references(VALUE obj); bool rb_gc_handle_weak_references_alive_p(VALUE obj); diff --git a/vm_method.c b/vm_method.c index e2648c0ee9e627..d8a42621c8f448 100644 --- a/vm_method.c +++ b/vm_method.c @@ -850,7 +850,6 @@ rb_clear_all_refinement_method_cache(void) // All objects should be live as weak references are pruned in // cc_refinement_set_handle_weak_references - VM_ASSERT(rb_gc_pointer_to_heap_p(v)); VM_ASSERT(!rb_objspace_garbage_object_p(v)); const struct rb_callcache *cc = (const struct rb_callcache *)v; From 7f5909f73c10b98bcd69840bcdc43f6458a0c968 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 8 Jun 2026 15:12:56 -0700 Subject: [PATCH 7/8] Rename pointer_to_heap_p live_object_p --- gc.c | 18 +++++++++--------- gc/default/default.c | 2 +- gc/gc_impl.h | 2 +- gc/mmtk/mmtk.c | 8 ++++---- gc/wbcheck/wbcheck.c | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/gc.c b/gc.c index ece52789f71fbe..7407fa66420a30 100644 --- a/gc.c +++ b/gc.c @@ -671,7 +671,7 @@ typedef struct gc_function_map { const char *(*active_gc_name)(void); // Miscellaneous struct rb_gc_object_metadata_entry *(*object_metadata)(void *objspace_ptr, VALUE obj); - bool (*pointer_to_heap_p)(void *objspace_ptr, const void *ptr); + bool (*live_object_p)(void *objspace_ptr, const void *ptr); bool (*garbage_object_p)(void *objspace_ptr, VALUE obj); void (*set_event_hook)(void *objspace_ptr, const rb_event_flag_t event); void (*copy_attributes)(void *objspace_ptr, VALUE dest, VALUE obj); @@ -850,7 +850,7 @@ ruby_modular_gc_init(void) load_modular_gc_func(active_gc_name); // Miscellaneous load_modular_gc_func(object_metadata); - load_modular_gc_func(pointer_to_heap_p); + load_modular_gc_func(live_object_p); load_modular_gc_func(garbage_object_p); load_modular_gc_func(set_event_hook); load_modular_gc_func(copy_attributes); @@ -938,7 +938,7 @@ ruby_modular_gc_init(void) # define rb_gc_impl_active_gc_name rb_gc_functions.active_gc_name // Miscellaneous # define rb_gc_impl_object_metadata rb_gc_functions.object_metadata -# define rb_gc_impl_pointer_to_heap_p rb_gc_functions.pointer_to_heap_p +# define rb_gc_impl_live_object_p rb_gc_functions.live_object_p # define rb_gc_impl_garbage_object_p rb_gc_functions.garbage_object_p # define rb_gc_impl_set_event_hook rb_gc_functions.set_event_hook # define rb_gc_impl_copy_attributes rb_gc_functions.copy_attributes @@ -2886,7 +2886,7 @@ ruby_stack_check(void) (func)(objspace, (obj_or_ptr)); \ } \ else if (check_obj ? \ - rb_gc_impl_pointer_to_heap_p(objspace, (const void *)obj) && \ + rb_gc_impl_live_object_p(objspace, (const void *)obj) && \ !rb_gc_impl_garbage_object_p(objspace, obj) : \ true) { \ GC_ASSERT(!rb_gc_impl_during_gc_p(objspace)); \ @@ -3585,7 +3585,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) if (BUILTIN_TYPE(obj) == T_ZOMBIE) rb_bug("rb_gc_mark(): %p is T_ZOMBIE", (void *)obj); rb_bug("rb_gc_mark(): unknown data type 0x%x(%p) %s", BUILTIN_TYPE(obj), (void *)obj, - rb_gc_impl_pointer_to_heap_p(objspace, (void *)obj) ? "corrupted object" : "non object"); + rb_gc_impl_live_object_p(objspace, (void *)obj) ? "corrupted object" : "non object"); } } @@ -3714,11 +3714,11 @@ rb_gc_ractor_cache_free(void *cache) void rb_gc_register_mark_object(VALUE obj) { - /* rb_gc_impl_pointer_to_heap_p() walks objspace->heap_pages.sorted, which + /* rb_gc_impl_live_object_p() walks objspace->heap_pages.sorted, which * another ractor may mutate while allocating heap pages under the VM lock, * so the lookup must be done under the VM lock as well. */ RB_VM_LOCKING() { - if (rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj)) { + if (rb_gc_impl_live_object_p(rb_gc_get_objspace(), (void *)obj)) { rb_vm_register_global_object(obj); } } @@ -4950,7 +4950,7 @@ rb_raw_obj_info_common(char *const buff, const size_t buff_size, const VALUE obj else { // const int age = RVALUE_AGE_GET(obj); - if (rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj)) { + if (rb_gc_impl_live_object_p(rb_gc_get_objspace(), (void *)obj)) { APPEND_F("%p %s/", (void *)obj, obj_type_name(obj)); // TODO: fixme // APPEND_F("%p [%d%s%s%s%s%s%s] %s ", @@ -5230,7 +5230,7 @@ rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) if (SPECIAL_CONST_P(obj)) { raw_obj_info(buff, buff_size, obj); } - else if (!rb_gc_impl_pointer_to_heap_p(objspace, (const void *)obj)) { + else if (!rb_gc_impl_live_object_p(objspace, (const void *)obj)) { snprintf(buff, buff_size, "out-of-heap:%p", (void *)obj); } #if 0 // maybe no need to check it? diff --git a/gc/default/default.c b/gc/default/default.c index 3e00f908cb24cd..530adb1ed8627d 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -2802,7 +2802,7 @@ is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr) } bool -rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr) +rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr) { rb_objspace_t *objspace = objspace_ptr; diff --git a/gc/gc_impl.h b/gc/gc_impl.h index d9e44cc66d89e6..b8417fd03a1bba 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -117,7 +117,7 @@ GC_IMPL_FN VALUE rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE GC_IMPL_FN const char *rb_gc_impl_active_gc_name(void); // Miscellaneous GC_IMPL_FN struct rb_gc_object_metadata_entry *rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj); -GC_IMPL_FN bool rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr); +GC_IMPL_FN bool rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr); GC_IMPL_FN bool rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj); GC_IMPL_FN void rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event); GC_IMPL_FN void rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 8be69b4fe65dc8..a725432c6e7c3a 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -1034,7 +1034,7 @@ rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj) void rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) { - if (rb_gc_impl_pointer_to_heap_p(objspace_ptr, (const void *)obj)) { + if (rb_gc_impl_live_object_p(objspace_ptr, (const void *)obj)) { rb_gc_impl_mark_and_pin(objspace_ptr, obj); } } @@ -1080,12 +1080,12 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) if (SPECIAL_CONST_P(b)) return; #ifdef MMTK_DEBUG - if (!rb_gc_impl_pointer_to_heap_p(objspace_ptr, (void *)a)) { + if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)a)) { char buff[256]; rb_bug("a: %s is not an object", rb_raw_obj_info(buff, 256, a)); } - if (!rb_gc_impl_pointer_to_heap_p(objspace_ptr, (void *)b)) { + if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)b)) { char buff[256]; rb_bug("b: %s is not an object", rb_raw_obj_info(buff, 256, b)); } @@ -1624,7 +1624,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj) } bool -rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr) +rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr) { if (ptr == NULL) return false; if ((uintptr_t)ptr % sizeof(void*) != 0) return false; diff --git a/gc/wbcheck/wbcheck.c b/gc/wbcheck/wbcheck.c index 9a669daf8e9485..43936ef1953543 100644 --- a/gc/wbcheck/wbcheck.c +++ b/gc/wbcheck/wbcheck.c @@ -1215,7 +1215,7 @@ rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) { rb_wbcheck_objspace_t *objspace = objspace_ptr; - if (!rb_gc_impl_pointer_to_heap_p(objspace_ptr, (void *)obj)) return; + if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)obj)) return; switch (objspace->phase) { case WBCHECK_PHASE_SNAPSHOT: @@ -1901,7 +1901,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj) } bool -rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr) +rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr) { GC_ASSERT(wbcheck_global_objspace); From 1128ac4d2e917cf5196d3667682cde20ff7135aa Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 3 Jun 2026 15:30:45 -0700 Subject: [PATCH 8/8] Only allow garbage_object_p on not-yet-swept objects --- gc/default/default.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 530adb1ed8627d..18d7a05b336430 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -1750,21 +1750,16 @@ rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr) { rb_objspace_t *objspace = objspace_ptr; - bool dead = false; - + /* Asking whether a freed (T_NONE), moved (T_MOVED), or finalized (T_ZOMBIE) + * object is garbage gives an unreliable answer: the slot may since have been + * reused for an unrelated object. A reference to one of these is stale and a + * bug in the caller. */ asan_unpoisoning_object(ptr) { - switch (BUILTIN_TYPE(ptr)) { - case T_NONE: - case T_MOVED: - case T_ZOMBIE: - dead = true; - break; - default: - break; - } + GC_ASSERT(BUILTIN_TYPE(ptr) != T_NONE); + GC_ASSERT(BUILTIN_TYPE(ptr) != T_MOVED); + GC_ASSERT(BUILTIN_TYPE(ptr) != T_ZOMBIE); } - if (dead) return true; return is_lazy_sweeping(objspace) && GET_HEAP_PAGE(ptr)->flags.before_sweep && !RVALUE_MARKED(objspace, ptr); } @@ -5369,6 +5364,22 @@ check_children_i(const VALUE child, void *ptr) } } +/* Whether a heap slot currently holds a live object. Returns false for empty + * (T_NONE), moved (T_MOVED), and zombie (T_ZOMBIE) slots, and for garbage + * objects about to be swept. */ +static bool +gc_slot_live_object_p(rb_objspace_t *objspace, VALUE obj) +{ + switch (BUILTIN_TYPE(obj)) { + case T_NONE: + case T_MOVED: + case T_ZOMBIE: + return false; + default: + return !rb_gc_impl_garbage_object_p(objspace, obj); + } +} + static int verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, struct verify_internal_consistency_struct *data) @@ -5378,7 +5389,7 @@ verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, for (obj = (VALUE)page_start; obj != (VALUE)page_end; obj += stride) { asan_unpoisoning_object(obj) { - if (!rb_gc_impl_garbage_object_p(objspace, obj)) { + if (gc_slot_live_object_p(objspace, obj)) { /* count objects */ data->live_object_count++; data->parent = obj;