{"record":{"id":"8940b042c47203d7","repo":"BoundaryML/baml","slug":"exception-table-handler-block-handler-has-no-pc-address","errorCode":null,"errorMessage":"exception table: handler block {handler:?} has no PC address — catch region was emitted but its handler block was dropped","messagePattern":"exception table: handler block (.+?) has no PC address — catch region was emitted but its handler block was dropped","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"baml_language/crates/baml_compiler2_emit/src/emit.rs","lineNumber":2830,"sourceCode":"    /// anchor it), and both escaped their `catch` before this was made exact.\n    ///\n    /// Nested regions overlap: the protected PC set of an inner region is a\n    /// subset of every enclosing region's (inner windows nest inside outer\n    /// windows at lowering). The VM picks the innermost covering entry —\n    /// largest `start_pc`, then smallest `end_pc`, then latest table order —\n    /// which subset-nesting makes unambiguous: the inner region's coalesced\n    /// range around any PC is contained in the outer's, and for byte-identical\n    /// ranges the stable sort below preserves `catch_regions` creation order\n    /// (always outer before inner), so the last matching entry is the inner\n    /// handler.\n    fn build_exception_table(&mut self, mir: &MirFunctionBody<'ctx>) {\n        use bex_vm_types::bytecode::{ExceptionTableEntry, HandlerContextEntry};\n\n        for region in &mir.catch_regions {\n            let handler = self.analysis.resolve_jump_target(region.handler);\n\n            let &handler_pc = self.block_addresses.get(&handler).unwrap_or_else(|| {\n                unreachable!(\n                    \"exception table: handler block {handler:?} has no PC address — \\\n                     catch region was emitted but its handler block was dropped\"\n                )\n            });\n            // If the error local was optimized away (e.g. an inline\n            // `throw X catch ...` that the MIR lowers as a direct jump),\n            // the catch region doesn't need a VM-level exception table entry.\n            let Some(&error_slot) = self.local_slots.get(&region.error_local) else {\n                log::debug!(\n                    \"exception table: error local {:?} has no slot (optimized away)\",\n                    region.error_local,\n                );\n                continue;\n            };\n\n            let stack_trace_slot = region\n                .stack_trace_local\n                .and_then(|local| self.local_slots.get(&local).copied())","sourceCodeStart":2812,"sourceCodeEnd":2848,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/baml_language/crates/baml_compiler2_emit/src/emit.rs#L2812-L2848","documentation":"Panic (via unreachable!) while building the exception table: a MIR catch region's handler block, after jump-target resolution, has no entry in block_addresses. The catch region was emitted (so the compiler believes a handler is needed) but the handler block itself was dropped during emission. Every emitted catch region must have a handler block with a concrete PC.","triggerScenarios":"Finalizing a function with mir.catch_regions non-empty where the resolved handler block was never emitted, e.g. the handler block was eliminated by an optimization but its catch region survived.","commonSituations":"Compiler development around try/catch lowering or dead-block elimination in BAML's MIR-to-bytecode pipeline; users see it as a compiler crash on some try/catch programs.","solutions":["Keep the handler block alive whenever a catch region referencing it is emitted, or drop both together.","Record a redirect address for eliminated handler blocks so the exception table can point at the surviving equivalent.","Reproduce with the try/catch BAML source and file a compiler bug including the MIR dump.","Use a compiler build without the offending block-elimination change."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// compiler panic — not catchable in-process; guard the build\nif !baml_cli_compile(src).is_ok() {\n    report_compiler_bug(src);\n}","preventionTips":["Keep catch regions and their handler blocks alive/dropped together","Assert handler-block coverage before building the exception table","Add CI tests for try/catch under dead-code elimination","Pin a known-good compiler version while a regression is open"],"tags":["compiler","exception-handling","bytecode","panic"],"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-14T05:17:10.506Z"}