unsafe_fields/
lib.rs

1// Copyright 2024 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9// After updating the following doc comment, make sure to run the following
10// command to update `README.md` based on its contents:
11//
12//   cargo -q run --manifest-path ../tools/Cargo.toml -p generate-readme > README.md
13
14//! Support for unsafe fields.
15//!
16//! This crate provides the [`unsafe_fields!`] macro, which can be used to mark
17//! fields as unsafe. Unsafe fields automatically have their types wrapped using
18//! the [`Unsafe`] wrapper type. An `Unsafe` is intended to be used to for
19//! struct, enum, or union fields which carry safety invariants. All accessors
20//! are `unsafe`, which requires any use of an `Unsafe` field to be inside an
21//! `unsafe` block. One exception is [`Unsafe::as_ref`], which is available when
22//! the `zerocopy_0_8` feature is enabled. See its docs for more information.
23//!
24//! An unsafe field has the type `Unsafe<O, F, const NAME_HASH: u128>`. `O` is
25//! the enclosing type (struct, enum, or union), `F` is the type of the field,
26//! and `NAME_HASH` is the hash of the field's name. `O` prevents swapping
27//! unsafe fields of the same `F` type between different enclosing types, and
28//! `NAME_HASH` prevents swapping different fields of the same `F` type within
29//! the same enclosing type. Note that swapping the same field between instances
30//! of the same type [cannot be prevented](crate#limitations).
31//!
32//! [immutable]: zerocopy_0_8::Immutable
33//!
34//! # Examples
35//!
36//! ```rust
37//! use unsafe_fields::{unsafe_fields, Unsafe};
38//!
39//! unsafe_fields! {
40//!     /// A `usize` which is guaranteed to be even.
41//!     pub struct EvenUsize {
42//!         // INVARIANT: `n` is even.
43//!         #[unsafe]
44//!         n: usize,
45//!     }
46//! }
47//!
48//! impl EvenUsize {
49//!     /// Constructs a new `EvenUsize`.
50//!     ///
51//!     /// Returns `None` if `n` is odd.
52//!     pub fn new(n: usize) -> Option<EvenUsize> {
53//!         if n % 2 != 0 {
54//!             return None;
55//!         }
56//!         // SAFETY: We just confirmed that `n` is even.
57//!         let n = unsafe { Unsafe::new(n) };
58//!         Some(EvenUsize { n })
59//!     }
60//! }
61//! ```
62//!
63//! Attempting to swap unsafe fields of the same type is prevented:
64//!
65//! ```rust,compile_fail,E0308
66//! use unsafe_fields::{unsafe_fields, Unsafe};
67//!
68//! unsafe_fields! {
69//!     /// A range.
70//!     pub struct Range {
71//!         // INVARIANT: `lo <= hi`.
72//!         #[unsafe]
73//!         lo: usize,
74//!         #[unsafe]
75//!         hi: usize,
76//!     }
77//! }
78//!
79//! impl Range {
80//!     pub fn swap(&mut self) {
81//!         // ERROR: Mismatched types
82//!         core::mem::swap(&mut self.lo, &mut self.hi);
83//!     }
84//! }
85//! ```
86//!
87//! # Limitations
88//!
89//! Note that we cannot prevent `Unsafe`s from being swapped between the same
90//! field in instances of the same type:
91//!
92//! ```rust
93//! use unsafe_fields::{unsafe_fields, Unsafe};
94//!
95//! unsafe_fields! {
96//!     /// A `usize` which is guaranteed to be even.
97//!     pub struct EvenUsize {
98//!         // INVARIANT: `n` is even.
99//!         #[unsafe]
100//!         n: usize,
101//!     }
102//! }
103//!
104//! pub fn swap(a: &mut EvenUsize, b: &mut EvenUsize) {
105//!     core::mem::swap(&mut a.n, &mut b.n);
106//! }
107//! ```
108
109// Sometimes we want to use lints which were added after our MSRV.
110// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
111// this attribute, any unknown lint would cause a CI failure when testing with
112// our MSRV.
113#![allow(unknown_lints, non_local_definitions, unreachable_patterns)]
114#![deny(renamed_and_removed_lints)]
115#![deny(
116    anonymous_parameters,
117    deprecated_in_future,
118    late_bound_lifetime_arguments,
119    missing_docs,
120    path_statements,
121    patterns_in_fns_without_body,
122    rust_2018_idioms,
123    trivial_numeric_casts,
124    unreachable_pub,
125    unsafe_op_in_unsafe_fn,
126    unused_extern_crates,
127    // We intentionally choose not to deny `unused_qualifications`. When items
128    // are added to the prelude (e.g., `core::mem::size_of`), this has the
129    // consequence of making some uses trigger this lint on the latest toolchain
130    // (e.g., `mem::size_of`), but fixing it (e.g. by replacing with `size_of`)
131    // does not work on older toolchains.
132    //
133    // We tested a more complicated fix in #1413, but ultimately decided that,
134    // since this lint is just a minor style lint, the complexity isn't worth it
135    // - it's fine to occasionally have unused qualifications slip through,
136    // especially since these do not affect our user-facing API in any way.
137    variant_size_differences
138)]
139#![deny(
140    clippy::all,
141    clippy::alloc_instead_of_core,
142    clippy::arithmetic_side_effects,
143    clippy::as_underscore,
144    clippy::assertions_on_result_states,
145    clippy::as_conversions,
146    clippy::correctness,
147    clippy::dbg_macro,
148    clippy::decimal_literal_representation,
149    clippy::double_must_use,
150    clippy::get_unwrap,
151    clippy::indexing_slicing,
152    clippy::missing_const_for_fn,
153    clippy::missing_inline_in_public_items,
154    clippy::missing_safety_doc,
155    clippy::must_use_candidate,
156    clippy::must_use_unit,
157    clippy::obfuscated_if_else,
158    clippy::perf,
159    clippy::print_stdout,
160    clippy::return_self_not_must_use,
161    clippy::std_instead_of_core,
162    clippy::style,
163    clippy::suspicious,
164    clippy::todo,
165    clippy::undocumented_unsafe_blocks,
166    clippy::unimplemented,
167    clippy::unnested_or_patterns,
168    clippy::unwrap_used,
169    clippy::use_debug
170)]
171#![allow(clippy::type_complexity)]
172#![deny(
173    rustdoc::bare_urls,
174    rustdoc::broken_intra_doc_links,
175    rustdoc::invalid_codeblock_attributes,
176    rustdoc::invalid_html_tags,
177    rustdoc::invalid_rust_codeblocks,
178    rustdoc::missing_crate_level_docs,
179    rustdoc::private_intra_doc_links
180)]
181#![cfg_attr(doc_cfg, feature(doc_cfg))]
182
183use core::marker::PhantomData;
184
185/// A field with safety invariants.
186///
187/// `Unsafe` should not be named directly - instead, use [`unsafe_fields!`] to
188/// declare a type with unsafe fields.
189///
190/// See the [crate-level documentation](crate) for more information.
191#[repr(transparent)]
192pub struct Unsafe<O: ?Sized, F: ?Sized, const NAME_HASH: u128> {
193    _marker: PhantomData<O>,
194    // INVARIANT: `field` is only modified via public `unsafe` methods. User code is never
195    // invoked implicitly except via public `unsafe` methods.
196    field: F,
197}
198
199// NOTE on design: It may seem counter-intuitive to offer an impl of traits that
200// don't require `unsafe` to call. Unfortunately, this is a fundamental
201// requirement if users want to be able to mark their types as `Copy`. Luckily,
202// we can implement `Copy` (and its unavoidable super-trait, `Clone`) without
203// invoking user code or opening up the possibility of modifying the field. We
204// do this by only implementing `Copy` and `Clone` when `F: Copy`. For `Clone`,
205// the user is still able to provide a manual impl, so this does not
206// fundamentally restrict what behavior can be supported.
207impl<O: ?Sized, F: Copy, const NAME_HASH: u128> Copy for Unsafe<O, F, { NAME_HASH }> {}
208impl<O: ?Sized, F: Copy, const NAME_HASH: u128> Clone for Unsafe<O, F, { NAME_HASH }> {
209    #[inline(always)]
210    #[allow(clippy::non_canonical_clone_impl)]
211    fn clone(&self) -> Self {
212        // SAFETY: We don't call any user-defined code here (only make a
213        // bit-for-bit copy of `self.field`), so there's no way to accidentally
214        // invoke user-defined code or modify `self.field`.
215        Unsafe { _marker: PhantomData, field: self.field }
216    }
217}
218
219impl<O: ?Sized, F: ?Sized, const NAME_HASH: u128> Unsafe<O, F, { NAME_HASH }> {
220    /// Gets a reference to the inner value.
221    ///
222    /// If [`F: Immutable`][immutable], prefer [`as_ref`], which is safe.
223    ///
224    /// [immutable]: zerocopy_0_8::Immutable
225    /// [`as_ref`]: Unsafe::as_ref
226    ///
227    /// # Safety
228    ///
229    /// The caller is responsible for upholding any safety invariants associated
230    /// with this field.
231    #[inline(always)]
232    pub const unsafe fn as_ref_unchecked(&self) -> &F {
233        // SAFETY: This method is unsafe to call.
234        &self.field
235    }
236
237    /// Gets a reference to the inner value safely so long as the inner value is
238    /// immutable.
239    ///
240    /// If [`F: Immutable`][immutable], then `F` does not permit interior
241    /// mutation, and so it is safe to return a reference to it.
242    ///
243    /// [immutable]: zerocopy_0_8::Immutable
244    #[inline(always)]
245    #[cfg(feature = "zerocopy_0_8")]
246    #[cfg_attr(doc_cfg, doc(cfg(feature = "zerocopy_0_8")))]
247    pub const fn as_ref(&self) -> &F
248    where
249        F: zerocopy_0_8::Immutable,
250    {
251        // SAFETY: `F: Immutable` guarantees that the returned `&F` cannot be
252        // used to mutate `self`, and so it cannot be used to violate any
253        // invariant.
254        unsafe { self.as_ref_unchecked() }
255    }
256
257    /// Gets a mutable reference to the inner value.
258    ///
259    /// # Safety
260    ///
261    /// The caller is responsible for upholding any safety invariants associated
262    /// with this field.
263    #[inline(always)]
264    pub unsafe fn as_mut(&mut self) -> &mut F {
265        // SAFETY: This method is unsafe to call.
266        &mut self.field
267    }
268}
269
270impl<O: ?Sized, F, const NAME_HASH: u128> Unsafe<O, F, { NAME_HASH }> {
271    /// Constructs a new `Unsafe`.
272    ///
273    /// # Safety
274    ///
275    /// The caller is responsible for upholding any safety invariants associated
276    /// with this field.
277    #[inline(always)]
278    pub const unsafe fn new(field: F) -> Unsafe<O, F, { NAME_HASH }> {
279        // SAFETY: This method is unsafe to call.
280        Unsafe { _marker: PhantomData, field }
281    }
282
283    /// Extracts the inner `F` from `self`.
284    #[inline(always)]
285    pub const fn into(self) -> F {
286        use core::mem::ManuallyDrop;
287        let slf = ManuallyDrop::new(self);
288
289        #[repr(C)]
290        union Transmute<Src, Dst> {
291            src: ManuallyDrop<Src>,
292            dst: ManuallyDrop<Dst>,
293        }
294
295        // We'd like to just return `self.field` here, but Rust would drop
296        // `self` in doing that, which we don't want. Even destructuring (ie,
297        // `let Unsafe { field, .. } = self`) also causes a drop. We also can't
298        // use `mem::transmute` because that requires all types to be concrete,
299        // so a union transmute is our only option.
300        //
301        // SAFETY: `ManuallyDrop<Unsafe<_, F, _>>` has the same size and bit
302        // validity as `Unsafe<_, F, _>`. [1] `Unsafe<_, F, _>` is
303        // `#[repr(transparent)]` and has no other fields, and so it has the
304        // same size and bit validity as `F`.
305        //
306        // [1] Per https://doc.rust-lang.org/1.81.0/core/mem/struct.ManuallyDrop.html:
307        //
308        //   `ManuallyDrop<T>` is guaranteed to have the same layout and bit
309        //   validity as `T`
310        let dst = unsafe { Transmute { src: slf }.dst };
311
312        ManuallyDrop::into_inner(dst)
313    }
314}
315
316/// Defines a type with unsafe fields.
317///
318/// See the [crate-level documentation](crate) for more information.
319// TODO: Allow specifying *which* fields are unsafe.
320#[macro_export]
321macro_rules! unsafe_fields {
322    ($(#[$attr:meta])* $vis:vis struct $name:ident {
323        $($(#[$field_attr:tt])? $field:ident: $field_ty:ty),* $(,)?
324    }) => {
325        $(#[$attr])*
326        $vis struct $name {
327            $(
328                $field: unsafe_fields!(@field $(#[$field_attr])? $field: $field_ty),
329            )*
330        }
331    };
332    (@field #[unsafe] $field:ident: $field_ty:ty) => {
333        $crate::Unsafe<Self, $field_ty, {$crate::macro_util::hash_field_name(stringify!($field))}>
334    };
335    (@field $_field:ident: $field_ty:ty) => {
336        $field_ty
337    }
338}
339
340#[doc(hidden)]
341pub mod macro_util {
342    // TODO: Implement a stronger hash function so we can basically just ignore
343    // collisions. If users encounter collisions in practice, we can just deal
344    // with it then, publish a new version, and tell them to upgrade.
345    #[inline(always)]
346    #[must_use]
347    #[allow(clippy::as_conversions, clippy::indexing_slicing, clippy::arithmetic_side_effects)]
348    pub const fn hash_field_name(field_name: &str) -> u128 {
349        // An implementation of FxHasher, although returning a u128. Probably
350        // not as strong as it could be, but probably more collision resistant
351        // than normal 64-bit FxHasher.
352        let field_name = field_name.as_bytes();
353        let mut hash = 0u128;
354        let mut i = 0;
355        while i < field_name.len() {
356            // This is just FxHasher's `0x517cc1b727220a95` constant
357            // concatenated back-to-back.
358            const K: u128 = 0x517cc1b727220a95517cc1b727220a95;
359            hash = (hash.rotate_left(5) ^ (field_name[i] as u128)).wrapping_mul(K);
360            i += 1;
361        }
362        hash
363    }
364}
365
366#[cfg(test)]
367#[allow(clippy::missing_const_for_fn)]
368mod tests {
369    use super::*;
370
371    unsafe_fields! {
372        /// A `Foo`.
373        #[allow(unused)]
374        struct Foo {
375            #[unsafe]
376            a: usize,
377            b: usize,
378        }
379    }
380
381    unsafe_fields! {
382        /// A `Bar`.
383        #[allow(unused)]
384        struct Bar {
385            #[unsafe]
386            a: usize,
387            #[unsafe]
388            b: usize,
389        }
390    }
391
392    #[test]
393    #[allow(clippy::undocumented_unsafe_blocks)]
394    fn test_unsafe_fieds() {
395        let mut _foo = Foo { a: unsafe { Unsafe::new(0) }, b: 0 };
396        let mut _bar = Bar { a: unsafe { Unsafe::new(0) }, b: unsafe { Unsafe::new(0) } };
397    }
398}
399
400/// This module exists so that we can use rustdoc to perform compile-fail tests
401/// rather than having to set up an entire trybuild set suite.
402///
403/// ```compile_fail,E0308
404/// use unsafe_fields::*;
405///
406/// unsafe_fields! {
407///     struct Foo {
408///         #[unsafe]
409///         a: usize,
410///         b: usize,
411///     }
412/// }
413///
414/// impl Foo {
415///     // Swapping an unsafe field with a non-unsafe field is a compile error.
416///     fn swap(&mut self) {
417///         core::mem::swap(&mut self.a, &mut self.b);
418///     }
419/// }
420/// ```
421///
422/// ```compile_fail,E0308
423/// use unsafe_fields::*;
424///
425/// unsafe_fields! {
426///     struct Foo {
427///         #[unsafe]
428///         a: usize,
429///         #[unsafe]
430///         b: usize,
431///     }
432/// }
433///
434/// impl Foo {
435///     // Swapping an unsafe field with another unsafe field is a compile
436///     // error.
437///     fn swap(&mut self) {
438///         core::mem::swap(&mut self.a, &mut self.b);
439///     }
440/// }
441/// ```
442///
443/// ```compile_fail,E0308
444/// use unsafe_fields::*;
445///
446/// unsafe_fields! {
447///     struct Foo {
448///         #[unsafe]
449///         a: usize,
450///     }
451/// }
452///
453/// unsafe_fields! {
454///     struct Bar {
455///         #[unsafe]
456///         a: usize,
457///     }
458/// }
459///
460/// // Swapping identically-named unsafe fields from different types is a
461/// // compile error.
462/// fn swap(foo: &mut Foo, bar: &mut Bar) {
463///     core::mem::swap(&mut foo.a, &mut bar.a);
464/// }
465/// ```
466#[doc(hidden)]
467pub mod compile_fail {}