From d46b726c68e26fd1f73225dfa90856e0d84daf77 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 30 Apr 2025 10:28:25 -0700 Subject: [PATCH] Add a check in non-debug mode for a T_NONE class Although a crash could occur anywhere, one of the most common symptoms we see from getting a reference to a garbage collected object is crashing while attempting to call a method on it. These crashes usually occur when trying to perform an rb_id_table_lookup in the "class" cc_tbl, where the class is usually another garbage collected object, because the freelist is stored in the class pointer. This commit aims to have a better error message in this case, in hopes of having a better grouping of errors and to give a better hint to those investigating and triaging crashes. --- vm_insnhelper.c | 12 ++++++++++++ vm_method.c | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 2357a8d08b0ea4..58f1457cd57fa3 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2184,6 +2184,18 @@ rb_vm_search_method_slowpath(const struct rb_callinfo *ci, VALUE klass) { const struct rb_callcache *cc; + VM_ASSERT(!SPECIAL_CONST_P(klass)); + + if (RB_BUILTIN_TYPE(klass) == T_NONE) { + // If we find a T_NONE here, it's most likely we called CLASS_OF(obj) on a + // garbage collected object (the freelist is stored in the class pointer), + // but it's possible that just the class was GC'd. + // This message intentionally tries to imply the former, but make an + // accurate statement for either case. + rb_bug("attempted to search method '%s' on a garbage collected object", + rb_id2name(vm_ci_mid(ci))); + } + VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS); RB_VM_LOCK_ENTER(); diff --git a/vm_method.c b/vm_method.c index 7288fdd2ec5cd0..31dfa7b388f9e1 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1488,6 +1488,18 @@ callable_method_entry_or_negative(VALUE klass, ID mid, VALUE *defined_class_ptr) { const rb_callable_method_entry_t *cme; + VM_ASSERT(!SPECIAL_CONST_P(klass)); + + if (RB_BUILTIN_TYPE(klass) == T_NONE) { + // If we find a T_NONE here, it's most likely we called CLASS_OF(obj) on a + // garbage collected object (the freelist is stored in the class pointer), + // but it's possible that just the class was GC'd. + // This message intentionally tries to imply the former, but make an + // accurate statement for either case. + rb_bug("attempted to search method '%s' on a garbage collected object", + rb_id2name(mid)); + } + VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS); RB_VM_LOCK_ENTER(); {