{"record":{"id":"c5b219adf0bc9288","repo":"BoundaryML/baml","slug":"invalid-handle-expected-expected","errorCode":null,"errorMessage":"Invalid handle: expected {expected}","messagePattern":"Invalid handle: expected (.+?)","errorType":"exception","errorClass":"AccessError","httpStatus":null,"severity":"error","filePath":"baml_language/crates/bex_heap/src/accessor.rs","lineNumber":15,"sourceCode":"//! Safe accessor API for external code to read heap objects.\n//!\n//! External code cannot safely hold bare `HeapPtr` values across GC. This\n//! module provides an API that takes a `PermitProof<'_>` (obtained from any\n//! held `ActiveHeapPermit<T>`) to witness GC-exclusion at the type level.\n\nuse baml_type::RuntimeTy;\nuse bex_external_types::{BexExternalAdt, BexExternalValue, WeakHeapRef};\nuse bex_vm_types::{HeapPtr, Object, PermitProof, Value};\n\nuse crate::BexHeap;\n\n#[derive(Debug, PartialEq, thiserror::Error, Clone)]\npub enum AccessError {\n    #[error(\"Invalid handle: expected {expected}\")]\n    InvalidHandle { expected: &'static str },\n\n    #[error(\"Type mismatch: expected {expected}, got {actual}\")]\n    TypeMismatch {\n        expected: &'static str,\n        actual: String,\n    },\n\n    #[error(\"Field not found: expected {expected}\")]\n    FieldNotFound { expected: String },\n\n    #[error(\"Function not found: {expected}\")]\n    FunctionNotFound { expected: String },\n\n    #[error(\"Cannot convert to owned: {reason}\")]\n    CannotConvertToOwned { reason: String },\n}\n","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/baml_language/crates/bex_heap/src/accessor.rs#L1-L33","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet obj = heap.get(handle)?; // handle already freed\n// after\nif !heap.contains(handle) { return Err(AccessError::InvalidHandle { expected: \"live heap object\" }); }\nlet obj = heap.get(handle)?;","handlingStrategy":"type-guard","validationCode":"if !heap.contains(handle) {\n    return Err(AccessError::InvalidHandle { expected: \"live heap object\" });\n}","typeGuard":"fn is_live(heap: &BexHeap, ptr: HeapPtr) -> bool { heap.contains(ptr) }","tryCatchPattern":"match heap.get(handle) {\n    Ok(obj) => obj,\n    Err(AccessError::InvalidHandle { expected }) => { log::error!(\"dangling handle, expected {expected}\"); recover(); },\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["heap","vm","invalid-handle"],"backgroundTag":"invalid-argument-value","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}