Skip to content

Commit 5ef235b

Browse files
committed
[project] Initial commit
TODO: Could we support unsized types so long as we never call offset_of! on the trailing unsized field? TODO: Use `Name` type parameter trick to emit better error messages: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=97771667892860935bea60b218f8fdf4 gherrit-pr-id: I54eb863c96f056738f58727fc0fec4e1630bf97a
1 parent 986e3c5 commit 5ef235b

File tree

3 files changed

+236
-0
lines changed

3 files changed

+236
-0
lines changed

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ pub mod layout;
333333
mod macros;
334334
#[doc(hidden)]
335335
pub mod pointer;
336+
#[doc(hidden)]
337+
pub mod project;
336338
mod r#ref;
337339
// TODO(#252): If we make this pub, come up with a better name.
338340
mod wrappers;

src/pointer/ptr.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,16 @@ mod _casts {
815815
// The caller promises all other safety preconditions.
816816
unsafe { self.cast_unsized_unchecked(cast) }
817817
}
818+
819+
pub const unsafe fn cast<U>(
820+
self,
821+
) -> Ptr<
822+
'a,
823+
U,
824+
(I::Aliasing, Unknown, MappedValidity<I::Validity, (Initialized, Initialized)>),
825+
> {
826+
todo!()
827+
}
818828
}
819829

820830
impl<'a, T, I> Ptr<'a, T, I>
@@ -1059,6 +1069,44 @@ mod _casts {
10591069
mod _project {
10601070
use super::*;
10611071

1072+
impl<'a, T, I> Ptr<'a, T, I>
1073+
where
1074+
T: 'a + ?Sized,
1075+
I: Invariants,
1076+
{
1077+
/// Projects a field from `self`.
1078+
///
1079+
/// The resulting `Ptr` is not guaranteed to be aligned because, for
1080+
/// example, a `#[repr(packed)]` struct can be properly aligned but have
1081+
/// unaligned fields.
1082+
pub fn project_foo<F, const NAME_HASH: u128>(
1083+
self,
1084+
) -> Ptr<
1085+
'a,
1086+
F::Type,
1087+
(
1088+
I::Aliasing,
1089+
MappedAlignment<
1090+
I::Alignment,
1091+
<F as crate::project::Field<{ NAME_HASH }>>::AlignmentMapping,
1092+
>,
1093+
I::Validity,
1094+
),
1095+
>
1096+
where
1097+
F: crate::project::Field<{ NAME_HASH }>,
1098+
{
1099+
// TODO: Maybe we could use an associated `AlignmentMapping` type on
1100+
// `Field` to work around the alignment issue? We'd need to be able
1101+
// to detect `#[repr(packed)]` and stuff like that in order to
1102+
// generate the correct mapping when implementing `Field`.
1103+
//
1104+
// I don't *think* that validity has the same issue (at least not
1105+
// for structs).
1106+
todo!()
1107+
}
1108+
}
1109+
10621110
impl<'a, T, I> Ptr<'a, T, I>
10631111
where
10641112
T: 'a + ?Sized,

src/project.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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

Comments
 (0)