{"record":{"id":"98efb763c2519ea1","repo":"BoundaryML/baml","slug":"package-classes-only-contains-class-pointers","errorCode":null,"errorMessage":"Package.classes only contains class pointers","messagePattern":"Package\\.classes only contains class pointers","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"baml_language/crates/bex_vm/src/package_reflect/reflect.rs","lineNumber":287,"sourceCode":"        interface: &Interface,\n        member: &baml_type::Name,\n        fuel: u32,\n    ) -> baml_type::normalize::ProjectionStep<bex_vm_types::TypeHead> {\n        TypeContext::project(self.vm, base, interface, member, fuel)\n    }\n}\n\nfn package_class_type(vm: &mut BexVm, runtime_type: Option<HeapPtr>, class_ptr: HeapPtr) -> Value {\n    if let Some(runtime_type) = runtime_type {\n        let ty_value = Value::object(runtime_type);\n        return super::type_kinds::alloc_kind_view(\n            vm,\n            baml_type::type_kind::TypeKind::Class,\n            ty_value,\n        );\n    }\n    let Object::Class(class) = vm.get_object(class_ptr) else {\n        unreachable!(\"Package.classes only contains class pointers\")\n    };\n    let ty = RealizedTy::Class(\n        bex_vm_types::TypeHead::new(class_ptr, class.type_tag),\n        Box::new([]),\n        class.ty_attr.clone(),\n    );\n    let ty_value = Value::object(vm.tlab.alloc_type(TypeValue::new(ty)));\n    super::type_kinds::alloc_kind_view(vm, baml_type::type_kind::TypeKind::Class, ty_value)\n}\n\nfn package_enum_type(vm: &mut BexVm, runtime_type: Option<HeapPtr>, enum_ptr: HeapPtr) -> Value {\n    if let Some(runtime_type) = runtime_type {\n        let ty_value = Value::object(runtime_type);\n        return super::type_kinds::alloc_kind_view(\n            vm,\n            baml_type::type_kind::TypeKind::Enum,\n            ty_value,\n        );","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/baml_language/crates/bex_vm/src/package_reflect/reflect.rs#L269-L305","documentation":"`package_class_type` builds a reflected type for a class obtained from a package's `Package.classes` list. The `unreachable!` fires if the stored pointer does not point to an `Object::Class` — meaning the package's classes table was corrupted or populated with a non-class object. This is a hard internal invariant: the library only ever stores class pointers there.","triggerScenarios":"Calling `package_class_type` (directly or via `get_class`/`classes`) when a package's class slot holds a moved/stale/dropped heap pointer whose memory now holds a different object variant — typically after a GC forwarding bug or manual pointer manipulation.","commonSituations":"GC bugs that forward class pointers incorrectly; tests that read class pointers after sweeping without following forwarding entries; heap corruption from unsafe code that writes non-class objects into the package tables.","solutions":["Audit the GC/forwarding path: ensure class pointers in Package.classes are updated on object moves before reflection reads them.","Verify no unsafe code writes non-class objects into the package's classes table.","Reproduce with the GC test suite and check whether the pointer was swept/forwarded correctly (see dynamic_dispatch_entries tests).","If hit in a test, follow forwarding entries for stored pointers before calling reflection APIs."],"exampleFix":"// before: using a stale pointer captured before a GC\nlet class_ptr = package.classes(vm)[0];\nsweep_and_forward();\nlet ty = package_class_type(vm, class_ptr); // panics: pointer moved\n// after: re-read pointers after the sweep, or follow forwarding\nlet class_ptr = forwarding.get(&class_ptr).copied().unwrap_or(class_ptr);\nlet ty = package_class_type(vm, class_ptr);","handlingStrategy":"validation","validationCode":"// verify a class pointer is still valid after GC before reflection\nfn is_class(vm: &BexVm, p: HeapPtr) -> bool { matches!(vm.get_object(p), Object::Class(_)) }","typeGuard":"fn as_class<'a>(vm: &'a BexVm, p: HeapPtr) -> Option<&'a Class> {\n    match vm.get_object(p) { Object::Class(c) => Some(c), _ => None }\n}","tryCatchPattern":null,"preventionTips":["Re-read pointers from Package.classes after any GC cycle instead of caching them.","Keep GC forwarding tests green so package-stored pointers always follow relocations.","Avoid unsafe writes into package tables; use the loader's typed insertion APIs only."],"tags":["rust","vm","gc","invariant","reflection"],"backgroundTag":"internal-invariant-violation","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}