BoundaryML/baml · error · AccessError

Invalid handle: expected

Error message

Invalid handle: expected {expected}

What it means

AccessError::InvalidHandle is produced by bex_heap accessors when a raw HeapPtr/handle passed to the heap does not refer to a live heap object (dangling, null, or stale). The `expected` field names what kind of handle the accessor required. It is a thiserror enum wrapping unsafe VM heap access, so it signals the caller used an invalid or freed handle.

Solutions

  1. Validate the handle against the heap's live-object set before dereferencing
  2. Refresh/reload handles after any GC or heap-compaction step
  3. Check allocation lifetime: don't cache HeapPtr across heap resets
  4. Use the accessor's Result-based API and surface the AccessError instead of assuming validity

Example fix

// before
let obj = heap.get(handle)?; // handle already freed
// after
if !heap.contains(handle) { return Err(AccessError::InvalidHandle { expected: "live heap object" }); }
let obj = heap.get(handle)?;
Defensive patterns

Strategy: type-guard

Validate before calling

if !heap.contains(handle) {
    return Err(AccessError::InvalidHandle { expected: "live heap object" });
}

Type guard

fn is_live(heap: &BexHeap, ptr: HeapPtr) -> bool { heap.contains(ptr) }

Try / catch

match heap.get(handle) {
    Ok(obj) => obj,
    Err(AccessError::InvalidHandle { expected }) => { log::error!("dangling handle, expected {expected}"); recover(); },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling BexHeap accessors (e.g. reading an object through a handle) with a HeapPtr that was never allocated, was freed/GC-collected, or was fabricated from an out-of-range value.

Common situations: Use-after-free during VM execution after a GC pass; storing handles across heap compaction; off-by-one index math when deriving pointers; FFI boundaries passing raw pointers through.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/c5b219adf0bc9288. Report an issue: GitHub.

Appendix: source

Thrown at baml_language/crates/bex_heap/src/accessor.rs:15

//! Safe accessor API for external code to read heap objects.
//!
//! External code cannot safely hold bare `HeapPtr` values across GC. This
//! module provides an API that takes a `PermitProof<'_>` (obtained from any
//! held `ActiveHeapPermit<T>`) to witness GC-exclusion at the type level.

use baml_type::RuntimeTy;
use bex_external_types::{BexExternalAdt, BexExternalValue, WeakHeapRef};
use bex_vm_types::{HeapPtr, Object, PermitProof, Value};

use crate::BexHeap;

#[derive(Debug, PartialEq, thiserror::Error, Clone)]
pub enum AccessError {
    #[error("Invalid handle: expected {expected}")]
    InvalidHandle { expected: &'static str },

    #[error("Type mismatch: expected {expected}, got {actual}")]
    TypeMismatch {
        expected: &'static str,
        actual: String,
    },

    #[error("Field not found: expected {expected}")]
    FieldNotFound { expected: String },

    #[error("Function not found: {expected}")]
    FunctionNotFound { expected: String },

    #[error("Cannot convert to owned: {reason}")]
    CannotConvertToOwned { reason: String },
}

View on GitHub (pinned to bd85ce9dee)