|
| 1 | +// Copyright 2024 The Fuchsia Authors |
| 2 | +// |
| 3 | +// Licensed under the 2-Clause BSD License <LICENSE-BSD or |
| 4 | +// https://opensource.org/license/bsd-2-clause>, Apache License, Version 2.0 |
| 5 | +// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT |
| 6 | +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option. |
| 7 | +// This file may not be copied, modified, or distributed except according to |
| 8 | +// those terms. |
| 9 | + |
| 10 | +use crate::pointer::{ |
| 11 | + invariant::{Aliasing, Aligned, Alignment, AlignmentMapping, MappedAlignment, Valid}, |
| 12 | + Ptr, |
| 13 | +}; |
| 14 | + |
| 15 | +// NOTE: `NAME_HASH` is not semver-stable - it just needs to be consistent |
| 16 | +// within a single compilation. |
| 17 | +// |
| 18 | +// TODO: `#[diagnostic::on_unimplemented]` |
| 19 | +#[doc(hidden)] |
| 20 | +pub unsafe trait Field<const NAME_HASH: u128> { |
| 21 | + type Type; |
| 22 | + const OFFSET: isize; |
| 23 | + |
| 24 | + // Basically, `Preserved` unless the struct is `#[repr(packed)]`. TODO: |
| 25 | + // Confirm that this is the correct behavior. |
| 26 | + type AlignmentMapping: AlignmentMapping; |
| 27 | +} |
| 28 | + |
| 29 | +pub unsafe trait Projectable { |
| 30 | + type Of<T>; |
| 31 | + type Inner; |
| 32 | +} |
| 33 | + |
| 34 | +unsafe impl<T> Projectable for core::mem::ManuallyDrop<T> { |
| 35 | + type Of<U> = core::mem::ManuallyDrop<U>; |
| 36 | + type Inner = T; |
| 37 | +} |
| 38 | + |
| 39 | +// TODO: Use `Ptr` to make this generic over `&`, `&mut`, `Box`, etc. |
| 40 | +#[doc(hidden)] |
| 41 | +#[allow(clippy::must_use_candidate)] |
| 42 | +#[inline(never)] |
| 43 | +pub const fn project<'a, P, A, AA, const NAME_HASH: u128>( |
| 44 | + // ptr: *const P, |
| 45 | + ptr: Ptr<'a, P, (A, AA, Valid)>, |
| 46 | +) -> Ptr< |
| 47 | + 'a, |
| 48 | + P::Of<<P::Inner as Field<{ NAME_HASH }>>::Type>, |
| 49 | + (A, MappedAlignment<AA, <P::Inner as Field<{ NAME_HASH }>>::AlignmentMapping>, Valid), |
| 50 | +> |
| 51 | +where |
| 52 | + P: Projectable, |
| 53 | + P::Inner: Field<{ NAME_HASH }>, |
| 54 | + A: Aliasing, |
| 55 | + AA: Alignment, |
| 56 | +{ |
| 57 | + // NOTE: Doing the actual cast inside this function makes the `project!` |
| 58 | + // macro simpler, but it also means that we can't handle both const code and |
| 59 | + // unsized types at the same time. In order to be const, we can't call trait |
| 60 | + // methods, but in order to support unsized types, we'd need to provide a |
| 61 | + // trait method to perform certain pointer casts (if we were using unsized |
| 62 | + // types, Rust wouldn't be smart enough to know that all of these casts had |
| 63 | + // the same vtable kinds). |
| 64 | + // |
| 65 | + // An alternative is to perform the casts in the macro body itself, but that |
| 66 | + // turns out to be unsound. In particular, despite `Field` guaranteeing that |
| 67 | + // `P::Inner` has a field of the given name, if we use the `addr_of!` macro |
| 68 | + // to perform field projection, it could still be the case that: |
| 69 | + // - The field is private, and so inaccessible |
| 70 | + // - The type implements `Deref` |
| 71 | + // - The `Deref::Target` type has a field of the same name |
| 72 | + // |
| 73 | + // In this case, implicit deref still happens despite our fancy `Field` |
| 74 | + // machinery. This is demonstrated in practice in [1]. |
| 75 | + // |
| 76 | + // Here, we perform the casts manually using the associated `OFFSET` const, |
| 77 | + // which is not vulnerable to this issue. Note that we *could* use the |
| 78 | + // `OFFSET` const directly in the macro instead, but it would still not |
| 79 | + // allow us to support unsized types, as `offset_of!` doesn't support taking |
| 80 | + // the offset of unsized fields. |
| 81 | + // |
| 82 | + // [1] https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=978de749dca4f67ed24707ac049a5e4f |
| 83 | + |
| 84 | + let inner = unsafe { ptr.cast::<P::Inner>() }; |
| 85 | + let field_inner = inner.project_foo::<<P as Projectable>::Inner, { NAME_HASH }>(); |
| 86 | + unsafe { field_inner.cast() } |
| 87 | +} |
| 88 | + |
| 89 | +// TODO: Implement a stronger hash function so we can basically just ignore |
| 90 | +// collisions. If users encounter collisions in practice, we can just deal with |
| 91 | +// it then, publish a new version, and tell them to upgrade. |
| 92 | +#[doc(hidden)] |
| 93 | +#[must_use] |
| 94 | +#[inline(always)] |
| 95 | +#[allow(clippy::as_conversions, clippy::indexing_slicing, clippy::arithmetic_side_effects)] |
| 96 | +pub const fn hash_field_name(field_name: &str) -> u128 { |
| 97 | + let field_name = field_name.as_bytes(); |
| 98 | + let mut hash = 0u64; |
| 99 | + let mut i = 0; |
| 100 | + while i < field_name.len() { |
| 101 | + const K: u64 = 0x517cc1b727220a95; |
| 102 | + hash = (hash.rotate_left(5) ^ (field_name[i] as u64)).wrapping_mul(K); |
| 103 | + i += 1; |
| 104 | + } |
| 105 | + hash as u128 |
| 106 | +} |
| 107 | + |
| 108 | +#[macro_export] |
| 109 | +macro_rules! projectable { |
| 110 | + // It's unlikely that anyone would ever call this macro on a field-less |
| 111 | + // struct, but might as well support it for completeness. |
| 112 | + ($(#[$attr:meta])* $vis:vis struct $name:ident;) => {}; |
| 113 | + ($(#[$attr:meta])* $vis:vis struct $name:ident { |
| 114 | + $($field:ident: $field_ty:ty),* $(,)? |
| 115 | + }) => { |
| 116 | + $(#[$attr])* |
| 117 | + $vis struct $name { |
| 118 | + $($field: $field_ty,)* |
| 119 | + } |
| 120 | + |
| 121 | + $( |
| 122 | + $crate::projectable!(@inner $name . $field: $field_ty); |
| 123 | + )* |
| 124 | + }; |
| 125 | + ($(#[$attr:meta])* $vis:vis struct $name:ident ( |
| 126 | + $($field_ty:ty),* $(,)? |
| 127 | + );) => { |
| 128 | + $(#[$attr])* |
| 129 | + $vis struct $name ( |
| 130 | + $($field_ty,)* |
| 131 | + ); |
| 132 | + |
| 133 | + // TODO: How to generate the idents `0`, `1`, etc in order to call |
| 134 | + // `projectable!(@inner ...)`? |
| 135 | + }; |
| 136 | + (@inner $name:ident . $field:ident: $field_ty:ty) => { |
| 137 | + unsafe impl $crate::project::Field<{ |
| 138 | + $crate::project::hash_field_name(stringify!($field)) |
| 139 | + }> for $name { |
| 140 | + type Type = $field_ty; |
| 141 | + const OFFSET: isize = $crate::util::macro_util::core_reexport::mem::offset_of!($name, $field) as isize; |
| 142 | + // TODO: Change this if we detect `#[repr(packed)]` structs. |
| 143 | + type AlignmentMapping = $crate::pointer::invariant::Preserved; |
| 144 | + } |
| 145 | + }; |
| 146 | +} |
| 147 | + |
| 148 | +#[macro_export] |
| 149 | +macro_rules! project { |
| 150 | + ($outer:ident $(. $field:ident)*) => { $crate::project!(($outer) . $field) }; |
| 151 | + (($outer:expr) . $field:ident) => {{ |
| 152 | + let outer: &_ = $outer; |
| 153 | + let outer: *const _ = outer; |
| 154 | + let field = $crate::project::project::<_, { |
| 155 | + $crate::project::hash_field_name(stringify!($field)) |
| 156 | + }>(outer); |
| 157 | + unsafe { &*field } |
| 158 | + }}; |
| 159 | + (($outer:expr) $(. $field:ident)+) => {{ |
| 160 | + let outer = $outer; |
| 161 | + $( |
| 162 | + let outer = project!((outer).$field); |
| 163 | + )+ |
| 164 | + outer |
| 165 | + }}; |
| 166 | +} |
| 167 | + |
| 168 | +#[cfg(test)] |
| 169 | +mod tests { |
| 170 | + projectable! { |
| 171 | + struct Foo { |
| 172 | + a: u8, |
| 173 | + b: u16, |
| 174 | + c: u32, |
| 175 | + } |
| 176 | + } |
| 177 | + |
| 178 | + #[test] |
| 179 | + fn test_project() { |
| 180 | + use core::mem::ManuallyDrop; |
| 181 | + |
| 182 | + let f = ManuallyDrop::new(Foo { a: 0, b: 1, c: 2 }); |
| 183 | + let p = project!((&f).c); |
| 184 | + assert_eq!(p, &ManuallyDrop::new(2)); |
| 185 | + } |
| 186 | +} |
0 commit comments