Issue with Memory Deallocation in Rust WebAssembly Custom Elements

I encountered an issue while working on my first Rust-produced WASM, and I am unsure how to debug it.

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 scenario is as follows: I am using the latest Chrome browser, with Rust wasm-bindgen code that is being called from a TypeScript custom element. This operation involves a canvas within the shadow DOM, where the data rendered to the canvas comes from an HTML5 AudioBuffer. All Rust variables are locally scoped.

When there is only one instance of the web component present in the document, everything works perfectly fine. However, when multiple instances are added, a stack trace similar to the one above is generated. The code executes without any other issues.

I am aware of the existing memory bugs in Chrome - could this be a manifestation of those bugs, or does it appear unusual to experienced Rust/WASM developers?

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 itself is relatively small and simply renders two channels of an AudioBuffer to a supplied 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 have attempted debugging by commenting out parts of the functionality. If the code does not manipulate the canvas but includes the aforementioned section, the error occurs. Making a slight change results in a straightforward 'out of wam memory' error. The size of the audio file is 1,200k.

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

EDIT: I was initially puzzled by the out of memory error message that arose after making the correction above. However, it turns out to be a known Chrome bug.

Answer №1

You are encountering an issue with uninitialized memory creation that is not initialized 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) // unnecessary `as u32` here by the way
        .unwrap();
}

The Rust Ranges (notated as a..b) are exclusive, which means the loop only iterates once instead of twice due to this. As a result, one Vec<f32> remains uninitialized and may cause panic during dropping. (Refer to Matthieu M.'s answer for detailed explanation)

There are several ways to address this issue:

  1. Utilize the correct range such as 0..2
  2. Opt for an inclusive range like 0..=1
  3. Avoid using the unsafe method and consider
    let mut channel_data: [Vec<f32>; 2] = Default::default()
    
    This ensures proper initialization of the two Vecs.

For further insights on initializing an array, refer to What is the proper way to initialize a fixed length array?

Note: It's advisable to steer clear of unsafe, especially if you're new to Rust.

Answer №2

Here are a couple of issues to address:

  1. You're manipulating uninitialized memory as if it's initialized.
  2. Your iteration logic is incorrect, as 0..1 only iterates over [0] (it's exclusive).

Let's tackle these one by one.


Avoid the use of unsafe.

It's generally best practice to steer clear of unsafe. There are few valid reasons to employ it, and plenty of opportunities to misuse it (as in this case).

The problem at hand.

In this specific scenario:

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

A couple of issues become apparent:

  1. The usage of std::mem::uninitialized is now deprecated due to safety concerns; leaning on it is highly discouraged. Its substitute is MaybeUninitialized.
  2. Assigning values to uninitialized memory ventures into Undefined Behavior territory.

Rust lacks an assignment operator; instead, when performing an assignment, the language will:

  • Discard the previous instance.
  • Overwrite the now vacant memory space.

Disposing of raw memory that mistakenly believes it's a Vec constitutes Undefined Behavior; the probable result here would involve reading and freeing some random pointer value. This could lead to crashes, unintended deallocations of unrelated pointers resulting in subsequent crashes or memory corruption—a recipe for disaster.

The fix.

There exists little justification for employing unsafe in this context:

  • Safely initializing the array can be easily achieved.
  • Direcly initializing the array is also feasible.
  • If sticking to two-step initialization is a must, sheer performance benefits from skipping default initialization are minimal since the Default implementation of Vec doesn't invoke memory allocation.

To put it succinctly:

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 straightforward, secure, and optimally efficient.


Opt for iterators rather than indexing.

If set on proceeding with dual-phase initialization, favor iterators over indexing to sidestep off-by-one errors.

In your situation:

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();
}

A multitude of utility functions exist within Iterator, with enumerate standing out in this case—it wraps the item yielded by iter_mut() (a &mut Vec<f32>) into a tuple (usize, &mut Vec<32>):

  • You gain direct access to the element sans additional computations.
  • The index of the element is also accessible, reducing the likelihood of off-by-one discrepancies.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Understanding the meaning of `.then()` in Excel JavaScript involves recognizing its function

Excel.run(function (context) { var sheet = context.workbook.worksheets.getItem("Sample"); var range = sheet.getRange("B2:C5"); range.load("address"); return context.sync() .then(function () { ...

Steps for assigning a parameter to a general purpose function

Having a generic function named retrieve: function retrieve<T>(endpoint: string, id: string, /* etc */): T {} The goal is to define a function like retrieveUser, which binds the first parameter and specifies T. An attempt was made using Function.pr ...

Discovering errors within a reactive form in Angular using a loop to iterate through the FormArray

I am currently utilizing Angular CLI version 16.2.1. As I progress through a course, I am working with reactive forms. In a list of 'Recipes', I aim to include a Recipe with various inputs and a collection of ingredients. However, I am encounter ...

Unable to find a solution to Angular response options

I'm having trouble saving an item to local storage when receiving a 200 response from the backend. It seems like the request status is not being recognized properly. options = { headers: new HttpHeaders({ 'Content-Type': &apos ...

Utilizing TS2722 alongside restrictive parameters in tsconfig

I have encountered these errors due to the presence of the strictNullChecks parameter, which is activated by the strict configuration in the tsconfig.json file. It appears that when arguments.length === 2, the ab function should be available (thanks to Fun ...

Enhance the aesthetic appeal of the imported React component with added style

I need assistance with applying different styles to an imported 'notification' component within my header component. The notification component has its own CSS style, but I want to display it in the header component with unique styling. How can I ...

What could be causing my D3.js stacked bar chart to show inaccurate results?

I'm encountering some challenges while creating a stacked bar chart in d3.js. The current appearance of my chart is depicted here (developed with D3.js): https://i.sstatic.net/G6UA6.png However, I aim to achieve a design similar to this one (crafted ...

Create a function that retrieves the value associated with a specific path within an object

I want to implement a basic utility function that can extract a specific path from an object like the following: interface Human { address: { city: { name: string; } } } const human: Human = { address: { city: { name: "Town"}}}; getIn< ...

The perplexing behavior of RxJS Observables with Mongo Cursors

Recently, I've been working on converting a mongo cursor into an observable using my own RxJS implementation. Despite finding numerous solutions online, I wanted to challenge myself by creating one from scratch. I would greatly appreciate it if someo ...

Having trouble displaying the button upon initial load using ngIf

My goal is to display a button when editing an input form. Initially, the button is hidden when the page loads but it should appear once any of the input fields are edited. I have also implemented highlighting for the input box that is being edited. Howeve ...

What are the steps to resolve warnings in an imported json file?

I am working on a Vue project where I have imported a JSON file into my TypeScript script using import jsonData from '@/assets/data1.json'; Although the data is accessible and functions correctly, I am encountering numerous warnings during the b ...

Error: The "res.json" method is not defined in CustomerComponent

FetchData(){ this.http.get("http://localhost:3000/Customers") .subscribe(data=>this.OnSuccess(data),data=>this.OnError(data)); } OnError(data:any){ console.debug(data.json()); } OnSuccess(data:any){ this.FetchData(); } SuccessGe ...

Versatile typing capabilities

Is it possible to have a function that takes a configuration object as its parameter, specifying which properties in a data object should be read? The configuration object has two properties that correspond to keys in the data object. The configuration ob ...

Attempting to create a sorting functionality using Vue and Typescript

I am trying to implement a feature where clicking on the TableHead should toggle between sorting by most stock on top and least stock on top. Currently, I have two buttons for this functionality, but it's not very user-friendly. My approach involves ...

Discover the offsetTop value of a child element in React using TypeScript

How can I retrieve the offsetTop of React children in Typescript? Here is an example of my component: export default class FadeIn extends Component { private onScroll = () => { React.Children.forEach(this.props.children, child => { // G ...

Error: module not found in yarn

https://i.sstatic.net/3zEMq.png In my yarn workspace, I have organized folders named public and server. While working with TypeScript in VS Code, I encounter an error message stating: Cannot find module 'x' Interestingly, even though the error ...

Using Typescript in combination with snowpack may result in nullish coalescing operators being generated when targeting a version lower than ES2020

I've been working on compiling my TypeScript code/packages to ensure compatibility with Safari Version less than 14. After researching, I discovered that nullish coalescing operators (??) are not allowed in the targeted version. Despite changing my t ...

Immutable.Map<K, T> used as Object in Typescript

While refactoring some TypeScript code, I encountered an issue that has me feeling a bit stuck. I'm curious about how the "as" keyword converts a Map<number, Trip> into a "Trip" object in the code snippet below. If it's not doing that, the ...

What is the proper way to use Object.entries with my specific type?

On my form, I have three fields (sku, sku_variation, name) that I want to use for creating a new product. I thought of converting the parsedData to unknown first, but it seems like a bad practice. export type TProduct = { id: string, sku: number ...

Encountering an issue in the test file when using react-router-dom v6: "The 'history' property is not found on the 'IntrinsicAttributes & RouterProps' type."

Main script: import { useContext, useEffect } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import AuthenticationContext from './AuthenticationContext'; function HandleOAuthCallbackRoute() { co ...