Skip to content

Commit 118d554

Browse files
authored
Fix unsoundness in generated Rust code (#846)
* Refactor how Rust handles ownership This commit is the next in a long line of refactors to try to model how the Rust generator handles ownership. The Rust generator has an `ownership` knob for deciding how to render types. The Rust generator additionally models ownership of resources/lists in params/results of imports/exports. Putting all of this together has led to many attempts to wrangle this in a form that's understandable but every time a bug comes up I feel like I don't understand what's going on. I've made another attempt in this commit. Here the goal is to centralize knowledge about types as much as possible. Instead of being spread out over a few methods everything is hopefully now centralized in a single type and a single "main method". All other little pieces stem from these two and are modeled to be relatively simple compared to the "main method". Whether or not this stands the test of time we'll see. This does change generated code in some situations as can be seen by the test that was updated. Nothing major should be changed, but a few more borrows are now required in places which probably should have been borrows before. More comments are found in the code about specific changes made here. * Fix unsoundness in generated Rust code This commit fixes an soundness issue in the Rust code generator's generated code. Previously unsound code was generated when: * An import had a parameter * That had either a list or a variant of an aggregate where: * Where one field was a `list<T>` or `string` * And another field was an owned resource In this situation the Rust generator uses an "owned" type for the argument, such as `MyStruct`. This is done to reflect how ownership of the resource in the argument is lost when the function is called. The problem with this, however, is that the rest of bindings generation assumes that imported arguments are all "borrowed" meaning that raw pointers from lists/strings can be passed through to the canonical ABI. This is not the case here because the argument is owned, meaning that the generated code would record an argument to be passed to the canonical ABI and then promptly deallocate the argument. The fix here is preceded by a refactoring to how Rust manages owned types to make the fix possible. The general idea for the fix though is that while `x: MyStruct` is the argument to the function all references internally are through `&x` to ensure that it remains rooted as an argument, preserving all pointers to lists and such. This unfortunately means that ownership can no longer model movement of resources and instead interior mutability must be used (since we have to move out of `&Resource<T>` since everything is borrowed). Fixing that, however, is probably not worth the complexity at this time. Closes #817 Closes bytecodealliance/wasmtime#7951
1 parent 422f6af commit 118d554

File tree

13 files changed

+678
-365
lines changed

13 files changed

+678
-365
lines changed

crates/guest-rust/src/lib.rs

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ extern crate alloc;
55
use alloc::boxed::Box;
66
use core::fmt;
77
use core::marker;
8-
use core::mem::ManuallyDrop;
98
use core::ops::{Deref, DerefMut};
9+
use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
1010

1111
#[cfg(feature = "macros")]
1212
pub use wit_bindgen_rust_macro::*;
@@ -188,7 +188,13 @@ type RawRep<T> = Option<T>;
188188
/// resources.
189189
#[repr(transparent)]
190190
pub struct Resource<T: WasmResource> {
191-
handle: u32,
191+
// NB: This would ideally be `u32` but it is not. The fact that this has
192+
// interior mutability is not exposed in the API of this type except for the
193+
// `take_handle` method which is supposed to in theory be private.
194+
//
195+
// This represents, almost all the time, a valid handle value. When it's
196+
// invalid it's stored as `u32::MAX`.
197+
handle: AtomicU32,
192198
_marker: marker::PhantomData<Box<T>>,
193199
}
194200

@@ -215,20 +221,33 @@ pub unsafe trait RustResource: WasmResource {
215221
impl<T: WasmResource> Resource<T> {
216222
#[doc(hidden)]
217223
pub unsafe fn from_handle(handle: u32) -> Self {
224+
assert!(handle != u32::MAX);
218225
Self {
219-
handle,
226+
handle: AtomicU32::new(handle),
220227
_marker: marker::PhantomData,
221228
}
222229
}
223230

231+
/// Takes ownership of the handle owned by `resource`.
232+
///
233+
/// Note that this ideally would be `into_handle` taking `Resource<T>` by
234+
/// ownership. The code generator does not enable that in all situations,
235+
/// unfortunately, so this is provided instead.
236+
///
237+
/// Also note that `take_handle` is in theory only ever called on values
238+
/// owned by a generated function. For example a generated function might
239+
/// take `Resource<T>` as an argument but then call `take_handle` on a
240+
/// reference to that argument. In that sense the dynamic nature of
241+
/// `take_handle` should only be exposed internally to generated code, not
242+
/// to user code.
224243
#[doc(hidden)]
225-
pub fn into_handle(resource: Resource<T>) -> u32 {
226-
ManuallyDrop::new(resource).handle
244+
pub fn take_handle(resource: &Resource<T>) -> u32 {
245+
resource.handle.swap(u32::MAX, Relaxed)
227246
}
228247

229248
#[doc(hidden)]
230249
pub fn handle(resource: &Resource<T>) -> u32 {
231-
resource.handle
250+
resource.handle.load(Relaxed)
232251
}
233252

234253
/// Creates a new Rust-defined resource from the underlying representation
@@ -261,7 +280,7 @@ impl<T: WasmResource> Resource<T> {
261280
T: RustResource,
262281
{
263282
unsafe {
264-
let rep = T::rep(resource.handle);
283+
let rep = T::rep(resource.handle.load(Relaxed));
265284
RawRep::take(&mut *(rep as *mut RawRep<T>)).unwrap()
266285
}
267286
}
@@ -280,7 +299,7 @@ impl<T: RustResource> Deref for Resource<T> {
280299

281300
fn deref(&self) -> &T {
282301
unsafe {
283-
let rep = T::rep(self.handle);
302+
let rep = T::rep(self.handle.load(Relaxed));
284303
RawRep::as_ref(&*(rep as *const RawRep<T>)).unwrap()
285304
}
286305
}
@@ -289,7 +308,7 @@ impl<T: RustResource> Deref for Resource<T> {
289308
impl<T: RustResource> DerefMut for Resource<T> {
290309
fn deref_mut(&mut self) -> &mut T {
291310
unsafe {
292-
let rep = T::rep(self.handle);
311+
let rep = T::rep(self.handle.load(Relaxed));
293312
RawRep::as_mut(&mut *(rep as *mut RawRep<T>)).unwrap()
294313
}
295314
}
@@ -306,7 +325,15 @@ impl<T: WasmResource> fmt::Debug for Resource<T> {
306325
impl<T: WasmResource> Drop for Resource<T> {
307326
fn drop(&mut self) {
308327
unsafe {
309-
T::drop(self.handle);
328+
match self.handle.load(Relaxed) {
329+
// If this handle was "taken" then don't do anything in the
330+
// destructor.
331+
u32::MAX => {}
332+
333+
// ... but otherwise do actually destroy it with the imported
334+
// component model intrinsic as defined through `T`.
335+
other => T::drop(other),
336+
}
310337
}
311338
}
312339
}

crates/rust/src/bindgen.rs

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,8 @@ impl Bindgen for FunctionBindgen<'_, '_> {
413413
let rt = self.gen.gen.runtime_path();
414414
let resource = dealias(self.gen.resolve, *resource);
415415
results.push(match self.gen.gen.resources[&resource].direction {
416-
Direction::Import => format!("({op}).into_handle() as i32"),
417-
Direction::Export => format!("{rt}::Resource::into_handle({op}) as i32"),
416+
Direction::Import => format!("({op}).take_handle() as i32"),
417+
Direction::Export => format!("{rt}::Resource::take_handle(&{op}) as i32"),
418418
});
419419
}
420420

@@ -435,41 +435,39 @@ impl Bindgen for FunctionBindgen<'_, '_> {
435435

436436
let dealiased_resource = dealias(resolve, *resource);
437437

438-
results.push(
439-
if let Direction::Export = self.gen.gen.resources[&dealiased_resource].direction
440-
{
441-
match handle {
442-
Handle::Borrow(_) => {
443-
let name = resolve.types[*resource]
444-
.name
445-
.as_deref()
446-
.unwrap()
447-
.to_upper_camel_case();
448-
let rt = self.gen.gen.runtime_path();
449-
format!(
450-
"{rt}::Resource::<{name}>::lift_borrow({op} as u32 as usize)"
451-
)
452-
}
453-
Handle::Own(_) => {
454-
let name = self.gen.type_path(dealiased_resource, true);
455-
format!("{name}::from_handle({op} as u32)")
456-
}
438+
let result = if let Direction::Export =
439+
self.gen.gen.resources[&dealiased_resource].direction
440+
{
441+
match handle {
442+
Handle::Borrow(_) => {
443+
let name = resolve.types[*resource]
444+
.name
445+
.as_deref()
446+
.unwrap()
447+
.to_upper_camel_case();
448+
let rt = self.gen.gen.runtime_path();
449+
format!("{rt}::Resource::<{name}>::lift_borrow({op} as u32 as usize)")
457450
}
458-
} else if prefix == "" {
459-
let name = self.gen.type_path(dealiased_resource, true);
460-
format!("{name}::from_handle({op} as u32)")
461-
} else {
462-
let tmp = format!("handle{}", self.tmp());
463-
self.handle_decls.push(format!("let {tmp};"));
464-
let name = self.gen.type_path(dealiased_resource, true);
465-
format!(
466-
"{{\n
467-
{tmp} = {name}::from_handle({op} as u32);
468-
{prefix}{tmp}
469-
}}"
470-
)
471-
},
472-
);
451+
Handle::Own(_) => {
452+
let name = self.gen.type_path(dealiased_resource, true);
453+
format!("{name}::from_handle({op} as u32)")
454+
}
455+
}
456+
} else if prefix == "" {
457+
let name = self.gen.type_path(dealiased_resource, true);
458+
format!("{name}::from_handle({op} as u32)")
459+
} else {
460+
let tmp = format!("handle{}", self.tmp());
461+
self.handle_decls.push(format!("let {tmp};"));
462+
let name = self.gen.type_path(dealiased_resource, true);
463+
format!(
464+
"{{\n
465+
{tmp} = {name}::from_handle({op} as u32);
466+
{prefix}{tmp}
467+
}}"
468+
)
469+
};
470+
results.push(result);
473471
}
474472

475473
Instruction::RecordLower { ty, record, .. } => {

0 commit comments

Comments
 (0)