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
- Validate the handle against the heap's live-object set before dereferencing
- Refresh/reload handles after any GC or heap-compaction step
- Check allocation lifetime: don't cache HeapPtr across heap resets
- 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
- Never cache HeapPtr values across GC or heap resets
- Re-fetch handles after any allocation that may trigger collection
- Use accessor Results instead of assuming handle validity
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
- Field not found: expected
- Function not found
- Type mismatch: expected
- baml.panics.AssertionFailed
- baml.panics.Cancelled
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)