From 05e8a6dd4d2b07239e6957410a4cd0cb56cb44e6 Mon Sep 17 00:00:00 2001 From: Kai Tamkun <13513421+heimskr@users.noreply.github.com> Date: Mon, 7 Jul 2025 19:29:53 -0700 Subject: [PATCH] Add support for vm.constants.DONT_CONTEXTIFY (#20088) --- src/bun.js/bindings/ErrorCode.cpp | 2 + src/bun.js/bindings/ErrorCode.ts | 1 + src/bun.js/bindings/NodeVM.cpp | 576 ++++++++++++++---- src/bun.js/bindings/NodeVM.h | 143 +++-- src/bun.js/bindings/NodeVMModule.cpp | 51 +- src/bun.js/bindings/NodeVMScript.cpp | 101 ++- src/bun.js/bindings/NodeVMScript.h | 3 +- src/bun.js/bindings/NodeVMScriptFetcher.h | 22 +- .../bindings/NodeVMSourceTextModule.cpp | 64 +- src/bun.js/bindings/NodeVMSyntheticModule.cpp | 10 +- src/bun.js/bindings/ZigGlobalObject.cpp | 18 +- src/bun.js/bindings/ZigGlobalObject.h | 6 +- .../bindings/webcore/DOMClientIsoSubspaces.h | 1 + src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 1 + src/js/node/vm.ts | 26 +- .../test-vm-context-dont-contextify.js | 185 ++++++ .../parallel/test-vm-module-dynamic-import.js | 117 ++++ .../test-vm-module-dynamic-namespace.js | 26 + .../test-vm-module-referrer-realm.mjs | 70 +++ .../test-vm-no-dynamic-import-callback.js | 20 + .../sequential/test-vm-timeout-rethrow.js | 44 ++ test/no-validate-exceptions.txt | 1 + 22 files changed, 1212 insertions(+), 276 deletions(-) create mode 100644 test/js/node/test/parallel/test-vm-context-dont-contextify.js create mode 100644 test/js/node/test/parallel/test-vm-module-dynamic-import.js create mode 100644 test/js/node/test/parallel/test-vm-module-dynamic-namespace.js create mode 100644 test/js/node/test/parallel/test-vm-module-referrer-realm.mjs create mode 100644 test/js/node/test/parallel/test-vm-no-dynamic-import-callback.js create mode 100644 test/js/node/test/sequential/test-vm-timeout-rethrow.js diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 0a5ab24e12..bf8768a6af 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -2499,6 +2499,8 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_NOT_MODULE, "Provided module is not an instance of Module"_s)); case ErrorCode::ERR_VM_MODULE_DIFFERENT_CONTEXT: return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_DIFFERENT_CONTEXT, "Linked modules must use the same context"_s)); + case ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, "A dynamic import callback was not specified."_s)); default: { break; diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index ecd518b692..33dcec4582 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -296,6 +296,7 @@ const errors: ErrorCodeMapping = [ ["ERR_VM_MODULE_DIFFERENT_CONTEXT", Error], ["ERR_VM_MODULE_LINK_FAILURE", Error], ["ERR_VM_MODULE_CACHED_DATA_REJECTED", Error], + ["ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING", TypeError], ["HPE_INVALID_HEADER_TOKEN", Error], ["HPE_HEADER_OVERFLOW", Error], ]; diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index c0aef23763..8d91cc4a4b 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -55,16 +55,23 @@ #include "JavaScriptCore/FunctionCodeBlock.h" #include "JavaScriptCore/JIT.h" #include "JavaScriptCore/ProgramCodeBlock.h" +#include "JavaScriptCore/GlobalObjectMethodTable.h" #include "NodeVMScriptFetcher.h" #include "wtf/FileHandle.h" #include "../vm/SigintWatcher.h" +#include "JavaScriptCore/GetterSetter.h" + namespace Bun { using namespace WebCore; +static JSInternalPromise* moduleLoaderImportModuleInner(NodeVMGlobalObject* globalObject, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin); + namespace NodeVM { +static JSInternalPromise* importModuleInner(JSGlobalObject* globalObject, JSString* moduleName, JSValue parameters, const SourceOrigin& sourceOrigin, JSValue dynamicImportCallback, JSValue owner); + bool extractCachedData(JSValue cachedDataValue, WTF::Vector& outCachedData) { if (!cachedDataValue.isCell()) { @@ -126,6 +133,8 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c if (actuallyValid) { auto exception = error.toErrorObject(globalObject, sourceCode, -1); + RETURN_IF_EXCEPTION(throwScope, nullptr); + throwException(globalObject, throwScope, exception); return nullptr; } @@ -174,6 +183,7 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c { DeferGC deferGC(vm); programCodeBlock = ProgramCodeBlock::create(vm, programExecutable, unlinkedProgramCodeBlock, scope); + RETURN_IF_EXCEPTION(throwScope, nullptr); } if (!programCodeBlock || programCodeBlock->numberOfFunctionExprs() == 0) { @@ -193,6 +203,7 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c RefPtr producedBytecode = getBytecode(globalObject, programExecutable, sourceCode); if (producedBytecode) { JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, producedBytecode->span()); + RETURN_IF_EXCEPTION(throwScope, nullptr); function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedData"_s), buffer); function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(true)); } else { @@ -201,39 +212,84 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c } } else { function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataRejected"_s), jsBoolean(bytecodeAccepted == TriState::False)); + RETURN_IF_EXCEPTION(throwScope, nullptr); } return function; } -JSInternalPromise* importModule(JSGlobalObject* globalObject, JSString* moduleNameValue, JSValue parameters, const SourceOrigin& sourceOrigin) +JSInternalPromise* importModule(JSGlobalObject* globalObject, JSString* moduleName, JSValue parameters, const SourceOrigin& sourceOrigin) { - if (auto* fetcher = sourceOrigin.fetcher(); !fetcher || fetcher->fetcherType() != ScriptFetcher::Type::NodeVM) { - return nullptr; - } - VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - auto* fetcher = static_cast(sourceOrigin.fetcher()); - - JSValue dynamicImportCallback = fetcher->dynamicImportCallback(); - - if (!dynamicImportCallback || !dynamicImportCallback.isCallable()) { + if (auto* fetcher = sourceOrigin.fetcher(); !fetcher || fetcher->fetcherType() != ScriptFetcher::Type::NodeVM) { + if (!sourceOrigin.url().isEmpty()) { + if (auto* nodeVmGlobalObject = jsDynamicCast(globalObject)) { + if (nodeVmGlobalObject->dynamicImportCallback()) { + RELEASE_AND_RETURN(scope, moduleLoaderImportModuleInner(nodeVmGlobalObject, globalObject->moduleLoader(), moduleName, parameters, sourceOrigin)); + } + } + } return nullptr; } - JSFunction* owner = fetcher->owner(); + auto* fetcher = static_cast(sourceOrigin.fetcher()); + + if (fetcher->isUsingDefaultLoader()) { + return nullptr; + } + + JSValue dynamicImportCallback = fetcher->dynamicImportCallback(); + + if (isUseMainContextDefaultLoaderConstant(globalObject, dynamicImportCallback)) { + auto defer = fetcher->temporarilyUseDefaultLoader(); + Zig::GlobalObject* zigGlobalObject = defaultGlobalObject(globalObject); + RELEASE_AND_RETURN(scope, zigGlobalObject->moduleLoaderImportModule(zigGlobalObject, zigGlobalObject->moduleLoader(), moduleName, parameters, sourceOrigin)); + } else if (!dynamicImportCallback || !dynamicImportCallback.isCallable()) { + throwException(globalObject, scope, createError(globalObject, ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, "A dynamic import callback was not specified."_s)); + return nullptr; + } + + RELEASE_AND_RETURN(scope, importModuleInner(globalObject, moduleName, parameters, sourceOrigin, dynamicImportCallback, fetcher->owner())); +} + +static JSInternalPromise* importModuleInner(JSGlobalObject* globalObject, JSString* moduleName, JSValue parameters, const SourceOrigin& sourceOrigin, JSValue dynamicImportCallback, JSValue owner) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (parameters.isObject()) { + if (JSValue with = asObject(parameters)->getIfPropertyExists(globalObject, vm.propertyNames->with)) { + parameters = with; + } + RETURN_IF_EXCEPTION(scope, nullptr); + } MarkedArgumentBuffer args; - args.append(moduleNameValue); - args.append(owner ? owner : jsUndefined()); + args.append(moduleName); + if (owner) { + args.append(owner); + } else if (auto* nodeVmGlobalObject = jsDynamicCast(globalObject)) { + if (nodeVmGlobalObject->isNotContextified()) { + args.append(nodeVmGlobalObject->specialSandbox()); + } else { + args.append(nodeVmGlobalObject->contextifiedObject()); + } + } else { + args.append(jsUndefined()); + } args.append(parameters); JSValue result = AsyncContextFrame::call(globalObject, dynamicImportCallback, jsUndefined(), args); RETURN_IF_EXCEPTION(scope, nullptr); + if (result.isUndefinedOrNull()) { + throwException(globalObject, scope, createError(globalObject, ErrorCode::ERR_VM_MODULE_NOT_MODULE, "Provided module is not an instance of Module"_s)); + return nullptr; + } + if (auto* promise = jsDynamicCast(result)) { return promise; } @@ -267,7 +323,7 @@ JSInternalPromise* importModule(JSGlobalObject* globalObject, JSString* moduleNa promise = promise->then(globalObject, transformer, nullptr); RETURN_IF_EXCEPTION(scope, nullptr); - return promise; + RELEASE_AND_RETURN(scope, promise); } // Helper function to create an anonymous function expression with parameters @@ -368,9 +424,11 @@ JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::So std::span bytes = bytecode->span(); JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, bytes); - RETURN_IF_EXCEPTION(scope, {}); - ASSERT(buffer); + + if (!buffer) { + return throwVMError(globalObject, scope, "Failed to create buffer"_s); + } return JSValue::encode(buffer); } @@ -386,6 +444,7 @@ bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtrstack(); size_t stack_size = e_stack.size(); @@ -411,8 +470,12 @@ bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey) +std::optional getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey, JSValue* importer) { + if (importer) { + *importer = jsUndefined(); + } + outOptions = {}; // If options is provided, validate name and origin properties @@ -440,10 +503,19 @@ std::optional getNodeVMContextOptions(JSGlobalObject* globa } } - auto codeGenerationValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, codeGenerationKey)); + JSValue importModuleDynamicallyValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "importModuleDynamically"_s)); RETURN_IF_EXCEPTION(scope, {}); - if (codeGenerationValue) { + if (importModuleDynamicallyValue) { + if (importer && importModuleDynamicallyValue && (importModuleDynamicallyValue.isCallable() || isUseMainContextDefaultLoaderConstant(globalObject, importModuleDynamicallyValue))) { + *importer = importModuleDynamicallyValue; + } + } + + JSValue codeGenerationValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, codeGenerationKey)); + RETURN_IF_EXCEPTION(scope, {}); + + if (codeGenerationValue) { if (codeGenerationValue.isUndefined()) { return std::nullopt; } @@ -462,6 +534,7 @@ std::optional getNodeVMContextOptions(JSGlobalObject* globa } outOptions.allowStrings = allowStringsValue.toBoolean(globalObject); + RETURN_IF_EXCEPTION(scope, {}); } auto allowWasmValue = codeGenerationObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "wasm"_s)); @@ -472,6 +545,7 @@ std::optional getNodeVMContextOptions(JSGlobalObject* globa } outOptions.allowWasm = allowWasmValue.toBoolean(globalObject); + RETURN_IF_EXCEPTION(scope, {}); } } @@ -500,13 +574,28 @@ NodeVMGlobalObject* getGlobalObjectFromContext(JSGlobalObject* globalObject, JSV auto* zigGlobalObject = defaultGlobalObject(globalObject); JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); if (scopeValue.isUndefined()) { + if (auto* specialSandbox = jsDynamicCast(context)) { + return specialSandbox->parentGlobal(); + } + + if (auto* proxy = jsDynamicCast(context)) { + if (auto* nodeVmGlobalObject = jsDynamicCast(proxy->target())) { + return nodeVmGlobalObject; + } + } + if (canThrow) { INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); } return nullptr; } - NodeVMGlobalObject* nodeVmGlobalObject = jsDynamicCast(scopeValue); + auto* nodeVmGlobalObject = jsDynamicCast(scopeValue); + + if (!nodeVmGlobalObject) { + nodeVmGlobalObject = jsDynamicCast(context); + } + if (!nodeVmGlobalObject) { if (canThrow) { INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); @@ -525,12 +614,98 @@ JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, return {}; } +bool isContext(JSGlobalObject* globalObject, JSValue value) +{ + auto* zigGlobalObject = defaultGlobalObject(globalObject); + + if (zigGlobalObject->vmModuleContextMap()->has(asObject(value))) { + return true; + } + + if (value.inherits(NodeVMSpecialSandbox::info())) { + return true; + } + + if (auto* proxy = jsDynamicCast(value); proxy && proxy->target()) { + return proxy->target()->inherits(NodeVMGlobalObject::info()); + } + + return false; +} + +bool getContextArg(JSGlobalObject* globalObject, JSValue& contextArg) +{ + if (contextArg.isUndefined()) { + contextArg = JSC::constructEmptyObject(globalObject); + } else if (contextArg.isSymbol()) { + Zig::GlobalObject* zigGlobalObject = defaultGlobalObject(globalObject); + if (contextArg == zigGlobalObject->m_nodeVMDontContextify.get(zigGlobalObject)) { + contextArg = JSC::constructEmptyObject(globalObject); + return true; + } + } + + return false; +} + +bool isUseMainContextDefaultLoaderConstant(JSGlobalObject* globalObject, JSValue value) +{ + if (value.isSymbol()) { + Zig::GlobalObject* zigGlobalObject = defaultGlobalObject(globalObject); + if (value == zigGlobalObject->m_nodeVMUseMainContextDefaultLoader.get(zigGlobalObject)) { + return true; + } + } + + return false; +} + } // namespace NodeVM using namespace NodeVM; -NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure) +template JSC::GCClient::IsoSubspace* NodeVMSpecialSandbox::subspaceFor(JSC::VM& vm) +{ + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMSpecialSandbox.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMSpecialSandbox = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForNodeVMSpecialSandbox.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSpecialSandbox = std::forward(space); }); +} + +NodeVMSpecialSandbox* NodeVMSpecialSandbox::create(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject) +{ + NodeVMSpecialSandbox* ptr = new (NotNull, allocateCell(vm)) NodeVMSpecialSandbox(vm, structure, globalObject); + ptr->finishCreation(vm); + return ptr; +} + +JSC::Structure* NodeVMSpecialSandbox::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) +{ + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); +} + +NodeVMSpecialSandbox::NodeVMSpecialSandbox(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject) : Base(vm, structure) +{ + m_parentGlobal.set(vm, this, globalObject); +} + +void NodeVMSpecialSandbox::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +const JSC::ClassInfo NodeVMSpecialSandbox::s_info = { "NodeVMSpecialSandbox"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSpecialSandbox) }; + +NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions contextOptions, JSValue importer) + : Base(vm, structure, &globalObjectMethodTable()) + , m_dynamicImportCallback(vm, this, importer) + , m_contextOptions(contextOptions) { } @@ -547,10 +722,10 @@ template JSC::GCClient::IsoSubspace* NodeVMG [](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForNodeVMGlobalObject; }); } -NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options) +NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options, JSValue importer) { - auto* cell = new (NotNull, JSC::allocateCell(vm)) NodeVMGlobalObject(vm, structure); - cell->finishCreation(vm, options); + auto* cell = new (NotNull, JSC::allocateCell(vm)) NodeVMGlobalObject(vm, structure, options, importer); + cell->finishCreation(vm); return cell; } @@ -560,11 +735,40 @@ Structure* NodeVMGlobalObject::createStructure(JSC::VM& vm, JSC::JSValue prototy return JSC::Structure::create(vm, nullptr, prototype, JSC::TypeInfo(JSC::GlobalObjectType, StructureFlags & ~IsImmutablePrototypeExoticObject), info()); } -void NodeVMGlobalObject::finishCreation(JSC::VM& vm, NodeVMContextOptions options) +const JSC::GlobalObjectMethodTable& NodeVMGlobalObject::globalObjectMethodTable() +{ + static const JSC::GlobalObjectMethodTable table { + &supportsRichSourceInfo, + &shouldInterruptScript, + &javaScriptRuntimeFlags, + nullptr, // queueTaskToEventLoop + nullptr, // shouldInterruptScriptBeforeTimeout, + &moduleLoaderImportModule, + nullptr, // moduleLoaderResolve + nullptr, // moduleLoaderFetch + nullptr, // moduleLoaderCreateImportMetaProperties + nullptr, // moduleLoaderEvaluate + nullptr, // promiseRejectionTracker + &reportUncaughtExceptionAtEventLoop, + ¤tScriptExecutionOwner, + &scriptExecutionStatus, + nullptr, // reportViolationForUnsafeEval + nullptr, // defaultLanguage + nullptr, // compileStreaming + nullptr, // instantiateStreaming + nullptr, + &codeForEval, + &canCompileStrings, + &trustedScriptStructure, + }; + return table; +} + +void NodeVMGlobalObject::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); - setEvalEnabled(options.allowStrings, "Code generation from strings disallowed for this context"_s); - setWebAssemblyEnabled(options.allowWasm, "Wasm code generation disallowed by embedder"_s); + setEvalEnabled(m_contextOptions.allowStrings, "Code generation from strings disallowed for this context"_s); + setWebAssemblyEnabled(m_contextOptions.allowWasm, "Wasm code generation disallowed by embedder"_s); vm.ensureTerminationException(); } @@ -619,18 +823,27 @@ bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, Propert bool isFunction = value.isCallable(); if (slot.isStrictMode() && !isDeclared && isContextualStore && !isFunction) { - return Base::put(cell, globalObject, propertyName, value, slot); + RELEASE_AND_RETURN(scope, Base::put(cell, globalObject, propertyName, value, slot)); } if (!isDeclared && value.isSymbol()) { - return Base::put(cell, globalObject, propertyName, value, slot); + RELEASE_AND_RETURN(scope, Base::put(cell, globalObject, propertyName, value, slot)); + } + + if (thisObject->m_contextOptions.notContextified) { + JSObject* specialSandbox = thisObject->specialSandbox(); + slot.setThisValue(specialSandbox); + RELEASE_AND_RETURN(scope, specialSandbox->putInline(globalObject, propertyName, value, slot)); } slot.setThisValue(sandbox); + bool result = sandbox->methodTable()->put(sandbox, globalObject, propertyName, value, slot); + RETURN_IF_EXCEPTION(scope, false); - if (!sandbox->methodTable()->put(sandbox, globalObject, propertyName, value, slot)) { + if (!result) { return false; } + RETURN_IF_EXCEPTION(scope, false); if (isDeclaredOnSandbox && getter.isAccessor() and (getter.attributes() & PropertyAttribute::DontEnum) == 0) { @@ -638,21 +851,54 @@ bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, Propert } slot.setThisValue(thisValue); - - return Base::put(cell, globalObject, propertyName, value, slot); + RELEASE_AND_RETURN(scope, Base::put(cell, globalObject, propertyName, value, slot)); } // This is copy-pasted from JSC's ProxyObject.cpp static const ASCIILiteral s_proxyAlreadyRevokedErrorMessage { "Proxy has already been revoked. No more operations are allowed to be performed on it"_s }; +bool NodeVMSpecialSandbox::getOwnPropertySlot(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsCast(cell); + NodeVMGlobalObject* parentGlobal = thisObject->parentGlobal(); + + if (propertyName.uid()->utf8() == "globalThis") [[unlikely]] { + slot.disableCaching(); + slot.setThisValue(thisObject); + slot.setValue(thisObject, slot.attributes(), thisObject); + return true; + } + + bool result = parentGlobal->getOwnPropertySlot(parentGlobal, globalObject, propertyName, slot); + RETURN_IF_EXCEPTION(scope, false); + + if (result) { + return true; + } + + RELEASE_AND_RETURN(scope, Base::getOwnPropertySlot(cell, globalObject, propertyName, slot)); +} + bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) { VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* thisObject = jsCast(cell); - if (thisObject->m_sandbox) { - auto* contextifiedObject = thisObject->m_sandbox.get(); + + bool notContextified = thisObject->isNotContextified(); + + if (notContextified && propertyName.uid()->utf8() == "globalThis") [[unlikely]] { + slot.disableCaching(); + slot.setThisValue(thisObject); + slot.setValue(thisObject, slot.attributes(), thisObject->specialSandbox()); + return true; + } + + if (JSObject* contextifiedObject = thisObject->contextifiedObject()) { slot.setThisValue(contextifiedObject); // Unfortunately we must special case ProxyObjects. Why? // @@ -719,8 +965,12 @@ bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* glob goto try_from_global; } - if (contextifiedObject->getPropertySlot(globalObject, propertyName, slot)) { - return true; + if (!notContextified) { + bool result = contextifiedObject->getPropertySlot(globalObject, propertyName, slot); + RETURN_IF_EXCEPTION(scope, false); + if (result) { + return true; + } } try_from_global: @@ -729,47 +979,61 @@ bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* glob RETURN_IF_EXCEPTION(scope, false); } - return Base::getOwnPropertySlot(cell, globalObject, propertyName, slot); + bool result = Base::getOwnPropertySlot(cell, globalObject, propertyName, slot); + RETURN_IF_EXCEPTION(scope, false); + + if (result) { + return true; + } + + if (thisObject->m_contextOptions.notContextified) { + JSObject* specialSandbox = thisObject->specialSandbox(); + RELEASE_AND_RETURN(scope, JSObject::getOwnPropertySlot(specialSandbox, globalObject, propertyName, slot)); + } + + return false; } bool NodeVMGlobalObject::defineOwnProperty(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) { - // if (!propertyName.isSymbol()) - // printf("defineOwnProperty called for %s\n", propertyName.publicName()->utf8().data()); - auto* thisObject = jsCast(cell); - if (!thisObject->m_sandbox) { - return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow); - } - - auto* contextifiedObject = thisObject->m_sandbox.get(); VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); + auto* thisObject = jsCast(cell); + if (!thisObject->m_sandbox) { + RELEASE_AND_RETURN(scope, Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow)); + } + + auto* contextifiedObject = thisObject->isNotContextified() ? thisObject->specialSandbox() : thisObject->m_sandbox.get(); + PropertySlot slot(globalObject, PropertySlot::InternalMethodType::GetOwnProperty, nullptr); bool isDeclaredOnGlobalProxy = globalObject->JSC::JSGlobalObject::getOwnPropertySlot(globalObject, globalObject, propertyName, slot); // If the property is set on the global as neither writable nor // configurable, don't change it on the global or sandbox. if (isDeclaredOnGlobalProxy && (slot.attributes() & PropertyAttribute::ReadOnly) != 0 && (slot.attributes() & PropertyAttribute::DontDelete) != 0) { - return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow); + RELEASE_AND_RETURN(scope, Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow)); } if (descriptor.isAccessorDescriptor()) { - return contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow); + RELEASE_AND_RETURN(scope, JSObject::defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow)); } bool isDeclaredOnSandbox = contextifiedObject->getPropertySlot(globalObject, propertyName, slot); RETURN_IF_EXCEPTION(scope, false); if (isDeclaredOnSandbox && !isDeclaredOnGlobalProxy) { - return contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow); + RELEASE_AND_RETURN(scope, JSObject::defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow)); } - if (!contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow)) { + bool result = JSObject::defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow); + RETURN_IF_EXCEPTION(scope, false); + + if (!result) { return false; } - return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow); + RELEASE_AND_RETURN(scope, Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow)); } DEFINE_VISIT_CHILDREN(NodeVMGlobalObject); @@ -780,6 +1044,8 @@ void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) Base::visitChildren(cell, visitor); auto* thisObject = jsCast(cell); visitor.append(thisObject->m_sandbox); + visitor.append(thisObject->m_specialSandbox); + visitor.append(thisObject->m_dynamicImportCallback); } JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -792,41 +1058,44 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject return ERR::INVALID_ARG_TYPE(scope, globalObject, "code"_s, "string"_s, code); JSValue contextArg = callFrame->argument(1); - if (contextArg.isUndefined()) { - contextArg = JSC::constructEmptyObject(globalObject); - } + bool notContextified = getContextArg(globalObject, contextArg); - if (!contextArg.isObject()) + if (!contextArg.isObject()) { return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); + } JSObject* sandbox = asObject(contextArg); JSValue contextOptionsArg = callFrame->argument(2); - NodeVMContextOptions contextOptions {}; - if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, contextOptionsArg, contextOptions, "contextCodeGeneration")) { + JSValue globalObjectDynamicImportCallback; + + if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, contextOptionsArg, contextOptions, "contextCodeGeneration", &globalObjectDynamicImportCallback)) { return *encodedException; } + contextOptions.notContextified = notContextified; + // Create context and run code auto* context = NodeVMGlobalObject::create(vm, defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(), - contextOptions); + contextOptions, globalObjectDynamicImportCallback); context->setContextifiedObject(sandbox); JSValue optionsArg = callFrame->argument(2); + JSValue scriptDynamicImportCallback; ScriptOptions options(optionsArg.toWTFString(globalObject), OrdinalNumber::fromZeroBasedInt(0), OrdinalNumber::fromZeroBasedInt(0)); if (optionsArg.isString()) { options.filename = optionsArg.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); - } else if (!options.fromJS(globalObject, vm, scope, optionsArg)) { + } else if (!options.fromJS(globalObject, vm, scope, optionsArg, &scriptDynamicImportCallback)) { RETURN_IF_EXCEPTION(scope, {}); } - RefPtr fetcher(NodeVMScriptFetcher::create(vm, options.importer)); + RefPtr fetcher(NodeVMScriptFetcher::create(vm, scriptDynamicImportCallback, jsUndefined())); SourceCode sourceCode( JSC::StringSourceProvider::create( @@ -862,19 +1131,21 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInThisContext, (JSGlobalObject * globalObjec return ERR::INVALID_ARG_TYPE(throwScope, globalObject, "code"_s, "string"_s, sourceStringValue); } - auto sourceString = sourceStringValue.toWTFString(globalObject); + String sourceString = sourceStringValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, encodedJSUndefined()); + JSValue importer; + JSValue optionsArg = callFrame->argument(1); ScriptOptions options(optionsArg.toWTFString(globalObject), OrdinalNumber::fromZeroBasedInt(0), OrdinalNumber::fromZeroBasedInt(0)); if (optionsArg.isString()) { options.filename = optionsArg.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); - } else if (!options.fromJS(globalObject, vm, throwScope, optionsArg)) { + } else if (!options.fromJS(globalObject, vm, throwScope, optionsArg, &importer)) { RETURN_IF_EXCEPTION(throwScope, encodedJSUndefined()); } - RefPtr fetcher(NodeVMScriptFetcher::create(vm, options.importer)); + RefPtr fetcher(NodeVMScriptFetcher::create(vm, importer, jsUndefined())); SourceCode source( JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename), *fetcher), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), @@ -927,7 +1198,9 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject // Get options argument JSValue optionsArg = callFrame->argument(2); CompileFunctionOptions options; - if (!options.fromJS(globalObject, vm, scope, optionsArg)) { + JSValue importer; + + if (!options.fromJS(globalObject, vm, scope, optionsArg, &importer)) { RETURN_IF_EXCEPTION(scope, {}); options = {}; options.parsingContext = globalObject; @@ -949,7 +1222,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject // Add the function body constructFunctionArgs.append(jsString(vm, sourceString)); - RefPtr fetcher(NodeVMScriptFetcher::create(vm, options.importer)); + RefPtr fetcher(NodeVMScriptFetcher::create(vm, importer, jsUndefined())); // Create the source origin SourceOrigin sourceOrigin { WTF::URL::fileURLWithFileSystemPath(options.filename), *fetcher }; @@ -983,14 +1256,18 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject // Create the function using constructAnonymousFunction with the appropriate scope chain JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, WTFMove(options), JSC::SourceTaintedOrigin::Untainted, functionScope); - fetcher->owner(vm, function); - RETURN_IF_EXCEPTION(scope, {}); if (!function) { return throwVMError(globalObject, scope, "Failed to compile function"_s); } + fetcher->owner(vm, function); + + if (!function) { + return throwVMError(globalObject, scope, "Failed to compile function"_s); + } + return JSValue::encode(function); } @@ -999,31 +1276,16 @@ Structure* createNodeVMGlobalObjectStructure(JSC::VM& vm) return NodeVMGlobalObject::createStructure(vm, jsNull()); } -NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox) -{ - auto* targetContext = NodeVMGlobalObject::create(vm, - defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(), - NodeVMContextOptions {}); - - // Set sandbox as contextified object - targetContext->setContextifiedObject(sandbox); - - // Store context in WeakMap for isContext checks - auto* zigGlobalObject = defaultGlobalObject(globalObject); - zigGlobalObject->vmModuleContextMap()->set(vm, sandbox, targetContext); - - return targetContext; -} - JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + NodeVMContextOptions contextOptions {}; + JSValue contextArg = callFrame->argument(0); - if (contextArg.isUndefined()) { - contextArg = JSC::constructEmptyObject(globalObject); - } + bool notContextified = getContextArg(globalObject, contextArg); + RETURN_IF_EXCEPTION(scope, {}); if (!contextArg.isObject()) { return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); @@ -1036,25 +1298,48 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, return ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); } - NodeVMContextOptions contextOptions {}; + JSValue importer; - if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, optionsArg, contextOptions, "codeGeneration")) { + if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, optionsArg, contextOptions, "codeGeneration", &importer)) { return *encodedException; } + contextOptions.notContextified = notContextified; + JSObject* sandbox = asObject(contextArg); + if (isContext(globalObject, sandbox)) { + if (auto* proxy = jsDynamicCast(sandbox)) { + if (auto* targetContext = jsDynamicCast(proxy->target())) { + if (targetContext->isNotContextified()) { + return JSValue::encode(targetContext->specialSandbox()); + } + } + } + return JSValue::encode(sandbox); + } + + auto* zigGlobalObject = defaultGlobalObject(globalObject); + auto* targetContext = NodeVMGlobalObject::create(vm, - defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(), - contextOptions); + zigGlobalObject->NodeVMGlobalObjectStructure(), + contextOptions, importer); + + RETURN_IF_EXCEPTION(scope, {}); // Set sandbox as contextified object targetContext->setContextifiedObject(sandbox); // Store context in WeakMap for isContext checks - auto* zigGlobalObject = defaultGlobalObject(globalObject); zigGlobalObject->vmModuleContextMap()->set(vm, sandbox, targetContext); + if (notContextified) { + auto* specialSandbox = NodeVMSpecialSandbox::create(vm, zigGlobalObject->NodeVMSpecialSandboxStructure(), targetContext); + RETURN_IF_EXCEPTION(scope, {}); + targetContext->setSpecialSandbox(specialSandbox); + return JSValue::encode(targetContext->specialSandbox()); + } + return JSValue::encode(sandbox); } @@ -1064,39 +1349,12 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_isContext, (JSGlobalObject * globalObject, Cal JSValue contextArg = callFrame->argument(0); VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - bool isContext; if (!contextArg || !contextArg.isObject()) { - isContext = false; return ERR::INVALID_ARG_TYPE(scope, globalObject, "object"_s, "object"_s, contextArg); - } else { - auto* zigGlobalObject = defaultGlobalObject(globalObject); - isContext = zigGlobalObject->vmModuleContextMap()->has(asObject(contextArg)); } - return JSValue::encode(jsBoolean(isContext)); + return JSValue::encode(jsBoolean(isContext(globalObject, contextArg))); } -// NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) -// { -// auto* obj = new (NotNull, allocateCell(vm)) NodeVMGlobalObject(vm, structure); -// obj->finishCreation(vm); -// return obj; -// } - -// void NodeVMGlobalObject::finishCreation(VM& vm, JSObject* context) -// { -// Base::finishCreation(vm); -// // We don't need to store the context anymore since we use proxies -// } - -// DEFINE_VISIT_CHILDREN(NodeVMGlobalObject); - -// template -// void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) -// { -// Base::visitChildren(cell, visitor); -// // auto* thisObject = jsCast(cell); -// // visitor.append(thisObject->m_proxyTarget); -// } const ClassInfo NodeVMGlobalObject::s_info = { "NodeVMGlobalObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMGlobalObject) }; bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot) @@ -1118,19 +1376,55 @@ bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObje return Base::deleteProperty(cell, globalObject, propertyName, slot); } +static JSInternalPromise* moduleLoaderImportModuleInner(NodeVMGlobalObject* globalObject, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); + + if (sourceOrigin.fetcher() == nullptr && sourceOrigin.url().isEmpty()) { + if (globalObject->dynamicImportCallback().isCallable()) { + return NodeVM::importModuleInner(globalObject, moduleName, parameters, sourceOrigin, globalObject->dynamicImportCallback(), JSValue {}); + } + + promise->reject(globalObject, createError(globalObject, ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, "A dynamic import callback was not specified."_s)); + return promise; + } + + // Default behavior copied from JSModuleLoader::importModule + auto moduleNameString = moduleName->value(globalObject); + RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope)); + + scope.release(); + promise->reject(globalObject, createError(globalObject, makeString("Could not import the module '"_s, moduleNameString.data, "'."_s))); + return promise; +} + +JSInternalPromise* NodeVMGlobalObject::moduleLoaderImportModule(JSGlobalObject* globalObject, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin) +{ + auto* nodeVmGlobalObject = static_cast(globalObject); + + if (JSInternalPromise* result = NodeVM::importModule(nodeVmGlobalObject, moduleName, parameters, sourceOrigin)) { + return result; + } + + return moduleLoaderImportModuleInner(nodeVmGlobalObject, moduleLoader, moduleName, parameters, sourceOrigin); +} + void NodeVMGlobalObject::getOwnPropertyNames(JSObject* cell, JSGlobalObject* globalObject, JSC::PropertyNameArray& propertyNames, JSC::DontEnumPropertiesMode mode) { auto* thisObject = jsCast(cell); - if (thisObject->m_sandbox) { - thisObject->m_sandbox->getOwnPropertyNames( - thisObject->m_sandbox.get(), - globalObject, - propertyNames, - mode); + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (thisObject->m_sandbox) [[likely]] { + thisObject->m_sandbox->getOwnPropertyNames(thisObject->m_sandbox.get(), globalObject, propertyNames, mode); + RETURN_IF_EXCEPTION(scope, ); } - Base::getOwnPropertyNames(cell, globalObject, propertyNames, mode); + RELEASE_AND_RETURN(scope, Base::getOwnPropertyNames(cell, globalObject, propertyNames, mode)); } JSC_DEFINE_HOST_FUNCTION(vmIsModuleNamespaceObject, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -1150,7 +1444,7 @@ JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject) defaultGlobalObject(globalObject)->NodeVMSourceTextModule(), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "createContext"_s)), - JSC::JSFunction::create(vm, globalObject, 0, "createContext"_s, vmModule_createContext, ImplementationVisibility::Public), 0); + JSC::JSFunction::create(vm, globalObject, 0, "createContext"_s, vmModule_createContext, ImplementationVisibility::Public, Intrinsic::NoIntrinsic, vmModule_createContext), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "isContext"_s)), JSC::JSFunction::create(vm, globalObject, 0, "isContext"_s, vmModule_isContext, ImplementationVisibility::Public), 0); @@ -1190,11 +1484,24 @@ JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject) obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kSynthetic"_s)), JSC::jsNumber(static_cast(NodeVMModule::Type::Synthetic)), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "DONT_CONTEXTIFY"_s)), + globalObject->m_nodeVMDontContextify.get(globalObject), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "USE_MAIN_CONTEXT_DEFAULT_LOADER"_s)), + globalObject->m_nodeVMUseMainContextDefaultLoader.get(globalObject), 0); return obj; } void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) { + globalObject->m_nodeVMDontContextify.initLater([](const LazyProperty::Initializer& init) { + init.set(JSC::Symbol::createWithDescription(init.vm, "vm_dont_contextify"_s)); + }); + globalObject->m_nodeVMUseMainContextDefaultLoader.initLater([](const LazyProperty::Initializer& init) { + init.set(JSC::Symbol::createWithDescription(init.vm, "vm_use_main_context_default_loader"_s)); + }); + globalObject->m_NodeVMScriptClassStructure.initLater( [](LazyClassStructure::Initializer& init) { auto prototype = NodeVMScript::createPrototype(init.vm, init.global); @@ -1238,6 +1545,11 @@ void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) [](const JSC::LazyProperty::Initializer& init) { init.set(createNodeVMGlobalObjectStructure(init.vm)); }); + + globalObject->m_cachedNodeVMSpecialSandboxStructure.initLater( + [](const JSC::LazyProperty::Initializer& init) { + init.set(NodeVMSpecialSandbox::createStructure(init.vm, init.owner, init.owner->objectPrototype())); // TODO(@heimskr): or maybe jsNull() for the prototype? + }); } BaseVMOptions::BaseVMOptions(String filename) @@ -1376,8 +1688,12 @@ bool BaseVMOptions::validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& return false; } -bool CompileFunctionOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) +bool CompileFunctionOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg, JSValue* importer) { + if (importer) { + *importer = jsUndefined(); + } + this->parsingContext = globalObject; bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg); RETURN_IF_EXCEPTION(scope, false); @@ -1448,8 +1764,10 @@ bool CompileFunctionOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& JSValue importModuleDynamicallyValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "importModuleDynamically"_s)); RETURN_IF_EXCEPTION(scope, {}); - if (importModuleDynamicallyValue && importModuleDynamicallyValue.isCallable()) { - this->importer = importModuleDynamicallyValue; + if (importModuleDynamicallyValue && (importModuleDynamicallyValue.isCallable() || isUseMainContextDefaultLoaderConstant(globalObject, importModuleDynamicallyValue))) { + if (importer) { + *importer = importModuleDynamicallyValue; + } any = true; } } diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 43e85b7981..797af6aa4f 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -25,65 +25,19 @@ RefPtr getBytecode(JSGlobalObject* globalObject, JSC::Modul bool extractCachedData(JSValue cachedDataValue, WTF::Vector& outCachedData); String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset); JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source); -NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox); bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr exception, ThrowScope& throwScope); -std::optional getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey); +std::optional getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey, JSValue* importer); NodeVMGlobalObject* getGlobalObjectFromContext(JSGlobalObject* globalObject, JSValue contextValue, bool canThrow); JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value); // For vm.compileFunction we need to return an anonymous function expression. This code is adapted from/inspired by JSC::constructFunction, which is used for function declarations. JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, CompileFunctionOptions&& options, JSC::SourceTaintedOrigin sourceTaintOrigin, JSC::JSScope* scope); JSInternalPromise* importModule(JSGlobalObject* globalObject, JSString* moduleNameValue, JSValue parameters, const SourceOrigin& sourceOrigin); +bool isContext(JSC::JSGlobalObject* globalObject, JSValue); +bool getContextArg(JSC::JSGlobalObject* globalObject, JSValue& contextArg); +bool isUseMainContextDefaultLoaderConstant(JSC::JSGlobalObject* globalObject, JSValue value); } // namespace NodeVM -// This class represents a sandboxed global object for vm contexts -class NodeVMGlobalObject final : public Bun::GlobalScope { - using Base = Bun::GlobalScope; - -public: - static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesGetOwnPropertySlot | JSC::OverridesPut | JSC::OverridesGetOwnPropertyNames | JSC::GetOwnPropertySlotMayBeWrongAboutDontEnum | JSC::ProhibitsPropertyCaching; - static constexpr JSC::DestructionMode needsDestruction = NeedsDestruction; - - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); - static NodeVMGlobalObject* create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options); - static Structure* createStructure(JSC::VM& vm, JSC::JSValue prototype); - - DECLARE_INFO; - DECLARE_VISIT_CHILDREN; - - void finishCreation(JSC::VM&, NodeVMContextOptions options); - static void destroy(JSCell* cell); - void setContextifiedObject(JSC::JSObject* contextifiedObject); - JSC::JSObject* contextifiedObject() const { return m_sandbox.get(); } - void clearContextifiedObject(); - void sigintReceived(); - - // Override property access to delegate to contextified object - static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&); - static bool put(JSCell*, JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&); - static void getOwnPropertyNames(JSObject*, JSGlobalObject*, JSC::PropertyNameArray&, JSC::DontEnumPropertiesMode); - static bool defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow); - static bool deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot); - -private: - NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure); - ~NodeVMGlobalObject(); - - // The contextified object that acts as the global proxy - mutable JSC::WriteBarrier m_sandbox; -}; - -// Helper functions to create vm contexts and run code -JSC::JSValue createNodeVMBinding(Zig::GlobalObject*); -Structure* createNodeVMGlobalObjectStructure(JSC::VM&); -void configureNodeVM(JSC::VM&, Zig::GlobalObject*); - -// VM module functions -JSC_DECLARE_HOST_FUNCTION(vmModule_createContext); -JSC_DECLARE_HOST_FUNCTION(vmModule_isContext); -JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext); -JSC_DECLARE_HOST_FUNCTION(vmModuleRunInThisContext); - class BaseVMOptions { public: String filename; @@ -106,18 +60,103 @@ public: WTF::Vector cachedData; JSGlobalObject* parsingContext = nullptr; JSValue contextExtensions {}; - JSValue importer {}; bool produceCachedData = false; using BaseVMOptions::BaseVMOptions; - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg); + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg, JSValue* importer); }; class NodeVMContextOptions final { public: bool allowStrings = true; bool allowWasm = true; + bool notContextified = false; }; +class NodeVMGlobalObject; + +class NodeVMSpecialSandbox final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesGetOwnPropertySlot; + + static NodeVMSpecialSandbox* create(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject); + + DECLARE_INFO; + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + static Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); + + static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&); + + NodeVMGlobalObject* parentGlobal() const { return m_parentGlobal.get(); } + +private: + WriteBarrier m_parentGlobal; + + NodeVMSpecialSandbox(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject); + + void finishCreation(VM&); +}; + +// This class represents a sandboxed global object for vm contexts +class NodeVMGlobalObject final : public Bun::GlobalScope { +public: + using Base = Bun::GlobalScope; + + static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesGetOwnPropertySlot | JSC::OverridesPut | JSC::OverridesGetOwnPropertyNames | JSC::GetOwnPropertySlotMayBeWrongAboutDontEnum | JSC::ProhibitsPropertyCaching; + static constexpr JSC::DestructionMode needsDestruction = NeedsDestruction; + + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + static NodeVMGlobalObject* create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options, JSValue importer); + static Structure* createStructure(JSC::VM& vm, JSC::JSValue prototype); + static const JSC::GlobalObjectMethodTable& globalObjectMethodTable(); + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + ~NodeVMGlobalObject(); + + void finishCreation(JSC::VM&); + static void destroy(JSCell* cell); + void setContextifiedObject(JSC::JSObject* contextifiedObject); + JSObject* contextifiedObject() const { return m_sandbox.get(); } + void clearContextifiedObject(); + void sigintReceived(); + bool isNotContextified() const { return m_contextOptions.notContextified; } + NodeVMSpecialSandbox* specialSandbox() const { return m_specialSandbox.get(); } + void setSpecialSandbox(NodeVMSpecialSandbox* sandbox) { m_specialSandbox.set(vm(), this, sandbox); } + JSValue dynamicImportCallback() const { return m_dynamicImportCallback.get(); } + + // Override property access to delegate to contextified object + static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&); + static bool put(JSCell*, JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&); + static void getOwnPropertyNames(JSObject*, JSGlobalObject*, JSC::PropertyNameArray&, JSC::DontEnumPropertiesMode); + static bool defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow); + static bool deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot); + static JSC::JSInternalPromise* moduleLoaderImportModule(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSString* moduleNameValue, JSC::JSValue parameters, const JSC::SourceOrigin&); + +private: + // The contextified object that acts as the global proxy + WriteBarrier m_sandbox; + // A special object used when the context is not contextified. + WriteBarrier m_specialSandbox; + WriteBarrier m_dynamicImportCallback; + NodeVMContextOptions m_contextOptions {}; + + NodeVMGlobalObject(VM& vm, Structure* structure, NodeVMContextOptions contextOptions, JSValue importer); +}; + +// Helper functions to create vm contexts and run code +JSC::JSValue createNodeVMBinding(Zig::GlobalObject*); +Structure* createNodeVMGlobalObjectStructure(JSC::VM&); +void configureNodeVM(JSC::VM&, Zig::GlobalObject*); + +// VM module functions +JSC_DECLARE_HOST_FUNCTION(vmModule_createContext); +JSC_DECLARE_HOST_FUNCTION(vmModule_isContext); +JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext); +JSC_DECLARE_HOST_FUNCTION(vmModuleRunInThisContext); + } // namespace Bun diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index 4b6ce40cb2..14097cac21 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -29,15 +29,20 @@ JSArray* NodeVMModuleRequest::toJS(JSGlobalObject* globalObject) const JSArray* array = JSC::constructEmptyArray(globalObject, nullptr, 2); RETURN_IF_EXCEPTION(scope, {}); + array->putDirectIndex(globalObject, 0, JSC::jsString(globalObject->vm(), m_specifier)); + RETURN_IF_EXCEPTION(scope, {}); JSObject* attributes = JSC::constructEmptyObject(globalObject); RETURN_IF_EXCEPTION(scope, {}); + for (const auto& [key, value] : m_importAttributes) { attributes->putDirect(globalObject->vm(), JSC::Identifier::fromString(globalObject->vm(), key), JSC::jsString(globalObject->vm(), value), PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete); + RETURN_IF_EXCEPTION(scope, {}); } array->putDirectIndex(globalObject, 1, attributes); + RETURN_IF_EXCEPTION(scope, {}); return array; } @@ -73,6 +78,7 @@ JSValue NodeVMModule::evaluate(JSGlobalObject* globalObject, uint32_t timeout, b JSValue result {}; NodeVMGlobalObject* nodeVmGlobalObject = NodeVM::getGlobalObjectFromContext(globalObject, m_context.get(), false); + RETURN_IF_EXCEPTION(scope, {}); if (nodeVmGlobalObject) { globalObject = nodeVmGlobalObject; @@ -82,13 +88,12 @@ JSValue NodeVMModule::evaluate(JSGlobalObject* globalObject, uint32_t timeout, b if (sourceTextThis) { status(Status::Evaluating); evaluateDependencies(globalObject, record, timeout, breakOnSigint); + RETURN_IF_EXCEPTION(scope, ); sourceTextThis->initializeImportMeta(globalObject); } else if (syntheticThis) { syntheticThis->evaluate(globalObject); } - if (scope.exception()) [[unlikely]] { - return; - } + RETURN_IF_EXCEPTION(scope, ); result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast(JSGenerator::ResumeMode::NormalMode))); }; @@ -206,11 +211,11 @@ NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObjec JSValue disambiguator = args.at(2); if (disambiguator.isString()) { - return NodeVMSourceTextModule::create(vm, globalObject, args); + RELEASE_AND_RETURN(scope, NodeVMSourceTextModule::create(vm, globalObject, args)); } if (disambiguator.inherits(JSArray::info())) { - return NodeVMSyntheticModule::create(vm, globalObject, args); + RELEASE_AND_RETURN(scope, NodeVMSyntheticModule::create(vm, globalObject, args)); } throwArgumentTypeError(*globalObject, scope, 2, "sourceText or syntheticExportNames"_s, "Module"_s, "Module"_s, "string or array"_s); @@ -227,11 +232,14 @@ JSModuleNamespaceObject* NodeVMModule::namespaceObject(JSC::JSGlobalObject* glob if (auto* thisObject = jsDynamicCast(this)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - object = thisObject->moduleRecord(globalObject)->getModuleNamespace(globalObject); + AbstractModuleRecord* record = thisObject->moduleRecord(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + object = record->getModuleNamespace(globalObject); RETURN_IF_EXCEPTION(scope, {}); if (object) { namespaceObject(vm, object); } + RETURN_IF_EXCEPTION(scope, {}); } else { RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("NodeVMModule::namespaceObject called on an unsupported module type (%s)", classInfo()->className.characters()); } @@ -333,7 +341,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * glob auto scope = DECLARE_THROW_SCOPE(vm); if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return JSValue::encode(thisObject->namespaceObject(globalObject)); + RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->namespaceObject(globalObject))); } throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s); @@ -366,6 +374,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * if (auto* sourceTextModule = jsDynamicCast(callFrame->thisValue())) { sourceTextModule->ensureModuleRecord(globalObject); + RETURN_IF_EXCEPTION(scope, {}); } const WTF::Vector& requests = thisObject->moduleRequests(); @@ -399,11 +408,11 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalOb } if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return JSValue::encode(thisObject->evaluate(globalObject, timeout, breakOnSigint)); - } else { - throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s); - return {}; + RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->evaluate(globalObject, timeout, breakOnSigint))); } + + throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s); + return {}; } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) @@ -423,14 +432,11 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject } if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return JSValue::encode(thisObject->link(globalObject, specifiers, moduleNatives, callFrame->argument(2))); - // return thisObject->link(globalObject, linker); - // } else if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - // return thisObject->link(globalObject, specifiers, moduleNatives); - } else { - throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s); - return {}; + RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->link(globalObject, specifiers, moduleNatives, callFrame->argument(2)))); } + + throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule"_s); + return {}; } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) @@ -439,11 +445,11 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globa auto scope = DECLARE_THROW_SCOPE(vm); if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return JSValue::encode(thisObject->instantiate(globalObject)); + RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->instantiate(globalObject))); } if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return JSValue::encode(thisObject->instantiate(globalObject)); + RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->instantiate(globalObject))); } throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s); @@ -455,7 +461,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalO VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - if (auto* thisObject = jsCast(callFrame->thisValue())) { + if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { JSValue nameValue = callFrame->argument(0); if (!nameValue.isString()) { Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "name"_str, "string"_s, nameValue); @@ -478,7 +484,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * auto scope = DECLARE_THROW_SCOPE(vm); if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return JSValue::encode(thisObject->cachedData(globalObject)); + RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->cachedData(globalObject))); } throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule"_s); @@ -517,6 +523,7 @@ constructModule(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT ArgList args(callFrame); NodeVMModule* module = NodeVMModule::create(vm, globalObject, args); + RETURN_IF_EXCEPTION(scope, {}); return JSValue::encode(module); } diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 42726c8275..077629b910 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -9,6 +9,7 @@ #include "JavaScriptCore/ProgramCodeBlock.h" #include "JavaScriptCore/SourceCodeKey.h" +#include "NodeVMScriptFetcher.h" #include "../vm/SigintWatcher.h" #include @@ -16,8 +17,12 @@ namespace Bun { using namespace NodeVM; -bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) +bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg, JSValue* importer) { + if (importer) { + *importer = jsUndefined(); + } + bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg); RETURN_IF_EXCEPTION(scope, false); @@ -64,9 +69,16 @@ bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC:: JSValue importModuleDynamicallyValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "importModuleDynamically"_s)); RETURN_IF_EXCEPTION(scope, {}); - if (importModuleDynamicallyValue && importModuleDynamicallyValue.isCallable()) { - this->importer = importModuleDynamicallyValue; - any = true; + if (importModuleDynamicallyValue) { + if ((importModuleDynamicallyValue.isCallable() || isUseMainContextDefaultLoaderConstant(globalObject, importModuleDynamicallyValue))) { + if (importer) { + *importer = importModuleDynamicallyValue; + } + any = true; + } else if (!importModuleDynamicallyValue.isUndefined()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.importModuleDynamically"_s, "function"_s, importModuleDynamicallyValue); + return false; + } } } @@ -80,15 +92,22 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT auto scope = DECLARE_THROW_SCOPE(vm); ArgList args(callFrame); JSValue sourceArg = args.at(0); - String sourceString = sourceArg.isUndefined() ? emptyString() : sourceArg.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, encodedJSUndefined()); + String sourceString; + if (sourceArg.isUndefined()) { + sourceString = emptyString(); + } else { + sourceString = sourceArg.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, encodedJSUndefined()); + } JSValue optionsArg = args.at(1); ScriptOptions options(""_s); + JSValue importer; + if (optionsArg.isString()) { options.filename = optionsArg.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); - } else if (!options.fromJS(globalObject, vm, scope, optionsArg)) { + } else if (!options.fromJS(globalObject, vm, scope, optionsArg, &importer)) { RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); } @@ -108,13 +127,18 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT scope.release(); } - SourceCode source = makeSource(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), JSC::SourceTaintedOrigin::Untainted, options.filename, TextPosition(options.lineOffset, options.columnOffset)); + RefPtr fetcher(NodeVMScriptFetcher::create(vm, importer, jsUndefined())); + + SourceCode source = makeSource(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename), *fetcher), JSC::SourceTaintedOrigin::Untainted, options.filename, TextPosition(options.lineOffset, options.columnOffset)); RETURN_IF_EXCEPTION(scope, {}); const bool produceCachedData = options.produceCachedData; auto filename = options.filename; NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, WTFMove(source), WTFMove(options)); + RETURN_IF_EXCEPTION(scope, {}); + + fetcher->owner(vm, script); WTF::Vector& cachedData = script->cachedData(); @@ -139,6 +163,7 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT // JSC::ProgramCodeBlock::create() requires GC to be deferred. DeferGC deferGC(vm); codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); + RETURN_IF_EXCEPTION(scope, {}); } JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); if (compilationResult != JSC::CompilationResult::CompilationFailed) { @@ -199,6 +224,9 @@ JSC::JSUint8Array* NodeVMScript::getBytecodeBuffer() std::span bytes = m_cachedBytecode->span(); m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject(), bytes)); + if (!m_cachedBytecodeBuffer) { + return nullptr; + } } ASSERT(m_cachedBytecodeBuffer); @@ -333,6 +361,8 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM run(); } + RETURN_IF_EXCEPTION(scope, {}); + if (options.timeout) { vm.watchdog()->setTimeLimit(WTF::Seconds::fromMilliseconds(*oldLimit)); } @@ -351,7 +381,8 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM return {}; } - return JSValue::encode(result); + RETURN_IF_EXCEPTION(scope, {}); + RELEASE_AND_RETURN(scope, JSValue::encode(result)); } JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -414,7 +445,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, } RETURN_IF_EXCEPTION(scope, {}); - return JSValue::encode(result); + RELEASE_AND_RETURN(scope, JSValue::encode(result)); } JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) @@ -433,7 +464,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, return encodedJSUndefined(); } - return JSValue::encode(jsString(vm, url)); + RELEASE_AND_RETURN(scope, JSValue::encode(jsString(vm, url))); } JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) @@ -447,10 +478,10 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JS } if (auto* buffer = script->getBytecodeBuffer()) { - return JSValue::encode(buffer); + RELEASE_AND_RETURN(scope, JSValue::encode(buffer)); } - return JSValue::encode(jsUndefined()); + RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined())); } JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) @@ -463,7 +494,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalOb return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } - return JSValue::encode(jsBoolean(script->cachedDataProduced())); + RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(script->cachedDataProduced()))); } JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) @@ -478,11 +509,11 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalOb switch (script->cachedDataRejected()) { case TriState::True: - return JSValue::encode(jsBoolean(true)); + RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(true))); case TriState::False: - return JSValue::encode(jsBoolean(false)); + RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(false))); default: - return JSValue::encode(jsUndefined()); + RELEASE_AND_RETURN(scope, encodedJSUndefined()); } } @@ -498,7 +529,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, } const JSC::SourceCode& source = script->source(); - return createCachedData(globalObject, source); + RELEASE_AND_RETURN(scope, createCachedData(globalObject, source)); } JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -519,7 +550,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, Cal JSObject* context = asObject(contextArg); ASSERT(nodeVmGlobalObject != nullptr); - return runInContext(nodeVmGlobalObject, script, context, args.at(1)); + RELEASE_AND_RETURN(scope, runInContext(nodeVmGlobalObject, script, context, args.at(1))); } JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -527,8 +558,6 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, VM& vm = JSC::getVM(globalObject); NodeVMScript* script = jsDynamicCast(callFrame->thisValue()); JSValue contextObjectValue = callFrame->argument(0); - // TODO: options - // JSValue optionsObjectValue = callFrame->argument(1); auto scope = DECLARE_THROW_SCOPE(vm); if (!script) { @@ -536,24 +565,36 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, return {}; } - if (contextObjectValue.isUndefined()) { - contextObjectValue = JSC::constructEmptyObject(globalObject); - } + bool notContextified = NodeVM::getContextArg(globalObject, contextObjectValue); if (!contextObjectValue || !contextObjectValue.isObject()) [[unlikely]] { throwTypeError(globalObject, scope, "Context must be an object"_s); return {}; } - // we don't care about options for now - // TODO: options - // bool didThrow = false; + JSValue contextOptionsArg = callFrame->argument(1); + NodeVMContextOptions contextOptions {}; + JSValue importer; - auto* zigGlobal = defaultGlobalObject(globalObject); + if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, contextOptionsArg, contextOptions, "contextCodeGeneration", &importer)) { + return *encodedException; + } + + contextOptions.notContextified = notContextified; + + auto* zigGlobalObject = defaultGlobalObject(globalObject); JSObject* context = asObject(contextObjectValue); auto* targetContext = NodeVMGlobalObject::create(vm, - zigGlobal->NodeVMGlobalObjectStructure(), - {}); + zigGlobalObject->NodeVMGlobalObjectStructure(), + contextOptions, importer); + RETURN_IF_EXCEPTION(scope, {}); + + if (notContextified) { + auto* specialSandbox = NodeVMSpecialSandbox::create(vm, zigGlobalObject->NodeVMSpecialSandboxStructure(), targetContext); + RETURN_IF_EXCEPTION(scope, {}); + targetContext->setSpecialSandbox(specialSandbox); + RELEASE_AND_RETURN(scope, runInContext(targetContext, script, targetContext->specialSandbox(), callFrame->argument(1))); + } RELEASE_AND_RETURN(scope, runInContext(targetContext, script, context, callFrame->argument(1))); } diff --git a/src/bun.js/bindings/NodeVMScript.h b/src/bun.js/bindings/NodeVMScript.h index b7e7b2dec4..5637ae939a 100644 --- a/src/bun.js/bindings/NodeVMScript.h +++ b/src/bun.js/bindings/NodeVMScript.h @@ -10,12 +10,11 @@ class ScriptOptions : public BaseVMOptions { public: WTF::Vector cachedData; std::optional timeout = std::nullopt; - JSValue importer {}; bool produceCachedData = false; using BaseVMOptions::BaseVMOptions; - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg); + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg, JSValue* importer); }; class NodeVMScriptConstructor final : public JSC::InternalFunction { diff --git a/src/bun.js/bindings/NodeVMScriptFetcher.h b/src/bun.js/bindings/NodeVMScriptFetcher.h index 0ec7416e06..b8676b2445 100644 --- a/src/bun.js/bindings/NodeVMScriptFetcher.h +++ b/src/bun.js/bindings/NodeVMScriptFetcher.h @@ -3,27 +3,39 @@ #include "root.h" #include +#include namespace Bun { // The presence of this class in a JSFunction's sourceOrigin indicates that the function was compiled by Bun's node:vm implementation. class NodeVMScriptFetcher : public JSC::ScriptFetcher { public: - static Ref create(JSC::VM& vm, JSC::JSValue dynamicImportCallback) { return adoptRef(*new NodeVMScriptFetcher(vm, dynamicImportCallback)); } + static Ref create(JSC::VM& vm, JSC::JSValue dynamicImportCallback, JSC::JSValue owner) { return adoptRef(*new NodeVMScriptFetcher(vm, dynamicImportCallback, owner)); } Type fetcherType() const final { return Type::NodeVM; } JSC::JSValue dynamicImportCallback() const { return m_dynamicImportCallback.get(); } - JSC::JSFunction* owner() const { return m_owner.get(); } - void owner(JSC::VM& vm, JSC::JSFunction* value) { m_owner.set(vm, value); } + JSC::JSValue owner() const { return m_owner.get(); } + void owner(JSC::VM& vm, JSC::JSValue value) { m_owner.set(vm, value); } + + bool isUsingDefaultLoader() const { return m_isUsingDefaultLoader; } + auto temporarilyUseDefaultLoader() + { + m_isUsingDefaultLoader = true; + return makeScopeExit([this] { + m_isUsingDefaultLoader = false; + }); + } private: JSC::Strong m_dynamicImportCallback; - JSC::Strong m_owner; + JSC::Strong m_owner; + bool m_isUsingDefaultLoader = false; - NodeVMScriptFetcher(JSC::VM& vm, JSC::JSValue dynamicImportCallback) + NodeVMScriptFetcher(JSC::VM& vm, JSC::JSValue dynamicImportCallback, JSC::JSValue owner) : m_dynamicImportCallback(vm, dynamicImportCallback) + , m_owner(vm, owner) { } }; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index fb72e3bb84..8af22d59a5 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -1,3 +1,4 @@ +#include "NodeVMScriptFetcher.h" #include "NodeVMSourceTextModule.h" #include "NodeVMSyntheticModule.h" @@ -77,16 +78,35 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g return nullptr; } - uint32_t lineOffset = lineOffsetValue.toUInt32(globalObject); - uint32_t columnOffset = columnOffsetValue.toUInt32(globalObject); + JSValue dynamicImportCallback = args.at(8); + if (!dynamicImportCallback.isUndefined() && !dynamicImportCallback.isCallable()) { + throwArgumentTypeError(*globalObject, scope, 8, "dynamicImportCallback"_s, "Module"_s, "Module"_s, "function"_s); + return nullptr; + } - Ref sourceProvider = StringSourceProvider::create(sourceTextValue.toWTFString(globalObject), SourceOrigin {}, String {}, SourceTaintedOrigin::Untainted, + uint32_t lineOffset = lineOffsetValue.toUInt32(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + uint32_t columnOffset = columnOffsetValue.toUInt32(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + + RefPtr fetcher(NodeVMScriptFetcher::create(vm, dynamicImportCallback, moduleWrapper)); + RETURN_IF_EXCEPTION(scope, nullptr); + + SourceOrigin sourceOrigin { {}, *fetcher }; + + WTF::String sourceText = sourceTextValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + + Ref sourceProvider = StringSourceProvider::create(WTFMove(sourceText), sourceOrigin, String {}, SourceTaintedOrigin::Untainted, TextPosition { OrdinalNumber::fromZeroBasedInt(lineOffset), OrdinalNumber::fromZeroBasedInt(columnOffset) }, SourceProviderSourceType::Module); SourceCode sourceCode(WTFMove(sourceProvider), lineOffset, columnOffset); auto* zigGlobalObject = defaultGlobalObject(globalObject); - NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject), contextValue, WTFMove(sourceCode), moduleWrapper); + WTF::String identifier = identifierValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), WTFMove(identifier), contextValue, WTFMove(sourceCode), moduleWrapper); + RETURN_IF_EXCEPTION(scope, nullptr); ptr->finishCreation(vm); if (!initializeImportMeta.isUndefined()) { @@ -111,7 +131,9 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; SourceCodeKey key(ptr->sourceCode(), {}, SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, DerivedContextType::None, EvalContextType::None, false, {}, std::nullopt); Ref cachedBytecode = CachedBytecode::create(std::span(cachedData), nullptr, {}); + RETURN_IF_EXCEPTION(scope, nullptr); UnlinkedModuleProgramCodeBlock* unlinkedBlock = decodeCodeBlock(vm, key, WTFMove(cachedBytecode)); + RETURN_IF_EXCEPTION(scope, nullptr); if (unlinkedBlock) { JSScope* jsScope = globalObject->globalScope(); @@ -120,9 +142,11 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g // JSC::ProgramCodeBlock::create() requires GC to be deferred. DeferGC deferGC(vm); codeBlock = ModuleProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); + RETURN_IF_EXCEPTION(scope, nullptr); } if (codeBlock) { CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); + RETURN_IF_EXCEPTION(scope, nullptr); if (compilationResult != CompilationResult::CompilationFailed) { executable->installCode(codeBlock); return ptr; @@ -184,7 +208,9 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) const auto& requests = moduleRecord->requestedModules(); if (requests.isEmpty()) { - return constructEmptyArray(globalObject, nullptr, 0); + JSArray* requestsArray = constructEmptyArray(globalObject, nullptr, 0); + RETURN_IF_EXCEPTION(scope, {}); + return requestsArray; } JSArray* requestsArray = constructEmptyArray(globalObject, nullptr, requests.size()); @@ -312,26 +338,35 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec if (length != 0) { for (unsigned i = 0; i < length; i++) { JSValue specifierValue = specifiers->getDirectIndex(globalObject, i); + RETURN_IF_EXCEPTION(scope, {}); JSValue moduleNativeValue = moduleNatives->getDirectIndex(globalObject, i); + RETURN_IF_EXCEPTION(scope, {}); ASSERT(specifierValue.isString()); ASSERT(moduleNativeValue.isObject()); WTF::String specifier = specifierValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); JSObject* moduleNative = moduleNativeValue.getObject(); + RETURN_IF_EXCEPTION(scope, {}); AbstractModuleRecord* resolvedRecord = jsCast(moduleNative)->moduleRecord(globalObject); + RETURN_IF_EXCEPTION(scope, {}); record->setImportedModule(globalObject, Identifier::fromString(vm, specifier), resolvedRecord); + RETURN_IF_EXCEPTION(scope, {}); m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); + RETURN_IF_EXCEPTION(scope, {}); } } - if (NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false)) { + NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false); + RETURN_IF_EXCEPTION(scope, {}); + + if (nodeVmGlobalObject) { globalObject = nodeVmGlobalObject; } Synchronousness sync = record->link(globalObject, scriptFetcher); - RETURN_IF_EXCEPTION(scope, {}); if (sync == Synchronousness::Async) { @@ -355,6 +390,7 @@ RefPtr NodeVMSourceTextModule::bytecode(JSGlobalObject* globalOb if (!m_bytecode) { if (!m_cachedExecutable) { ModuleProgramExecutable* executable = ModuleProgramExecutable::tryCreate(globalObject, m_sourceCode); + RETURN_IF_EXCEPTION(scope, nullptr); if (!executable) { if (!scope.exception()) { throwSyntaxError(globalObject, scope, "Failed to create cached executable"_s); @@ -364,6 +400,7 @@ RefPtr NodeVMSourceTextModule::bytecode(JSGlobalObject* globalOb m_cachedExecutable.set(vm, this, executable); } m_bytecode = getBytecode(globalObject, m_cachedExecutable.get(), m_sourceCode); + RETURN_IF_EXCEPTION(scope, nullptr); } return m_bytecode; @@ -371,10 +408,16 @@ RefPtr NodeVMSourceTextModule::bytecode(JSGlobalObject* globalOb JSUint8Array* NodeVMSourceTextModule::cachedData(JSGlobalObject* globalObject) { + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (!m_cachedBytecodeBuffer) { RefPtr cachedBytecode = bytecode(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); std::span bytes = cachedBytecode->span(); - m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject, bytes)); + JSUint8Array* buffer = WebCore::createBuffer(globalObject, bytes); + RETURN_IF_EXCEPTION(scope, nullptr); + m_cachedBytecodeBuffer.set(vm, this, buffer); } return m_cachedBytecodeBuffer.get(); @@ -389,7 +432,11 @@ void NodeVMSourceTextModule::initializeImportMeta(JSGlobalObject* globalObject) JSModuleEnvironment* moduleEnvironment = m_moduleRecord->moduleEnvironmentMayBeNull(); ASSERT(moduleEnvironment != nullptr); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue metaValue = moduleEnvironment->get(globalObject, globalObject->vm().propertyNames->builtinNames().metaPrivateName()); + RETURN_IF_EXCEPTION(scope, ); ASSERT(metaValue); ASSERT(metaValue.isObject()); @@ -400,6 +447,7 @@ void NodeVMSourceTextModule::initializeImportMeta(JSGlobalObject* globalObject) args.append(m_moduleWrapper.get()); JSC::call(globalObject, m_initializeImportMeta.get(), callData, jsUndefined(), args); + RETURN_IF_EXCEPTION(scope, ); } JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) diff --git a/src/bun.js/bindings/NodeVMSyntheticModule.cpp b/src/bun.js/bindings/NodeVMSyntheticModule.cpp index 5a178a18cf..3cd49018a1 100644 --- a/src/bun.js/bindings/NodeVMSyntheticModule.cpp +++ b/src/bun.js/bindings/NodeVMSyntheticModule.cpp @@ -67,15 +67,20 @@ NodeVMSyntheticModule* NodeVMSyntheticModule::create(VM& vm, JSGlobalObject* glo WTF::HashSet exportNames; for (unsigned i = 0; i < exportNamesArray->getArrayLength(); i++) { JSValue exportNameValue = exportNamesArray->getIndex(globalObject, i); + RETURN_IF_EXCEPTION(scope, nullptr); if (!exportNameValue.isString()) { throwArgumentTypeError(*globalObject, scope, 2, "exportNames"_s, "Module"_s, "Module"_s, "string[]"_s); + return nullptr; } exportNames.addVoid(exportNameValue.toWTFString(globalObject)); + RETURN_IF_EXCEPTION(scope, nullptr); } auto* zigGlobalObject = defaultGlobalObject(globalObject); auto* structure = zigGlobalObject->NodeVMSyntheticModuleStructure(); - auto* ptr = new (NotNull, allocateCell(vm)) NodeVMSyntheticModule(vm, structure, identifierValue.toWTFString(globalObject), contextValue, moduleWrapperValue, WTFMove(exportNames), syntheticEvaluationStepsValue); + WTF::String identifier = identifierValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + auto* ptr = new (NotNull, allocateCell(vm)) NodeVMSyntheticModule(vm, structure, WTFMove(identifier), contextValue, moduleWrapperValue, WTFMove(exportNames), syntheticEvaluationStepsValue); ptr->finishCreation(vm); return ptr; } @@ -204,8 +209,11 @@ void NodeVMSyntheticModule::setExport(JSGlobalObject* globalObject, WTF::String } ensureModuleRecord(globalObject); + RETURN_IF_EXCEPTION(scope, ); JSModuleNamespaceObject* namespaceObject = m_moduleRecord->getModuleNamespace(globalObject, false); + RETURN_IF_EXCEPTION(scope, ); namespaceObject->overrideExportValue(globalObject, Identifier::fromString(vm, exportName), value); + RETURN_IF_EXCEPTION(scope, ); } JSObject* NodeVMSyntheticModule::createPrototype(VM& vm, JSGlobalObject* globalObject) diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index d69a6da4f6..e518145214 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -4136,22 +4136,26 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderImportModule(JSGlobalObject* j { auto* globalObject = static_cast(jsGlobalObject); - if (JSC::JSInternalPromise* result = NodeVM::importModule(globalObject, moduleNameValue, parameters, sourceOrigin)) { - return result; + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + { + JSC::JSInternalPromise* result = NodeVM::importModule(globalObject, moduleNameValue, parameters, sourceOrigin); + RETURN_IF_EXCEPTION(scope, nullptr); + if (result) { + return result; + } } - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); JSC::Identifier resolvedIdentifier; auto moduleName = moduleNameValue->value(globalObject); - RETURN_IF_EXCEPTION(scope, {}); + RETURN_IF_EXCEPTION(scope, nullptr); if (globalObject->onLoadPlugins.hasVirtualModules()) { if (auto resolution = globalObject->onLoadPlugins.resolveVirtualModule(moduleName, sourceOrigin.url().protocolIsFile() ? sourceOrigin.url().fileSystemPath() : String())) { resolvedIdentifier = JSC::Identifier::fromString(vm, resolution.value()); - auto result = JSC::importModule(globalObject, resolvedIdentifier, - JSC::jsUndefined(), parameters, JSC::jsUndefined()); + auto result = JSC::importModule(globalObject, resolvedIdentifier, JSC::jsUndefined(), parameters, JSC::jsUndefined()); if (scope.exception()) [[unlikely]] { auto* promise = JSC::JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); return promise->rejectWithCaughtException(globalObject, scope); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 3bf94799d4..9420108724 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -289,6 +289,7 @@ public: JSC::JSFunction* requireESMFromHijackedExtension() const { return m_commonJSRequireESMFromHijackedExtensionFunction.getInitializedOnMainThread(this); } Structure* NodeVMGlobalObjectStructure() const { return m_cachedNodeVMGlobalObjectStructure.getInitializedOnMainThread(this); } + Structure* NodeVMSpecialSandboxStructure() const { return m_cachedNodeVMSpecialSandboxStructure.getInitializedOnMainThread(this); } Structure* globalProxyStructure() const { return m_cachedGlobalProxyStructure.getInitializedOnMainThread(this); } JSObject* lazyTestModuleObject() const { return m_lazyTestModuleObject.getInitializedOnMainThread(this); } JSObject* lazyPreloadTestModuleObject() const { return m_lazyPreloadTestModuleObject.getInitializedOnMainThread(this); } @@ -576,6 +577,7 @@ public: V(private, LazyPropertyOfGlobalObject, m_lazyPreloadTestModuleObject) \ V(public, LazyPropertyOfGlobalObject, m_testMatcherUtilsObject) \ V(public, LazyPropertyOfGlobalObject, m_cachedNodeVMGlobalObjectStructure) \ + V(public, LazyPropertyOfGlobalObject, m_cachedNodeVMSpecialSandboxStructure) \ V(private, LazyPropertyOfGlobalObject, m_cachedGlobalProxyStructure) \ V(private, LazyPropertyOfGlobalObject, m_commonJSModuleObjectStructure) \ V(private, LazyPropertyOfGlobalObject, m_JSSocketAddressDTOStructure) \ @@ -617,7 +619,9 @@ public: V(public, LazyPropertyOfGlobalObject, m_statValues) \ V(public, LazyPropertyOfGlobalObject, m_bigintStatValues) \ V(public, LazyPropertyOfGlobalObject, m_statFsValues) \ - V(public, LazyPropertyOfGlobalObject, m_bigintStatFsValues) + V(public, LazyPropertyOfGlobalObject, m_bigintStatFsValues) \ + V(public, LazyPropertyOfGlobalObject, m_nodeVMDontContextify) \ + V(public, LazyPropertyOfGlobalObject, m_nodeVMUseMainContextDefaultLoader) #define DECLARE_GLOBALOBJECT_GC_MEMBER(visibility, T, name) \ visibility: \ diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 66e29275fd..427954d53c 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -36,6 +36,7 @@ public: std::unique_ptr m_clientSubspaceForRequireResolveFunction; std::unique_ptr m_clientSubspaceForBundlerPlugin; std::unique_ptr m_clientSubspaceForNodeVMGlobalObject; + std::unique_ptr m_clientSubspaceForNodeVMSpecialSandbox; std::unique_ptr m_clientSubspaceForNodeVMScript; std::unique_ptr m_clientSubspaceForNodeVMSourceTextModule; std::unique_ptr m_clientSubspaceForNodeVMSyntheticModule; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 1de228dd13..12a275ca46 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -36,6 +36,7 @@ public: std::unique_ptr m_subspaceForRequireResolveFunction; std::unique_ptr m_subspaceForBundlerPlugin; std::unique_ptr m_subspaceForNodeVMGlobalObject; + std::unique_ptr m_subspaceForNodeVMSpecialSandbox; std::unique_ptr m_subspaceForNodeVMScript; std::unique_ptr m_subspaceForNodeVMSourceTextModule; std::unique_ptr m_subspaceForNodeVMSyntheticModule; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 86b64c9b1e..19da675fcb 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -43,14 +43,14 @@ const { Module: ModuleNative, createContext, isContext, - // runInNewContext: moduleRunInNewContext, - // runInThisContext: moduleRunInThisContext, compileFunction, isModuleNamespaceObject, kUnlinked, kLinked, kEvaluated, kErrored, + DONT_CONTEXTIFY, + USE_MAIN_CONTEXT_DEFAULT_LOADER, } = vm; function runInContext(code, context, options) { @@ -88,7 +88,7 @@ function measureMemory() { } function validateContext(contextifiedObject) { - if (!isContext(contextifiedObject) && contextifiedObject !== constants.DONT_CONTEXTIFY) { + if (contextifiedObject !== constants.DONT_CONTEXTIFY && !isContext(contextifiedObject)) { const error = new Error('The "contextifiedObject" argument must be an vm.Context'); error.code = "ERR_INVALID_ARG_TYPE"; error.name = "TypeError"; @@ -143,7 +143,6 @@ class Module { }); } - let registry: any = { __proto__: null }; if (sourceText !== undefined) { this[kNative] = new ModuleNative( identifier, @@ -154,19 +153,8 @@ class Module { options.cachedData, options.initializeImportMeta, this, + options.importModuleDynamically ? importModuleDynamicallyWrap(options.importModuleDynamically) : undefined, ); - registry = { - __proto__: null, - initializeImportMeta: options.initializeImportMeta, - importModuleDynamically: options.importModuleDynamically - ? importModuleDynamicallyWrap(options.importModuleDynamically) - : undefined, - }; - // This will take precedence over the referrer as the object being - // passed into the callbacks. - registry.callbackReferrer = this; - // const { registerModule } = require("internal/modules/esm/utils"); - // registerModule(this[kNative], registry); } else { $assert(syntheticEvaluationSteps); this[kNative] = new ModuleNative(identifier, context, syntheticExportNames, syntheticEvaluationSteps, this); @@ -442,8 +430,8 @@ class SyntheticModule extends Module { const constants = { __proto__: null, - USE_MAIN_CONTEXT_DEFAULT_LOADER: Symbol("vm_dynamic_import_main_context_default"), - DONT_CONTEXTIFY: Symbol("vm_context_no_contextify"), + USE_MAIN_CONTEXT_DEFAULT_LOADER, + DONT_CONTEXTIFY, }; function isModule(object) { @@ -452,7 +440,7 @@ function isModule(object) { function importModuleDynamicallyWrap(importModuleDynamically) { const importModuleDynamicallyWrapper = async (...args) => { - const m: any = importModuleDynamically.$apply(this, args); + const m: any = await importModuleDynamically.$apply(this, args); if (isModuleNamespaceObject(m)) { return m; } diff --git a/test/js/node/test/parallel/test-vm-context-dont-contextify.js b/test/js/node/test/parallel/test-vm-context-dont-contextify.js new file mode 100644 index 0000000000..d75fc1438d --- /dev/null +++ b/test/js/node/test/parallel/test-vm-context-dont-contextify.js @@ -0,0 +1,185 @@ +'use strict'; + +// Check vm.constants.DONT_CONTEXTIFY works. + +const common = require('../common'); + +const assert = require('assert'); +const vm = require('vm'); +const fixtures = require('../common/fixtures'); + +{ + // Check identity of the returned object. + const context = vm.createContext(vm.constants.DONT_CONTEXTIFY); + // The globalThis in the new context should be reference equal to the returned object. + assert.strictEqual(vm.runInContext('globalThis', context), context); + assert(vm.isContext(context)); + assert.strictEqual(typeof context.Array, 'function'); // Can access builtins directly. + assert.deepStrictEqual(Object.keys(context), []); // Properties on the global proxy are not enumerable +} + +{ + // Check that vm.createContext can return the original context if re-passed. + const context = vm.createContext(vm.constants.DONT_CONTEXTIFY); + const context2 = new vm.createContext(context); + assert.strictEqual(context, context2); +} + +{ + // Check that the context is vanilla and that Script.runInContext works. + const context = vm.createContext(vm.constants.DONT_CONTEXTIFY); + const result = + new vm.Script('globalThis.hey = 1; Object.freeze(globalThis); globalThis.process') + .runInContext(context); + assert.strictEqual(globalThis.hey, undefined); // Should not leak into current context. + assert.strictEqual(result, undefined); // Vanilla context has no Node.js globals +} + +{ + // Check Script.runInNewContext works. + const result = + new vm.Script('globalThis.hey = 1; Object.freeze(globalThis); globalThis.process') + .runInNewContext(vm.constants.DONT_CONTEXTIFY); + assert.strictEqual(globalThis.hey, undefined); // Should not leak into current context. + assert.strictEqual(result, undefined); // Vanilla context has no Node.js globals +} + +{ + // Check that vm.runInNewContext() works + const result = vm.runInNewContext( + 'globalThis.hey = 1; Object.freeze(globalThis); globalThis.process', + vm.constants.DONT_CONTEXTIFY); + assert.strictEqual(globalThis.hey, undefined); // Should not leak into current context. + assert.strictEqual(result, undefined); // Vanilla context has no Node.js globals +} + +{ + // Check that the global object of vanilla contexts work as expected. + const context = vm.createContext(vm.constants.DONT_CONTEXTIFY); + + // Check mutation via globalThis. + vm.runInContext('globalThis.foo = 1;', context); + assert.strictEqual(globalThis.foo, undefined); // Should not pollute the current context. + assert.strictEqual(context.foo, 1); + assert.strictEqual(vm.runInContext('globalThis.foo', context), 1); + assert.strictEqual(vm.runInContext('foo', context), 1); + + // Check mutation from outside. + context.foo = 2; + assert.strictEqual(context.foo, 2); + assert.strictEqual(vm.runInContext('globalThis.foo', context), 2); + assert.strictEqual(vm.runInContext('foo', context), 2); + + // Check contextual mutation. + vm.runInContext('bar = 1;', context); + assert.strictEqual(globalThis.bar, undefined); // Should not pollute the current context. + assert.strictEqual(context.bar, 1); + assert.strictEqual(vm.runInContext('globalThis.bar', context), 1); + assert.strictEqual(vm.runInContext('bar', context), 1); + + // Check adding new property from outside. + context.baz = 1; + assert.strictEqual(context.baz, 1); + assert.strictEqual(vm.runInContext('globalThis.baz', context), 1); + assert.strictEqual(vm.runInContext('baz', context), 1); + + // Check mutation via Object.defineProperty(). + vm.runInContext('Object.defineProperty(globalThis, "qux", {' + + 'enumerable: false, configurable: false, get() { return 1; } })', context); + assert.strictEqual(globalThis.qux, undefined); // Should not pollute the current context. + assert.strictEqual(context.qux, 1); + assert.strictEqual(vm.runInContext('qux', context), 1); + const desc = Object.getOwnPropertyDescriptor(context, 'qux'); + assert.strictEqual(desc.enumerable, false); + assert.strictEqual(desc.configurable, false); + assert.strictEqual(typeof desc.get, 'function'); + assert.throws(() => { context.qux = 1; }, { name: 'TypeError' }); + assert.throws(() => { Object.defineProperty(context, 'qux', { value: 1 }); }, { name: 'TypeError' }); + // Setting a value without a setter fails silently. + assert.strictEqual(vm.runInContext('qux = 2; qux', context), 1); + assert.throws(() => { + vm.runInContext('Object.defineProperty(globalThis, "qux", { value: 1 });'); + }, { name: 'TypeError' }); +} + +function checkFrozen(context) { + // Check mutation via globalThis. + vm.runInContext('globalThis.foo = 1', context); // Invoking setters on freezed object fails silently. + assert.strictEqual(context.foo, undefined); + assert.strictEqual(vm.runInContext('globalThis.foo', context), undefined); + assert.throws(() => { + vm.runInContext('foo', context); // It should not be looked up contextually. + }, { + name: 'ReferenceError' + }); + + // Check mutation from outside. + assert.throws(() => { + context.foo = 2; + }, { name: 'TypeError' }); + assert.strictEqual(context.foo, undefined); + assert.strictEqual(vm.runInContext('globalThis.foo', context), undefined); + assert.throws(() => { + vm.runInContext('foo', context); // It should not be looked up contextually. + }, { + name: 'ReferenceError' + }); + + // Check contextual mutation. + vm.runInContext('bar = 1', context); // Invoking setters on freezed object fails silently. + assert.strictEqual(context.bar, undefined); + assert.strictEqual(vm.runInContext('globalThis.bar', context), undefined); + assert.throws(() => { + vm.runInContext('bar', context); // It should not be looked up contextually. + }, { + name: 'ReferenceError' + }); + + // Check mutation via Object.defineProperty(). + assert.throws(() => { + vm.runInContext('Object.defineProperty(globalThis, "qux", {' + + 'enumerable: false, configurable: false, get() { return 1; } })', context); + }, { + name: 'TypeError' + }); + assert.strictEqual(context.qux, undefined); + assert.strictEqual(vm.runInContext('globalThis.qux', context), undefined); + assert.strictEqual(Object.getOwnPropertyDescriptor(context, 'qux'), undefined); + assert.throws(() => { Object.defineProperty(context, 'qux', { value: 1 }); }, { name: 'TypeError' }); + assert.throws(() => { + vm.runInContext('qux', context); + }, { + name: 'ReferenceError' + }); +} + +{ + // Check freezing the vanilla context's global object from within the context. + const context = vm.createContext(vm.constants.DONT_CONTEXTIFY); + // Only vanilla contexts' globals can be freezed. Contextified global objects cannot be freezed + // due to the presence of interceptors. + vm.runInContext('Object.freeze(globalThis)', context); + checkFrozen(context); +} + +{ + // Check freezing the vanilla context's global object from outside the context. + const context = vm.createContext(vm.constants.DONT_CONTEXTIFY); + Object.freeze(context); + checkFrozen(context); +} + +// Check importModuleDynamically works. +(async function() { + { + const moduleUrl = fixtures.fileURL('es-modules', 'message.mjs'); + const namespace = await import(moduleUrl.href); + // Check dynamic import works + const context = vm.createContext(vm.constants.DONT_CONTEXTIFY); + const script = new vm.Script(`import(${JSON.stringify(moduleUrl)})`, { + importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, + }); + const promise = script.runInContext(context); + assert.strictEqual(await promise, namespace); + } +})().catch(common.mustNotCall()); diff --git a/test/js/node/test/parallel/test-vm-module-dynamic-import.js b/test/js/node/test/parallel/test-vm-module-dynamic-import.js new file mode 100644 index 0000000000..bd542ca920 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-module-dynamic-import.js @@ -0,0 +1,117 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +const common = require('../common'); + +const assert = require('assert'); +const { Script, SourceTextModule } = require('vm'); + +async function testNoCallback() { + const m = new SourceTextModule(` + globalThis.importResult = import("foo"); + globalThis.importResult.catch(() => {}); + `); + await m.link(common.mustNotCall()); + await m.evaluate(); + let threw = false; + try { + await globalThis.importResult; + } catch (err) { + threw = true; + assert.strictEqual(err.code, 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING'); + } + delete globalThis.importResult; + assert(threw); +} + +async function test() { + const foo = new SourceTextModule('export const a = 1;'); + await foo.link(common.mustNotCall()); + await foo.evaluate(); + + { + const s = new Script('import("foo")', { + importModuleDynamically: common.mustCall((specifier, wrap) => { + assert.strictEqual(specifier, 'foo'); + assert.strictEqual(wrap, s); + return foo; + }), + }); + + const result = s.runInThisContext(); + assert.strictEqual(await result, foo.namespace); + } + + { + const m = new SourceTextModule('globalThis.fooResult = import("foo")', { + importModuleDynamically: common.mustCall((specifier, wrap) => { + assert.strictEqual(specifier, 'foo'); + assert.strictEqual(wrap, m); + return foo; + }), + }); + await m.link(common.mustNotCall()); + await m.evaluate(); + assert.strictEqual(await globalThis.fooResult, foo.namespace); + delete globalThis.fooResult; + } + + { + const s = new Script('import("foo", { with: { key: "value" } })', { + importModuleDynamically: common.mustCall((specifier, wrap, attributes) => { + assert.strictEqual(specifier, 'foo'); + assert.strictEqual(wrap, s); + assert.deepStrictEqual(attributes, { __proto__: null, key: 'value' }); + return foo; + }), + }); + + const result = s.runInThisContext(); + assert.strictEqual(await result, foo.namespace); + } +} + +async function testInvalid() { + const m = new SourceTextModule('globalThis.fooResult = import("foo")', { + importModuleDynamically: common.mustCall((specifier, wrap) => { + return 5; + }), + }); + await m.link(common.mustNotCall()); + await m.evaluate(); + await globalThis.fooResult.catch(common.mustCall((e) => { + assert.strictEqual(e.code, 'ERR_VM_MODULE_NOT_MODULE'); + })); + delete globalThis.fooResult; + + const s = new Script('import("bar")', { + importModuleDynamically: common.mustCall((specifier, wrap) => { + return undefined; + }), + }); + let threw = false; + try { + await s.runInThisContext(); + } catch (e) { + threw = true; + assert.strictEqual(e.code, 'ERR_VM_MODULE_NOT_MODULE'); + } + assert(threw); +} + +async function testInvalidimportModuleDynamically() { + assert.throws( + () => new Script( + 'import("foo")', + { importModuleDynamically: false }), + { code: 'ERR_INVALID_ARG_TYPE' } + ); +} + +(async function() { + await testNoCallback(); + await test(); + await testInvalid(); + await testInvalidimportModuleDynamically(); +}()).then(common.mustCall()); diff --git a/test/js/node/test/parallel/test-vm-module-dynamic-namespace.js b/test/js/node/test/parallel/test-vm-module-dynamic-namespace.js new file mode 100644 index 0000000000..84937cd78d --- /dev/null +++ b/test/js/node/test/parallel/test-vm-module-dynamic-namespace.js @@ -0,0 +1,26 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +const common = require('../common'); + +const assert = require('assert'); + +const { types } = require('util'); +const { SourceTextModule } = require('vm'); + +(async () => { + const m = new SourceTextModule('globalThis.importResult = import("");', { + importModuleDynamically: common.mustCall(async (specifier, wrap) => { + const m = new SourceTextModule(''); + await m.link(() => 0); + await m.evaluate(); + return m.namespace; + }), + }); + await m.link(() => 0); + await m.evaluate(); + const ns = await globalThis.importResult; + delete globalThis.importResult; + assert.ok(types.isModuleNamespaceObject(ns)); +})().then(common.mustCall()); diff --git a/test/js/node/test/parallel/test-vm-module-referrer-realm.mjs b/test/js/node/test/parallel/test-vm-module-referrer-realm.mjs new file mode 100644 index 0000000000..3957f147d8 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-module-referrer-realm.mjs @@ -0,0 +1,70 @@ +// Flags: --experimental-vm-modules +import * as common from '../common/index.mjs'; +import assert from 'node:assert'; +import { Script, SourceTextModule, createContext } from 'node:vm'; + +async function test() { + const foo = new SourceTextModule('export const a = 1;'); + await foo.link(common.mustNotCall()); + await foo.evaluate(); + + const ctx = createContext({}, { + importModuleDynamically: common.mustCall((specifier, wrap) => { + assert.strictEqual(specifier, 'foo'); + assert.strictEqual(wrap, ctx); + return foo; + }, 2), + }); + { + const s = new Script('Promise.resolve("import(\'foo\')").then(eval)', { + importModuleDynamically: common.mustNotCall(), + }); + + const result = s.runInContext(ctx); + assert.strictEqual(await result, foo.namespace); + } + + { + const m = new SourceTextModule('globalThis.fooResult = Promise.resolve("import(\'foo\')").then(eval)', { + context: ctx, + importModuleDynamically: common.mustNotCall(), + }); + await m.link(common.mustNotCall()); + await m.evaluate(); + assert.strictEqual(await ctx.fooResult, foo.namespace); + delete ctx.fooResult; + } +} + +async function testMissing() { + const ctx = createContext({}); + { + const s = new Script('Promise.resolve("import(\'foo\')").then(eval)', { + importModuleDynamically: common.mustNotCall(), + }); + + const result = s.runInContext(ctx); + await assert.rejects(result, { + code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING', + }); + } + + { + const m = new SourceTextModule('globalThis.fooResult = Promise.resolve("import(\'foo\')").then(eval)', { + context: ctx, + importModuleDynamically: common.mustNotCall(), + }); + await m.link(common.mustNotCall()); + await m.evaluate(); + + await assert.rejects(ctx.fooResult, { + code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING', + }); + delete ctx.fooResult; + } +} + +await Promise.all([ + test(), + testMissing(), +]).then(common.mustCall()); diff --git a/test/js/node/test/parallel/test-vm-no-dynamic-import-callback.js b/test/js/node/test/parallel/test-vm-no-dynamic-import-callback.js new file mode 100644 index 0000000000..35b553d587 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-no-dynamic-import-callback.js @@ -0,0 +1,20 @@ +'use strict'; + +const common = require('../common'); +const { Script, compileFunction } = require('vm'); +const assert = require('assert'); + +assert.rejects(async () => { + const script = new Script('import("fs")'); + const imported = script.runInThisContext(); + await imported; +}, { + code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING' +}).then(common.mustCall()); + +assert.rejects(async () => { + const imported = compileFunction('return import("fs")')(); + await imported; +}, { + code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING' +}).then(common.mustCall()); diff --git a/test/js/node/test/sequential/test-vm-timeout-rethrow.js b/test/js/node/test/sequential/test-vm-timeout-rethrow.js new file mode 100644 index 0000000000..d4682fe975 --- /dev/null +++ b/test/js/node/test/sequential/test-vm-timeout-rethrow.js @@ -0,0 +1,44 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const spawn = require('child_process').spawn; + +if (process.argv[2] === 'child') { + const code = 'while(true);'; + + const ctx = vm.createContext(); + + vm.runInContext(code, ctx, { timeout: 1 }); +} else { + const proc = spawn(process.execPath, process.argv.slice(1).concat('child')); + let err = ''; + proc.stderr.on('data', function(data) { + err += data; + }); + + process.on('exit', function() { + assert.match(err, /Script execution timed out after 1ms/); + }); +} diff --git a/test/no-validate-exceptions.txt b/test/no-validate-exceptions.txt index e161025ce7..3823c677bc 100644 --- a/test/no-validate-exceptions.txt +++ b/test/no-validate-exceptions.txt @@ -1522,6 +1522,7 @@ test/js/node/test/parallel/test-vm-module-errors.js test/js/node/test/parallel/test-vm-module-import-meta.js test/js/node/test/parallel/test-vm-module-link.js test/js/node/test/parallel/test-vm-module-reevaluate.js +test/js/node/test/parallel/test-vm-module-referrer-realm.mjs test/js/node/test/parallel/test-vm-module-synthetic.js test/js/node/test/parallel/test-vm-new-script-context.js test/js/node/test/parallel/test-vm-new-script-new-context.js