Skip to content

Commit 8f81be9

Browse files
committed
remove potential ub in render_resource_wrapper (#7279)
# Objective [as noted](#5950 (comment)) by james, transmuting arcs may be UB. we now store a `*const ()` pointer internally, and only rely on `ptr.cast::<()>().cast::<T>() == ptr`. as a happy side effect this removes the need for boxing the value, so todo: potentially use this for release mode as well
1 parent 4fd092f commit 8f81be9

File tree

1 file changed

+44
-52
lines changed

1 file changed

+44
-52
lines changed

crates/bevy_render/src/render_resource/resource_macros.rs

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,82 @@
11
// structs containing wgpu types take a long time to compile. this is particularly bad for generic
22
// structs containing wgpu structs. we avoid that in debug builds (and for cargo check and rust analyzer)
3-
// by boxing and type-erasing with the `render_resource_wrapper` macro.
3+
// by type-erasing with the `render_resource_wrapper` macro. The resulting type behaves like Arc<$wgpu_type>,
4+
// but avoids explicitly storing an Arc<$wgpu_type> member.
45
// analysis from https://github.com/bevyengine/bevy/pull/5950#issuecomment-1243473071 indicates this is
56
// due to `evaluate_obligations`. we should check if this can be removed after a fix lands for
67
// https://github.com/rust-lang/rust/issues/99188 (and after other `evaluate_obligations`-related changes).
78
#[cfg(debug_assertions)]
89
#[macro_export]
910
macro_rules! render_resource_wrapper {
1011
($wrapper_type:ident, $wgpu_type:ty) => {
11-
#[derive(Clone, Debug)]
12-
pub struct $wrapper_type(Option<std::sync::Arc<Box<()>>>);
12+
#[derive(Debug)]
13+
// SAFETY: while self is live, self.0 comes from `into_raw` of an Arc<$wgpu_type> with a strong ref.
14+
pub struct $wrapper_type(*const ());
1315

1416
impl $wrapper_type {
1517
pub fn new(value: $wgpu_type) -> Self {
16-
unsafe {
17-
Self(Some(std::sync::Arc::new(std::mem::transmute(Box::new(
18-
value,
19-
)))))
20-
}
18+
let arc = std::sync::Arc::new(value);
19+
let value_ptr = std::sync::Arc::into_raw(arc);
20+
let unit_ptr = value_ptr.cast::<()>();
21+
Self(unit_ptr)
2122
}
2223

23-
pub fn try_unwrap(mut self) -> Option<$wgpu_type> {
24-
let inner = self.0.take();
25-
if let Some(inner) = inner {
26-
match std::sync::Arc::try_unwrap(inner) {
27-
Ok(untyped_box) => {
28-
let typed_box = unsafe {
29-
std::mem::transmute::<Box<()>, Box<$wgpu_type>>(untyped_box)
30-
};
31-
Some(*typed_box)
32-
}
33-
Err(inner) => {
34-
let _ = unsafe {
35-
std::mem::transmute::<
36-
std::sync::Arc<Box<()>>,
37-
std::sync::Arc<Box<$wgpu_type>>,
38-
>(inner)
39-
};
40-
None
41-
}
42-
}
43-
} else {
44-
None
45-
}
24+
pub fn try_unwrap(self) -> Option<$wgpu_type> {
25+
let value_ptr = self.0.cast::<$wgpu_type>();
26+
// SAFETY: pointer refers to a valid Arc, and was created from Arc::into_raw.
27+
let arc = unsafe { std::sync::Arc::from_raw(value_ptr) };
28+
29+
// we forget ourselves here since the reconstructed arc will be dropped/decremented within this scope
30+
std::mem::forget(self);
31+
32+
std::sync::Arc::try_unwrap(arc).ok()
4633
}
4734
}
4835

4936
impl std::ops::Deref for $wrapper_type {
5037
type Target = $wgpu_type;
5138

5239
fn deref(&self) -> &Self::Target {
53-
let untyped_box = self
54-
.0
55-
.as_ref()
56-
.expect("render_resource_wrapper inner value has already been taken (via drop or try_unwrap")
57-
.as_ref();
58-
59-
let typed_box =
60-
unsafe { std::mem::transmute::<&Box<()>, &Box<$wgpu_type>>(untyped_box) };
61-
typed_box.as_ref()
40+
let value_ptr = self.0.cast::<$wgpu_type>();
41+
// SAFETY: the arc lives for 'self, so the ref lives for 'self
42+
let value_ref = unsafe { value_ptr.as_ref() };
43+
value_ref.unwrap()
6244
}
6345
}
6446

6547
impl Drop for $wrapper_type {
6648
fn drop(&mut self) {
67-
let inner = self.0.take();
68-
if let Some(inner) = inner {
69-
let _ = unsafe {
70-
std::mem::transmute::<
71-
std::sync::Arc<Box<()>>,
72-
std::sync::Arc<Box<$wgpu_type>>,
73-
>(inner)
74-
};
75-
}
49+
let value_ptr = self.0.cast::<$wgpu_type>();
50+
// SAFETY: pointer refers to a valid Arc, and was created from Arc::into_raw.
51+
// this reconstructed arc is dropped/decremented within this scope.
52+
unsafe { std::sync::Arc::from_raw(value_ptr) };
7653
}
7754
}
7855

79-
// Arc<Box<()>> and Arc<()> will be Sync and Send even when $wgpu_type is not Sync or Send.
56+
// SAFETY: We manually implement Send and Sync, which is valid for Arc<T> when T: Send + Sync.
8057
// We ensure correctness by checking that $wgpu_type does implement Send and Sync.
8158
// If in future there is a case where a wrapper is required for a non-send/sync type
82-
// we can implement a macro variant that also does `impl !Send for $wrapper_type {}` and
83-
// `impl !Sync for $wrapper_type {}`
59+
// we can implement a macro variant that omits these manual Send + Sync impls
60+
unsafe impl Send for $wrapper_type {}
61+
unsafe impl Sync for $wrapper_type {}
8462
const _: () = {
8563
trait AssertSendSyncBound: Send + Sync {}
8664
impl AssertSendSyncBound for $wgpu_type {}
8765
};
66+
67+
impl Clone for $wrapper_type {
68+
fn clone(&self) -> Self {
69+
let value_ptr = self.0.cast::<$wgpu_type>();
70+
// SAFETY: pointer refers to a valid Arc, and was created from Arc::into_raw.
71+
let arc = unsafe { std::sync::Arc::from_raw(value_ptr.cast::<$wgpu_type>()) };
72+
let cloned = std::sync::Arc::clone(&arc);
73+
// we forget the reconstructed Arc to avoid decrementing the ref counter, as self is still live.
74+
std::mem::forget(arc);
75+
let cloned_value_ptr = std::sync::Arc::into_raw(cloned);
76+
let cloned_unit_ptr = cloned_value_ptr.cast::<()>();
77+
Self(cloned_unit_ptr)
78+
}
79+
}
8880
};
8981
}
9082

0 commit comments

Comments
 (0)