build_helper/drop_bomb/
mod.rs

1//! This module implements "drop bombs" intended for use by command wrappers to ensure that the
2//! constructed commands are *eventually* executed. This is exactly like `rustc_errors::Diag` where
3//! we force every `Diag` to be consumed or we emit a bug, but we panic instead.
4//!
5//! This is adapted from <https://docs.rs/drop_bomb/latest/drop_bomb/> and simplified for our
6//! purposes.
7
8use std::ffi::{OsStr, OsString};
9use std::panic;
10
11#[cfg(test)]
12mod tests;
13
14#[derive(Debug)]
15pub struct DropBomb {
16    command: OsString,
17    defused: bool,
18    armed_location: panic::Location<'static>,
19}
20
21impl DropBomb {
22    /// Arm a [`DropBomb`]. If the value is dropped without being [`defused`][Self::defused], then
23    /// it will panic. It is expected that the command wrapper uses `#[track_caller]` to help
24    /// propagate the caller location.
25    #[track_caller]
26    pub fn arm<S: AsRef<OsStr>>(command: S) -> DropBomb {
27        DropBomb {
28            command: command.as_ref().into(),
29            defused: false,
30            armed_location: *panic::Location::caller(),
31        }
32    }
33
34    pub fn get_created_location(&self) -> panic::Location<'static> {
35        self.armed_location
36    }
37
38    /// Defuse the [`DropBomb`]. This will prevent the drop bomb from panicking when dropped.
39    pub fn defuse(&mut self) {
40        self.defused = true;
41    }
42}
43
44impl Drop for DropBomb {
45    fn drop(&mut self) {
46        if !self.defused && !std::thread::panicking() {
47            panic!(
48                "command constructed at `{}` was dropped without being executed: `{}`",
49                self.armed_location,
50                self.command.to_string_lossy()
51            )
52        }
53    }
54}