emscripten-core / emscripten-core/emscripten
Emscripten double frees object allocated within a nested callback
- Dominant language
- C++
- Stars
- 27.6k
- Forks
- 3.6k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 105
Description
When allocating a value object within a callback function exposed to emscripten, that value object is destructed multiple times (see Typescript example).
See below for details and ASAN failures.
**Version of emscripten/emsdk:**
4.0.4
**Flags Used:**
```
"-sMODULARIZE=1",
"-fsanitize=address",
"-sALLOW_MEMORY_GROWTH=1",
# Also tried the following flags
# "-sWASM=1",
# "-sMALLOC=emmalloc",
```
C++ Code:
```cpp
static int global_id = 1;
struct Foo {
Foo() : id(global_id++) {
std::cout << id << ": Constructing Foo" << std::endl;
}
~Foo() {
std::cout << id << ": Destructing foo with name: " << name << std::endl;
}
std::string name;
private:
// Private id is only used to uniquely identify the object in the logs.
const int id;
};
// const-ref is used here, but happens with value objects too.
void echoFoo(const Foo& foo, emscripten::val callback) {
// I also tried callback.call("call", foo); with the same result.
callback(foo);
}
EMSCRIPTEN_BINDINGS(demo) {
value_object("Foo").field("name", &Foo::name);
function("echoFoo", &echoFoo);
}
```
Typescript Code
```ts
const module: MainModule = await loadWasm();
const outerFoo = {
x: "outerObject",
};
// Note the nested callbacks. This seems to be the crux of the issue.
module.echoFoo(outerFoo, (outerResponse: Foo) => {
// This `innerFoo` gets destructed multiple times.
const innerFoo = {
x: "innerObject",
};
module.echoFoo(innerFoo, (innerResponse: Foo) => {
});
});
```
Console output (note the destructor is being called for the inner object multiple times):
```
example.js:8 1: Destructing Foo with name: outerObject
example.js:8 2: Constructing Foo
example.js:8 2: Destructing Foo with name: innerObject
example.js:8 2: Destructing Foo with name: innerObject
example.js:8 2: Destructing Foo with name: �Q�Q�i�i�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�QRRRRRRRR$R$R,R,R4R4RB�BC�C�C�DDE�E�>?�?$@�@(�A(�FG
```
with `-fsanitize=address`
```
example.js 1566: Constructing Foo
example.js:1566 1: Destructing foo with name: outerObject
example.js:1566 2: Constructing Foo
example.js:1566 2: Destructing foo with name: innerObject
example.js:1566 2: Destructing foo with name: innerObject
example.js:1566 2: Destructing foo with name:
example.js:1599 ==42==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x0000001f in thread T0
example.js:1599 #0 0x0004241d in free+0x4241d (this.program+0x4241d)
example.js:1599 #1 0x00033dc7 in operator delete(void*)+0x33dc7 (this.program+0x33dc7)
example.js:1599 #2 0x00033dcf in operator delete(void*, unsigned long)+0x33dcf (this.program+0x33dcf)
example.js:1599 #3 0x000095db in void std::__2::__libcpp_operator_delete[abi:nn190106](void*, unsigned long)+0x95db (this.program+0x95db)
example.js:1599 #4 0x0000951b in void std::__2::__do_deallocate_handle_size[abi:nn190106]<>(void*, unsigned long)+0x951b (this.program+0x951b)
example.js:1599 #5 0x00009411 in std::__2::__libcpp_deallocate[abi:nn190106](void*, unsigned long, unsigned long)+0x9411 (this.program+0x9411)
example.js:1599 #6 0x000092aa in std::__2::allocator::deallocate[abi:nn190106](char*, unsigned long)+0x92aa (this.program+0x92aa)
example.js:1599
example.js:1599 Address 0x0000001f is located in the shadow gap area.
example.js:1599 SUMMARY: AddressSanitizer: bad-free (this.program+0x42419) in free+0x42419
example.js:1599 ==42==ABORTING
tslib_closure.js:170 Uncaught (in promise)
ExitStatus {name: 'ExitStatus', message: 'Program terminated with exit(1)', status: 1}
```
Contributor guide
Assessment
This issue has not been assessed yet.