unwind safety fixes#140
Conversation
235fcfb to
45e29c0
Compare
7ad7985 to
225f13b
Compare
e45f41e to
e3b2cb6
Compare
| /// | ||
| /// Drops the already initialized elements of the array if an error or panic occurs | ||
| /// partway through the initialization process. | ||
| pub struct ArrayInit<T, F> { |
There was a problem hiding this comment.
| pub struct ArrayInit<T, F> { | |
| pub(crate) struct ArrayInit<T, F> { |
| unsafe { init.__init(ptr) }?; | ||
| self.num_init += 1; | ||
| } | ||
| core::mem::forget(self); |
There was a problem hiding this comment.
This incorrectly forgets F. You should just set self.ptr to null.
There was a problem hiding this comment.
Ouch. This is fixed, thank you for pointing it out.
| // SAFETY: Safety contract of `ArrayInit` guarantees that elements | ||
| // `self.ptr[0..self.num_init]` are initialized and contain valid `T` | ||
| // values, so dropping them is safe. |
There was a problem hiding this comment.
This should be represented as invariants of the type.
There was a problem hiding this comment.
I've added an Invariants section to ArrayInit docs, I hope it is acceptable.
| let val = unsafe { Pin::new_unchecked(val) }; | ||
| // SAFETY: `slot` was initialized above. | ||
| (self.1)(val).inspect_err(|_| unsafe { core::ptr::drop_in_place(slot) }) | ||
| (self.1)(val)?; |
There was a problem hiding this comment.
The whole implementation here can probably be simplified to something like
(self.1)(unsafe { Slot::new(slot) }.init(self.0).let_binding()) or something similar if #143 lands.
There was a problem hiding this comment.
I could rebase this branch to dev/accessor-rework if you'd like? Otherwise I don't mind waiting for #143 to land.
| } | ||
|
|
||
| impl<T, F> ArrayInit<T, F> { | ||
| /// # Safety |
There was a problem hiding this comment.
Use whatever you actually require for safety requirement. This function can probably not be unsafe at all.
There was a problem hiding this comment.
Removed unsafe from ArrayInit::new.
e3b2cb6 to
e5f75a9
Compare
|
I cannot review diff of diffs. Please always squash your changes to individually meaningful commits. |
e5f75a9 to
5c83836
Compare
|
I see, everything has been squashed to the original three commits. When reviewing, I prefer having diffs that do exactly what each of the comments suggests and squashing after the review. I will squash future fixups immediately. |
|
For GitHub-centric workflow that's indeed convenient, but for this project as the commits are synced to the kernel I need to review each commit individually so it's up to kernel's quality requirement. Honestly the actual issue is that GitHub does not provide a good way to visualize the diff, despite that it has all the needed information to render as such. The "Compare" button sometimes does the job, but it breaks when you rebase and fixup commits in a single push. Longer term, either we can wait until GitHub implements it (which is quite unlikely given the slop), or we can adopt what the Rust Project's triagebot does by automatically generates a range diff like this. |
|
I understand and fully agree. Unfortunately, looking at recent trends at GitHub, I don't expect much in terms of improvements to the platform. Thank you for taking the time to explain your reasoning. :) |
Adds a guard type that safely initializes an array by running an initializer on each element, keeping track of the number of initialized elements. In the case of a panic or error in the per-element initializer, the guard drops the already-initialized portion of the array; nothing is dropped on success. 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. Link: Rust-for-Linux#136 Reported-by: Gary Guo <gary@garyguo.net> Suggested-by: Gary Guo <gary@garyguo.net> Signed-off-by: Mirko Adzic <adzicmirko97@gmail.com>
Adds a drop guard before the call to the chained closure so that the value initialized by the first stage is dropped if the closure errors or panics; `mem::forget` the guard on success. The previous code only ran cleanup on the explicit error path, leaking the first-stage value if the chained closure panicked. Link: Rust-for-Linux#136 Reported-by: Gary Guo <gary@garyguo.net> Suggested-by: Gary Guo <gary@garyguo.net> Signed-off-by: Mirko Adzic <adzicmirko97@gmail.com>
Cover both fixes added in the series: - `[pin_]init_array_from_fn`: a panic or error from element `i`'s initializer drops the previously initialized elements `0..i`. - `[pin_]chain`: a panic or error from the chained closure drops the value initialized by the first stage. Also assert no double-drop on the success paths. Signed-off-by: Mirko Adzic <adzicmirko97@gmail.com>
5c83836 to
eebe948
Compare
| /// - `ptr` is null: | ||
| /// - before `__init` or `__pinned_init` is called | ||
| /// - after the array is successfully initialized | ||
| /// - `ptr[0..num_init]` contains initialized elements of type `T` | ||
| /// - `ptr[num_init..N]` (where N is the size of the array) contains uninitialized memory |
There was a problem hiding this comment.
| /// - `ptr` is null: | |
| /// - before `__init` or `__pinned_init` is called | |
| /// - after the array is successfully initialized | |
| /// - `ptr[0..num_init]` contains initialized elements of type `T` | |
| /// - `ptr[num_init..N]` (where N is the size of the array) contains uninitialized memory | |
| /// If `ptr` is not null: | |
| /// - `ptr[0..num_init]` contains initialized elements of type `T` | |
| /// - `ptr[num_init..N]` (where N is the size of the array) contains uninitialized memory |
| } | ||
|
|
||
| impl<T, F> ArrayInit<T, F> { | ||
| pub(crate) fn new(make_init: F) -> Self { |
| // SAFETY: Since `0 <= i < N`, `self.ptr.add(i)` is in bounds and | ||
| // valid for writes by the safety contract of `__init`. |
There was a problem hiding this comment.
This is not calling __init yet. So just mention about inbounds.
|
|
||
| impl<T, F> ArrayInit<T, F> { | ||
| pub(crate) fn new(make_init: F) -> Self { | ||
| Self { |
| // SAFETY: The pointer is derived from `slot` and thus satisfies the | ||
| // `__init` requirements. | ||
| unsafe { init.__init(ptr) }?; | ||
| self.num_init += 1; |
There was a problem hiding this comment.
I'd actually either put self.num_init = i; at the beginning of the loop, or explicitly specify self.num_init = 0 at the beginning of the function.
| // valid for writes by the safety contract of `__init`. | ||
| let ptr = unsafe { self.ptr.add(i) }; | ||
| // SAFETY: The pointer is derived from `slot` and thus satisfies the | ||
| // `__init` requirements. |
There was a problem hiding this comment.
This needs to be more explicit and refers to the safety requirements of Init::__init.
| F: FnMut(usize) -> I, | ||
| I: PinInit<T, E>, | ||
| { | ||
| unsafe fn __pinned_init(mut self, slot: *mut [T; N]) -> Result<(), E> { |
| // SAFETY: Invariants of `ArrayInit` guarantee that elements | ||
| // `self.ptr[0..self.num_init]` are initialized and contain valid `T` | ||
| // values, so dropping them is safe. |
There was a problem hiding this comment.
Perhaps also mention before the safety comment that "In this code path, the initialization has failed partway and we need to drop elements initialized thus far to uphold the pinning requirement."
| static DROPS: AtomicUsize = AtomicUsize::new(0); | ||
| /// Serialize tests so the shared [DROPS] counter stays meaningful even | ||
| /// when cargo runs tests in parallel. | ||
| static LOCK: Mutex<()> = Mutex::new(()); |
There was a problem hiding this comment.
Could you make Counted something like:
struct Counted<'a>(&'a AtomicUsize);to avoid things like this?
|
|
||
| impl Drop for Counted { | ||
| fn drop(&mut self) { | ||
| DROPS.fetch_add(1, Ordering::SeqCst); |
Adds unwind-safety fixes to in-place initialization helpers so partially initialized values are dropped on both errors and panics. Changes:
__internal::ArrayInitguard type that safely initializes an array by running an initializer on each element, keeping track of the number of initialized elements. When dropped, if the initialization was partially successful, the guard drops the initialized portion of the array. Use the guard in[pin_]init_array_from_fn.__internal::DropGuardin[pin_]chainso a panic/error in the chained closure drops the value initialized in the first stage;mem::forgetthe guard on success.Closes: #136
The approach taken here was suggested in the issue itself, thus I added a
Suggested-bytag. I hope that is okay.Edits:
v1 -> v2:
ArrayInitGuardtoArrayInit[Pin]InitforArrayInitinstead of wrapping it in[pin_]init_from_closurev2 -> v3:
mem::forgettheArrayInitguard on success; state that the guard will only drop initialized elements on partial success