pydantic/monty · critical

HeapPtr::entry: slot has been freed

Error message

HeapPtr::entry: slot has been freed

What it means

`HeapPtr::entry` panics when the heap slot it points at is currently freed; the doc comment directs code that can legitimately see freed slots (linear scans over `0..heap.entries.len()`) to `try_entry` instead. Panicking here means a `HeapPtr` outlived its entry's lifetime — a use-after-free of a heap object. Root cause is almost always a refcount/drop ordering bug upstream.

Source

Thrown at crates/monty/src/heap/mod.rs:653

    /// so a `HeapPtr` cannot be reborrowed under a different reader scope.
    brand: PhantomData<fn(&'a ()) -> &'a ()>,
}

impl<'a> HeapPtr<'a> {
    /// Returns the live [`HeapEntry`] this pointer refers to, panicking if the slot
    /// has been freed.
    ///
    /// All `HeapEntry` fields are interior-mutable — `refcount`/`readers`/`color`
    /// via `Cell` and `data` via `UnsafeCell` — so callers can mutate them through
    /// the returned `&HeapEntry` without ever needing `&mut HeapEntry`. That's
    /// what makes a `&self`-derived `HeapPtr` (with Shared provenance) sound to
    /// dereference: we never derive `&mut` from it, so the SB/TB rules permit
    /// interior mutation via the embedded `Cell`/`UnsafeCell`.
    ///
    /// Use [`Self::try_entry`] for code paths that may legitimately encounter
    /// freed slots (e.g. linear scans over `0..heap.entries.len()`).
    pub fn entry<'r>(self, reader: &'r HeapReader<'a>) -> &'r HeapEntry {
        self.try_entry(reader).expect("HeapPtr::entry: slot has been freed")
    }

    /// Returns the [`HeapEntry`] this pointer refers to, or `None` if the slot is
    /// currently freed.
    ///
    /// Use where a freed slot is part of the expected state (linear scans, root
    /// reseeds, etc.). Mutation paths (cycle collector mark/scan inner loops) should
    /// prefer [`Self::entry`] so that an unexpectedly-freed entry surfaces as a
    /// loud panic rather than a silent skip.
    pub(crate) fn try_entry<'r>(self, _reader: &'r HeapReader<'a>) -> Option<&'r HeapEntry> {
        // SAFETY:
        //  - The invariant `'a` on `_reader` matches this pointer's brand, which is
        //    only settable inside `HeapReader::with`. That guarantees same-heap
        //    origin: a `HeapPtr<'a>` from a different reader scope cannot satisfy
        //    this signature.
        //  - `StableHeap::entry_ptr` only returns pointers to initialized slots, so
        //    the `Option<HeapEntry>` behind the pointer is always a valid place.
        //  - The `&HeapReader` borrow excludes any `&mut HeapReader` op that could

View on GitHub (pinned to adc986b362)

Solutions

  1. Switch code that may observe freed slots to `try_entry` and handle `None` explicitly.
  2. Find who freed the entry early: check `py_dec_ref_ids` pushes every owned id exactly once and `DropWithContext` releases owned ids on all branches.
  3. Ensure no `HeapPtr`/`HeapRead` is retained across `allocate` calls that can free slots — re-read by `HeapId` after mutating operations.
  4. Run with `--features memory-model-checks` to pinpoint the refcount violation.

Example fix

// before
let ptr = heap.read(id).as_list();
ptr.append(vm, item)?;               // append allocates, may free `ptr`'s slot
let data = ptr.entry(reader);        // panics: slot freed
// after
let ptr = heap.read(id).as_list();
ptr.append(vm, item)?;               // frees/mutates through owned handles only
// do not re-derive entry from a stale HeapPtr; re-read by id if needed
let ptr2 = heap.read(id);
let data = ptr2.entry(reader);
Defensive patterns

Strategy: type-guard

Validate before calling

// in scans where a freed slot is possible
if let Some(entry) = ptr.try_entry(reader) { /* use entry */ }

Type guard

fn is_live<'a>(ptr: HeapPtr<'a>, reader: &HeapReader<'a>) -> bool { ptr.try_entry(reader).is_some() }

Prevention

When it happens

Trigger: Calling `entry()` on a `HeapPtr` whose slot was freed by a prior `dec_ref`/cycle-collection pass; holding a `HeapPtr` across an operation that allocates/frees (which `HeapPtr` is supposed to prevent); GC code touching a collected object.

Common situations: New heap types missing entries in `py_dec_ref_ids` (or double-pushing ids), causing premature frees; iteration code holding a `HeapRead` while calling dropping operations; cycle collector changes that free entries still referenced.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/7927c4d049320d79. Report an issue: GitHub.