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}