Skip to content

Emscripten double frees object allocated within a nested callback #24482

Open
@Aerlinger

Description

@Aerlinger

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:

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<void>("call", foo); with the same result.
  callback(foo);
}

EMSCRIPTEN_BINDINGS(demo) {
  value_object<Foo>("Foo").field("name", &Foo::name);
  function("echoFoo", &echoFoo);
}

Typescript Code

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�Q�R�RRR�R�R�R�R$R$R,R,R4R4R<R<RDRDRLRLRTRTR\R\RdRdRlRlRtRtR|R|R�R�R�R�R�R�R����0h���H=TU����������2: Destructing foo with name: innerObject�������`^haL\h�|�����_ha�\�������^ha ��������P_ha����0��^haL\�����0��_ha�\�������^ha��������_ha4�ha�N$g�^��������ha@O$g�^���ha�O$g�_��ha�N,gH_�����d�ha@O,g�_d�ha�O,g�_�8h���������������
�������������������������������������������������������������������������������������(���1|a�a�a�f�gg�g$g,g8g@gHgPgXgpg�g�g�g�g�g�g�g�g�g�g�g�g�g�g�h�h�h$h�C�ha�ha�������������������������������	���������
example.js:8 ���������(;�H;��1�P1�7��7�X5�(�48��9��8��9��5�.,�5�.,h;�`<�4=� >��B��B�C��C��C��D�DE��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>(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<char>::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}

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions