SERVER-116053 WASM exception handling and MongoDB error code propagation (#53048)
Co-authored-by: Lee Maguire <lee.maguire@mongodb.com> Co-authored-by: Andrew Bradshaw <andrew.bradshaw@mongodb.com> Co-authored-by: Copilot <copilot@github.com> GitOrigin-RevId: d979c7e312a6e788509c978372095eb3c2c7fc0a
This commit is contained in:
parent
6151edfe1f
commit
637267bda7
@ -428,6 +428,7 @@ crate.spec(
|
||||
"stack-switching",
|
||||
"winch",
|
||||
"pulley",
|
||||
"gc",
|
||||
],
|
||||
package = "wasmtime-cli",
|
||||
version = "=44.0.1",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"checksum": "62ca5177d0a4cca5b8b4a2a74bcddd80c933b4f53e5ac79a9bbaac62e3c8e063",
|
||||
"checksum": "09e089e4d857eb519291450cae1e356a0e8486203c17893a9ded5d57f1857484",
|
||||
"crates": {
|
||||
"addr2line 0.26.1": {
|
||||
"name": "addr2line",
|
||||
@ -17706,6 +17706,7 @@
|
||||
"cranelift",
|
||||
"debug-builtins",
|
||||
"demangle",
|
||||
"gc",
|
||||
"logging",
|
||||
"parallel-compilation",
|
||||
"pooling-allocator",
|
||||
@ -17959,6 +17960,7 @@
|
||||
"component-model-async",
|
||||
"coredump",
|
||||
"cranelift",
|
||||
"gc",
|
||||
"logging",
|
||||
"parallel-compilation",
|
||||
"pooling-allocator",
|
||||
|
||||
@ -122,6 +122,16 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
|
||||
"-Wno-tautological-unsigned-enum-zero-compare",
|
||||
"-Wno-inconsistent-missing-override",
|
||||
"-Wno-instantiation-after-specialization",
|
||||
# Enable the WebAssembly exception-handling proposal
|
||||
# (https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md),
|
||||
# which defines try_table/catch/throw/exnref instructions for structured
|
||||
# exception handling in Wasm. -mllvm --wasm-use-legacy-eh=false switches
|
||||
# from the legacy try/catch opcodes (0x06/0x07) to the new
|
||||
# try_table/exnref opcodes, matching what wasi-sdk 33's eh/ runtime
|
||||
# libs are compiled with (required for wasmtime 44+ Cranelift AOT).
|
||||
"-fwasm-exceptions",
|
||||
"-mllvm",
|
||||
"--wasm-use-legacy-eh=false",
|
||||
])],
|
||||
)],
|
||||
)
|
||||
@ -146,6 +156,11 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
|
||||
)],
|
||||
)
|
||||
|
||||
# WASI SDK paths (relative to execroot), matching the default search order
|
||||
# reported by: wasm32-wasip2-clang++ -v -x c++ /dev/null -fsyntax-only
|
||||
wasi_sdk = "external/_main~_repo_rules~wasi_sdk"
|
||||
wasi_sysroot = wasi_sdk + "/share/wasi-sysroot"
|
||||
|
||||
# Linker flags
|
||||
link_flags = feature(
|
||||
name = "default_link_flags",
|
||||
@ -155,12 +170,15 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
|
||||
flag_groups = [flag_group(flags = [
|
||||
"-Wl,--gc-sections",
|
||||
"-Wl,--strip-all",
|
||||
# wasi-sdk 33 moved the exception-aware C++ runtime libs into eh/.
|
||||
"-L" + wasi_sysroot + "/lib/wasm32-wasip2/eh",
|
||||
"-lc++",
|
||||
"-lc++abi",
|
||||
"-lwasi-emulated-signal",
|
||||
"-lwasi-emulated-mman",
|
||||
"-lwasi-emulated-process-clocks",
|
||||
"-lwasi-emulated-getpid",
|
||||
"-lunwind",
|
||||
])],
|
||||
)],
|
||||
)
|
||||
@ -217,11 +235,6 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
|
||||
)],
|
||||
)
|
||||
|
||||
# WASI SDK paths (relative to execroot), matching the default search order
|
||||
# reported by: wasm32-wasip2-clang++ -v -x c++ /dev/null -fsyntax-only
|
||||
wasi_sdk = "external/_main~_repo_rules~wasi_sdk"
|
||||
wasi_sysroot = wasi_sdk + "/share/wasi-sysroot"
|
||||
|
||||
# Construct the toolchain.
|
||||
return [cc_common.create_cc_toolchain_config_info(
|
||||
ctx = ctx,
|
||||
@ -239,7 +252,7 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
|
||||
cxx_builtin_include_directories = [
|
||||
wasi_sysroot + "/include/wasm32-wasip2/c++/v1",
|
||||
wasi_sysroot + "/include/c++/v1",
|
||||
wasi_sdk + "/lib/clang/21/include",
|
||||
wasi_sdk + "/lib/clang/22/include",
|
||||
wasi_sysroot + "/include/wasm32-wasip2",
|
||||
wasi_sysroot + "/include",
|
||||
],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
"""Rule to AOT-compile a WASM component using the wasmtime CLI by default.
|
||||
"""Rule to AOT-compile a WASM component using the wasmtime CLI.
|
||||
|
||||
The wasmtime serialized format embeds engine configuration metadata. The tool
|
||||
that produces the .cwasm must be built with the same wasmtime library build
|
||||
@ -34,10 +34,10 @@ def _aot_compile_wasm_impl(ctx):
|
||||
"-C",
|
||||
"cache=no",
|
||||
# -W sets wasmtime runtime options.
|
||||
# Thes options must match the options we pass at
|
||||
# These options must match the options we pass at
|
||||
# startup or else starting the module will throw.
|
||||
"-W",
|
||||
"epoch-interruption=y",
|
||||
"epoch-interruption=y,exceptions=y",
|
||||
],
|
||||
mnemonic = "WasmAotCompile",
|
||||
progress_message = "AOT compiling %s" % input_file.short_path,
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
//
|
||||
// @tags: [
|
||||
// requires_scripting,
|
||||
// # TODO SERVER-116053: Add support for mapReduce.
|
||||
// mozjs_wasm_unsupported,
|
||||
// ]
|
||||
import {resultsEq} from "jstests/aggregation/extras/utils.js";
|
||||
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
// requires_fastcount,
|
||||
// uses_map_reduce_with_temp_collections,
|
||||
// requires_scripting,
|
||||
// # TODO SERVER-116053: Add support for mapReduce.
|
||||
// mozjs_wasm_unsupported,
|
||||
// ]
|
||||
const coll = db.mr_bigobject;
|
||||
coll.drop();
|
||||
|
||||
@ -9,8 +9,6 @@
|
||||
* requires_fcv_63,
|
||||
* requires_scripting,
|
||||
* requires_getmore,
|
||||
* # TODO SERVER-116052: Add support for $function.
|
||||
* mozjs_wasm_unsupported,
|
||||
* ]
|
||||
*/
|
||||
/**
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
// @tags: [
|
||||
// does_not_support_stepdowns,
|
||||
// uses_map_reduce_with_temp_collections,
|
||||
// requires_scripting
|
||||
// requires_scripting,
|
||||
// # TODO SERVER-116053: Add support for mapReduce.
|
||||
// mozjs_wasm_unsupported,
|
||||
// ]
|
||||
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
||||
|
||||
|
||||
@ -83,6 +83,11 @@ typedef struct {
|
||||
uint32_t line;
|
||||
uint32_t column;
|
||||
err_code_t code;
|
||||
|
||||
// Non-zero when the error originated from a DBException (e.g. uassert).
|
||||
// Holds the ErrorCodes::Error value so the bridge can rethrow with the
|
||||
// original code rather than always mapping to JSInterpreterFailure.
|
||||
uint32_t mongo_error_code;
|
||||
} wasm_mozjs_error_t;
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
@ -67,7 +67,6 @@ mongo_cc_library(
|
||||
"//src/mongo/scripting/mozjs/wasm/engine:api.cpp",
|
||||
"//src/mongo/scripting/mozjs/wasm/engine:engine.cpp",
|
||||
"//src/mongo/scripting/mozjs/wasm/engine:error.cpp",
|
||||
"//src/mongo/scripting/mozjs/wasm/engine:exception_stubs.cpp",
|
||||
],
|
||||
hdrs = [
|
||||
":api.h",
|
||||
@ -252,6 +251,7 @@ mongo_cc_library(
|
||||
"//src/mongo:base",
|
||||
"//src/mongo/db:server_options",
|
||||
"//src/mongo/db:service_context",
|
||||
"//src/mongo/db/query:query_knobs",
|
||||
"//src/mongo/scripting:scripting_common",
|
||||
"//src/mongo/util/concurrency:spin_lock",
|
||||
"@crates//:wasmtime_c",
|
||||
|
||||
@ -58,6 +58,7 @@ std::shared_ptr<WasmEngineContext> WasmEngineContext::createFromPrecompiled(cons
|
||||
wt::Config config;
|
||||
config.wasm_component_model(true);
|
||||
config.epoch_interruption(true);
|
||||
config.wasm_exceptions(true);
|
||||
|
||||
wt::Engine engine(std::move(config));
|
||||
|
||||
@ -177,7 +178,7 @@ bool MozJSWasmBridge::initialize() {
|
||||
LOGV2_DEBUG(11542332, 2, "Wasm Bridge Initializing", "ok"_attr = isInitialized());
|
||||
wc::Val optionsArg(wc::Record({{"heap-size-mb", wc::Val(_jsHeapLimitMB)}}));
|
||||
_callFunc(*_initEngineFunc, &result, 1, std::move(optionsArg));
|
||||
if (_isResultOk(result)) {
|
||||
if (_assertWitResult(result)) {
|
||||
_state.store(State::Initialized);
|
||||
} else {
|
||||
LOGV2_DEBUG(11542356,
|
||||
@ -195,10 +196,7 @@ void MozJSWasmBridge::shutdown() {
|
||||
wc::Val result(wc::WitResult::ok(std::nullopt));
|
||||
LOGV2_DEBUG(11542334, 2, "Wasm Bridge Shutting Down");
|
||||
_callFuncNoArgs(*_shutdownEngineFunc, &result, 1);
|
||||
uassert(ErrorCodes::JSInterpreterFailure,
|
||||
str::stream() << "Wasm Bridge failed shutdown: "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload()),
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result, "Wasm Bridge failed shutdown");
|
||||
LOGV2_DEBUG(11542333, 2, "Wasm Bridge Shutdown");
|
||||
_state.store(State::Uninitialized);
|
||||
}
|
||||
@ -210,11 +208,9 @@ uint64_t MozJSWasmBridge::createFunction(std::string_view source) {
|
||||
uassert(11542310,
|
||||
str::stream() << "Failed to call to create JS function " << std::string(source),
|
||||
_callFunc(*_createFunctionFunc, &result, 1, std::move(srcArg)));
|
||||
uassert(ErrorCodes::JSInterpreterFailure,
|
||||
str::stream() << "Failed to create JS function "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload())
|
||||
<< " :: source = " << std::string(source),
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result,
|
||||
str::stream()
|
||||
<< "Failed to create JS function :: source = " << std::string(source));
|
||||
const wc::Val* payload = result.get_result().payload();
|
||||
invariant(payload && payload->is_u64() && payload->get_u64());
|
||||
LOGV2_DEBUG(11542330,
|
||||
@ -236,12 +232,8 @@ StatusWith<BSONObj> MozJSWasmBridge::invokeFunction(uint64_t handle,
|
||||
return Status{ErrorCodes::Error{11542313},
|
||||
str::stream() << "Failed to call to invoke JS function number " << handle};
|
||||
}
|
||||
if (!_isResultOk(result))
|
||||
return Status{ErrorCodes::JSInterpreterFailure,
|
||||
str::stream()
|
||||
<< "Failed to invoke JS function "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload())
|
||||
<< " :: function id = " << handle};
|
||||
_assertWitResult(result,
|
||||
str::stream() << "Failed to invoke JS function :: function id = " << handle);
|
||||
if (ignoreReturn)
|
||||
return BSONObj();
|
||||
return _getReturnValueBson();
|
||||
@ -255,11 +247,10 @@ void MozJSWasmBridge::setGlobal(std::string_view name, const BSONObj& value) {
|
||||
uassert(11542312,
|
||||
str::stream() << "Failed to call to set global JS variable " << std::string(name),
|
||||
_callFunc(*_setGlobalFunc, &result, 1, std::move(nameArg), std::move(valueArg)));
|
||||
uassert(11542300,
|
||||
str::stream() << "Failed to set global JS variable "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload())
|
||||
<< " :: name = " << std::string(name),
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result,
|
||||
str::stream()
|
||||
<< "Failed to set global JS variable :: name = " << std::string(name),
|
||||
ErrorCodes::Error{11542300});
|
||||
}
|
||||
|
||||
void MozJSWasmBridge::setGlobalValue(std::string_view name, const BSONObj& value) {
|
||||
@ -271,11 +262,10 @@ void MozJSWasmBridge::setGlobalValue(std::string_view name, const BSONObj& value
|
||||
uassert(11542316,
|
||||
str::stream() << "Failed to call to set global JS value variable " << std::string(name),
|
||||
_callFunc(*_setGlobalValueFunc, &result, 1, std::move(nameArg), std::move(valueArg)));
|
||||
uassert(11542317,
|
||||
str::stream() << "Failed to set global JS value variable "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload())
|
||||
<< " :: name = " << std::string(name),
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result,
|
||||
str::stream() << "Failed to set global JS value variable :: name = "
|
||||
<< std::string(name),
|
||||
ErrorCodes::Error{11542317});
|
||||
}
|
||||
|
||||
void MozJSWasmBridge::setupEmit(boost::optional<int64_t> byteLimit) {
|
||||
@ -288,10 +278,7 @@ void MozJSWasmBridge::setupEmit(boost::optional<int64_t> byteLimit) {
|
||||
}
|
||||
wc::Val arg = wc::WitOption(stdArg);
|
||||
_callFunc(*_setupEmitFunc, &result, 1, std::move(arg));
|
||||
uassert(ErrorCodes::JSInterpreterFailure,
|
||||
str::stream() << "Wasm Bridge failed to setup-emit: "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload()),
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result, "Wasm Bridge failed to setup-emit");
|
||||
}
|
||||
|
||||
void MozJSWasmBridge::invokeMap(uint64_t handle, const BSONObj& args) {
|
||||
@ -302,11 +289,8 @@ void MozJSWasmBridge::invokeMap(uint64_t handle, const BSONObj& args) {
|
||||
uassert(11542319,
|
||||
str::stream() << "Failed to call to invoke JS function number " << handle,
|
||||
_callFunc(*_invokeMapFunc, &result, 1, std::move(arg0), std::move(arg1)));
|
||||
uassert(ErrorCodes::JSInterpreterFailure,
|
||||
str::stream() << "Failed to invoke JS function "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload())
|
||||
<< " :: function id = " << handle,
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result,
|
||||
str::stream() << "Failed to invoke JS function :: function id = " << handle);
|
||||
}
|
||||
|
||||
bool MozJSWasmBridge::invokePredicate(uint64_t handle, const BSONObj& args) {
|
||||
@ -317,11 +301,8 @@ bool MozJSWasmBridge::invokePredicate(uint64_t handle, const BSONObj& args) {
|
||||
uassert(11542339,
|
||||
str::stream() << "Failed to call to invoke JS function number " << handle,
|
||||
_callFunc(*_invokePredicateFunc, &result, 1, std::move(arg0), std::move(arg1)));
|
||||
uassert(ErrorCodes::JSInterpreterFailure,
|
||||
str::stream() << "Failed to invoke JS function "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload())
|
||||
<< " :: function id = " << handle,
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result,
|
||||
str::stream() << "Failed to invoke JS predicate :: function id = " << handle);
|
||||
const wc::Val* payload = result.get_result().payload();
|
||||
invariant(payload && payload->is_bool());
|
||||
return payload->get_bool();
|
||||
@ -344,10 +325,7 @@ BSONObj MozJSWasmBridge::drainEmitBuffer() {
|
||||
_assertUsable();
|
||||
wc::Val result(wc::WitResult::ok(std::nullopt));
|
||||
_callFuncNoArgs(*_drainEmitBufferFunc, &result, 1);
|
||||
uassert(ErrorCodes::JSInterpreterFailure,
|
||||
str::stream() << "Wasm Bridge failed to drain emit: "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload()),
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result, "Wasm Bridge failed to drain emit");
|
||||
return _extractBSON(result);
|
||||
}
|
||||
|
||||
@ -373,23 +351,38 @@ BSONObj MozJSWasmBridge::getGlobal(std::string_view name, bool implicitNull) {
|
||||
b.appendNull("__value");
|
||||
return b.obj();
|
||||
}
|
||||
uassert(11542301,
|
||||
str::stream() << "Failed to get global JS variable "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload())
|
||||
<< " :: name = " << std::string(name),
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result,
|
||||
str::stream()
|
||||
<< "Failed to get global JS variable :: name = " << std::string(name),
|
||||
ErrorCodes::Error{11542301});
|
||||
return _extractBSON(result);
|
||||
}
|
||||
|
||||
bool MozJSWasmBridge::_isResultOk(const wc::Val& result) {
|
||||
bool MozJSWasmBridge::_assertWitResult(const wc::Val& result,
|
||||
std::string errorPrefix,
|
||||
ErrorCodes::Error code) {
|
||||
if (wasm_helpers::isResultOk(result))
|
||||
return true;
|
||||
const wc::Val* payload = result.get_result().payload();
|
||||
if (payload) {
|
||||
if (wasm_helpers::isFatalWitError(*payload))
|
||||
_state.store(State::Trapped);
|
||||
else if (wasm_helpers::isOomWitError(*payload))
|
||||
_state.store(State::OOM);
|
||||
if (!payload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wasm_helpers::isFatalWitError(*payload)) {
|
||||
_state.store(State::Trapped);
|
||||
} else if (wasm_helpers::isOomWitError(*payload)) {
|
||||
_state.store(State::OOM);
|
||||
}
|
||||
|
||||
if (auto mongoCode = wasm_helpers::mozJSErrorCode(*payload);
|
||||
mongoCode != ErrorCodes::JSInterpreterFailure) {
|
||||
code = mongoCode;
|
||||
}
|
||||
|
||||
if (!errorPrefix.empty()) {
|
||||
uasserted(code,
|
||||
str::stream() << errorPrefix << ": "
|
||||
<< wasm_helpers::translateMozJSError(*payload));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -415,13 +408,9 @@ BSONObj MozJSWasmBridge::_extractBSON(const wc::Val& result) {
|
||||
BSONObj MozJSWasmBridge::_getReturnValueBson() {
|
||||
// Uses getGlobal which does not preserve JS array types (arrays become BSON objects with
|
||||
// numeric keys). Use getReturnValueWrapped() when array type preservation matters.
|
||||
// getGlobal also fails when the return value is undefined (e.g., a function with no return
|
||||
// statement). Return an empty object in that case to match MozJS behavior.
|
||||
try {
|
||||
return getGlobal(kReturnValue, true);
|
||||
} catch (const DBException&) {
|
||||
return BSONObj();
|
||||
}
|
||||
// When the return value is undefined (e.g., a function with no return statement),
|
||||
// getGlobal returns {"__value": null} via its implicitNull=true path rather than throwing.
|
||||
return getGlobal(kReturnValue, true);
|
||||
}
|
||||
|
||||
BSONObj MozJSWasmBridge::getReturnValueWrapped() {
|
||||
@ -430,10 +419,7 @@ BSONObj MozJSWasmBridge::getReturnValueWrapped() {
|
||||
uassert(11542350,
|
||||
"Failed to call get-return-value-bson",
|
||||
_callFuncNoArgs(*_getReturnValueBsonFunc, &result, 1));
|
||||
uassert(11542351,
|
||||
str::stream() << "Failed to get return value BSON "
|
||||
<< wasm_helpers::translateMozJSError(*result.get_result().payload()),
|
||||
_isResultOk(result));
|
||||
_assertWitResult(result, "Failed to get return value BSON", ErrorCodes::Error{11542351});
|
||||
return _extractBSON(result);
|
||||
}
|
||||
|
||||
|
||||
@ -137,9 +137,15 @@ private:
|
||||
// Triggers an epoch increment to interrupt WASM execution.
|
||||
void _signalInterrupt();
|
||||
|
||||
// Checks the WIT result and latches _trapped when the error code is fatal
|
||||
// (e-oom or e-internal).
|
||||
bool _isResultOk(const wc::Val& result);
|
||||
// Inspects the WIT result and handles all error cases:
|
||||
// - mongo C++ exception (mozJSErrorCode != JSInterpreterFailure): overrides code, throws
|
||||
// - fatal WIT error (e-oom, e-internal): latches Trapped
|
||||
// - non-fatal OOM (e-runtime + OOM msg): latches OOM
|
||||
// If errorPrefix is non-empty, uasserts with errorPrefix+translateMozJSError for any failure.
|
||||
// If errorPrefix is empty, returns false on failure so the caller can decide (e.g. log).
|
||||
bool _assertWitResult(const wc::Val& result,
|
||||
std::string errorPrefix = {},
|
||||
ErrorCodes::Error code = ErrorCodes::JSInterpreterFailure);
|
||||
|
||||
// Calls a WASM function with the given arguments. Latches _state and
|
||||
// uasserts on wasmtime traps so callers don't need explicit trap handling.
|
||||
|
||||
@ -2491,6 +2491,72 @@ TEST_F(WasmMozJSBridgeTest, ServerParam_DefaultLimitAllowsExecution) {
|
||||
ASSERT_TRUE(result.isOK());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Within-JS::Call exception tests (ExecutionCheck::capture() boundary)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// NumberLong.compare() with no args hits uassert(ErrorCodes::BadValue, ...) in the C++ binding
|
||||
// compiled into the WASM binary — a reliable trigger for the capture-cpp-exception path.
|
||||
// Three named JS wrappers around the native compare() call so the captured SpiderMonkey stack
|
||||
// contains multiple identifiable named frames.
|
||||
constexpr auto kNestedCallToNumberLongCompare =
|
||||
"function() {"
|
||||
" function c() { NumberLong(1).compare(); }"
|
||||
" function b() { c(); }"
|
||||
" function a() { b(); }"
|
||||
" a();"
|
||||
"}";
|
||||
|
||||
TEST_F(WasmMozJSBridgeTest, CppMongoExceptionPreservesCodeAndMessage) {
|
||||
// C++ MongoDB exceptions thrown inside WASM are wrapped in JSExceptionInfo so the JS stack is
|
||||
// attached. The original error code and message are preserved inExpand commentComment on lines
|
||||
// R2512 to R2513 JSExceptionInfo::originalError.
|
||||
auto handle = createFunction(kNestedCallToNumberLongCompare);
|
||||
ASSERT_THROWS_CODE(invokeFunction(handle, BSONObj()), DBException, ErrorCodes::BadValue);
|
||||
}
|
||||
|
||||
TEST_F(WasmMozJSBridgeTest, CppMongoExceptionDoesNotTrapBridge) {
|
||||
// A C++ exception caught cleanly at the WASM boundary (via ExecutionCheck) should not mark
|
||||
// the bridge as Trapped; the engine remains usable for subsequent calls.
|
||||
auto handle = createFunction(kNestedCallToNumberLongCompare);
|
||||
ASSERT_THROWS(invokeFunction(handle, BSONObj()), DBException);
|
||||
ASSERT_FALSE(_bridge->hasTrapped());
|
||||
// The function itself still throws the same error on re-invocation.
|
||||
ASSERT_THROWS_CODE(invokeFunction(handle, BSONObj()), DBException, ErrorCodes::BadValue);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// post-JS::Call exception tests (runSafely boundary)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// JS::Call succeeds (the function returns normally), but the returned value is an invalid
|
||||
// Timestamp whose fields have been set to out-of-range values. toBSON() detects the invalid
|
||||
// fields and throws DBException(BadValue) during BSON serialization — after JS::Call has
|
||||
// already returned true and ExecutionCheck has already cleared. runSafely catches this at
|
||||
// the extern "C" boundary and converts it to a WIT error with mongo_error_code set, so the
|
||||
// bridge can rethrow with the original BadValue code rather than a trap code.
|
||||
constexpr auto kReturnInvalidTimestamp =
|
||||
"function() {"
|
||||
" let ts = Timestamp(1, 1);"
|
||||
" ts.t = 20000000000;" // overflows uint32_t — invalid Timestamp
|
||||
" return ts;"
|
||||
"}";
|
||||
|
||||
TEST_F(WasmMozJSBridgeTest, PostJsCallExceptionPreservesCode) {
|
||||
auto handle = createFunction(kReturnInvalidTimestamp);
|
||||
ASSERT_THROWS_CODE(invokeFunction(handle, BSONObj()), DBException, ErrorCodes::BadValue);
|
||||
}
|
||||
|
||||
TEST_F(WasmMozJSBridgeTest, PostJsCallExceptionDoesNotTrapBridge) {
|
||||
// A DBException caught cleanly at the WASM boundary (via runSafely after JS::Call) should
|
||||
// not mark the bridge as Trapped; the engine remains usable for subsequent calls.
|
||||
auto handle = createFunction(kReturnInvalidTimestamp);
|
||||
ASSERT_THROWS_CODE(invokeFunction(handle, BSONObj()), DBException, ErrorCodes::BadValue);
|
||||
ASSERT_FALSE(_bridge->hasTrapped());
|
||||
// The function itself still throws the same error on re-invocation.
|
||||
ASSERT_THROWS_CODE(invokeFunction(handle, BSONObj()), DBException, ErrorCodes::BadValue);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wasm
|
||||
} // namespace mozjs
|
||||
|
||||
@ -93,6 +93,18 @@ std::string translateMozJSError(const wc::Val& mozJSError) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
ErrorCodes::Error mozJSErrorCode(const wc::Val& mozJSError) {
|
||||
if (!mozJSError.is_record())
|
||||
return ErrorCodes::JSInterpreterFailure;
|
||||
|
||||
const auto* mc = findField("mongo-code", mozJSError.get_record());
|
||||
if (mc && mc->is_u32()) {
|
||||
if (auto code = mc->get_u32(); code != 0)
|
||||
return ErrorCodes::Error(code);
|
||||
}
|
||||
return ErrorCodes::JSInterpreterFailure;
|
||||
}
|
||||
|
||||
wc::Func getMozjsFunc(wc::Instance& instance,
|
||||
wt::Store::Context ctx,
|
||||
std::string_view ifaceName,
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mongo/base/error_codes.h"
|
||||
#include "mongo/bson/bsonobj.h"
|
||||
#include "mongo/bson/bsonobjbuilder.h"
|
||||
#include "mongo/util/overloaded_visitor.h"
|
||||
@ -48,9 +49,13 @@ namespace wasm_helpers {
|
||||
|
||||
std::vector<uint8_t> readWasmFile(const std::string& path);
|
||||
|
||||
// Translates errors coming from the MozJS interpreter.
|
||||
// Translates a WIT wasm-mozjs-error record to a human-readable string.
|
||||
std::string translateMozJSError(const wc::Val& mozJSError);
|
||||
|
||||
// Extracts the mongo-code field from a wasm-mozjs-error record.
|
||||
// Returns JSInterpreterFailure if the field is absent or zero.
|
||||
ErrorCodes::Error mozJSErrorCode(const wc::Val& mozJSError);
|
||||
|
||||
const wc::Val* findField(std::string_view name, const wc::Record& record);
|
||||
|
||||
wc::Func getMozjsFunc(wc::Instance& instance,
|
||||
|
||||
@ -27,7 +27,6 @@ Key files in this directory:
|
||||
| `engine.h / engine.cpp` | `MozJSScriptEngine` — owns the SpiderMonkey runtime, prototype installer, function handle table |
|
||||
| `api.cpp` | Implements each WIT export; bridges WIT types to `MozJSScriptEngine` |
|
||||
| `error.h / error.cpp` | `ExecutionCheck` — wraps JSAPI calls and captures exceptions into `wasm_mozjs_error_t` |
|
||||
| `exception_stubs.cpp` | C++ exception / RTTI stubs (WASM has no native exception support) |
|
||||
| `utils.h` | `cabi_realloc` helper and string utilities |
|
||||
| `engine_test.cpp` | Unit tests — loads the `.wasm` module via Wasmtime's component model |
|
||||
| `linkset.bzl` | `cc_linkset` Starlark rule for collecting linker inputs into response files |
|
||||
|
||||
@ -90,6 +90,8 @@ static void fill_wit_error(exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* out,
|
||||
|
||||
out->line = in.line;
|
||||
out->column = in.column;
|
||||
|
||||
out->mongo_code = in.mongo_error_code;
|
||||
}
|
||||
|
||||
static bool return_err(exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err,
|
||||
@ -103,6 +105,31 @@ static bool return_err(exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err,
|
||||
return false;
|
||||
}
|
||||
|
||||
// Catches DBException before it escapes the WASM component boundary as an unhandled
|
||||
// exception/trap. Converts it to a WIT error with the original MongoDB error code preserved
|
||||
// so the bridge can rethrow with the exact code (e.g. BadValue) instead of a trap code.
|
||||
//
|
||||
// err is never null: the WIT canonical ABI always provides non-null storage for both
|
||||
// arms of a result<T, E> return type.
|
||||
template <typename F>
|
||||
static bool run_safely(F&& f, exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
invariant(err);
|
||||
try {
|
||||
return std::forward<F>(f)();
|
||||
} catch (const mongo::DBException& ex) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
e.code = mongo::mozjs::wasm::SM_E_RUNTIME;
|
||||
e.mongo_error_code = static_cast<uint32_t>(ex.code());
|
||||
mongo::mozjs::wasm::set_string(&e.msg, &e.msg_len, ex.what());
|
||||
return return_err(err, &e);
|
||||
} catch (const std::exception& ex) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
e.code = mongo::mozjs::wasm::SM_E_RUNTIME;
|
||||
mongo::mozjs::wasm::set_string(&e.msg, &e.msg_len, ex.what());
|
||||
return return_err(err, &e);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns false on allocation failure so callers can propagate OOM errors.
|
||||
static bool list_u8_dup(api_list_u8_t* out, const uint8_t* data, size_t len) {
|
||||
if (len == 0) {
|
||||
@ -164,75 +191,92 @@ extern "C" bool exports_mongo_mozjs_mozjs_initialize_engine(
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_startup_options_t* options,
|
||||
exports_mongo_mozjs_mozjs_ok_t* ret,
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
mongo::mozjs::wasm::wasm_mozjs_startup_options_t opt{};
|
||||
opt.heapSize = options->heap_size_mb > 0 ? options->heap_size_mb : kDefaultHeapSizeMB;
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
mongo::mozjs::wasm::wasm_mozjs_startup_options_t opt{};
|
||||
opt.heapSize = options->heap_size_mb > 0 ? options->heap_size_mb : kDefaultHeapSizeMB;
|
||||
|
||||
int64_t rc = mongo::mozjs::wasm::g_engine.init(&opt, &e);
|
||||
int64_t rc = mongo::mozjs::wasm::g_engine.init(&opt, &e);
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0; // Set the ok value
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0; // Set the ok value
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_shutdown_engine(
|
||||
exports_mongo_mozjs_mozjs_ok_t* ret, exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
int64_t rc = mongo::mozjs::wasm::g_engine.shutdown(&e);
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
g_function_count = 0;
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
int64_t rc = mongo::mozjs::wasm::g_engine.shutdown(&e);
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
g_function_count = 0;
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_interrupt_current_op(
|
||||
exports_mongo_mozjs_mozjs_ok_t* ret, exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
int64_t rc = mongo::mozjs::wasm::g_engine.interrupt(&e);
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
int64_t rc = mongo::mozjs::wasm::g_engine.interrupt(&e);
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_create_function(
|
||||
api_list_u8_t* source,
|
||||
exports_mongo_mozjs_mozjs_function_handle_t* ret,
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
|
||||
const uint8_t* bytes = source ? source->ptr : nullptr;
|
||||
size_t len = source ? source->len : 0;
|
||||
const uint8_t* bytes = source ? source->ptr : nullptr;
|
||||
size_t len = source ? source->len : 0;
|
||||
|
||||
if (len > kMaxJsSourceSize) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
mongo::mozjs::wasm::set_string(&e.msg, &e.msg_len, "JS source exceeds maximum size (1 MB)");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (len > kMaxJsSourceSize) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
mongo::mozjs::wasm::set_string(
|
||||
&e.msg, &e.msg_len, "JS source exceeds maximum size (1 MB)");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
if (g_function_count >= kMaxFunctions) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_NOMEM;
|
||||
mongo::mozjs::wasm::set_string(
|
||||
&e.msg, &e.msg_len, "Maximum function count reached (10000)");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (g_function_count >= kMaxFunctions) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_NOMEM;
|
||||
mongo::mozjs::wasm::set_string(
|
||||
&e.msg, &e.msg_len, "Maximum function count reached (10000)");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
uint64_t handle = 0;
|
||||
int64_t rc = mongo::mozjs::wasm::g_engine.createFunction(bytes, len, &handle, &e);
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
g_function_count++;
|
||||
*ret = static_cast<exports_mongo_mozjs_mozjs_function_handle_t>(handle);
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
uint64_t handle = 0;
|
||||
int64_t rc = mongo::mozjs::wasm::g_engine.createFunction(bytes, len, &handle, &e);
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
g_function_count++;
|
||||
*ret = static_cast<exports_mongo_mozjs_mozjs_function_handle_t>(handle);
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_invoke_function(
|
||||
@ -240,50 +284,59 @@ extern "C" bool exports_mongo_mozjs_mozjs_invoke_function(
|
||||
api_list_u8_t* bson,
|
||||
exports_mongo_mozjs_mozjs_ok_t* ret,
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
|
||||
if (handle == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (handle == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
mongo::BSONObj argsObj;
|
||||
if (bson && bson->ptr && bson->len > 0) {
|
||||
if (!validate_bson(bson->ptr, bson->len, &e)) {
|
||||
mongo::BSONObj argsObj;
|
||||
if (bson && bson->ptr && bson->len > 0) {
|
||||
if (!validate_bson(bson->ptr, bson->len, &e)) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
argsObj = mongo::BSONObj(reinterpret_cast<const char*>(bson->ptr));
|
||||
}
|
||||
|
||||
mongo::BSONObj outBson;
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.invokeFunction(
|
||||
static_cast<uint64_t>(handle), std::move(argsObj), &outBson, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
}
|
||||
argsObj = mongo::BSONObj(reinterpret_cast<const char*>(bson->ptr));
|
||||
}
|
||||
|
||||
mongo::BSONObj outBson;
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.invokeFunction(
|
||||
static_cast<uint64_t>(handle), std::move(argsObj), &outBson, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_get_return_value_bson(
|
||||
api_list_u8_t* ret, exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
mongo::BSONObj out;
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
mongo::BSONObj out;
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.getReturnValueBson(&out, &e));
|
||||
if (rc != mongo::mozjs::wasm::SM_OK) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
auto rc =
|
||||
static_cast<int64_t>(mongo::mozjs::wasm::g_engine.getReturnValueBson(&out, &e));
|
||||
if (rc != mongo::mozjs::wasm::SM_OK) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
if (!list_u8_dup(ret, reinterpret_cast<const uint8_t*>(out.objdata()), out.objsize())) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_NOMEM;
|
||||
mongo::mozjs::wasm::set_string(
|
||||
&e.msg, &e.msg_len, "OOM: failed to allocate BSON return buffer");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
return true;
|
||||
if (!list_u8_dup(ret, reinterpret_cast<const uint8_t*>(out.objdata()), out.objsize())) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_NOMEM;
|
||||
mongo::mozjs::wasm::set_string(
|
||||
&e.msg, &e.msg_len, "OOM: failed to allocate BSON return buffer");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_set_global(
|
||||
@ -291,56 +344,64 @@ extern "C" bool exports_mongo_mozjs_mozjs_set_global(
|
||||
api_list_u8_t* bson_value,
|
||||
exports_mongo_mozjs_mozjs_ok_t* ret,
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
|
||||
if (!name || name->len == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (!name || name->len == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
mongo::BSONObj valueObj;
|
||||
if (bson_value && bson_value->ptr && bson_value->len > 0) {
|
||||
if (!validate_bson(bson_value->ptr, bson_value->len, &e)) {
|
||||
mongo::BSONObj valueObj;
|
||||
if (bson_value && bson_value->ptr && bson_value->len > 0) {
|
||||
if (!validate_bson(bson_value->ptr, bson_value->len, &e)) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
valueObj = mongo::BSONObj(reinterpret_cast<const char*>(bson_value->ptr));
|
||||
}
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.setGlobal(
|
||||
reinterpret_cast<const char*>(name->ptr), name->len, valueObj, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
}
|
||||
valueObj = mongo::BSONObj(reinterpret_cast<const char*>(bson_value->ptr));
|
||||
}
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.setGlobal(
|
||||
reinterpret_cast<const char*>(name->ptr), name->len, valueObj, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_get_global(
|
||||
api_string_t* name, api_list_u8_t* ret, exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
|
||||
if (!name || name->len == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (!name || name->len == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
mongo::BSONObj out;
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.getGlobal(
|
||||
reinterpret_cast<const char*>(name->ptr), name->len, &out, &e));
|
||||
mongo::BSONObj out;
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.getGlobal(
|
||||
reinterpret_cast<const char*>(name->ptr), name->len, &out, &e));
|
||||
|
||||
if (rc != mongo::mozjs::wasm::SM_OK) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (rc != mongo::mozjs::wasm::SM_OK) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
if (!list_u8_dup(ret, reinterpret_cast<const uint8_t*>(out.objdata()), out.objsize())) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_NOMEM;
|
||||
mongo::mozjs::wasm::set_string(
|
||||
&e.msg, &e.msg_len, "OOM: failed to allocate global return buffer");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
return true;
|
||||
if (!list_u8_dup(ret, reinterpret_cast<const uint8_t*>(out.objdata()), out.objsize())) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_NOMEM;
|
||||
mongo::mozjs::wasm::set_string(
|
||||
&e.msg, &e.msg_len, "OOM: failed to allocate global return buffer");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_set_global_value(
|
||||
@ -348,49 +409,58 @@ extern "C" bool exports_mongo_mozjs_mozjs_set_global_value(
|
||||
api_list_u8_t* bson_element,
|
||||
exports_mongo_mozjs_mozjs_ok_t* ret,
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
|
||||
if (!name || name->len == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (!name || name->len == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
mongo::BSONObj valueObj;
|
||||
if (bson_element && bson_element->ptr && bson_element->len > 0) {
|
||||
if (!validate_bson(bson_element->ptr, bson_element->len, &e)) {
|
||||
mongo::BSONObj valueObj;
|
||||
if (bson_element && bson_element->ptr && bson_element->len > 0) {
|
||||
if (!validate_bson(bson_element->ptr, bson_element->len, &e)) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
valueObj = mongo::BSONObj(reinterpret_cast<const char*>(bson_element->ptr));
|
||||
}
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.setGlobalValue(
|
||||
reinterpret_cast<const char*>(name->ptr), name->len, valueObj, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
}
|
||||
valueObj = mongo::BSONObj(reinterpret_cast<const char*>(bson_element->ptr));
|
||||
}
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.setGlobalValue(
|
||||
reinterpret_cast<const char*>(name->ptr), name->len, valueObj, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_setup_emit(
|
||||
int64_t* maybe_byte_limit,
|
||||
exports_mongo_mozjs_mozjs_ok_t* ret,
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
|
||||
bool hasLimit = (maybe_byte_limit != nullptr);
|
||||
int64_t limit = hasLimit ? *maybe_byte_limit : 0;
|
||||
bool hasLimit = (maybe_byte_limit != nullptr);
|
||||
int64_t limit = hasLimit ? *maybe_byte_limit : 0;
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.setupEmit(limit, hasLimit, &e));
|
||||
auto rc =
|
||||
static_cast<int64_t>(mongo::mozjs::wasm::g_engine.setupEmit(limit, hasLimit, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_invoke_predicate(
|
||||
@ -398,31 +468,35 @@ extern "C" bool exports_mongo_mozjs_mozjs_invoke_predicate(
|
||||
api_list_u8_t* document,
|
||||
bool* ret,
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
|
||||
if (handle == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (handle == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
mongo::BSONObj docObj;
|
||||
if (document && document->ptr && document->len > 0) {
|
||||
if (!validate_bson(document->ptr, document->len, &e)) {
|
||||
mongo::BSONObj docObj;
|
||||
if (document && document->ptr && document->len > 0) {
|
||||
if (!validate_bson(document->ptr, document->len, &e)) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
docObj = mongo::BSONObj(reinterpret_cast<const char*>(document->ptr));
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.invokePredicate(
|
||||
static_cast<uint64_t>(handle), std::move(docObj), &result, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = result;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
}
|
||||
docObj = mongo::BSONObj(reinterpret_cast<const char*>(document->ptr));
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.invokePredicate(
|
||||
static_cast<uint64_t>(handle), std::move(docObj), &result, &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = result;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_invoke_map(
|
||||
@ -430,46 +504,55 @@ extern "C" bool exports_mongo_mozjs_mozjs_invoke_map(
|
||||
api_list_u8_t* document,
|
||||
exports_mongo_mozjs_mozjs_ok_t* ret,
|
||||
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
|
||||
if (handle == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
if (handle == 0) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
mongo::BSONObj docObj;
|
||||
if (document && document->ptr && document->len > 0) {
|
||||
if (!validate_bson(document->ptr, document->len, &e)) {
|
||||
mongo::BSONObj docObj;
|
||||
if (document && document->ptr && document->len > 0) {
|
||||
if (!validate_bson(document->ptr, document->len, &e)) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
docObj = mongo::BSONObj(reinterpret_cast<const char*>(document->ptr));
|
||||
}
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.invokeMap(
|
||||
static_cast<uint64_t>(handle), std::move(docObj), &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
}
|
||||
docObj = mongo::BSONObj(reinterpret_cast<const char*>(document->ptr));
|
||||
}
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.invokeMap(
|
||||
static_cast<uint64_t>(handle), std::move(docObj), &e));
|
||||
|
||||
if (rc == mongo::mozjs::wasm::SM_OK) {
|
||||
if (ret)
|
||||
*ret = 0;
|
||||
return true;
|
||||
}
|
||||
return return_err(err, &e);
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
extern "C" bool exports_mongo_mozjs_mozjs_drain_emit_buffer(
|
||||
api_list_u8_t* ret, exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
mongo::BSONObj out;
|
||||
return run_safely(
|
||||
[&]() -> bool {
|
||||
mongo::mozjs::wasm::wasm_mozjs_error_t e{};
|
||||
mongo::BSONObj out;
|
||||
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.drainEmitBuffer(&out, &e));
|
||||
if (rc != mongo::mozjs::wasm::SM_OK) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
auto rc = static_cast<int64_t>(mongo::mozjs::wasm::g_engine.drainEmitBuffer(&out, &e));
|
||||
if (rc != mongo::mozjs::wasm::SM_OK) {
|
||||
return return_err(err, &e);
|
||||
}
|
||||
|
||||
if (!list_u8_dup(ret, reinterpret_cast<const uint8_t*>(out.objdata()), out.objsize())) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_NOMEM;
|
||||
mongo::mozjs::wasm::set_string(&e.msg, &e.msg_len, "OOM: failed to allocate emit buffer");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
return true;
|
||||
if (!list_u8_dup(ret, reinterpret_cast<const uint8_t*>(out.objdata()), out.objsize())) {
|
||||
e.code = mongo::mozjs::wasm::SM_E_NOMEM;
|
||||
mongo::mozjs::wasm::set_string(
|
||||
&e.msg, &e.msg_len, "OOM: failed to allocate emit buffer");
|
||||
return return_err(err, &e);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
err);
|
||||
}
|
||||
|
||||
@ -29,6 +29,8 @@
|
||||
|
||||
#include "error.h"
|
||||
|
||||
#include "mongo/scripting/mozjs/common/exception.h"
|
||||
|
||||
#include "js/ErrorReport.h"
|
||||
#include "js/Exception.h"
|
||||
|
||||
@ -48,6 +50,21 @@ void ExecutionCheck::capture(err_code_t fallback) {
|
||||
|
||||
_out->code = SM_E_PENDING_EXCEPTION;
|
||||
|
||||
// Before consuming the exception via ErrorReportBuilder, check whether it
|
||||
// is a MongoStatusInfo object. statusToJSException() round-trips a
|
||||
// DBException through JS as a MongoStatusInfo, so jsExceptionToStatus()
|
||||
// can recover the original ErrorCodes::Error. We save it in
|
||||
// mongo_error_code so the bridge can rethrow with the right code.
|
||||
{
|
||||
JS::RootedValue excn(_cx);
|
||||
if (JS_GetPendingException(_cx, &excn)) {
|
||||
Status status = jsExceptionToStatus(_cx, excn, ErrorCodes::JSInterpreterFailure, "");
|
||||
if (status.code() != ErrorCodes::JSInterpreterFailure) {
|
||||
_out->mongo_error_code = static_cast<uint32_t>(status.code());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JS::ExceptionStack exnStack(_cx);
|
||||
if (!JS::StealPendingExceptionStack(_cx, &exnStack)) {
|
||||
JS_ClearPendingException(_cx);
|
||||
|
||||
@ -65,6 +65,7 @@ inline void clear_error(wasm_mozjs_error_t* e) {
|
||||
e->code = SM_OK;
|
||||
e->line = 0;
|
||||
e->column = 0;
|
||||
e->mongo_error_code = 0;
|
||||
}
|
||||
|
||||
// Execution check helper for error handling
|
||||
|
||||
@ -1,146 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2026-present MongoDB, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the Server Side Public License, version 1,
|
||||
* as published by MongoDB, Inc.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* Server Side Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the Server Side Public License
|
||||
* along with this program. If not, see
|
||||
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
||||
*
|
||||
* As a special exception, the copyright holders give permission to link the
|
||||
* code of portions of this program with the OpenSSL library under certain
|
||||
* conditions as described in each individual source file and distribute
|
||||
* linked combinations including the program with the OpenSSL library. You
|
||||
* must comply with the Server Side Public License in all respects for
|
||||
* all of the code used other than as permitted herein. If you modify file(s)
|
||||
* with this exception, you may extend this exception to your version of the
|
||||
* file(s), but you are not obligated to do so. If you do not wish to do so,
|
||||
* delete this exception statement from your version. If you delete this
|
||||
* exception statement from all source files in the program, then also delete
|
||||
* it in the license file.
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
|
||||
// Boost source_location stub
|
||||
namespace boost {
|
||||
struct source_location {
|
||||
const char* file_name() const {
|
||||
return "";
|
||||
}
|
||||
const char* function_name() const {
|
||||
return "";
|
||||
}
|
||||
unsigned int line() const {
|
||||
return 0;
|
||||
}
|
||||
unsigned int column() const {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
} // namespace boost
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Exception allocation
|
||||
void* __cxa_allocate_exception(size_t thrown_size) {
|
||||
(void)thrown_size;
|
||||
std::fprintf(stderr,
|
||||
"FATAL: __cxa_allocate_exception called - exceptions not supported in WASM\n");
|
||||
__builtin_trap();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void __cxa_free_exception(void* thrown_exception) {
|
||||
(void)thrown_exception;
|
||||
std::fprintf(stderr, "FATAL: __cxa_free_exception called - exceptions not supported in WASM\n");
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
// Throwing
|
||||
void __cxa_throw(void* thrown_exception, void* tinfo, void (*dest)(void*)) {
|
||||
(void)thrown_exception;
|
||||
(void)tinfo;
|
||||
(void)dest;
|
||||
std::fprintf(stderr, "FATAL: __cxa_throw called - exceptions not supported in WASM\n");
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
void __cxa_rethrow(void) {
|
||||
std::fprintf(stderr, "FATAL: __cxa_rethrow called - exceptions not supported in WASM\n");
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
// Catching
|
||||
void* __cxa_begin_catch(void* exception_object) {
|
||||
(void)exception_object;
|
||||
std::fprintf(stderr, "FATAL: __cxa_begin_catch called - exceptions not supported in WASM\n");
|
||||
__builtin_trap();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void __cxa_end_catch(void) {
|
||||
std::fprintf(stderr, "FATAL: __cxa_end_catch called - exceptions not supported in WASM\n");
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
// WASM-specific exception handling
|
||||
struct __wasm_lpad_context_t {
|
||||
int selector;
|
||||
void* exception;
|
||||
};
|
||||
struct __wasm_lpad_context_t __wasm_lpad_context = {0, nullptr};
|
||||
|
||||
int _Unwind_CallPersonality(void* exception_object) {
|
||||
(void)exception_object;
|
||||
std::fprintf(stderr,
|
||||
"FATAL: _Unwind_CallPersonality called - exceptions not supported in WASM\n");
|
||||
__builtin_trap();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Additional exception-related functions
|
||||
void* __cxa_get_exception_ptr(void* exception_object) {
|
||||
(void)exception_object;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void __cxa_pure_virtual(void) {
|
||||
std::fprintf(stderr, "FATAL: pure virtual function called\n");
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
void __cxa_deleted_virtual(void) {
|
||||
std::fprintf(stderr, "FATAL: deleted virtual function called\n");
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
// Boost exception stubs
|
||||
namespace boost {
|
||||
|
||||
void throw_exception(std::exception const& e) {
|
||||
std::fprintf(stderr, "FATAL: boost::throw_exception called: %s\n", e.what());
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
void throw_exception(std::exception const& e, boost::source_location const& loc) {
|
||||
std::fprintf(stderr,
|
||||
"FATAL: boost::throw_exception called at %s:%u: %s\n",
|
||||
loc.file_name(),
|
||||
loc.line(),
|
||||
e.what());
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
} // namespace boost
|
||||
@ -32,6 +32,8 @@
|
||||
#include "mongo/bson/bsonobj.h"
|
||||
#include "mongo/bson/bsonobjbuilder.h"
|
||||
#include "mongo/bson/bsontypes_util.h"
|
||||
#include "mongo/bson/util/builder.h"
|
||||
#include "mongo/db/query/query_execution_knobs_gen.h"
|
||||
#include "mongo/scripting/config_engine_gen.h"
|
||||
#include "mongo/scripting/deadline_monitor.h"
|
||||
#include "mongo/scripting/mozjs/wasm/wasmtime_engine.h"
|
||||
@ -184,9 +186,9 @@ void WasmtimeImplScope::injectNative(const char* field, NativeFunction func, voi
|
||||
|
||||
_emitCallback = func;
|
||||
_emitCallbackData = data;
|
||||
// Set 16 MB byte limit for the emit buffer,
|
||||
// which is allocated inside WASM linear memory.
|
||||
_bridge->setupEmit(16 * 1024 * 1024);
|
||||
// Margin lets WASM buffer one over-limit doc so the host's EmitState sees
|
||||
// it during drain and can throw, instead of WASM silently dropping it.
|
||||
_bridge->setupEmit(internalQueryMaxJsEmitBytes.load() + BSONObjMaxInternalSize);
|
||||
}
|
||||
|
||||
BSONObj WasmtimeImplScope::_resolveGlobal(const char* field) const {
|
||||
|
||||
@ -38,6 +38,11 @@ interface mozjs {
|
||||
|
||||
line: u32,
|
||||
column: u32,
|
||||
|
||||
/// MongoDB ErrorCodes::Error value, non-zero when the error originated from
|
||||
/// a DBException (e.g. uassert). Allows the bridge to rethrow with the
|
||||
/// original error code rather than always mapping to JSInterpreterFailure.
|
||||
mongo-code: u32,
|
||||
}
|
||||
|
||||
type ok = u16;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user