Skip to content

Commit b39b4b1

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? gherrit-pr-id: I54eb863c96f056738f58727fc0fec4e1630bf97a
1 parent af67d9a commit b39b4b1

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-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: 37 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,33 @@ 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<'a, F::Type, I::WithAlignment<Unknown>>
1085+
where
1086+
F: crate::project::Field<{ NAME_HASH }>,
1087+
{
1088+
// TODO: Maybe we could use an associated `AlignmentMapping` type on
1089+
// `Field` to work around the alignment issue? We'd need to be able
1090+
// to detect `#[repr(packed)]` and stuff like that in order to
1091+
// generate the correct mapping when implementing `Field`.
1092+
//
1093+
// I don't *think* that validity has the same issue (at least not
1094+
// for structs).
1095+
todo!()
1096+
}
1097+
}
1098+
10621099
impl<'a, T, I> Ptr<'a, T, I>
10631100
where
10641101
T: 'a + ?Sized,

src/project.rs

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

0 commit comments

Comments
 (0)