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:
CALVIN NGUYEN 2026-05-06 07:10:21 -07:00 committed by MongoDB Bot
parent 6151edfe1f
commit 637267bda7
22 changed files with 504 additions and 443 deletions

View File

@ -428,6 +428,7 @@ crate.spec(
"stack-switching",
"winch",
"pulley",
"gc",
],
package = "wasmtime-cli",
version = "=44.0.1",

View File

@ -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",

View File

@ -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",
],

View File

@ -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,

View File

@ -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";

View File

@ -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();

View File

@ -9,8 +9,6 @@
* requires_fcv_63,
* requires_scripting,
* requires_getmore,
* # TODO SERVER-116052: Add support for $function.
* mozjs_wasm_unsupported,
* ]
*/
/**

View File

@ -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";

View File

@ -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

View File

@ -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",

View File

@ -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);
}

View File

@ -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.

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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 |

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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;