emscripten-core / emscripten-core/emscripten
Heap corruption after fetch when using WASM exceptions
- Dominant language
- C++
- Stars
- 27.6k
- Forks
- 3.6k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 105
Description
This is a weird one!
After calling `emscripten_fetch`, heap-allocated memory held by members of objects allocated on the stack (such as the `test_vector` in the example below) becomes corrupted with garbage values. This **only** happens when exception mode is WASM exceptions.
I isolated a minimal example of this with `emscripten_fetch`, but I believe it's not limited to the fetch functionality, as I get similar symptoms elsewhere when enabling WASM exceptions in more complex programs.
Below is a minimal example to trigger this behaviour, with a CMakeLists to aid in setting the right flags:
main.cpp
```cpp
#include
#include
#include
struct test_struct {
unsigned int test_int{123};
std::vector test_vector{123, 456, 789};
test_struct() {
std::cout << "At construction: Test int " << test_int << ", vector " << test_vector[0] << ", " << test_vector[1] << ", " << test_vector[2] << std::endl;
}
};
void on_success(emscripten_fetch_t *fetch) {
/// Success callback
auto &test{*static_cast(fetch->userData)};
std::cout << "In success callback: Test int " << test.test_int << ", vector " << test.test_vector[0] << ", " << test.test_vector[1] << ", " << test.test_vector[2] << std::endl;
emscripten_fetch_close(fetch);
};
auto main()->int {
test_struct test;
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
std::strcpy(attr.requestMethod, "GET");
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE;
attr.userData = &test;
attr.onsuccess = on_success;
emscripten_fetch(&attr, "test.txt");
std::cout << "Immediately after fetch: Test int " << test.test_int << ", vector " << test.test_vector[0] << ", " << test.test_vector[1] << ", " << test.test_vector[2] << std::endl;
emscripten_set_main_loop_arg([](void *data){
auto &test{*reinterpret_cast(data)};
std::cout << "In main loop: Test int " << test.test_int << ", vector " << test.test_vector[0] << ", " << test.test_vector[1] << ", " << test.test_vector[2] << std::endl;
}, &test, 0, true);
std::unreachable();
}
```
CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.13)
project(client)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(EXCEPTION_HANDLING wasm CACHE STRING "Exception handling mode: none, js or wasm")
if(EXCEPTION_HANDLING STREQUAL "none")
message(STATUS "Exception handling disabled")
set(exception_compile_definitions
DISABLE_EXCEPTION_THROWING
DISABLE_EXCEPTION_CATCHING
)
set(exception_compile_options
-fignore-exceptions
-fno-exceptions
)
set(exception_link_options
-fno-except
-fignore-exceptions
)
elseif(EXCEPTION_HANDLING STREQUAL "js")
message(STATUS "Exception handling using JS (slow)")
set(exception_compile_definitions
NO_DISABLE_EXCEPTION_CATCHING
)
set(exception_compile_options
-fexceptions
)
set(exception_link_options
-fexceptions
)
elseif(EXCEPTION_HANDLING STREQUAL "wasm")
message(STATUS "Exception handling using WASM)")
set(exception_compile_definitions
NO_DISABLE_EXCEPTION_CATCHING
)
set(exception_compile_options
-fwasm-exceptions
)
set(exception_link_options
-fwasm-exceptions
)
else()
message(FATAL_ERROR "Invalid exception handling mode \"${EXCEPTION_HANDLING}\"")
endif()
add_executable(client
main.cpp
)
target_compile_options(client PRIVATE
${exception_compile_options}
)
target_link_options(client PRIVATE
-sFETCH
${exception_link_options}
-sENVIRONMENT=web
)
set(CMAKE_EXECUTABLE_SUFFIX ".html")
```
Build and run:
```sh
emcmake cmake -B build
emmake cmake --build build -j$(nproc)
emrun build/client.html
```
Example console output in Chromium 131.0.6778.108:

Example output in Firefox Nightly:

Note that we expect the `test_vector` contents to be `123`, `456`, `789` but these values get replaced after the `emscripten_fetch` call.
Switch `EXCEPTION_HANDLING` in `CMakeLists.txt` from `wasm` to `js` or `none` to observe that this issue only happens when using WASM exceptions.
Comment out `emscripten_fetch` to observe the heap corruption goes away, even in WASM exception mode.
You can create a `test.txt` file to trigger the on_success callback, but the issue occurs even if the download doesn't succeed.
Contributor guide
Assessment
This issue has not been assessed yet.