mirror of
https://github.com/ruby/ruby.git
synced 2026-11-08 01:20:26 +08:00
Multiple YJIT functions created overlapping `&'static mut IseqPayload` references by calling `get_iseq_payload()` multiple times for the same iseq. LLVM's noalias optimizations could then cache stale version_map Vec headers, causing the version_map to point to freed backing storage. This manifested as crashes in various YJIT operations (block lookup, GC marking, block removal) that dereference the stale pointer. Fix by moving `delayed_deallocation` and `get_or_create_version_list` from free functions (which each call `get_iseq_payload()` internally) to methods on `IseqPayload` that operate through `&mut self`. This lets callers obtain a single payload reference and use it for all operations without creating overlapping mutable borrows. The three fixed call sites: 1. `rb_yjit_tracing_invalidate_all` (invariants.rs): The loop called `delayed_deallocation()` which internally called `get_iseq_payload()`, creating a second `&mut` overlapping with the outer `payload` reference. Fix: call `payload.delayed_deallocation()` method instead. 2. `add_block_version` (core.rs): Called `get_or_create_version_list()` then later `get_iseq_payload()` for pages, creating two references. Fix: use a single `get_or_create_iseq_payload()` call then call the `get_or_create_version_list()` method on it for both version_map and pages access. 3. `invalidate_block_version` (core.rs): Called `remove_block_version()` (which calls `get_iseq_payload()`) then `delayed_deallocation()` (which also calls `get_iseq_payload()`), creating two references. Fix: get the payload once, inline the version_map removal, and call `payload.delayed_deallocation()` method for the rest. Also adds regression tests exercising tracing invalidation with on-stack methods and suspended fibers. (cherry picked from commit a6fcad276a4dd058491c5027f7f5c747ad39e372)