BoundaryML/baml · critical

HeapPtr cannot be deserialized: runtime pointer leaked into…

Error message

HeapPtr cannot be deserialized: runtime pointer leaked into serialized data

What it means

HeapPtr's BorshDeserialize impl is intentionally a hard failure: deserializing would have to fabricate a runtime heap pointer from stored bytes, which is never valid. Encountering this means runtime pointer data leaked into a serialized payload — a real bug that must fail fast rather than reconstruct a dangling pointer.

Solutions

  1. Regenerate the serialized Program from the compiler with a correct, HeapPtr-free build.
  2. Verify you are deserializing with the matching type/schema and library version used at serialization time.
  3. If the artifact is third-party or hand-edited, obtain it from a trusted source and re-check its integrity.
  4. Report it if normal compiled programs trigger this — it indicates a compiler bug leaking runtime pointers into serialized output.

Example fix

// before: decoding into a struct with a HeapPtr field
let snap: Snapshot = borsh::from_reader(&mut r)?; // Snapshot { obj: HeapPtr }

// after: decode into the HeapPtr-free Program object types
let prog: Program = borsh::from_reader(&mut r)?;
Defensive patterns

Strategy: type-guard

Prevention

When it happens

Trigger: Calling borsh deserialization on a byte blob whose structure contains a HeapPtr-typed field; loading a corrupted or hand-crafted serialized Program where a runtime pointer was encoded.

Common situations: Loading stale/foreign serialized artifacts produced by buggy tooling; deserializing into the wrong type so bytes are interpreted as a HeapPtr; tampering or corruption in shipped program files.

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/ac8a78656e45b0e0. Report an issue: GitHub.

Appendix: source

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

// 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
    }
}

impl Eq for HeapPtr {}

impl Default for HeapPtr {
    fn default() -> Self {
        Self::null()
    }

View on GitHub (pinned to bd85ce9dee)