Skip to content

Commit 6b098d2

Browse files
committed
Avoid UB on opaque types.
autocxx generates opaque types of a given length, represented by an array of bytes. Unless autocxx is configured to use CppRef, references exist to those types, and as those references pass through C++ they may not obey Rust aliasing rules. This is the main motivation behind adopting CppRef<T>, but meanwhile, to reduce such theoretical UB, we mark the data as both MaybeUninit and UnsafeCell such that Rust makes fewer assumptions about the underlying data. Per comments, this doesn't eliminate the chance of UB.
1 parent dbe9f35 commit 6b098d2

File tree

1 file changed

+27
-1
lines changed

1 file changed

+27
-1
lines changed

engine/src/conversion/codegen_rs/non_pod_struct.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@ pub(crate) fn make_non_pod(s: &mut ItemStruct, layout: Option<Layout>) {
3838
// (if we have layout information from bindgen we use that instead)
3939
// (2) We want to ensure the type is !Unpin
4040
// (3) We want to ensure it's not Send or Sync
41+
// In addition, we want to avoid UB:
42+
// (4) By marking the data as MaybeUninit we ensure there's no UB
43+
// by Rust assuming it's initialized
44+
// (5) By marking it as UnsafeCell we perhaps help reduce aliasing UB.
45+
// This is on the assumption that references to this type may pass
46+
// through C++ and get duplicated, so there may be multiple Rust
47+
// references to the same underlying data.
48+
// The correct solution to this is to put autocxx into the mode
49+
// where it uses CppRef<T> instead of Rust references, but otherwise,
50+
// using UnsafeCell here may help a bit. It definitely does not
51+
// eliminate the UB here for the following reasons:
52+
// a) The references floating around are to the outer type, not the
53+
// data stored within the UnsafeCell.
54+
// b) C++ may have multiple mutable references, or may have mutable
55+
// references coexisting with immutable references, and no amount
56+
// of UnsafeCell can make that safe.
57+
// Nevertheless the use of UnsafeCell here may (*may*) reduce the
58+
// opportunities for aliasing UB. Again, the only actual way to
59+
// eliminate UB is to use CppRef<T> everywhere instead of &T and &mut T.
4160
//
4261
// For opaque types, the Rusty opaque structure could in fact be generated
4362
// by three different things:
@@ -51,6 +70,13 @@ pub(crate) fn make_non_pod(s: &mut ItemStruct, layout: Option<Layout>) {
5170
// much more difficult.
5271
// We use (c) for abstract types. For everything else, we do it ourselves
5372
// for maximal control. See codegen_rs/mod.rs generate_type for more notes.
73+
//
74+
// It is worth noting that our constraints here are a bit more severe than
75+
// for cxx. In the case of cxx, C++ types are usually represented as
76+
// zero-sized types within Rust. Zero-sized types, by definition, can't
77+
// have overlapping references and thus can't have aliasing UB. We can't
78+
// do that because we want C++ types to be representable on the Rust stack,
79+
// and thus we need to tell Rust their real size and alignment.
5480
// First work out attributes.
5581
let doc_attr = s
5682
.attrs
@@ -98,7 +124,7 @@ pub(crate) fn make_non_pod(s: &mut ItemStruct, layout: Option<Layout>) {
98124
Some(
99125
syn::Field::parse_named
100126
.parse2(quote! {
101-
_data: [u8; #size]
127+
_data: ::core::cell::UnsafeCell<::core::mem::MaybeUninit<[u8; #size]>>
102128
})
103129
.unwrap(),
104130
)

0 commit comments

Comments
 (0)