Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify safety properties of casts between [MaybeUninit<T>] <-> [T]. #431

Merged
merged 1 commit into from
May 29, 2024

Conversation

briansmith
Copy link
Contributor

@briansmith briansmith commented May 22, 2024

Reduce the scope of the unsafe blocks to the unsafe operations.

@briansmith briansmith force-pushed the b/avoid-unsized-casts branch 2 times, most recently from 669b857 to 215d402 Compare May 22, 2024 22:11
@briansmith briansmith changed the title Replace casts of slices (unsized types) with slice::from_raw_parts[_mut](). Clarify safety properties of casts between [MaybeUninit<T>] <-> [T]. May 22, 2024
@briansmith briansmith force-pushed the b/avoid-unsized-casts branch 2 times, most recently from fd6209d to 017a7db Compare May 22, 2024 22:37
src/util.rs Outdated
let ptr: *mut [MaybeUninit<T>] = slice;
// SAFETY: `MaybeUninit<T>` is guaranteed to be layout-compatible with `T`
// and casting between two slice types of layout-compatible types is sound.
let ptr = ptr as *mut [T];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think let ptr = slice as *mut [MaybeUninit<T>] as *mut [T] would be a bit easier to understand than let ptr: *mut [MaybeUninit<T>] = slice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree doing it on one line is more reasonable, we also probably don't need the first SAFETY comment, as it confuses things. It is always legal to cast from a *mut [A] to a *mut [B] (where A and B are sized). It's dereferencing where we need to be sure that:

  • The pointer is aligned
  • The range [ptr,ptr+len) is exclusive and valid for reads/writes
  • The bytes are initialized

So something like:

    let ptr = slice as *mut [MaybeUninit<T>] as *mut [T];
    // SAFETY: As `MaybeUninit<T>` is layout compatible with `T`, `ptr` is
    // appropriately aligned and points to the same memory as `slice`. As `ptr`
    // was created from a valid `&mut` reference and the caller ensures every
    // element is initialized, it is safe to dereference `ptr`.
    unsafe { &mut *ptr }

src/util.rs Outdated
let ptr: *mut [MaybeUninit<T>] = slice;
// SAFETY: `MaybeUninit<T>` is guaranteed to be layout-compatible with `T`
// and casting between two slice types of layout-compatible types is sound.
let ptr = ptr as *mut [T];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree doing it on one line is more reasonable, we also probably don't need the first SAFETY comment, as it confuses things. It is always legal to cast from a *mut [A] to a *mut [B] (where A and B are sized). It's dereferencing where we need to be sure that:

  • The pointer is aligned
  • The range [ptr,ptr+len) is exclusive and valid for reads/writes
  • The bytes are initialized

So something like:

    let ptr = slice as *mut [MaybeUninit<T>] as *mut [T];
    // SAFETY: As `MaybeUninit<T>` is layout compatible with `T`, `ptr` is
    // appropriately aligned and points to the same memory as `slice`. As `ptr`
    // was created from a valid `&mut` reference and the caller ensures every
    // element is initialized, it is safe to dereference `ptr`.
    unsafe { &mut *ptr }

src/util.rs Outdated
Comment on lines 28 to 25
let ptr: *const [T] = slice;
// SAFETY: `MaybeUninit<T>` is guaranteed to be layout-compatible with `T`
// and casting between two slice types of layout-compatible types is sound.
let ptr = ptr as *const [MaybeUninit<T>];
// SAFETY:
// * ptr was soundly constructed from a valid reference so it safe to
// dereference it.
// * There is no risk of writing a `MaybeUninit<T>` into the result
// since the result isn't mutable.
unsafe { &*ptr }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps:

    let ptr = slice as *const [T] as *const [MaybeUninit<T>];
    // SAFETY: As `MaybeUninit<T>` is layout compatible with `T`, `ptr` is
    // appropriately aligned and points to the same memory as `slice`. As `ptr`
    // was created from a valid reference, any `T` is a valid `MaybeUninit<T>`,
    // and the return type is immutable, it is safe to dereference `ptr`.
    unsafe { &*ptr }

Comment on lines 42 to 31
/// This is unsafe because it allows assigning uninitialized values into
/// `slice`, which would be undefined behavior.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should update this to be:

The caller ensures that the return slice will never be uninitialized.

src/util.rs Outdated
Comment on lines 47 to 37
let ptr: *mut [T] = slice;
// SAFETY: `MaybeUninit<T>` is guaranteed to be layout-compatible with `T`
// and casting between two slice types of layout-compatible types is sound.
let ptr = ptr as *mut [MaybeUninit<T>];
// SAFETY:
// * ptr was soundly constructed from a valid reference so it safe to
// dereference it.
// * There *IS* a risk of writing a `MaybeUninit<T>` into the result,
// which is why this function is unsafe.
unsafe { &mut *ptr }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then this implementation can be:

    let ptr = slice as *mut [T] as *mut [MaybeUninit<T>];
    // SAFETY: As `MaybeUninit<T>` is layout compatible with `T`, `ptr` is
    // appropriately aligned and points to the same memory as `slice`. As `ptr`
    // was created from a valid `&mut` reference, any `T` is a valid
    // `MaybeUninit<T>`, and the caller ensures the returned reference will not
    // be uninitialized, it is safe to dereference `ptr`.
    unsafe { &*ptr }

@briansmith briansmith force-pushed the b/avoid-unsized-casts branch from 017a7db to d11265a Compare May 28, 2024 18:47
Reduce the scope of the `unsafe` blocks to the unsafe operations.
@briansmith briansmith force-pushed the b/avoid-unsized-casts branch from d11265a to 534fd68 Compare May 28, 2024 18:51
@briansmith
Copy link
Contributor Author

I updated this with the following changes:

  • Use polyfills for core::ptr::{from_ref, from_mut} instead of the unnecessary casts we were using before. My overall goal with this series of PRs is to remove all as casts in favor of safer alternatives, as much as practical. I agree that the implicit coersion, though safer than a cast, is unfortunate because it requires a separate statement. Using from_ref/from_mut alleviates this tension.

  • Undo all the changes to the safety comments. I appreciate the suggestions on how to improve the safety comments I tried to write. Ultimately I think less is more here. What really matters regarding the safety of these functions is the validity of the type coersions and casts. "Proper" safety comments just completely obfuscate this and distract the reader from focusing on the correctness of the type conversions.

Copy link
Member

@josephlr josephlr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fixes @briansmith. Agreed w.r.t. to the shorter SAFETY comments

@josephlr josephlr merged commit 0afee65 into rust-random:master May 29, 2024
51 checks passed
@briansmith briansmith deleted the b/avoid-unsized-casts branch May 29, 2024 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants