Skip to main content

aligned_vmem/api/
release.rs

1use core::ptr::NonNull;
2
3#[cfg(aligned_vmem_mock)]
4use crate::mock;
5use crate::os::release_reservation;
6use crate::page::PAGE;
7use crate::reservation_parts::ReservationParts;
8
9/// Release a whole OS reservation obtained from [`Reservation::into_parts`](crate::Reservation::into_parts).
10///
11/// # Safety
12///
13/// `reservation`, `reservation_len` and `align` must be the three values
14/// returned by [`Reservation::into_parts`](crate::Reservation::into_parts) (or, for a self-hosting caller that
15/// always uses one alignment, that same alignment constant), and the
16/// reservation must be released **exactly once**. The native (`munmap` /
17/// `VirtualFree`) paths ignore `align`; it is consulted only by the miri
18/// fallback to reconstruct the exact `Layout`.
19///
20/// If `reservation` is null, this function returns early and does nothing
21/// (the call is a no-op). The mock recorder is also skipped in this case,
22/// so a `mock`-based test's expected call log may desync if it expects a
23/// record for a null pointer.
24///
25/// # Panics
26///
27/// Panics if `reservation` is non-null and `(reservation_len, align)` violates
28/// the documented contract above: `reservation_len` must be non-zero and a
29/// multiple of [`PAGE`], `align` must be a power of two `>= PAGE`, and the
30/// pair must form a valid [`std::alloc::Layout`]. The assert runs before
31/// `mock::record`, so under the `aligned_vmem_mock` cfg a contract-violating
32/// call panics before it is ever recorded in the mock call log — it does not
33/// appear as a `Release` entry.
34///
35/// A null `reservation` is unaffected by this: it remains the documented
36/// no-op above and is not a panic path.
37pub unsafe fn release(reservation: *mut u8, reservation_len: usize, align: usize) {
38    // Historical note (task #947/G-1): before this assert existed, this doc
39    // comment used to claim "the native (`munmap`/`VirtualFree`) paths ignore
40    // `align`" — which was true in the sense that a contract-violating call
41    // would silently "succeed" (no crash, no error) on those native backends;
42    // only the `miri` fallback path (which reconstructs a `Layout` from
43    // `reservation_len`/`align` to call back into `std::alloc`) would panic on
44    // the same bad input, with a bare, uninformative `.expect()` message. That
45    // divergence is now closed: this function validates the contract up front
46    // and panics with a descriptive message on **every** backend, not only
47    // under `miri`. The assert runs before `mock::record`, so under the
48    // `aligned_vmem_mock` cfg a contract-violating call panics before it is
49    // ever recorded in the mock call log — it does not appear as a `Release`
50    // entry.
51    //
52    // The checked invariants are a subset of `from_raw_parts`'s checks because
53    // `release` receives only `(reservation_len, align)` (not the full
54    // `(base, len, reservation, reservation_len, align)` tuple), so the bounds
55    // between `base` and `reservation` are uncheckable here — we validate what
56    // we can and keep the same informative message style.
57    if reservation.is_null() {
58        return;
59    }
60    assert!(
61        reservation_len != 0
62            && reservation_len.is_multiple_of(PAGE)
63            && align.is_power_of_two()
64            && align >= PAGE
65            && std::alloc::Layout::from_size_align(reservation_len, align).is_ok(),
66        "release: \
67         reservation_len must be non-zero and a multiple of PAGE; \
68         align must be a power of two >= PAGE; \
69         (reservation_len, align) must form a valid Layout; \
70         got reservation_len={reservation_len}, align={align}"
71    );
72
73    let nn = NonNull::new(reservation).expect("checked non-null above");
74    #[cfg(aligned_vmem_mock)]
75    mock::record(mock::Call::Release {
76        reservation: reservation.addr(),
77        reservation_len,
78    });
79    // SAFETY: forwarded from the caller's contract above.
80    unsafe { release_reservation(nn, reservation_len, align) };
81}
82
83/// Release a reservation obtained from [`Reservation::into_reservation_parts`](crate::Reservation::into_reservation_parts).
84///
85/// This is the typed alternative to [`release`]: it takes a [`ReservationParts`]
86/// struct instead of raw parameters, preventing accidental swapping of `len` and
87/// `align` (which would cause undefined behavior on the native backend and leaks
88/// or crashes on Unix).
89///
90/// For backwards compatibility with code that uses the raw tuple form, you can
91/// convert a `ReservationParts` to a tuple via [`ReservationParts::as_tuple`] and
92/// call [`release`].
93///
94/// # Safety
95///
96/// `parts.ptr` must be a reservation obtained from [`Reservation::into_reservation_parts`](crate::Reservation::into_reservation_parts)
97/// (or the raw [`Reservation::into_parts`](crate::Reservation::into_parts)) and must be live. The reservation must be released
98/// exactly once.
99pub unsafe fn release_parts(parts: ReservationParts) {
100    let ReservationParts {
101        ptr: reservation,
102        len: reservation_len,
103        align,
104    } = parts;
105    // Delegate to the existing release function.
106    unsafe { release(reservation, reservation_len, align) };
107}