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}