diff --git a/iseq.c b/iseq.c index a60228c8e585eb..19be0b3e1643dc 100644 --- a/iseq.c +++ b/iseq.c @@ -114,7 +114,9 @@ remove_from_constant_cache(ID id, IC ic) st_table *ics = (st_table *)lookup_result; st_delete(ics, &ic_data, NULL); - if (ics->num_entries == 0) { + if (ics->num_entries == 0 && + // See comment in vm_track_constant_cache on why we need this check + id != vm->inserting_constant_cache_id) { rb_id_table_delete(vm->constant_cache, id); st_free_table(ics); } diff --git a/vm_core.h b/vm_core.h index bce5f35763383c..d05a9e73439656 100644 --- a/vm_core.h +++ b/vm_core.h @@ -773,6 +773,7 @@ typedef struct rb_vm_struct { // and Qtrue as values. It is used when inline constant caches need to be // invalidated or ISEQs are being freed. struct rb_id_table *constant_cache; + ID inserting_constant_cache_id; #ifndef VM_GLOBAL_CC_CACHE_TABLE_SIZE #define VM_GLOBAL_CC_CACHE_TABLE_SIZE 1023 diff --git a/vm_insnhelper.c b/vm_insnhelper.c index bd616b2422ca56..f4752a7d97f9a8 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -6279,7 +6279,8 @@ rb_vm_opt_newarray_pack(rb_execution_context_t *ec, rb_num_t num, const VALUE *p static void vm_track_constant_cache(ID id, void *ic) { - struct rb_id_table *const_cache = GET_VM()->constant_cache; + rb_vm_t *vm = GET_VM(); + struct rb_id_table *const_cache = vm->constant_cache; VALUE lookup_result; st_table *ics; @@ -6291,7 +6292,23 @@ vm_track_constant_cache(ID id, void *ic) rb_id_table_insert(const_cache, id, (VALUE)ics); } + /* The call below to st_insert could allocate which could trigger a GC. + * If it triggers a GC, it may free an iseq that also holds a cache to this + * constant. If that iseq is the last iseq with a cache to this constant, then + * it will free this ST table, which would cause an use-after-free during this + * st_insert. + * + * So to fix this issue, we store the ID that is currently being inserted + * and, in remove_from_constant_cache, we don't free the ST table for ID + * equal to this one. + * + * See [Bug #20921]. + */ + vm->inserting_constant_cache_id = id; + st_insert(ics, (st_data_t) ic, (st_data_t) Qtrue); + + vm->inserting_constant_cache_id = (ID)0; } static void