emscripten-core / emscripten-core/emscripten

Emscripten double frees object allocated within a nested callback

Open
#24,482 1 comment 0 reactions 1 assignee Claimed by @brendandahl View on GitHub
embind
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�QRR R RRRRR$R$R,R,R4R4RB�B C�C�C�DDE�E�>?�?$@�@(�A(�F G
```

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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.