简体   繁体   English

Rust WebAssembly自定义元素内存释放错误

[英]Rust WebAssembly Custom Elements Memory Deallocation Error

My first Rust-produced WASM is producing the following error, I have no idea how to go about it debugging. 我的第一个Rust生成的WASM产生了以下错误,我不知道如何进行调试。

wasm-000650c2-23:340 Uncaught RuntimeError: memory access out of bounds
    at dlmalloc::dlmalloc::Dlmalloc::free::h36961b6fbcc40c05 (wasm-function[23]:670)
    at __rdl_dealloc (wasm-function[367]:8)
    at __rust_dealloc (wasm-function[360]:7)
    at alloc::alloc::dealloc::h90df92e1f727e726 (wasm-function[146]:100)
    at <alloc::alloc::Global as core::alloc::Alloc>::dealloc::h7f22ab187c7f5835 (wasm-function[194]:84)
    at <alloc::raw_vec::RawVec<T, A>>::dealloc_buffer::hdce29184552be976 (wasm-function[82]:231)
    at <alloc::raw_vec::RawVec<T, A> as core::ops::drop::Drop>::drop::h3910dccc175e44e6 (wasm-function[269]:38)
    at core::ptr::real_drop_in_place::hd26be2408c00ce9d (wasm-function[267]:38)
    at core::ptr::real_drop_in_place::h6acb013dbd13c114 (wasm-function[241]:50)
    at core::ptr::real_drop_in_place::hb270ba635548ab74 (wasm-function[69]:192)

The context: latest Chrome, Rust wasm-bindgen code called from a TypeScript custom element, operating upon a canvas in the shadow DOM. 上下文:最新的Chrome,Rust ism-bindgen代码,从TypeScript自定义元素调用,在shadow DOM中的画布上运行。 Data rendered to the canvas comes from an HTML5 AudioBuffer. 呈现给画布的数据来自HTML5 AudioBuffer。 All rust variables are locally scoped. 所有生锈变量都是局部范围的。

The web component works perfectly if only one instance appears in the document, but if I further instances, a stack trace is dumped as above. 如果文档中只显示一个实例,则Web组件可以正常工作,但如果我进一步实例,则会像上面那样转储堆栈跟踪。 The code runs without any other issue. 代码运行没有任何其他问题。

I know there are outstanding memory bugs in Chrome -- is this what they look like, or can an experienced rust/wasm developer tell me if this is unusual? 我知道Chrome中存在未解决的内存错误 - 这是它们的样子,还是经验丰富的生锈/开发人员告诉我这是不寻常的?

js-sys = "0.3.19"
wasm-bindgen = "0.2.42"
wee_alloc = { version = "0.4.2", optional = true }
[dependencies.web-sys]
version = "0.3.4"

The rust code is small, and just renders two channels of an AudioBuffer to a supplied HTMLCanvasElement: 生锈代码很小,只需将AudioBuffer的两个通道呈现给提供的HTMLCanvasElement:

#[wasm_bindgen]
pub fn render(
    canvas: web_sys::HtmlCanvasElement,
    audio_buffer: &web_sys::AudioBuffer,
    stroke_style: &JsValue,
    line_width: f64,
    step_size: usize,
) { 
  // ...
    let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() }; // !
    for channel_number in 0..1 {
        channel_data[channel_number] = audio_buffer
            .get_channel_data(channel_number as u32)
            .unwrap();
    }
  // ...

I've tried commenting out functionality, and if the code doesn't touch the canvas but does the above, I get the error. 我已经尝试评论功能,如果代码没有触及画布但是上面做了,我得到了错误。 Making the below change results in a simple 'out of wam memory' error. 进行以下更改会导致简单的“out of wam memory”错误。 The audio file is is 1,200 k. 音频文件是1,200 k。

    let channel_data: [Vec<f32>; 2] = [
        audio_buffer.get_channel_data(0).unwrap(),
        audio_buffer.get_channel_data(1).unwrap()
    ];

EDIT : The latter out of memory error, for the correct code above, really threw me, but it is actually a Chrome bug . 编辑 :后者out of memory不足错误,对于上面的正确代码,真的把我扔了,但它实际上是一个Chrome错误

Your problem is that you create a chunk of uninitialized memory and don't initialize it properly: 您的问题是您创建了一块未初始化的内存,并且没有正确初始化它:

let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in 0..1 {
    channel_data[channel_number] = audio_buffer
        .get_channel_data(channel_number as u32) // no need for `as u32` here btw
        .unwrap();
}

Range s (aka a..b ) are exclusive in Rust. Range s (又名a..b )在Rust中是独占的。 This means that your loop does not iterate twice as you would suppose, but instead only once and you have one uninitialized Vec<f32> which then will panic while dropping it. 这意味着你的循环不会像你想象的那样迭代两次,而是只有一次并且你有一个未初始化的Vec<f32> 然后在放弃它时会发生恐慌。 (Please see Matthieu M.'s answer for a proper explanation) (请参阅Matthieu M.的答案以获得正确的解释)

There are a few possibilities here. 这里有一些可能性。

  1. Use the proper range, eg 0..2 使用适当的范围,例如0..2
  2. Use an inclusive range 0..=1 使用包含范围 0..=1
  3. Don't use the unsafe construct, but instead 不要使用不安全的构造,而是使用
     let mut channel_data: [Vec<f32>; 2] = Default::default() 
    This will properly initialize the two Vec s. 这将正确初始化两个Vec

For a more complete overview on how to initialize an array, see What is the proper way to initialize a fixed length array? 有关如何初始化数组的更完整概述,请参阅初始化固定长度数组的正确方法是什么?

As a sidenote: avoid using unsafe , especially if you're new to Rust. 作为旁注:避免使用unsafe ,特别是如果你是Rust的新手。

There are two issues here: 这里有两个问题:

  1. You create an uninitialized chunk of memory, and treat it as if it were initialized. 您创建一个未初始化的内存块,并将其视为已初始化。
  2. Your iteration is wrong, 0..1 iterates over [0] (it's exclusive). 你的迭代是错误的, 0..1遍历[0] (它是独占的)。

Let's check them one at a time. 我们一次检查一下。


Don't use unsafe . 不要使用unsafe

In general, you should strive to avoid unsafe . 一般来说,你应该努力避免unsafe There are very few reasons to use it, and many ways to use it incorrectly (such as here). 使用它的原因很少,并且有很多方法不正确地使用它(例如这里)。

The issue. 问题。

In this particular case: 在这种特殊情况下:

let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in /*...*/ {
    channel_data[channel_number] = /*...*/;
}

There are two issues: 有两个问题:

  1. The use of std::mem::uninitialized is deprecated because of safety reasons; 出于安全原因,不推荐使用std::mem::uninitialized ; it's a very bad idea to use it. 使用它是一个非常糟糕的主意。 Its replacement is MaybeUninitialized . 它的替代品是MaybeUninitialized
  2. Assigning to uninitialized memory is Undefined Behavior. 分配给未初始化的内存是未定义的行为。

There is no assignment operator in Rust, in order to perform an assignment the language will: Rust中没有赋值运算符,为了执行赋值,语言将:

  • Drop the previous instance. 删除上一个实例。
  • Overwrite the now unused memory. 覆盖现在未使用的内存。

Dropping raw memory which thinks it's a Vec is Undefined Behavior; 丢弃认为它是Vec原始内存是未定义的行为; in this case the likely effect is that some random pointer value is read and freed. 在这种情况下,可能的效果是读取并释放一些随机指针值。 This may crash, this may free an unrelated pointer, leading to a latter crash or memory corruption, it's BAD . 这可能会崩溃,这可能会释放一个无关的指针,导致后一次崩溃或内存损坏,这是不好的

The solution. 解决方案。

There is little reason to use unsafe here: 没有理由在这里使用unsafe

  • It is perfectly possible to safely perform the initialization of the array. 完全可以安全地执行阵列的初始化。
  • It is perfectly possible to directly initialize the array. 完全可以直接初始化数组。
  • There is little performance benefit in NOT performing default initialization if you insist on two-steps initialization, as the Default implementation of Vec does not allocate memory. 如果您坚持两步初始化,则不执行默认初始化几乎没有性能优势,因为VecDefault实现不分配内存。

In short: 简而言之:

auto create_channel = |channel_number: u32| {
    audio_buffer
        .get_channel_data(channel_number)
        .unwrap()
};

let mut channel_data = [create_channel(0), create_channel(1)];

is simple, safe, and most efficient. 简单,安全,高效。


Prefer iterators to indexing. 首选迭代器进行索引。

If you insist on two-step initialization, then use iterators rather than indexing to avoid off-by-one errors. 如果你坚持两步初始化,那么使用迭代器而不是索引来避免一个一个错误。

In your case: 在你的情况下:

let mut channel_data = [vec!(), vec!()];
for (channel_number, channel) = channel_data.iter_mut().enumerate() {
    *channel = audio_buffer
        .get_channel_data(channel_number as u32)
        .unwrap();
}

There are many utility functions on Iterator , in this particular case, enumerate will wrap the item returned by iter_mut() (a &mut Vec<f32> ) into a tuple (usize, &mut Vec<32>) : Iterator上有许多实用函数,在这种特殊情况下, enumerate会将iter_mut()返回的项(a &mut Vec<f32>(usize, &mut Vec<32>)到元组中(usize, &mut Vec<32>)

  • You have direct access to the element, no computation required. 您可以直接访问该元素,无需计算。
  • You also have the index of the element, without off-by-one errors. 您还拥有元素的索引,没有逐个错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM