SERVER-115422 Implement the WASM MozJS wrapper and WIT exported interface (#48033)

Co-authored-by: Andrew Bradshaw <andrew.bradshaw@mongodb.com>
GitOrigin-RevId: ba0a73f930f7120fa0fecef4611f43e00d2f0b91
This commit is contained in:
Lee Maguire 2026-03-06 23:09:30 +00:00 committed by MongoDB Bot
parent 8bb52ae9bf
commit 8ceb6ed988
94 changed files with 10086 additions and 397 deletions

View File

@ -57,6 +57,28 @@ filegroup(
] * 5,
)
http_file(
name = "mozjs_wasm",
downloaded_file_path = "mozjs_wasm_api.wasm",
sha256 = "3d5a780b657fdfe66cc2f45f141e9e89e9bcb5ab7a1c90f0fb4778b5c0080b15",
urls = [
# Implements retry by relisting each url multiple times to be used as a failover.
# TODO(SERVER-86719): Re-implement http_archive to allow sleeping between retries
"https://mdb-build-public.s3.us-east-1.amazonaws.com/wasm_scratch/mozjs_wasm_api.wasm",
] * 5,
)
http_file(
name = "spidermonkey_wasip2_dist",
downloaded_file_path = "spidermonkey-wasip2-release.tar.gz",
sha256 = "f528e5730721559781aef47d355079da63185588bb14ebb6bf5266d4aa5e3eb0",
urls = [
# Implements retry by relisting each url multiple times to be used as a failover.
# TODO(SERVER-86719): Re-implement http_archive to allow sleeping between retries
"https://mdb-build-public.s3.us-east-1.amazonaws.com/wasm_scratch/spidermonkey-wasip2-release.tar.gz",
] * 5,
)
# SourceGraph indexer
http_file(
name = "scip-clang",
@ -362,3 +384,14 @@ wasi_deps = use_repo_rule(
)
wasi_deps(name = "wasi_sdk")
spidermonkey_repo = use_repo_rule(
"//src/mongo/scripting/mozjs/wasm:spidermonkey_repo.bzl",
"spidermonkey_repository",
)
spidermonkey_repo(
name = "spidermonkey",
repository_file = "//src/mongo/scripting/mozjs/wasm/spider-monkey:spider-monkey-repository",
version_file = "//src/mongo/scripting/mozjs/wasm/spider-monkey:spider-monkey-version",
)

2
MODULE.bazel.lock generated
View File

@ -1306,7 +1306,7 @@
"usagesDigest": "9e6XGcxkYrSsu0+gftPsVGZrX02VFK68P4HmulrwVAE=",
"recordedFileInputs": {
"@@//src/third_party/wasmtime/BUILD.bazel.wasmtime": "f8f59cc6f0f55ead667c8b438e3d056250807cbb565b0cfe1c4b7bce196ca22f",
"@@//MODULE.bazel": "8fc6eef7f959c870cba70c101a56f6e7148dfde24ce476d7f02f0e4002c929ea",
"@@//MODULE.bazel": "dc17238883ec055ae6309e1489ddfec05de2c74dd5fd3671ce4153b9316d4a46",
"@@rules_rust~~rust_host_tools~rust_host_tools//rust_host_tools": "ENOENT"
},
"recordedDirentsInputs": {},

View File

@ -18,7 +18,6 @@ BOOST_DEFINES = [
# our current Xcode 12 doesn't offer std::atomic_ref, so we cannot.
"BOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF",
"BOOST_LOG_NO_SHORTHAND_NAMES",
"BOOST_LOG_USE_NATIVE_SYSLOG",
"BOOST_LOG_WITHOUT_THREAD_ATTR",
"BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS",
"BOOST_SYSTEM_NO_DEPRECATED",
@ -27,6 +26,10 @@ BOOST_DEFINES = [
] + select({
"@//bazel/config:linkdynamic_not_shared_archive": ["BOOST_LOG_DYN_LINK"],
"@//conditions:default": [],
}) + select({
"@platforms//os:windows": ["BOOST_LOG_WITHOUT_SYSLOG"],
"@platforms//os:wasi": ["BOOST_LOG_WITHOUT_SYSLOG"],
"@//conditions:default": ["BOOST_LOG_USE_NATIVE_SYSLOG"],
})
ENTERPRISE_DEFINES = select({

View File

@ -83,13 +83,21 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
# can be used to find it if we use a different repository rule later.
"--sysroot={}".format("external/_main~_repo_rules~wasi_sdk/share/wasi-sysroot"),
"-fno-common",
"-fno-omit-frame-pointer",
"-Oz",
"-ffunction-sections",
"-fdata-sections",
"-fvisibility=hidden",
"-U_FORTIFY_SOURCE",
"-D_FORTIFY_SOURCE=0",
# WASI emulation shims - enable syscall emulation for POSIX APIs
"-D_WASI_EMULATED_SIGNAL",
"-D_WASI_EMULATED_MMAN",
"-D_WASI_EMULATED_PROCESS_CLOCKS",
"-D_WASI_EMULATED_GETPID",
# WASI platform lacks syslog support
"-DBOOST_LOG_WITHOUT_SYSLOG",
])],
)],
)
@ -103,6 +111,12 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
flag_groups = [flag_group(flags = [
"-std=c++20",
"-fexceptions",
# Include headers for declarations needed by third-party code
"-include",
"wasm32-wasip2/assert.h", # assert() for Abseil
"-include",
"stdlib.h", # malloc/free for fmt
])],
)],
)
@ -115,16 +129,77 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
actions = all_link_actions,
flag_groups = [flag_group(flags = [
"-Wl,--gc-sections",
"-Wl,--strip-all",
"-lc++",
"-lc++abi",
"-lwasi-emulated-signal",
"-lwasi-emulated-mman",
"-lwasi-emulated-process-clocks",
"-lwasi-emulated-getpid",
])],
)],
)
# Include path features (mirrors the Linux toolchain).
# Without these, external-repo headers (like Abseil) are only added via
# -iquote (quoted includes) and <angled> includes fail.
all_compile = [
ACTION_NAMES.preprocess_assemble,
ACTION_NAMES.linkstamp_compile,
ACTION_NAMES.c_compile,
ACTION_NAMES.cpp_compile,
ACTION_NAMES.cpp_header_parsing,
ACTION_NAMES.cpp_module_compile,
ACTION_NAMES.clif_match,
ACTION_NAMES.objc_compile,
ACTION_NAMES.objcpp_compile,
]
include_paths_feature = feature(
name = "include_paths",
enabled = True,
flag_sets = [flag_set(
actions = all_compile,
flag_groups = [
flag_group(
flags = ["-iquote", "%{quote_include_paths}"],
iterate_over = "quote_include_paths",
),
flag_group(
flags = ["-I%{include_paths}"],
iterate_over = "include_paths",
),
flag_group(
flags = ["-isystem", "%{system_include_paths}"],
iterate_over = "system_include_paths",
),
],
)],
)
external_include_paths_feature = feature(
name = "external_include_paths",
enabled = True,
flag_sets = [flag_set(
actions = all_compile,
flag_groups = [
flag_group(
flags = ["-isystem", "%{external_include_paths}"],
iterate_over = "external_include_paths",
expand_if_available = "external_include_paths",
),
],
)],
)
# WASI SDK sysroot path (relative to execroot)
wasi_sysroot = "external/_main~_repo_rules~wasi_sdk/share/wasi-sysroot"
# Construct the toolchain.
return [cc_common.create_cc_toolchain_config_info(
ctx = ctx,
action_configs = action_configs,
tool_paths = tool_paths,
toolchain_identifier = "wasi_sdk_cc_wasip2",
host_system_name = "local",
target_system_name = "wasi",
@ -133,11 +208,18 @@ def _wasi_cc_toolchain_config_wasip2_impl(ctx):
compiler = "clang",
abi_version = "none",
abi_libc_version = "wasi",
builtin_sysroot = wasi_sysroot,
cxx_builtin_include_directories = [
wasi_sysroot + "/include",
wasi_sysroot + "/include/wasm32-wasip2",
],
features = [
compile_flags,
cxx20_flags,
link_flags,
feature(name = "supports_dynamic_linker", enabled = True),
include_paths_feature,
external_include_paths_feature,
feature(name = "supports_dynamic_linker", enabled = False),
],
)]

View File

@ -232,6 +232,7 @@ class Linter:
files_to_ignore = set(
[
"src/mongo/scripting/mozjs/shell/PosixNSPR.cpp",
"src/mongo/scripting/mozjs/wasm/wit_gen/generated/",
"src/mongo/shell/linenoise.cpp",
"src/mongo/shell/linenoise.h",
"src/mongo/shell/mk_wcwidth.cpp",

View File

@ -65,3 +65,14 @@ deadlock:mongo::(anonymous namespace)::SSLManagerOpenSSL::SSLManagerOpenSSL
race:S2Loop::FindVertex
race:S2EdgeIndex::IncrementQueryCount
race:S2RegionCoverer::NewCandidate
# SERVER-115422: wasmtime's parallel compilation (via rayon) triggers TSan false positives.
# TSan cannot unwind Rust stacks fully, so reported frames vary across runs: sometimes
# wasmtime/rayon/cranelift-specific, sometimes bare Rust stdlib functions (core::ptr,
# core::mem, alloc::, __rustc allocator). These patterns cover all observed variants.
race:rayon_core
race:wasmtime
race:cranelift
race:__rustc
race:core::ptr
race:core::mem

View File

@ -27,7 +27,6 @@
* it in the license file.
*/
#include "mongo/logv2/log_domain_global.h"
#include <cstdint>
@ -92,7 +91,14 @@ struct LogDomainGlobal::Impl {
RamLogSink,
UserAssertSink>
ConsoleBackend;
#ifndef _WIN32
// Syslog support is conditionally compiled using Boost's BOOST_LOG_USE_NATIVE_SYSLOG macro.
// This macro is defined by Boost.Log only when the platform provides a native syslog
// implementation (via <syslog.h>). Platforms that lack syslog, notably WASI and Windows
// will not have this macro defined, causing all syslog-related
// types, members, and logic to be excluded from compilation. This avoids link errors and
// unavailable-API references on those platforms without requiring us to maintain our own
// platform-detection logic.
#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
typedef CompositeBackend<boost::log::sinks::syslog_backend,
RamLogSink,
RamLogSink,
@ -118,7 +124,7 @@ struct LogDomainGlobal::Impl {
boost::shared_ptr<boost::log::sinks::unlocked_sink<ConsoleBackend>> _consoleSink;
boost::shared_ptr<boost::log::sinks::unlocked_sink<RotatableFileBackend>> _rotatableFileSink;
boost::shared_ptr<boost::log::sinks::unlocked_sink<BacktraceBackend>> _backtraceSink;
#ifndef _WIN32
#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
boost::shared_ptr<boost::log::sinks::unlocked_sink<SyslogBackend>> _syslogSink;
#endif
AtomicWord<int32_t> activeSourceThreadLocals{0};
@ -152,7 +158,7 @@ LogDomainGlobal::Impl::Impl(LogDomainGlobal& parent) : _parent(parent) {
}
Status LogDomainGlobal::Impl::configure(LogDomainGlobal::ConfigurationOptions const& options) {
#ifndef _WIN32
#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
if (options.syslogEnabled) {
// Create a backend
auto backend = boost::make_shared<SyslogBackend>(
@ -244,7 +250,7 @@ Status LogDomainGlobal::Impl::configure(LogDomainGlobal::ConfigurationOptions co
_consoleSink->set_formatter(mkFmt());
if (_rotatableFileSink)
_rotatableFileSink->set_formatter(mkFmt());
#ifndef _WIN32
#ifdef BOOST_LOG_USE_NATIVE_SYSLOG
if (_syslogSink)
_syslogSink->set_formatter(mkFmt());
#endif

View File

@ -80,6 +80,13 @@ inline NativeProcessId getCurrentNativeThreadId() {
inline NativeProcessId getCurrentNativeThreadId() {
return pthread_getthreadid_np();
}
#elif defined(__wasi__)
inline NativeProcessId getCurrentNativeThreadId() {
// WASI doesn't support threads or thread IDs, so return a constant value
// Using the SERVER-115422 ticket number as a return number for better debugging
// and searchability in logs.
return 115422;
}
#else
inline NativeProcessId getCurrentNativeThreadId() {
return ::syscall(SYS_gettid);

View File

@ -57,7 +57,8 @@
#ifdef _WIN32
#define SECURE_RANDOM_BCRYPT
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__)
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
defined(__EMSCRIPTEN__) || defined(__wasi__)
#define SECURE_RANDOM_URANDOM
#elif defined(__OpenBSD__)
#define SECURE_RANDOM_ARCFOUR

View File

@ -235,6 +235,39 @@ bool waitUntil(const void* uaddr,
return timeoutOverflow || errno != ETIMEDOUT;
}
#elif defined(__wasi__)
// WASI is single-threaded and has no futex or atomic-wait primitives.
// notify* are no-ops; waitUntil returns false (timeout) when the value
// hasn't changed because a single-threaded environment can never receive
// an external wake-up. Returning true would cause an infinite busy-spin.
void notifyOne(const void* uaddr) {
(void)uaddr;
}
void notifyMany(const void* uaddr, int nToWake) {
(void)uaddr;
(void)nToWake;
}
void notifyAll(const void* uaddr) {
(void)uaddr;
}
bool waitUntil(const void* uaddr,
uint32_t old,
boost::optional<system_clock::time_point> deadline) {
// Value already changed before we started waiting.
if (*static_cast<const uint32_t*>(uaddr) != old) {
return true;
}
// Single-threaded: the value cannot change during this call.
// Return false (timeout) to avoid infinite busy-spin.
return false;
}
#else
#error "Need an implementation of waitUntil(), notifyOne(), notifyMany(), notifyAll() for this OS"
#endif

View File

@ -1,5 +1,4 @@
load("@bazel_skylib//lib:selects.bzl", "selects")
load("//bazel:mongo_src_rules.bzl", "idl_generator", "mongo_cc_binary", "mongo_cc_library", "mongo_cc_test", "mongo_cc_unit_test")
load("//bazel:mongo_src_rules.bzl", "idl_generator", "mongo_cc_library", "mongo_cc_unit_test")
package(default_visibility = ["//visibility:public"])

View File

@ -261,14 +261,6 @@ void Scope::storedFuncMod(OperationContext* opCtx) {
[](OperationContext*, boost::optional<Timestamp>) { _lastVersion.fetchAndAdd(1); });
}
void Scope::validateObjectIdString(const string& str) {
uassert(10448, "invalid object id: length", str.size() == 24);
auto isAllHex = [](StringData s) {
return std::all_of(s.begin(), s.end(), [](char c) { return ctype::isXdigit(c); });
};
uassert(10430, "invalid object id: not hex", isAllHex(str));
}
void Scope::loadStored(OperationContext* opCtx, bool ignoreNotConnected) {
if (_localDBName.isEmpty()) {
if (ignoreNotConnected)

View File

@ -39,6 +39,7 @@
#include "mongo/db/service_context.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/js_regex.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/modules.h"
#include "mongo/util/time_support.h"
@ -67,15 +68,6 @@ struct MONGO_MOD_NEEDS_REPLACEMENT JSFile {
const StringData source;
};
struct MONGO_MOD_PUBLIC JSRegEx {
std::string pattern;
std::string flags;
JSRegEx() = default;
JSRegEx(std::string pattern, std::string flags)
: pattern(std::move(pattern)), flags(std::move(flags)) {}
};
class MONGO_MOD_OPEN Scope {
Scope(const Scope&) = delete;
Scope& operator=(const Scope&) = delete;
@ -209,8 +201,6 @@ public:
*/
static void storedFuncMod(OperationContext* opCtx);
static void validateObjectIdString(const std::string& str);
/** gets the time at which the scope was created */
Date_t getCreateTime() const {
return _createTime;

View File

@ -27,14 +27,20 @@
* it in the license file.
*/
#include "mongo/scripting/mozjs/common/scope_base.h"
#pragma once
#include <jsapi.h>
#include <string>
#include <utility>
namespace mongo::mozjs {
namespace mongo {
MozJSScopeBase* getMozJSScope(JSContext* cx) {
return static_cast<MozJSScopeBase*>(JS_GetContextPrivate(cx));
}
struct JSRegEx {
std::string pattern;
std::string flags;
} // namespace mongo::mozjs
JSRegEx() = default;
JSRegEx(std::string pattern, std::string flags)
: pattern(std::move(pattern)), flags(std::move(flags)) {}
};
} // namespace mongo

View File

@ -10,26 +10,40 @@ exports_files(
]),
)
# Type sources shared between WASI and non-WASI builds.
# status.cpp is excluded because WASI and non-WASI builds use different
# implementations (wasm stub vs common/types).
TYPES_SRCS = [
"//src/mongo/scripting/mozjs/common/types:bindata.cpp",
"//src/mongo/scripting/mozjs/common/types:bson.cpp",
"//src/mongo/scripting/mozjs/common/types:code.cpp",
"//src/mongo/scripting/mozjs/common/types:dbpointer.cpp",
"//src/mongo/scripting/mozjs/common/types:dbref.cpp",
"//src/mongo/scripting/mozjs/common/types:maxkey.cpp",
"//src/mongo/scripting/mozjs/common/types:minkey.cpp",
"//src/mongo/scripting/mozjs/common/types:nativefunction.cpp",
"//src/mongo/scripting/mozjs/common/types:numberdecimal.cpp",
"//src/mongo/scripting/mozjs/common/types:numberint.cpp",
"//src/mongo/scripting/mozjs/common/types:numberlong.cpp",
"//src/mongo/scripting/mozjs/common/types:object.cpp",
"//src/mongo/scripting/mozjs/common/types:oid.cpp",
"//src/mongo/scripting/mozjs/common/types:regexp.cpp",
"//src/mongo/scripting/mozjs/common/types:timestamp.cpp",
]
# Filegroup for WASI builds - exports source files for compilation in genrules.
# WASI builds use a stub status.cpp from the wasm package.
filegroup(
name = "wasi_sources",
srcs = glob(["*.cpp"]) + TYPES_SRCS + [
"//src/mongo/scripting/mozjs/wasm:status.cpp",
],
visibility = ["//src/mongo/scripting/mozjs/wasm:__pkg__"],
)
mongo_cc_library(
name = "mozjs_common",
srcs = glob(["*.cpp"]) + [
"//src/mongo/scripting/mozjs/common/types:bindata.cpp",
"//src/mongo/scripting/mozjs/common/types:bson.cpp",
"//src/mongo/scripting/mozjs/common/types:code.cpp",
"//src/mongo/scripting/mozjs/common/types:dbpointer.cpp",
"//src/mongo/scripting/mozjs/common/types:dbref.cpp",
"//src/mongo/scripting/mozjs/common/types:maxkey.cpp",
"//src/mongo/scripting/mozjs/common/types:minkey.cpp",
"//src/mongo/scripting/mozjs/common/types:nativefunction.cpp",
"//src/mongo/scripting/mozjs/common/types:numberdecimal.cpp",
"//src/mongo/scripting/mozjs/common/types:numberint.cpp",
"//src/mongo/scripting/mozjs/common/types:numberlong.cpp",
"//src/mongo/scripting/mozjs/common/types:object.cpp",
"//src/mongo/scripting/mozjs/common/types:oid.cpp",
"//src/mongo/scripting/mozjs/common/types:regexp.cpp",
"//src/mongo/scripting/mozjs/common/types:status.cpp",
"//src/mongo/scripting/mozjs/common/types:timestamp.cpp",
],
srcs = glob(["*.cpp"]) + TYPES_SRCS,
copts = select({
"@platforms//os:windows": [
# The default MSVC preprocessor elides commas in some cases as a
@ -41,10 +55,23 @@ mongo_cc_library(
],
"//conditions:default": [],
}),
textual_hdrs = glob(["*.defs"]),
deps = [
"//src/mongo/scripting:scripting_common",
"//src/mongo/util:buildinfo",
"//src/third_party/mozjs",
srcs_select = [
{
"//bazel/config:wasi": ["//src/mongo/scripting/mozjs/wasm:status.cpp"],
"//conditions:default": ["//src/mongo/scripting/mozjs/common/types:status.cpp"],
},
],
textual_hdrs = glob(["*.defs"]),
deps = select({
"//bazel/config:wasi": [
# WASI builds don't need scripting_common (avoids networking/SSL dependencies)
# and use SpiderMonkey headers from @spidermonkey, not //src/third_party/mozjs
"//src/mongo/util:buildinfo",
],
"//conditions:default": [
"//src/mongo/scripting:scripting_common",
"//src/mongo/util:buildinfo",
"//src/third_party/mozjs",
],
}),
)

View File

@ -32,7 +32,7 @@
#include "mongo/scripting/mozjs/common/error.h"
#include "mongo/scripting/mozjs/common/jsstringwrapper.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/types/status.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/scripting/mozjs/common/wraptype.h"
@ -45,7 +45,11 @@
#include <js/RootingAPI.h>
#include <js/TypeDecls.h>
#include <js/friend/ErrorMessages.h>
#ifdef MONGO_MOZJS_WASI_BUILD
#include "mongo/scripting/mozjs/wasm/engine/error.h"
#else
#include <mongo/scripting/mozjs/mongoErrorReportToString.h>
#endif
namespace mongo {
namespace mozjs {
@ -64,16 +68,16 @@ void mongoToJSException(JSContext* cx) {
JS::ReportUncatchableException(cx);
// If a JSAPI callback returns false without setting a pending exception, SpiderMonkey will
// treat it as an uncatchable error.
auto* scope = getMozJSScope(cx);
scope->setStatus(std::move(status));
auto* runtime = getCommonRuntime(cx);
runtime->setStatus(std::move(status));
}
}
std::string currentJSStackToString(JSContext* cx) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
JS::RootedValue error(cx);
getProto<ErrorInfo>(scope).newInstance(&error);
getProto<ErrorInfo>(runtime).newInstance(&error);
return ObjectWrapper(cx, error).getString("stack");
}
@ -127,21 +131,26 @@ Status jsExceptionToStatus(JSContext* cx,
JS::HandleValue excn,
ErrorCodes::Error altCode,
StringData altReason) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
// It's possible that we have an uncaught exception for OOM, which is reported on the
// exception status of the JSContext. We must check for this OOM exception first to ensure
// we return the correct error code and message (i.e JSInterpreterFailure). This is consistent
// with MozJSImplScope::_checkForPendingException().
// JS_IsThrowingOutOfMemoryException is a MongoDB-specific modification to
// SpiderMonkey (see src/third_party/mozjs). The WASI build uses upstream
// Firefox SpiderMonkey which doesn't have this.
#ifndef MONGO_MOZJS_WASI_BUILD
if (JS_IsThrowingOutOfMemoryException(cx, excn)) {
return Status(ErrorCodes::JSInterpreterFailure, "Out of memory");
}
#endif
if (!excn.isObject()) {
return Status(altCode, ValueWriter(cx, excn).toString());
}
if (getProto<MongoStatusInfo>(scope).instanceOf(excn)) {
if (getProto<MongoStatusInfo>(runtime).instanceOf(excn)) {
return MongoStatusInfo::toStatus(cx, excn);
}

View File

@ -32,13 +32,19 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/logv2/log.h"
#ifndef MONGO_MOZJS_WASI_BUILD
#include "mongo/scripting/engine.h"
#else
#include "mongo/scripting/js_regex.h"
#endif
#include "mongo/scripting/mozjs/common/jsstringwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/util/assert_util.h"
#ifndef MONGO_MOZJS_WASI_BUILD
#include "mongo/util/buildinfo.h"
#endif
#include "mongo/util/version.h"
#include <cstddef>
@ -55,6 +61,12 @@
namespace mongo {
namespace mozjs {
#ifdef MONGO_MOZJS_WASI_BUILD
namespace wasm {
extern uint32_t g_wasmJsHeapLimitMB;
} // namespace wasm
#endif
const JSFunctionSpec GlobalInfo::freeFunctions[7] = {
MONGO_ATTACH_JS_FUNCTION(sleep),
MONGO_ATTACH_JS_FUNCTION(gc),
@ -102,18 +114,27 @@ void GlobalInfo::Functions::version::call(JSContext* cx, JS::CallArgs args) {
}
void GlobalInfo::Functions::buildInfo::call(JSContext* cx, JS::CallArgs args) {
#ifdef MONGO_MOZJS_WASI_BUILD
// WASI builds don't have getBuildInfo() - return empty object
BSONObjBuilder b;
ValueReader(cx, args.rval()).fromBSON(b.obj(), nullptr, false);
#else
BSONObjBuilder b;
getBuildInfo().serialize(&b);
ValueReader(cx, args.rval()).fromBSON(b.obj(), nullptr, false);
#endif
}
void GlobalInfo::Functions::getJSHeapLimitMB::call(JSContext* cx, JS::CallArgs args) {
ValueReader(cx, args.rval()).fromDouble(mongo::getGlobalScriptEngine()->getJSHeapLimitMB());
#ifdef MONGO_MOZJS_WASI_BUILD
ValueReader(cx, args.rval()).fromDouble(static_cast<double>(wasm::g_wasmJsHeapLimitMB));
#else
ValueReader(cx, args.rval()).fromDouble(getGlobalScriptEngine()->getJSHeapLimitMB());
#endif
}
void GlobalInfo::Functions::gc::call(JSContext* cx, JS::CallArgs args) {
getMozJSScope(cx)->gc();
getCommonRuntime(cx)->gc();
args.rval().setUndefined();
}
@ -123,7 +144,7 @@ void GlobalInfo::Functions::sleep::call(JSContext* cx, JS::CallArgs args) {
args.length() == 1 && args.get(0).isNumber());
int64_t duration = ValueWriter(cx, args.get(0)).toInt64();
getMozJSScope(cx)->sleep(Milliseconds(duration));
getCommonRuntime(cx)->sleep(Milliseconds(duration));
args.rval().setUndefined();
}

View File

@ -29,7 +29,7 @@
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/util/assert_util.h"
#include <js/Id.h>
@ -63,7 +63,7 @@ InternedStringTable::~InternedStringTable() {
}
InternedStringId::InternedStringId(JSContext* cx, InternedString id)
: _id(cx, getMozJSScope(cx)->getInternedStringId(id)) {}
: _id(cx, getCommonRuntime(cx)->getInternedStringId(id)) {}
} // namespace mozjs
} // namespace mongo

View File

@ -33,8 +33,9 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/util/builder.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/js_regex.h"
#include "mongo/scripting/mozjs/common/idwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/types/bson.h"
#include "mongo/scripting/mozjs/common/types/dbref.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
@ -599,9 +600,9 @@ void ObjectWrapper::callMethod(JS::HandleValue fun, JS::MutableHandleValue out)
}
BSONObj ObjectWrapper::toBSON() {
auto* scope = getMozJSScope(_context);
if (getProto<BSONInfo>(scope).instanceOf(_object) ||
getProto<DBRefInfo>(scope).instanceOf(_object)) {
auto* runtime = getCommonRuntime(_context);
if (getProto<BSONInfo>(runtime).instanceOf(_object) ||
getProto<DBRefInfo>(runtime).instanceOf(_object)) {
BSONObj* originalBSON = nullptr;
bool altered;
@ -734,9 +735,9 @@ ObjectWrapper::WriteFieldRecursionFrame::WriteFieldRecursionFrame(JSContext* cx,
}
}
auto* scope = getMozJSScope(cx);
if (getProto<BSONInfo>(scope).instanceOf(thisv) ||
getProto<DBRefInfo>(scope).instanceOf(thisv)) {
auto* runtime = getCommonRuntime(cx);
if (getProto<BSONInfo>(runtime).instanceOf(thisv) ||
getProto<DBRefInfo>(runtime).instanceOf(thisv)) {
std::tie(originalBSON, altered) = BSONInfo::originalBSON(cx, thisv);
}
}

View File

@ -38,13 +38,16 @@
#include "mongo/bson/oid.h"
#include "mongo/bson/timestamp.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/engine.h"
#include "mongo/scripting/mozjs/common/exception.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/jsstringwrapper.h"
#include "mongo/scripting/mozjs/common/lifetimestack.h"
#include "mongo/util/modules.h"
namespace mongo {
struct JSRegEx;
}
#include <cstddef>
#include <cstdint>
#include <functional>

View File

@ -0,0 +1,53 @@
/**
* 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.
*/
#pragma once
#include "mongo/base/string_data.h"
#include "mongo/util/assert_util.h"
namespace mongo {
namespace mozjs {
/**
* Validates that a string is a valid ObjectId string (24 hex characters).
* Extracted from Scope::validateObjectIdString for WASI builds.
*/
inline void validateObjectIdString(StringData str) {
uassert(11542200, "invalid object id: length", str.size() == 24);
auto isAllHex = [](StringData s) {
return std::all_of(s.begin(), s.end(), [](char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
});
};
uassert(11542201, "invalid object id: not hex", isAllHex(str));
}
} // namespace mozjs
} // namespace mongo

View File

@ -29,8 +29,6 @@
#pragma once
#include "mongo/base/string_data.h"
#include "mongo/scripting/engine.h"
#include "mongo/util/duration.h"
#include "mongo/util/modules.h"
@ -38,9 +36,14 @@
#include <cstdint>
#include <utility>
#include <jsapi.h>
#include <js/Id.h>
struct JSContext;
namespace mongo {
class Status;
class StringData;
} // namespace mongo
namespace mongo::mozjs {
@ -52,14 +55,11 @@ enum class InternedString;
struct BinDataInfo;
struct BSONInfo;
struct CodeInfo;
struct CursorHandleInfo;
struct CursorInfo;
struct DBPointerInfo;
struct DBRefInfo;
struct ErrorInfo;
struct MaxKeyInfo;
struct MinKeyInfo;
struct MongoExternalInfo;
struct MongoStatusInfo;
struct NativeFunctionInfo;
struct NumberDecimalInfo;
@ -67,194 +67,171 @@ struct NumberIntInfo;
struct NumberLongInfo;
struct OIDInfo;
struct RegExpInfo;
struct SessionInfo;
struct TimestampInfo;
struct URIInfo;
/**
* MozJS-specific scope base class.
* Abstract interface for the JS runtime services shared by both the shell
* (MozJSImplScope) and the WASM sandbox (wasm::MozJSScriptEngine).
*
* This class extends the engine-agnostic Scope class with MozJS-specific
* functionality needed by code in `scripting/mozjs/common/`.
* Provides access to common BSON type prototypes, interned strings, GC,
* status propagation, and ASAN pointer tracking.
*
* Intended inheritance hierarchy:
* Scope (scripting/engine.h)
* MozJSScopeBase
* MozJSImplScope (shell/implscope.h)
* MozJSWasmScope (wasm/implscope.h)
* The concrete shell scope composes
* the hierarchy as:
*
* MozJSImplScope : public Scope,
* public MozJSShellRuntimeInterface,
* private MozJSCommonRuntimeInterface
*
* A pointer to this interface is stored in JSContext private data and
* retrieved via getCommonRuntime(cx).
*/
class MONGO_MOD_PUB MozJSScopeBase : public Scope {
public:
~MozJSScopeBase() override = default;
struct MONGO_MOD_PUB MozJSCommonRuntimeInterface {
virtual ~MozJSCommonRuntimeInterface() = default;
virtual void gc() = 0;
virtual void sleep(Milliseconds ms) = 0;
virtual std::size_t getGeneration() const = 0;
virtual JS::HandleId getInternedStringId(InternedString name) = 0;
// Helpers used by `common/valuereader.cpp`.
virtual bool isJavaScriptProtectionEnabled() const = 0;
virtual void newFunction(StringData code, JS::MutableHandleValue out) = 0;
virtual std::int64_t* trackedNewInt64(std::int64_t value) = 0;
// Scope generation tracking (used by `common/types/bson.cpp`).
virtual std::size_t getGeneration() const = 0;
// Whether this scope requires all bound BSON objects to be owned.
virtual bool requiresOwnedObjects() const = 0;
// Store a "current status" on the scope (used by `common/exception.cpp`).
virtual void setStatus(Status status) = 0;
// Sleep for the given duration. Used by the JS `sleep()` function.
virtual void sleep(Milliseconds ms) = 0;
// Pointer tracking hooks for ASANHandles. Implementations may no-op these when not needed.
virtual void trackNewPointer(void* ptr) = 0;
virtual void trackDeletePointer(void* ptr) = 0;
// --- Prototype accessors ---
virtual WrapType<NumberLongInfo>& numberLongProto() = 0;
virtual WrapType<NumberIntInfo>& numberIntProto() = 0;
virtual WrapType<NumberDecimalInfo>& numberDecimalProto() = 0;
virtual WrapType<OIDInfo>& oidProto() = 0;
virtual WrapType<BinDataInfo>& binDataProto() = 0;
virtual WrapType<BSONInfo>& bsonProto() = 0;
virtual WrapType<CodeInfo>& codeProto() = 0;
virtual WrapType<CursorHandleInfo>& cursorHandleProto() = 0;
virtual WrapType<CursorInfo>& cursorProto() = 0;
virtual WrapType<DBPointerInfo>& dbPointerProto() = 0;
virtual WrapType<DBRefInfo>& dbRefProto() = 0;
virtual WrapType<ErrorInfo>& errorProto() = 0;
virtual WrapType<TimestampInfo>& timestampProto() = 0;
virtual WrapType<MaxKeyInfo>& maxKeyProto() = 0;
virtual WrapType<MinKeyInfo>& minKeyProto() = 0;
virtual WrapType<MongoExternalInfo>& mongoExternalProto() = 0;
virtual WrapType<MongoStatusInfo>& mongoStatusProto() = 0;
virtual WrapType<CodeInfo>& codeProto() = 0;
virtual WrapType<DBPointerInfo>& dbPointerProto() = 0;
virtual WrapType<NativeFunctionInfo>& nativeFunctionProto() = 0;
virtual WrapType<NumberDecimalInfo>& numberDecimalProto() = 0;
virtual WrapType<NumberIntInfo>& numberIntProto() = 0;
virtual WrapType<NumberLongInfo>& numberLongProto() = 0;
virtual WrapType<OIDInfo>& oidProto() = 0;
virtual WrapType<ErrorInfo>& errorProto() = 0;
virtual WrapType<MongoStatusInfo>& mongoStatusProto() = 0;
virtual WrapType<BSONInfo>& bsonProto() = 0;
virtual WrapType<DBRefInfo>& dbRefProto() = 0;
virtual WrapType<RegExpInfo>& regExpProto() = 0;
virtual WrapType<SessionInfo>& sessionProto() = 0;
virtual WrapType<TimestampInfo>& timestampProto() = 0;
virtual WrapType<URIInfo>& uriProto() = 0;
virtual void setStatus(Status status) = 0;
virtual bool isJavaScriptProtectionEnabled() const = 0;
virtual bool requiresOwnedObjects() const = 0;
virtual void newFunction(StringData code, JS::MutableHandleValue out) = 0;
// Pointer tracking for ASAN
virtual void trackNewPointer(void* ptr) = 0;
virtual void trackDeletePointer(void* ptr) = 0;
};
MONGO_MOD_PUB MozJSScopeBase* getMozJSScope(JSContext* cx);
template <typename T>
WrapType<T>& getProto(MozJSScopeBase* scope);
WrapType<T>& getProto(MozJSCommonRuntimeInterface* runtime);
template <>
inline WrapType<BinDataInfo>& getProto(MozJSScopeBase* scope) {
return scope->binDataProto();
inline WrapType<NumberLongInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->numberLongProto();
}
template <>
inline WrapType<BSONInfo>& getProto(MozJSScopeBase* scope) {
return scope->bsonProto();
inline WrapType<NumberIntInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->numberIntProto();
}
template <>
inline WrapType<CodeInfo>& getProto(MozJSScopeBase* scope) {
return scope->codeProto();
inline WrapType<NumberDecimalInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->numberDecimalProto();
}
template <>
inline WrapType<CursorHandleInfo>& getProto(MozJSScopeBase* scope) {
return scope->cursorHandleProto();
inline WrapType<OIDInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->oidProto();
}
template <>
inline WrapType<CursorInfo>& getProto(MozJSScopeBase* scope) {
return scope->cursorProto();
inline WrapType<BinDataInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->binDataProto();
}
template <>
inline WrapType<DBPointerInfo>& getProto(MozJSScopeBase* scope) {
return scope->dbPointerProto();
inline WrapType<TimestampInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->timestampProto();
}
template <>
inline WrapType<DBRefInfo>& getProto(MozJSScopeBase* scope) {
return scope->dbRefProto();
inline WrapType<MaxKeyInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->maxKeyProto();
}
template <>
inline WrapType<ErrorInfo>& getProto(MozJSScopeBase* scope) {
return scope->errorProto();
inline WrapType<MinKeyInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->minKeyProto();
}
template <>
inline WrapType<MaxKeyInfo>& getProto(MozJSScopeBase* scope) {
return scope->maxKeyProto();
inline WrapType<CodeInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->codeProto();
}
template <>
inline WrapType<MinKeyInfo>& getProto(MozJSScopeBase* scope) {
return scope->minKeyProto();
inline WrapType<DBPointerInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->dbPointerProto();
}
template <>
inline WrapType<MongoExternalInfo>& getProto(MozJSScopeBase* scope) {
return scope->mongoExternalProto();
inline WrapType<NativeFunctionInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->nativeFunctionProto();
}
template <>
inline WrapType<MongoStatusInfo>& getProto(MozJSScopeBase* scope) {
return scope->mongoStatusProto();
inline WrapType<ErrorInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->errorProto();
}
template <>
inline WrapType<NativeFunctionInfo>& getProto(MozJSScopeBase* scope) {
return scope->nativeFunctionProto();
inline WrapType<MongoStatusInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->mongoStatusProto();
}
template <>
inline WrapType<NumberDecimalInfo>& getProto(MozJSScopeBase* scope) {
return scope->numberDecimalProto();
inline WrapType<BSONInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->bsonProto();
}
template <>
inline WrapType<NumberIntInfo>& getProto(MozJSScopeBase* scope) {
return scope->numberIntProto();
inline WrapType<DBRefInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->dbRefProto();
}
template <>
inline WrapType<NumberLongInfo>& getProto(MozJSScopeBase* scope) {
return scope->numberLongProto();
inline WrapType<RegExpInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return runtime->regExpProto();
}
template <>
inline WrapType<OIDInfo>& getProto(MozJSScopeBase* scope) {
return scope->oidProto();
}
template <>
inline WrapType<RegExpInfo>& getProto(MozJSScopeBase* scope) {
return scope->regExpProto();
}
template <>
inline WrapType<SessionInfo>& getProto(MozJSScopeBase* scope) {
return scope->sessionProto();
}
template <>
inline WrapType<TimestampInfo>& getProto(MozJSScopeBase* scope) {
return scope->timestampProto();
}
template <>
inline WrapType<URIInfo>& getProto(MozJSScopeBase* scope) {
return scope->uriProto();
}
//
// Tracked memory allocation helpers
// These ensure proper ASAN tracking of dynamically allocated objects.
//
template <typename T, typename... Args>
T* trackedNew(MozJSScopeBase* scope, Args&&... args) {
T* trackedNew(MozJSCommonRuntimeInterface* runtime, Args&&... args) {
T* t = new T(std::forward<Args>(args)...);
scope->trackNewPointer(t);
runtime->trackNewPointer(t);
return t;
}
template <typename T>
void trackedDelete(MozJSScopeBase* scope, T* t) {
scope->trackDeletePointer(t);
delete (t);
void trackedDelete(MozJSCommonRuntimeInterface* runtime, T* t) {
runtime->trackDeletePointer(t);
delete t;
}
MONGO_MOD_PUB inline MozJSCommonRuntimeInterface* getCommonRuntime(JSContext* cx) {
return static_cast<MozJSCommonRuntimeInterface*>(JS_GetContextPrivate(cx));
}
} // namespace mongo::mozjs

View File

@ -38,7 +38,7 @@
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
@ -97,7 +97,7 @@ void hexToBinData(JSContext* cx,
int type,
const JS::Handle<JS::Value> hexdata,
JS::MutableHandleValue out) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
uassert(ErrorCodes::BadValue, "BinData data must be a String", hexdata.isString());
auto hexstr = ValueWriter(cx, hexdata).toString();
@ -110,7 +110,7 @@ void hexToBinData(JSContext* cx,
args[0].setInt32(type);
ValueReader(cx, args[1]).fromStringData(encoded);
return getProto<BinDataInfo>(scope).newInstance(args, out);
return getProto<BinDataInfo>(runtime).newInstance(args, out);
}
std::string* getEncoded(JS::HandleValue thisv) {
@ -128,7 +128,7 @@ void BinDataInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
auto str = getEncoded(obj);
if (str) {
trackedDelete(getMozJSScope(freeOpToJSContext(gcCtx)), str);
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), str);
}
}
@ -156,7 +156,7 @@ void BinDataInfo::Functions::UUID::call(JSContext* cx, JS::CallArgs args) {
JS::RootedValueArray<2> newArgs(cx);
newArgs[0].setInt32(newUUID);
ValueReader(cx, newArgs[1]).fromStringData(encoded);
getProto<BinDataInfo>(getMozJSScope(cx)).newInstance(newArgs, args.rval());
getCommonRuntime(cx)->binDataProto().newInstance(newArgs, args.rval());
}
void BinDataInfo::Functions::MD5::call(JSContext* cx, JS::CallArgs args) {
@ -247,7 +247,7 @@ void BinDataInfo::Functions::hex::call(JSContext* cx, JS::CallArgs args) {
}
void BinDataInfo::construct(JSContext* cx, JS::CallArgs args) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
if (args.length() != 2) {
uasserted(ErrorCodes::BadValue, "BinData takes 2 arguments -- BinData(subtype,data)");
@ -271,7 +271,7 @@ void BinDataInfo::construct(JSContext* cx, JS::CallArgs args) {
auto tmpBase64 = mongo::base64::decode(str);
JS::RootedObject thisv(cx);
getProto<BinDataInfo>(scope).newObject(&thisv);
getProto<BinDataInfo>(runtime).newObject(&thisv);
ObjectWrapper o(cx, thisv);
JS::RootedValue len(cx);
@ -280,8 +280,9 @@ void BinDataInfo::construct(JSContext* cx, JS::CallArgs args) {
o.defineProperty(InternedString::len, len, JSPROP_READONLY);
o.defineProperty(InternedString::type, type, JSPROP_READONLY);
JS::SetReservedSlot(
thisv, BinDataStringSlot, JS::PrivateValue(trackedNew<std::string>(scope, std::move(str))));
JS::SetReservedSlot(thisv,
BinDataStringSlot,
JS::PrivateValue(trackedNew<std::string>(runtime, std::move(str))));
args.rval().setObjectOrNull(thisv);
}

View File

@ -26,6 +26,7 @@
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/scripting/mozjs/common/types/bson.h"
#include "mongo/base/error_codes.h"
@ -34,13 +35,15 @@
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj_comparator_interface.h"
#include "mongo/bson/bsontypes.h"
#ifndef MONGO_MOZJS_WASI_BUILD
#include "mongo/db/pipeline/resume_token.h"
#endif
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/idwrapper.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/jsstringwrapper.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/util/assert_util.h"
@ -49,7 +52,9 @@
#include <cstddef>
#include <jsapi.h>
#if !defined(MONGO_MOZJS_WASI_BUILD)
#include <jscustomallocator.h>
#endif
#include <absl/container/node_hash_map.h>
#include <boost/move/utility_core.hpp>
@ -96,17 +101,21 @@ BSONObj getBSONFromArg(JSContext* cx, JS::HandleValue arg, bool isBSON) {
* the appearance of mutable state on the read/write versions.
*/
struct BSONHolder {
BSONHolder(const BSONObj& obj, const BSONObj* parent, const MozJSScopeBase* scope, bool ro)
BSONHolder(const BSONObj& obj,
const BSONObj* parent,
const MozJSCommonRuntimeInterface* runtime,
bool ro)
: _obj(obj),
_generation(scope->getGeneration()),
_generation(runtime->getGeneration()),
_isOwned(obj.isOwned() || (parent && parent->isOwned())),
_resolved(false),
_readOnly(ro),
_altered(false) {
bool requiresOwned = runtime->requiresOwnedObjects();
uassert(
ErrorCodes::BadValue,
"Attempt to bind an unowned BSON Object to a JS scope marked as requiring ownership",
_isOwned || (!scope->requiresOwnedObjects()));
_isOwned || (!requiresOwned));
if (parent) {
_parent.emplace(*parent);
}
@ -117,7 +126,7 @@ struct BSONHolder {
}
void uassertValid(JSContext* cx) const {
if (!_isOwned && getMozJSScope(cx)->getGeneration() != _generation)
if (!_isOwned && getCommonRuntime(cx)->getGeneration() != _generation)
uasserted(ErrorCodes::BadValue,
"Attempt to access an invalidated BSON Object in JS scope");
}
@ -162,12 +171,13 @@ void definePropertyFromBSONElement(JSContext* cx,
void BSONInfo::make(
JSContext* cx, JS::MutableHandleObject obj, BSONObj bson, const BSONObj* parent, bool ro) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
getProto<BSONInfo>(scope).newObject(obj);
JS::SetReservedSlot(obj,
BSONHolderSlot,
JS::PrivateValue(trackedNew<BSONHolder>(scope, bson, parent, scope, ro)));
getProto<BSONInfo>(runtime).newObject(obj);
JS::SetReservedSlot(
obj,
BSONHolderSlot,
JS::PrivateValue(trackedNew<BSONHolder>(runtime, bson, parent, runtime, ro)));
}
void BSONInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
@ -176,7 +186,7 @@ void BSONInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
if (!holder)
return;
trackedDelete(getMozJSScope(freeOpToJSContext(gcCtx)), holder);
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), holder);
}
/*
@ -342,9 +352,9 @@ void bsonCompareCommon(JSContext* cx,
uasserted(ErrorCodes::BadValue, fmt::format("{} needs 2 arguments", funcName));
// If either argument is not proper BSON, then we wrap both objects.
auto* scope = getMozJSScope(cx);
bool isBSON = getProto<BSONInfo>(scope).instanceOf(args.get(0)) &&
getProto<BSONInfo>(scope).instanceOf(args.get(1));
auto* runtime = getCommonRuntime(cx);
bool isBSON = getProto<BSONInfo>(runtime).instanceOf(args.get(0)) &&
getProto<BSONInfo>(runtime).instanceOf(args.get(1));
BSONObj bsonObject1 = getBSONFromArg(cx, args.get(0), isBSON);
BSONObj bsonObject2 = getBSONFromArg(cx, args.get(1), isBSON);
@ -370,9 +380,9 @@ void BSONInfo::Functions::bsonBinaryEqual::call(JSContext* cx, JS::CallArgs args
uasserted(ErrorCodes::BadValue, "bsonBinaryEqual needs 2 arguments");
// If either argument is not a proper BSON, then we wrap both objects.
auto* scope = getMozJSScope(cx);
bool isBSON = getProto<BSONInfo>(scope).instanceOf(args.get(0)) &&
getProto<BSONInfo>(scope).instanceOf(args.get(1));
auto* runtime = getCommonRuntime(cx);
bool isBSON = getProto<BSONInfo>(runtime).instanceOf(args.get(0)) &&
getProto<BSONInfo>(runtime).instanceOf(args.get(1));
BSONObj bsonObject1 = getBSONFromArg(cx, args.get(0), isBSON);
BSONObj bsonObject2 = getBSONFromArg(cx, args.get(1), isBSON);

View File

@ -33,7 +33,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
#include "mongo/util/assert_util.h"
@ -70,11 +70,11 @@ void CodeInfo::construct(JSContext* cx, JS::CallArgs args) {
uassert(ErrorCodes::BadValue,
"Code needs 0, 1 or 2 arguments",
args.length() == 0 || args.length() == 1 || args.length() == 2);
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
JS::RootedObject thisv(cx);
getProto<CodeInfo>(scope).newObject(&thisv);
getProto<CodeInfo>(runtime).newObject(&thisv);
ObjectWrapper o(cx, thisv);
if (args.length() == 0) {

View File

@ -32,7 +32,7 @@
#include "mongo/base/error_codes.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/types/oid.h"
#include "mongo/scripting/mozjs/common/wraptype.h"
#include "mongo/util/assert_util.h"
@ -47,7 +47,7 @@ namespace mozjs {
const char* const DBPointerInfo::className = "DBPointer";
void DBPointerInfo::construct(JSContext* cx, JS::CallArgs args) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
if (args.length() != 2)
uasserted(ErrorCodes::BadValue, "DBPointer needs 2 arguments");
@ -55,11 +55,11 @@ void DBPointerInfo::construct(JSContext* cx, JS::CallArgs args) {
if (!args.get(0).isString())
uasserted(ErrorCodes::BadValue, "DBPointer 1st parameter must be a string");
if (!getProto<OIDInfo>(scope).instanceOf(args.get(1)))
if (!getProto<OIDInfo>(runtime).instanceOf(args.get(1)))
uasserted(ErrorCodes::BadValue, "DBPointer 2nd parameter must be an ObjectId");
JS::RootedObject thisv(cx);
getProto<DBPointerInfo>(scope).newObject(&thisv);
getProto<DBPointerInfo>(runtime).newObject(&thisv);
ObjectWrapper o(cx, thisv);
o.setValue(InternedString::ns, args.get(0));

View File

@ -32,7 +32,7 @@
#include "mongo/base/error_codes.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/types/bson.h"
#include "mongo/scripting/mozjs/common/wraptype.h"
#include "mongo/util/assert_util.h"
@ -115,9 +115,9 @@ void DBRefInfo::make(
JS::RootedObject local(cx);
BSONInfo::make(cx, &local, std::move(bson), parent, ro);
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
getProto<DBRefInfo>(scope).newObject(obj);
getProto<DBRefInfo>(runtime).newObject(obj);
JS::SetReservedSlot(
obj,

View File

@ -35,7 +35,7 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
#include "mongo/util/assert_util.h"
@ -68,22 +68,22 @@ void MaxKeyInfo::construct(JSContext* cx, JS::CallArgs args) {
* == and === to MinKey even if created by "new MinKey()" in JS.
*/
void MaxKeyInfo::call(JSContext* cx, JS::CallArgs args) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
ObjectWrapper o(cx, getProto<MaxKeyInfo>(scope).getProto());
ObjectWrapper o(cx, getProto<MaxKeyInfo>(runtime).getProto());
JS::RootedValue val(cx);
if (!o.hasField(InternedString::singleton)) {
JS::RootedObject thisv(cx);
getProto<MaxKeyInfo>(scope).newObject(&thisv);
getProto<MaxKeyInfo>(runtime).newObject(&thisv);
val.setObjectOrNull(thisv);
o.setValue(InternedString::singleton, val);
} else {
o.getValue(InternedString::singleton, &val);
if (!getProto<MaxKeyInfo>(scope).instanceOf(val))
if (!getProto<MaxKeyInfo>(runtime).instanceOf(val))
uasserted(ErrorCodes::BadValue, "MaxKey singleton not of type MaxKey");
}
@ -102,15 +102,15 @@ void MaxKeyInfo::Functions::hasInstance::call(JSContext* cx, JS::CallArgs args)
uassert(ErrorCodes::BadValue, "hasInstance needs 1 argument", args.length() == 1);
uassert(ErrorCodes::BadValue, "argument must be an object", args.get(0).isObject());
auto* scope = getMozJSScope(cx);
args.rval().setBoolean(getProto<MaxKeyInfo>(scope).instanceOf(args.get(0)));
auto* runtime = getCommonRuntime(cx);
args.rval().setBoolean(getProto<MaxKeyInfo>(runtime).instanceOf(args.get(0)));
}
void MaxKeyInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
ObjectWrapper protoWrapper(cx, proto);
JS::RootedValue value(cx);
getProto<MaxKeyInfo>(getMozJSScope(cx)).newObject(&value);
getCommonRuntime(cx)->maxKeyProto().newObject(&value);
ObjectWrapper(cx, global).setValue(InternedString::MaxKey, value);
protoWrapper.setValue(InternedString::singleton, value);

View File

@ -35,7 +35,7 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
#include "mongo/util/assert_util.h"
@ -68,22 +68,22 @@ void MinKeyInfo::construct(JSContext* cx, JS::CallArgs args) {
* == and === to MinKey even if created by "new MinKey()" in JS.
*/
void MinKeyInfo::call(JSContext* cx, JS::CallArgs args) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
ObjectWrapper o(cx, getProto<MinKeyInfo>(scope).getProto());
ObjectWrapper o(cx, getProto<MinKeyInfo>(runtime).getProto());
JS::RootedValue val(cx);
if (!o.hasField(InternedString::singleton)) {
JS::RootedObject thisv(cx);
getProto<MinKeyInfo>(scope).newObject(&thisv);
getProto<MinKeyInfo>(runtime).newObject(&thisv);
val.setObjectOrNull(thisv);
o.setValue(InternedString::singleton, val);
} else {
o.getValue(InternedString::singleton, &val);
if (!getProto<MinKeyInfo>(scope).instanceOf(val))
if (!getProto<MinKeyInfo>(runtime).instanceOf(val))
uasserted(ErrorCodes::BadValue, "MinKey singleton not of type MinKey");
}
@ -103,15 +103,15 @@ void MinKeyInfo::Functions::hasInstance::call(JSContext* cx, JS::CallArgs args)
uassert(ErrorCodes::BadValue, "hasInstance needs 1 argument", args.length() == 1);
uassert(ErrorCodes::BadValue, "argument must be an object", args.get(0).isObject());
auto* scope = getMozJSScope(cx);
args.rval().setBoolean(getProto<MinKeyInfo>(scope).instanceOf(args.get(0)));
auto* runtime = getCommonRuntime(cx);
args.rval().setBoolean(getProto<MinKeyInfo>(runtime).instanceOf(args.get(0)));
}
void MinKeyInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
ObjectWrapper protoWrapper(cx, proto);
JS::RootedValue value(cx);
getProto<MinKeyInfo>(getMozJSScope(cx)).newObject(&value);
getCommonRuntime(cx)->minKeyProto().newObject(&value);
ObjectWrapper(cx, global).setValue(InternedString::MinKey, value);
protoWrapper.setValue(InternedString::singleton, value);

View File

@ -33,7 +33,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
#include "mongo/util/assert_util.h"
@ -103,7 +103,7 @@ void NativeFunctionInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
auto holder = JS::GetMaybePtrFromReservedSlot<NativeHolder>(obj, NativeHolderSlot);
if (holder)
trackedDelete(getMozJSScope(freeOpToJSContext(gcCtx)), holder);
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), holder);
}
void NativeFunctionInfo::Functions::toString::call(JSContext* cx, JS::CallArgs args) {
@ -120,10 +120,10 @@ void NativeFunctionInfo::make(JSContext* cx,
JS::MutableHandleObject obj,
NativeFunction function,
void* data) {
auto scope = getMozJSScope(cx);
getProto<NativeFunctionInfo>(scope).newObject(obj);
auto runtime = getCommonRuntime(cx);
getProto<NativeFunctionInfo>(runtime).newObject(obj);
JS::SetReservedSlot(
obj, NativeHolderSlot, JS::PrivateValue(trackedNew<NativeHolder>(scope, function, data)));
obj, NativeHolderSlot, JS::PrivateValue(trackedNew<NativeHolder>(runtime, function, data)));
}
} // namespace mozjs

View File

@ -35,7 +35,7 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
@ -66,7 +66,7 @@ void NumberDecimalInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
auto x = JS::GetMaybePtrFromReservedSlot<Decimal128>(obj, Decimal128Slot);
if (x)
trackedDelete(getMozJSScope(freeOpToJSContext(gcCtx)), x);
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), x);
}
Decimal128 NumberDecimalInfo::ToNumberDecimal(JSContext* cx, JS::HandleValue thisv) {
@ -97,11 +97,11 @@ void NumberDecimalInfo::Functions::toJSON::call(JSContext* cx, JS::CallArgs args
}
void NumberDecimalInfo::construct(JSContext* cx, JS::CallArgs args) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
JS::RootedObject thisv(cx);
getProto<NumberDecimalInfo>(scope).newObject(&thisv);
getProto<NumberDecimalInfo>(runtime).newObject(&thisv);
Decimal128 x(0);
@ -112,18 +112,19 @@ void NumberDecimalInfo::construct(JSContext* cx, JS::CallArgs args) {
} else {
uasserted(ErrorCodes::BadValue, "NumberDecimal takes 0 or 1 arguments");
}
JS::SetReservedSlot(thisv, Decimal128Slot, JS::PrivateValue(trackedNew<Decimal128>(scope, x)));
JS::SetReservedSlot(
thisv, Decimal128Slot, JS::PrivateValue(trackedNew<Decimal128>(runtime, x)));
args.rval().setObjectOrNull(thisv);
}
void NumberDecimalInfo::make(JSContext* cx, JS::MutableHandleValue thisv, Decimal128 decimal) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
getProto<NumberDecimalInfo>(scope).newObject(thisv);
getProto<NumberDecimalInfo>(runtime).newObject(thisv);
JS::SetReservedSlot(thisv.toObjectOrNull(),
Decimal128Slot,
JS::PrivateValue(trackedNew<Decimal128>(scope, decimal)));
JS::PrivateValue(trackedNew<Decimal128>(runtime, decimal)));
}
} // namespace mozjs

View File

@ -31,7 +31,7 @@
#include "mongo/base/error_codes.h"
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
@ -64,7 +64,7 @@ void NumberIntInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
auto x = JS::GetMaybePtrFromReservedSlot<int>(obj, IntSlot);
if (x)
trackedDelete(getMozJSScope(freeOpToJSContext(gcCtx)), x);
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), x);
}
int NumberIntInfo::ToNumberInt(JSContext* cx, JS::HandleValue thisv) {
@ -107,7 +107,7 @@ void NumberIntInfo::Functions::toJSON::call(JSContext* cx, JS::CallArgs args) {
void NumberIntInfo::construct(JSContext* cx, JS::CallArgs args) {
JS::RootedObject thisv(cx);
getProto<NumberIntInfo>(getMozJSScope(cx)).newObject(&thisv);
getCommonRuntime(cx)->numberIntProto().newObject(&thisv);
int32_t x = 0;
@ -118,7 +118,7 @@ void NumberIntInfo::construct(JSContext* cx, JS::CallArgs args) {
} else {
uasserted(ErrorCodes::BadValue, "NumberInt takes 0 or 1 arguments");
}
JS::SetReservedSlot(thisv, IntSlot, JS::PrivateValue(trackedNew<int>(getMozJSScope(cx), x)));
JS::SetReservedSlot(thisv, IntSlot, JS::PrivateValue(trackedNew<int>(getCommonRuntime(cx), x)));
args.rval().setObjectOrNull(thisv);
}

View File

@ -38,7 +38,7 @@
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
@ -80,7 +80,7 @@ void NumberLongInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
auto numLong = JS::GetMaybePtrFromReservedSlot<int64_t>(obj, Int64Slot);
if (numLong)
trackedDelete(getMozJSScope(freeOpToJSContext(gcCtx)), numLong);
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), numLong);
}
int64_t NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleValue thisv) {
@ -129,7 +129,7 @@ void NumberLongInfo::Functions::compare::call(JSContext* cx, JS::CallArgs args)
uassert(ErrorCodes::BadValue, "NumberLong.compare() needs 1 argument", args.length() == 1);
uassert(ErrorCodes::BadValue,
"NumberLong.compare() argument must be a NumberLong",
getProto<NumberLongInfo>(getMozJSScope(cx)).instanceOf(args.get(0)));
getCommonRuntime(cx)->numberLongProto().instanceOf(args.get(0)));
int64_t thisVal = NumberLongInfo::ToNumberLong(cx, args.thisv());
int64_t otherVal = NumberLongInfo::ToNumberLong(cx, args.get(0));
@ -171,11 +171,11 @@ void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) {
"NumberLong needs 0, 1 or 3 arguments",
args.length() == 0 || args.length() == 1 || args.length() == 3);
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
JS::RootedObject thisv(cx);
getProto<NumberLongInfo>(scope).newObject(&thisv);
getProto<NumberLongInfo>(runtime).newObject(&thisv);
int64_t numLong;
@ -221,7 +221,7 @@ void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) {
numLong = (top << 32) + bot;
}
JS::SetReservedSlot(
thisv, Int64Slot, JS::PrivateValue(getMozJSScope(cx)->trackedNewInt64(numLong)));
thisv, Int64Slot, JS::PrivateValue(getCommonRuntime(cx)->trackedNewInt64(numLong)));
args.rval().setObjectOrNull(thisv);
}
@ -234,7 +234,7 @@ void NumberLongInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::Han
if (!JS_DefinePropertyById(
cx,
proto,
getMozJSScope(cx)->getInternedStringId(InternedString::floatApprox),
getCommonRuntime(cx)->getInternedStringId(InternedString::floatApprox),
smUtils::wrapConstrainedMethod<Functions::floatApprox, false, NumberLongInfo>,
nullptr,
JSPROP_ENUMERATE)) {
@ -245,7 +245,7 @@ void NumberLongInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::Han
if (!JS_DefinePropertyById(
cx,
proto,
getMozJSScope(cx)->getInternedStringId(InternedString::top),
getCommonRuntime(cx)->getInternedStringId(InternedString::top),
smUtils::wrapConstrainedMethod<Functions::top, false, NumberLongInfo>,
nullptr,
JSPROP_ENUMERATE)) {
@ -256,7 +256,7 @@ void NumberLongInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::Han
if (!JS_DefinePropertyById(
cx,
proto,
getMozJSScope(cx)->getInternedStringId(InternedString::bottom),
getCommonRuntime(cx)->getInternedStringId(InternedString::bottom),
smUtils::wrapConstrainedMethod<Functions::bottom, false, NumberLongInfo>,
nullptr,
JSPROP_ENUMERATE)) {
@ -267,7 +267,7 @@ void NumberLongInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::Han
if (!JS_DefinePropertyById(
cx,
proto,
getMozJSScope(cx)->getInternedStringId(InternedString::exactValueString),
getCommonRuntime(cx)->getInternedStringId(InternedString::exactValueString),
smUtils::wrapConstrainedMethod<Functions::exactValueString, false, NumberLongInfo>,
nullptr,
JSPROP_ENUMERATE)) {

View File

@ -33,13 +33,13 @@
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/scripting/engine.h"
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
#include "mongo/scripting/oid_validation.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/str.h"
@ -70,7 +70,7 @@ void OIDInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
auto oid = JS::GetMaybePtrFromReservedSlot<OID>(obj, OIDSlot);
if (oid) {
trackedDelete(getMozJSScope(freeOpToJSContext(gcCtx)), oid);
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), oid);
}
}
@ -101,7 +101,7 @@ void OIDInfo::construct(JSContext* cx, JS::CallArgs args) {
} else {
auto str = ValueWriter(cx, args.get(0)).toString();
Scope::validateObjectIdString(str);
validateObjectIdString(str);
oid.init(str);
}
@ -109,11 +109,11 @@ void OIDInfo::construct(JSContext* cx, JS::CallArgs args) {
}
void OIDInfo::make(JSContext* cx, const OID& oid, JS::MutableHandleValue out) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
JS::RootedObject thisv(cx);
getProto<OIDInfo>(scope).newObject(&thisv);
JS::SetReservedSlot(thisv, OIDSlot, JS::PrivateValue(trackedNew<OID>(scope, oid)));
getProto<OIDInfo>(runtime).newObject(&thisv);
JS::SetReservedSlot(thisv, OIDSlot, JS::PrivateValue(trackedNew<OID>(runtime, oid)));
out.setObjectOrNull(thisv);
}
@ -139,7 +139,7 @@ void OIDInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObje
if (!JS_DefinePropertyById(cx,
proto,
getMozJSScope(cx)->getInternedStringId(InternedString::str),
getCommonRuntime(cx)->getInternedStringId(InternedString::str),
smUtils::wrapConstrainedMethod<Functions::getter, true, OIDInfo>,
nullptr,
JSPROP_ENUMERATE)) {

View File

@ -35,7 +35,7 @@
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
#include "mongo/util/assert_util.h"
@ -70,7 +70,7 @@ Status MongoStatusInfo::toStatus(JSContext* cx, JS::HandleValue value) {
void MongoStatusInfo::fromStatus(JSContext* cx, Status status, JS::MutableHandleValue value) {
invariant(status != Status::OK());
auto scope = getMozJSScope(cx);
auto runtime = getCommonRuntime(cx);
JS::RootedValue undef(cx);
undef.setUndefined();
@ -78,10 +78,10 @@ void MongoStatusInfo::fromStatus(JSContext* cx, Status status, JS::MutableHandle
JS::RootedValueArray<1> args(cx);
ValueReader(cx, args[0]).fromStringData(status.reason());
JS::RootedObject error(cx);
getProto<ErrorInfo>(scope).newInstance(args, &error);
getProto<ErrorInfo>(runtime).newInstance(args, &error);
JS::RootedObject thisv(cx);
getProto<MongoStatusInfo>(scope).newObjectWithProto(&thisv, error);
getProto<MongoStatusInfo>(runtime).newObjectWithProto(&thisv, error);
ObjectWrapper thisvObj(cx, thisv);
thisvObj.defineProperty(InternedString::code,
JSPROP_ENUMERATE,
@ -103,7 +103,7 @@ void MongoStatusInfo::fromStatus(JSContext* cx, Status status, JS::MutableHandle
nullptr);
JS::SetReservedSlot(
thisv, StatusSlot, JS::PrivateValue(trackedNew<Status>(scope, std::move(status))));
thisv, StatusSlot, JS::PrivateValue(trackedNew<Status>(runtime, std::move(status))));
value.setObjectOrNull(thisv);
}
@ -112,7 +112,7 @@ void MongoStatusInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
auto status = JS::GetMaybePtrFromReservedSlot<Status>(obj, StatusSlot);
if (status)
trackedDelete(getMozJSScope(freeOpToJSContext(gcCtx)), status);
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), status);
}
void MongoStatusInfo::Functions::code::call(JSContext* cx, JS::CallArgs args) {
@ -160,11 +160,11 @@ void MongoStatusInfo::Functions::stack::call(JSContext* cx, JS::CallArgs args) {
}
void MongoStatusInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
auto scope = getMozJSScope(cx);
auto runtime = getCommonRuntime(cx);
JS::SetReservedSlot(proto,
StatusSlot,
JS::PrivateValue(trackedNew<Status>(
scope, Status(ErrorCodes::UnknownError, "Mongo Status Prototype"))));
runtime, Status(ErrorCodes::UnknownError, "Mongo Status Prototype"))));
}
} // namespace mozjs

View File

@ -35,7 +35,7 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
@ -79,10 +79,10 @@ double getTimestampComponent(JSContext* cx, JS::HandleValue component, std::stri
} // namespace
void TimestampInfo::construct(JSContext* cx, JS::CallArgs args) {
auto* scope = getMozJSScope(cx);
auto* runtime = getCommonRuntime(cx);
JS::RootedObject thisv(cx);
getProto<TimestampInfo>(scope).newObject(&thisv);
getProto<TimestampInfo>(runtime).newObject(&thisv);
ObjectWrapper o(cx, thisv);
if (args.length() == 0) {

View File

@ -35,7 +35,7 @@
#include "mongo/bson/oid.h"
#include "mongo/logv2/log.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/types/bindata.h"
#include "mongo/scripting/mozjs/common/types/bson.h"
#include "mongo/scripting/mozjs/common/types/code.h"
@ -58,7 +58,9 @@
#include <cstdio>
#include <iosfwd>
#if !defined(MONGO_MOZJS_WASI_BUILD)
#include <jscustomallocator.h>
#endif
#include <js/Array.h>
#include <js/CharacterEncoding.h>
@ -83,36 +85,34 @@ ValueReader::ValueReader(JSContext* cx, JS::MutableHandleValue value)
: _context(cx), _value(value) {}
void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent, bool readOnly) {
auto scope = getMozJSScope(_context);
auto runtime = getCommonRuntime(_context);
switch (elem.type()) {
case BSONType::code:
// javascriptProtection prevents Code and CodeWScope BSON types from
// being automatically marshalled into executable functions.
if (scope->isJavaScriptProtectionEnabled()) {
if (runtime->isJavaScriptProtectionEnabled()) {
JS::RootedValueArray<1> args(_context);
ValueReader(_context, args[0]).fromStringData(elem.valueStringData());
JS::RootedObject obj(_context);
getProto<CodeInfo>(scope).newInstance(args, _value);
getProto<CodeInfo>(runtime).newInstance(args, _value);
} else {
scope->newFunction(elem.valueStringData(), _value);
runtime->newFunction(elem.valueStringData(), _value);
}
return;
case BSONType::codeWScope:
if (scope->isJavaScriptProtectionEnabled()) {
if (runtime->isJavaScriptProtectionEnabled()) {
JS::RootedValueArray<2> args(_context);
ValueReader(_context, args[0]).fromStringData(elem.codeWScopeCode());
ValueReader(_context, args[1])
.fromBSON(elem.codeWScopeObject().getOwned(), nullptr, readOnly);
getProto<CodeInfo>(scope).newInstance(args, _value);
getProto<CodeInfo>(runtime).newInstance(args, _value);
} else {
#ifndef MONGO_MOZJS_WASI_BUILD
if (!elem.codeWScopeObject().isEmpty())
LOGV2_WARNING(23826, "CodeWScope doesn't transfer to db.eval");
scope->newFunction(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1),
_value);
#endif
runtime->newFunction(
StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen() - 1), _value);
}
return;
case BSONType::symbol:
@ -157,7 +157,7 @@ void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent
ValueReader(_context, args[1]).fromStringData(elem.regexFlags());
JS::RootedObject obj(_context);
getProto<RegExpInfo>(scope).newInstance(args, &obj);
getProto<RegExpInfo>(runtime).newInstance(args, &obj);
_value.setObjectOrNull(obj);
@ -175,7 +175,7 @@ void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent
ValueReader(_context, args[1]).fromStringData(ss.str());
getProto<BinDataInfo>(scope).newInstance(args, _value);
getProto<BinDataInfo>(runtime).newInstance(args, _value);
return;
}
case BSONType::timestamp: {
@ -185,16 +185,16 @@ void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent
.fromDouble(static_cast<double>(elem.timestampTime().toMillisSinceEpoch()) / 1000);
ValueReader(_context, args[1]).fromDouble(elem.timestampInc());
getProto<TimestampInfo>(scope).newInstance(args, _value);
getProto<TimestampInfo>(runtime).newInstance(args, _value);
return;
}
case BSONType::numberLong: {
JS::RootedObject thisv(_context);
getProto<NumberLongInfo>(scope).newObject(&thisv);
getProto<NumberLongInfo>(runtime).newObject(&thisv);
JS::SetReservedSlot(thisv,
NumberLongInfo::Int64Slot,
JS::PrivateValue(scope->trackedNewInt64(elem.numberLong())));
JS::PrivateValue(runtime->trackedNewInt64(elem.numberLong())));
_value.setObjectOrNull(thisv);
return;
}
@ -204,16 +204,16 @@ void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent
ValueReader(_context, args[0]).fromDecimal128(decimal);
JS::RootedObject obj(_context);
getProto<NumberDecimalInfo>(scope).newInstance(args, &obj);
getProto<NumberDecimalInfo>(runtime).newInstance(args, &obj);
_value.setObjectOrNull(obj);
return;
}
case BSONType::minKey:
getProto<MinKeyInfo>(scope).newInstance(_value);
getProto<MinKeyInfo>(runtime).newInstance(_value);
return;
case BSONType::maxKey:
getProto<MaxKeyInfo>(scope).newInstance(_value);
getProto<MaxKeyInfo>(runtime).newInstance(_value);
return;
case BSONType::dbRef: {
JS::RootedValueArray<1> oidArgs(_context);
@ -221,9 +221,9 @@ void ValueReader::fromBSONElement(const BSONElement& elem, const BSONObj& parent
JS::RootedValueArray<2> dbPointerArgs(_context);
ValueReader(_context, dbPointerArgs[0]).fromStringData(elem.dbrefNS());
getProto<OIDInfo>(scope).newInstance(oidArgs, dbPointerArgs[1]);
getProto<OIDInfo>(runtime).newInstance(oidArgs, dbPointerArgs[1]);
getProto<DBPointerInfo>(scope).newInstance(dbPointerArgs, _value);
getProto<DBPointerInfo>(runtime).newInstance(dbPointerArgs, _value);
return;
}
default:
@ -337,11 +337,11 @@ void ValueReader::fromDouble(double d) {
}
void ValueReader::fromInt64(int64_t i) {
auto scope = getMozJSScope(_context);
auto runtime = getCommonRuntime(_context);
JS::RootedObject num(_context);
getProto<NumberLongInfo>(scope).newObject(&num);
getProto<NumberLongInfo>(runtime).newObject(&num);
JS::SetReservedSlot(
num, NumberLongInfo::Int64Slot, JS::PrivateValue(scope->trackedNewInt64(i)));
num, NumberLongInfo::Int64Slot, JS::PrivateValue(runtime->trackedNewInt64(i)));
_value.setObjectOrNull(num);
}

View File

@ -33,11 +33,12 @@
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/js_regex.h"
#include "mongo/scripting/mozjs/common/exception.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/jsstringwrapper.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/types/bindata.h"
#include "mongo/scripting/mozjs/common/types/code.h"
#include "mongo/scripting/mozjs/common/types/dbpointer.h"
@ -137,22 +138,22 @@ int ValueWriter::type() {
}
if (auto jsClass = JS::GetClass(obj)) {
auto scope = getMozJSScope(_context);
if (scope->numberIntProto().getJSClass() == jsClass) {
auto runtime = getCommonRuntime(_context);
if (runtime->numberIntProto().getJSClass() == jsClass) {
return stdx::to_underlying(BSONType::numberInt);
} else if (scope->numberLongProto().getJSClass() == jsClass) {
} else if (runtime->numberLongProto().getJSClass() == jsClass) {
return stdx::to_underlying(BSONType::numberLong);
} else if (scope->numberDecimalProto().getJSClass() == jsClass) {
} else if (runtime->numberDecimalProto().getJSClass() == jsClass) {
return stdx::to_underlying(BSONType::numberDecimal);
} else if (scope->oidProto().getJSClass() == jsClass) {
} else if (runtime->oidProto().getJSClass() == jsClass) {
return stdx::to_underlying(BSONType::oid);
} else if (scope->binDataProto().getJSClass() == jsClass) {
} else if (runtime->binDataProto().getJSClass() == jsClass) {
return stdx::to_underlying(BSONType::binData);
} else if (scope->timestampProto().getJSClass() == jsClass) {
} else if (runtime->timestampProto().getJSClass() == jsClass) {
return stdx::to_underlying(BSONType::timestamp);
} else if (scope->minKeyProto().getJSClass() == jsClass) {
} else if (runtime->minKeyProto().getJSClass() == jsClass) {
return stdx::to_underlying(BSONType::minKey);
} else if (scope->maxKeyProto().getJSClass() == jsClass) {
} else if (runtime->maxKeyProto().getJSClass() == jsClass) {
return stdx::to_underlying(BSONType::maxKey);
}
}
@ -251,7 +252,7 @@ int32_t ValueWriter::toInt32() {
int64_t ValueWriter::toInt64() {
int64_t out;
if (getMozJSScope(_context)->numberLongProto().instanceOf(_value))
if (getCommonRuntime(_context)->numberLongProto().instanceOf(_value))
return NumberLongInfo::ToNumberLong(_context, _value);
if (JS::ToInt64(_context, _value, &out))
@ -265,13 +266,13 @@ Decimal128 ValueWriter::toDecimal128() {
if (_value.isNumber()) {
return Decimal128(toNumber(), Decimal128::kRoundTo15Digits);
}
if (getMozJSScope(_context)->numberIntProto().instanceOf(_value))
if (getCommonRuntime(_context)->numberIntProto().instanceOf(_value))
return Decimal128(NumberIntInfo::ToNumberInt(_context, _value));
if (getMozJSScope(_context)->numberLongProto().instanceOf(_value))
if (getCommonRuntime(_context)->numberLongProto().instanceOf(_value))
return Decimal128(static_cast<int64_t>(NumberLongInfo::ToNumberLong(_context, _value)));
if (getMozJSScope(_context)->numberDecimalProto().instanceOf(_value))
if (getCommonRuntime(_context)->numberDecimalProto().instanceOf(_value))
return NumberDecimalInfo::ToNumberDecimal(_context, _value);
if (_value.isString()) {
@ -296,7 +297,7 @@ Decimal128 ValueWriter::toDecimal128() {
}
OID ValueWriter::toOID() {
if (getMozJSScope(_context)->oidProto().instanceOf(_value)) {
if (getCommonRuntime(_context)->oidProto().instanceOf(_value)) {
return OIDInfo::getOID(_context, _value);
}
@ -304,7 +305,7 @@ OID ValueWriter::toOID() {
}
void ValueWriter::toBinData(std::function<void(const BSONBinData&)> withBinData) {
if (!getMozJSScope(_context)->binDataProto().instanceOf(_value)) {
if (!getCommonRuntime(_context)->binDataProto().instanceOf(_value)) {
throwCurrentJSException(_context, ErrorCodes::BadValue, "Unable to write BinData value.");
}
@ -318,9 +319,8 @@ void ValueWriter::toBinData(std::function<void(const BSONBinData&)> withBinData)
uassert(ErrorCodes::BadValue, "Cannot call getter on BinData prototype", binDataStr);
auto binData = base64::decode(*binDataStr);
withBinData(BSONBinData(binData.c_str(),
binData.size(),
static_cast<mongo::BinDataType>(static_cast<int>(subType))));
withBinData(BSONBinData(
binData.c_str(), binData.size(), static_cast<BinDataType>(static_cast<int>(subType))));
}
Timestamp ValueWriter::toTimestamp() {
@ -328,7 +328,7 @@ Timestamp ValueWriter::toTimestamp() {
uassert(ErrorCodes::BadValue,
"Unable to write Timestamp value.",
getMozJSScope(_context)->timestampProto().getJSClass() == JS::GetClass(obj));
getCommonRuntime(_context)->timestampProto().getJSClass() == JS::GetClass(obj));
return TimestampInfo::getValidatedValue(_context, obj);
}
@ -393,7 +393,7 @@ void ValueWriter::writeThis(BSONObjBuilder* b,
void ValueWriter::_writeObject(BSONObjBuilder* b,
StringData sd,
ObjectWrapper::WriteFieldRecursionFrames* frames) {
auto scope = getMozJSScope(_context);
auto runtime = getCommonRuntime(_context);
// We open a block here because it's important that the two rooting types
// we need (obj and o) go out of scope before we actually open a
@ -407,26 +407,26 @@ void ValueWriter::_writeObject(BSONObjBuilder* b,
auto jsclass = JS::GetClass(obj);
if (jsclass) {
if (scope->oidProto().getJSClass() == jsclass) {
if (runtime->oidProto().getJSClass() == jsclass) {
b->append(sd, OIDInfo::getOID(_context, obj));
return;
}
if (scope->numberLongProto().getJSClass() == jsclass) {
if (runtime->numberLongProto().getJSClass() == jsclass) {
long long out = NumberLongInfo::ToNumberLong(_context, obj);
b->append(sd, out);
return;
}
if (scope->numberIntProto().getJSClass() == jsclass) {
if (runtime->numberIntProto().getJSClass() == jsclass) {
b->append(sd, NumberIntInfo::ToNumberInt(_context, obj));
return;
}
if (scope->codeProto().getJSClass() == jsclass) {
if (runtime->codeProto().getJSClass() == jsclass) {
if (o.hasOwnField(InternedString::scope) // CodeWScope
&& o.type(InternedString::scope) == stdx::to_underlying(BSONType::object)) {
if (o.type(InternedString::code) != stdx::to_underlying(BSONType::string)) {
@ -445,16 +445,16 @@ void ValueWriter::_writeObject(BSONObjBuilder* b,
return;
}
if (scope->numberDecimalProto().getJSClass() == jsclass) {
if (runtime->numberDecimalProto().getJSClass() == jsclass) {
b->append(sd, NumberDecimalInfo::ToNumberDecimal(_context, obj));
return;
}
if (scope->dbPointerProto().getJSClass() == jsclass) {
if (runtime->dbPointerProto().getJSClass() == jsclass) {
uassert(ErrorCodes::BadValue,
"can't serialize DBPointer prototype",
scope->dbPointerProto().getProto() != obj);
runtime->dbPointerProto().getProto() != obj);
JS::RootedValue id(_context);
o.getValue("id", &id);
@ -464,7 +464,7 @@ void ValueWriter::_writeObject(BSONObjBuilder* b,
return;
}
if (scope->binDataProto().getJSClass() == jsclass) {
if (runtime->binDataProto().getJSClass() == jsclass) {
auto str = JS::GetMaybePtrFromReservedSlot<std::string>(
obj, BinDataInfo::BinDataStringSlot);
@ -479,26 +479,26 @@ void ValueWriter::_writeObject(BSONObjBuilder* b,
b->appendBinData(sd,
binData.size(),
static_cast<mongo::BinDataType>(static_cast<int>(subType)),
static_cast<BinDataType>(static_cast<int>(subType)),
binData.c_str());
return;
}
if (scope->timestampProto().getJSClass() == jsclass) {
if (runtime->timestampProto().getJSClass() == jsclass) {
Timestamp ot = TimestampInfo::getValidatedValue(_context, obj);
b->append(sd, ot);
return;
}
if (scope->minKeyProto().getJSClass() == jsclass) {
if (runtime->minKeyProto().getJSClass() == jsclass) {
b->appendMinKey(sd);
return;
}
if (scope->maxKeyProto().getJSClass() == jsclass) {
if (runtime->maxKeyProto().getJSClass() == jsclass) {
b->appendMaxKey(sd);
return;
@ -511,7 +511,7 @@ void ValueWriter::_writeObject(BSONObjBuilder* b,
case JSProto_Function: {
uassert(16716,
"cannot convert native function to BSON",
!scope->nativeFunctionProto().instanceOf(obj));
!runtime->nativeFunctionProto().instanceOf(obj));
JSStringWrapper jsstr;
b->appendCode(sd, ValueWriter(_context, _value).toStringData(&jsstr));
return;

View File

@ -36,11 +36,14 @@
#include "mongo/bson/oid.h"
#include "mongo/bson/timestamp.h"
#include "mongo/platform/decimal128.h"
#include "mongo/scripting/engine.h"
#include "mongo/scripting/mozjs/common/jsstringwrapper.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/util/modules.h"
namespace mongo {
struct JSRegEx;
}
#include <cstdint>
#include <functional>
#include <string>

View File

@ -30,7 +30,7 @@
#pragma once
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/scripting/mozjs/common/wraptype.h"
#include "mongo/util/assert_util.h"
@ -52,8 +52,8 @@ namespace smUtils {
* void as the last type in wrapConstrainedMethod.
*/
template <typename T, typename... Args>
bool instanceOf(MozJSScopeBase* scope, bool* isProto, JS::HandleValue value) {
auto& proto = getProto<T>(scope);
bool instanceOf(MozJSCommonRuntimeInterface* runtime, bool* isProto, JS::HandleValue value) {
auto& proto = getProto<T>(runtime);
if (proto.instanceOf(value)) {
if (value.toObjectOrNull() == proto.getProto()) {
@ -63,7 +63,7 @@ bool instanceOf(MozJSScopeBase* scope, bool* isProto, JS::HandleValue value) {
return true;
}
return instanceOf<Args...>(scope, isProto, value);
return instanceOf<Args...>(runtime, isProto, value);
}
/**
@ -72,7 +72,9 @@ bool instanceOf(MozJSScopeBase* scope, bool* isProto, JS::HandleValue value) {
* We use this to identify the end of the template list in the general case.
*/
template <>
inline bool instanceOf<void>(MozJSScopeBase* scope, bool* isProto, JS::HandleValue value) {
inline bool instanceOf<void>(MozJSCommonRuntimeInterface* runtime,
bool* isProto,
JS::HandleValue value) {
return false;
}
@ -85,7 +87,7 @@ inline bool instanceOf<void>(MozJSScopeBase* scope, bool* isProto, JS::HandleVal
* ::call - a static function of type void (JSContext* cx, JS::CallArgs args)
* ::name - a static function which returns a const char* with the type name
* noProto - whether the method can be invoked on the prototype
* Args - The list of types to check against scope->getProto<T>().instanceOf
* Args - The list of types to check against runtime->getProto<T>().instanceOf
* for the thisv the method has been invoked against
*/
template <typename T, bool noProto, typename... Args>
@ -101,7 +103,7 @@ bool wrapConstrainedMethod(JSContext* cx, unsigned argc, JS::Value* vp) {
<< ValueWriter(cx, args.thisv()).typeAsString() << "\"");
}
if (!instanceOf<Args..., void>(getMozJSScope(cx), &isProto, args.thisv())) {
if (!instanceOf<Args..., void>(getCommonRuntime(cx), &isProto, args.thisv())) {
uasserted(ErrorCodes::BadValue,
str::stream() << "Cannot call \"" << T::name() << "\" on object of type \""
<< ObjectWrapper(cx, args.thisv()).getClassName() << "\"");

View File

@ -0,0 +1,19 @@
load("//bazel:mongo_src_rules.bzl", "mongo_cc_library")
package(default_visibility = ["//visibility:public"])
exports_files([
"mozjs_error_types.h",
"mozjs_wasm_startup_options.h",
])
# Shared types and definitions for MozJS that can be used by both
# the WASM engine and the mongod host side.
mongo_cc_library(
name = "mozjs_shared_types",
hdrs = [
"mozjs_error_types.h",
"mozjs_wasm_startup_options.h",
],
deps = [],
)

View File

@ -0,0 +1,91 @@
/**
* 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.
*/
#pragma once
#include <cstddef>
#include <cstdint>
namespace mongo {
namespace mozjs {
namespace wasm {
/**
* Shared error codes for MozJS operations.
* Used by both the WASM engine and the mongo host side.
*/
typedef enum {
SM_OK = 0,
// Wrapper / host-side errors
SM_E_INVALID_ARG,
SM_E_BAD_STATE,
SM_E_NOMEM,
SM_E_IO,
SM_E_TIMEOUT,
SM_E_NOT_SUPPORTED,
SM_E_INTERNAL,
// MozJS API usage
SM_E_JSAPI_FAIL,
SM_E_PENDING_EXCEPTION,
SM_E_NO_EXCEPTION,
SM_E_TERMINATED,
SM_E_OOM,
// Script failures
SM_E_COMPILE,
SM_E_RUNTIME,
SM_E_MODULE,
SM_E_PROMISE_REJECTION,
SM_E_STACK_OVERFLOW,
// Data marshalling / boundary errors
SM_E_TYPE,
SM_E_ENCODING,
} err_code_t;
typedef struct {
char* msg; // Error message
size_t msg_len; // Length of msg (excluding null terminator)
char* filename; // Source filename
size_t filename_len; // Length of filename (excluding null terminator)
char* stack; // Stack trace
size_t stack_len; // Length of stack (excluding null terminator)
uint32_t line;
uint32_t column;
err_code_t code;
} wasm_mozjs_error_t;
} // namespace wasm
} // namespace mozjs
} // namespace mongo

View File

@ -0,0 +1,46 @@
/**
* 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.
*/
#pragma once
#include <cstddef>
#include <cstdint>
namespace mongo {
namespace mozjs {
namespace wasm {
typedef struct {
size_t opTimeout; // microseconds (0 = no timeout)
size_t heapSize; // in MB
} wasm_mozjs_startup_options_t;
} // namespace wasm
} // namespace mozjs
} // namespace mongo

View File

@ -68,7 +68,7 @@ bool isExternalScriptingEnabled() {
}
namespace {
auto operationMozJSScopeBaseDecoration =
auto operationMozJSShellRuntimeInterfaceDecoration =
OperationContext::declareDecoration<mozjs::MozJSImplScope*>();
}
@ -117,8 +117,8 @@ mongo::Scope* MozJSScriptEngine::createScopeForCurrentThread(boost::optional<int
}
void MozJSScriptEngine::interrupt(ClientLock&, OperationContext* opCtx) {
if (opCtx && (*opCtx)[operationMozJSScopeBaseDecoration]) {
(*opCtx)[operationMozJSScopeBaseDecoration]->kill();
if (opCtx && (*opCtx)[operationMozJSShellRuntimeInterfaceDecoration]) {
(*opCtx)[operationMozJSShellRuntimeInterfaceDecoration]->kill();
LOGV2_DEBUG(22808, 2, "Interrupting op", "opId"_attr = opCtx->getOpID());
} else if (opCtx) {
LOGV2_DEBUG(
@ -133,8 +133,8 @@ void MozJSScriptEngine::interruptAll(ServiceContextLock& svcCtxLock) {
while (auto client = cursor.next()) {
stdx::lock_guard lk(*client);
if (auto opCtx = client->getOperationContext();
opCtx && (*opCtx)[operationMozJSScopeBaseDecoration]) {
(*opCtx)[operationMozJSScopeBaseDecoration]->kill();
opCtx && (*opCtx)[operationMozJSShellRuntimeInterfaceDecoration]) {
(*opCtx)[operationMozJSShellRuntimeInterfaceDecoration]->kill();
}
}
}
@ -183,7 +183,7 @@ void MozJSScriptEngine::registerOperation(OperationContext* opCtx, MozJSImplScop
"opId"_attr = opCtx->getOpID());
stdx::lock_guard lk(*opCtx->getClient());
(*opCtx)[operationMozJSScopeBaseDecoration] = scope;
(*opCtx)[operationMozJSShellRuntimeInterfaceDecoration] = scope;
if (auto status = opCtx->checkForInterruptNoAssert(); !status.isOK()) {
scope->kill();
@ -198,7 +198,7 @@ void MozJSScriptEngine::unregisterOperation(OperationContext* opCtx) {
"opId"_attr = opCtx->getOpID());
stdx::lock_guard lk(*opCtx->getClient());
(*opCtx)[operationMozJSScopeBaseDecoration] = nullptr;
(*opCtx)[operationMozJSShellRuntimeInterfaceDecoration] = nullptr;
}
} // namespace mozjs

View File

@ -582,7 +582,7 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine, boost::optional<int> j
{
JS_AddInterruptCallback(_context, _interruptCallback);
JS_SetGCCallback(_context, _gcCallback, this);
JS_SetContextPrivate(_context, this);
JS_SetContextPrivate(_context, static_cast<MozJSCommonRuntimeInterface*>(this));
JSAutoRealm ac(_context, _global);
_environmentPreparer = std::make_unique<EnvironmentPreparer>(_context);
@ -631,6 +631,10 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine, boost::optional<int> j
currentJSScope = this;
}
MozJSShellRuntimeInterface* getShellRuntime(JSContext* cx) {
return static_cast<MozJSImplScope*>(getCommonRuntime(cx));
}
MozJSImplScope::~MozJSImplScope() {
invariant(!_promiseResult.initialized());
currentJSScope = nullptr;

View File

@ -44,7 +44,6 @@
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/global.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/types/bindata.h"
#include "mongo/scripting/mozjs/common/types/bson.h"
#include "mongo/scripting/mozjs/common/types/code.h"
@ -74,6 +73,7 @@
#include "mongo/scripting/mozjs/shell/mongo.h"
#include "mongo/scripting/mozjs/shell/mongohelpers.h"
#include "mongo/scripting/mozjs/shell/resumetoken.h"
#include "mongo/scripting/mozjs/shell/runtime.h"
#include "mongo/scripting/mozjs/shell/session.h"
#include "mongo/scripting/mozjs/shell/uri.h"
#include "mongo/stdx/condition_variable.h"
@ -131,15 +131,20 @@ const StringData kUnknownError = "Unknown Failure from JSInterpreter";
*
* For more information about overriden fields, see mongo::Scope
*/
class MONGO_MOD_PUB MozJSImplScope final : public MozJSScopeBase {
class MONGO_MOD_PUB MozJSImplScope final : public Scope,
public MozJSShellRuntimeInterface,
public MozJSCommonRuntimeInterface {
MozJSImplScope(const MozJSImplScope&) = delete;
MozJSImplScope& operator=(const MozJSImplScope&) = delete;
template <typename T>
friend WrapType<T>& getProto(MozJSCommonRuntimeInterface*);
public:
explicit MozJSImplScope(MozJSScriptEngine* engine, boost::optional<int> jsHeapLimitMB);
~MozJSImplScope() override;
// ---- MozJSScopeBase (common) surface ----
// ---- Proto accessors ----
WrapType<BinDataInfo>& binDataProto() override {
return _binDataProto;
}
@ -668,13 +673,37 @@ private:
};
MONGO_MOD_PUB inline MozJSImplScope* getScope(JSContext* cx) {
return static_cast<MozJSImplScope*>(JS_GetContextPrivate(cx));
return static_cast<MozJSImplScope*>(getCommonRuntime(cx));
}
inline MozJSImplScope* getScope(class JS::GCContext* gcCtx) {
return getScope(freeOpToJSContext(gcCtx));
}
template <>
inline WrapType<CursorHandleInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return static_cast<MozJSImplScope*>(runtime)->cursorHandleProto();
}
template <>
inline WrapType<CursorInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return static_cast<MozJSImplScope*>(runtime)->cursorProto();
}
template <>
inline WrapType<MongoExternalInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return static_cast<MozJSImplScope*>(runtime)->mongoExternalProto();
}
template <>
inline WrapType<SessionInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return static_cast<MozJSImplScope*>(runtime)->sessionProto();
}
template <>
inline WrapType<URIInfo>& getProto(MozJSCommonRuntimeInterface* runtime) {
return static_cast<MozJSImplScope*>(runtime)->uriProto();
}
} // namespace mozjs
} // namespace mongo

View File

@ -30,9 +30,9 @@
#include "mongo/scripting/mozjs/shell/resumetoken.h"
#include "mongo/db/pipeline/resume_token.h"
#include "mongo/scripting/mozjs/common/scope_base.h"
#include "mongo/scripting/mozjs/common/types/bson.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h" // IWYU pragma: keep
#include "mongo/scripting/mozjs/shell/runtime.h"
#include <string>
@ -60,8 +60,8 @@ void ResumeTokenDataUtility::Functions::decodeResumeToken::call(JSContext* cx, J
const auto resumeTokenDataBson = ResumeToken::parse(encodedResumeTokenBson).getData().toBSON();
JS::RootedObject thisValue(cx);
auto* scope = getMozJSScope(cx);
getProto<BSONInfo>(scope).newObject(&thisValue);
auto* runtime = getCommonRuntime(cx);
getProto<BSONInfo>(runtime).newObject(&thisValue);
BSONInfo::make(cx, &thisValue, std::move(resumeTokenDataBson), nullptr, true);
args.rval().setObjectOrNull(thisValue);

View File

@ -0,0 +1,67 @@
/**
* 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.
*/
#pragma once
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/util/modules.h"
struct JSContext;
namespace mongo::mozjs {
struct CursorHandleInfo;
struct CursorInfo;
struct MongoExternalInfo;
struct SessionInfo;
struct URIInfo;
/**
* Shell-specific prototype accessors for JS types that only exist in the
* full mongo shell (cursors, connections, sessions, URIs).
*/
class MONGO_MOD_PUB MozJSShellRuntimeInterface {
public:
virtual ~MozJSShellRuntimeInterface() = default;
virtual WrapType<CursorHandleInfo>& cursorHandleProto() = 0;
virtual WrapType<CursorInfo>& cursorProto() = 0;
virtual WrapType<MongoExternalInfo>& mongoExternalProto() = 0;
virtual WrapType<SessionInfo>& sessionProto() = 0;
virtual WrapType<URIInfo>& uriProto() = 0;
};
/**
* Get the MozJSShellRuntimeInterface from a JSContext.
* Use this only when shell-specific features are needed.
* For common code, use getCommonRuntime() from scope.h instead.
*/
MozJSShellRuntimeInterface* getShellRuntime(JSContext* cx);
} // namespace mongo::mozjs

View File

@ -0,0 +1,195 @@
load("//bazel:mongo_src_rules.bzl", "mongo_cc_benchmark", "mongo_cc_binary", "mongo_cc_library", "mongo_cc_unit_test") # @unused mongo_cc_binary mongo_cc_library
load("//bazel/toolchains/cc/mongo_wasm/toolchain:with_wasi_config.bzl", "with_wasi_config")
load("//src/mongo/scripting/mozjs/wasm/engine:linkset.bzl", "cc_linkset")
# Gate WASM module compilation behind --define=build_mozjs_wasm=true.
# Without this flag the test compiles and runs but skips (no .wasm module found).
config_setting(
name = "build_mozjs_wasm",
define_values = {"build_mozjs_wasm": "true"},
)
exports_files([
"status.cpp",
])
cc_linkset(
name = "mongo_base_linkset",
extra_flags = [
"-lc++",
"-lc++abi",
"-lwasi-emulated-signal",
"-lwasi-emulated-mman",
"-lwasi-emulated-process-clocks",
"-lwasi-emulated-getpid",
"-Wl,--gc-sections",
"-Wl,--no-entry",
"-Wl,--export-memory",
"-Wl,--stack-first",
"-mexec-model=reactor",
],
deps = ["//src/mongo:base"],
)
genrule(
name = "spidermonkey_wasip2_dist_release_from_source",
srcs = [
"@spidermonkey//:mach",
"@spidermonkey//:srcs",
"support/rust_shims/src/lib.rs",
"support/rust_shims/Cargo.toml.template",
"scripts/build_spidermonkey_wasip2.sh",
],
outs = ["spidermonkey-wasip2-release.tar.gz"],
cmd = """
export WASI_SDK_BIN_FILES="$(locations @wasi_sdk//:bin)"
export SPIDER_MACH_PATH="$(location @spidermonkey//:mach)"
export RUST_SHIMS_LIB_RS="$(location support/rust_shims/src/lib.rs)"
export CARGO_TEMPLATE_PATH="$(location support/rust_shims/Cargo.toml.template)"
export OUTPUT="$@"
bash $(location scripts/build_spidermonkey_wasip2.sh)
""",
tags = [
"local",
"manual",
"no-sandbox",
],
tools = [
"@wasi_sdk//:bin",
],
)
alias(
name = "spidermonkey_wasip2_dist_release",
actual = select({
"@platforms//os:wasi": "@spidermonkey_wasip2_dist//file",
"//conditions:default": ":spidermonkey_wasip2_dist_release_from_source",
}),
)
# Source files for WASM compilation (excludes test files and stubs)
filegroup(
name = "wasm_sources",
srcs = glob(
["*.cpp"],
exclude = [
"status.cpp", # Included in wasi_sources filegroup
],
) + [
# engine/ is its own package so glob cannot reach it; list explicitly.
"//src/mongo/scripting/mozjs/wasm/engine:engine.cpp",
"//src/mongo/scripting/mozjs/wasm/engine:error.cpp",
],
)
# Rust shims for WASI linkage (provides encoding_* symbols)
# These are built by spidermonkey_wasip2_dist_release and included in the tarball.
# This genrule extracts them from the tarball.
genrule(
name = "rust_shims",
srcs = [
":spidermonkey_wasip2_dist_release",
"scripts/extract_rust_shims.sh",
],
outs = ["rust_shims.a"],
cmd = """
export TARBALL="$(location :spidermonkey_wasip2_dist_release)"
export OUTPUT="$@"
bash $(location scripts/extract_rust_shims.sh)
""",
tags = ["manual"],
tools = [],
)
genrule(
name = "mozjs_wasm_api",
srcs = [
":wasm_sources",
"//src/mongo/scripting/mozjs/common:wasi_sources",
"//src/mongo/scripting/mozjs/wasm/engine:exception_stubs.cpp",
"//src/mongo/scripting/mozjs/wasm/engine:api.cpp",
":spidermonkey_wasip2_dist_release",
":rust_shims",
":mongo_base_linkset",
"//src/mongo/base:error_codes_header",
"//src/mongo:mongo_config_header",
"//src/mongo:base",
# WIT + generated bindings
"//src/mongo/scripting/mozjs/wasm/wit:mozjs.wit",
"//src/mongo/scripting/mozjs/wasm/wit_gen/generated:api.c",
"//src/mongo/scripting/mozjs/wasm/wit_gen/generated:api.h",
"//src/mongo/scripting/mozjs/wasm/wit_gen/generated:api_component_type.o",
# Build scripts
"scripts/compile_mozjs_wasm_api.sh",
"scripts/compile_wasi_source.sh",
],
outs = ["mozjs_wasm_api.wasm"],
cmd = """
export WASI_SDK_BIN_FILES="$(locations @wasi_sdk//:bin)"
export SM_TARBALL="$(location :spidermonkey_wasip2_dist_release)"
export RUST_SHIMS_PATH="$(location :rust_shims)"
export WASM_SOURCES="$(locations :wasm_sources)"
export COMMON_WASI_SOURCES="$(locations //src/mongo/scripting/mozjs/common:wasi_sources)"
export EXCEPTION_STUBS_SRC="$(location //src/mongo/scripting/mozjs/wasm/engine:exception_stubs.cpp)"
export MOZJS_API_SRC="$(location //src/mongo/scripting/mozjs/wasm/engine:api.cpp)"
export WIT_API_C="$(location //src/mongo/scripting/mozjs/wasm/wit_gen/generated:api.c)"
export WIT_COMPONENT_TYPE_OBJ="$(location //src/mongo/scripting/mozjs/wasm/wit_gen/generated:api_component_type.o)"
export ERROR_CODES_HEADER_FILES="$(locations //src/mongo/base:error_codes_header)"
export CONFIG_HEADER_FILES="$(locations //src/mongo:mongo_config_header)"
export LINKSET_FILES="$(locations :mongo_base_linkset)"
export COMPILE_HELPER="$(location scripts/compile_wasi_source.sh)"
export OUTPUT="$@"
bash $(location scripts/compile_mozjs_wasm_api.sh)
""",
tags = [
"local",
"manual",
"no-sandbox",
],
target_compatible_with = select({
"@platforms//os:wasi": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
tools = [
"@wasi_sdk//:bin",
],
visibility = ["//visibility:public"],
)
with_wasi_config(
name = "mozjs_wasm_api_data_from_source",
srcs = [":mozjs_wasm_api"],
tags = ["manual"],
target_compatible_with = ["@platforms//os:linux"],
)
alias(
name = "mozjs_wasm_api_data",
actual = select({
":build_mozjs_wasm": ":mozjs_wasm_api_data_from_source",
"//conditions:default": "@mozjs_wasm//file",
}),
)
mongo_cc_unit_test(
name = "wasm_mozjs_test",
size = "large",
timeout = "long",
srcs = ["//src/mongo/scripting/mozjs/wasm/engine:engine_test.cpp"],
auto_header = False,
data = [":mozjs_wasm_api_data"],
tags = [
"incompatible_with_bazel_remote_test",
"local",
"mongo_unittest_eighth_group",
"mozjs_wasm_tests",
"no-sandbox",
],
target_compatible_with = ["@platforms//os:linux"],
deps = [
"//src/mongo:base",
"@crates//:wasmtime_c",
],
)

View File

@ -0,0 +1,51 @@
/**
* 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 <cstdlib>
// mozglue stub for rust hooks
extern "C" void install_rust_hooks() {}
// Canonical ABI allocator hook used by the component model.
// Required for wasm-component-ld to produce a valid component.
extern "C" void* cabi_realloc(void* ptr, size_t old_size, size_t align, size_t new_size) {
(void)old_size;
(void)align;
if (new_size == 0) {
std::free(ptr);
return nullptr;
}
void* result = std::realloc(ptr, new_size);
if (!result) {
// realloc failed -- original ptr is still valid but we cannot grow.
// In WASM component model this is typically fatal.
std::abort();
}
return result;
}

View File

@ -0,0 +1 @@
exports_files(glob(["*"]))

View File

@ -0,0 +1,81 @@
# MozJS WASM Engine
The WASM engine wraps SpiderMonkey into a WASI Preview 2 component
(`mozjs_wasm_api.wasm`) that the host (mongod/mongos) loads via Wasmtime. The
host calls exported WIT functions to create JS contexts, compile functions,
invoke them with BSON arguments, and read back BSON results — all inside a
sandboxed WebAssembly instance.
## Architecture
```
Host (Wasmtime)
WIT exports ── api.cpp extern "C" functions generated by wit-bindgen
MozJSScriptEngine ── engine.cpp/.h manages JSContext, function slots, BSON ↔ JS
SpiderMonkey (libjs_static.a, compiled for wasm32-wasip2)
```
Key files in this directory:
| File | Purpose |
| ----------------------- | ----------------------------------------------------------------------------------------------- |
| `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 |
## WIT Interface
The public API is defined in `../wit/mozjs.wit` (package `mongo:mozjs`, world
`api`). The world exports a single `mozjs` interface:
- `initialize-engine` / `shutdown-engine` / `interrupt-current-op` — lifecycle
- `create-function(source)``function-handle` — compile JS source
- `invoke-function(handle, bson)` — call a compiled function with BSON args
- `get-return-value-bson` — retrieve the last return value as BSON
- `set-global` / `get-global` — read/write named JS globals as BSON
C bindings live in `../wit_gen/generated/` and are produced by:
```bash
wit-bindgen c ../wit --out-dir ../wit_gen/generated
```
The generated `api.h` declares the `exports_*` symbols that `api.cpp`
implements. The generated `api_component_type.o` is linked into the final
`.wasm` to embed the component type section.
## Building
Everything is driven by Bazel from `../BUILD.bazel`.
Without `--define=build_mozjs_wasm=true` a prebuilt `.wasm` is fetched from S3
instead.
**Build WASM module and run unit tests:**
```bash
bazel test //src/mongo/scripting/mozjs/wasm:wasm_mozjs_test --define=build_mozjs_wasm=true --spawn_strategy=local
```
Tests load `mozjs_wasm_api.wasm` into Wasmtime, instantiate it as a WASI
Preview 2 component, and exercise every WIT export.
## Dependencies
| Dependency | How it's pulled in |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SpiderMonkey** | `@spidermonkey//` Bazel repo (version in `../spider-monkey/`). Built for `wasm32-wasip2` by `scripts/build_spidermonkey_wasip2.sh`. |
| **WASI SDK** | `@wasi_sdk//` Bazel repo (`bazel/toolchains/cc/mongo_wasm/`). Provides `clang` cross-compiler and WASI sysroot. |
| **Rust shims** | `../support/rust_shims/` — small Rust crate providing `encoding_c` / `encoding_c_mem` symbols needed by SpiderMonkey's ICU layer. Built during the SpiderMonkey build, extracted by `scripts/extract_rust_shims.sh`. |
| **Wasmtime** | `@crates//:wasmtime_c`. Used by the host side and tests, not compiled into the `.wasm`. |
| **MongoDB base** | `//src/mongo:base` — linked into the WASM module via the `cc_linkset` rule so that code like `BSONObj`, `Status`, etc. is available inside the sandbox. |

View File

@ -0,0 +1,478 @@
/**
* 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 "mongo/scripting/mozjs/wasm/wit_gen/generated/api.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/scripting/mozjs/shared/mozjs_error_types.h"
#include "mongo/scripting/mozjs/shared/mozjs_wasm_startup_options.h"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include "engine.h"
#include "error.h"
namespace {
// Maximum JavaScript source code size (1 MB)
constexpr size_t kMaxJsSourceSize = 1 * 1024 * 1024;
// Maximum BSON document size (16mb)
constexpr size_t kMaxBsonSize = 16 * 1024 * 1024;
// Maximum number of functions that can be created
constexpr size_t kMaxFunctions = 10000;
// Default operation timeout in microseconds (30 seconds)
// This prevents infinite loops in JavaScript code.
constexpr size_t kDefaultOpTimeoutMicros = 30 * 1000 * 1000;
// Default JS heap size in MB for the WASM engine.
constexpr uint32_t kDefaultHeapSizeMB = 100;
} // namespace
namespace mongo {
namespace mozjs {
namespace wasm {
// Thread-safety: This module is designed for single-threaded use within a
// single WASM instance. Each WASM instance gets its own linear memory and
// thus its own copy of g_engine. Do not share a WASM instance across threads.
static MozJSScriptEngine g_engine;
} // namespace wasm
} // namespace mozjs
} // namespace mongo
static void set_opt_string(api_option_string_t* out, const char* ptr, size_t len) {
if (ptr && len > 0) {
out->is_some = true;
api_string_dup_n(&out->val, ptr, len);
} else {
out->is_some = false;
out->val.ptr = nullptr;
out->val.len = 0;
}
}
static void fill_wit_error(exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* out,
const mongo::mozjs::wasm::wasm_mozjs_error_t& in) {
out->code = static_cast<exports_mongo_mozjs_mozjs_err_code_t>(in.code);
set_opt_string(&out->msg, in.msg, in.msg_len);
set_opt_string(&out->filename, in.filename, in.filename_len);
set_opt_string(&out->stack, in.stack, in.stack_len);
out->line = in.line;
out->column = in.column;
}
static bool return_err(exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err,
mongo::mozjs::wasm::wasm_mozjs_error_t* native_err) {
if (!native_err) {
return false;
}
fill_wit_error(err, *native_err);
mongo::mozjs::wasm::clear_error(native_err);
// wit-bindgen C convention: return false means error (adapter does: is_err = !retval)
return false;
}
// 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) {
out->ptr = nullptr;
out->len = 0;
return true;
}
auto* p = static_cast<uint8_t*>(std::malloc(len));
if (!p) {
out->ptr = nullptr;
out->len = 0;
return false; // OOM
}
std::memcpy(p, data, len);
out->ptr = p;
out->len = len;
return true;
}
// Validates BSON data before constructing BSONObj to prevent crashes from
// malformed input. This performs basic structural validation.
static bool validate_bson(const uint8_t* data,
size_t len,
mongo::mozjs::wasm::wasm_mozjs_error_t* err) {
if (len < 5 || len > kMaxBsonSize) {
if (err) {
err->code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
mongo::mozjs::wasm::set_string(&err->msg,
&err->msg_len,
len < 5 ? "BSON too small (minimum 5 bytes)"
: "BSON exceeds 16 MB maximum");
}
return false;
}
int32_t declared_size;
std::memcpy(&declared_size, data, sizeof(declared_size));
if (declared_size < 5 || static_cast<size_t>(declared_size) > len ||
data[declared_size - 1] != 0) {
if (err) {
err->code = mongo::mozjs::wasm::SM_E_INVALID_ARG;
mongo::mozjs::wasm::set_string(
&err->msg, &err->msg_len, "BSON header invalid (size mismatch or no terminator)");
}
return false;
}
return true;
}
// Track function count for limiting.
static size_t g_function_count = 0;
// Exported Functions from `mongo:mozjs/mozjs`
extern "C" bool exports_mongo_mozjs_mozjs_initialize_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{};
// TODO SERVER-116051: Make wasm_mozjs_startup_options_t an argument.
mongo::mozjs::wasm::wasm_mozjs_startup_options_t opt{};
opt.opTimeout = opt.opTimeout > 0 ? opt.opTimeout : kDefaultOpTimeoutMicros;
opt.heapSize = opt.heapSize > 0 ? opt.heapSize : kDefaultHeapSizeMB;
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);
}
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);
}
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);
}
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{};
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 (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);
}
extern "C" bool exports_mongo_mozjs_mozjs_invoke_function(
exports_mongo_mozjs_mozjs_function_handle_t handle,
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{};
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)) {
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);
}
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;
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;
}
extern "C" bool exports_mongo_mozjs_mozjs_set_global(
api_string_t* name,
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{};
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)) {
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);
}
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{};
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));
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;
}
extern "C" bool exports_mongo_mozjs_mozjs_set_global_value(
api_string_t* name,
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{};
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)) {
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);
}
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{};
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));
if (rc == mongo::mozjs::wasm::SM_OK) {
if (ret)
*ret = 0;
return true;
}
return return_err(err, &e);
}
extern "C" bool exports_mongo_mozjs_mozjs_invoke_predicate(
exports_mongo_mozjs_mozjs_function_handle_t handle,
api_list_u8_t* document,
bool* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err) {
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);
}
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);
}
extern "C" bool exports_mongo_mozjs_mozjs_invoke_map(
exports_mongo_mozjs_mozjs_function_handle_t handle,
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{};
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)) {
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);
}
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;
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;
}

View File

@ -0,0 +1,942 @@
/**
* 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 "engine.h"
#include "mongo/base/status.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/scripting/mozjs/common/error.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/types/bindata.h"
#include "mongo/scripting/mozjs/common/types/bson.h"
#include "mongo/scripting/mozjs/common/types/code.h"
#include "mongo/scripting/mozjs/common/types/dbpointer.h"
#include "mongo/scripting/mozjs/common/types/dbref.h"
#include "mongo/scripting/mozjs/common/types/maxkey.h"
#include "mongo/scripting/mozjs/common/types/minkey.h"
#include "mongo/scripting/mozjs/common/types/nativefunction.h"
#include "mongo/scripting/mozjs/common/types/numberdecimal.h"
#include "mongo/scripting/mozjs/common/types/numberint.h"
#include "mongo/scripting/mozjs/common/types/numberlong.h"
#include "mongo/scripting/mozjs/common/types/oid.h"
#include "mongo/scripting/mozjs/common/types/regexp.h"
#include "mongo/scripting/mozjs/common/types/status.h"
#include "mongo/scripting/mozjs/common/types/timestamp.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/valuewriter.h"
#include "mongo/util/assert_util.h"
#include <cstdio>
#include <cstring>
#include <ctime>
#include <memory>
#include <string>
#include "error.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/CompilationAndEvaluation.h"
#include "js/Conversions.h"
#include "js/ErrorReport.h"
#include "js/Exception.h"
#include "js/GlobalObject.h"
#include "js/Initialization.h"
#include "js/Interrupt.h"
#include "js/Realm.h"
#include "js/SourceText.h"
#include "js/String.h"
#include "js/Value.h"
#include "js/ValueArray.h"
#include <mozilla/Utf8.h>
namespace mongo {
namespace mozjs {
namespace wasm {
uint32_t g_wasmJsHeapLimitMB = 0;
const char* const kInvokeResult = "__returnValue";
FunctionSlot* MozJSScriptEngine::resolveHandle(uint64_t handle, wasm_mozjs_error_t* err) {
if (handle == 0 || handle > _slots.size()) {
if (err) {
err->code = SM_E_INVALID_ARG;
set_string(&err->msg, &err->msg_len, "invalid function handle");
}
return nullptr;
}
FunctionSlot& slot = _slots[handle - 1];
if (!slot.fn) {
if (err) {
err->code = SM_E_INVALID_ARG;
set_string(&err->msg, &err->msg_len, "function handle has no function");
}
return nullptr;
}
return &slot;
}
MozJSScriptEngine::~MozJSScriptEngine() {
if (_initialized) {
shutdown(nullptr);
}
}
err_code_t MozJSScriptEngine::init(const wasm_mozjs_startup_options_t* opt,
wasm_mozjs_error_t* err) {
clear_error(err);
if (_initialized)
return SM_OK;
if (!JS_Init()) {
if (err) {
err->code = SM_E_INTERNAL;
set_string(&err->msg, &err->msg_len, "JS_Init failed");
}
return SM_E_INTERNAL;
}
// Create context with reasonable heap size for WASM builds
constexpr size_t kMaxHeapSizeMB = 2048; // 2 GB upper bound
if (opt->heapSize == 0 || opt->heapSize > kMaxHeapSizeMB) {
if (err) {
err->code = SM_E_INVALID_ARG;
set_string(&err->msg, &err->msg_len, "heapSize out of valid range (1-2048 MB)");
}
JS_ShutDown();
return SM_E_INVALID_ARG;
}
_cx = JS_NewContext(static_cast<size_t>(opt->heapSize) * 1024 * 1024);
if (!_cx) {
if (err) {
err->code = SM_E_OOM;
set_string(&err->msg, &err->msg_len, "JS_NewContext failed");
}
JS_ShutDown();
return SM_E_OOM;
}
// Store configured heap limit for GlobalInfo::getJSHeapLimitMB.
g_wasmJsHeapLimitMB = opt->heapSize;
_rt = JS_GetRuntime(_cx);
if (!_rt) {
if (err) {
err->code = SM_E_INTERNAL;
set_string(&err->msg, &err->msg_len, "JS_GetRuntime returned null");
}
JS_DestroyContext(_cx);
_cx = nullptr;
JS_ShutDown();
return SM_E_INTERNAL;
}
// Initialize self-hosted code (required before creating global)
ExecutionCheck chk(_cx, err);
if (!chk.ok(JS::InitSelfHostedCode(_cx), SM_E_INTERNAL)) {
JS_DestroyContext(_cx);
_cx = nullptr;
JS_ShutDown();
return err ? err->code : SM_E_INTERNAL;
}
// Create global object (must be done before setting interrupt callback)
static const JSClass _globalclass = {
"global", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps};
JS::RealmOptions ro;
_global.init(_cx);
{
JS::RootedObject g(_cx,
chk.okPtr(JS_NewGlobalObject(
_cx, &_globalclass, nullptr, JS::DontFireOnNewGlobalHook, ro)));
if (!g) {
shutdown(nullptr);
return err ? err->code : SM_E_INTERNAL;
}
_global = g;
}
// Enter realm and initialize standard classes
{
JSAutoRealm ar(_cx, _global);
if (!chk.ok(JS::InitRealmStandardClasses(_cx), SM_E_INTERNAL)) {
shutdown(nullptr);
return err ? err->code : SM_E_INTERNAL;
}
}
// Fire the new global hook after initialization (as per SpiderMonkey docs)
JS_FireOnNewGlobalObject(_cx, _global);
// JS_FireOnNewGlobalObject doesn't return a value, but we check for exceptions
if (!chk.ok(!JS_IsExceptionPending(_cx), SM_E_INTERNAL)) {
shutdown(nullptr);
return err ? err->code : SM_E_INTERNAL;
}
// Stash pointer for interrupt callback (after global is set up)
JS_SetContextPrivate(_cx, static_cast<MozJSCommonRuntimeInterface*>(this));
// Set up interrupt callback for timeouts/cancel/interrupt
if (!JS_AddInterruptCallback(_cx, &MozJSScriptEngine::interruptCallback)) {
if (err) {
err->code = SM_E_INTERNAL;
set_string(&err->msg, &err->msg_len, "JS_AddInterruptCallback failed");
}
shutdown(nullptr);
return SM_E_INTERNAL;
}
_prototypeInstaller = std::make_unique<MozJSPrototypeInstaller>(_cx);
{
JSAutoRealm ar(_cx, _global);
_internedStrings = std::make_unique<InternedStringTable>(_cx);
_prototypeInstaller->installTypes(_global);
}
_initialized = true;
return SM_OK;
}
err_code_t MozJSScriptEngine::shutdown(wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized && !_cx)
return SM_OK;
// Drop all PersistentRooted objects while the context is alive.
_slots.clear();
_internedStrings.reset();
_prototypeInstaller.reset();
_global.reset();
if (_cx) {
// Do NOT null context-private before DestroyContext:
// GC finalizers need getCommonRuntime() during teardown.
JS_DestroyContext(_cx);
_cx = nullptr;
}
JS_ShutDown();
_rt = nullptr;
_initialized = false;
return SM_OK;
}
err_code_t MozJSScriptEngine::interrupt(wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx)
return SM_E_BAD_STATE;
ExecutionCheck chk(_cx, err);
// Request interrupt callback - host side manages interrupt state
JS_RequestInterruptCallback(_cx);
// JS_RequestInterruptCallback doesn't return a value, but we check for exceptions
if (!chk.ok(!JS_IsExceptionPending(_cx), SM_E_INTERNAL)) {
return err ? err->code : SM_E_INTERNAL;
}
return SM_OK;
}
err_code_t MozJSScriptEngine::createFunction(const uint8_t* src,
size_t len,
uint64_t* out_handle,
wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
if (!src || len == 0 || !out_handle)
return SM_E_INVALID_ARG;
JSAutoRealm ar(_cx, _global);
ExecutionCheck chk(_cx, err);
std::string code_str;
code_str.reserve(len + 2);
code_str += '(';
code_str.append(reinterpret_cast<const char*>(src), len);
code_str += ')';
JS::CompileOptions opts(_cx);
opts.setFileAndLine("wasm:function", 1);
JS::SourceText<mozilla::Utf8Unit> text;
// Use .data() and .size() like in implscope.cpp
// The CharT overload accepts const char* for UTF-8
if (!chk.ok(text.init(_cx, code_str.data(), code_str.size(), JS::SourceOwnership::Borrowed),
SM_E_ENCODING)) {
return err ? err->code : SM_E_ENCODING;
}
JS::RootedValue v(_cx);
if (!chk.ok(JS::Evaluate(_cx, opts, text, &v), SM_E_COMPILE)) {
// classify common path
if (err && err->code == SM_E_PENDING_EXCEPTION)
err->code = SM_E_COMPILE;
return err ? err->code : SM_E_COMPILE;
}
if (!v.isObject() || !js::IsFunctionObject(v.toObjectOrNull())) {
if (err) {
err->code = SM_E_TYPE;
set_string(
&err->msg, &err->msg_len, "createFunction: evaluated value is not a function");
}
return SM_E_TYPE;
}
_slots.emplace_back(_cx);
FunctionSlot& slot = _slots.back();
slot.fn = &v.toObject();
*out_handle = static_cast<uint64_t>(_slots.size()); // 1-based
return SM_OK;
}
err_code_t MozJSScriptEngine::invokeFunction(uint64_t handle,
mongo::BSONObj&& argsObject,
mongo::BSONObj* outBson,
wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
auto* slot = resolveHandle(handle, err);
if (!slot)
return SM_E_INVALID_ARG;
JSAutoRealm ar(_cx, _global);
ExecutionCheck chk(_cx, err);
const int nargs = argsObject.nFields();
JS::RootedValueVector args(_cx);
if (nargs) {
BSONObjIterator it(argsObject);
for (int i = 0; i < nargs; i++) {
BSONElement next = it.next();
JS::RootedValue value(_cx);
ValueReader(_cx, &value).fromBSONElement(next, argsObject, false /*readOnlyArgs*/);
if (!args.append(value)) {
if (err) {
err->code = SM_E_INVALID_ARG;
set_string(&err->msg, &err->msg_len, "Failed to append property");
}
return SM_E_INVALID_ARG;
}
}
}
JS::RootedValue out(_cx);
JS::RootedObject funcObj(_cx, slot->fn);
JS::RootedValue funVal(_cx, JS::ObjectValue(*funcObj));
bool success = JS::Call(_cx, _global, funVal, args, &out);
if (!chk.ok(success, SM_E_RUNTIME)) {
if (err && err->code == SM_E_PENDING_EXCEPTION)
err->code = SM_E_RUNTIME;
return err ? err->code : SM_E_RUNTIME;
}
if (_emitByteLimit > 0 && _emitBytesUsed > _emitByteLimit) {
if (err) {
err->code = SM_E_RUNTIME;
set_string(&err->msg, &err->msg_len, "emit() exceeded memory limit");
}
return SM_E_RUNTIME;
}
// Store return value on global (same key as implscope) so getReturnValueBson can read it.
ObjectWrapper(_cx, _global).setValue(kInvokeResult, out);
if (outBson) {
// Also write return value as BSON when caller provides a buffer (wrapped like implscope).
JS::RootedObject rout(_cx, JS_NewPlainObject(_cx));
if (rout) {
ObjectWrapper wout(_cx, rout);
wout.setValue(kInvokeResult, out);
*outBson = wout.toBSON();
}
}
return SM_OK;
}
err_code_t MozJSScriptEngine::getReturnValueBson(mongo::BSONObj* out, wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
if (!out)
return SM_E_INVALID_ARG;
JSAutoRealm ar(_cx, _global);
ExecutionCheck chk(_cx, err);
JS::RootedValue rval(_cx);
JS::RootedString key_str(_cx, chk.okPtr(JS_NewStringCopyZ(_cx, kInvokeResult)));
if (!key_str) {
return err ? err->code : SM_E_INTERNAL;
}
JS::RootedId rid(_cx);
if (!chk.ok(JS_StringToId(_cx, key_str, &rid), SM_E_INTERNAL)) {
return err ? err->code : SM_E_INTERNAL;
}
if (!chk.ok(JS_GetPropertyById(_cx, _global, rid, &rval), SM_E_INTERNAL)) {
return err ? err->code : SM_E_INTERNAL;
}
// Wrap value in object for BSON (same pattern as implscope) so primitives serialize correctly.
JS::RootedObject rout(_cx, JS_NewPlainObject(_cx));
if (!rout) {
if (err) {
err->code = SM_E_INTERNAL;
set_string(
&err->msg, &err->msg_len, "getReturnValueBson: failed to create wrapper object");
}
return SM_E_INTERNAL;
}
ObjectWrapper wout(_cx, rout);
wout.setValue(kInvokeResult, rval);
*out = wout.toBSON();
return SM_OK;
}
err_code_t MozJSScriptEngine::invokePredicate(uint64_t handle,
mongo::BSONObj&& document,
bool* outResult,
wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
if (!outResult)
return SM_E_INVALID_ARG;
auto* slot = resolveHandle(handle, err);
if (!slot)
return SM_E_INVALID_ARG;
JSAutoRealm ar(_cx, _global);
ExecutionCheck chk(_cx, err);
JS::RootedValue smrecv(_cx);
if (!document.isEmpty())
ValueReader(_cx, &smrecv).fromBSON(document, nullptr, false);
else
smrecv.setObjectOrNull(_global);
JS::RootedValueVector args(_cx);
JS::RootedValue out(_cx);
JS::RootedObject obj(_cx, smrecv.toObjectOrNull());
JS::RootedObject funcObj(_cx, slot->fn);
JS::RootedValue funVal(_cx, JS::ObjectValue(*funcObj));
bool success = JS::Call(_cx, obj, funVal, args, &out);
if (!chk.ok(success, SM_E_RUNTIME)) {
if (err && err->code == SM_E_PENDING_EXCEPTION)
err->code = SM_E_RUNTIME;
return err ? err->code : SM_E_RUNTIME;
}
*outResult = JS::ToBoolean(out);
return SM_OK;
}
err_code_t MozJSScriptEngine::invokeMap(uint64_t handle,
mongo::BSONObj&& document,
wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
auto* slot = resolveHandle(handle, err);
if (!slot)
return SM_E_INVALID_ARG;
JSAutoRealm ar(_cx, _global);
ExecutionCheck chk(_cx, err);
JS::RootedValue smrecv(_cx);
if (!document.isEmpty())
ValueReader(_cx, &smrecv).fromBSON(document, nullptr, false);
else
smrecv.setObjectOrNull(_global);
JS::RootedValueVector args(_cx);
JS::RootedValue out(_cx);
JS::RootedObject obj(_cx, smrecv.toObjectOrNull());
JS::RootedObject funcObj(_cx, slot->fn);
JS::RootedValue funVal(_cx, JS::ObjectValue(*funcObj));
bool success = JS::Call(_cx, obj, funVal, args, &out);
if (!chk.ok(success, SM_E_RUNTIME)) {
if (err && err->code == SM_E_PENDING_EXCEPTION)
err->code = SM_E_RUNTIME;
return err ? err->code : SM_E_RUNTIME;
}
if (_emitByteLimit > 0 && _emitBytesUsed > _emitByteLimit) {
if (err) {
err->code = SM_E_RUNTIME;
set_string(&err->msg, &err->msg_len, "emit() exceeded memory limit");
}
return SM_E_RUNTIME;
}
return SM_OK;
}
err_code_t MozJSScriptEngine::setGlobal(const char* name,
size_t name_len,
const mongo::BSONObj& value,
wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
if (!name || name_len == 0)
return SM_E_INVALID_ARG;
JSAutoRealm ar(_cx, _global);
ExecutionCheck chk(_cx, err);
// Create a JS plain object and populate from BSON fields.
JS::RootedObject jsObj(_cx, JS_NewPlainObject(_cx));
if (!jsObj) {
if (err) {
err->code = SM_E_OOM;
set_string(&err->msg, &err->msg_len, "setGlobal: JS_NewPlainObject failed");
}
return SM_E_OOM;
}
for (BSONObjIterator it(value); it.more();) {
BSONElement elem = it.next();
JS::RootedValue val(_cx);
ValueReader(_cx, &val).fromBSONElement(elem, value, false /* readOnlyArgs */);
if (!chk.ok(JS_SetProperty(_cx, jsObj, elem.fieldName(), val), SM_E_INTERNAL)) {
return err ? err->code : SM_E_INTERNAL;
}
}
// Set the constructed object as a property on the global.
JS::RootedValue objVal(_cx, JS::ObjectValue(*jsObj));
std::string nameStr(name, name_len);
if (!chk.ok(JS_SetProperty(_cx, _global, nameStr.c_str(), objVal), SM_E_INTERNAL)) {
return err ? err->code : SM_E_INTERNAL;
}
return SM_OK;
}
err_code_t MozJSScriptEngine::getGlobal(const char* name,
size_t name_len,
mongo::BSONObj* out,
wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
if (!name || name_len == 0 || !out)
return SM_E_INVALID_ARG;
JSAutoRealm ar(_cx, _global);
ExecutionCheck chk(_cx, err);
std::string nameStr(name, name_len);
// Check if the property exists on the global object.
bool found = false;
if (!chk.ok(JS_HasProperty(_cx, _global, nameStr.c_str(), &found), SM_E_INTERNAL)) {
return err ? err->code : SM_E_INTERNAL;
}
if (!found) {
if (err) {
err->code = SM_E_INVALID_ARG;
set_string(&err->msg, &err->msg_len, "getGlobal: property not found");
}
return SM_E_INVALID_ARG;
}
JS::RootedValue rval(_cx);
if (!chk.ok(JS_GetProperty(_cx, _global, nameStr.c_str(), &rval), SM_E_INTERNAL)) {
return err ? err->code : SM_E_INTERNAL;
}
if (rval.isUndefined()) {
if (err) {
err->code = SM_E_INVALID_ARG;
set_string(&err->msg, &err->msg_len, "getGlobal: property is undefined");
}
return SM_E_INVALID_ARG;
}
// Convert the JS value to BSON.
if (rval.isObject()) {
JS::RootedObject robj(_cx, &rval.toObject());
ObjectWrapper wrapper(_cx, robj);
*out = wrapper.toBSON();
} else {
// Wrap a non-object (primitive) value in a BSON document.
JS::RootedObject wrapObj(_cx, JS_NewPlainObject(_cx));
if (!wrapObj) {
if (err) {
err->code = SM_E_OOM;
}
return SM_E_OOM;
}
ObjectWrapper wout(_cx, wrapObj);
wout.setValue("__value", rval);
*out = wout.toBSON();
}
return SM_OK;
}
err_code_t MozJSScriptEngine::setGlobalValue(const char* name,
size_t name_len,
const mongo::BSONObj& singleElementDoc,
wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
if (!name || name_len == 0)
return SM_E_INVALID_ARG;
BSONObjIterator it(singleElementDoc);
if (!it.more()) {
if (err) {
err->code = SM_E_INVALID_ARG;
set_string(&err->msg, &err->msg_len, "setGlobalValue: empty BSON document");
}
return SM_E_INVALID_ARG;
}
BSONElement elem = it.next();
JSAutoRealm ar(_cx, _global);
ExecutionCheck chk(_cx, err);
JS::RootedValue val(_cx);
ValueReader(_cx, &val).fromBSONElement(elem, singleElementDoc, false);
std::string nameStr(name, name_len);
if (!chk.ok(JS_SetProperty(_cx, _global, nameStr.c_str(), val), SM_E_INTERNAL)) {
return err ? err->code : SM_E_INTERNAL;
}
return SM_OK;
}
BSONObj MozJSScriptEngine::_emitCallback(const BSONObj& args, void* data) {
auto* engine = static_cast<MozJSScriptEngine*>(data);
BSONObjIterator it(args);
BSONElement keyElem = it.more() ? it.next() : BSONElement();
BSONElement valElem = it.more() ? it.next() : BSONElement();
BSONObjBuilder b;
if (keyElem.type() == BSONType::undefined || keyElem.eoo())
b.appendNull("k");
else
b.appendAs(keyElem, "k");
if (valElem.eoo())
b.appendNull("v");
else
b.appendAs(valElem, "v");
BSONObj doc = b.obj();
engine->_emitBytesUsed += doc.objsize();
if (engine->_emitBytesUsed <= engine->_emitByteLimit)
engine->_emitBuffer.push_back(std::move(doc));
return BSONObj();
}
err_code_t MozJSScriptEngine::setupEmit(int64_t byteLimit,
bool hasByteLimit,
wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized || !_cx || !_global)
return SM_E_BAD_STATE;
_emitByteLimit = hasByteLimit ? byteLimit : (16 * 1024 * 1024);
_emitBytesUsed = 0;
_emitBuffer.clear();
JSAutoRealm ar(_cx, _global);
JS::RootedObject obj(_cx);
NativeFunctionInfo::make(_cx, &obj, _emitCallback, this);
JS::RootedValue value(_cx);
value.setObjectOrNull(obj);
ObjectWrapper(_cx, _global).setValue("emit", value);
return SM_OK;
}
err_code_t MozJSScriptEngine::drainEmitBuffer(mongo::BSONObj* out, wasm_mozjs_error_t* err) {
clear_error(err);
if (!_initialized)
return SM_E_BAD_STATE;
if (!out)
return SM_E_INVALID_ARG;
BSONObjBuilder builder;
BSONArrayBuilder arr(builder.subarrayStart("emits"));
for (const auto& doc : _emitBuffer) {
arr.append(doc);
}
arr.done();
builder.append("bytesUsed", static_cast<long long>(_emitBytesUsed));
*out = builder.obj();
_emitBuffer.clear();
_emitBytesUsed = 0;
return SM_OK;
}
bool MozJSScriptEngine::interruptCallback(JSContext* cx) {
// Interrupt callback is called by MozJS when JS_RequestInterruptCallback is invoked.
// The host side manages interrupt/cancel state and termination logic.
// This callback just allows the interrupt to be processed - the host side
// should check return codes and exceptions to determine if execution was terminated.
(void)cx;
return true;
}
void MozJSScriptEngine::gc() {
if (!_initialized || !_cx) {
return;
}
JS_GC(_cx);
}
void MozJSScriptEngine::sleep(Milliseconds ms) {
auto count = ms.count();
if (count <= 0)
return;
struct timespec ts;
ts.tv_sec = static_cast<time_t>(count / 1000);
ts.tv_nsec = static_cast<long>((count % 1000) * 1000000L);
nanosleep(&ts, nullptr);
}
std::size_t MozJSScriptEngine::getGeneration() const {
// Return a constant generation for WASM
return 1;
}
JS::HandleId MozJSScriptEngine::getInternedStringId(InternedString name) {
return _internedStrings->getInternedString(name);
}
std::int64_t* MozJSScriptEngine::trackedNewInt64(std::int64_t value) {
auto* p = new std::int64_t(value);
trackNewPointer(p);
return p;
}
WrapType<NumberLongInfo>& MozJSScriptEngine::numberLongProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->numberLongProto();
}
WrapType<NumberIntInfo>& MozJSScriptEngine::numberIntProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->numberIntProto();
}
WrapType<NumberDecimalInfo>& MozJSScriptEngine::numberDecimalProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->numberDecimalProto();
}
WrapType<OIDInfo>& MozJSScriptEngine::oidProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->oidProto();
}
WrapType<BinDataInfo>& MozJSScriptEngine::binDataProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->binDataProto();
}
WrapType<TimestampInfo>& MozJSScriptEngine::timestampProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->timestampProto();
}
WrapType<MaxKeyInfo>& MozJSScriptEngine::maxKeyProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->maxKeyProto();
}
WrapType<MinKeyInfo>& MozJSScriptEngine::minKeyProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->minKeyProto();
}
void MozJSScriptEngine::trackNewPointer(void* ptr) {
(void)ptr;
}
void MozJSScriptEngine::trackDeletePointer(void* ptr) {
(void)ptr;
}
WrapType<CodeInfo>& MozJSScriptEngine::codeProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->codeProto();
}
WrapType<DBPointerInfo>& MozJSScriptEngine::dbPointerProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->dbPointerProto();
}
WrapType<NativeFunctionInfo>& MozJSScriptEngine::nativeFunctionProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->nativeFunctionProto();
}
WrapType<ErrorInfo>& MozJSScriptEngine::errorProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->errorProto();
}
WrapType<MongoStatusInfo>& MozJSScriptEngine::mongoStatusProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->statusProto();
}
void MozJSScriptEngine::setStatus(Status status) {
_status = std::move(status);
}
bool MozJSScriptEngine::isJavaScriptProtectionEnabled() const {
// JavaScript protection (--enableJavaScriptProtection) is not applicable
// in WASM builds where the engine runs in a sandboxed component.
return false;
}
bool MozJSScriptEngine::requiresOwnedObjects() const {
// WASM engine does not require BSON objects to be owned.
return false;
}
void MozJSScriptEngine::newFunction(StringData raw, JS::MutableHandleValue out) {
// Compile the code string as a function expression, same as createFunction().
std::string wrapped;
wrapped.reserve(raw.size() + 2);
wrapped += '(';
wrapped.append(raw.data(), raw.size());
wrapped += ')';
JS::CompileOptions opts(_cx);
opts.setFileAndLine("wasm:newFunction", 1);
JS::SourceText<mozilla::Utf8Unit> text;
if (!text.init(_cx, wrapped.data(), wrapped.size(), JS::SourceOwnership::Borrowed)) {
return; // JS exception is pending
}
JS::Evaluate(_cx, opts, text, out);
// If Evaluate fails, JS exception is pending — caller handles it.
}
WrapType<BSONInfo>& MozJSScriptEngine::bsonProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->bsonProto();
}
WrapType<DBRefInfo>& MozJSScriptEngine::dbRefProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->dbRefProto();
}
WrapType<RegExpInfo>& MozJSScriptEngine::regExpProto() {
if (!_prototypeInstaller) {
__builtin_trap(); // WASM trap: prototypeInstaller not initialized
}
return _prototypeInstaller->regExpProto();
}
} // namespace wasm
} // namespace mozjs
} // namespace mongo

View File

@ -0,0 +1,312 @@
/**
* 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.
*/
#pragma once
#include "mongo/base/status.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/scripting/mozjs/common/error.h"
#include "mongo/scripting/mozjs/common/global.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/types/bindata.h"
#include "mongo/scripting/mozjs/common/types/bson.h"
#include "mongo/scripting/mozjs/common/types/code.h"
#include "mongo/scripting/mozjs/common/types/dbpointer.h"
#include "mongo/scripting/mozjs/common/types/dbref.h"
#include "mongo/scripting/mozjs/common/types/maxkey.h"
#include "mongo/scripting/mozjs/common/types/minkey.h"
#include "mongo/scripting/mozjs/common/types/nativefunction.h"
#include "mongo/scripting/mozjs/common/types/numberdecimal.h"
#include "mongo/scripting/mozjs/common/types/numberint.h"
#include "mongo/scripting/mozjs/common/types/numberlong.h"
#include "mongo/scripting/mozjs/common/types/oid.h"
#include "mongo/scripting/mozjs/common/types/regexp.h"
#include "mongo/scripting/mozjs/common/types/status.h"
#include "mongo/scripting/mozjs/common/types/timestamp.h"
#include "mongo/scripting/mozjs/common/wraptype.h"
#include "mongo/scripting/mozjs/shared/mozjs_wasm_startup_options.h"
#include "mongo/util/assert_util.h"
#include <cstdint>
#include <memory>
#include <vector>
#include "error.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
struct JSContext;
struct JSRuntime;
namespace mongo {
namespace mozjs {
namespace wasm {
extern uint32_t g_wasmJsHeapLimitMB;
class ExecutionCheck;
struct FunctionSlot {
JS::PersistentRootedObject fn;
explicit FunctionSlot(JSContext* cx) : fn(cx) {}
};
class MozJSPrototypeInstaller {
public:
explicit MozJSPrototypeInstaller(JSContext* context)
: _cx(context),
_globalProto(_cx),
_binDataProto(_cx),
_bsonProto(_cx),
_codeProto(_cx),
_dbPointerProto(_cx),
_dbRefProto(_cx),
_errorProto(_cx),
_maxKeyProto(_cx),
_minKeyProto(_cx),
_nativeFunctionProto(_cx),
_numberDecimalProto(_cx),
_numberIntProto(_cx),
_numberLongProto(_cx),
_oidProto(_cx),
_regExpProto(_cx),
_statusProto(_cx),
_timestampProto(_cx) {
invariant(_cx);
}
WrapType<GlobalInfo>& globalProto() {
return _globalProto;
}
WrapType<BinDataInfo>& binDataProto() {
return _binDataProto;
}
WrapType<BSONInfo>& bsonProto() {
return _bsonProto;
}
WrapType<CodeInfo>& codeProto() {
return _codeProto;
}
WrapType<DBPointerInfo>& dbPointerProto() {
return _dbPointerProto;
}
WrapType<DBRefInfo>& dbRefProto() {
return _dbRefProto;
}
WrapType<ErrorInfo>& errorProto() {
return _errorProto;
}
WrapType<MaxKeyInfo>& maxKeyProto() {
return _maxKeyProto;
}
WrapType<MinKeyInfo>& minKeyProto() {
return _minKeyProto;
}
WrapType<NativeFunctionInfo>& nativeFunctionProto() {
return _nativeFunctionProto;
}
WrapType<NumberDecimalInfo>& numberDecimalProto() {
return _numberDecimalProto;
}
WrapType<NumberIntInfo>& numberIntProto() {
return _numberIntProto;
}
WrapType<NumberLongInfo>& numberLongProto() {
return _numberLongProto;
}
WrapType<OIDInfo>& oidProto() {
return _oidProto;
}
WrapType<RegExpInfo>& regExpProto() {
return _regExpProto;
}
WrapType<MongoStatusInfo>& statusProto() {
return _statusProto;
}
WrapType<TimestampInfo>& timestampProto() {
return _timestampProto;
}
void installTypes(JS::HandleObject global) {
// GlobalInfo cannot use install() because JSCLASS_GLOBAL_FLAGS
// prevents JS_InitClass on an existing global. Install its
// freeFunctions (print, gc, sleep, etc.) directly.
JS_DefineFunctions(_cx, global, GlobalInfo::freeFunctions);
_binDataProto.install(global);
_bsonProto.install(global);
_codeProto.install(global);
_dbPointerProto.install(global);
_dbRefProto.install(global);
_errorProto.install(global);
_maxKeyProto.install(global);
_minKeyProto.install(global);
_nativeFunctionProto.install(global);
_numberDecimalProto.install(global);
_numberIntProto.install(global);
_numberLongProto.install(global);
_oidProto.install(global);
_regExpProto.install(global);
_timestampProto.install(global);
_statusProto.install(global);
}
private:
JSContext* _cx = nullptr;
WrapType<GlobalInfo> _globalProto;
WrapType<BinDataInfo> _binDataProto;
WrapType<BSONInfo> _bsonProto;
WrapType<CodeInfo> _codeProto;
WrapType<DBPointerInfo> _dbPointerProto;
WrapType<DBRefInfo> _dbRefProto;
WrapType<ErrorInfo> _errorProto;
WrapType<MaxKeyInfo> _maxKeyProto;
WrapType<MinKeyInfo> _minKeyProto;
WrapType<NativeFunctionInfo> _nativeFunctionProto;
WrapType<NumberDecimalInfo> _numberDecimalProto;
WrapType<NumberIntInfo> _numberIntProto;
WrapType<NumberLongInfo> _numberLongProto;
WrapType<OIDInfo> _oidProto;
WrapType<RegExpInfo> _regExpProto;
WrapType<MongoStatusInfo> _statusProto;
WrapType<TimestampInfo> _timestampProto;
};
class MozJSScriptEngine : private MozJSCommonRuntimeInterface {
public:
MozJSScriptEngine() = default;
~MozJSScriptEngine();
MozJSScriptEngine(const MozJSScriptEngine&) = delete;
MozJSScriptEngine& operator=(const MozJSScriptEngine&) = delete;
err_code_t init(const wasm_mozjs_startup_options_t* opt, wasm_mozjs_error_t* err);
err_code_t shutdown(wasm_mozjs_error_t* err);
err_code_t interrupt(wasm_mozjs_error_t* err);
err_code_t createFunction(const uint8_t* src,
size_t len,
uint64_t* out_handle,
wasm_mozjs_error_t* err);
err_code_t invokeFunction(uint64_t handle,
mongo::BSONObj&& bsonArgs,
mongo::BSONObj* outBson,
wasm_mozjs_error_t* err);
/// Invoke a predicate: document becomes `this` e.g `this.someVar`, returns bool.
err_code_t invokePredicate(uint64_t handle,
mongo::BSONObj&& document,
bool* outResult,
wasm_mozjs_error_t* err);
/// Invoke a map function: document becomes `this`, emits buffered.
err_code_t invokeMap(uint64_t handle, mongo::BSONObj&& document, wasm_mozjs_error_t* err);
err_code_t getReturnValueBson(mongo::BSONObj* out, wasm_mozjs_error_t* err);
/// Set a named global variable from a BSON-encoded value.
err_code_t setGlobal(const char* name,
size_t name_len,
const mongo::BSONObj& value,
wasm_mozjs_error_t* err);
/// Get a named global variable as BSON-encoded bytes.
err_code_t getGlobal(const char* name,
size_t name_len,
mongo::BSONObj* out,
wasm_mozjs_error_t* err);
/// Set a named global to a single BSON element's JS value directly.
err_code_t setGlobalValue(const char* name,
size_t name_len,
const mongo::BSONObj& singleElementDoc,
wasm_mozjs_error_t* err);
/// Set up the emit() built-in for mapReduce. Resets the emit buffer.
err_code_t setupEmit(int64_t byteLimit, bool hasByteLimit, wasm_mozjs_error_t* err);
/// Drain the emit buffer: returns accumulated {k,v} pairs, then clears.
err_code_t drainEmitBuffer(mongo::BSONObj* out, wasm_mozjs_error_t* err);
// MozJSCommonRuntimeInterface implementation
void gc() override;
void sleep(Milliseconds ms) override;
std::size_t getGeneration() const override;
JS::HandleId getInternedStringId(InternedString name) override;
std::int64_t* trackedNewInt64(std::int64_t value) override;
WrapType<NumberLongInfo>& numberLongProto() override;
WrapType<NumberIntInfo>& numberIntProto() override;
WrapType<NumberDecimalInfo>& numberDecimalProto() override;
WrapType<OIDInfo>& oidProto() override;
WrapType<BinDataInfo>& binDataProto() override;
WrapType<TimestampInfo>& timestampProto() override;
WrapType<MaxKeyInfo>& maxKeyProto() override;
WrapType<MinKeyInfo>& minKeyProto() override;
WrapType<CodeInfo>& codeProto() override;
WrapType<DBPointerInfo>& dbPointerProto() override;
WrapType<NativeFunctionInfo>& nativeFunctionProto() override;
WrapType<ErrorInfo>& errorProto() override;
WrapType<MongoStatusInfo>& mongoStatusProto() override;
WrapType<BSONInfo>& bsonProto() override;
WrapType<DBRefInfo>& dbRefProto() override;
WrapType<RegExpInfo>& regExpProto() override;
void setStatus(Status status) override;
bool isJavaScriptProtectionEnabled() const override;
void newFunction(StringData code, JS::MutableHandleValue out) override;
bool requiresOwnedObjects() const override;
void trackNewPointer(void* ptr) override;
void trackDeletePointer(void* ptr) override;
private:
FunctionSlot* resolveHandle(uint64_t handle, wasm_mozjs_error_t* err);
static bool interruptCallback(JSContext* cx);
bool _initialized = false;
JSContext* _cx = nullptr;
JSRuntime* _rt = nullptr;
JS::PersistentRootedObject _global;
std::vector<FunctionSlot> _slots;
std::unique_ptr<MozJSPrototypeInstaller> _prototypeInstaller;
std::unique_ptr<InternedStringTable> _internedStrings;
Status _status = Status::OK();
std::vector<mongo::BSONObj> _emitBuffer;
int64_t _emitBytesUsed = 0;
int64_t _emitByteLimit = 16 * 1024 * 1024; // default 16 MB
static mongo::BSONObj _emitCallback(const mongo::BSONObj& args, void* data);
};
} // namespace wasm
} // namespace mozjs
} // namespace mongo

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
/**
* 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 "error.h"
#include "js/ErrorReport.h"
#include "js/Exception.h"
namespace mongo {
namespace mozjs {
namespace wasm {
void ExecutionCheck::capture(err_code_t fallback) {
if (!_out)
return;
clear_error(_out);
_out->code = fallback;
if (!JS_IsExceptionPending(_cx))
return;
_out->code = SM_E_PENDING_EXCEPTION;
JS::ExceptionStack exnStack(_cx);
if (!JS::StealPendingExceptionStack(_cx, &exnStack)) {
JS_ClearPendingException(_cx);
return;
}
JS::ErrorReportBuilder report(_cx);
if (!report.init(_cx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) {
JS_ClearPendingException(_cx);
return;
}
JSErrorReport* r = report.report();
if (!r)
return;
if (r->message())
set_string(&_out->msg, &_out->msg_len, r->message().c_str());
if (r->filename)
set_string(&_out->filename, &_out->filename_len, r->filename.c_str());
_out->line = r->lineno;
_out->column = r->column.oneOriginValue();
}
} // namespace wasm
JSString* mongoErrorReportToString(JSContext* cx, JSErrorReport* reportp) {
if (!reportp) {
return JS_NewStringCopyZ(cx, "Unknown error");
}
std::string msg;
constexpr size_t kErrorMsgReserveSize = 256;
msg.reserve(kErrorMsgReserveSize);
if (reportp->message()) {
msg.append(reportp->message().c_str());
} else {
msg.append("JavaScript error");
}
if (reportp->filename) {
msg.append(" at ");
msg.append(reportp->filename.c_str());
if (reportp->lineno > 0) {
msg.push_back(':');
msg.append(std::to_string(reportp->lineno));
}
}
return JS_NewStringCopyZ(cx, msg.c_str());
}
} // namespace mozjs
} // namespace mongo

View File

@ -0,0 +1,107 @@
/**
* 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.
*/
#pragma once
#include "mongo/scripting/mozjs/shared/mozjs_error_types.h"
#include <cstdio>
#include <cstring>
#include "jsapi.h"
#include "utils.h"
namespace mongo {
namespace mozjs {
namespace wasm {
inline void clear_error(wasm_mozjs_error_t* e) {
if (!e)
return;
if (e->msg) {
cabi_realloc(e->msg, e->msg_len + 1, 1, 0);
e->msg = nullptr;
e->msg_len = 0;
}
if (e->filename) {
cabi_realloc(e->filename, e->filename_len + 1, 1, 0);
e->filename = nullptr;
e->filename_len = 0;
}
if (e->stack) {
cabi_realloc(e->stack, e->stack_len + 1, 1, 0);
e->stack = nullptr;
e->stack_len = 0;
}
e->code = SM_OK;
e->line = 0;
e->column = 0;
}
// Execution check helper for error handling
class ExecutionCheck {
public:
ExecutionCheck(JSContext* cx, wasm_mozjs_error_t* out_err) : _cx(cx), _out(out_err) {
clear_error(out_err);
}
bool ok(bool success, err_code_t onFail = SM_E_JSAPI_FAIL) {
if (success)
return true;
capture(onFail);
return false;
}
template <class T>
T* okPtr(T* p, err_code_t onFail = SM_E_JSAPI_FAIL) {
if (p)
return p;
capture(onFail);
return nullptr;
}
private:
void capture(err_code_t fallback);
JSContext* _cx;
wasm_mozjs_error_t* _out;
};
} // namespace wasm
// WASI-compatible error report to string (declaration).
// Implementation in error.cpp.
JSString* mongoErrorReportToString(JSContext* cx, JSErrorReport* reportp);
} // namespace mozjs
} // namespace mongo

View File

@ -0,0 +1,146 @@
/**
* 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

@ -0,0 +1,111 @@
"""Expose C++ link information (objects/libs/flags) via response files for genrules."""
def _as_list(x):
if x == None:
return []
t = type(x)
if t == "depset":
return x.to_list()
if t == "list" or t == "tuple":
return x
# File or string or other singletons
return [x]
def _collect_files_from_library(lib):
"""Return (objects, libs) from a LibraryToLink in a Bazel-version-tolerant way."""
objs = []
# Object files (may be depsets)
if hasattr(lib, "objects"):
objs += _as_list(lib.objects)
if hasattr(lib, "pic_objects"):
objs += _as_list(lib.pic_objects)
libs = []
# Prefer static archives; include others if present
for attr in (
"static_library",
"pic_static_library",
"dynamic_library",
"interface_library",
):
if hasattr(lib, attr):
val = getattr(lib, attr)
libs += _as_list(val)
return objs, libs
def _cc_linkset_impl(ctx):
linking_ctxs = [d[CcInfo].linking_context for d in ctx.attr.deps]
merged = cc_common.merge_linking_contexts(linking_contexts = linking_ctxs)
objs_ordered = []
libs_ordered = []
flags_ordered = []
# Iterate in Bazel's link order
linker_inputs_list = merged.linker_inputs.to_list()
# Collect from all deps - include both direct and transitive
for li in linker_inputs_list:
if hasattr(li, "user_link_flags"):
flags_ordered += list(li.user_link_flags)
for lib in li.libraries:
o, libs = _collect_files_from_library(lib)
# Collect all objects and libraries
objs_ordered += o
libs_ordered += libs
noncode = []
if hasattr(merged, "non_code_inputs"):
noncode = merged.non_code_inputs.to_list()
# Emit response files
objs_rsp = ctx.actions.declare_file(ctx.label.name + ".objects.rsp")
libs_rsp = ctx.actions.declare_file(ctx.label.name + ".libs.rsp")
flags_rsp = ctx.actions.declare_file(ctx.label.name + ".flags.rsp")
def _write_list(out, items):
lines = []
for it in items:
if hasattr(it, "path"):
lines.append(it.path)
else:
lines.append(str(it))
content = "\n".join(lines)
if content:
content += "\n"
ctx.actions.write(out, content)
_write_list(objs_rsp, objs_ordered)
_write_list(libs_rsp, libs_ordered + noncode)
_write_list(flags_rsp, flags_ordered + ctx.attr.extra_flags)
# Expose everything so genrules can use $(locations :target)
all_files = depset(
direct = [objs_rsp, libs_rsp, flags_rsp] + objs_ordered + libs_ordered + noncode,
)
return [
DefaultInfo(files = all_files),
OutputGroupInfo(
rsp = depset([objs_rsp, libs_rsp, flags_rsp]),
objects = depset(objs_ordered),
libs = depset(libs_ordered),
),
]
cc_linkset = rule(
implementation = _cc_linkset_impl,
attrs = {
"deps": attr.label_list(providers = [CcInfo]),
"extra_flags": attr.string_list(default = []),
"_cc_toolchain": attr.label(
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
),
},
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
fragments = ["cpp"],
)

View File

@ -0,0 +1,89 @@
/**
* 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.
*/
#pragma once
#include <cstdio>
#include <cstring>
extern "C" void* cabi_realloc(void* ptr, size_t old_size, size_t align, size_t new_size);
namespace mongo {
namespace mozjs {
namespace wasm {
/**
* Set a string field, allocating memory via cabi_realloc.
* If the field already has a value, it will be freed first.
*/
inline void set_string(char** dst, size_t* dst_len, const char* src) {
if (!dst || !dst_len)
return;
// Free existing string
if (*dst) {
cabi_realloc(*dst, *dst_len + 1, 1, 0);
*dst = nullptr;
*dst_len = 0;
}
if (!src || src[0] == '\0') {
return;
}
size_t src_len = std::strlen(src);
char* allocated = static_cast<char*>(cabi_realloc(nullptr, 0, 1, src_len + 1));
if (!allocated) {
return;
}
std::memcpy(allocated, src, src_len);
allocated[src_len] = '\0';
*dst = allocated;
*dst_len = src_len;
}
/**
* Copy a C string into a fixed-size output buffer (always null-terminates).
*/
inline void set_cstr(char* dst, size_t cap, const char* src) {
if (!dst || cap == 0)
return;
if (!src) {
dst[0] = '\0';
return;
}
std::snprintf(dst, cap, "%s", src);
}
} // namespace wasm
} // namespace mozjs
} // namespace mongo

View File

@ -0,0 +1,44 @@
/**
* 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 "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "gc/GCContext.h"
#include "vm/JSContext.h"
#include "vm/Runtime.h"
namespace mongo {
namespace mozjs {
JSContext* freeOpToJSContext(JS::GCContext* gcCtx) {
return gcCtx->runtime()->mainContextFromOwnThread();
}
} // namespace mozjs
} // namespace mongo

View File

@ -0,0 +1,126 @@
# WASM Build Scripts
These scripts implement the WASM build steps that Bazel runs via genrules. You can run them **from Bazel** (as usual) or **standalone** for debugging or CI. All behaviour is controlled by environment variables; each script's header lists every variable and a short example.
---
## Run order
To build **mozjs_wasm_api.wasm** (the main WASM API module) outside Bazel, run scripts in this order:
| Step | Script | Produces |
| ---- | ------------------------------ | ------------------------------------- |
| 1 | `build_spidermonkey_wasip2.sh` | SpiderMonkey tarball (libs + headers) |
| 2 | `extract_rust_shims.sh` | `rust_shims.a` (from that tarball) |
| 3 | `compile_mozjs_wasm_api.sh` | `mozjs_wasm_api.wasm` |
Step 3 needs the tarball, `rust_shims.a`, the MongoDB base **linkset** response files (`.libs.rsp` etc.), and all MozJS/WIT sources. The linkset files normally come from building `//src/mongo/scripting/mozjs/wasm:mongo_base_linkset` in Bazel; for a fully standalone build you'd need to copy those RSP files out of the Bazel output tree.
**Optional:** `link_mongo_base_wasm.sh` builds **mongo_base.wasm** (MongoDB base as a single WASM). It only needs the same linkset RSP files and the WASI SDK; it does not depend on SpiderMonkey. Run it whenever you have the linkset and want `mongo_base.wasm`.
**Helper (not run directly):** `compile_wasi_source.sh` — compiles one C/C++ file at a time; called by `compile_mozjs_wasm_api.sh` via `xargs`.
---
## Invocation examples
Assume you are in the **MongoDB repo root** and have a WASI SDK at `$WASI_SDK` (e.g. `/opt/wasi-sdk` or `$HOME/wasi-sdk-24.0`). Paths below are relative to the repo root unless noted.
### 1. Build SpiderMonkey (WASI Preview 2)
Produces a tarball with `libjs_static.a`, headers, and Rust shims. Requires SpiderMonkey source (e.g. from Bazel's external repo or a local gecko checkout) and the Rust shim sources under `support/rust_shims/`.
```bash
cd src/mongo/scripting/mozjs/wasm
export WASI_SDK_PATH=/opt/wasi-sdk
export SPIDER_MACH_PATH=/path/to/gecko-dev/mach # or path from Bazel execroot to mach
export RUST_SHIMS_LIB_RS=support/rust_shims/src/lib.rs
export CARGO_TEMPLATE_PATH=support/rust_shims/Cargo.toml.template
export OUTPUT=out/spidermonkey-wasip2-release.tar.gz
mkdir -p out
bash scripts/build_spidermonkey_wasip2.sh
```
### 2. Extract Rust shims from the tarball
```bash
cd src/mongo/scripting/mozjs/wasm
export TARBALL=out/spidermonkey-wasip2-release.tar.gz
export OUTPUT=out/rust_shims.a
bash scripts/extract_rust_shims.sh
```
### 3. Build mongo_base.wasm (optional)
You need the three linkset response files (e.g. from a Bazel build of `:mongo_base_linkset`). If they live in `bazel-bin/.../mongo_base_linkset/` as `*.objects.rsp`, `*.libs.rsp`, `*.flags.rsp`:
```bash
cd src/mongo/scripting/mozjs/wasm
export CXX=/opt/wasi-sdk/bin/wasm32-wasi-clang++
export OBJS_RSP=/path/to/xxx.objects.rsp
export LIBS_RSP=/path/to/xxx.libs.rsp
export FLAGS_RSP=/path/to/xxx.flags.rsp
export OUTPUT=out/mongo_base.wasm
bash scripts/link_mongo_base_wasm.sh
```
Or pass the directory and let the script find the RSPs:
```bash
export LINKSET_FILES="/path/to/foo.objects.rsp /path/to/foo.libs.rsp /path/to/foo.flags.rsp"
export OUTPUT=out/mongo_base.wasm
bash scripts/link_mongo_base_wasm.sh
```
### 4. Build mozjs_wasm_api.wasm
Requires the SpiderMonkey tarball, `rust_shims.a`, the linkset files, all MozJS/WIT sources, and the WIT component-type object. Easiest is to run from the package dir and point at Bazel's outputs for linkset and WIT object; fill in the source lists from the genrule in `BUILD.bazel` if needed.
```bash
cd src/mongo/scripting/mozjs/wasm
export WASI_SDK_PATH=/opt/wasi-sdk
export CXX=$WASI_SDK_PATH/bin/wasm32-wasip2-clang++
export CC=$WASI_SDK_PATH/bin/wasm32-wasip2-clang
export SM_TARBALL=out/spidermonkey-wasip2-release.tar.gz
export RUST_SHIMS_PATH=out/rust_shims.a
export WIT_COMPONENT_TYPE_OBJ=wit_gen/generated/api_component_type.o # or path in bazel-bin
# Paths to linkset RSP files (e.g. from bazel build of :mongo_base_linkset)
export LINKSET_FILES="/path/to/xxx.objects.rsp /path/to/xxx.libs.rsp /path/to/xxx.flags.rsp"
# Header parents for error_codes and mongo config (e.g. bazel-bin dirs)
export ERROR_CODES_HEADER_FILES=/path/to/error_codes
export CONFIG_HEADER_FILES=/path/to/mongo_config
# Source file lists (space-separated); see BUILD.bazel genrule "mozjs_wasm_api_genrule" for full list
export WASM_SOURCES="engine/engine.cpp engine/error.cpp helpers.cpp ..."
export COMMON_WASI_SOURCES="/path/to/common/wasi_sources..."
export EXCEPTION_STUBS_SRC=engine/exception_stubs.cpp
export MOZJS_API_SRC=engine/api.cpp
export WIT_API_C=wit_gen/generated/api.c
export OUTPUT=out/mozjs_wasm_api.wasm
bash scripts/compile_mozjs_wasm_api.sh
```
For the exact source lists and paths, run the corresponding genrule once and inspect the `cmd` in `BUILD.bazel`, or run the build via Bazel and use the script only for repros.
---
## What each script does
| Script | Purpose |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **build_spidermonkey_wasip2.sh** | Builds SpiderMonkey for WASI Preview 2 (mozconfig + `mach build`), builds the Rust encoding shims, and packs static libs, headers, and extra objects into a tarball. |
| **extract_rust_shims.sh** | Unpacks `libmongo_wasip2_rust_shims.a` from the SpiderMonkey tarball into a single `.a` file. |
| **compile_mozjs_wasm_api.sh** | Unpacks the SM tarball, compiles all MozJS wrapper and WIT glue sources in parallel, gathers MongoDB base libs from the linkset, and links `mozjs_wasm_api.wasm`. |
| **link_mongo_base_wasm.sh** | Links the MongoDB base library into a single `mongo_base.wasm` using the WASI linkset RSP files and `wasm32-wasi-clang++`. |
| **compile_wasi_source.sh** | Compiles one C or C++ file for WASI. Used by `compile_mozjs_wasm_api.sh` via `xargs`; not intended to be run directly. |

View File

@ -0,0 +1,286 @@
#!/bin/bash
# Builds SpiderMonkey for WASI Preview 2, producing a tarball with:
# - libjs_static.a (the JS engine)
# - headers (public + internal, .h/.hpp only)
# - libjsrust.a (if available)
# - libmongo_wasip2_rust_shims.a (encoding shims)
# - obj-extra/ (support objects outside libjs_static.a)
#
# Required environment variables:
# OUTPUT - Output tarball path (.tar.gz)
# SPIDER_MACH_PATH - Path to SpiderMonkey's mach file
# RUST_SHIMS_LIB_RS - Path to support/rust_shims/src/lib.rs
# CARGO_TEMPLATE_PATH - Path to support/rust_shims/Cargo.toml.template
#
# Compiler discovery (set one of):
# WASI_SDK_BIN_FILES - Space-separated wasi_sdk bin files (auto-discovers clang)
# WASI_SDK_PATH - Direct path to WASI SDK root
#
# Example standalone usage:
# SPIDER_MACH_PATH=/path/to/gecko-dev/mach \
# RUST_SHIMS_LIB_RS=support/rust_shims/src/lib.rs \
# CARGO_TEMPLATE_PATH=support/rust_shims/Cargo.toml.template \
# WASI_SDK_PATH=/opt/wasi-sdk \
# OUTPUT=spidermonkey-wasip2-release.tar.gz \
# bash scripts/build_spidermonkey_wasip2.sh
set -euo pipefail
# Enable verbose tracing only when VERBOSE is set (reduces CI log noise).
[[ -n "${VERBOSE:-}" ]] && set -x
EXECROOT="${EXECROOT:-$(pwd -P)}"
# Resolve output to absolute path.
case "$OUTPUT" in
/*) OUT_TAR_ABS="$OUTPUT" ;;
*) OUT_TAR_ABS="$EXECROOT/$OUTPUT" ;;
esac
OUT_DIR_ABS="$(dirname "$OUT_TAR_ABS")"
PKG_DIR="$OUT_DIR_ABS/pkg"
WORK_DIR="$OUT_DIR_ABS/work"
rm -rf "$PKG_DIR"
mkdir -p "$PKG_DIR/lib" "$WORK_DIR"
# Keep a real HOME so rustup doesn't trip over Bazel's sandbox home.
REAL_HOME="$(getent passwd "$(id -u)" | cut -d: -f6 || true)"
if [ -z "$REAL_HOME" ]; then
if [ -n "${HOME:-}" ]; then
REAL_HOME="$HOME"
else
echo "ERROR: Could not determine HOME directory; set HOME or ensure getent is available." >&2
exit 1
fi
fi
export HOME="$REAL_HOME"
export MOZ_FETCHES_DIR="$HOME/.mozbuild"
# --- Locate WASI SDK ---
if [ -z "${WASI_SDK_PATH:-}" ]; then
WASI_CLANG=""
for f in ${WASI_SDK_BIN_FILES:-}; do
case "$f" in */clang)
WASI_CLANG="$f"
break
;;
esac
done
if [ -z "$WASI_CLANG" ]; then
echo "ERROR: clang not found. Set WASI_SDK_PATH or WASI_SDK_BIN_FILES." >&2
exit 1
fi
WASI_SDK_PATH="$(cd "$(dirname "$WASI_CLANG")/.." && pwd)"
fi
# --- Copy SpiderMonkey source into a writable directory ---
SPIDER_ROOT="$(cd "$(dirname "$SPIDER_MACH_PATH")" && pwd)"
SRC_ROOT="$WORK_DIR/spidermonkey-src"
rm -rf "$SRC_ROOT"
mkdir -p "$SRC_ROOT"
cp -a "$SPIDER_ROOT/." "$SRC_ROOT/"
SRC_ROOT_REAL="$(cd "$SRC_ROOT" && pwd -P)"
MOZCONFIG="$SRC_ROOT_REAL/mozconfig-release"
OBJDIR="$WORK_DIR/obj-release-wasip2"
mkdir -p "$OBJDIR"
OBJDIR_REAL="$(cd "$OBJDIR" && pwd -P)"
cat >"$MOZCONFIG" <<'EOF'
ac_add_options --enable-project=js
ac_add_options --disable-js-shell
ac_add_options --target=wasm32-unknown-wasip2
ac_add_options --without-system-zlib
ac_add_options --without-intl-api
ac_add_options --disable-jit
ac_add_options --disable-shared-js
ac_add_options --disable-shared-memory
ac_add_options --disable-tests
ac_add_options --disable-clang-plugin
ac_add_options --enable-optimize=-Oz
ac_add_options --enable-portable-baseline-interp
mk_add_options AUTOCLOBBER=1
EOF
echo "mk_add_options MOZ_OBJDIR=$OBJDIR_REAL" >>"$MOZCONFIG"
echo "ac_add_options --prefix=$OBJDIR_REAL/dist" >>"$MOZCONFIG"
echo "ac_add_options --disable-stdcxx-compat" >>"$MOZCONFIG"
echo "ac_add_options --disable-debug" >>"$MOZCONFIG"
echo "ac_add_options --disable-debug-symbols" >>"$MOZCONFIG"
echo "ac_add_options --with-sysroot=$WASI_SDK_PATH/share/wasi-sysroot" >>"$MOZCONFIG"
# Use WASI Preview2 compiler wrappers.
export WASI_SDK_PATH="$WASI_SDK_PATH"
export CC="$WASI_SDK_PATH/bin/wasm32-wasip2-clang"
export CXX="$WASI_SDK_PATH/bin/wasm32-wasip2-clang++"
export AR="$WASI_SDK_PATH/bin/llvm-ar"
export RANLIB="$WASI_SDK_PATH/bin/llvm-ranlib"
export HOST_CC="$(command -v clang)"
export HOST_CXX="$(command -v clang++)"
# Enable per-function/data sections so the linker's --gc-sections can remove
# unused code. Also hide internal symbols to aid dead-code elimination.
WASM_SIZE_FLAGS="-ffunction-sections -fdata-sections -fvisibility=hidden"
export CFLAGS="${CFLAGS:-} $WASM_SIZE_FLAGS"
export CXXFLAGS="${CXXFLAGS:-} $WASM_SIZE_FLAGS"
# Let cargo/rustc know what target to build for when SpiderMonkey builds Rust support code.
export RUST_TARGET=wasm32-wasip2
# Configure expects a Rust stdlib for wasm32-wasip2 to be installed.
# This is a local (non-hermetic) build step by design (tags = local/no-sandbox).
if [ -x "$HOME/.cargo/bin/rustup" ]; then
"$HOME/.cargo/bin/rustup" target add wasm32-wasip2 || true
elif command -v rustup >/dev/null 2>&1; then
rustup target add wasm32-wasip2 || true
fi
export MOZCONFIG="$MOZCONFIG"
cd "$SRC_ROOT_REAL"
python3 "./mach" -v --no-interactive build
# Build Rust support library if present/required (best-effort).
python3 "./mach" -v --no-interactive build js/src/rust || true
LIBJS_A="$OBJDIR_REAL/js/src/build/libjs_static.a"
test -s "$LIBJS_A"
cp "$LIBJS_A" "$PKG_DIR/lib/libjs_static.a"
# Headers for embedders (needed to compile our wrapper).
if [ -d "$OBJDIR_REAL/dist/include" ]; then
cp -Lr "$OBJDIR_REAL/dist/include" "$PKG_DIR/include"
# Generated defines header is used by some includes.
if [ -f "$OBJDIR_REAL/js/src/js-confdefs.h" ]; then
cp "$OBJDIR_REAL/js/src/js-confdefs.h" "$PKG_DIR/include/js-confdefs.h"
fi
fi
# Internal SpiderMonkey headers (js/src) for gc/GCContext.h, vm/Runtime.h etc.
# Copy source headers first, then overlay generated headers from objdir.
if [ -d "$SRC_ROOT_REAL/js/src" ]; then
echo "Copying js/src internal headers (*.h, *.hpp only) from source..." >&2
mkdir -p "$PKG_DIR/include/src"
# Copy only header files -- not .cpp, tests, or build configs which bloat
# the tarball by hundreds of megabytes.
(cd "$SRC_ROOT_REAL/js/src" && find . \( -name '*.h' -o -name '*.hpp' \) -type f) | while IFS= read -r hdr; do
mkdir -p "$PKG_DIR/include/src/$(dirname "$hdr")"
cp "$SRC_ROOT_REAL/js/src/$hdr" "$PKG_DIR/include/src/$hdr"
done
fi
if [ -d "$SRC_ROOT_REAL/mfbt" ]; then
echo "Copying mfbt headers (*.h, *.hpp only) from source..." >&2
mkdir -p "$PKG_DIR/include/mfbt"
(cd "$SRC_ROOT_REAL/mfbt" && find . \( -name '*.h' -o -name '*.hpp' \) -type f) | while IFS= read -r hdr; do
mkdir -p "$PKG_DIR/include/mfbt/$(dirname "$hdr")"
cp "$SRC_ROOT_REAL/mfbt/$hdr" "$PKG_DIR/include/mfbt/$hdr"
done
fi
# Overlay generated .h files from objdir (e.g., wasm/WasmBuiltinModuleGenerated.h)
if [ -d "$OBJDIR_REAL/js/src" ]; then
echo "Overlaying generated headers from objdir..." >&2
find "$OBJDIR_REAL/js/src" -name "*.h" -type f 2>/dev/null | while read f; do
rel="${f#$OBJDIR_REAL/js/src/}"
mkdir -p "$PKG_DIR/include/src/$(dirname "$rel")"
cp "$f" "$PKG_DIR/include/src/$rel" 2>/dev/null || true
done
fi
# If a Rust static lib exists, include it (may satisfy encoding_* symbols).
# Name can vary by toolchain/version, so accept libjsrust*.a.
RUST_LIB="$(find "$OBJDIR_REAL" -type f -name 'libjsrust*.a' | head -n1 || true)"
if [ -n "$RUST_LIB" ] && [ -f "$RUST_LIB" ]; then
cp "$RUST_LIB" "$PKG_DIR/lib/libjsrust.a"
fi
# Build a single Rust staticlib that bundles the encoding shims + Rust
# runtime, so the final wasm can be fully linked without `env::...`
# imports.
CARGO="$(command -v cargo || true)"
if [ -z "$CARGO" ] && [ -x "$HOME/.cargo/bin/cargo" ]; then
CARGO="$HOME/.cargo/bin/cargo"
fi
if [ -z "$CARGO" ]; then
echo "ERROR: cargo not found; cannot build Rust encoding staticlib" >&2
exit 1
fi
RUST_SHIMS_DIR="$WORK_DIR/rust-shims"
rm -rf "$RUST_SHIMS_DIR"
mkdir -p "$RUST_SHIMS_DIR/src"
# Copy lib.rs from support files.
# Path may be relative to EXECROOT or absolute.
case "$RUST_SHIMS_LIB_RS" in
/*) cp "$RUST_SHIMS_LIB_RS" "$RUST_SHIMS_DIR/src/lib.rs" ;;
*) cp "$EXECROOT/$RUST_SHIMS_LIB_RS" "$RUST_SHIMS_DIR/src/lib.rs" ;;
esac
# Generate Cargo.toml from template with SpiderMonkey root path.
case "$CARGO_TEMPLATE_PATH" in
/*) sed "s|{{SPIDERMONKEY_ROOT}}|$SRC_ROOT_REAL|g" "$CARGO_TEMPLATE_PATH" >"$RUST_SHIMS_DIR/Cargo.toml" ;;
*) sed "s|{{SPIDERMONKEY_ROOT}}|$SRC_ROOT_REAL|g" "$EXECROOT/$CARGO_TEMPLATE_PATH" >"$RUST_SHIMS_DIR/Cargo.toml" ;;
esac
export CARGO_TARGET_DIR="$OBJDIR_REAL/cargo-target"
(cd "$RUST_SHIMS_DIR" && "$CARGO" build --release --target wasm32-wasip2)
SHIMS_A="$CARGO_TARGET_DIR/wasm32-wasip2/release/libmongo_wasip2_rust_shims.a"
if [ ! -f "$SHIMS_A" ]; then
echo "ERROR: rust shims archive not found: $SHIMS_A" >&2
exit 1
fi
cp "$SHIMS_A" "$PKG_DIR/lib/libmongo_wasip2_rust_shims.a"
# Include additional build outputs (outside libjs_static.a) needed to link.
# The downstream linker uses libjs_static.a directly for SpiderMonkey objects,
# so we do NOT extract individual .o files from the archive (which would
# roughly double the tarball size). Only the "extra" objects that live outside
# the archive are needed here.
#
# Paths are relative to MOZ_OBJDIR (OBJDIR_REAL).
EXTRA_ROOT="$PKG_DIR/obj-extra"
for rel in \
memory/build/Unified_cpp_memory_build0.o \
memory/mozalloc/Unified_cpp_memory_mozalloc0.o \
mozglue/misc/AutoProfilerLabel.o \
mozglue/misc/ConditionVariable_noop.o \
mozglue/misc/MmapFaultHandler.o \
mozglue/misc/Mutex_noop.o \
mozglue/misc/Now.o \
mozglue/misc/Printf.o \
mozglue/misc/StackWalk.o \
mozglue/misc/TimeStamp.o \
mozglue/misc/TimeStamp_posix.o \
mozglue/misc/Uptime.o \
mozglue/misc/Decimal.o \
mozglue/misc/SIMD.o \
mfbt/lz4.o \
mfbt/lz4frame.o \
mfbt/lz4hc.o \
mfbt/xxhash.o \
mfbt/Unified_cpp_mfbt0.o \
mfbt/Unified_cpp_mfbt1.o; do
if [ -f "$OBJDIR_REAL/$rel" ]; then
mkdir -p "$EXTRA_ROOT/$(dirname "$rel")"
cp "$OBJDIR_REAL/$rel" "$EXTRA_ROOT/$rel"
else
echo "WARN: extra object missing (skipping): $OBJDIR_REAL/$rel" >&2
fi
done
# Strip debug info from archives and objects to further reduce tarball size.
# Even with --disable-debug-symbols the toolchain may leave some sections.
STRIP="${WASI_SDK_PATH}/bin/llvm-strip"
if [ -x "$STRIP" ]; then
echo "Stripping debug info from libraries and objects..." >&2
for lib in "$PKG_DIR"/lib/*.a; do
[ -f "$lib" ] && "$STRIP" --strip-debug "$lib" 2>/dev/null || true
done
find "$PKG_DIR/obj-extra" -name '*.o' -type f 2>/dev/null | while IFS= read -r obj; do
"$STRIP" --strip-debug "$obj" 2>/dev/null || true
done
else
echo "WARN: llvm-strip not found at $STRIP; skipping strip step" >&2
fi
mkdir -p "$(dirname "$OUT_TAR_ABS")"
tar -C "$PKG_DIR" -czf "$OUT_TAR_ABS" .
test -s "$OUT_TAR_ABS"

View File

@ -0,0 +1,268 @@
#!/bin/bash
# Compiles and links the MozJS WASM API module (mozjs_wasm_api.wasm).
#
# This script unpacks a SpiderMonkey tarball, compiles MozJS wrapper sources
# against it, collects MongoDB base libraries, and links everything into a
# single WASI Preview 2 WASM component.
#
# Required environment variables:
# OUTPUT - Output .wasm file path
# SM_TARBALL - Path to spidermonkey-wasip2-release.tar.gz
# RUST_SHIMS_PATH - Path to extracted rust_shims.a
# WIT_COMPONENT_TYPE_OBJ - Path to WIT component type object file
#
# Source files (space-separated paths):
# WASM_SOURCES - MozJS WASM wrapper sources
# COMMON_WASI_SOURCES - Common WASI source files
# EXCEPTION_STUBS_SRC - engine/exception_stubs.cpp
# MOZJS_API_SRC - engine/api.cpp
# WIT_API_C - WIT-generated api.c glue
#
# Header discovery:
# ERROR_CODES_HEADER_FILES - Space-separated error_codes header file paths
# CONFIG_HEADER_FILES - Space-separated mongo_config header file paths
#
# Linker inputs:
# LINKSET_FILES - Space-separated linkset output files
#
# Compiler discovery (set one of):
# CXX / CC - Direct paths to wasm32-wasip2-clang++ / clang
# WASI_SDK_BIN_FILES - Space-separated wasi_sdk bin files (auto-discovers)
#
# Compile helper (optional, auto-discovered from script directory):
# COMPILE_HELPER - Path to compile_wasi_source.sh
#
# Example standalone usage:
# CXX=/opt/wasi-sdk/bin/wasm32-wasip2-clang++ \
# CC=/opt/wasi-sdk/bin/wasm32-wasip2-clang \
# SM_TARBALL=spidermonkey-wasip2-release.tar.gz \
# RUST_SHIMS_PATH=rust_shims.a \
# WASM_SOURCES="engine.cpp error.cpp helpers.cpp" \
# WIT_COMPONENT_TYPE_OBJ=api_component_type.o \
# OUTPUT=mozjs_wasm_api.wasm \
# bash scripts/compile_mozjs_wasm_api.sh
set -euo pipefail
EXECROOT="${EXECROOT:-$(pwd -P)}"
START_TIME=$(date +%s)
# ============================================================
# STEP 1: Find compilers and setup
# ============================================================
echo "=== STEP 1: Finding WASI compilers ===" >&2
if [ -z "${CXX:-}" ] || [ -z "${CC:-}" ]; then
for f in ${WASI_SDK_BIN_FILES:-}; do
case "$f" in
*/wasm32-wasip2-clang++) CXX="$f" ;;
*/wasm32-wasip2-clang) CC="$f" ;;
esac
done
fi
if [ -z "${CXX:-}" ]; then
echo "ERROR: wasm32-wasip2-clang++ not found. Set CXX or WASI_SDK_BIN_FILES." >&2
exit 1
fi
if [ -z "${CC:-}" ]; then
echo "ERROR: wasm32-wasip2-clang not found. Set CC or WASI_SDK_BIN_FILES." >&2
exit 1
fi
WASI_SDK_ROOT="$(cd "$(dirname "$CXX")/.." && pwd -P)"
SYSROOT="${SYSROOT:-$WASI_SDK_ROOT/share/wasi-sysroot}"
echo "Using CXX: $CXX" >&2
echo "Using CC: $CC" >&2
# ============================================================
# STEP 2: Unpack SpiderMonkey
# ============================================================
echo "=== STEP 2: Unpacking SpiderMonkey ===" >&2
OUT_DIR="$(dirname "$OUTPUT")"
STAGE="$OUT_DIR/sm_stage"
rm -rf "$STAGE"
mkdir -p "$STAGE"
tar -xzf "$SM_TARBALL" -C "$STAGE"
test -f "$STAGE/lib/libjs_static.a" || (
echo "ERROR: libjs_static.a not found" >&2
exit 1
)
test -d "$STAGE/include" || (
echo "ERROR: include directory not found" >&2
exit 1
)
cp "$RUST_SHIMS_PATH" "$STAGE/lib/libmongo_wasip2_rust_shims.a"
echo "SpiderMonkey unpacked to: $STAGE" >&2
# ============================================================
# STEP 3: Setup compilation environment
# ============================================================
echo "=== STEP 3: Setting up compilation environment ===" >&2
OBJ_DIR="$STAGE/objs"
mkdir -p "$OBJ_DIR"
ERROR_CODES_H_PARENT=""
CONFIG_H_PARENT=""
for f in ${ERROR_CODES_HEADER_FILES:-}; do
ERROR_CODES_H_PARENT="$(dirname "$(dirname "$f")")"
break
done
for f in ${CONFIG_HEADER_FILES:-}; do
CONFIG_H_PARENT="$(dirname "$(dirname "$f")")"
break
done
BOOST_INCLUDES="-I$EXECROOT/src/third_party/boost"
[ -d "$EXECROOT/src/third_party/boost/libs/numeric/conversion/include" ] &&
BOOST_INCLUDES="$BOOST_INCLUDES -I$EXECROOT/src/third_party/boost/libs/numeric/conversion/include"
for lib_include in "$EXECROOT/src/third_party/boost/libs"/*/include; do
[ -d "$lib_include" ] && BOOST_INCLUDES="$BOOST_INCLUDES -I$lib_include"
done
# Auto-detect Bazel bin/src path for generated headers (e.g., error_codes.h).
# This must work on any host architecture (aarch64, x86_64, etc.).
BAZEL_BIN_SRC=""
for d in "$EXECROOT"/bazel-out/*/bin/src; do
if [ -d "$d" ]; then
BAZEL_BIN_SRC="$d"
break
fi
done
# ============================================================
# STEP 4: Compile MozJS library sources (including WIT glue)
# ============================================================
echo "=== STEP 4: Compiling MozJS library sources ===" >&2
MOZJS_SRCS="${WASM_SOURCES:-}"
MOZJS_SRCS="$MOZJS_SRCS ${COMMON_WASI_SOURCES:-}"
MOZJS_SRCS="$MOZJS_SRCS ${EXCEPTION_STUBS_SRC:-}"
MOZJS_SRCS="$MOZJS_SRCS ${MOZJS_API_SRC:-}"
# Add wit-bindgen generated glue (C) to compilation inputs.
MOZJS_SRCS="$MOZJS_SRCS ${WIT_API_C:-}"
SRC_LIST="$STAGE/src_list.txt"
>"$SRC_LIST"
# Note: intentionally unquoted to split space-separated paths (Bazel paths never contain spaces).
for src in $MOZJS_SRCS; do
[ -n "$src" ] && echo "$src" >>"$SRC_LIST"
done
# Locate the compile helper script.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COMPILE_HELPER="${COMPILE_HELPER:-$SCRIPT_DIR/compile_wasi_source.sh}"
NPROC=$(nproc 2>/dev/null || echo 8)
echo "Compiling $(wc -l <"$SRC_LIST") files with $NPROC parallel jobs" >&2
OBJ_LIST="$STAGE/obj_list.txt"
if ! cat "$SRC_LIST" | xargs -P "$NPROC" -I {} bash "$COMPILE_HELPER" {} \
"$CXX" "$CC" "$SYSROOT" "$STAGE" "$OBJ_DIR" "$EXECROOT" \
"$ERROR_CODES_H_PARENT" "$CONFIG_H_PARENT" "$BOOST_INCLUDES" \
"$BAZEL_BIN_SRC" >"$OBJ_LIST" 2>&1; then
echo "ERROR: Parallel compilation failed" >&2
cat "$OBJ_LIST" >&2
exit 1
fi
OBJ_FILES=""
for obj in "$OBJ_DIR"/*.o; do
[ -f "$obj" ] && OBJ_FILES="$OBJ_FILES $obj"
done
COMPILE_COUNT=$(ls -1 "$OBJ_DIR"/*.o 2>/dev/null | wc -l)
echo "Compiled $COMPILE_COUNT files successfully" >&2
# ============================================================
# STEP 5: Collect MongoDB base libraries
# ============================================================
echo "=== STEP 5: Collecting MongoDB base libraries ===" >&2
MONGO_BASE_LIBS=""
MONGO_BASE_LIBS_RSP=""
for f in ${LINKSET_FILES:-}; do
case "$f" in
*.libs.rsp) MONGO_BASE_LIBS_RSP="$f" ;;
esac
done
# Use only the .a archives from libs.rsp (they contain all objects).
# We skip objects.rsp to avoid duplicate symbols since the archives
# contain the same objects that would appear there.
if [ -n "$MONGO_BASE_LIBS_RSP" ] && [ -f "$MONGO_BASE_LIBS_RSP" ]; then
LIB_COUNT=0
while IFS= read -r lib_path || [ -n "$lib_path" ]; do
[ -z "$lib_path" ] && continue
case "$lib_path" in "#"*) continue ;; esac
if [ ! "$lib_path" = "${lib_path#/}" ]; then
LIB_ABS="$lib_path"
else
LIB_ABS="$EXECROOT/$lib_path"
fi
if [ -f "$LIB_ABS" ]; then
MONGO_BASE_LIBS="$MONGO_BASE_LIBS $LIB_ABS"
LIB_COUNT=$((LIB_COUNT + 1))
fi
done <"$MONGO_BASE_LIBS_RSP"
echo "Found $LIB_COUNT libraries from libs.rsp" >&2
fi
# ============================================================
# STEP 6: Final linking (include wit component type object)
# ============================================================
echo "=== STEP 6: Linking final WASM binary ===" >&2
EXTRA_OBJS="$(find "$STAGE/obj-extra" -type f -name '*.o' 2>/dev/null | tr '\n' ' ' || true)"
JSRUST_LIB=""
[ -f "$STAGE/lib/libjsrust.a" ] && JSRUST_LIB="$STAGE/lib/libjsrust.a"
echo "Linking with:" >&2
echo " MozJS objects: $(echo $OBJ_FILES | wc -w)" >&2
echo " Mongo base libs: $(echo $MONGO_BASE_LIBS | wc -w)" >&2
if ! "$CXX" --sysroot="$SYSROOT" -std=c++20 -Oz \
-flto -fexceptions \
$OBJ_FILES \
"$WIT_COMPONENT_TYPE_OBJ" \
"$STAGE/lib/libjs_static.a" \
"$STAGE/lib/libmongo_wasip2_rust_shims.a" \
$JSRUST_LIB \
$EXTRA_OBJS \
$MONGO_BASE_LIBS \
-L"$SYSROOT/lib/wasm32-wasip2" \
-lc++ -lc++abi \
-lwasi-emulated-getpid \
-lwasi-emulated-signal \
-lwasi-emulated-mman \
-lwasi-emulated-process-clocks \
-mexec-model=reactor \
-Wl,--export=cabi_realloc \
-Wl,--gc-sections \
-Wl,--strip-all \
-Wl,--no-entry \
-o "$OUTPUT" 2>&1; then
echo "ERROR: Linking failed" >&2
exit 1
fi
test -s "$OUTPUT"
ls -lh "$OUTPUT"
# Post-link size optimization with wasm-opt.
# wasm-opt can typically shrink a WASM binary by another 10-20% through
# WASM-specific optimisations (dead code removal, constant folding, etc.).
WASM_OPT="$(command -v wasm-opt 2>/dev/null || true)"
if [ -z "$WASM_OPT" ] && [ -x "$WASI_SDK_ROOT/bin/wasm-opt" ]; then
WASM_OPT="$WASI_SDK_ROOT/bin/wasm-opt"
fi
if [ -n "$WASM_OPT" ]; then
echo "Running wasm-opt -Oz on $OUTPUT ..." >&2
"$WASM_OPT" -Oz --enable-bulk-memory --enable-sign-ext \
-o "$OUTPUT.opt" "$OUTPUT" 2>&1 && mv "$OUTPUT.opt" "$OUTPUT"
ls -lh "$OUTPUT"
else
echo "NOTE: wasm-opt not found; skipping post-link optimisation" >&2
fi
END_TIME=$(date +%s)
ELAPSED=$((END_TIME - START_TIME))
echo "=== SUCCESS: Built mozjs_wasm_api.wasm in ${ELAPSED}s ===" >&2

View File

@ -0,0 +1,96 @@
#!/bin/bash
# Compiles a single C/C++ source file for WASI.
# Used as a helper by compile_mozjs_wasm_api.sh for parallel compilation via xargs.
#
# Positional arguments:
# $1 - Source file path
# $2 - C++ compiler (wasm32-wasip2-clang++)
# $3 - C compiler (wasm32-wasip2-clang)
# $4 - Sysroot path
# $5 - Stage directory (contains unpacked SpiderMonkey headers)
# $6 - Object output directory
# $7 - Execution root (repo root)
# $8 - Error codes header parent directory
# $9 - Config header parent directory
# $10 - Boost include flags
# $11 - Bazel bin/src path (e.g., bazel-out/k8-fastbuild/bin/src)
#
# Example standalone usage:
# bash scripts/compile_wasi_source.sh myfile.cpp \
# /opt/wasi-sdk/bin/wasm32-wasip2-clang++ \
# /opt/wasi-sdk/bin/wasm32-wasip2-clang \
# /opt/wasi-sdk/share/wasi-sysroot \
# /tmp/sm_stage /tmp/objs /path/to/repo \
# /path/to/error_codes_parent /path/to/config_parent \
# "-I/path/to/boost" \
# /path/to/bazel-out/k8-fastbuild/bin/src
set -euo pipefail
src="$1"
CXX="$2"
CC="$3"
SYSROOT="$4"
STAGE="$5"
OBJ_DIR="$6"
EXECROOT="$7"
ERROR_CODES_H_PARENT="$8"
CONFIG_H_PARENT="$9"
BOOST_INCLUDES="${10}"
BAZEL_BIN_SRC="${11:-}"
# Auto-detect bazel-out bin/src path if not provided.
# Searches for any bazel-out/*/bin/src directory under EXECROOT.
if [ -z "$BAZEL_BIN_SRC" ]; then
for d in "$EXECROOT"/bazel-out/*/bin/src; do
if [ -d "$d" ]; then
BAZEL_BIN_SRC="$d"
break
fi
done
fi
# Derive a unique object name by prefixing with the parent directory.
# This avoids collisions when files from different directories share the
# same basename (e.g. common/error.cpp vs engine/error.cpp, or
# generated/api.c vs engine/api.cpp).
parent="$(basename "$(dirname "$src")")"
base="$(basename "$src")"
OBJ_NAME="${parent}_${base%.*}.o"
OBJ_PATH="$OBJ_DIR/$OBJ_NAME"
ext="${src##*.}"
COMP="$CXX"
STD="-std=c++20"
if [ "$ext" = "c" ]; then
COMP="$CC"
STD="-std=c17"
fi
# Build include flags for Bazel-generated headers (e.g., error_codes.h, config.h).
BAZEL_BIN_INCLUDE=""
if [ -n "$BAZEL_BIN_SRC" ] && [ -d "$BAZEL_BIN_SRC" ]; then
BAZEL_BIN_INCLUDE="-I$BAZEL_BIN_SRC"
fi
"$COMP" --sysroot="$SYSROOT" $STD -Oz \
-flto -fexceptions -ffunction-sections -fdata-sections \
-fvisibility=hidden \
-D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN \
-D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULATED_GETPID \
-DMOZ_HAS_MOZGLUE \
-I"$STAGE/include" -I"$STAGE/include/js" -I"$STAGE/include/mozilla" \
-I"$STAGE/include/src" -I"$STAGE/include/mfbt" \
-include "$STAGE/include/js-confdefs.h" \
-I"$EXECROOT/src" $BAZEL_BIN_INCLUDE \
-I"$EXECROOT/src/mongo/scripting/mozjs/wasm" \
-I"$EXECROOT/src/mongo/scripting/mozjs/common" \
-I"$EXECROOT/src/mongo/scripting/mozjs/common/types" \
-I"$EXECROOT/src/third_party/abseil-cpp/dist" \
-I"$EXECROOT/src/mongo/scripting/mozjs/wasm/wit_gen/generated" \
-DMONGO_MOZJS_WASI_BUILD \
$BOOST_INCLUDES \
-I"$ERROR_CODES_H_PARENT" -I"$CONFIG_H_PARENT" \
-c "$src" -o "$OBJ_PATH" 2>&1
echo "$OBJ_PATH"

View File

@ -0,0 +1,49 @@
#!/bin/bash
# Extracts the Rust encoding-shims static library from a SpiderMonkey WASI tarball.
#
# Required environment variables:
# TARBALL - Path to spidermonkey-wasip2-release.tar.gz
# OUTPUT - Output path for the extracted .a file
#
# Example standalone usage:
# TARBALL=spidermonkey-wasip2-release.tar.gz \
# OUTPUT=rust_shims.a \
# bash scripts/extract_rust_shims.sh
set -euo pipefail
if [ -z "${TARBALL:-}" ]; then
echo "ERROR: TARBALL not set." >&2
exit 1
fi
if [ -z "${OUTPUT:-}" ]; then
echo "ERROR: OUTPUT not set." >&2
exit 1
fi
OUT_DIR="$(dirname "$OUTPUT")"
STAGE="$OUT_DIR/rust_stage"
rm -rf "$STAGE"
mkdir -p "$STAGE"
# Extract lib directory from tarball (tarball has ./lib/ prefix).
tar -xzf "$TARBALL" -C "$STAGE" --strip-components=0 ./lib/libmongo_wasip2_rust_shims.a 2>/dev/null || {
# Try without ./ prefix if that fails.
if ! tar -xzf "$TARBALL" -C "$STAGE" lib/libmongo_wasip2_rust_shims.a 2>/dev/null; then
echo "WARNING: Could not extract rust shims with either path prefix; will check below." >&2
fi
}
RUST_SHIMS="$STAGE/lib/libmongo_wasip2_rust_shims.a"
if [ ! -f "$RUST_SHIMS" ]; then
# Try alternative path.
RUST_SHIMS="$STAGE/./lib/libmongo_wasip2_rust_shims.a"
fi
if [ -f "$RUST_SHIMS" ]; then
cp "$RUST_SHIMS" "$OUTPUT"
echo "Extracted rust shims to: $OUTPUT" >&2
exit 0
fi
echo "ERROR: Rust shims not found in tarball" >&2
exit 1

View File

@ -0,0 +1,82 @@
#!/bin/bash
# Links mongo_base.wasm from WASI linkset response files.
#
# Required environment variables:
# OUTPUT - Output .wasm file path
#
# Compiler discovery (set one of):
# CXX - Direct path to wasm32-wasip2-clang++
# WASI_SDK_BIN_FILES - Space-separated wasi_sdk bin files (auto-discovers compiler)
#
# RSP files (set one of):
# OBJS_RSP / LIBS_RSP / FLAGS_RSP - Direct paths to response files
# LINKSET_FILES - Space-separated linkset outputs (auto-discovers RSPs)
#
# Example standalone usage:
# CXX=/path/to/wasm32-wasip2-clang++ \
# OBJS_RSP=objs.rsp LIBS_RSP=libs.rsp FLAGS_RSP=flags.rsp \
# OUTPUT=mongo_base.wasm \
# bash scripts/link_mongo_base_wasm.sh
set -euo pipefail
# --- Discover compiler ---
# Prefer wasip2 (WASI Preview 2) compiler to match the rest of the build.
if [ -z "${CXX:-}" ]; then
for f in ${WASI_SDK_BIN_FILES:-}; do
case "$f" in */wasm32-wasip2-clang++)
CXX="$f"
break
;;
esac
done
fi
# Fallback to WASI Preview 1 compiler name if wasip2 variant not found.
if [ -z "${CXX:-}" ]; then
for f in ${WASI_SDK_BIN_FILES:-}; do
case "$f" in */wasm32-wasi-clang++)
CXX="$f"
break
;;
esac
done
fi
if [ -z "${CXX:-}" ]; then
echo "ERROR: wasm32-wasip2-clang++ (or wasm32-wasi-clang++) not found. Set CXX or WASI_SDK_BIN_FILES." >&2
exit 1
fi
# --- Discover RSP files ---
if [ -z "${OBJS_RSP:-}" ] || [ -z "${LIBS_RSP:-}" ] || [ -z "${FLAGS_RSP:-}" ]; then
for f in ${LINKSET_FILES:-}; do
case "$f" in
*.objects.rsp) OBJS_RSP="$f" ;;
*.libs.rsp) LIBS_RSP="$f" ;;
*.flags.rsp) FLAGS_RSP="$f" ;;
esac
done
fi
if [ -z "${OBJS_RSP:-}" ] || [ -z "${LIBS_RSP:-}" ] || [ -z "${FLAGS_RSP:-}" ]; then
echo "ERROR: Missing RSP files (.objects/.libs/.flags)." >&2
echo "Set OBJS_RSP/LIBS_RSP/FLAGS_RSP or LINKSET_FILES." >&2
exit 1
fi
# Select the correct --target based on the compiler name.
# wasip2 compiler requires wasip2 target; wasi compiler requires wasi target.
WASM_TARGET="wasm32-wasip2"
echo "Linking mongo_base.wasm" >&2
echo " CXX: $CXX" >&2
echo " TARGET: $WASM_TARGET" >&2
echo " OBJS_RSP: $OBJS_RSP" >&2
echo " LIBS_RSP: $LIBS_RSP" >&2
echo " FLAGS_RSP: $FLAGS_RSP" >&2
"$CXX" --target="$WASM_TARGET" -std=c++20 -Oz -fexceptions \
@"$OBJS_RSP" @"$LIBS_RSP" @"$FLAGS_RSP" \
-Wl,--gc-sections \
-Wl,--strip-all \
-o "$OUTPUT"
echo "Output: $OUTPUT" >&2

View File

@ -0,0 +1,6 @@
package(default_visibility = ["//visibility:public"])
exports_files([
"spider-monkey-repository",
"spider-monkey-version",
])

View File

@ -0,0 +1 @@
https://github.com/mozilla-firefox/firefox

View File

@ -0,0 +1 @@
FIREFOX_140_6_0esr_RELEASE

View File

@ -0,0 +1,80 @@
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
def _strip(s):
return s.strip(" \t\r\n")
def _basename_no_git(url):
u = url
if u.endswith("/"):
u = u[:-1]
if u.endswith(".git"):
u = u[:-4]
# last path component
parts = u.split("/")
return parts[-1] if parts else "repo"
def _spidermonkey_repository_impl(rctx):
repo_url = _strip(rctx.read(rctx.attr.repository_file))
version = _strip(rctx.read(rctx.attr.version_file))
if not repo_url:
fail("spidermonkey_repository: repository_file was empty")
if not version:
fail("spidermonkey_repository: version_file was empty")
# Normalize.
if repo_url.endswith("/"):
repo_url = repo_url[:-1]
if repo_url.endswith(".git"):
repo_url = repo_url[:-4]
repo_name = _basename_no_git(repo_url)
# GitHub archive URL for tags.
# Example: https://github.com/mozilla-firefox/firefox/archive/refs/tags/FIREFOX_140_6_0esr_RELEASE.tar.gz
archive_url = "{}/archive/refs/tags/{}.tar.gz".format(repo_url, version)
strip_prefix = "{}-{}".format(repo_name, version)
if not rctx.attr.sha256:
# buildifier: disable=print
print("WARNING: spidermonkey_repository: sha256 not set for %s; download integrity will not be verified." % archive_url)
rctx.download_and_extract(
url = archive_url,
sha256 = rctx.attr.sha256,
stripPrefix = strip_prefix,
)
# Minimal BUILD file so downstream genrules can depend on `:mach` and `:srcs`.
rctx.file(
"BUILD.bazel",
content = """
package(default_visibility = ["//visibility:public"])
exports_files(["mach"])
filegroup(
name = "srcs",
srcs = glob(
["**"],
exclude = [
"**/.git/**",
"**/.hg/**",
],
),
)
""",
)
spidermonkey_repository = repository_rule(
implementation = _spidermonkey_repository_impl,
attrs = {
# Labels in the main workspace that contain the repo URL and tag/version.
"repository_file": attr.label(mandatory = True, allow_single_file = True),
"version_file": attr.label(mandatory = True, allow_single_file = True),
# Optional, but recommended for hermeticity. If empty, Bazel will not verify.
"sha256": attr.string(default = ""),
},
doc = "Downloads SpiderMonkey/Firefox source from a repo+tag stored in files, and exposes `:mach` and `:srcs`.",
)

View File

@ -0,0 +1,149 @@
/**
* 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 "mongo/scripting/mozjs/common/types/status.h"
#include "mongo/base/error_codes.h"
#include "mongo/base/status.h"
#include "mongo/scripting/mozjs/common/error.h"
#include "mongo/scripting/mozjs/common/freeOpToJSContext.h"
#include "mongo/scripting/mozjs/common/internedstring.h"
#include "mongo/scripting/mozjs/common/objectwrapper.h"
#include "mongo/scripting/mozjs/common/runtime.h"
#include "mongo/scripting/mozjs/common/valuereader.h"
#include "mongo/scripting/mozjs/common/wrapconstrainedmethod.h"
#include "mongo/scripting/mozjs/common/wraptype.h"
#include "mongo/util/assert_util.h"
#include <jsapi.h>
#include <jsfriendapi.h>
#include <js/CallArgs.h>
#include <js/ComparisonOperators.h>
#include <js/Object.h>
#include <js/PropertyDescriptor.h>
#include <js/RootingAPI.h>
#include <js/TypeDecls.h>
#include <js/ValueArray.h>
namespace mongo {
namespace mozjs {
const char* const MongoStatusInfo::className = "MongoStatus";
const char* const MongoStatusInfo::inheritFrom = "Error";
Status MongoStatusInfo::toStatus(JSContext* cx, JS::HandleObject object) {
return *JS::GetMaybePtrFromReservedSlot<Status>(object, StatusSlot);
}
Status MongoStatusInfo::toStatus(JSContext* cx, JS::HandleValue value) {
return *JS::GetMaybePtrFromReservedSlot<Status>(value.toObjectOrNull(), StatusSlot);
}
void MongoStatusInfo::fromStatus(JSContext* cx, Status status, JS::MutableHandleValue value) {
invariant(status != Status::OK());
auto runtime = getCommonRuntime(cx);
JS::RootedValue undef(cx);
undef.setUndefined();
JS::RootedValueArray<1> args(cx);
ValueReader(cx, args[0]).fromStringData(status.reason());
JS::RootedObject error(cx);
getProto<ErrorInfo>(runtime).newInstance(args, &error);
JS::RootedObject thisv(cx);
getProto<MongoStatusInfo>(runtime).newObjectWithProto(&thisv, error);
ObjectWrapper thisvObj(cx, thisv);
thisvObj.defineProperty(InternedString::code,
JSPROP_ENUMERATE,
smUtils::wrapConstrainedMethod<Functions::code, false, MongoStatusInfo>,
nullptr);
thisvObj.defineProperty(
InternedString::reason,
JSPROP_ENUMERATE,
smUtils::wrapConstrainedMethod<Functions::reason, false, MongoStatusInfo>,
nullptr);
// We intentionally omit JSPROP_ENUMERATE to match how Error.prototype.stack is a non-enumerable
// property.
thisvObj.defineProperty(
InternedString::stack,
0,
smUtils::wrapConstrainedMethod<Functions::stack, false, MongoStatusInfo>,
nullptr);
JS::SetReservedSlot(
thisv, StatusSlot, JS::PrivateValue(trackedNew<Status>(runtime, std::move(status))));
value.setObjectOrNull(thisv);
}
void MongoStatusInfo::finalize(JS::GCContext* gcCtx, JSObject* obj) {
auto status = JS::GetMaybePtrFromReservedSlot<Status>(obj, StatusSlot);
if (status)
trackedDelete(getCommonRuntime(freeOpToJSContext(gcCtx)), status);
}
void MongoStatusInfo::Functions::code::call(JSContext* cx, JS::CallArgs args) {
args.rval().setInt32(toStatus(cx, args.thisv()).code());
}
void MongoStatusInfo::Functions::reason::call(JSContext* cx, JS::CallArgs args) {
ValueReader(cx, args.rval()).fromStringData(toStatus(cx, args.thisv()).reason());
}
void MongoStatusInfo::Functions::stack::call(JSContext* cx, JS::CallArgs args) {
JS::RootedObject thisv(cx, args.thisv().toObjectOrNull());
JS::RootedObject parent(cx);
if (!JS_GetPrototype(cx, thisv, &parent)) {
uasserted(ErrorCodes::JSInterpreterFailure, "Couldn't get prototype");
}
ObjectWrapper parentWrapper(cx, parent);
auto status = toStatus(cx, args.thisv());
// WASI version: Skip JSExceptionInfo handling as its not available in WASI builds
// Just return the parent's stack property
parentWrapper.getValue(InternedString::stack, args.rval());
}
void MongoStatusInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
auto runtime = getCommonRuntime(cx);
JS::SetReservedSlot(proto,
StatusSlot,
JS::PrivateValue(trackedNew<Status>(
runtime, Status(ErrorCodes::UnknownError, "Mongo Status Prototype"))));
}
} // namespace mozjs
} // namespace mongo

View File

@ -0,0 +1,11 @@
[package]
name = "mongo_wasip2_rust_shims"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
encoding_c = { path = "{{SPIDERMONKEY_ROOT}}/third_party/rust/encoding_c" }
encoding_c_mem = { path = "{{SPIDERMONKEY_ROOT}}/third_party/rust/encoding_c_mem" }

View File

@ -0,0 +1,45 @@
/**
* 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.
*/
// Rust shims for WASI linkage.
// Force-link the encoding shim crates so their exported C symbols are present.
extern crate encoding_c;
extern crate encoding_c_mem;
#[no_mangle]
pub extern "C" fn mongo_wasip2_rust_shims_keepalive() {
// Touch at least one symbol from each crate so the linker keeps them.
unsafe {
let p = core::ptr::null::<u8>();
let _ = encoding_c::encoding_utf8_valid_up_to(p, 0);
let q = core::ptr::null::<u16>();
let _ = encoding_c_mem::encoding_mem_is_utf8_latin1(p, 0);
let _ = q;
}
}

View File

@ -0,0 +1,3 @@
exports_files([
"mozjs.wit",
])

View File

@ -0,0 +1,35 @@
# Add more methods to public API:
```
world api {
export mozjstest: func() -> s32;
export mynewfunc: func() -> s32;
}
```
# Install wit-gen on your dev VM
```
cargo install --git https://github.com/bytecodealliance/wit-bindgen --locked wit-bindgen-cli
```
# Run the bindgen
```
cd src/mongo/scripting/mozjs/wasm
wit-bindgen c ./wit --out-dir wit_gen/generated
```
# Implement the symbol in engine/api.cpp
```
extern "C" int32_t exports_api_mozjstest(void) {
return 1234;
}
extern "C" int32_t exports_api_mynewfunc(void) {
return ...;
}
```
# Commit the generated files in wit_gen/generated

View File

@ -0,0 +1,90 @@
package mongo:mozjs;
interface mozjs {
/// Error codes mirrored from `err_code_t`.
enum err-code {
ok,
e-invalid-arg,
e-bad-state,
e-nomem,
e-io,
e-timeout,
e-not-supported,
e-internal,
e-jsapi-fail,
e-pending-exception,
e-no-exception,
e-terminated,
e-oom,
e-compile,
e-runtime,
e-module,
e-promise-rejection,
e-stack-overflow,
e-type,
e-encoding,
}
/// Mirrors `wasm_mozjs_error_t` (but as owned values).
record wasm-mozjs-error {
code: err-code,
msg: option<string>,
filename: option<string>,
stack: option<string>,
line: u32,
column: u32,
}
type ok = u16;
/// Opaque function handle.
type function-handle = u64;
/// Initialize/shutdown/interrupt
initialize-engine: func() -> result<ok, wasm-mozjs-error>;
shutdown-engine: func() -> result<ok, wasm-mozjs-error>;
interrupt-current-op: func() -> result<ok, wasm-mozjs-error>;
/// Create a JS function from source.
create-function: func(source: list<u8>) -> result<function-handle, wasm-mozjs-error>;
/// Invoke a function with BSON args (no `this.` binding).
/// Used by $function, $accumulator, mapReduce.reduce, mapReduce.finalize.
invoke-function: func(handle: function-handle, bson: list<u8>) -> result<ok, wasm-mozjs-error>;
/// Invoke a predicate with the document as `this`, returns bool directly.
/// Used by $where: function() { return this.age > 18; }
invoke-predicate: func(handle: function-handle, document: list<u8>) -> result<bool, wasm-mozjs-error>;
/// Invoke a map function with the document as `this`; emits buffered internally.
/// Used by mapReduce.map: function() { emit(this.key, this.value); }
invoke-map: func(handle: function-handle, document: list<u8>) -> result<ok, wasm-mozjs-error>;
/// Get last return value as BSON bytes.
get-return-value-bson: func() -> result<list<u8>, wasm-mozjs-error>;
/// Set a named global variable from a BSON-encoded value.
set-global: func(name: string, bson-value: list<u8>) -> result<ok, wasm-mozjs-error>;
/// Get a named global variable as BSON-encoded bytes.
get-global: func(name: string) -> result<list<u8>, wasm-mozjs-error>;
/// Set a named global to a single BSON element's JS value directly.
set-global-value: func(name: string, bson-element: list<u8>) -> result<ok, wasm-mozjs-error>;
/// Set up the emit() built-in for mapReduce. Resets the emit buffer.
/// Optional byte-limit caps total emitted bytes (default 16 MiB).
setup-emit: func(byte-limit: option<s64>) -> result<ok, wasm-mozjs-error>;
/// Drain the emit buffer: returns accumulated {k,v} pairs as BSON, then clears.
drain-emit-buffer: func() -> result<list<u8>, wasm-mozjs-error>;
}
world api {
export mozjs;
}

View File

@ -0,0 +1,7 @@
exports_files(
glob([
"*.h",
"*.cpp",
"*.defs",
]),
)

View File

@ -0,0 +1,7 @@
exports_files(
glob([
"*.h",
"*.c",
"*.o",
]),
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,186 @@
// Generated by `wit-bindgen` 0.51.0. DO NOT EDIT!
#ifndef __BINDINGS_API_H
#define __BINDINGS_API_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct api_string_t {
uint8_t* ptr;
size_t len;
} api_string_t;
// Error codes mirrored from `err_code_t`.
typedef uint8_t exports_mongo_mozjs_mozjs_err_code_t;
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_OK 0
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_INVALID_ARG 1
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_BAD_STATE 2
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_NOMEM 3
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_IO 4
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_TIMEOUT 5
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_NOT_SUPPORTED 6
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_INTERNAL 7
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_JSAPI_FAIL 8
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_PENDING_EXCEPTION 9
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_NO_EXCEPTION 10
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_TERMINATED 11
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_OOM 12
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_COMPILE 13
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_RUNTIME 14
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_MODULE 15
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_PROMISE_REJECTION 16
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_STACK_OVERFLOW 17
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_TYPE 18
#define EXPORTS_MONGO_MOZJS_MOZJS_ERR_CODE_E_ENCODING 19
typedef struct {
bool is_some;
api_string_t val;
} api_option_string_t;
// Mirrors `wasm_mozjs_error_t` (but as owned values).
typedef struct exports_mongo_mozjs_mozjs_wasm_mozjs_error_t {
exports_mongo_mozjs_mozjs_err_code_t code;
api_option_string_t msg;
api_option_string_t filename;
api_option_string_t stack;
uint32_t line;
uint32_t column;
} exports_mongo_mozjs_mozjs_wasm_mozjs_error_t;
typedef uint16_t exports_mongo_mozjs_mozjs_ok_t;
// Opaque function handle.
typedef uint64_t exports_mongo_mozjs_mozjs_function_handle_t;
typedef struct {
bool is_err;
union {
exports_mongo_mozjs_mozjs_ok_t ok;
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t err;
} val;
} exports_mongo_mozjs_mozjs_result_ok_wasm_mozjs_error_t;
typedef struct {
uint8_t* ptr;
size_t len;
} api_list_u8_t;
typedef struct {
bool is_err;
union {
exports_mongo_mozjs_mozjs_function_handle_t ok;
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t err;
} val;
} exports_mongo_mozjs_mozjs_result_function_handle_wasm_mozjs_error_t;
typedef struct {
bool is_err;
union {
bool ok;
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t err;
} val;
} exports_mongo_mozjs_mozjs_result_bool_wasm_mozjs_error_t;
typedef struct {
bool is_err;
union {
api_list_u8_t ok;
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t err;
} val;
} exports_mongo_mozjs_mozjs_result_list_u8_wasm_mozjs_error_t;
typedef struct {
bool is_some;
int64_t val;
} api_option_s64_t;
// Exported Functions from `mongo:mozjs/mozjs`
bool exports_mongo_mozjs_mozjs_initialize_engine(exports_mongo_mozjs_mozjs_ok_t* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
bool exports_mongo_mozjs_mozjs_shutdown_engine(exports_mongo_mozjs_mozjs_ok_t* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
bool exports_mongo_mozjs_mozjs_interrupt_current_op(
exports_mongo_mozjs_mozjs_ok_t* ret, exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
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);
bool exports_mongo_mozjs_mozjs_invoke_function(exports_mongo_mozjs_mozjs_function_handle_t handle,
api_list_u8_t* bson,
exports_mongo_mozjs_mozjs_ok_t* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
bool exports_mongo_mozjs_mozjs_invoke_predicate(exports_mongo_mozjs_mozjs_function_handle_t handle,
api_list_u8_t* document,
bool* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
bool exports_mongo_mozjs_mozjs_invoke_map(exports_mongo_mozjs_mozjs_function_handle_t handle,
api_list_u8_t* document,
exports_mongo_mozjs_mozjs_ok_t* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
bool exports_mongo_mozjs_mozjs_get_return_value_bson(
api_list_u8_t* ret, exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
bool exports_mongo_mozjs_mozjs_set_global(api_string_t* name,
api_list_u8_t* bson_value,
exports_mongo_mozjs_mozjs_ok_t* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
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);
bool exports_mongo_mozjs_mozjs_set_global_value(api_string_t* name,
api_list_u8_t* bson_element,
exports_mongo_mozjs_mozjs_ok_t* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
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);
bool exports_mongo_mozjs_mozjs_drain_emit_buffer(api_list_u8_t* ret,
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* err);
// Helper Functions
void api_option_string_free(api_option_string_t* ptr);
void exports_mongo_mozjs_mozjs_wasm_mozjs_error_free(
exports_mongo_mozjs_mozjs_wasm_mozjs_error_t* ptr);
void exports_mongo_mozjs_mozjs_result_ok_wasm_mozjs_error_free(
exports_mongo_mozjs_mozjs_result_ok_wasm_mozjs_error_t* ptr);
void api_list_u8_free(api_list_u8_t* ptr);
void exports_mongo_mozjs_mozjs_result_function_handle_wasm_mozjs_error_free(
exports_mongo_mozjs_mozjs_result_function_handle_wasm_mozjs_error_t* ptr);
void exports_mongo_mozjs_mozjs_result_bool_wasm_mozjs_error_free(
exports_mongo_mozjs_mozjs_result_bool_wasm_mozjs_error_t* ptr);
void exports_mongo_mozjs_mozjs_result_list_u8_wasm_mozjs_error_free(
exports_mongo_mozjs_mozjs_result_list_u8_wasm_mozjs_error_t* ptr);
void api_option_s64_free(api_option_s64_t* ptr);
// Sets the string `ret` to reference the input string `s` without copying it
void api_string_set(api_string_t* ret, const char* s);
// Creates a copy of the input nul-terminated string `s` and
// stores it into the component model string `ret`.
void api_string_dup(api_string_t* ret, const char* s);
// Creates a copy of the input string `s` with length `len` and
// stores it into the component model string `ret`.
// The length is specified in code units (bytes for UTF-8, 16-bit values for UTF-16).
void api_string_dup_n(api_string_t* ret, const char* s, size_t len);
// Deallocates the string pointed to by `ret`, deallocating
// the memory behind the string.
void api_string_free(api_string_t* ret);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,54 @@
/**
* 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.
*/
#pragma once
#include "mongo/base/string_data.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/ctype.h"
#include <algorithm>
namespace mongo {
/**
* Validates that a string is a valid ObjectId string (24 hex characters).
*
* This function was extracted from Scope::validateObjectIdString to decouple
* ObjectId validation from the Scope class and engine.h dependencies.
*/
inline void validateObjectIdString(StringData str) {
uassert(10448, "invalid object id: length", str.size() == 24);
auto isAllHex = [](StringData s) {
return std::all_of(s.begin(), s.end(), [](char c) { return mongo::ctype::isXdigit(c); });
};
uassert(10430, "invalid object id: not hex", isAllHex(str));
}
} // namespace mongo

View File

@ -71,6 +71,7 @@ mongo_cc_library(
private_hdrs = [
"//src/mongo/base:string_data.h",
"//src/mongo/scripting:engine.h",
"//src/mongo/scripting:js_regex.h",
],
deps = [
"//src/mongo:base",

View File

@ -43,6 +43,32 @@
#include <unistd.h>
#endif
// If building for WASI/wasm, noop all debugger functionality to avoid POSIX/signal/fork issues.
// See https://github.com/WebAssembly/WASI/issues/166
#if defined(__wasi__) || defined(__WASI__) || defined(__wasm__) || defined(__wasm32__)
namespace mongo {
void breakpoint() {
// no-op under WASI
}
void setupSIGTRAPforDebugger() {
// no-op under WASI
}
void waitForDebugger() {
// no-op under WASI
}
bool isDebuggerActive() {
return false;
}
} // namespace mongo
#else // non-WASI builds
#ifndef _WIN32
namespace {
std::once_flag breakpointOnceFlag;
@ -199,3 +225,5 @@ bool isDebuggerActive() {
}
} // namespace mongo
#endif

View File

@ -38,7 +38,7 @@
#include <winsock2.h>
#endif
#ifndef _WIN32
#if !defined(_WIN32) && !defined(__wasi__)
#include <netdb.h>
#endif
@ -64,6 +64,8 @@ public:
std::string message(int e) const override {
#ifdef _WIN32
return systemError(e).message();
#elif defined(__wasi__)
return "getaddrinfo error " + std::to_string(e) + " (networking not supported in WASI)";
#else
return gai_strerror(e);
#endif

View File

@ -228,7 +228,7 @@ private:
PROCESS_INFORMATION _process;
DWORD _exitcode = STILL_ACTIVE;
};
#else
#elif !defined(__wasi__)
class ProcessStream {
public:
ProcessStream(const std::string& cmd) {
@ -291,6 +291,41 @@ private:
int _fd;
int _exitcode = 1;
};
#else
// WASI doesn't support process execution
// See: https://github.com/WebAssembly/WASI/issues/414
class ProcessStream {
public:
ProcessStream(const std::string& cmd) {
uasserted(ErrorCodes::OperationFailed,
str::stream() << "Shell execution not supported in WASI environment: " << cmd);
}
int close() {
return 1;
}
bool eof() {
return true;
}
Status wait(Milliseconds duration) {
(void)duration;
return {ErrorCodes::OperationFailed, "Shell execution not supported in WASI"};
}
void read(StringBuilder& sb, size_t len) {
(void)sb;
(void)len;
}
~ProcessStream() = default;
private:
ProcessStream() = delete;
ProcessStream(const ProcessStream&) = delete;
ProcessStream& operator=(const ProcessStream&) = delete;
};
#endif
} // namespace
} // namespace mongo

View File

@ -285,7 +285,7 @@ void myPureCallHandler() {
abruptQuit(SIGABRT);
}
#else
#elif !defined(__wasi__)
extern "C" void abruptQuitAction(int signalNum, siginfo_t*, void*) {
if (gSynchronousSignalHandlerCb) {
@ -361,6 +361,37 @@ void setupSignalTestingHandler() {
#endif
}
#else
// WASI doesn't support signals currently. The effort tracking the support
// for signals is https://github.com/WebAssembly/WASI/issues/414,
// See https://github.com/WebAssembly/WASI/issues/166
extern "C" void abruptQuitAction(int signalNum, void*, void*) {
abruptQuit(signalNum);
};
void printSigInfo(const void* siginfo) {
(void)siginfo;
}
extern "C" void abruptQuitWithAddrSignal(int signalNum, void* siginfo, void* ucontext_erased) {
(void)siginfo;
(void)ucontext_erased;
abruptQuit(signalNum);
}
extern "C" void noopSignalHandler(int signalNum, void*, void*) {
(void)signalNum;
}
extern "C" typedef void(sigAction_t)(int signum, void* info, void* context);
void installSignalHandler(int signal, sigAction_t handler) {
(void)signal;
(void)handler;
}
void setupSignalTestingHandler() {}
#endif
} // namespace
@ -382,7 +413,7 @@ void endProcessWithSignal(int signalNum) {
// The exception filter exits the process
quickExit(ExitCode::abrupt);
}
#else
#elif !defined(__wasi__)
// This works by restoring the system-default handler for the given signal, unblocking the
// signal, and re-raising it, in order to get the system default termination behavior (i.e.,
// dumping core, or just exiting).
@ -398,6 +429,10 @@ void endProcessWithSignal(int signalNum) {
invariant(sigprocmask(SIG_UNBLOCK, &unblockSignalMask, nullptr) == 0);
raise(signalNum);
#else
// WASI doesn't support signals, just exit with the appropriate code
(void)signalNum;
quickExit(ExitCode::abrupt);
#endif
}
@ -410,7 +445,7 @@ void setupSynchronousSignalHandlers() {
_set_purecall_handler(myPureCallHandler);
_set_invalid_parameter_handler(myInvalidParameterHandler);
setWindowsUnhandledExceptionFilter();
#else
#elif !defined(__wasi__)
static constexpr struct {
int signal;
sigAction_t* function; // signal ignored if nullptr
@ -435,6 +470,8 @@ void setupSynchronousSignalHandlers() {
#if defined(MONGO_STACKTRACE_CAN_DUMP_ALL_THREADS)
setupStackTraceSignalAction(stackTraceSignal());
#endif
#else
// WASI doesn't support signals, so signal setup is a no-op
#endif
}
@ -451,7 +488,7 @@ void reportOutOfMemoryErrorAndExit() {
}
void clearSignalMask() {
#ifndef _WIN32
#if !defined(_WIN32) && !defined(__wasi__)
// We need to make sure that all signals are unmasked so signals are handled correctly
sigset_t unblockSignalMask;
invariant(sigemptyset(&unblockSignalMask) == 0);

View File

@ -27,7 +27,9 @@
* it in the license file.
*/
#ifndef __wasi__
#include <dlfcn.h>
#endif
#include <fmt/format.h>
// IWYU pragma: no_include "cxxabi.h"
@ -241,6 +243,7 @@ void printMetadata(StackTraceSink& sink, const StackTraceAddressMetadata& meta)
}
void mergeDlInfo(StackTraceAddressMetadata& f) {
#ifndef __wasi__
if (f.file() && f.symbol())
return;
Dl_info dli;
@ -258,6 +261,10 @@ void mergeDlInfo(StackTraceAddressMetadata& f) {
if (!f.symbol() && dli.dli_saddr) {
f.symbol().assign(reinterpret_cast<uintptr_t>(dli.dli_saddr), dli.dli_sname);
}
#else
// WASI doesn't support dynamic linking, so we can't get symbol info
(void)f;
#endif
}
#if MONGO_STACKTRACE_BACKEND == MONGO_STACKTRACE_BACKEND_LIBUNWIND

View File

@ -637,7 +637,8 @@ private:
// Find minimum timer resolution of OS
Nanoseconds getMinimumTimerResolution() {
Nanoseconds minTimerResolution;
#if defined(__linux__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__)
#if defined(__linux__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) || defined(__wasi__) || \
defined(__wasm__) || defined(__wasm32__) || defined(__wasm64__)
struct timespec tp;
int ret = clock_getres(CLOCK_REALTIME, &tp);
if (ret == -1) {

View File

@ -22,20 +22,30 @@ INTEL_RDFP_MATHLIB_DEFINES = [
"DECIMAL_CALL_BY_REFERENCE=0",
"DECIMAL_GLOBAL_ROUNDING=0",
"DECIMAL_GLOBAL_EXCEPTION_FLAGS=0",
"UNCHANGED_BINARY_STATUS_FLAGS=0",
"USE_COMPILER_F128_TYPE=0",
"USE_COMPILER_F80_TYPE=0",
"USE_NATIVE_QUAD_TYPE=0",
] + select({
# WASI/WASM: treat as Linux-like.
# Do NOT define UNCHANGED_BINARY_STATUS_FLAGS so that
# BID_OPT_RESTORE_BINARY_FLAGS() becomes a no-op (WASI has no fenv.h).
"@platforms//os:wasi": [
"LINUX=1",
"linux=1",
],
"@platforms//os:linux": [
"LINUX=1",
"linux=1",
"UNCHANGED_BINARY_STATUS_FLAGS=0",
],
"@platforms//os:macos": [
"LINUX=1",
"mach=1",
"UNCHANGED_BINARY_STATUS_FLAGS=0",
],
"//conditions:default": [
"UNCHANGED_BINARY_STATUS_FLAGS=0",
],
"//conditions:default": [],
}) + select({
"@platforms//cpu:s390x": [
"s390x=1",

View File

@ -217,6 +217,10 @@ mongo_cc_library(
"libs/thread/src/pthread/once.cpp",
"libs/thread/src/pthread/thread.cpp",
],
"@platforms//os:wasi": [
"libs/thread/src/pthread/once.cpp",
"libs/thread/src/pthread/thread.cpp",
],
"@platforms//os:windows": [
"libs/thread/src/win32/thread.cpp",
"libs/thread/src/win32/thread_primitives.cpp",
@ -231,6 +235,9 @@ mongo_cc_library(
"@platforms//os:macos": [
"libs/thread/src/pthread/once_atomic.cpp",
],
"@platforms//os:wasi": [
"libs/thread/src/pthread/once_atomic.cpp",
],
"//conditions:default": [],
}),
copts = BOOST_COPTS,
@ -242,6 +249,9 @@ mongo_cc_library(
"@platforms//os:macos": [
"BOOST_THREAD_PTHREAD",
],
"@platforms//os:wasi": [
"BOOST_THREAD_PTHREAD",
],
"@platforms//os:windows": [
"BOOST_THREAD_WIN32",
],
@ -287,9 +297,7 @@ mongo_cc_library(
"libs/log/src/trivial.cpp",
] + select({
"@platforms//os:windows": [],
"//conditions:default": [
"libs/log/src/syslog_backend.cpp",
],
"//conditions:default": ["libs/log/src/syslog_backend.cpp"],
}),
hdrs = BOOST_LIB_HEADERS,
copts = BOOST_COPTS + select({

View File

@ -6,8 +6,11 @@ mongo_cc_library(
name = "fmt",
srcs = [
"dist/src/format.cc",
"dist/src/os.cc",
],
] + select({
# os.cc uses POSIX I/O (dup, dup2, pipe) not available in WASI
"@platforms//os:wasi": [],
"//conditions:default": ["dist/src/os.cc"],
}),
hdrs = glob([
"dist/include/fmt/*.h",
]),
@ -21,4 +24,9 @@ mongo_cc_library(
"//conditions:default": [],
}),
includes = ["dist/include"],
local_defines = select({
# Disable POSIX file operations for WASI
"@platforms//os:wasi": ["FMT_USE_FCNTL=0"],
"//conditions:default": [],
}),
)