Skip to content

Futures/streams with 2+ levels of indirection leak memory #1178

Open
@alexcrichton

Description

@alexcrichton

This WIT today:

package test:foo;

world the-world {
  import x: func(x: future<list<list<u32>>>);
}

generates this write function for the vtable:

        fn write(
            future: u32,
            value: super::super::_rt::Vec<super::super::_rt::Vec<u32>>,
        ) -> ::core::pin::Pin<
            super::super::_rt::Box<dyn ::core::future::Future<Output = bool>>,
        > {
            super::super::_rt::Box::pin(async move {
                #[repr(align(4))]
                struct Buffer([::core::mem::MaybeUninit<u8>; 8]);
                let mut buffer = Buffer([::core::mem::MaybeUninit::uninit(); 8]);
                let address = buffer.0.as_mut_ptr() as *mut u8;
                unsafe {
                    let vec1 = &value;
                    let len1 = vec1.len();
                    let layout1 = super::super::_rt::alloc::Layout::from_size_align_unchecked(
                        vec1.len() * 8,
                        4,
                    );
                    let result1 = if layout1.size() != 0 {
                        let ptr = super::super::_rt::alloc::alloc(layout1).cast::<u8>();
                        if ptr.is_null() {
                            super::super::_rt::alloc::handle_alloc_error(layout1);
                        }
                        ptr
                    } else {
                        ::core::ptr::null_mut()
                    };
                    for (i, e) in vec1.into_iter().enumerate() {
                        let base = result1.add(i * 8);
                        {
                            let vec0 = e;
                            let ptr0 = vec0.as_ptr().cast::<u8>();
                            let len0 = vec0.len();
                            *base.add(4).cast::<usize>() = len0;
                            *base.add(0).cast::<*mut u8>() = ptr0.cast_mut();
                        }
                    }
                    *address.add(4).cast::<usize>() = len1;
                    *address.add(0).cast::<*mut u8>() = result1;
                }
                #[cfg(not(target_arch = "wasm32"))]
                unsafe extern "C" fn wit_import(_: u32, _: *mut u8) -> u32 {
                    unreachable!()
                }
                #[cfg(target_arch = "wasm32")]
                #[link(wasm_import_module = "[import-payload]$root")]
                extern "C" {
                    #[link_name = "[async][future-write-0]x"]
                    fn wit_import(_: u32, _: *mut u8) -> u32;
                }
                match unsafe {
                    wit_bindgen::rt::async_support::await_future_result(
                            wit_import,
                            future,
                            address,
                        )
                        .await
                } {
                    wit_bindgen::rt::async_support::AsyncWaitResult::Values(_) => true,
                    wit_bindgen::rt::async_support::AsyncWaitResult::End => false,
                    wit_bindgen::rt::async_support::AsyncWaitResult::Error(_) => {
                        unreachable!("received error while performing write")
                    }
                }
            })
        }

This notably does not include cleanup of result1 or layout1, for example:

        if layout1.size() != 0 {
            _rt::alloc::dealloc(result1.cast(), layout1);
        }

which is present when generating synchronous bindings for import x: func(x: list<list<u32>>); for example

Metadata

Metadata

Assignees

No one assigned

    Labels

    asyncRelated to async/streams in the component model.gen-rustRelated to bindings for Rust-compiled-to-WebAssembly

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions