vstd_extra/drop_tracking.rs
1use core::{marker::PhantomData, ops::Deref};
2use vstd::prelude::*;
3use vstd::resource::Loc;
4
5use crate::resource::ghost_resource::excl::ExclusiveGhost;
6
7verus! {
8
9/// A linear-looking "must drop" obligation tied to a value of type `R`.
10///
11/// `R` is the *key* type used to identify the resource in the State's
12/// obligation ledger — e.g. `Range<Paddr>` for a `Segment<M>`, `usize` for a
13/// per-slot resource, `()` for impls that pipe the token through without a
14/// per-instance ledger. Two-layer enforcement (the design that survives
15/// Verus's affineness):
16///
17/// 1. **Token (this type)**: an `ExclusiveGhost<R>` wrapper. Each `alloc`
18/// produces a unique [`Loc`]; two outstanding tokens can be proven
19/// distinct via [`DropObligation::validate_with_other`]. No external_body
20/// axioms — the allocation is verified by `vstd`'s resource algebra.
21/// 2. **Ledger (in State)**: optional. For impls that opt in, the State
22/// carries a set/multiset of outstanding keys; mint adds, redeem removes,
23/// and the State's `clean_inv()` (or any boundary predicate) requires the
24/// set to be empty (or the per-slot counter to be zero). Combined with
25/// `1`, the linear guarantee is sound: silently dropping the token leaves
26/// the ledger non-empty and breaks the boundary check.
27///
28/// Impls that don't add a per-state ledger still pass tokens through —
29/// they get the API uniformity but not the enforcement.
30#[verifier::reject_recursive_types(R)]
31pub tracked struct DropObligation<R> {
32 inner: ExclusiveGhost<R>,
33}
34
35impl<R> DropObligation<R> {
36 pub closed spec fn value(self) -> R {
37 self.inner.view()
38 }
39
40 /// Unique identifier of this token. Two outstanding `DropObligation`s
41 /// can be proven distinct via [`Self::validate_with_other`].
42 pub closed spec fn id(self) -> Loc {
43 self.inner.id()
44 }
45
46 /// Mint a fresh obligation token. Sound on its own (no axiom — the
47 /// allocation goes through `vstd`'s resource algebra and is verified).
48 /// Two legitimate uses:
49 ///
50 /// - Inside an impl that opts out of per-state-ledger enforcement
51 /// (e.g. `Key = ()` impls): the token is a no-op marker; the trait
52 /// API stays uniform but no `clean_inv()` enforcement applies.
53 /// - As the underlying allocator for a paired mint axiom on
54 /// [`HasObligations`] — the axiom adds the ledger entry alongside.
55 pub proof fn tracked_mint(value: R) -> (tracked res: DropObligation<R>)
56 ensures
57 res.value() == value,
58 {
59 DropObligation { inner: ExclusiveGhost::alloc(value) }
60 }
61
62 /// Two outstanding obligations have distinct `Loc`s.
63 pub proof fn validate_with_other(tracked &mut self, tracked other: &Self)
64 ensures
65 *old(self) == *final(self),
66 final(self).id() != other.id(),
67 {
68 self.inner.validate_with_other(&other.inner);
69 }
70}
71
72/// State that carries an obligation ledger keyed by `K`.
73pub trait HasObligations<K> {
74 spec fn obligations(self) -> Set<K>;
75}
76
77pub trait TrackDrop: Sized {
78 type State;
79
80 /// Identifies which obligation this resource holds in the ledger.
81 type Key;
82
83 /// The ledger key for *this* instance. Pinned by
84 /// `constructor_spec`'s ensures and `Drop::drop`'s requires.
85 spec fn key(self) -> Self::Key;
86
87 spec fn constructor_requires(self, s: Self::State) -> bool;
88
89 spec fn constructor_ensures(self, s0: Self::State, s1: Self::State, obl_key: Self::Key) -> bool;
90
91 proof fn constructor_spec(self, tracked s: &mut Self::State) -> (tracked obl: DropObligation<
92 Self::Key,
93 >)
94 requires
95 self.constructor_requires(*old(s)),
96 ensures
97 self.constructor_ensures(*old(s), *final(s), obl.value()),
98 obl.value() == self.key(),
99 ;
100
101 spec fn drop_requires(self, s: Self::State) -> bool;
102
103 /// Postcondition of [`Drop::drop`]. `obl_id` is the [`Loc`] of the
104 /// consumed obligation token — ledger-enforcing impls reference it
105 /// to pin the ledger transition (e.g. "the entry at `obl_id` was
106 /// removed"); ledger-less impls ignore it.
107 spec fn drop_ensures(self, s0: Self::State, s1: Self::State, obl_key: Self::Key) -> bool;
108
109 /// Precondition for "consume the obligation without running the
110 /// destructor" (see [`ConsumeDrop`]). `obl_id` is exposed so
111 /// ledger-enforcing impls can require the entry to be outstanding.
112 spec fn consume_requires(self, s: Self::State, obl_key: Self::Key) -> bool;
113
114 /// Postcondition for consuming an obligation. Encodes any ledger
115 /// mutation (e.g. redeeming the entry at `obl_id`). Impls without
116 /// a ledger keep `s0 == s1`.
117 spec fn consume_ensures(self, s0: Self::State, s1: Self::State, obl_key: Self::Key) -> bool;
118
119 /// Consume the obligation token without running the destructor body.
120 /// Used by [`ConsumeDrop::new`] to discharge the verifier's
121 /// linear-drop obligation when the underlying resource is
122 /// intentionally leaked (or freed via a separate path).
123 ///
124 /// Impls with a real ledger should call a paired redeem axiom here;
125 /// impls without (`Key = ()`) can leave the body empty — `obl` is
126 /// silently dropped, which is fine because no ledger entry needs to
127 /// shrink to match.
128 proof fn consume_obligation(
129 self,
130 tracked s: &mut Self::State,
131 tracked obl: DropObligation<Self::Key>,
132 )
133 requires
134 self.consume_requires(*old(s), obl.value()),
135 obl.value() == self.key(),
136 ensures
137 self.consume_ensures(*old(s), *final(s), obl.value()),
138 ;
139}
140
141pub trait Drop: TrackDrop {
142 fn drop(
143 self,
144 Tracked(s): Tracked<&mut Self::State>,
145 Tracked(obl): Tracked<DropObligation<Self::Key>>,
146 )
147 requires
148 // The body must call `self.consume_obligation(s, obl)` first
149 // (redeeming the token / shrinking the ledger), then run the
150 // destructor work. Both preconditions are required up front.
151
152 self.consume_requires(*old(s), obl.value()),
153 self.drop_requires(*old(s)),
154 obl.value() == self.key(),
155 ensures
156 self.drop_ensures(*old(s), *final(s), obl.value()),
157 ;
158}
159
160/// Linear-drop obligation wrapper. `ManuallyDrop::new(t, regions)`
161/// **consumes** the State-side obligation entry for `t.key()` (via
162/// `T::consume_obligation`) and wraps the value. The wrapper carries only
163/// the value — no embedded obligation — and can be silently dropped
164/// affinely; the linear-drop guarantee comes from the State-side ledger.
165///
166/// The precondition `consume_requires` (e.g. `frame_obligations.count(idx)
167/// > 0` for Frame) is the load-bearing safety check: callers must
168/// establish an outstanding obligation entry at `t.key()`. Producers
169/// like `Frame::from_raw`, `Frame::clone`, `Frame::from_unused`,
170/// `Frame::from_in_use` mint that entry. The mint + consume pair is
171/// net-zero on the ledger — "the borrow ends here."
172///
173/// # Unsoundness warning
174///
175/// **It is unsound to extract the inner `T` from `ManuallyDrop<T>` via
176/// `take`/`into_inner`-style operations** without minting a fresh
177/// obligation at the extraction site. A `ManuallyDrop<T>` carries no
178/// obligation, so the extracted `T` would have none either — but
179/// `T::drop` (e.g. `Frame::drop`) requires an obligation as input, so the
180/// extracted value cannot legally be dropped. Any extraction site must
181/// mint a fresh entry into the State-side ledger, gated by a soundness
182/// justification (typically `ref_count >= 1` for `MD<Frame>`, mirroring
183/// `Frame::from_raw`'s safety condition).
184///
185/// At the time of this redesign no ostd callsite extracts a `Frame` from
186/// a `ManuallyDrop<Frame>` (only `Deref` borrows are taken; the one
187/// `into_inner` is on `MD<Arc<T>>`, not `MD<Frame>`). Adding such an
188/// extraction without the matching mint resurrects the double-counting
189/// bug that motivated this redesign.
190pub struct ManuallyDrop<T: TrackDrop>(pub T);
191
192impl<T: TrackDrop> ManuallyDrop<T> {
193 #[verifier::external_body]
194 pub fn new(t: T, Tracked(s): Tracked<&mut T::State>) -> (res: Self)
195 requires
196 t.consume_requires(*old(s), t.key()),
197 ensures
198 t.consume_ensures(*old(s), *final(s), t.key()),
199 res.0 == t,
200 {
201 proof {
202 // Materialize a ledger-less identity token for `t.key()` and
203 // immediately discharge it via `T::consume_obligation`. The
204 // caller's `consume_requires` precondition guarantees there
205 // is an outstanding ledger entry at this key for the redeem
206 // axiom to remove.
207 let tracked obl = DropObligation::tracked_mint(t.key());
208 t.consume_obligation(s, obl);
209 }
210 Self(t)
211 }
212}
213
214impl<T: TrackDrop> Deref for ManuallyDrop<T> {
215 type Target = T;
216
217 #[verifier::external_body]
218 fn deref(&self) -> (res: &Self::Target)
219 ensures
220 res == &self.0,
221 {
222 &self.0
223 }
224}
225
226impl<T: TrackDrop> View for ManuallyDrop<T> {
227 type V = T;
228
229 open spec fn view(&self) -> (res: Self::V) {
230 self.0
231 }
232}
233
234} // verus!