From: Gary Guo <gary@garyguo.net>
To: Benno Lossin <lossin@kernel.org>, Miguel Ojeda <ojeda@kernel.org>
Cc: "Boqun Feng" <boqun@kernel.org>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
"Gary Guo" <gary@garyguo.net>,
"Mirko Adzic" <adzicmirko97@gmail.com>
Subject: [PATCH 6/7] rust: pin-init: make `[pin_]init_array_from_fn` unwind safe
Date: Fri, 10 Jul 2026 17:20:36 +0100 [thread overview]
Message-ID: <20260710-pin-init-sync-v1-6-8fa16cde87ae@garyguo.net> (raw)
In-Reply-To: <20260710-pin-init-sync-v1-0-8fa16cde87ae@garyguo.net>
From: Mirko Adzic <adzicmirko97@gmail.com>
The previous code only ran cleanup on the explicit error path. If the per-
element initializer panicked partway through, the elements already written
into the array would be leaked: their `Drop` impls would never run. This
violates the pinning requirement.
Fix the unwind safety issue by adding a guard type that drops element on
both error and panic path.
To avoid having to duplicate code between `pin_init_array_from_fn` and the
non-pin variant, extract the code to a shared `ArrayInit` type; this type
is internal and not visible via API.
Reported-by: Gary Guo <gary@garyguo.net>
Closes: https://github.com/Rust-for-Linux/pin-init/issues/136
Signed-off-by: Mirko Adzic <adzicmirko97@gmail.com>
[ Split guard type and the initializer type, move the guard type to be
within __pinned_init. - Gary ]
Co-developed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/pin-init/src/lib.rs | 122 +++++++++++++++++++++++++++++++----------------
1 file changed, 80 insertions(+), 42 deletions(-)
diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs
index 90e9d501d44a..3fc4a674a487 100644
--- a/rust/pin-init/src/lib.rs
+++ b/rust/pin-init/src/lib.rs
@@ -1186,6 +1186,82 @@ pub fn uninit<T, E>() -> impl Init<MaybeUninit<T>, E> {
unsafe { init_from_closure(|_| Ok(())) }
}
+/// Array initializer from element initializer.
+struct ArrayInit<T: ?Sized, F>(F, __internal::PhantomInvariant<T>);
+
+// SAFETY: On success, all `N` elements of the array have been initialized. On error or panic, the
+// elements that have been initialized so far are dropped, thus leaving the array uninitialized and
+// ready to deallocate.
+unsafe impl<T, F, I, E, const N: usize> PinInit<[T; N], E> for ArrayInit<T, F>
+where
+ F: FnMut(usize) -> I,
+ I: PinInit<T, E>,
+{
+ unsafe fn __pinned_init(mut self, slot: *mut [T; N]) -> Result<(), E> {
+ /// # Invariants
+ ///
+ /// - `ptr[..num_init]` contains initialized elements of type `T`
+ /// - `ptr[num_init..N]` (where N is the size of the array) contains uninitialized memory
+ struct ArrayInitGuard<T> {
+ /// A pointer to the first element of the array.
+ ptr: *mut T,
+ /// The number of initialized elements in the array.
+ num_init: usize,
+ }
+
+ impl<T> Drop for ArrayInitGuard<T> {
+ #[inline]
+ fn drop(&mut self) {
+ // SAFETY: Per type invariant, `self.ptr[..self.num_init]` are initialized.
+ unsafe {
+ core::ptr::drop_in_place(core::ptr::slice_from_raw_parts_mut(
+ self.ptr,
+ self.num_init,
+ ))
+ };
+ }
+ }
+
+ // INVARIANT: nothing is initialized yet.
+ let mut guard = ArrayInitGuard {
+ ptr: slot.cast::<T>(),
+ num_init: 0,
+ };
+
+ for i in 0..N {
+ // INVARIANT: Elements `self.ptr[..self.num_init]` have been initialized
+ // thus far. This holds true for every `self.num_init = i`.
+ guard.num_init = i;
+
+ let init = (self.0)(i);
+ // SAFETY:
+ // - The subslot is derived from `slot` with a valid offset.
+ // - If `Err` is touched, the subslot is not touched further, the guard will drop
+ // previously initialized elements only.
+ // - `slot` is pinned so is the subslot.
+ unsafe { init.__pinned_init(&raw mut (*slot)[i]) }?;
+ }
+
+ // Dismiss the drop guard now that all elements are initialized.
+ core::mem::forget(guard);
+ Ok(())
+ }
+}
+
+// SAFETY: Follows the `PinInit` impl. `__init` executes the same code as `__pinned_init`.
+unsafe impl<T, F, I, E, const N: usize> Init<[T; N], E> for ArrayInit<T, F>
+where
+ F: FnMut(usize) -> I,
+ I: Init<T, E>,
+{
+ #[inline(always)]
+ unsafe fn __init(self, slot: *mut [T; N]) -> Result<(), E> {
+ // SAFETY: `I: Init` cancels out the pinning requirement on subslots. The other safety
+ // requirements follow that of `__init`.
+ unsafe { self.__pinned_init(slot) }
+ }
+}
+
/// Initializes an array by initializing each element via the provided initializer.
///
/// # Examples
@@ -1197,31 +1273,12 @@ pub fn uninit<T, E>() -> impl Init<MaybeUninit<T>, E> {
/// assert_eq!(array.len(), 1_000);
/// ```
pub fn init_array_from_fn<I, const N: usize, T, E>(
- mut make_init: impl FnMut(usize) -> I,
+ make_init: impl FnMut(usize) -> I,
) -> impl Init<[T; N], E>
where
I: Init<T, E>,
{
- let init = move |slot: *mut [T; N]| {
- let slot = slot.cast::<T>();
- for i in 0..N {
- let init = make_init(i);
- // SAFETY: Since 0 <= `i` < N, it is still in bounds of `[T; N]`.
- let ptr = unsafe { slot.add(i) };
- // SAFETY: The pointer is derived from `slot` and thus satisfies the `__init`
- // requirements.
- if let Err(e) = unsafe { init.__init(ptr) } {
- // SAFETY: The loop has initialized the elements `slot[0..i]` and since we return
- // `Err` below, `slot` will be considered uninitialized memory.
- unsafe { ptr::drop_in_place(ptr::slice_from_raw_parts_mut(slot, i)) };
- return Err(e);
- }
- }
- Ok(())
- };
- // SAFETY: The initializer above initializes every element of the array. On failure it drops
- // any initialized elements and returns `Err`.
- unsafe { init_from_closure(init) }
+ ArrayInit(make_init, __internal::PhantomInvariant::new())
}
/// Initializes an array by initializing each element via the provided initializer.
@@ -1240,31 +1297,12 @@ pub fn init_array_from_fn<I, const N: usize, T, E>(
/// assert_eq!(array.len(), 1_000);
/// ```
pub fn pin_init_array_from_fn<I, const N: usize, T, E>(
- mut make_init: impl FnMut(usize) -> I,
+ make_init: impl FnMut(usize) -> I,
) -> impl PinInit<[T; N], E>
where
I: PinInit<T, E>,
{
- let init = move |slot: *mut [T; N]| {
- let slot = slot.cast::<T>();
- for i in 0..N {
- let init = make_init(i);
- // SAFETY: Since 0 <= `i` < N, it is still in bounds of `[T; N]`.
- let ptr = unsafe { slot.add(i) };
- // SAFETY: The pointer is derived from `slot` and thus satisfies the `__init`
- // requirements.
- if let Err(e) = unsafe { init.__pinned_init(ptr) } {
- // SAFETY: The loop has initialized the elements `slot[0..i]` and since we return
- // `Err` below, `slot` will be considered uninitialized memory.
- unsafe { ptr::drop_in_place(ptr::slice_from_raw_parts_mut(slot, i)) };
- return Err(e);
- }
- }
- Ok(())
- };
- // SAFETY: The initializer above initializes every element of the array. On failure it drops
- // any initialized elements and returns `Err`.
- unsafe { pin_init_from_closure(init) }
+ ArrayInit(make_init, __internal::PhantomInvariant::new())
}
/// Construct an initializer in a closure and run it.
--
2.54.0
next prev parent reply other threads:[~2026-07-10 16:21 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-10 16:20 [PATCH 0/7] rust: pin-init: upstream synchronization for 7.3 (round 1) Gary Guo
2026-07-10 16:20 ` [PATCH 1/7] rust: pin-init: internal: error on duplicate `#[pin]` attribute Gary Guo
2026-07-10 16:20 ` [PATCH 2/7] rust: pin-init: examples: fix incorrect drop Gary Guo
2026-07-10 16:20 ` [PATCH 3/7] rust: pin-init: remove redundant clippy expects in doc tests Gary Guo
2026-07-10 16:20 ` [PATCH 4/7] rust: pin-init: internal: stop using `expect` in macro expansion Gary Guo
2026-07-10 16:20 ` [PATCH 5/7] rust: pin-init: internal: generate brace in macro for init code blocks Gary Guo
2026-07-10 16:20 ` Gary Guo [this message]
2026-07-10 16:20 ` [PATCH 7/7] rust: pin-init: make `[pin_]chain` unwind safe Gary Guo
2026-07-13 11:50 ` [PATCH 0/7] rust: pin-init: upstream synchronization for 7.3 (round 1) Gary Guo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260710-pin-init-sync-v1-6-8fa16cde87ae@garyguo.net \
--to=gary@garyguo.net \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=adzicmirko97@gmail.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox