Skip to content

Bug: Passing std::optional<T> to emscripten::val causes marshalling error unless <emscripten/bind.h> is included #24747

@ChocolateChipKookie

Description

@ChocolateChipKookie

Bug: Passing std::optional<T> to emscripten::val causes marshalling error unless <emscripten/bind.h> is included

I encountered what appears to be a marshalling bug when passing an std::optional<T> to a emscripten::val callback function in Emscripten.

Emscripten version: 4.0.11

Minimal repro

Files

test.h

#pragma once
#include <emscripten/val.h>

class MyType {
public:
    void RunCallback(emscripten::val callback);
};

test.cpp

#include "test.h"

#include <emscripten/val.h>
// #include <emscripten/bind.h> // Adding this header resolves the behaviour
#include <optional>
#include <string>

void MyType::RunCallback(emscripten::val cb) {
    cb(std::make_optional(std::string{"Hey"})); 
}

embind.cpp

#include <emscripten/bind.h>
#include "test.h"
#include <string>

using namespace emscripten;

EMSCRIPTEN_BINDINGS(my_module) {
	register_optional<std::string>();

	class_<MyType>("MyType")
		.constructor<>()
		.function("RunCallback", &MyType::RunCallback);
}

test.html

<!DOCTYPE html>
<html>
<body>
<script src="test.js"></script>
<script>
MyModule().then(Module => {
	let value = new Module.MyType();
	value.RunCallback((e) => {
		console.log(e);
	});
});
</script>
</body>
</html>

I compile the whole thing with:

emcc -std=c++17 test.cpp embind.cpp -o test.js \
  -O3 \
  -s MODULARIZE=1 \
  -s EXPORT_NAME="MyModule" \
  -s ENVIRONMENT=web \
  -s WASM=1 \
  -s ALLOW_MEMORY_GROWTH=1 \
  --bind

Results

I expected the script in test.html to log "Hey" to the console, but instead it logs undefined.

When I compile everything in debug mode:

emcc -std=c++17 test.cpp embind.cpp -o test.js \
  -g -O0 \
  -s SAFE_HEAP=1 \
  -s ASSERTIONS=2 \
  -s MODULARIZE=1 \
  -s EXPORT_NAME="MyModule" \
  -s ENVIRONMENT=web \
  -s WASM=1 \
  -s ALLOW_MEMORY_GROWTH=1 \
  --bind

a runtime assertion failure occurs:

test.js:531 Uncaught (in promise) RuntimeError: Aborted(Assertion failed: invalid handle: 73080)
    at abort (http://localhost:8080/test.js:531:41)
    at assert (http://localhost:8080/test.js:154:5)
    at Object.toValue (http://localhost:8080/test.js:1955:5)
    at Object.fromWireType (http://localhost:8080/test.js:1986:20)
    at Object.readPointer [as readValueFromPointer] (http://localhost:8080/test.js:1484:15)
    at methodCaller<(emscripten::val) => emscripten::val> (eval at __emval_create_invoker (http://localhost:8080/test.js:2549:25), <anonymous>:4:74)
    at __emval_invoke (http://localhost:8080/test.js:2554:103)
    at test.wasm.emscripten::val emscripten::val::internalCall<(emscripten::internal::EM_INVOKER_KIND)0, emscripten::internal::WithPolicies<>, emscripten::val, std::__2::optional<std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>>>>(emscripten::_EM_VAL*, char const*, std::__2::optional<std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>>>&&) (http://localhost:8080/test.wasm:wasm-function[33]:0xb43)
    at test.wasm.emscripten::val emscripten::val::operator()<std::__2::optional<std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>>>>(std::__2::optional<std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>>>&&) const (http://localhost:8080/test.wasm:wasm-function[29]:0x911)
    at test.wasm.MyType::RunCallback(emscripten::val) (http://localhost:8080/test.wasm:wasm-function[26]:0x797)

Workaround

As written in test.cpp a workaround is including the <emscripten/bind.h> header in the places where the callback gets called with an optional.

I'm open to contributing a fix, but would appreciate any guidance on where to start investigating, particularly around how optional types are handled in val marshalling.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions