BoundaryML/baml · critical

HeapPtr is a runtime-only pointer and must not be serialized

Error message

HeapPtr is a runtime-only pointer and must not be serialized

What it means

HeapPtr's BorshSerialize impl is intentionally a hard failure: a HeapPtr is a runtime-only heap pointer and must never appear in serialized data (a compiled Program). The VM deliberately rejects serialization of any structure containing one so a genuine bug is surfaced immediately rather than silently round-tripping a meaningless pointer.

Solutions

  1. Stop serializing live VM/runtime objects; only serialize the compiler-produced Program types (Object::{Function, Class, Enum, String}) which are HeapPtr-free by design.
  2. Audit the type being serialized for accidental inclusion of runtime pointers (heap values, futures).
  3. If you hit this from normal program compilation, report it — it indicates an internal invariant violation where a runtime pointer leaked into the compiled artifact.
  4. Strip/detach runtime-only fields before serializing your own structures.

Example fix

// before: serializing a runtime struct that embeds a HeapPtr
#[derive(BorshSerialize)]
struct Snapshot { obj: HeapPtr }

// after: serialize only HeapPtr-free Program types
#[derive(BorshSerialize)]
struct Snapshot { name: Object_String }
Defensive patterns

Strategy: type-guard

Validate before calling

// Reject runtime graphs containing HeapPtr before attempting serialization
fn serializable_check(v: &VmValue) -> bool {
    !matches!(v, VmValue::Heap(_))
}

Prevention

When it happens

Trigger: Calling borsh serialization on a runtime object graph that still contains HeapPtr-backed values (e.g. attempting to persist/serialize live VM state or an object not meant for compiled programs).

Common situations: Trying to cache or ship serialized VM heap state; a compiler/VM bug placing a runtime-only object into the serialized Program; custom tooling serializing Value graphs that were never supposed to leave the runtime.

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 BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/54e64271047ad9d4. Report an issue: GitHub.

Appendix: source

Thrown at baml_language/crates/bex_vm_types/src/heap_ptr.rs:163

    #[cfg(feature = "heap_debug")]
    #[inline]
    pub fn epoch(self) -> u32 {
        self.epoch
    }
}

// `HeapPtr` is a runtime-only address into the live heap. It must never
// reach a serialized payload (e.g. a borsh-encoded `Program` in a pack
// envelope) — the addresses are meaningless outside the originating
// process, and a deserialized non-null `HeapPtr` would be undefined
// behavior on first deref. The borsh impls below therefore *fail* at the
// boundary instead of round-tripping to `null()`: every type the compiler
// actually puts into a serialized `Program` (`Object::{Function, Class,
// Enum, String}`) is HeapPtr-free, so any path that does encounter one
// reflects a real bug we want to surface immediately rather than mask.
impl BorshSerialize for HeapPtr {
    fn serialize<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> {
        Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "HeapPtr is a runtime-only pointer and must not be serialized",
        ))
    }
}

impl BorshDeserialize for HeapPtr {
    fn deserialize_reader<R: std::io::Read>(_reader: &mut R) -> std::io::Result<Self> {
        Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "HeapPtr cannot be deserialized: runtime pointer leaked into serialized data",
        ))
    }
}

impl PartialEq for HeapPtr {
    fn eq(&self, other: &Self) -> bool {
        self.ptr == other.ptr

View on GitHub (pinned to bd85ce9dee)