tor_memquota/
drop_reentrancy.rs

1//! Newtype which helps assure lack of drop entrance hazards
2//!
3//! Provides a drop bomb which will help tests detect latent bugs.
4//!
5//! We want this because there are places where we handle an Arc containing
6//! a user-provided trait object, but where we want to prevent invoking
7//! the user's Drop impl since that may lead to reentrancy.
8//!
9//! See the section on "Reentrancy" in the docs for `mtracker::State`.
10//!
11//! Outside tests, the types in this module are equivalent to `std::sync`'s.
12//! So, we never panic in a drop in production.
13//! Dropping in the wrong place might lead to a deadlock
14//! (due to mutex reentrancy)
15//! but this is far from certain:
16//! probably, while we're running, the caller has another live reference,
17//! so the drop of the underlying type won't happen now anyway.
18//!
19//! In any case, drop bombs mustn't be used in production.
20//! Not only can they escalate the severity of problems,
21//! where the program might blunder on,
22//! but also
23//! because Rust upstream are seriously considering
24//! [turning them into aborts](https://github.com/rust-lang/rfcs/pull/3288)!
25//
26// There are no separate tests for this module.  Drop bombs are hard to test for.
27// However, in an ad-hoc test, the bomb has been shown to be able to explode,
28// if a `ProtectedArc` is dropped.
29
30use crate::internal_prelude::*;
31
32/// A `Weak<P>`, but upgradeable only to a `ProtectedArc`, not a raw `Arc`.
33#[derive(Debug)]
34pub(crate) struct ProtectedWeak<P: ?Sized>(Weak<P>);
35
36/// An `Arc`, but containing a type which should only be dropped in certain places
37///
38/// In non `#[cfg(test)]` builds, this is just `Arc<P>`.
39///
40/// When testing, it has a drop bomb.  You must call `.promise_dropping_is_ok`.
41/// It will panic if it's simply dropped.
42#[derive(Debug, Deref, DerefMut)]
43pub(crate) struct ProtectedArc<P: ?Sized> {
44    /// The actual explosive (might be armed or disarmed)
45    bomb: DropBomb,
46
47    /// The underlying `Arc`
48    #[deref(forward)]
49    #[deref_mut(forward)]
50    arc: Arc<P>,
51}
52
53impl<P: ?Sized> ProtectedWeak<P> {
54    /// Make a new `ProtectedWeak`
55    pub(crate) fn new(p: Weak<P>) -> Self {
56        ProtectedWeak(p)
57    }
58
59    /// Upgrade a `ProtectedWeak` to a `ProtectedArc`, if it's not been garbage collected
60    pub(crate) fn upgrade(&self) -> Option<ProtectedArc<P>> {
61        Some(ProtectedArc::new(self.0.upgrade()?))
62    }
63
64    /// Convert back into an unprotected `Weak`.
65    ///
66    /// # CORRECTNESS
67    ///
68    /// You must arrange that the drop reentrancy requirements aren't violated
69    /// by `Arc`s made from the returned `Weak`.
70    pub(crate) fn unprotect(self) -> Weak<P> {
71        self.0
72    }
73}
74
75impl<P: ?Sized> ProtectedArc<P> {
76    /// Make a new `ProtectedArc` from a raw `Arc`
77    ///
78    /// # CORRECTNESS
79    ///
80    /// Presumably the `Arc` came from an uncontrolled external source, such as user code.
81    pub(crate) fn new(arc: Arc<P>) -> Self {
82        let bomb = DropBomb::new_armed();
83        ProtectedArc { arc, bomb }
84    }
85
86    /// Obtain a `ProtectedWeak` from a `&ProtectedArc`
87    //
88    // If this were a more general-purpose library, we'd avoid this and other methods on `self`.
89    pub(crate) fn downgrade(&self) -> ProtectedWeak<P> {
90        ProtectedWeak(Arc::downgrade(&self.arc))
91    }
92
93    /// Convert back into an unprotected `Arc`
94    ///
95    /// # CORRECTNESS
96    ///
97    /// If the return value is dropped, the location must be suitable for that.
98    /// Or, maybe the returned value is going to calling code in the external user,
99    /// (which, therefore, wouldn't pose a reentrancy hazard).
100    pub(crate) fn promise_dropping_is_ok(mut self) -> Arc<P> {
101        self.bomb.disarm();
102        self.arc
103    }
104}
105
106#[cfg(test)]
107mod test {
108    // @@ begin test lint list maintained by maint/add_warning @@
109    #![allow(clippy::bool_assert_comparison)]
110    #![allow(clippy::clone_on_copy)]
111    #![allow(clippy::dbg_macro)]
112    #![allow(clippy::mixed_attributes_style)]
113    #![allow(clippy::print_stderr)]
114    #![allow(clippy::print_stdout)]
115    #![allow(clippy::single_char_pattern)]
116    #![allow(clippy::unwrap_used)]
117    #![allow(clippy::unchecked_duration_subtraction)]
118    #![allow(clippy::useless_vec)]
119    #![allow(clippy::needless_pass_by_value)]
120    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
121    #![allow(clippy::let_and_return)] // TODO this lint is annoying and we should disable it
122
123    use super::*;
124
125    struct Payload;
126
127    #[test]
128    fn fine() {
129        let arc = Arc::new(Payload);
130        let prot = ProtectedArc::new(arc);
131        let arc = prot.promise_dropping_is_ok();
132        drop(arc);
133    }
134
135    #[test]
136    fn bad() {
137        let arc = Arc::new(Payload);
138        let mut prot = ProtectedArc::new(arc);
139        let h = prot.bomb.make_simulated();
140        drop(prot);
141        h.expect_exploded();
142    }
143}