{"id":"e932b9eb7ba56908","repo":"rust-lang/rust","slug":"invalid-terminator-state","errorCode":null,"errorMessage":"invalid terminator state","messagePattern":"invalid terminator state","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"compiler/rustc_middle/src/mir/mod.rs","lineNumber":1368,"sourceCode":"        statements: Vec<Statement<'tcx>>,\n        terminator: Option<Terminator<'tcx>>,\n        is_cleanup: bool,\n    ) -> BasicBlockData<'tcx> {\n        BasicBlockData {\n            statements,\n            after_last_stmt_debuginfos: StmtDebugInfos::default(),\n            terminator,\n            is_cleanup,\n        }\n    }\n\n    /// Accessor for terminator.\n    ///\n    /// Terminator may not be None after construction of the basic block is complete. This accessor\n    /// provides a convenient way to reach the terminator.\n    #[inline]\n    pub fn terminator(&self) -> &Terminator<'tcx> {\n        self.terminator.as_ref().expect(\"invalid terminator state\")\n    }\n\n    #[inline]\n    pub fn terminator_mut(&mut self) -> &mut Terminator<'tcx> {\n        self.terminator.as_mut().expect(\"invalid terminator state\")\n    }\n\n    /// Does the block have no statements and an unreachable terminator?\n    #[inline]\n    pub fn is_empty_unreachable(&self) -> bool {\n        self.statements.is_empty() && matches!(self.terminator().kind, TerminatorKind::Unreachable)\n    }\n\n    /// Like [`Terminator::successors`] but tries to use information available from the [`Instance`]\n    /// to skip successors like the `false` side of an `if const {`.\n    ///\n    /// This is used to implement [`traversal::mono_reachable`] and\n    /// [`traversal::mono_reachable_reverse_postorder`].","sourceCodeStart":1350,"sourceCodeEnd":1386,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_middle/src/mir/mod.rs#L1350-L1386","documentation":"`BasicBlockData::terminator()` is an accessor that asserts the block's optional `terminator` field is `Some`. rustc_middle documents that a basic block's terminator must be set once the block is finished, so calling this on a block still under construction (or one produced by a buggy MIR pass that forgot to set a terminator) violates the invariant and panics with 'invalid terminator state'.","triggerScenarios":"Triggered by any code path — MIR traversal, optimization pass, or consumer — that calls `body.basic_blocks[block].terminator()` on a `BasicBlockData` whose `terminator` field is `None`. Most often hit by a MIR transformation that builds a new block via `BasicBlockData::new_stmts(stmts, None, is_cleanup)` or removes a terminator without re-adding one, then immediately queries it.","commonSituations":"Writing a custom `MirPass` or rustc plugin that inserts or rewrites basic blocks; mir-opt instrumentation that walks blocks before a pass finishes; out-of-tree Cranelift/gcc backend consuming half-built MIR; in-tree regression where an optimization drops the terminator edge case.","solutions":["Audit the MIR pass that constructed or mutated the block and ensure it always sets a terminator (e.g. `Unreachable`, `Return`, or `Goto`) before the block is observed by another pass.","If you must query a block mid-construction, guard with `block.terminator.as_ref()` (Option) instead of the asserting accessor.","Reproduce with `-Zmir-opt-level=0` to rule out an optimizer pass; if it only fails at higher levels, bisect with `-Zmir-opt-level=N` and report the offending pass.","Dump MIR with `-Zunpretty=mir` per pass (`-Zdump-mir=all`) to find the exact pass leaving a terminator-less block."],"exampleFix":"// before\nlet bb = body.basic_blocks_mut().push(BasicBlockData::new_stmts(\n    stmts, None, false,\n));\nlet term = body[bb].terminator(); // panic: invalid terminator state\n\n// after\nlet bb = body.basic_blocks_mut().push(BasicBlockData::new_stmts(\n    stmts,\n    Some(Terminator {\n        source_info,\n        kind: TerminatorKind::Unreachable,\n    }),\n    false,\n));\nlet term = body[bb].terminator();","handlingStrategy":"validation","validationCode":"// `BasicBlockData::terminator()` calls `.expect(\"invalid terminator state\")`.\n// Validate the block is fully built BEFORE touching the accessor.\nuse rustc_middle::mir::BasicBlockData;\nfn block_ready_for_use<'tcx>(bb: &BasicBlockData<'tcx>) -> bool {\n    bb.terminator.is_some() // Option<Terminator<'tcx>>\n}\n// Usage:\n//   if block_ready_for_use(&block) {\n//       let term = block.terminator();\n//   } else {\n//       // block is still under construction: insert/assign a terminator first\n//   }","typeGuard":"// Narrow an incompletely-built block to a usable one without panicking.\nuse rustc_middle::mir::{BasicBlockData, Terminator};\nfn as_complete_block<'tcx>(bb: &BasicBlockData<'tcx>) -> Option<&Terminator<'tcx>> {\n    bb.terminator.as_ref() // None instead of panic\n}\n// Caller pattern:\n//   match as_complete_block(&block) {\n//       Some(term) => { /* safe */ }\n//       None => { /* finish building: block.terminator = Some(...); */ }\n//   }","tryCatchPattern":null,"preventionTips":["A BasicBlockData is only valid for reads once its `terminator` field is `Some`; treat `None` as 'still under construction'.","When you construct or clone BasicBlockData, always set the terminator in the same statement — never leave a half-built block reachable by other code.","In any pass that removes or rewrites blocks (inline, unreachable-block pruning), re-establish the terminator before the block becomes observable to other passes.","If you traverse blocks produced by an upstream pass, assert/validate `terminator.is_some()` at the boundary rather than relying on the accessor's expect()."],"tags":["rustc","mir","internal-invariant","compiler-pass"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}