-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TODO: - Use in zerocopy (maybe in a follow-up commit?) - Fill out `Cargo.toml` with more fields - Include `build.rs` to enable version detection so we can support `as_mut` as a `const fn` on recent toolchains - Confirm that it's sound to derive `Copy` and `Clone` Closes #1931 gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
- Loading branch information
Showing
3 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Copyright 2024 The Fuchsia Authors | ||
# | ||
# Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 | ||
# <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT | ||
# license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. | ||
# This file may not be copied, modified, or distributed except according to | ||
# those terms. | ||
|
||
[package] | ||
name = "unsafe-fields" | ||
version = "0.1.0" | ||
edition = "2021" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,337 @@ | ||
// Copyright 2024 The Fuchsia Authors | ||
// | ||
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 | ||
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT | ||
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. | ||
// This file may not be copied, modified, or distributed except according to | ||
// those terms. | ||
|
||
//! Support for unsafe fields. | ||
//! | ||
//! This crate provides the [`unsafe_fields!`] macro, which can be used to mark | ||
//! fields as unsafe. Unsafe fields automatically have their types wrapped using | ||
//! the [`Unsafe`] wrapper type. An `Unsafe` is intended to be used to for | ||
//! struct, enum, or union fields which carry safety invariants. All accessors | ||
//! are `unsafe`, which requires any use of an `Unsafe` field to be inside an | ||
//! `unsafe` block. | ||
//! | ||
//! An unsafe field has the type `Unsafe<O, F, const NAME_HASH: u128>`. `O` is | ||
//! the enclosing type (struct, enum, or union), `F` is the type of the field, | ||
//! and `NAME_HASH` is the hash of the field's name. `O` prevents swapping | ||
//! unsafe fields of the same `F` type between different enclosing types, and | ||
//! `NAME_HASH` prevents swapping different fields of the same `F` type within | ||
//! the same enclosing type. Note that swapping the same field between instances | ||
//! of the same type [cannot be prevented](crate#limitations). | ||
//! | ||
//! # Examples | ||
//! | ||
//! ``` | ||
//! use unsafe_fields::{unsafe_fields, Unsafe}; | ||
//! | ||
//! unsafe_fields! { | ||
//! /// A `usize` which is guaranteed to be even. | ||
//! pub struct EvenUsize { | ||
//! // INVARIANT: `n` is even. | ||
//! #[unsafe] | ||
//! n: usize, | ||
//! } | ||
//! } | ||
//! | ||
//! impl EvenUsize { | ||
//! /// Constructs a new `EvenUsize`. | ||
//! /// | ||
//! /// Returns `None` if `n` is odd. | ||
//! pub fn new(n: usize) -> Option<EvenUsize> { | ||
//! if n % 2 != 0 { | ||
//! return None; | ||
//! } | ||
//! // SAFETY: We just confirmed that `n` is even. | ||
//! let n = unsafe { Unsafe::new(n) }; | ||
//! Some(EvenUsize { n }) | ||
//! } | ||
//! } | ||
//! ``` | ||
//! | ||
//! Attempting to swap unsafe fields of the same type is prevented: | ||
//! | ||
//! ```compile_fail,E0308 | ||
//! use unsafe_fields::{unsafe_fields, Unsafe}; | ||
//! | ||
//! unsafe_fields! { | ||
//! /// A range. | ||
//! pub struct Range { | ||
//! // INVARIANT: `lo <= hi`. | ||
//! #[unsafe] | ||
//! lo: usize, | ||
//! #[unsafe] | ||
//! hi: usize, | ||
//! } | ||
//! } | ||
//! | ||
//! impl Range { | ||
//! pub fn swap(&mut self) { | ||
//! // ERROR: Mismatched types | ||
//! core::mem::swap(&mut self.lo, &mut self.hi); | ||
//! } | ||
//! } | ||
//! ``` | ||
//! | ||
//! # Limitations | ||
//! | ||
//! Note that we cannot prevent `Unsafe`s from being swapped between the same | ||
//! field in instances of the same type: | ||
//! | ||
//! ``` | ||
//! use unsafe_fields::{unsafe_fields, Unsafe}; | ||
//! | ||
//! unsafe_fields! { | ||
//! /// A `usize` which is guaranteed to be even. | ||
//! pub struct EvenUsize { | ||
//! // INVARIANT: `n` is even. | ||
//! #[unsafe] | ||
//! n: usize, | ||
//! } | ||
//! } | ||
//! | ||
//! pub fn swap(a: &mut EvenUsize, b: &mut EvenUsize) { | ||
//! core::mem::swap(&mut a.n, &mut b.n); | ||
//! } | ||
//! ``` | ||
|
||
use core::marker::PhantomData; | ||
|
||
/// A field with safety invariants. | ||
/// | ||
/// `Unsafe` should not be named directly - instead, use [`unsafe_fields!`] to | ||
/// declare a type with unsafe fields. | ||
/// | ||
/// See the [crate-level documentation](crate) for more information. | ||
#[derive(Copy, Clone)] // TODO: We need Copy in case user types need to be Copy. Is this sound? | ||
#[repr(transparent)] | ||
pub struct Unsafe<O: ?Sized, F: ?Sized, const NAME_HASH: u128> { | ||
_marker: PhantomData<O>, | ||
field: F, | ||
} | ||
|
||
impl<O: ?Sized, F: ?Sized, const NAME_HASH: u128> Unsafe<O, F, { NAME_HASH }> { | ||
/// Gets a reference to the inner value. | ||
/// | ||
/// # Safety | ||
/// | ||
/// The caller is responsible for upholding any safety invariants associated | ||
/// with this field. | ||
#[inline(always)] | ||
pub const unsafe fn as_ref(&self) -> &F { | ||
&self.field | ||
} | ||
|
||
/// Gets a mutable reference to the inner value. | ||
/// | ||
/// # Safety | ||
/// | ||
/// The caller is responsible for upholding any safety invariants associated | ||
/// with this field. | ||
#[inline(always)] | ||
pub unsafe fn as_mut(&mut self) -> &mut F { | ||
&mut self.field | ||
} | ||
} | ||
|
||
impl<O: ?Sized, F, const NAME_HASH: u128> Unsafe<O, F, { NAME_HASH }> { | ||
/// Constructs a new `Unsafe<O, F, NAME_HASH>`. | ||
/// | ||
/// # Safety | ||
/// | ||
/// The caller is responsible for upholding any safety invariants associated | ||
/// with this field. | ||
#[inline(always)] | ||
pub const unsafe fn new(field: F) -> Unsafe<O, F, { NAME_HASH }> { | ||
Unsafe { _marker: PhantomData, field } | ||
} | ||
|
||
/// Extracts the inner `F` from `self`. | ||
/// | ||
/// # Safety | ||
/// | ||
/// The caller is responsible for upholding any safety invariants associated | ||
/// with this field. | ||
#[inline(always)] | ||
pub const unsafe fn into(self) -> F { | ||
use core::mem::ManuallyDrop; | ||
|
||
let slf = ManuallyDrop::new(self); | ||
|
||
#[repr(C)] | ||
union Transmute<O: ?Sized, F, const NAME_HASH: u128> { | ||
src: ManuallyDrop<Unsafe<O, F, { NAME_HASH }>>, | ||
dst: ManuallyDrop<F>, | ||
} | ||
|
||
// SAFETY: `ManuallyDrop<Unsafe<_, F, _>>` has the same size and bit | ||
// validity as `Unsafe<_, F, _>`. [1] `Unsafe<_, F, _>` is | ||
// `#[repr(transparent)]` and has no other fields, and so it has the | ||
// same size and bit validity as `F`. | ||
// | ||
// [1] Per https://doc.rust-lang.org/1.81.0/core/mem/struct.ManuallyDrop.html: | ||
// | ||
// `ManuallyDrop<T>` is guaranteed to have the same layout and bit | ||
// validity as `T` | ||
let dst = unsafe { Transmute { src: slf }.dst }; | ||
ManuallyDrop::into_inner(dst) | ||
} | ||
} | ||
|
||
/// Defines a type with unsafe fields. | ||
/// | ||
/// See the [crate-level documentation](crate) for more information. | ||
// TODO: Allow specifying *which* fields are unsafe. | ||
#[macro_export] | ||
macro_rules! unsafe_fields { | ||
($(#[$attr:meta])* $vis:vis struct $name:ident { | ||
$($(#[$field_attr:tt])? $field:ident: $field_ty:ty),* $(,)? | ||
}) => { | ||
$(#[$attr])* | ||
$vis struct $name { | ||
$( | ||
$field: unsafe_fields!(@field $(#[$field_attr])? $field: $field_ty), | ||
)* | ||
} | ||
}; | ||
(@field #[unsafe] $field:ident: $field_ty:ty) => { | ||
$crate::Unsafe<Self, $field_ty, {$crate::macro_util::hash_field_name(stringify!($field))}> | ||
}; | ||
(@field $_field:ident: $field_ty:ty) => { | ||
$field_ty | ||
} | ||
} | ||
|
||
#[doc(hidden)] | ||
pub mod macro_util { | ||
// TODO: Implement a stronger hash function so we can basically just ignore | ||
// collisions. If users encounter collisions in practice, we can just deal | ||
// with it then, publish a new version, and tell them to upgrade. | ||
pub const fn hash_field_name(field_name: &str) -> u128 { | ||
// An implementation of FxHasher, although returning a u128. Probably | ||
// not as strong as it could be, but probably more collision resistant | ||
// than normal 64-bit FxHasher. | ||
let field_name = field_name.as_bytes(); | ||
let mut hash = 0u128; | ||
let mut i = 0; | ||
while i < field_name.len() { | ||
// This is just FxHasher's `0x517cc1b727220a95` constant | ||
// concatenated back-to-back. | ||
const K: u128 = 0x517cc1b727220a95517cc1b727220a95; | ||
hash = (hash.rotate_left(5) ^ (field_name[i] as u128)).wrapping_mul(K); | ||
i += 1; | ||
} | ||
hash | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use core::mem; | ||
|
||
use super::*; | ||
|
||
unsafe_fields! { | ||
/// A `Foo`. | ||
#[allow(unused)] | ||
struct Foo { | ||
#[unsafe] | ||
a: usize, | ||
b: usize, | ||
} | ||
} | ||
|
||
unsafe_fields! { | ||
/// A `Bar`. | ||
#[allow(unused)] | ||
struct Bar { | ||
#[unsafe] | ||
a: usize, | ||
#[unsafe] | ||
b: usize, | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_unsafe_fieds() { | ||
let mut foo = Foo { a: unsafe { Unsafe::new(0) }, b: 0 }; | ||
let mut bar = Bar { a: unsafe { Unsafe::new(0) }, b: unsafe { Unsafe::new(0) } }; | ||
|
||
// These all fail to compile: | ||
// mem::swap(&mut foo.a, &mut foo.b); | ||
// mem::swap(&mut foo.a, &mut bar.a); | ||
// mem::swap(&mut bar.a, &mut bar.b); | ||
} | ||
} | ||
|
||
/// This module exists so that we can use rustdoc to perform compile-fail tests | ||
/// rather than having to set up an entire trybuild set suite. | ||
/// | ||
/// ```compile_fail,E0308 | ||
/// use unsafe_fields::*; | ||
/// | ||
/// unsafe_fields! { | ||
/// struct Foo { | ||
/// #[unsafe] | ||
/// a: usize, | ||
/// b: usize, | ||
/// } | ||
/// } | ||
/// | ||
/// impl Foo { | ||
/// // Swapping an unsafe field with a non-unsafe field is a compile error. | ||
/// fn swap(&mut self) { | ||
/// core::mem::swap(&mut self.a, &mut self.b); | ||
/// } | ||
/// } | ||
/// ``` | ||
/// | ||
/// ```compile_fail,E0308 | ||
/// use unsafe_fields::*; | ||
/// | ||
/// unsafe_fields! { | ||
/// struct Foo { | ||
/// #[unsafe] | ||
/// a: usize, | ||
/// #[unsafe] | ||
/// b: usize, | ||
/// } | ||
/// } | ||
/// | ||
/// impl Foo { | ||
/// // Swapping an unsafe field with another unsafe field is a compile | ||
/// // error. | ||
/// fn swap(&mut self) { | ||
/// core::mem::swap(&mut self.a, &mut self.b); | ||
/// } | ||
/// } | ||
/// ``` | ||
/// | ||
/// ```compile_fail,E0308 | ||
/// use unsafe_fields::*; | ||
/// | ||
/// unsafe_fields! { | ||
/// struct Foo { | ||
/// #[unsafe] | ||
/// a: usize, | ||
/// } | ||
/// } | ||
/// | ||
/// unsafe_fields! { | ||
/// struct Bar { | ||
/// #[unsafe] | ||
/// a: usize, | ||
/// } | ||
/// } | ||
/// | ||
/// // Swapping identically-named unsafe fields from different types is a | ||
/// // compile error. | ||
/// fn swap(foo: &mut Foo, bar: &mut Bar) { | ||
/// core::mem::swap(&mut foo.a, &mut bar.a); | ||
/// } | ||
/// ``` | ||
#[doc(hidden)] | ||
pub mod compile_fail {} |