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

            
30
use crate::internal_prelude::*;
31

            
32
/// A `Weak<P>`, but upgradeable only to a `ProtectedArc`, not a raw `Arc`.
33
#[derive(Debug)]
34
pub(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)]
43
pub(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

            
53
impl<P: ?Sized> ProtectedWeak<P> {
54
    /// Make a new `ProtectedWeak`
55
332
    pub(crate) fn new(p: Weak<P>) -> Self {
56
332
        ProtectedWeak(p)
57
332
    }
58

            
59
    /// Upgrade a `ProtectedWeak` to a `ProtectedArc`, if it's not been garbage collected
60
212
    pub(crate) fn upgrade(&self) -> Option<ProtectedArc<P>> {
61
212
        Some(ProtectedArc::new(self.0.upgrade()?))
62
212
    }
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
164
    pub(crate) fn unprotect(self) -> Weak<P> {
71
164
        self.0
72
164
    }
73
}
74

            
75
impl<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
376
    pub(crate) fn new(arc: Arc<P>) -> Self {
82
376
        let bomb = DropBomb::new_armed();
83
376
        ProtectedArc { arc, bomb }
84
376
    }
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
164
    pub(crate) fn downgrade(&self) -> ProtectedWeak<P> {
90
164
        ProtectedWeak(Arc::downgrade(&self.arc))
91
164
    }
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
374
    pub(crate) fn promise_dropping_is_ok(mut self) -> Arc<P> {
101
374
        self.bomb.disarm();
102
374
        self.arc
103
374
    }
104
}
105

            
106
#[cfg(test)]
107
mod 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
}