From 2c9f4b61dda2a76437e2e12ce0717635c5b0d1b7 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Mon, 28 Apr 2025 16:26:07 -0700 Subject: [PATCH 01/76] Implement createCachedData --- src/bun.js/bindings/NodeVM.cpp | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 0a7b15f4ca98..a954c76e5e00 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -42,6 +42,9 @@ #include "NodeValidator.h" #include "JavaScriptCore/JSCInlines.h" +#include "JavaScriptCore/CodeCache.h" +#include "JavaScriptCore/BytecodeCacheError.h" +#include "wtf/FileHandle.h" namespace Bun { using namespace WebCore; @@ -829,7 +832,36 @@ JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); - return throwVMError(globalObject, scope, "TODO: Script.createCachedData"_s); + + JSValue thisValue = callFrame->thisValue(); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + const JSC::SourceCode& source = script->source(); + JSC::CodeCache* cache = vm.codeCache(); + JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); + JSC::ParserError parserError; + JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); + JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; + JSC::BytecodeCacheError bytecodeCacheError; + FileSystem::FileHandle fileHandle; + RefPtr bytecode = JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ModuleType, lexicallyScopedFeatures, JSParserScriptMode::Module, fileHandle, bytecodeCacheError, {}); + + if (UNLIKELY(!bytecode)) { + return throwVMError(globalObject, scope, "createCachedData failed"_s); + } + + std::span bytes = bytecode->span(); + + JSC::JSArrayBufferView* uint8Array = JSC::JSUint8Array::create(globalObject, globalObject->typedArrayStructure(JSC::TypeUint8, false), bytes.size()); + if (UNLIKELY(!uint8Array)) { + return throwVMError(globalObject, scope, "Failed to create Uint8Array for cached data"_s); + } + + std::memmove(uint8Array->vector(), bytes.data(), bytes.size()); + return JSValue::encode(uint8Array); } JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) From b0b7b850fa8a14b04b7efafecdf4714f6297c8d1 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Mon, 28 Apr 2025 16:43:27 -0700 Subject: [PATCH 02/76] Produce a proper Buffer --- src/bun.js/bindings/NodeVM.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index a954c76e5e00..5804ff93b2a5 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -854,14 +854,13 @@ JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, } std::span bytes = bytecode->span(); + auto* buffer = WebCore::createBuffer(globalObject, bytes); - JSC::JSArrayBufferView* uint8Array = JSC::JSUint8Array::create(globalObject, globalObject->typedArrayStructure(JSC::TypeUint8, false), bytes.size()); - if (UNLIKELY(!uint8Array)) { - return throwVMError(globalObject, scope, "Failed to create Uint8Array for cached data"_s); + if (UNLIKELY(!buffer)) { + return throwVMError(globalObject, scope, "Failed to create Buffer for cached data"_s); } - std::memmove(uint8Array->vector(), bytes.data(), bytes.size()); - return JSValue::encode(uint8Array); + return JSValue::encode(buffer); } JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) From 6f41459cb9dcd7a37cf562b21a614e9b2c02f675 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Mon, 28 Apr 2025 16:46:02 -0700 Subject: [PATCH 03/76] Fix wrong SourceCodeType --- src/bun.js/bindings/NodeVM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 5804ff93b2a5..d4e3a6bdba72 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -847,7 +847,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; JSC::BytecodeCacheError bytecodeCacheError; FileSystem::FileHandle fileHandle; - RefPtr bytecode = JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ModuleType, lexicallyScopedFeatures, JSParserScriptMode::Module, fileHandle, bytecodeCacheError, {}); + RefPtr bytecode = JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); if (UNLIKELY(!bytecode)) { return throwVMError(globalObject, scope, "createCachedData failed"_s); From 32630059b0f5e0e83c5b8f4ba716cca7555ef6ea Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Mon, 28 Apr 2025 18:07:55 -0700 Subject: [PATCH 04/76] Add support for produceCachedData and cachedDataProduced --- src/bun.js/bindings/NodeVM.cpp | 1152 +++++++++++++++++--------------- 1 file changed, 630 insertions(+), 522 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index d4e3a6bdba72..f6cd272cfad9 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -55,6 +55,9 @@ using namespace WebCore; static JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr); static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset); +static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, JSC::SourceCode source); +static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::SourceCode source); + NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox); /// For some reason Node has this error message with a grammar error and we have to match it so the tests pass: @@ -70,129 +73,465 @@ JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, return {}; } -class NodeVMScriptConstructor final : public JSC::InternalFunction { +class BaseOptions { public: - using Base = JSC::InternalFunction; + String filename = String(); + OrdinalNumber lineOffset; + OrdinalNumber columnOffset; + bool failed; - static NodeVMScriptConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) + { + JSObject* options = nullptr; + bool any = false; - DECLARE_EXPORT_INFO; + if (!optionsArg.isUndefined()) { + if (optionsArg.isObject()) { + options = asObject(optionsArg); + } else { + auto _ = ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); + return false; + } - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); - } + if (JSValue filenameOpt = options->getIfPropertyExists(globalObject, builtinNames(vm).filenamePublicName())) { + if (filenameOpt.isString()) { + this->filename = filenameOpt.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, false); + any = true; + } else if (!filenameOpt.isUndefined()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.filename"_s, "string"_s, filenameOpt); + return false; + } + } else { + this->filename = "evalmachine."_s; + } -private: - NodeVMScriptConstructor(JSC::VM& vm, JSC::Structure* structure); + if (JSValue lineOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "lineOffset"_s))) { + if (lineOffsetOpt.isAnyInt()) { + if (!lineOffsetOpt.isInt32()) { + ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), lineOffsetOpt); + return false; + } + this->lineOffset = OrdinalNumber::fromZeroBasedInt(lineOffsetOpt.asInt32()); + any = true; + } else if (lineOffsetOpt.isNumber()) { + ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, "an integer"_s, lineOffsetOpt); + return false; + } else if (!lineOffsetOpt.isUndefined()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.lineOffset"_s, "number"_s, lineOffsetOpt); + return false; + } + } - void finishCreation(JSC::VM&, JSC::JSObject* prototype); -}; -STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptConstructor, JSC::InternalFunction); + if (JSValue columnOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "columnOffset"_s))) { + if (columnOffsetOpt.isAnyInt()) { + if (!columnOffsetOpt.isInt32()) { + ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), columnOffsetOpt); + return false; + } + int columnOffsetValue = columnOffsetOpt.asInt32(); -class NodeVMScript final : public JSC::JSDestructibleObject { -public: - using Base = JSC::JSDestructibleObject; + this->columnOffset = OrdinalNumber::fromZeroBasedInt(columnOffsetValue); + any = true; + } else if (columnOffsetOpt.isNumber()) { + ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, "an integer"_s, columnOffsetOpt); + return false; + } else if (!columnOffsetOpt.isUndefined()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.columnOffset"_s, "number"_s, columnOffsetOpt); + return false; + } + } + } - static NodeVMScript* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::SourceCode source); + return any; + } - DECLARE_EXPORT_INFO; - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + bool validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool* outProduceCachedData) { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMScript.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMScript = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForNodeVMScript.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMScript = std::forward(space); }); + JSValue produceCachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "produceCachedData"_s)); + if (produceCachedDataOpt && !produceCachedDataOpt.isUndefined()) { + RETURN_IF_EXCEPTION(scope, {}); + if (!produceCachedDataOpt.isBoolean()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.produceCachedData"_s, "boolean"_s, produceCachedDataOpt); + return false; + } + *outProduceCachedData = produceCachedDataOpt.asBoolean(); + return true; + } + return false; } - static void destroy(JSC::JSCell*); - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options) { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); - } + JSValue cachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "cachedData"_s)); + if (cachedDataOpt && !cachedDataOpt.isUndefined()) { + RETURN_IF_EXCEPTION(scope, {}); + if (!cachedDataOpt.isCell()) { + ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); + return false; + } - static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); + // If it's a cell, verify it's a Buffer, TypedArray, or DataView + if (cachedDataOpt.isCell()) { + JSCell* cell = cachedDataOpt.asCell(); + bool isValidType = false; - const JSC::SourceCode& source() const { return m_source; } + // Check if it's a Buffer, TypedArray, or DataView + if (cell->inherits() || cell->inherits()) { + isValidType = true; + } else if (JSC::JSArrayBufferView* view = JSC::jsDynamicCast(cachedDataOpt)) { + isValidType = !view->isDetached(); + } - DECLARE_VISIT_CHILDREN; - mutable JSC::WriteBarrier m_cachedDirectExecutable; + if (!isValidType) { + ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); + return false; + } + return true; -private: - JSC::SourceCode m_source; + // TODO: actually use it + // this->cachedData = true; + } + } + return false; + } - NodeVMScript(JSC::VM& vm, JSC::Structure* structure, JSC::SourceCode source) - : Base(vm, structure) - , m_source(source) + bool validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional* outTimeout) { - } + JSValue timeoutOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "timeout"_s)); + if (timeoutOpt && !timeoutOpt.isUndefined()) { + if (!timeoutOpt.isNumber()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.timeout"_s, "number"_s, timeoutOpt); + return false; + } - void finishCreation(JSC::VM&); + ssize_t timeoutValue; + V::validateInteger(scope, globalObject, timeoutOpt, "options.timeout"_s, jsNumber(1), jsNumber(std::numeric_limits().max()), &timeoutValue); + RETURN_IF_EXCEPTION(scope, {}); + + *outTimeout = timeoutValue; + return true; + } + return false; + } }; -NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) -{ -} +class ScriptOptions : public BaseOptions { +public: + std::optional timeout = std::nullopt; + bool importModuleDynamically = false; + bool cachedData = false; + bool produceCachedData = false; -template JSC::GCClient::IsoSubspace* NodeVMGlobalObject::subspaceFor(JSC::VM& vm) -{ - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMGlobalObject.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMGlobalObject = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForNodeVMGlobalObject.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMGlobalObject = std::forward(space); }, - [](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForNodeVMGlobalObject; }); -} + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) + { + bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); + RETURN_IF_EXCEPTION(scope, false); -NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) -{ - auto* cell = new (NotNull, JSC::allocateCell(vm)) NodeVMGlobalObject(vm, structure); - cell->finishCreation(vm); - return cell; -} + if (!optionsArg.isUndefined() && !optionsArg.isString()) { + JSObject* options = asObject(optionsArg); -Structure* NodeVMGlobalObject::createStructure(JSC::VM& vm, JSC::JSValue prototype) -{ - // ~IsImmutablePrototypeExoticObject is necessary for JSDOM to work (it relies on __proto__ = on the GlobalObject). - return JSC::Structure::create(vm, nullptr, prototype, JSC::TypeInfo(JSC::GlobalObjectType, StructureFlags & ~IsImmutablePrototypeExoticObject), info()); -} + // Validate contextName and contextOrigin are strings + if (JSValue contextNameOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextName"_s))) { + if (!contextNameOpt.isUndefined() && !contextNameOpt.isString()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextName"_s, "string"_s, contextNameOpt); + return false; + } + any = true; + } -void NodeVMGlobalObject::finishCreation(JSC::VM&) -{ - Base::finishCreation(vm()); -} + if (JSValue contextOriginOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextOrigin"_s))) { + if (!contextOriginOpt.isUndefined() && !contextOriginOpt.isString()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextOrigin"_s, "string"_s, contextOriginOpt); + return false; + } + any = true; + } -void NodeVMGlobalObject::destroy(JSCell* cell) -{ - static_cast(cell)->~NodeVMGlobalObject(); -} + if (validateTimeout(globalObject, vm, scope, options, &this->timeout)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } -NodeVMGlobalObject::~NodeVMGlobalObject() -{ -} + if (validateProduceCachedData(globalObject, vm, scope, options, &this->produceCachedData)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } -void NodeVMGlobalObject::setContextifiedObject(JSC::JSObject* contextifiedObject) -{ - m_sandbox.set(vm(), this, contextifiedObject); -} + if (validateCachedData(globalObject, vm, scope, options)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + // TODO: actually use it + this->cachedData = true; + } + } -void NodeVMGlobalObject::clearContextifiedObject() -{ - m_sandbox.clear(); -} + return any; + } +}; -bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) -{ - // if (!propertyName.isSymbol()) - // printf("put called for %s\n", propertyName.publicName()->utf8().data()); +class RunningScriptOptions : public BaseOptions { +public: + bool displayErrors = true; + std::optional timeout = std::nullopt; + bool breakOnSigint = false; + + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) + { + bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); + RETURN_IF_EXCEPTION(scope, false); + + if (!optionsArg.isUndefined() && !optionsArg.isString()) { + JSObject* options = asObject(optionsArg); + + if (JSValue displayErrorsOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "displayErrors"_s))) { + RETURN_IF_EXCEPTION(scope, false); + if (!displayErrorsOpt.isBoolean()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.displayErrors"_s, "boolean"_s, displayErrorsOpt); + return false; + } + this->displayErrors = displayErrorsOpt.asBoolean(); + any = true; + } + + if (validateTimeout(globalObject, vm, scope, options, &this->timeout)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } + + if (JSValue breakOnSigintOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "breakOnSigint"_s))) { + RETURN_IF_EXCEPTION(scope, false); + if (!breakOnSigintOpt.isBoolean()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.breakOnSigint"_s, "boolean"_s, breakOnSigintOpt); + return false; + } + this->breakOnSigint = breakOnSigintOpt.asBoolean(); + any = true; + } + } + + return any; + } +}; + +class CompileFunctionOptions : public BaseOptions { +public: + bool cachedData = false; + bool produceCachedData; + JSGlobalObject* parsingContext; + JSValue contextExtensions; + + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) + { + this->parsingContext = globalObject; + bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); + RETURN_IF_EXCEPTION(scope, false); + + if (!optionsArg.isUndefined() && !optionsArg.isString()) { + JSObject* options = asObject(optionsArg); + + if (validateProduceCachedData(globalObject, vm, scope, options, &this->produceCachedData)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } + + if (validateCachedData(globalObject, vm, scope, options)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + // TODO: actually use it + this->cachedData = true; + } + + JSValue parsingContextValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "parsingContext"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!parsingContextValue.isEmpty() && !parsingContextValue.isUndefined()) { + if (parsingContextValue.isNull() || !parsingContextValue.isObject()) + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + + JSObject* context = asObject(parsingContextValue); + auto* zigGlobalObject = defaultGlobalObject(globalObject); + JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); + + if (scopeValue.isUndefined()) + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + + parsingContext = jsDynamicCast(scopeValue); + if (!parsingContext) + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + + any = true; + } + + // Handle contextExtensions option + JSValue contextExtensionsValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextExtensions"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!contextExtensionsValue.isEmpty() && !contextExtensionsValue.isUndefined()) { + if (contextExtensionsValue.isNull() || !contextExtensionsValue.isObject()) + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); + + if (auto* contextExtensionsObject = asObject(contextExtensionsValue)) { + if (!isArray(globalObject, contextExtensionsObject)) + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); + + // Validate that all items in the array are objects + auto* contextExtensionsArray = jsCast(contextExtensionsValue); + unsigned length = contextExtensionsArray->length(); + for (unsigned i = 0; i < length; i++) { + JSValue extension = contextExtensionsArray->getIndexQuickly(i); + if (!extension.isObject()) + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions[0]"_s, "object"_s, extension); + } + } else { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); + } + + this->contextExtensions = contextExtensionsValue; + any = true; + } + } + + return any; + } +}; + +class NodeVMScriptConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static NodeVMScriptConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); + + DECLARE_EXPORT_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); + } + +private: + NodeVMScriptConstructor(JSC::VM& vm, JSC::Structure* structure); + + void finishCreation(JSC::VM&, JSC::JSObject* prototype); +}; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptConstructor, JSC::InternalFunction); + +class NodeVMScript final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + + static NodeVMScript* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options); + + DECLARE_EXPORT_INFO; + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMScript.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMScript = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForNodeVMScript.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMScript = std::forward(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); + + JSC::ProgramExecutable* createExecutable(); + void cacheBytecode(); + JSC::JSUint8Array* getBytecodeBuffer(); + + const JSC::SourceCode& source() const { return m_source; } + RefPtr cachedBytecode() const { return m_cachedBytecode; } + JSC::ProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } + bool cachedDataProduced() const { return m_cachedDataProduced; } + void cachedDataProduced(bool value) { m_cachedDataProduced = value; } + + DECLARE_VISIT_CHILDREN; + +private: + JSC::SourceCode m_source; + RefPtr m_cachedBytecode; + mutable JSC::WriteBarrier m_cachedBytecodeBuffer; + mutable JSC::WriteBarrier m_cachedExecutable; + ScriptOptions m_options; + bool m_cachedDataProduced = false; + + NodeVMScript(JSC::VM& vm, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options) + : Base(vm, structure) + , m_source(source) + , m_options(options) + { + } + + void finishCreation(JSC::VM&); +}; + +NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) +{ +} + +template JSC::GCClient::IsoSubspace* NodeVMGlobalObject::subspaceFor(JSC::VM& vm) +{ + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMGlobalObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMGlobalObject = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForNodeVMGlobalObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMGlobalObject = std::forward(space); }, + [](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForNodeVMGlobalObject; }); +} + +NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) +{ + auto* cell = new (NotNull, JSC::allocateCell(vm)) NodeVMGlobalObject(vm, structure); + cell->finishCreation(vm); + return cell; +} + +Structure* NodeVMGlobalObject::createStructure(JSC::VM& vm, JSC::JSValue prototype) +{ + // ~IsImmutablePrototypeExoticObject is necessary for JSDOM to work (it relies on __proto__ = on the GlobalObject). + return JSC::Structure::create(vm, nullptr, prototype, JSC::TypeInfo(JSC::GlobalObjectType, StructureFlags & ~IsImmutablePrototypeExoticObject), info()); +} + +void NodeVMGlobalObject::finishCreation(JSC::VM&) +{ + Base::finishCreation(vm()); +} + +void NodeVMGlobalObject::destroy(JSCell* cell) +{ + static_cast(cell)->~NodeVMGlobalObject(); +} + +NodeVMGlobalObject::~NodeVMGlobalObject() +{ +} + +void NodeVMGlobalObject::setContextifiedObject(JSC::JSObject* contextifiedObject) +{ + m_sandbox.set(vm(), this, contextifiedObject); +} + +void NodeVMGlobalObject::clearContextifiedObject() +{ + m_sandbox.clear(); +} + +bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +{ + // if (!propertyName.isSymbol()) + // printf("put called for %s\n", propertyName.publicName()->utf8().data()); auto* thisObject = jsCast(cell); if (!thisObject->m_sandbox) { @@ -276,433 +615,110 @@ bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* glob if (handlerValue.isNull()) return throwTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); - JSObject* handler = jsCast(handlerValue); - CallData callData; - JSObject* getHandler = proxyObject->getHandlerTrap(globalObject, handler, callData, vm.propertyNames->get, ProxyObject::HandlerTrap::Get); - RETURN_IF_EXCEPTION(scope, {}); - - // If there is a `get` trap, we don't need to our special handling - if (getHandler) { - if (contextifiedObject->methodTable()->getOwnPropertySlot(contextifiedObject, globalObject, propertyName, slot)) { - return true; - } - goto try_from_global; - } - - // A lot of this is copy-pasted from JSC's `ProxyObject::getOwnPropertySlotCommon` function in - // ProxyObject.cpp, need to make sure we keep this in sync when we update JSC... - - slot.disableCaching(); - slot.setIsTaintedByOpaqueObject(); - - if (slot.isVMInquiry()) { - goto try_from_global; - } - - JSValue receiver = slot.thisValue(); - - // We're going to have to look this up ourselves - PropertySlot target_slot(receiver, PropertySlot::InternalMethodType::Get); - JSObject* target = proxyObject->target(); - bool hasProperty = target->getPropertySlot(globalObject, propertyName, target_slot); - EXCEPTION_ASSERT(!scope.exception() || !hasProperty); - if (hasProperty) { - unsigned ignoredAttributes = 0; - JSValue result = target_slot.getValue(globalObject, propertyName); - RETURN_IF_EXCEPTION(scope, {}); - slot.setValue(proxyObject, ignoredAttributes, result); - RETURN_IF_EXCEPTION(scope, {}); - return true; - } - - goto try_from_global; - } - - if (contextifiedObject->getPropertySlot(globalObject, propertyName, slot)) { - return true; - } - - try_from_global: - - slot.setThisValue(globalObject); - RETURN_IF_EXCEPTION(scope, false); - } - - return Base::getOwnPropertySlot(cell, globalObject, propertyName, slot); -} - -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(); - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - 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); - } - - if (descriptor.isAccessorDescriptor()) { - return contextifiedObject->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); - } - - if (!contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow)) { - return false; - } - - return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow); -} - -DEFINE_VISIT_CHILDREN(NodeVMGlobalObject); - -template -void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) -{ - Base::visitChildren(cell, visitor); - auto* thisObject = jsCast(cell); - visitor.append(thisObject->m_sandbox); -} - -class BaseOptions { -public: - String filename = String(); - OrdinalNumber lineOffset; - OrdinalNumber columnOffset; - bool failed; - - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) - { - JSObject* options = nullptr; - bool any = false; - - if (!optionsArg.isUndefined()) { - if (optionsArg.isObject()) { - options = asObject(optionsArg); - } else { - auto _ = ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); - return false; - } - - if (JSValue filenameOpt = options->getIfPropertyExists(globalObject, builtinNames(vm).filenamePublicName())) { - if (filenameOpt.isString()) { - this->filename = filenameOpt.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, false); - any = true; - } else if (!filenameOpt.isUndefined()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.filename"_s, "string"_s, filenameOpt); - return false; - } - } else { - this->filename = "evalmachine."_s; - } - - if (JSValue lineOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "lineOffset"_s))) { - if (lineOffsetOpt.isAnyInt()) { - if (!lineOffsetOpt.isInt32()) { - ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), lineOffsetOpt); - return false; - } - this->lineOffset = OrdinalNumber::fromZeroBasedInt(lineOffsetOpt.asInt32()); - any = true; - } else if (lineOffsetOpt.isNumber()) { - ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, "an integer"_s, lineOffsetOpt); - return false; - } else if (!lineOffsetOpt.isUndefined()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.lineOffset"_s, "number"_s, lineOffsetOpt); - return false; - } - } - - if (JSValue columnOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "columnOffset"_s))) { - if (columnOffsetOpt.isAnyInt()) { - if (!columnOffsetOpt.isInt32()) { - ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), columnOffsetOpt); - return false; - } - int columnOffsetValue = columnOffsetOpt.asInt32(); - - this->columnOffset = OrdinalNumber::fromZeroBasedInt(columnOffsetValue); - any = true; - } else if (columnOffsetOpt.isNumber()) { - ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, "an integer"_s, columnOffsetOpt); - return false; - } else if (!columnOffsetOpt.isUndefined()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.columnOffset"_s, "number"_s, columnOffsetOpt); - return false; - } - } - } - - return any; - } - - bool validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool* outProduceCachedData) - { - JSValue produceCachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "produceCachedData"_s)); - if (produceCachedDataOpt && !produceCachedDataOpt.isUndefined()) { - RETURN_IF_EXCEPTION(scope, {}); - if (!produceCachedDataOpt.isBoolean()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.produceCachedData"_s, "boolean"_s, produceCachedDataOpt); - return false; - } - *outProduceCachedData = produceCachedDataOpt.asBoolean(); - return true; - } - return false; - } - - bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options) - { - JSValue cachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "cachedData"_s)); - if (cachedDataOpt && !cachedDataOpt.isUndefined()) { - RETURN_IF_EXCEPTION(scope, {}); - if (!cachedDataOpt.isCell()) { - ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); - return false; - } - - // If it's a cell, verify it's a Buffer, TypedArray, or DataView - if (cachedDataOpt.isCell()) { - JSCell* cell = cachedDataOpt.asCell(); - bool isValidType = false; - - // Check if it's a Buffer, TypedArray, or DataView - if (cell->inherits() || cell->inherits()) { - isValidType = true; - } else if (JSC::JSArrayBufferView* view = JSC::jsDynamicCast(cachedDataOpt)) { - isValidType = !view->isDetached(); - } - - if (!isValidType) { - ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); - return false; - } - return true; - - // TODO: actually use it - // this->cachedData = true; - } - } - return false; - } - - bool validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional* outTimeout) - { - JSValue timeoutOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "timeout"_s)); - if (timeoutOpt && !timeoutOpt.isUndefined()) { - if (!timeoutOpt.isNumber()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.timeout"_s, "number"_s, timeoutOpt); - return false; - } - - ssize_t timeoutValue; - V::validateInteger(scope, globalObject, timeoutOpt, "options.timeout"_s, jsNumber(1), jsNumber(std::numeric_limits().max()), &timeoutValue); - RETURN_IF_EXCEPTION(scope, {}); - - *outTimeout = timeoutValue; - return true; - } - return false; - } -}; - -class ScriptOptions : public BaseOptions { -public: - bool importModuleDynamically = false; - std::optional timeout = std::nullopt; - bool cachedData = false; - bool produceCachedData = false; - - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) - { - bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); - RETURN_IF_EXCEPTION(scope, false); - - if (!optionsArg.isUndefined() && !optionsArg.isString()) { - JSObject* options = asObject(optionsArg); - - // Validate contextName and contextOrigin are strings - if (JSValue contextNameOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextName"_s))) { - if (!contextNameOpt.isUndefined() && !contextNameOpt.isString()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextName"_s, "string"_s, contextNameOpt); - return false; - } - any = true; - } - - if (JSValue contextOriginOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextOrigin"_s))) { - if (!contextOriginOpt.isUndefined() && !contextOriginOpt.isString()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextOrigin"_s, "string"_s, contextOriginOpt); - return false; - } - any = true; - } - - if (validateTimeout(globalObject, vm, scope, options, &this->timeout)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } - - if (validateProduceCachedData(globalObject, vm, scope, options, &this->produceCachedData)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } - - if (validateCachedData(globalObject, vm, scope, options)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - // TODO: actually use it - this->cachedData = true; - } - } - - return any; - } -}; - -class RunningScriptOptions : public BaseOptions { -public: - bool displayErrors = true; - std::optional timeout = std::nullopt; - bool breakOnSigint = false; - - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) - { - bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); - RETURN_IF_EXCEPTION(scope, false); - - if (!optionsArg.isUndefined() && !optionsArg.isString()) { - JSObject* options = asObject(optionsArg); - - if (JSValue displayErrorsOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "displayErrors"_s))) { - RETURN_IF_EXCEPTION(scope, false); - if (!displayErrorsOpt.isBoolean()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.displayErrors"_s, "boolean"_s, displayErrorsOpt); - return false; - } - this->displayErrors = displayErrorsOpt.asBoolean(); - any = true; - } - - if (validateTimeout(globalObject, vm, scope, options, &this->timeout)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } + JSObject* handler = jsCast(handlerValue); + CallData callData; + JSObject* getHandler = proxyObject->getHandlerTrap(globalObject, handler, callData, vm.propertyNames->get, ProxyObject::HandlerTrap::Get); + RETURN_IF_EXCEPTION(scope, {}); - if (JSValue breakOnSigintOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "breakOnSigint"_s))) { - RETURN_IF_EXCEPTION(scope, false); - if (!breakOnSigintOpt.isBoolean()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.breakOnSigint"_s, "boolean"_s, breakOnSigintOpt); - return false; + // If there is a `get` trap, we don't need to our special handling + if (getHandler) { + if (contextifiedObject->methodTable()->getOwnPropertySlot(contextifiedObject, globalObject, propertyName, slot)) { + return true; } - this->breakOnSigint = breakOnSigintOpt.asBoolean(); - any = true; + goto try_from_global; } - } - return any; - } -}; + // A lot of this is copy-pasted from JSC's `ProxyObject::getOwnPropertySlotCommon` function in + // ProxyObject.cpp, need to make sure we keep this in sync when we update JSC... -class CompileFunctionOptions : public BaseOptions { -public: - bool cachedData = false; - bool produceCachedData; - JSGlobalObject* parsingContext; - JSValue contextExtensions; + slot.disableCaching(); + slot.setIsTaintedByOpaqueObject(); - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) - { - this->parsingContext = globalObject; - bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); - RETURN_IF_EXCEPTION(scope, false); + if (slot.isVMInquiry()) { + goto try_from_global; + } - if (!optionsArg.isUndefined() && !optionsArg.isString()) { - JSObject* options = asObject(optionsArg); + JSValue receiver = slot.thisValue(); - if (validateProduceCachedData(globalObject, vm, scope, options, &this->produceCachedData)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; + // We're going to have to look this up ourselves + PropertySlot target_slot(receiver, PropertySlot::InternalMethodType::Get); + JSObject* target = proxyObject->target(); + bool hasProperty = target->getPropertySlot(globalObject, propertyName, target_slot); + EXCEPTION_ASSERT(!scope.exception() || !hasProperty); + if (hasProperty) { + unsigned ignoredAttributes = 0; + JSValue result = target_slot.getValue(globalObject, propertyName); + RETURN_IF_EXCEPTION(scope, {}); + slot.setValue(proxyObject, ignoredAttributes, result); + RETURN_IF_EXCEPTION(scope, {}); + return true; } - if (validateCachedData(globalObject, vm, scope, options)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - // TODO: actually use it - this->cachedData = true; - } + goto try_from_global; + } - JSValue parsingContextValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "parsingContext"_s)); - RETURN_IF_EXCEPTION(scope, {}); + if (contextifiedObject->getPropertySlot(globalObject, propertyName, slot)) { + return true; + } - if (!parsingContextValue.isEmpty() && !parsingContextValue.isUndefined()) { - if (parsingContextValue.isNull() || !parsingContextValue.isObject()) - return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + try_from_global: - JSObject* context = asObject(parsingContextValue); - auto* zigGlobalObject = defaultGlobalObject(globalObject); - JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); + slot.setThisValue(globalObject); + RETURN_IF_EXCEPTION(scope, false); + } - if (scopeValue.isUndefined()) - return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + return Base::getOwnPropertySlot(cell, globalObject, propertyName, slot); +} - parsingContext = jsDynamicCast(scopeValue); - if (!parsingContext) - return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); +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); + } - any = true; - } + auto* contextifiedObject = thisObject->m_sandbox.get(); + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); - // Handle contextExtensions option - JSValue contextExtensionsValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextExtensions"_s)); - RETURN_IF_EXCEPTION(scope, {}); + PropertySlot slot(globalObject, PropertySlot::InternalMethodType::GetOwnProperty, nullptr); + bool isDeclaredOnGlobalProxy = globalObject->JSC::JSGlobalObject::getOwnPropertySlot(globalObject, globalObject, propertyName, slot); - if (!contextExtensionsValue.isEmpty() && !contextExtensionsValue.isUndefined()) { - if (contextExtensionsValue.isNull() || !contextExtensionsValue.isObject()) - return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); + // 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); + } - if (auto* contextExtensionsObject = asObject(contextExtensionsValue)) { - if (!isArray(globalObject, contextExtensionsObject)) - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); + if (descriptor.isAccessorDescriptor()) { + return contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow); + } - // Validate that all items in the array are objects - auto* contextExtensionsArray = jsCast(contextExtensionsValue); - unsigned length = contextExtensionsArray->length(); - for (unsigned i = 0; i < length; i++) { - JSValue extension = contextExtensionsArray->getIndexQuickly(i); - if (!extension.isObject()) - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions[0]"_s, "object"_s, extension); - } - } else { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); - } + bool isDeclaredOnSandbox = contextifiedObject->getPropertySlot(globalObject, propertyName, slot); + RETURN_IF_EXCEPTION(scope, false); - this->contextExtensions = contextExtensionsValue; - any = true; - } - } + if (isDeclaredOnSandbox && !isDeclaredOnGlobalProxy) { + return contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow); + } - return any; + if (!contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow)) { + return false; } -}; + + return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow); +} + +DEFINE_VISIT_CHILDREN(NodeVMGlobalObject); + +template +void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + Base::visitChildren(cell, visitor); + auto* thisObject = jsCast(cell); + visitor.append(thisObject->m_sandbox); +} static EncodedJSValue constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = JSValue()) @@ -744,8 +760,15 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); RETURN_IF_EXCEPTION(scope, {}); - NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source); - return JSValue::encode(JSValue(script)); + NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, options); + + if (options.produceCachedData) { + script->cacheBytecode(); + // TODO(@heimskr): is there ever a case where bytecode production fails? + script->cachedDataProduced(true); + } + + return JSValue::encode(script); } static bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr exception, ThrowScope& throwScope) @@ -828,6 +851,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalOb { return JSValue::encode(jsBoolean(true)); // TODO } + JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = JSC::getVM(globalObject); @@ -840,27 +864,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, } const JSC::SourceCode& source = script->source(); - JSC::CodeCache* cache = vm.codeCache(); - JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); - JSC::ParserError parserError; - JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); - JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; - JSC::BytecodeCacheError bytecodeCacheError; - FileSystem::FileHandle fileHandle; - RefPtr bytecode = JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); - - if (UNLIKELY(!bytecode)) { - return throwVMError(globalObject, scope, "createCachedData failed"_s); - } - - std::span bytes = bytecode->span(); - auto* buffer = WebCore::createBuffer(globalObject, bytes); - - if (UNLIKELY(!buffer)) { - return throwVMError(globalObject, scope, "Failed to create Buffer for cached data"_s); - } - - return JSValue::encode(buffer); + return createCachedData(globalObject, source); } JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -954,6 +958,36 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, return JSValue::encode(jsString(vm, url)); } +JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue thisValue = JSValue::decode(thisValueEncoded); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + if (auto* buffer = script->getBytecodeBuffer()) { + return JSValue::encode(buffer); + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue thisValue = JSValue::decode(thisValueEncoded); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + return JSValue::encode(jsBoolean(script->cachedDataProduced())); +} + JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); @@ -1318,6 +1352,8 @@ static const struct HashTableValue scriptPrototypeTableValues[] = { { "runInNewContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInNewContext, 2 } }, { "runInThisContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInThisContext, 2 } }, { "sourceMapURL"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetSourceMapURL, nullptr } }, + { "cachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedData, nullptr } }, + { "cachedDataProduced"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataProduced, nullptr } }, }; // NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) @@ -1348,6 +1384,44 @@ const ClassInfo NodeVMScript::s_info = { "Script"_s, &Base::s_info, nullptr, nul const ClassInfo NodeVMScriptConstructor::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptConstructor) }; const ClassInfo NodeVMGlobalObject::s_info = { "NodeVMGlobalObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMGlobalObject) }; +JSC::ProgramExecutable* NodeVMScript::createExecutable() +{ + auto& vm = JSC::getVM(globalObject()); + m_cachedExecutable.set(vm, this, JSC::ProgramExecutable::create(globalObject(), m_source)); + return m_cachedExecutable.get(); +} + +void NodeVMScript::cacheBytecode() +{ + if (!m_cachedExecutable) { + createExecutable(); + } + + m_cachedBytecode = getBytecode(globalObject(), m_cachedExecutable.get(), m_source); + m_cachedDataProduced = true; +} + +JSC::JSUint8Array* NodeVMScript::getBytecodeBuffer() +{ + if (!m_options.produceCachedData) { + return nullptr; + } + + if (!m_cachedBytecodeBuffer) { + if (!m_cachedBytecode) { + cacheBytecode(); + } + + ASSERT(m_cachedBytecode); + + std::span bytes = m_cachedBytecode->span(); + m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject(), bytes)); + } + + ASSERT(m_cachedBytecodeBuffer); + return m_cachedBytecodeBuffer.get(); +} + DEFINE_VISIT_CHILDREN(NodeVMScript); template @@ -1356,7 +1430,7 @@ void NodeVMScript::visitChildrenImpl(JSCell* cell, Visitor& visitor) NodeVMScript* thisObject = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); - visitor.append(thisObject->m_cachedDirectExecutable); + visitor.append(thisObject->m_cachedExecutable); } NodeVMScriptConstructor::NodeVMScriptConstructor(VM& vm, Structure* structure) @@ -1390,9 +1464,9 @@ JSObject* NodeVMScript::createPrototype(VM& vm, JSGlobalObject* globalObject) return NodeVMScriptPrototype::create(vm, globalObject, NodeVMScriptPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); } -NodeVMScript* NodeVMScript::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, SourceCode source) +NodeVMScript* NodeVMScript::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, SourceCode source, ScriptOptions options) { - NodeVMScript* ptr = new (NotNull, allocateCell(vm)) NodeVMScript(vm, structure, source); + NodeVMScript* ptr = new (NotNull, allocateCell(vm)) NodeVMScript(vm, structure, source, options); ptr->finishCreation(vm); return ptr; } @@ -1639,4 +1713,38 @@ static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const Arg return program; } +static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, JSC::SourceCode source) +{ + auto& vm = JSC::getVM(globalObject); + JSC::CodeCache* cache = vm.codeCache(); + JSC::ParserError parserError; + JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); + JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; + JSC::BytecodeCacheError bytecodeCacheError; + FileSystem::FileHandle fileHandle; + return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); +} + +static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::SourceCode source) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); + RefPtr bytecode = getBytecode(globalObject, executable, source); + + if (UNLIKELY(!bytecode)) { + return throwVMError(globalObject, scope, "createCachedData failed"_s); + } + + std::span bytes = bytecode->span(); + auto* buffer = WebCore::createBuffer(globalObject, bytes); + + if (UNLIKELY(!buffer)) { + return throwVMError(globalObject, scope, "Failed to create Buffer for cached data"_s); + } + + return JSValue::encode(buffer); +} + } // namespace Bun From 737b04eb135d2f3178fad004a3fc30f6b8f30f1b Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 29 Apr 2025 17:57:17 -0700 Subject: [PATCH 05/76] Some progress on supporting the cachedData option in node:vm --- Makefile | 4 ++ src/bun.js/bindings/NodeVM.cpp | 125 +++++++++++++++++++++++---------- 2 files changed, 91 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 408c2af3ee3a..b354ec2af6c1 100644 --- a/Makefile +++ b/Makefile @@ -1183,6 +1183,8 @@ jsc-copy-headers: cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/SymbolObject.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/SymbolObject.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/JSGenerator.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/JSGenerator.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/bytecode/UnlinkedFunctionCodeBlock.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/UnlinkedFunctionCodeBlock.h + cp $(WEBKIT_DIR)/Source/JavaScriptCore/bytecode/GlobalCodeBlock.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/GlobalCodeBlock.h + cp $(WEBKIT_DIR)/Source/JavaScriptCore/bytecode/ProgramCodeBlock.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/ProgramCodeBlock.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/AggregateError.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/AggregateError.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/API/JSWeakValue.h $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/JSWeakValue.h find $(WEBKIT_RELEASE_DIR)/JavaScriptCore/Headers/JavaScriptCore/ -name "*.h" -exec cp {} $(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/ \; @@ -1234,6 +1236,8 @@ jsc-copy-headers-debug: cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/SymbolObject.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/SymbolObject.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/JSGenerator.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/JSGenerator.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/bytecode/UnlinkedFunctionCodeBlock.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/UnlinkedFunctionCodeBlock.h + cp $(WEBKIT_DIR)/Source/JavaScriptCore/bytecode/GlobalCodeBlock.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/GlobalCodeBlock.h + cp $(WEBKIT_DIR)/Source/JavaScriptCore/bytecode/ProgramCodeBlock.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/ProgramCodeBlock.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/runtime/AggregateError.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/AggregateError.h cp $(WEBKIT_DIR)/Source/JavaScriptCore/API/JSWeakValue.h $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/JSWeakValue.h find $(WEBKIT_DEBUG_DIR)/JavaScriptCore/Headers/JavaScriptCore/ -name "*.h" -exec cp {} $(WEBKIT_DEBUG_DIR)/JavaScriptCore/PrivateHeaders/JavaScriptCore/ \; diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index f6cd272cfad9..8c70444c199b 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -46,6 +46,9 @@ #include "JavaScriptCore/BytecodeCacheError.h" #include "wtf/FileHandle.h" +#include "JavaScriptCore/ProgramCodeBlock.h" +#include "JavaScriptCore/JIT.h" + namespace Bun { using namespace WebCore; @@ -146,7 +149,7 @@ class BaseOptions { return any; } - bool validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool* outProduceCachedData) + bool validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool& outProduceCachedData) { JSValue produceCachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "produceCachedData"_s)); if (produceCachedDataOpt && !produceCachedDataOpt.isUndefined()) { @@ -155,13 +158,13 @@ class BaseOptions { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.produceCachedData"_s, "boolean"_s, produceCachedDataOpt); return false; } - *outProduceCachedData = produceCachedDataOpt.asBoolean(); + outProduceCachedData = produceCachedDataOpt.asBoolean(); return true; } return false; } - bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options) + bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::vector& outCachedData) { JSValue cachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "cachedData"_s)); if (cachedDataOpt && !cachedDataOpt.isUndefined()) { @@ -171,32 +174,34 @@ class BaseOptions { return false; } - // If it's a cell, verify it's a Buffer, TypedArray, or DataView + // If it's a cell, verify it's a Buffer, TypedArray or DataView if (cachedDataOpt.isCell()) { - JSCell* cell = cachedDataOpt.asCell(); bool isValidType = false; - // Check if it's a Buffer, TypedArray, or DataView - if (cell->inherits() || cell->inherits()) { + if (auto* arrayBufferView = JSC::jsDynamicCast(cachedDataOpt)) { + if (!arrayBufferView->isDetached()) { + std::span span = arrayBufferView->span(); + outCachedData = { span.begin(), span.end() }; + isValidType = true; + } + } else if (auto* arrayBuffer = JSC::jsDynamicCast(cachedDataOpt)) { + std::span span = arrayBuffer->impl()->span(); + outCachedData = { span.begin(), span.end() }; isValidType = true; - } else if (JSC::JSArrayBufferView* view = JSC::jsDynamicCast(cachedDataOpt)) { - isValidType = !view->isDetached(); } if (!isValidType) { ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); return false; } - return true; - // TODO: actually use it - // this->cachedData = true; + return true; } } return false; } - bool validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional* outTimeout) + bool validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional& outTimeout) { JSValue timeoutOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "timeout"_s)); if (timeoutOpt && !timeoutOpt.isUndefined()) { @@ -209,7 +214,7 @@ class BaseOptions { V::validateInteger(scope, globalObject, timeoutOpt, "options.timeout"_s, jsNumber(1), jsNumber(std::numeric_limits().max()), &timeoutValue); RETURN_IF_EXCEPTION(scope, {}); - *outTimeout = timeoutValue; + outTimeout = timeoutValue; return true; } return false; @@ -220,8 +225,8 @@ class ScriptOptions : public BaseOptions { public: std::optional timeout = std::nullopt; bool importModuleDynamically = false; - bool cachedData = false; bool produceCachedData = false; + std::vector cachedData; bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { @@ -248,21 +253,19 @@ class ScriptOptions : public BaseOptions { any = true; } - if (validateTimeout(globalObject, vm, scope, options, &this->timeout)) { + if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { RETURN_IF_EXCEPTION(scope, false); any = true; } - if (validateProduceCachedData(globalObject, vm, scope, options, &this->produceCachedData)) { + if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) { RETURN_IF_EXCEPTION(scope, false); any = true; } - if (validateCachedData(globalObject, vm, scope, options)) { + if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) { RETURN_IF_EXCEPTION(scope, false); any = true; - // TODO: actually use it - this->cachedData = true; } } @@ -294,7 +297,7 @@ class RunningScriptOptions : public BaseOptions { any = true; } - if (validateTimeout(globalObject, vm, scope, options, &this->timeout)) { + if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { RETURN_IF_EXCEPTION(scope, false); any = true; } @@ -316,10 +319,10 @@ class RunningScriptOptions : public BaseOptions { class CompileFunctionOptions : public BaseOptions { public: - bool cachedData = false; - bool produceCachedData; - JSGlobalObject* parsingContext; + std::vector cachedData; + JSGlobalObject* parsingContext = nullptr; JSValue contextExtensions; + bool produceCachedData = false; bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { @@ -330,16 +333,14 @@ class CompileFunctionOptions : public BaseOptions { if (!optionsArg.isUndefined() && !optionsArg.isString()) { JSObject* options = asObject(optionsArg); - if (validateProduceCachedData(globalObject, vm, scope, options, &this->produceCachedData)) { + if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) { RETURN_IF_EXCEPTION(scope, false); any = true; } - if (validateCachedData(globalObject, vm, scope, options)) { + if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) { RETURN_IF_EXCEPTION(scope, false); any = true; - // TODO: actually use it - this->cachedData = true; } JSValue parsingContextValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "parsingContext"_s)); @@ -452,6 +453,8 @@ class NodeVMScript final : public JSC::JSDestructibleObject { JSC::ProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } bool cachedDataProduced() const { return m_cachedDataProduced; } void cachedDataProduced(bool value) { m_cachedDataProduced = value; } + TriState cachedDataRejected() const { return m_cachedDataRejected; } + void cachedDataRejected(TriState value) { m_cachedDataRejected = value; } DECLARE_VISIT_CHILDREN; @@ -462,11 +465,12 @@ class NodeVMScript final : public JSC::JSDestructibleObject { mutable JSC::WriteBarrier m_cachedExecutable; ScriptOptions m_options; bool m_cachedDataProduced = false; + TriState m_cachedDataRejected = TriState::Indeterminate; NodeVMScript(JSC::VM& vm, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options) : Base(vm, structure) , m_source(source) - , m_options(options) + , m_options(WTFMove(options)) { } @@ -760,9 +764,39 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); RETURN_IF_EXCEPTION(scope, {}); - NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, options); - if (options.produceCachedData) { + const bool produceCachedData = options.produceCachedData; + auto cachedData = WTFMove(options.cachedData); + auto filename = options.filename; + + NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, WTFMove(options)); + + if (!cachedData.empty()) { + JSC::ProgramExecutable* executable = script->cachedExecutable(); + if (!executable) { + executable = script->createExecutable(); + } + ASSERT(executable); + + JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? JSC::TaintedByWithScopeLexicallyScopedFeature : JSC::NoLexicallyScopedFeatures; + JSC::SourceCodeKey key(source, {}, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); + Ref cachedBytecode = JSC::CachedBytecode::create(cachedData, nullptr, {}); + JSC::UnlinkedProgramCodeBlock* unlinkedBlock = JSC::decodeCodeBlock(vm, key, WTFMove(cachedBytecode)); + JSC::JSScope* jsScope = globalObject->globalScope(); + JSC::CodeBlock* codeBlock = nullptr; + { + DeferGC deferGC(vm); + codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); + } + codeBlock->jitNextInvocation(); + JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); + if (compilationResult != JSC::CompilationResult::CompilationFailed) { + executable->installCode(codeBlock); + script->cachedDataRejected(TriState::False); + } else { + script->cachedDataRejected(TriState::True); + } + } else if (produceCachedData) { script->cacheBytecode(); // TODO(@heimskr): is there ever a case where bytecode production fails? script->cachedDataProduced(true); @@ -847,11 +881,6 @@ JSC_DEFINE_HOST_FUNCTION(scriptConstructorConstruct, (JSGlobalObject * globalObj return constructScript(globalObject, callFrame, callFrame->newTarget()); } -JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName)) -{ - return JSValue::encode(jsBoolean(true)); // TODO -} - JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = JSC::getVM(globalObject); @@ -988,6 +1017,26 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalOb return JSValue::encode(jsBoolean(script->cachedDataProduced())); } +JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue thisValue = JSValue::decode(thisValueEncoded); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + switch (script->cachedDataRejected()) { + case TriState::True: + return JSValue::encode(jsBoolean(true)); + case TriState::False: + return JSValue::encode(jsBoolean(false)); + default: + return JSValue::encode(jsUndefined()); + } +} + JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); @@ -1346,7 +1395,6 @@ class NodeVMScriptPrototype final : public JSNonFinalObject { STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, NodeVMScriptPrototype::Base); static const struct HashTableValue scriptPrototypeTableValues[] = { - { "cachedDataRejected"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataRejected, nullptr } }, { "createCachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptCreateCachedData, 1 } }, { "runInContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInContext, 2 } }, { "runInNewContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInNewContext, 2 } }, @@ -1354,6 +1402,7 @@ static const struct HashTableValue scriptPrototypeTableValues[] = { { "sourceMapURL"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetSourceMapURL, nullptr } }, { "cachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedData, nullptr } }, { "cachedDataProduced"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataProduced, nullptr } }, + { "cachedDataRejected"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataRejected, nullptr } }, }; // NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) @@ -1466,7 +1515,7 @@ JSObject* NodeVMScript::createPrototype(VM& vm, JSGlobalObject* globalObject) NodeVMScript* NodeVMScript::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, SourceCode source, ScriptOptions options) { - NodeVMScript* ptr = new (NotNull, allocateCell(vm)) NodeVMScript(vm, structure, source, options); + NodeVMScript* ptr = new (NotNull, allocateCell(vm)) NodeVMScript(vm, structure, source, WTFMove(options)); ptr->finishCreation(vm); return ptr; } From 9fa3989b26068299f2a95327cd603bb7691809ac Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 29 Apr 2025 18:01:55 -0700 Subject: [PATCH 06/76] Add cachedData support in node:vm (test-vm-cached-data.js) --- src/bun.js/bindings/NodeVM.cpp | 29 +++--- .../node/test/parallel/test-vm-cached-data.js | 96 +++++++++++++++++++ 2 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 test/js/node/test/parallel/test-vm-cached-data.js diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 8c70444c199b..bf0bd685a61e 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -782,19 +782,24 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT JSC::SourceCodeKey key(source, {}, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); Ref cachedBytecode = JSC::CachedBytecode::create(cachedData, nullptr, {}); JSC::UnlinkedProgramCodeBlock* unlinkedBlock = JSC::decodeCodeBlock(vm, key, WTFMove(cachedBytecode)); - JSC::JSScope* jsScope = globalObject->globalScope(); - JSC::CodeBlock* codeBlock = nullptr; - { - DeferGC deferGC(vm); - codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); - } - codeBlock->jitNextInvocation(); - JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); - if (compilationResult != JSC::CompilationResult::CompilationFailed) { - executable->installCode(codeBlock); - script->cachedDataRejected(TriState::False); - } else { + + if (!unlinkedBlock) { script->cachedDataRejected(TriState::True); + } else { + JSC::JSScope* jsScope = globalObject->globalScope(); + JSC::CodeBlock* codeBlock = nullptr; + { + DeferGC deferGC(vm); + codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); + } + codeBlock->jitNextInvocation(); + JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); + if (compilationResult != JSC::CompilationResult::CompilationFailed) { + executable->installCode(codeBlock); + script->cachedDataRejected(TriState::False); + } else { + script->cachedDataRejected(TriState::True); + } } } else if (produceCachedData) { script->cacheBytecode(); diff --git a/test/js/node/test/parallel/test-vm-cached-data.js b/test/js/node/test/parallel/test-vm-cached-data.js new file mode 100644 index 000000000000..39ae3fcdacd0 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-cached-data.js @@ -0,0 +1,96 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); +const spawnSync = require('child_process').spawnSync; + +function getSource(tag) { + return `(function ${tag}() { return '${tag}'; })`; +} + +function produce(source, count) { + count ||= 1; + + const out = spawnSync(process.execPath, [ '-e', ` + 'use strict'; + const assert = require('assert'); + const vm = require('vm'); + + var data; + for (var i = 0; i < ${count}; i++) { + var script = new vm.Script(process.argv[1], { + produceCachedData: true + }); + + assert(!script.cachedDataProduced || script.cachedData instanceof Buffer); + + if (script.cachedDataProduced) + data = script.cachedData.toString('base64'); + } + console.log(data); + `, source]); + + assert.strictEqual(out.status, 0, String(out.stderr)); + + return Buffer.from(out.stdout.toString(), 'base64'); +} + +function testProduceConsume() { + const source = getSource('original'); + + const data = produce(source); + + for (const cachedData of common.getArrayBufferViews(data)) { + // It should consume code cache + const script = new vm.Script(source, { + cachedData + }); + assert(!script.cachedDataRejected); + assert.strictEqual(script.runInThisContext()(), 'original'); + } +} +testProduceConsume(); + +function testProduceMultiple() { + const source = getSource('original'); + + produce(source, 3); +} +testProduceMultiple(); + +function testRejectInvalid() { + const source = getSource('invalid'); + + const data = produce(source); + + // It should reject invalid code cache + const script = new vm.Script(getSource('invalid_1'), { + cachedData: data + }); + assert(script.cachedDataRejected); + assert.strictEqual(script.runInThisContext()(), 'invalid_1'); +} +testRejectInvalid(); + +function testRejectSlice() { + const source = getSource('slice'); + + const data = produce(source).slice(4); + + const script = new vm.Script(source, { + cachedData: data + }); + assert(script.cachedDataRejected); +} +testRejectSlice(); + +// It should throw on non-Buffer cachedData +assert.throws(() => { + new vm.Script('function abc() {}', { + cachedData: 'ohai' + }); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /must be an instance of Buffer, TypedArray, or DataView/ +}); From 0e35e738cf85de2ba8e575f41120ebfb23e73c74 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 29 Apr 2025 18:03:23 -0700 Subject: [PATCH 07/76] Add test-vm-createcacheddata.js --- .../test/parallel/test-vm-createcacheddata.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/js/node/test/parallel/test-vm-createcacheddata.js diff --git a/test/js/node/test/parallel/test-vm-createcacheddata.js b/test/js/node/test/parallel/test-vm-createcacheddata.js new file mode 100644 index 000000000000..ba4d299b9c63 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-createcacheddata.js @@ -0,0 +1,22 @@ +'use strict'; + +require('../common'); + +const { Script } = require('vm'); +const assert = require('assert'); + +const source = 'function x() {} const y = x();'; + +const script = new Script(source); +let cachedData = script.createCachedData(); +assert(cachedData instanceof Buffer); + +assert(!new Script(source, { cachedData }).cachedDataRejected); + +script.runInNewContext(); + +for (let i = 0; i < 10; i += 1) { + cachedData = script.createCachedData(); + + assert(!new Script(source, { cachedData }).cachedDataRejected); +} From 0708bae30f8859ec61590859658e073951befeec Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 29 Apr 2025 18:12:49 -0700 Subject: [PATCH 08/76] Add test for createCachedData --- test/js/node/vm/vm.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts index 726dbde73703..d2fb0b0e1be4 100644 --- a/test/js/node/vm/vm.test.ts +++ b/test/js/node/vm/vm.test.ts @@ -687,3 +687,13 @@ resp.text().then((a) => { delete URL.prototype.ok; } }); + +test.only("can retrieve bytecode with createCachedData", () => { + const script = new Script("1 + 1;", { + produceCachedData: false, + }); + const cachedData = script.createCachedData(); + expect(cachedData).toBeDefined(); + expect(cachedData).toBeInstanceOf(Buffer); + expect(cachedData.byteLength).toBeGreaterThan(100); +}); From 20769c4500af79cb6c8e54ac02137dcec2c6efe5 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 29 Apr 2025 18:40:49 -0700 Subject: [PATCH 09/76] Update WebKit version --- cmake/tools/SetupWebKit.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/tools/SetupWebKit.cmake b/cmake/tools/SetupWebKit.cmake index 5552fdf651a5..e9fe5f720a0a 100644 --- a/cmake/tools/SetupWebKit.cmake +++ b/cmake/tools/SetupWebKit.cmake @@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use") option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading") if(NOT WEBKIT_VERSION) - set(WEBKIT_VERSION 11b10bb50bd69481fe4c7a4dd075eea4755b3fee) + set(WEBKIT_VERSION 53d4176ddc98ba721e50355826f58ec758766fa8) endif() string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX) From fed79629981bc3273e9e2ea0f88d7292cf0ef547 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 13:27:53 -0700 Subject: [PATCH 10/76] Address feedback --- src/bun.js/bindings/NodeVM.cpp | 12 +++++++----- test/js/node/vm/vm.test.ts | 9 ++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index bf0bd685a61e..1bd964538fc5 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -792,7 +792,6 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT DeferGC deferGC(vm); codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); } - codeBlock->jitNextInvocation(); JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); if (compilationResult != JSC::CompilationResult::CompilationFailed) { executable->installCode(codeBlock); @@ -1452,7 +1451,7 @@ void NodeVMScript::cacheBytecode() } m_cachedBytecode = getBytecode(globalObject(), m_cachedExecutable.get(), m_source); - m_cachedDataProduced = true; + m_cachedDataProduced = m_cachedBytecode != nullptr; } JSC::JSUint8Array* NodeVMScript::getBytecodeBuffer() @@ -1485,6 +1484,7 @@ void NodeVMScript::visitChildrenImpl(JSCell* cell, Visitor& visitor) ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); visitor.append(thisObject->m_cachedExecutable); + visitor.append(thisObject->m_cachedBytecodeBuffer); } NodeVMScriptConstructor::NodeVMScriptConstructor(VM& vm, Structure* structure) @@ -1773,6 +1773,9 @@ static RefPtr getBytecode(JSGlobalObject* globalObject, JSC JSC::CodeCache* cache = vm.codeCache(); JSC::ParserError parserError; JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); + if (!unlinked) { + return nullptr; + } JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; JSC::BytecodeCacheError bytecodeCacheError; FileSystem::FileHandle fileHandle; @@ -1794,9 +1797,8 @@ static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::S std::span bytes = bytecode->span(); auto* buffer = WebCore::createBuffer(globalObject, bytes); - if (UNLIKELY(!buffer)) { - return throwVMError(globalObject, scope, "Failed to create Buffer for cached data"_s); - } + RETURN_IF_EXCEPTION(scope, {}); + ASSERT(buffer); return JSValue::encode(buffer); } diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts index d2fb0b0e1be4..a1caa971579d 100644 --- a/test/js/node/vm/vm.test.ts +++ b/test/js/node/vm/vm.test.ts @@ -688,7 +688,7 @@ resp.text().then((a) => { } }); -test.only("can retrieve bytecode with createCachedData", () => { +test("can retrieve bytecode with createCachedData", () => { const script = new Script("1 + 1;", { produceCachedData: false, }); @@ -697,3 +697,10 @@ test.only("can retrieve bytecode with createCachedData", () => { expect(cachedData).toBeInstanceOf(Buffer); expect(cachedData.byteLength).toBeGreaterThan(100); }); + +test("can't use export syntax in vm.Script", () => { + expect(() => { + const script = new Script("export default {};"); + script.runInThisContext(); + }).toThrow(); +}); From e58a36f72763ff9b9c7030592c86ebffdc637b35 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 13:37:10 -0700 Subject: [PATCH 11/76] More cachedData tests --- test/js/node/vm/vm.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts index a1caa971579d..8129e244529b 100644 --- a/test/js/node/vm/vm.test.ts +++ b/test/js/node/vm/vm.test.ts @@ -704,3 +704,21 @@ test("can't use export syntax in vm.Script", () => { script.runInThisContext(); }).toThrow(); }); + +test("rejects invalid bytecode", () => { + const cachedData = Buffer.from("fhqwhgads"); + const script = new Script("1 + 1;", { + cachedData, + }); + expect(script.cachedDataRejected).toBeTrue(); +}); + +test("accepts valid bytecode", () => { + const source = "1 + 1;"; + const firstScript = new Script(source); + const cachedData = firstScript.createCachedData(); + const secondScript = new Script(source, { + cachedData, + }); + expect(secondScript.cachedDataRejected).toBeFalse(); +}); From cb482b3a7c892eb35a026d0ba56a89e06fe55896 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 13:44:47 -0700 Subject: [PATCH 12/76] You can never have too many tests --- test/js/node/vm/vm.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts index 8129e244529b..6566833e1bf1 100644 --- a/test/js/node/vm/vm.test.ts +++ b/test/js/node/vm/vm.test.ts @@ -722,3 +722,12 @@ test("accepts valid bytecode", () => { }); expect(secondScript.cachedDataRejected).toBeFalse(); }); + +test("can't use bytecode from a different script", () => { + const firstScript = new Script("1 + 1;"); + const cachedData = firstScript.createCachedData(); + const secondScript = new Script("2 + 2;", { + cachedData, + }); + expect(secondScript.cachedDataRejected).toBeTrue(); +}); From 8009728f902ec62ce0da6eb9b9017a8b99f91af3 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 14:20:20 -0700 Subject: [PATCH 13/76] Address feedback --- src/bun.js/bindings/NodeVM.cpp | 1 + test/js/node/vm/vm.test.ts | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 1bd964538fc5..6d39ef702b58 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -789,6 +789,7 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT JSC::JSScope* jsScope = globalObject->globalScope(); JSC::CodeBlock* codeBlock = nullptr; { + // JSC::ProgramCodeBlock::create() requires GC to be deferred. DeferGC deferGC(vm); codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); } diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts index 6566833e1bf1..ad0cec66c6de 100644 --- a/test/js/node/vm/vm.test.ts +++ b/test/js/node/vm/vm.test.ts @@ -688,21 +688,16 @@ resp.text().then((a) => { } }); -test("can retrieve bytecode with createCachedData", () => { - const script = new Script("1 + 1;", { - produceCachedData: false, - }); - const cachedData = script.createCachedData(); - expect(cachedData).toBeDefined(); - expect(cachedData).toBeInstanceOf(Buffer); - expect(cachedData.byteLength).toBeGreaterThan(100); -}); - test("can't use export syntax in vm.Script", () => { expect(() => { const script = new Script("export default {};"); script.runInThisContext(); - }).toThrow(); + }).toThrow({ name: "SyntaxError", message: "Unexpected keyword 'export'" }); + + expect(() => { + const script = new Script("export default {};"); + script.createCachedData(); + }).toThrow({ message: "createCachedData failed" }); }); test("rejects invalid bytecode", () => { @@ -715,12 +710,18 @@ test("rejects invalid bytecode", () => { test("accepts valid bytecode", () => { const source = "1 + 1;"; - const firstScript = new Script(source); + const firstScript = new Script(source, { + produceCachedData: false, + }); const cachedData = firstScript.createCachedData(); + expect(cachedData).toBeDefined(); + expect(cachedData).toBeInstanceOf(Buffer); const secondScript = new Script(source, { cachedData, }); expect(secondScript.cachedDataRejected).toBeFalse(); + expect(firstScript.runInThisContext()).toBe(2); + expect(secondScript.runInThisContext()).toBe(2); }); test("can't use bytecode from a different script", () => { @@ -730,4 +731,6 @@ test("can't use bytecode from a different script", () => { cachedData, }); expect(secondScript.cachedDataRejected).toBeTrue(); + expect(firstScript.runInThisContext()).toBe(2); + expect(secondScript.runInThisContext()).toBe(4); }); From fe6c6ef01d88a2935a538c8e5a4aeeaeb2d21905 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 14:26:58 -0700 Subject: [PATCH 14/76] Address feedback again --- test/js/node/vm/vm.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts index ad0cec66c6de..3850870d58cd 100644 --- a/test/js/node/vm/vm.test.ts +++ b/test/js/node/vm/vm.test.ts @@ -706,6 +706,7 @@ test("rejects invalid bytecode", () => { cachedData, }); expect(script.cachedDataRejected).toBeTrue(); + expect(script.runInThisContext()).toBe(2); }); test("accepts valid bytecode", () => { From 6b31f989fb5ea99d911aa7f3e6edd9f29360f6ed Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 15:02:22 -0700 Subject: [PATCH 15/76] Check for parser errors too --- src/bun.js/bindings/NodeVM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 6d39ef702b58..bfb923a3b58a 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -1774,7 +1774,7 @@ static RefPtr getBytecode(JSGlobalObject* globalObject, JSC JSC::CodeCache* cache = vm.codeCache(); JSC::ParserError parserError; JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); - if (!unlinked) { + if (!unlinked || !parserError.isValid()) { return nullptr; } JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; From 0bc239740b5263a483108ea336dbe064c9e5a7be Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 15:06:02 -0700 Subject: [PATCH 16/76] Add some RETURN_IF_EXCEPTIONs --- src/bun.js/bindings/NodeVM.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index bfb923a3b58a..023aff7351f5 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -1789,7 +1789,10 @@ static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::S auto scope = DECLARE_THROW_SCOPE(vm); JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); + RETURN_IF_EXCEPTION(scope, {}); + RefPtr bytecode = getBytecode(globalObject, executable, source); + RETURN_IF_EXCEPTION(scope, {}); if (UNLIKELY(!bytecode)) { return throwVMError(globalObject, scope, "createCachedData failed"_s); From cbd5e1be5c14d8131b641daff15d7a5a503216c6 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 15:11:29 -0700 Subject: [PATCH 17/76] Add some more RETURN_IF_EXCEPTIONs --- src/bun.js/bindings/NodeVM.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 023aff7351f5..29035b523038 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -244,6 +244,7 @@ class ScriptOptions : public BaseOptions { } any = true; } + RETURN_IF_EXCEPTION(scope, false); if (JSValue contextOriginOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextOrigin"_s))) { if (!contextOriginOpt.isUndefined() && !contextOriginOpt.isString()) { @@ -252,6 +253,7 @@ class ScriptOptions : public BaseOptions { } any = true; } + RETURN_IF_EXCEPTION(scope, false); if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { RETURN_IF_EXCEPTION(scope, false); From 66355712eb2fa95cdade50eea921f103d0892aba Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 30 Apr 2025 15:29:54 -0700 Subject: [PATCH 18/76] Adjust lifetimes and fix incorrect use of ParserError::isValid() --- src/bun.js/bindings/NodeVM.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 29035b523038..289b423a5db7 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -451,6 +451,7 @@ class NodeVMScript final : public JSC::JSDestructibleObject { JSC::JSUint8Array* getBytecodeBuffer(); const JSC::SourceCode& source() const { return m_source; } + std::vector& cachedData() { return m_options.cachedData; } RefPtr cachedBytecode() const { return m_cachedBytecode; } JSC::ProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } bool cachedDataProduced() const { return m_cachedDataProduced; } @@ -768,11 +769,12 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT RETURN_IF_EXCEPTION(scope, {}); const bool produceCachedData = options.produceCachedData; - auto cachedData = WTFMove(options.cachedData); auto filename = options.filename; NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, WTFMove(options)); + std::vector& cachedData = script->cachedData(); + if (!cachedData.empty()) { JSC::ProgramExecutable* executable = script->cachedExecutable(); if (!executable) { @@ -782,7 +784,7 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? JSC::TaintedByWithScopeLexicallyScopedFeature : JSC::NoLexicallyScopedFeatures; JSC::SourceCodeKey key(source, {}, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); - Ref cachedBytecode = JSC::CachedBytecode::create(cachedData, nullptr, {}); + Ref cachedBytecode = JSC::CachedBytecode::create(std::span(cachedData), nullptr, {}); JSC::UnlinkedProgramCodeBlock* unlinkedBlock = JSC::decodeCodeBlock(vm, key, WTFMove(cachedBytecode)); if (!unlinkedBlock) { @@ -1776,7 +1778,7 @@ static RefPtr getBytecode(JSGlobalObject* globalObject, JSC JSC::CodeCache* cache = vm.codeCache(); JSC::ParserError parserError; JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); - if (!unlinked || !parserError.isValid()) { + if (!unlinked || parserError.isValid()) { return nullptr; } JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; From 32a094dd06f30a660923436fef50ac42bc03bcbd Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 1 May 2025 13:58:42 -0700 Subject: [PATCH 19/76] Initial work on vm.Module --- src/bun.js/bindings/NodeVM.cpp | 179 ++++++++++++++++++ src/bun.js/bindings/NodeVM.h | 2 + src/bun.js/bindings/ZigGlobalObject.h | 1 + .../bindings/webcore/DOMClientIsoSubspaces.h | 1 + src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 1 + src/js/node/vm.ts | 43 ++++- 6 files changed, 224 insertions(+), 3 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 289b423a5db7..18e856788ff7 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -1585,6 +1585,11 @@ void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) [](const JSC::LazyProperty::Initializer& init) { init.set(createNodeVMGlobalObjectStructure(init.vm)); }); + + globalObject->m_JSNodeVMModuleStructure.initLater( + [](const JSC::LazyProperty::Initializer& init) { + init.set(Bun::createNodeVMModuleStructure(init.vm, init.owner)); + }); } bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot) @@ -1811,4 +1816,178 @@ static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::S return JSValue::encode(buffer); } +JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetNamespace); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetError); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleInstantiate); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleEvaluate); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleLink); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport); + +static const HashTableValue JSNodeVMModulePrototypeTableValues[] = { + { "identifier"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } }, + { "getStatus"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, + { "getNamespace"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetNamespace, 0 } }, + { "getError"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetError, 0 } }, + { "instantiate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleInstantiate, 0 } }, + { "evaluate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleEvaluate, 2 } }, + { "getModuleRequests"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetModuleRequests, 0 } }, + { "link"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleLink, 2 } }, + { "createCachedData"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleCreateCachedData, 0 } }, + { "setExport"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleSetExport, 2 } }, +}; + +class JSNodeVMModulePrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSNodeVMModulePrototype* create(VM& vm, Structure* structure) + { + JSNodeVMModulePrototype* prototype = new (NotNull, allocateCell(vm)) JSNodeVMModulePrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + DECLARE_INFO; + + static constexpr bool needsDestruction = false; + static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSNodeVMModulePrototype, Base); + return &vm.plainObjectSpace(); + } + +private: + JSNodeVMModulePrototype(VM& vm, Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(VM& vm) + { + Base::finishCreation(vm); + ASSERT(inherits(info())); + reifyStaticProperties(vm, info(), JSNodeVMModulePrototypeTableValues, *this); + this->structure()->setMayBePrototype(true); + } +}; + +class JSNodeVMModule : public JSC::JSDestructibleObject { +public: + enum class Status : uint8_t { + Unlinked, + Linking, + Linked, + Evaluating, + Evaluated, + Errored + }; + + using Base = JSC::JSDestructibleObject; + static JSNodeVMModule* create(JSC::VM& vm, JSC::Structure* structure) + { + auto* object = new (JSC::allocateCell(vm)) JSNodeVMModule(vm, structure); + object->finishCreation(vm); + return object; + } + + static JSNodeVMModule* create(JSC::VM& vm, Zig::GlobalObject* globalObject) + { + auto* structure = globalObject->m_JSNodeVMModuleStructure.getInitializedOnMainThread(globalObject); + return create(vm, structure); + } + + static void destroy(JSC::JSCell* cell) + { + static_cast(cell)->JSNodeVMModule::~JSNodeVMModule(); + } + + ~JSNodeVMModule() + { + } + + JSNodeVMModule(JSC::VM& vm, JSC::Structure* structure) + : JSC::JSDestructibleObject(vm, structure) + { + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSNodeVMModule.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNodeVMModule = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSNodeVMModule.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSNodeVMModule = std::forward(space); }); + } + + static Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) + { + auto* structure = JSC::Structure::create(vm, globalObject, globalObject->objectPrototype(), JSC::TypeInfo(JSC::ObjectType, StructureFlags), JSNodeVMModulePrototype::info()); + auto* prototype = JSNodeVMModulePrototype::create(vm, structure); + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + void finishCreation(JSC::VM& vm) + { + Base::finishCreation(vm); + } + + const WTF::String& identifier() const { return m_identifier; } + Status status() const { return m_status; } + void status(Status value) { m_status = value; } + +private: + WTF::String m_identifier; + Status m_status = Status::Unlinked; +}; + +JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + return JSValue::encode(JSC::jsString(globalObject->vm(), thisObject->identifier())); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + + switch (thisObject->status()) { + case JSNodeVMModule::Status::Unlinked: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("unlinked"_s))); + case JSNodeVMModule::Status::Linking: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linking"_s))); + case JSNodeVMModule::Status::Linked: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linked"_s))); + case JSNodeVMModule::Status::Evaluating: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluating"_s))); + case JSNodeVMModule::Status::Evaluated: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluated"_s))); + case JSNodeVMModule::Status::Errored: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("errored"_s))); + default: + return JSC::encodedJSUndefined(); + } +} + +const JSC::ClassInfo JSNodeVMModule::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNodeVMModule) }; +const JSC::ClassInfo JSNodeVMModulePrototype::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNodeVMModulePrototype) }; + +JSC::Structure* createNodeVMModuleStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + return JSNodeVMModule::createStructure(vm, globalObject); +} + } // namespace Bun diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 5e0e692170ba..64ac568d438d 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -60,4 +60,6 @@ JSC_DECLARE_HOST_FUNCTION(vmModule_isContext); JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext); JSC_DECLARE_HOST_FUNCTION(vmModuleRunInThisContext); +JSC::Structure* createNodeVMModuleStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + } // namespace Bun diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index ced926324f52..9aca162717fc 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -592,6 +592,7 @@ class GlobalObject : public Bun::GlobalScope { V(public, LazyPropertyOfGlobalObject, m_JSBunRequestParamsPrototype) \ \ V(public, LazyPropertyOfGlobalObject, m_JSNodeHTTPServerSocketStructure) \ + V(public, LazyPropertyOfGlobalObject, m_JSNodeVMModuleStructure) \ V(public, LazyPropertyOfGlobalObject, m_statValues) \ V(public, LazyPropertyOfGlobalObject, m_bigintStatValues) \ V(public, LazyPropertyOfGlobalObject, m_statFsValues) \ diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index bd97f654e9fe..a4149816647d 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -64,6 +64,7 @@ class DOMClientIsoSubspaces { std::unique_ptr m_clientSubspaceForFunctionTemplate; std::unique_ptr m_clientSubspaceForV8Function; std::unique_ptr m_clientSubspaceForJSNodeHTTPServerSocket; + std::unique_ptr m_clientSubspaceForJSNodeVMModule; std::unique_ptr m_clientSubspaceForNodeVMGlobalObject; std::unique_ptr m_clientSubspaceForJSS3Bucket; std::unique_ptr m_clientSubspaceForJSS3File; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index a7b07760f9a6..28c24e4a7f6b 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -61,6 +61,7 @@ class DOMIsoSubspaces { std::unique_ptr m_subspaceForJSMIMEParams; std::unique_ptr m_subspaceForV8Function; std::unique_ptr m_subspaceForJSNodeHTTPServerSocket; + std::unique_ptr m_subspaceForJSNodeVMModule; std::unique_ptr m_subspaceForNodeVMGlobalObject; std::unique_ptr m_subspaceForJSS3Bucket; std::unique_ptr m_subspaceForJSS3File; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index b277a6143276..661629ff0d1a 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -1,9 +1,12 @@ // Hardcoded module "node:vm" const { throwNotImplemented } = require("internal/shared"); - +const { validateObject, validateString } = require("internal/validators"); const vm = $cpp("NodeVM.cpp", "Bun::createNodeVMBinding"); const ObjectFreeze = Object.freeze; +const ObjectDefineProperty = Object.defineProperty; + +const kPerContextModuleId = Symbol("kPerContextModuleId"); const { createContext, isContext, Script, runInNewContext, runInThisContext, compileFunction } = vm; @@ -19,9 +22,43 @@ function measureMemory() { throwNotImplemented("node:vm measureMemory"); } +let globalModuleId = 0; +const defaultModuleName = "vm:module"; + class Module { - constructor() { - throwNotImplemented("node:vm.Module"); + constructor(options) { + if (new.target === Module) { + throw new TypeError("Module is not a constructor"); + } + + const { context } = options; + + if (context !== undefined) { + validateObject(context, "context"); + if (!isContext(context)) { + throw $ERR_INVALID_ARG_TYPE("options.context", "vm.Context", context); + } + } + + let { identifier } = options; + if (identifier !== undefined) { + validateString(identifier, "options.identifier"); + } else if (context === undefined) { + identifier = `${defaultModuleName}(${globalModuleId++})`; + } else if (context[kPerContextModuleId] !== undefined) { + const curId = context[kPerContextModuleId]; + identifier = `${defaultModuleName}(${curId})`; + context[kPerContextModuleId] += 1; + } else { + identifier = `${defaultModuleName}(0)`; + ObjectDefineProperty(context, kPerContextModuleId, { + __proto__: null, + value: 1, + writable: true, + enumerable: false, + configurable: true, + }); + } } } From 10d14a3eaac7bef0962bb63593d5b7a2fde98eb6 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 1 May 2025 14:00:32 -0700 Subject: [PATCH 20/76] Add a null check --- src/bun.js/bindings/NodeVM.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 289b423a5db7..8146a46774f0 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -184,7 +184,7 @@ class BaseOptions { outCachedData = { span.begin(), span.end() }; isValidType = true; } - } else if (auto* arrayBuffer = JSC::jsDynamicCast(cachedDataOpt)) { + } else if (auto* arrayBuffer = JSC::jsDynamicCast(cachedDataOpt); arrayBuffer && arrayBuffer->impl()) { std::span span = arrayBuffer->impl()->span(); outCachedData = { span.begin(), span.end() }; isValidType = true; @@ -224,7 +224,6 @@ class BaseOptions { class ScriptOptions : public BaseOptions { public: std::optional timeout = std::nullopt; - bool importModuleDynamically = false; bool produceCachedData = false; std::vector cachedData; From bc1654bf7a7e0e9f39101f10eb164bcf0980a169 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 1 May 2025 18:27:45 -0700 Subject: [PATCH 21/76] Some more work on vm.Module --- src/bun.js/bindings/ErrorCode.cpp | 28 +++ src/bun.js/bindings/ErrorCode.ts | 9 +- src/bun.js/bindings/NodeVM.cpp | 158 ++++++++++++--- src/bun.js/bindings/ZigGlobalObject.h | 5 + src/js/builtins.d.ts | 6 + src/js/node/vm.ts | 276 +++++++++++++++++++++++++- 6 files changed, 449 insertions(+), 33 deletions(-) diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 03b3ae176019..7b4bf316d39e 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -2208,6 +2208,26 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_CHILD_PROCESS_STDIO_MAXBUFFER, message)); } + case Bun::ErrorCode::ERR_VM_MODULE_STATUS: { + auto arg0 = callFrame->argument(1); + auto str0 = arg0.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto message = makeString("Module status "_s, str0); + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_STATUS, message)); + } + + case Bun::ErrorCode::ERR_VM_MODULE_LINK_FAILURE: { + auto arg0 = callFrame->argument(1); + auto message = arg0.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto cause = callFrame->argument(2); + JSObject* error = createError(globalObject, ErrorCode::ERR_VM_MODULE_LINK_FAILURE, message); + RETURN_IF_EXCEPTION(scope, {}); + error->putDirect(vm, Identifier::fromString(vm, "cause"_s), cause); + RETURN_IF_EXCEPTION(scope, {}); + return JSC::JSValue::encode(error); + } + case ErrorCode::ERR_IPC_DISCONNECTED: return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_IPC_DISCONNECTED, "IPC channel is already disconnected"_s)); case ErrorCode::ERR_SERVER_NOT_RUNNING: @@ -2314,6 +2334,14 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_SOCKET_ASSIGNED, "Socket already assigned"_s)); case ErrorCode::ERR_STREAM_RELEASE_LOCK: return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_STREAM_RELEASE_LOCK, "Stream reader cancelled via releaseLock()"_s)); + case ErrorCode::ERR_VM_MODULE_ALREADY_LINKED: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_ALREADY_LINKED, "Module has already been linked"_s)); + case ErrorCode::ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA, "Cached data cannot be created for a module which has been evaluated"_s)); + case ErrorCode::ERR_VM_MODULE_NOT_MODULE: + 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)); default: { break; diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index faf00552a188..ada38c4eeeae 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -242,8 +242,6 @@ const errors: ErrorCodeMapping = [ ["ERR_UNKNOWN_ENCODING", TypeError], ["ERR_UNKNOWN_SIGNAL", TypeError], ["ERR_USE_AFTER_CLOSE", Error], - ["ERR_VM_MODULE_CACHED_DATA_REJECTED", Error], - ["ERR_VM_MODULE_LINK_FAILURE", Error], ["ERR_WASI_NOT_STARTED", Error], ["ERR_WORKER_INIT_FAILED", Error], ["ERR_ZLIB_INITIALIZATION_FAILED", Error], @@ -275,5 +273,12 @@ const errors: ErrorCodeMapping = [ ["HPE_INVALID_EOF_STATE", Error], ["HPE_INVALID_METHOD", Error], ["HPE_INTERNAL", Error], + ["ERR_VM_MODULE_STATUS", Error], + ["ERR_VM_MODULE_ALREADY_LINKED", Error], + ["ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA", Error], + ["ERR_VM_MODULE_NOT_MODULE", Error], + ["ERR_VM_MODULE_DIFFERENT_CONTEXT", Error], + ["ERR_VM_MODULE_LINK_FAILURE", Error], + ["ERR_VM_MODULE_CACHED_DATA_REJECTED", Error], ]; export default errors; diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 73a5fe59cd04..d299f0f2b3a1 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -1540,31 +1540,6 @@ void NodeVMScript::destroy(JSCell* cell) static_cast(cell)->NodeVMScript::~NodeVMScript(); } -JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject) -{ - VM& vm = globalObject->vm(); - auto* obj = constructEmptyObject(globalObject); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Script"_s)), - defaultGlobalObject(globalObject)->NodeVMScript(), 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); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "isContext"_s)), - JSC::JSFunction::create(vm, globalObject, 0, "isContext"_s, vmModule_isContext, ImplementationVisibility::Public), 0); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "runInNewContext"_s)), - JSC::JSFunction::create(vm, globalObject, 0, "runInNewContext"_s, vmModuleRunInNewContext, ImplementationVisibility::Public), 0); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "runInThisContext"_s)), - JSC::JSFunction::create(vm, globalObject, 0, "runInThisContext"_s, vmModuleRunInThisContext, ImplementationVisibility::Public), 0); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "compileFunction"_s)), - JSC::JSFunction::create(vm, globalObject, 0, "compileFunction"_s, vmModuleCompileFunction, ImplementationVisibility::Public), 0); - return obj; -} - void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) { globalObject->m_NodeVMScriptClassStructure.initLater( @@ -1816,6 +1791,7 @@ static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::S } JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetNamespace); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetError); @@ -1829,6 +1805,7 @@ JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport); static const HashTableValue JSNodeVMModulePrototypeTableValues[] = { { "identifier"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } }, { "getStatus"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, + { "getStatusString"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, { "getNamespace"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetNamespace, 0 } }, { "getError"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetError, 0 } }, { "instantiate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleInstantiate, 0 } }, @@ -1947,10 +1924,13 @@ class JSNodeVMModule : public JSC::JSDestructibleObject { const WTF::String& identifier() const { return m_identifier; } Status status() const { return m_status; } void status(Status value) { m_status = value; } + JSObject* namespace_() const { return m_namespace.get(); } + void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } private: WTF::String m_identifier; Status m_status = Status::Unlinked; + mutable WriteBarrier m_namespace; }; JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) @@ -1959,6 +1939,12 @@ JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globa return JSValue::encode(JSC::jsString(globalObject->vm(), thisObject->identifier())); } +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(JSC::jsNumber(static_cast(thisObject->status()))); +} + JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = jsCast(callFrame->thisValue()); @@ -1981,6 +1967,64 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalO } } +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject->namespace_()); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject); +} + +template +void JSNodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSNodeVMModule* vmModule = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(vmModule, info()); + Base::visitChildren(vmModule, visitor); +} + +DEFINE_VISIT_CHILDREN(JSNodeVMModule); + const JSC::ClassInfo JSNodeVMModule::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNodeVMModule) }; const JSC::ClassInfo JSNodeVMModulePrototype::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNodeVMModulePrototype) }; @@ -1989,4 +2033,68 @@ JSC::Structure* createNodeVMModuleStructure(JSC::VM& vm, JSC::JSGlobalObject* gl return JSNodeVMModule::createStructure(vm, globalObject); } +JSC_DEFINE_HOST_FUNCTION(vmIsModuleNamespaceObject, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + return JSValue::encode(JSC::jsBoolean(false)); // TODO(@heimskr): implement + + // JSValue argument = callFrame->argument(0); + // if (!argument.isObject()) { + // return JSValue::encode(JSC::jsBoolean(false)); + // } + + // JSObject* object = asObject(argument); +} + +JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + auto* obj = constructEmptyObject(globalObject); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Script"_s)), + defaultGlobalObject(globalObject)->NodeVMScript(), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Module"_s)), + defaultGlobalObject(globalObject)->NodeVMModule(), 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); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "isContext"_s)), + JSC::JSFunction::create(vm, globalObject, 0, "isContext"_s, vmModule_isContext, ImplementationVisibility::Public), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "runInNewContext"_s)), + JSC::JSFunction::create(vm, globalObject, 0, "runInNewContext"_s, vmModuleRunInNewContext, ImplementationVisibility::Public), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "runInThisContext"_s)), + JSC::JSFunction::create(vm, globalObject, 0, "runInThisContext"_s, vmModuleRunInThisContext, ImplementationVisibility::Public), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "compileFunction"_s)), + JSC::JSFunction::create(vm, globalObject, 0, "compileFunction"_s, vmModuleCompileFunction, ImplementationVisibility::Public), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "isModuleNamespaceObject"_s)), + JSC::JSFunction::create(vm, globalObject, 0, "isModuleNamespaceObject"_s, vmIsModuleNamespaceObject, ImplementationVisibility::Public), 1); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kUnlinked"_s)), + JSC::jsNumber(static_cast(JSNodeVMModule::Status::Unlinked)), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kLinking"_s)), + JSC::jsNumber(static_cast(JSNodeVMModule::Status::Linking)), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kLinked"_s)), + JSC::jsNumber(static_cast(JSNodeVMModule::Status::Linked)), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kEvaluating"_s)), + JSC::jsNumber(static_cast(JSNodeVMModule::Status::Evaluating)), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kEvaluated"_s)), + JSC::jsNumber(static_cast(JSNodeVMModule::Status::Evaluated)), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kErrored"_s)), + JSC::jsNumber(static_cast(JSNodeVMModule::Status::Errored)), 0); + return obj; +} + } // namespace Bun diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 9aca162717fc..d592831c0d81 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -249,6 +249,10 @@ class GlobalObject : public Bun::GlobalScope { JSC::JSObject* NodeVMScript() const { return m_NodeVMScriptClassStructure.constructorInitializedOnMainThread(this); } JSC::JSValue NodeVMScriptPrototype() const { return m_NodeVMScriptClassStructure.prototypeInitializedOnMainThread(this); } + JSC::Structure* NodeVMModuleStructure() const { return m_NodeVMModuleClassStructure.getInitializedOnMainThread(this); } + JSC::JSObject* NodeVMModule() const { return m_NodeVMModuleClassStructure.constructorInitializedOnMainThread(this); } + JSC::JSValue NodeVMModulePrototype() const { return m_NodeVMModuleClassStructure.prototypeInitializedOnMainThread(this); } + JSC::JSMap* readableStreamNativeMap() const { return m_lazyReadableStreamPrototypeMap.getInitializedOnMainThread(this); } JSC::JSMap* requireMap() const { return m_requireMap.getInitializedOnMainThread(this); } JSC::JSMap* esmRegistryMap() const { return m_esmRegistryMap.getInitializedOnMainThread(this); } @@ -514,6 +518,7 @@ class GlobalObject : public Bun::GlobalScope { V(private, LazyClassStructure, m_callSiteStructure) \ V(public, LazyClassStructure, m_JSBufferClassStructure) \ V(public, LazyClassStructure, m_NodeVMScriptClassStructure) \ + V(public, LazyClassStructure, m_NodeVMModuleClassStructure) \ V(public, LazyClassStructure, m_JSX509CertificateClassStructure) \ V(public, LazyClassStructure, m_JSSignClassStructure) \ V(public, LazyClassStructure, m_JSVerifyClassStructure) \ diff --git a/src/js/builtins.d.ts b/src/js/builtins.d.ts index 89a2dc9191d8..28ad7eaf27f6 100644 --- a/src/js/builtins.d.ts +++ b/src/js/builtins.d.ts @@ -768,6 +768,12 @@ declare function $ERR_HTTP_BODY_NOT_ALLOWED(): Error; declare function $ERR_HTTP_SOCKET_ASSIGNED(): Error; declare function $ERR_DIR_CLOSED(): Error; declare function $ERR_INVALID_MIME_SYNTAX(production: string, str: string, invalidIndex: number | -1): TypeError; +declare function $ERR_VM_MODULE_STATUS(reason: string): Error; +declare function $ERR_VM_MODULE_ALREADY_LINKED(): Error; +declare function $ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA(): Error; +declare function $ERR_VM_MODULE_NOT_MODULE(): Error; +declare function $ERR_VM_MODULE_DIFFERENT_CONTEXT(): Error; +declare function $ERR_VM_MODULE_LINK_FAILURE(message: string, cause: Error): Error; /** * Convert a function to a class-like object. diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 661629ff0d1a..ac3664dfcf04 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -1,14 +1,49 @@ // Hardcoded module "node:vm" const { throwNotImplemented } = require("internal/shared"); -const { validateObject, validateString } = require("internal/validators"); +const { + validateObject, + validateString, + validateUint32, + validateBoolean, + validateInt32, + validateBuffer, + validateFunction, +} = require("internal/validators"); +const { SafePromiseAll } = require("internal/primordials"); + const vm = $cpp("NodeVM.cpp", "Bun::createNodeVMBinding"); const ObjectFreeze = Object.freeze; const ObjectDefineProperty = Object.defineProperty; +const ArrayPrototypeMap = Array.prototype.map; +const PromisePrototypeThen = Promise.prototype.then; +const PromiseResolve = Promise.resolve; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ReflectApply = Reflect.apply; const kPerContextModuleId = Symbol("kPerContextModuleId"); +const kNative = Symbol("kNative"); +const kContext = Symbol("kContext"); +const kLink = Symbol("kLink"); +const kDependencySpecifiers = Symbol("kDependencySpecifiers"); +const kNoError = Symbol("kNoError"); + +const kEmptyObject = Object.freeze(Object.create(null)); -const { createContext, isContext, Script, runInNewContext, runInThisContext, compileFunction } = vm; +const { + Script, + Module: ModuleNative, + createContext, + isContext, + runInNewContext, + runInThisContext, + compileFunction, + isModuleNamespaceObject, + kUnlinked, + kLinked, + kEvaluated, + kErrored, +} = vm; function runInContext(code, context, options) { return new Script(code, options).runInContext(context); @@ -31,7 +66,7 @@ class Module { throw new TypeError("Module is not a constructor"); } - const { context } = options; + const { context, sourceText, syntheticExportNames, syntheticEvaluationSteps } = options; if (context !== undefined) { validateObject(context, "context"); @@ -59,12 +94,220 @@ class Module { configurable: true, }); } + + let registry = { __proto__: null }; + if (sourceText !== undefined) { + this[kNative] = new ModuleNative( + identifier, + context, + sourceText, + options.lineOffset, + options.columnOffset, + options.cachedData, + ); + 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[kContext] = context; + } + + get identifier() { + return this[kNative].identifier; + } + + get context() { + return this[kContext]; + } + + get status() { + return this[kNative].getStatus(); + } + + get namespace() { + if (this[kNative].getStatusCode() < kLinked) { + throw $ERR_VM_MODULE_STATUS("must not be unlinked or linking"); + } + + return this[kNative].getNamespace(); + } + + get error() { + if (this[kNative].getStatusCode() !== kErrored) { + throw $ERR_VM_MODULE_STATUS("must be errored"); + } + + return this[kNative].getError(); + } + + async link(linker) { + if (this[kNative].getStatusCode() === kLinked) { + throw $ERR_VM_MODULE_ALREADY_LINKED(); + } + + if (this[kNative].getStatusCode() !== kUnlinked) { + throw $ERR_VM_MODULE_STATUS("must be unlinked"); + } + + await this[kLink](linker); + this[kNative].instantiate(); + } + + async evaluate(options = kEmptyObject) { + validateObject(options, "options"); + + let timeout = options.timeout; + if (timeout === undefined) { + timeout = -1; + } else { + validateUint32(timeout, "options.timeout", true); + } + const { breakOnSigint = false } = options; + validateBoolean(breakOnSigint, "options.breakOnSigint"); + const status = this[kNative].getStatus(); + if (status !== kLinked && status !== kEvaluated && status !== kErrored) { + throw $ERR_VM_MODULE_STATUS("must be one of linked, evaluated, or errored"); + } + await this[kNative].evaluate(timeout, breakOnSigint); } } -class SourceTextModule { - constructor() { - throwNotImplemented("node:vm.SourceTextModule"); +class SourceTextModule extends Module { + #error: any = kNoError; + #statusOverride: any; + + constructor(sourceText, options = kEmptyObject) { + validateString(sourceText, "sourceText"); + validateObject(options, "options"); + + const { + lineOffset = 0, + columnOffset = 0, + initializeImportMeta, + importModuleDynamically, + context, + identifier, + cachedData, + } = options; + + validateInt32(lineOffset, "options.lineOffset"); + validateInt32(columnOffset, "options.columnOffset"); + + if (initializeImportMeta !== undefined) { + validateFunction(initializeImportMeta, "options.initializeImportMeta"); + } + + if (importModuleDynamically !== undefined) { + validateFunction(importModuleDynamically, "options.importModuleDynamically"); + } + + if (cachedData !== undefined) { + validateBuffer(cachedData, "options.cachedData"); + } + + super({ + sourceText, + context, + identifier, + lineOffset, + columnOffset, + cachedData, + initializeImportMeta, + importModuleDynamically, + }); + + this[kDependencySpecifiers] = undefined; + } + + async [kLink](linker) { + this.#statusOverride = "linking"; + + const moduleRequests = this[kNative].getModuleRequests(); + // Iterates the module requests and links with the linker. + // Specifiers should be aligned with the moduleRequests array in order. + const specifiers = Array(moduleRequests.length); + const modulePromises = Array(moduleRequests.length); + // Iterates with index to avoid calling into userspace with `Symbol.iterator`. + for (let idx = 0; idx < moduleRequests.length; idx++) { + const { specifier, attributes } = moduleRequests[idx]; + + const linkerResult = linker(specifier, this, { + attributes, + assert: attributes, + }); + const modulePromise = PromisePrototypeThen.$call(PromiseResolve.$call(linkerResult), async mod => { + if (!isModule(mod)) { + throw $ERR_VM_MODULE_NOT_MODULE(); + } + if (mod.context !== this.context) { + throw $ERR_VM_MODULE_DIFFERENT_CONTEXT(); + } + if (mod.status === "errored") { + throw $ERR_VM_MODULE_LINK_FAILURE(`request for '${specifier}' resolved to an errored mod`, mod.error); + } + if (mod.status === "unlinked") { + await mod[kLink](linker); + } + return mod[kNative]; + }); + modulePromises[idx] = modulePromise; + specifiers[idx] = specifier; + } + + try { + const modules = await SafePromiseAll(modulePromises); + this[kNative].link(specifiers, modules); + } catch (e) { + this.#error = e; + throw e; + } finally { + this.#statusOverride = undefined; + } + } + + get dependencySpecifiers() { + this[kDependencySpecifiers] ??= ObjectFreeze( + ArrayPrototypeMap.$call(this[kNative].getModuleRequests(), request => request.specifier), + ); + return this[kDependencySpecifiers]; + } + + get status() { + if (this.#error !== kNoError) { + return "errored"; + } + if (this.#statusOverride) { + return this.#statusOverride; + } + return super.status; + } + + get error() { + if (this.#error !== kNoError) { + return this.#error; + } + return super.error; + } + + createCachedData() { + const { status } = this; + if (status === "evaluating" || status === "evaluated" || status === "errored") { + throw $ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA(); + } + return this[kNative].createCachedData(); } } @@ -80,6 +323,27 @@ const constants = { DONT_CONTEXTIFY: Symbol("vm_context_no_contextify"), }; +function isModule(object) { + return typeof object === "object" && object !== null && ObjectPrototypeHasOwnProperty.$call(object, kNative); +} + +function importModuleDynamicallyWrap(importModuleDynamically) { + const importModuleDynamicallyWrapper = async (...args) => { + const m = await ReflectApply(importModuleDynamically, this, args); + if (isModuleNamespaceObject(m)) { + return m; + } + if (!isModule(m)) { + throw $ERR_VM_MODULE_NOT_MODULE(); + } + if (m.status === "errored") { + throw m.error; + } + return m.namespace; + }; + return importModuleDynamicallyWrapper; +} + export default { createContext, runInContext, From 62dfa7499725e7bb0566d981a58ca534a0785c0a Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 1 May 2025 19:38:49 -0700 Subject: [PATCH 22/76] yay, it links --- src/bun.js/bindings/NodeVM.cpp | 297 +++++++++++------- src/bun.js/bindings/NodeVM.h | 2 - src/bun.js/bindings/ZigGlobalObject.h | 1 - .../bindings/webcore/DOMClientIsoSubspaces.h | 2 +- src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 2 +- src/js/node/vm.ts | 3 +- 6 files changed, 185 insertions(+), 122 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index d299f0f2b3a1..f8b3a1003dd4 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -727,7 +727,7 @@ void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) } static EncodedJSValue -constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = JSValue()) +constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {}) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -1369,9 +1369,9 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_isContext, (JSGlobalObject * globalObject, Cal return JSValue::encode(jsBoolean(isContext)); } -class NodeVMScriptPrototype final : public JSNonFinalObject { +class NodeVMScriptPrototype final : public JSC::JSNonFinalObject { public: - using Base = JSNonFinalObject; + using Base = JSC::JSNonFinalObject; static NodeVMScriptPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure) { @@ -1540,32 +1540,6 @@ void NodeVMScript::destroy(JSCell* cell) static_cast(cell)->NodeVMScript::~NodeVMScript(); } -void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) -{ - globalObject->m_NodeVMScriptClassStructure.initLater( - [](LazyClassStructure::Initializer& init) { - auto prototype = NodeVMScript::createPrototype(init.vm, init.global); - auto* structure = NodeVMScript::createStructure(init.vm, init.global, prototype); - auto* constructorStructure = NodeVMScriptConstructor::createStructure( - init.vm, init.global, init.global->m_functionPrototype.get()); - auto* constructor = NodeVMScriptConstructor::create( - init.vm, init.global, constructorStructure, prototype); - init.setPrototype(prototype); - init.setStructure(structure); - init.setConstructor(constructor); - }); - - globalObject->m_cachedNodeVMGlobalObjectStructure.initLater( - [](const JSC::LazyProperty::Initializer& init) { - init.set(createNodeVMGlobalObjectStructure(init.vm)); - }); - - globalObject->m_JSNodeVMModuleStructure.initLater( - [](const JSC::LazyProperty::Initializer& init) { - init.set(Bun::createNodeVMModuleStructure(init.vm, init.owner)); - }); -} - bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot) { @@ -1802,7 +1776,7 @@ JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleLink); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport); -static const HashTableValue JSNodeVMModulePrototypeTableValues[] = { +static const HashTableValue NodeVMModulePrototypeTableValues[] = { { "identifier"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } }, { "getStatus"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, { "getStatusString"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, @@ -1816,31 +1790,31 @@ static const HashTableValue JSNodeVMModulePrototypeTableValues[] = { { "setExport"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleSetExport, 2 } }, }; -class JSNodeVMModulePrototype final : public JSC::JSNonFinalObject { +class NodeVMModulePrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; - static JSNodeVMModulePrototype* create(VM& vm, Structure* structure) + static NodeVMModulePrototype* create(VM& vm, Structure* structure) { - JSNodeVMModulePrototype* prototype = new (NotNull, allocateCell(vm)) JSNodeVMModulePrototype(vm, structure); + NodeVMModulePrototype* prototype = new (NotNull, allocateCell(vm)) NodeVMModulePrototype(vm, structure); prototype->finishCreation(vm); return prototype; } DECLARE_INFO; - - static constexpr bool needsDestruction = false; - static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable; - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSNodeVMModulePrototype, Base); + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, Base); return &vm.plainObjectSpace(); } + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); + } private: - JSNodeVMModulePrototype(VM& vm, Structure* structure) + NodeVMModulePrototype(VM& vm, Structure* structure) : Base(vm, structure) { } @@ -1849,12 +1823,33 @@ class JSNodeVMModulePrototype final : public JSC::JSNonFinalObject { { Base::finishCreation(vm); ASSERT(inherits(info())); - reifyStaticProperties(vm, info(), JSNodeVMModulePrototypeTableValues, *this); + reifyStaticProperties(vm, info(), NodeVMModulePrototypeTableValues, *this); this->structure()->setMayBePrototype(true); } }; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, NodeVMModulePrototype::Base); + +class NodeVMModuleConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static NodeVMModuleConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); + + DECLARE_EXPORT_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); + } + +private: + NodeVMModuleConstructor(JSC::VM& vm, JSC::Structure* structure); + + void finishCreation(JSC::VM&, JSC::JSObject* prototype); +}; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModuleConstructor, JSC::InternalFunction); -class JSNodeVMModule : public JSC::JSDestructibleObject { +class NodeVMModule final : public JSC::JSDestructibleObject { public: enum class Status : uint8_t { Unlinked, @@ -1866,60 +1861,29 @@ class JSNodeVMModule : public JSC::JSDestructibleObject { }; using Base = JSC::JSDestructibleObject; - static JSNodeVMModule* create(JSC::VM& vm, JSC::Structure* structure) - { - auto* object = new (JSC::allocateCell(vm)) JSNodeVMModule(vm, structure); - object->finishCreation(vm); - return object; - } - static JSNodeVMModule* create(JSC::VM& vm, Zig::GlobalObject* globalObject) - { - auto* structure = globalObject->m_JSNodeVMModuleStructure.getInitializedOnMainThread(globalObject); - return create(vm, structure); - } - - static void destroy(JSC::JSCell* cell) - { - static_cast(cell)->JSNodeVMModule::~JSNodeVMModule(); - } - - ~JSNodeVMModule() - { - } - - JSNodeVMModule(JSC::VM& vm, JSC::Structure* structure) - : JSC::JSDestructibleObject(vm, structure) - { - } - - DECLARE_INFO; - DECLARE_VISIT_CHILDREN; + static NodeVMModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSValue options); + DECLARE_EXPORT_INFO; template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { if constexpr (mode == JSC::SubspaceAccess::Concurrently) return nullptr; - - return WebCore::subspaceForImpl( + return WebCore::subspaceForImpl( vm, - [](auto& spaces) { return spaces.m_clientSubspaceForJSNodeVMModule.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSNodeVMModule = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForJSNodeVMModule.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForJSNodeVMModule = std::forward(space); }); + [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMModule.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMModule = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForNodeVMModule.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMModule = std::forward(space); }); } - static Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) + static void destroy(JSC::JSCell* cell); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { - auto* structure = JSC::Structure::create(vm, globalObject, globalObject->objectPrototype(), JSC::TypeInfo(JSC::ObjectType, StructureFlags), JSNodeVMModulePrototype::info()); - auto* prototype = JSNodeVMModulePrototype::create(vm, structure); return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } - void finishCreation(JSC::VM& vm) - { - Base::finishCreation(vm); - } + static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); const WTF::String& identifier() const { return m_identifier; } Status status() const { return m_status; } @@ -1927,40 +1891,79 @@ class JSNodeVMModule : public JSC::JSDestructibleObject { JSObject* namespace_() const { return m_namespace.get(); } void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } + DECLARE_VISIT_CHILDREN; + private: WTF::String m_identifier; Status m_status = Status::Unlinked; mutable WriteBarrier m_namespace; + + NodeVMModule(JSC::VM& vm, JSC::Structure* structure, JSValue options) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM& vm) + { + Base::finishCreation(vm); + ASSERT(inherits(info())); + } }; +NodeVMModule* NodeVMModule::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSValue options) +{ + NodeVMModule* ptr = new (NotNull, allocateCell(vm)) NodeVMModule(vm, structure, options); + ptr->finishCreation(vm); + return ptr; +} + +void NodeVMModule::destroy(JSCell* cell) +{ + static_cast(cell)->NodeVMModule::~NodeVMModule(); +} + +static EncodedJSValue +constructModule(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {}) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + ArgList args(callFrame); + + auto* zigGlobalObject = defaultGlobalObject(globalObject); + Structure* structure = zigGlobalObject->NodeVMModuleStructure(); + NodeVMModule* module = NodeVMModule::create(vm, globalObject, structure, args.at(0)); + + return JSValue::encode(module); +} + JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); return JSValue::encode(JSC::jsString(globalObject->vm(), thisObject->identifier())); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(JSC::jsNumber(static_cast(thisObject->status()))); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); switch (thisObject->status()) { - case JSNodeVMModule::Status::Unlinked: + case NodeVMModule::Status::Unlinked: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("unlinked"_s))); - case JSNodeVMModule::Status::Linking: + case NodeVMModule::Status::Linking: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linking"_s))); - case JSNodeVMModule::Status::Linked: + case NodeVMModule::Status::Linked: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linked"_s))); - case JSNodeVMModule::Status::Evaluating: + case NodeVMModule::Status::Evaluating: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluating"_s))); - case JSNodeVMModule::Status::Evaluated: + case NodeVMModule::Status::Evaluated: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluated"_s))); - case JSNodeVMModule::Status::Errored: + case NodeVMModule::Status::Errored: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("errored"_s))); default: return JSC::encodedJSUndefined(); @@ -1969,69 +1972,65 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalO JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject->namespace_()); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject); } template -void JSNodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) +void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) { - JSNodeVMModule* vmModule = jsCast(cell); + NodeVMModule* vmModule = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(vmModule, info()); Base::visitChildren(vmModule, visitor); } -DEFINE_VISIT_CHILDREN(JSNodeVMModule); +DEFINE_VISIT_CHILDREN(NodeVMModule); -const JSC::ClassInfo JSNodeVMModule::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNodeVMModule) }; -const JSC::ClassInfo JSNodeVMModulePrototype::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSNodeVMModulePrototype) }; - -JSC::Structure* createNodeVMModuleStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) -{ - return JSNodeVMModule::createStructure(vm, globalObject); -} +const JSC::ClassInfo NodeVMModule::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModule) }; +const JSC::ClassInfo NodeVMModulePrototype::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModulePrototype) }; +const JSC::ClassInfo NodeVMModuleConstructor::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModuleConstructor) }; JSC_DEFINE_HOST_FUNCTION(vmIsModuleNamespaceObject, (JSGlobalObject * globalObject, CallFrame* callFrame)) { @@ -2078,23 +2077,91 @@ JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject) JSC::JSFunction::create(vm, globalObject, 0, "isModuleNamespaceObject"_s, vmIsModuleNamespaceObject, ImplementationVisibility::Public), 1); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kUnlinked"_s)), - JSC::jsNumber(static_cast(JSNodeVMModule::Status::Unlinked)), 0); + JSC::jsNumber(static_cast(NodeVMModule::Status::Unlinked)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kLinking"_s)), - JSC::jsNumber(static_cast(JSNodeVMModule::Status::Linking)), 0); + JSC::jsNumber(static_cast(NodeVMModule::Status::Linking)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kLinked"_s)), - JSC::jsNumber(static_cast(JSNodeVMModule::Status::Linked)), 0); + JSC::jsNumber(static_cast(NodeVMModule::Status::Linked)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kEvaluating"_s)), - JSC::jsNumber(static_cast(JSNodeVMModule::Status::Evaluating)), 0); + JSC::jsNumber(static_cast(NodeVMModule::Status::Evaluating)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kEvaluated"_s)), - JSC::jsNumber(static_cast(JSNodeVMModule::Status::Evaluated)), 0); + JSC::jsNumber(static_cast(NodeVMModule::Status::Evaluated)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kErrored"_s)), - JSC::jsNumber(static_cast(JSNodeVMModule::Status::Errored)), 0); + JSC::jsNumber(static_cast(NodeVMModule::Status::Errored)), 0); return obj; } +void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) +{ + globalObject->m_NodeVMScriptClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + auto prototype = NodeVMScript::createPrototype(init.vm, init.global); + auto* structure = NodeVMScript::createStructure(init.vm, init.global, prototype); + auto* constructorStructure = NodeVMScriptConstructor::createStructure( + init.vm, init.global, init.global->m_functionPrototype.get()); + auto* constructor = NodeVMScriptConstructor::create( + init.vm, init.global, constructorStructure, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); + }); + + globalObject->m_NodeVMModuleClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + auto prototype = NodeVMModule::createPrototype(init.vm, init.global); + auto* structure = NodeVMModule::createStructure(init.vm, init.global, prototype); + auto* constructorStructure = NodeVMModuleConstructor::createStructure( + init.vm, init.global, init.global->m_functionPrototype.get()); + auto* constructor = NodeVMModuleConstructor::create( + init.vm, init.global, constructorStructure, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); + }); + + globalObject->m_cachedNodeVMGlobalObjectStructure.initLater( + [](const JSC::LazyProperty::Initializer& init) { + init.set(createNodeVMGlobalObjectStructure(init.vm)); + }); +} + +JSObject* NodeVMModule::createPrototype(VM& vm, JSGlobalObject* globalObject) +{ + return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +NodeVMModuleConstructor* NodeVMModuleConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) +{ + NodeVMModuleConstructor* ptr = new (NotNull, allocateCell(vm)) NodeVMModuleConstructor(vm, structure); + ptr->finishCreation(vm, prototype); + return ptr; +} + +JSC_DEFINE_HOST_FUNCTION(moduleConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return constructModule(globalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(moduleConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return constructModule(globalObject, callFrame, callFrame->newTarget()); +} + +NodeVMModuleConstructor::NodeVMModuleConstructor(VM& vm, Structure* structure) + : NodeVMModuleConstructor::Base(vm, structure, moduleConstructorCall, moduleConstructorConstruct) +{ +} + +void NodeVMModuleConstructor::finishCreation(VM& vm, JSObject* prototype) +{ + Base::finishCreation(vm, 1, "Module"_s, PropertyAdditionMode::WithStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + } // namespace Bun diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 64ac568d438d..5e0e692170ba 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -60,6 +60,4 @@ JSC_DECLARE_HOST_FUNCTION(vmModule_isContext); JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext); JSC_DECLARE_HOST_FUNCTION(vmModuleRunInThisContext); -JSC::Structure* createNodeVMModuleStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject); - } // namespace Bun diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index d592831c0d81..61dc2ced27a4 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -597,7 +597,6 @@ class GlobalObject : public Bun::GlobalScope { V(public, LazyPropertyOfGlobalObject, m_JSBunRequestParamsPrototype) \ \ V(public, LazyPropertyOfGlobalObject, m_JSNodeHTTPServerSocketStructure) \ - V(public, LazyPropertyOfGlobalObject, m_JSNodeVMModuleStructure) \ V(public, LazyPropertyOfGlobalObject, m_statValues) \ V(public, LazyPropertyOfGlobalObject, m_bigintStatValues) \ V(public, LazyPropertyOfGlobalObject, m_statFsValues) \ diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index a4149816647d..c8c9a17dd34f 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -36,6 +36,7 @@ class DOMClientIsoSubspaces { std::unique_ptr m_clientSubspaceForRequireResolveFunction; std::unique_ptr m_clientSubspaceForBundlerPlugin; std::unique_ptr m_clientSubspaceForNodeVMScript; + std::unique_ptr m_clientSubspaceForNodeVMModule; std::unique_ptr m_clientSubspaceForJSCommonJSModule; std::unique_ptr m_clientSubspaceForJSCommonJSExtensions; std::unique_ptr m_clientSubspaceForJSMockImplementation; @@ -64,7 +65,6 @@ class DOMClientIsoSubspaces { std::unique_ptr m_clientSubspaceForFunctionTemplate; std::unique_ptr m_clientSubspaceForV8Function; std::unique_ptr m_clientSubspaceForJSNodeHTTPServerSocket; - std::unique_ptr m_clientSubspaceForJSNodeVMModule; std::unique_ptr m_clientSubspaceForNodeVMGlobalObject; std::unique_ptr m_clientSubspaceForJSS3Bucket; std::unique_ptr m_clientSubspaceForJSS3File; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 28c24e4a7f6b..0dcd26af9596 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -36,6 +36,7 @@ class DOMIsoSubspaces { std::unique_ptr m_subspaceForRequireResolveFunction; std::unique_ptr m_subspaceForBundlerPlugin; std::unique_ptr m_subspaceForNodeVMScript; + std::unique_ptr m_subspaceForNodeVMModule; std::unique_ptr m_subspaceForJSCommonJSModule; std::unique_ptr m_subspaceForJSCommonJSExtensions; std::unique_ptr m_subspaceForJSMockImplementation; @@ -61,7 +62,6 @@ class DOMIsoSubspaces { std::unique_ptr m_subspaceForJSMIMEParams; std::unique_ptr m_subspaceForV8Function; std::unique_ptr m_subspaceForJSNodeHTTPServerSocket; - std::unique_ptr m_subspaceForJSNodeVMModule; std::unique_ptr m_subspaceForNodeVMGlobalObject; std::unique_ptr m_subspaceForJSS3Bucket; std::unique_ptr m_subspaceForJSS3File; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index ac3664dfcf04..a00d80de04a7 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -19,7 +19,6 @@ const ArrayPrototypeMap = Array.prototype.map; const PromisePrototypeThen = Promise.prototype.then; const PromiseResolve = Promise.resolve; const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; -const ReflectApply = Reflect.apply; const kPerContextModuleId = Symbol("kPerContextModuleId"); const kNative = Symbol("kNative"); @@ -329,7 +328,7 @@ function isModule(object) { function importModuleDynamicallyWrap(importModuleDynamically) { const importModuleDynamicallyWrapper = async (...args) => { - const m = await ReflectApply(importModuleDynamically, this, args); + const m: any = importModuleDynamically.$apply(this, args); if (isModuleNamespaceObject(m)) { return m; } From a8cae2853ee3854b20eb41b3ee55f90ba301fa60 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 2 May 2025 18:44:09 -0700 Subject: [PATCH 23/76] A couple slight changes --- src/bun.js/bindings/NodeVM.cpp | 356 ++++++++++++------ src/bun.js/bindings/ZigGlobalObject.h | 13 +- .../bindings/webcore/DOMClientIsoSubspaces.h | 3 +- src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 3 +- src/js/node/vm.ts | 2 +- 5 files changed, 263 insertions(+), 114 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index f8b3a1003dd4..89cbc6d52aaa 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -49,6 +49,8 @@ #include "JavaScriptCore/ProgramCodeBlock.h" #include "JavaScriptCore/JIT.h" +#include "JSDOMExceptionHandling.h" + namespace Bun { using namespace WebCore; @@ -58,8 +60,8 @@ using namespace WebCore; static JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr); static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset); -static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, JSC::SourceCode source); -static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::SourceCode source); +static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source); +static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source); NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox); @@ -76,6 +78,25 @@ JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, return {}; } +static bool extractCachedData(JSValue cachedDataValue, WTF::Vector& outCachedData) +{ + if (!cachedDataValue.isCell()) { + return false; + } + + if (auto* arrayBufferView = JSC::jsDynamicCast(cachedDataValue)) { + if (!arrayBufferView->isDetached()) { + outCachedData = arrayBufferView->span(); + return true; + } + } else if (auto* arrayBuffer = JSC::jsDynamicCast(cachedDataValue); arrayBuffer && arrayBuffer->impl()) { + outCachedData = arrayBuffer->impl()->toVector(); + return true; + } + + return false; +} + class BaseOptions { public: String filename = String(); @@ -164,40 +185,20 @@ class BaseOptions { return false; } - bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::vector& outCachedData) + bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, WTF::Vector& outCachedData) { JSValue cachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "cachedData"_s)); - if (cachedDataOpt && !cachedDataOpt.isUndefined()) { - RETURN_IF_EXCEPTION(scope, {}); - if (!cachedDataOpt.isCell()) { - ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); - return false; - } - - // If it's a cell, verify it's a Buffer, TypedArray or DataView - if (cachedDataOpt.isCell()) { - bool isValidType = false; - - if (auto* arrayBufferView = JSC::jsDynamicCast(cachedDataOpt)) { - if (!arrayBufferView->isDetached()) { - std::span span = arrayBufferView->span(); - outCachedData = { span.begin(), span.end() }; - isValidType = true; - } - } else if (auto* arrayBuffer = JSC::jsDynamicCast(cachedDataOpt); arrayBuffer && arrayBuffer->impl()) { - std::span span = arrayBuffer->impl()->span(); - outCachedData = { span.begin(), span.end() }; - isValidType = true; - } - - if (!isValidType) { - ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); - return false; - } + RETURN_IF_EXCEPTION(scope, {}); + if (cachedDataOpt && !cachedDataOpt.isUndefined()) { + // Verify it's a Buffer, TypedArray or DataView and extract the data if it is. + if (extractCachedData(cachedDataOpt, outCachedData)) { return true; } + + ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); } + return false; } @@ -225,7 +226,7 @@ class ScriptOptions : public BaseOptions { public: std::optional timeout = std::nullopt; bool produceCachedData = false; - std::vector cachedData; + WTF::Vector cachedData; bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { @@ -320,7 +321,7 @@ class RunningScriptOptions : public BaseOptions { class CompileFunctionOptions : public BaseOptions { public: - std::vector cachedData; + WTF::Vector cachedData; JSGlobalObject* parsingContext = nullptr; JSValue contextExtensions; bool produceCachedData = false; @@ -450,7 +451,7 @@ class NodeVMScript final : public JSC::JSDestructibleObject { JSC::JSUint8Array* getBytecodeBuffer(); const JSC::SourceCode& source() const { return m_source; } - std::vector& cachedData() { return m_options.cachedData; } + WTF::Vector& cachedData() { return m_options.cachedData; } RefPtr cachedBytecode() const { return m_cachedBytecode; } JSC::ProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } bool cachedDataProduced() const { return m_cachedDataProduced; } @@ -772,9 +773,9 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, WTFMove(options)); - std::vector& cachedData = script->cachedData(); + WTF::Vector& cachedData = script->cachedData(); - if (!cachedData.empty()) { + if (!cachedData.isEmpty()) { JSC::ProgramExecutable* executable = script->cachedExecutable(); if (!executable) { executable = script->createExecutable(); @@ -813,7 +814,7 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT return JSValue::encode(script); } -static bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr exception, ThrowScope& throwScope) +static bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr exception, ThrowScope& throwScope) { if (auto* errorInstance = jsDynamicCast(exception->value())) { errorInstance->materializeErrorInfoIfNeeded(vm, vm.propertyNames->stack); @@ -865,7 +866,7 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM // Set the contextified object before evaluating globalObject->setContextifiedObject(contextifiedObject); - NakedPtr exception; + NakedPtr exception; JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); if (UNLIKELY(exception)) { @@ -966,7 +967,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, options = {}; } - NakedPtr exception; + NakedPtr exception; JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); if (UNLIKELY(exception)) { @@ -1091,7 +1092,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); - NakedPtr exception; + NakedPtr exception; JSValue result = JSC::evaluate(context, sourceCode, context, exception); if (UNLIKELY(exception)) { @@ -1132,7 +1133,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInThisContext, (JSGlobalObject * globalObjec JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); - WTF::NakedPtr exception; + WTF::NakedPtr exception; JSValue result = JSC::evaluate(globalObject, source, globalObject, exception); if (UNLIKELY(exception)) { @@ -1725,7 +1726,7 @@ static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const Arg return program; } -static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, JSC::SourceCode source) +static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source) { auto& vm = JSC::getVM(globalObject); JSC::CodeCache* cache = vm.codeCache(); @@ -1740,7 +1741,7 @@ static RefPtr getBytecode(JSGlobalObject* globalObject, JSC return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); } -static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::SourceCode source) +static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); @@ -1764,6 +1765,44 @@ static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, JSC::S return JSValue::encode(buffer); } +class NodeVMModuleRequest final { +public: + NodeVMModuleRequest(WTF::String specifier, WTF::HashMap importAttributes = {}) + : m_specifier(WTFMove(specifier)) + , m_importAttributes(WTFMove(importAttributes)) + { + } + + const WTF::String& specifier() const { return m_specifier; } + void specifier(WTF::String value) { m_specifier = value; } + + const WTF::HashMap& importAttributes() const { return m_importAttributes; } + + void addImportAttribute(WTF::String key, WTF::String value) + { + m_importAttributes.set(WTFMove(key), WTFMove(value)); + } + + JSArray* toJS(JSGlobalObject* globalObject) const + { + JSArray* array = JSC::constructEmptyArray(globalObject, nullptr, 2); + array->putDirectIndex(globalObject, 0, JSC::jsString(globalObject->vm(), m_specifier)); + + JSObject* attributes = JSC::constructEmptyObject(globalObject); + 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); + } + array->putDirectIndex(globalObject, 1, attributes); + + return array; + } + +private: + WTF::String m_specifier; + WTF::HashMap m_importAttributes; +}; + JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus); @@ -1778,8 +1817,8 @@ JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport); static const HashTableValue NodeVMModulePrototypeTableValues[] = { { "identifier"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } }, + { "getStatusCode"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatusCode, 0 } }, { "getStatus"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, - { "getStatusString"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, { "getNamespace"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetNamespace, 0 } }, { "getError"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetError, 0 } }, { "instantiate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleInstantiate, 0 } }, @@ -1849,8 +1888,10 @@ class NodeVMModuleConstructor final : public JSC::InternalFunction { }; STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModuleConstructor, JSC::InternalFunction); -class NodeVMModule final : public JSC::JSDestructibleObject { +class NodeVMModule : public JSC::JSDestructibleObject { public: + using Base = JSC::JSDestructibleObject; + enum class Status : uint8_t { Unlinked, Linking, @@ -1860,21 +1901,52 @@ class NodeVMModule final : public JSC::JSDestructibleObject { Errored }; - using Base = JSC::JSDestructibleObject; + enum class Type : uint8_t { + SourceText, + Synthetic, + }; + + static NodeVMModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args); + + const WTF::String& identifier() const { return m_identifier; } - static NodeVMModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSValue options); + Status status() const { return m_status; } + void status(Status value) { m_status = value; } + + JSObject* namespace_() const { return m_namespace.get(); } + void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } + +protected: + WTF::String m_identifier; + Status m_status = Status::Unlinked; + mutable WriteBarrier m_namespace; + + NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) + : Base(vm, structure) + , m_identifier(WTFMove(identifier)) + { + } DECLARE_EXPORT_INFO; + DECLARE_VISIT_CHILDREN; +}; + +class NodeVMSourceTextModule final : public NodeVMModule { +public: + using Base = NodeVMModule; + + static NodeVMSourceTextModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args); + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { if constexpr (mode == JSC::SubspaceAccess::Concurrently) return nullptr; - return WebCore::subspaceForImpl( + return WebCore::subspaceForImpl( vm, - [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMModule.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMModule = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForNodeVMModule.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMModule = std::forward(space); }); + [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMSourceTextModule.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMSourceTextModule = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForNodeVMSourceTextModule.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSourceTextModule = std::forward(space); }); } static void destroy(JSC::JSCell* cell); @@ -1885,21 +1957,16 @@ class NodeVMModule final : public JSC::JSDestructibleObject { static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); - const WTF::String& identifier() const { return m_identifier; } - Status status() const { return m_status; } - void status(Status value) { m_status = value; } - JSObject* namespace_() const { return m_namespace.get(); } - void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } + const WTF::Vector& moduleRequests() const { return m_moduleRequests; } + void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); } - DECLARE_VISIT_CHILDREN; + DECLARE_EXPORT_INFO; private: - WTF::String m_identifier; - Status m_status = Status::Unlinked; - mutable WriteBarrier m_namespace; + WTF::Vector m_moduleRequests; - NodeVMModule(JSC::VM& vm, JSC::Structure* structure, JSValue options) - : Base(vm, structure) + NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) + : Base(vm, structure, WTFMove(identifier)) { } @@ -1910,16 +1977,74 @@ class NodeVMModule final : public JSC::JSDestructibleObject { } }; -NodeVMModule* NodeVMModule::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSValue options) +NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue disambiguator = args.at(2); + + if (disambiguator.isString()) { + return NodeVMSourceTextModule::create(vm, globalObject, args); + } + + if (disambiguator.inherits(JSArray::info())) { + // return NodeVMSyntheticModule::create(vm, globalObject, args); + } + + throwArgumentTypeError(*globalObject, scope, 2, "sourceText or syntheticExportNames"_s, "Module"_s, "Module"_s, "string or array"_s); + return nullptr; +} + +NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* globalObject, ArgList args) { - NodeVMModule* ptr = new (NotNull, allocateCell(vm)) NodeVMModule(vm, structure, options); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue identifierValue = args.at(0); + if (!identifierValue.isString()) { + throwArgumentTypeError(*globalObject, scope, 0, "identifier"_s, "Module"_s, "Module"_s, "string"_s); + return nullptr; + } + + JSValue contextValue = args.at(1); + if (!contextValue.isObject()) { + // TODO(@heimskr): if undefined, use current execution context + throwArgumentTypeError(*globalObject, scope, 1, "context"_s, "Module"_s, "Module"_s, "object"_s); + return nullptr; + } + + JSValue sourceTextValue = args.at(2); + if (!sourceTextValue.isString()) { + throwArgumentTypeError(*globalObject, scope, 2, "sourceText"_s, "Module"_s, "Module"_s, "string"_s); + return nullptr; + } + + JSValue lineOffsetValue = args.at(3); + if (!lineOffsetValue.isUInt32AsAnyInt()) { + throwArgumentTypeError(*globalObject, scope, 3, "lineOffset"_s, "Module"_s, "Module"_s, "number"_s); + return nullptr; + } + + JSValue columnOffsetValue = args.at(4); + if (!columnOffsetValue.isUInt32AsAnyInt()) { + throwArgumentTypeError(*globalObject, scope, 4, "columnOffset"_s, "Module"_s, "Module"_s, "number"_s); + return nullptr; + } + + JSValue cachedDataValue = args.at(5); + WTF::Vector cachedData; + if (!cachedDataValue.isUndefined() && !extractCachedData(cachedDataValue, cachedData)) { + throwArgumentTypeError(*globalObject, scope, 5, "cachedData"_s, "Module"_s, "Module"_s, "Buffer, TypedArray, or DataView"_s); + return nullptr; + } + + auto* zigGlobalObject = defaultGlobalObject(globalObject); + NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject)); ptr->finishCreation(vm); return ptr; } -void NodeVMModule::destroy(JSCell* cell) +void NodeVMSourceTextModule::destroy(JSCell* cell) { - static_cast(cell)->NodeVMModule::~NodeVMModule(); + static_cast(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule(); } static EncodedJSValue @@ -1929,41 +2054,40 @@ constructModule(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT auto scope = DECLARE_THROW_SCOPE(vm); ArgList args(callFrame); - auto* zigGlobalObject = defaultGlobalObject(globalObject); - Structure* structure = zigGlobalObject->NodeVMModuleStructure(); - NodeVMModule* module = NodeVMModule::create(vm, globalObject, structure, args.at(0)); + NodeVMModule* module = NodeVMModule::create(vm, globalObject, args); return JSValue::encode(module); } JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); return JSValue::encode(JSC::jsString(globalObject->vm(), thisObject->identifier())); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(JSC::jsNumber(static_cast(thisObject->status()))); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); + using enum NodeVMModule::Status; switch (thisObject->status()) { - case NodeVMModule::Status::Unlinked: + case Unlinked: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("unlinked"_s))); - case NodeVMModule::Status::Linking: + case Linking: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linking"_s))); - case NodeVMModule::Status::Linked: + case Linked: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linked"_s))); - case NodeVMModule::Status::Evaluating: + case Evaluating: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluating"_s))); - case NodeVMModule::Status::Evaluated: + case Evaluated: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluated"_s))); - case NodeVMModule::Status::Errored: + case Errored: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("errored"_s))); default: return JSC::encodedJSUndefined(); @@ -1972,64 +2096,66 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalO JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); + auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject->namespace_()); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject); + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject); + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject); + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject); + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject); + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject); + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject); + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); } template void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) { - NodeVMModule* vmModule = jsCast(cell); + auto* vmModule = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(vmModule, info()); Base::visitChildren(vmModule, visitor); + visitor.append(vmModule->m_namespace); } DEFINE_VISIT_CHILDREN(NodeVMModule); -const JSC::ClassInfo NodeVMModule::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModule) }; -const JSC::ClassInfo NodeVMModulePrototype::s_info = { "NodeVMModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModulePrototype) }; +const JSC::ClassInfo NodeVMModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; +const JSC::ClassInfo NodeVMSourceTextModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; +const JSC::ClassInfo NodeVMModulePrototype::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModulePrototype) }; const JSC::ClassInfo NodeVMModuleConstructor::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModuleConstructor) }; JSC_DEFINE_HOST_FUNCTION(vmIsModuleNamespaceObject, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -2056,7 +2182,7 @@ JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject) defaultGlobalObject(globalObject)->NodeVMScript(), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Module"_s)), - defaultGlobalObject(globalObject)->NodeVMModule(), 0); + 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); @@ -2077,22 +2203,28 @@ JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject) JSC::JSFunction::create(vm, globalObject, 0, "isModuleNamespaceObject"_s, vmIsModuleNamespaceObject, ImplementationVisibility::Public), 1); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kUnlinked"_s)), - JSC::jsNumber(static_cast(NodeVMModule::Status::Unlinked)), 0); + JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Unlinked)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kLinking"_s)), - JSC::jsNumber(static_cast(NodeVMModule::Status::Linking)), 0); + JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Linking)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kLinked"_s)), - JSC::jsNumber(static_cast(NodeVMModule::Status::Linked)), 0); + JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Linked)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kEvaluating"_s)), - JSC::jsNumber(static_cast(NodeVMModule::Status::Evaluating)), 0); + JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Evaluating)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kEvaluated"_s)), - JSC::jsNumber(static_cast(NodeVMModule::Status::Evaluated)), 0); + JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Evaluated)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kErrored"_s)), - JSC::jsNumber(static_cast(NodeVMModule::Status::Errored)), 0); + JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Errored)), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kSourceText"_s)), + JSC::jsNumber(static_cast(NodeVMModule::Type::SourceText)), 0); + obj->putDirect( + vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kSynthetic"_s)), + JSC::jsNumber(static_cast(NodeVMModule::Type::Synthetic)), 0); return obj; } @@ -2111,10 +2243,10 @@ void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) init.setConstructor(constructor); }); - globalObject->m_NodeVMModuleClassStructure.initLater( + globalObject->m_NodeVMSourceTextModuleClassStructure.initLater( [](LazyClassStructure::Initializer& init) { - auto prototype = NodeVMModule::createPrototype(init.vm, init.global); - auto* structure = NodeVMModule::createStructure(init.vm, init.global, prototype); + auto prototype = NodeVMSourceTextModule::createPrototype(init.vm, init.global); + auto* structure = NodeVMSourceTextModule::createStructure(init.vm, init.global, prototype); auto* constructorStructure = NodeVMModuleConstructor::createStructure( init.vm, init.global, init.global->m_functionPrototype.get()); auto* constructor = NodeVMModuleConstructor::create( @@ -2124,13 +2256,23 @@ void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) init.setConstructor(constructor); }); + // globalObject->m_NodeVMSyntheticModuleClassStructure.initLater( + // [](LazyClassStructure::Initializer& init) { + // auto prototype = NodeVMSyntheticModule::createPrototype(init.vm, init.global); + // auto* structure = NodeVMSyntheticModule::createStructure(init.vm, init.global, prototype); + // auto* constructorStructure = NodeVMModuleConstructor::createStructure( + // init.vm, init.global, init.global->m_functionPrototype.get()); + // auto* constructor = NodeVMModuleConstructor::create( + // init.vm, init.global, constructorStructure, prototype); + // }); + globalObject->m_cachedNodeVMGlobalObjectStructure.initLater( [](const JSC::LazyProperty::Initializer& init) { init.set(createNodeVMGlobalObjectStructure(init.vm)); }); } -JSObject* NodeVMModule::createPrototype(VM& vm, JSGlobalObject* globalObject) +JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); } diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 61dc2ced27a4..61b7a0e81bc2 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -249,9 +249,13 @@ class GlobalObject : public Bun::GlobalScope { JSC::JSObject* NodeVMScript() const { return m_NodeVMScriptClassStructure.constructorInitializedOnMainThread(this); } JSC::JSValue NodeVMScriptPrototype() const { return m_NodeVMScriptClassStructure.prototypeInitializedOnMainThread(this); } - JSC::Structure* NodeVMModuleStructure() const { return m_NodeVMModuleClassStructure.getInitializedOnMainThread(this); } - JSC::JSObject* NodeVMModule() const { return m_NodeVMModuleClassStructure.constructorInitializedOnMainThread(this); } - JSC::JSValue NodeVMModulePrototype() const { return m_NodeVMModuleClassStructure.prototypeInitializedOnMainThread(this); } + JSC::Structure* NodeVMSourceTextModuleStructure() const { return m_NodeVMSourceTextModuleClassStructure.getInitializedOnMainThread(this); } + JSC::JSObject* NodeVMSourceTextModule() const { return m_NodeVMSourceTextModuleClassStructure.constructorInitializedOnMainThread(this); } + JSC::JSValue NodeVMSourceTextModulePrototype() const { return m_NodeVMSourceTextModuleClassStructure.prototypeInitializedOnMainThread(this); } + + JSC::Structure* NodeVMSyntheticModuleStructure() const { return m_NodeVMSyntheticModuleClassStructure.getInitializedOnMainThread(this); } + JSC::JSObject* NodeVMSyntheticModule() const { return m_NodeVMSyntheticModuleClassStructure.constructorInitializedOnMainThread(this); } + JSC::JSValue NodeVMSyntheticModulePrototype() const { return m_NodeVMSyntheticModuleClassStructure.prototypeInitializedOnMainThread(this); } JSC::JSMap* readableStreamNativeMap() const { return m_lazyReadableStreamPrototypeMap.getInitializedOnMainThread(this); } JSC::JSMap* requireMap() const { return m_requireMap.getInitializedOnMainThread(this); } @@ -518,7 +522,8 @@ class GlobalObject : public Bun::GlobalScope { V(private, LazyClassStructure, m_callSiteStructure) \ V(public, LazyClassStructure, m_JSBufferClassStructure) \ V(public, LazyClassStructure, m_NodeVMScriptClassStructure) \ - V(public, LazyClassStructure, m_NodeVMModuleClassStructure) \ + V(public, LazyClassStructure, m_NodeVMSourceTextModuleClassStructure) \ + V(public, LazyClassStructure, m_NodeVMSyntheticModuleClassStructure) \ V(public, LazyClassStructure, m_JSX509CertificateClassStructure) \ V(public, LazyClassStructure, m_JSSignClassStructure) \ V(public, LazyClassStructure, m_JSVerifyClassStructure) \ diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index c8c9a17dd34f..84c72d44db56 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -36,7 +36,8 @@ class DOMClientIsoSubspaces { std::unique_ptr m_clientSubspaceForRequireResolveFunction; std::unique_ptr m_clientSubspaceForBundlerPlugin; std::unique_ptr m_clientSubspaceForNodeVMScript; - std::unique_ptr m_clientSubspaceForNodeVMModule; + std::unique_ptr m_clientSubspaceForNodeVMSourceTextModule; + std::unique_ptr m_clientSubspaceForNodeVMSyntheticModule; std::unique_ptr m_clientSubspaceForJSCommonJSModule; std::unique_ptr m_clientSubspaceForJSCommonJSExtensions; std::unique_ptr m_clientSubspaceForJSMockImplementation; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 0dcd26af9596..68aad91ad59e 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -36,7 +36,8 @@ class DOMIsoSubspaces { std::unique_ptr m_subspaceForRequireResolveFunction; std::unique_ptr m_subspaceForBundlerPlugin; std::unique_ptr m_subspaceForNodeVMScript; - std::unique_ptr m_subspaceForNodeVMModule; + std::unique_ptr m_subspaceForNodeVMSourceTextModule; + std::unique_ptr m_subspaceForNodeVMSyntheticModule; std::unique_ptr m_subspaceForJSCommonJSModule; std::unique_ptr m_subspaceForJSCommonJSExtensions; std::unique_ptr m_subspaceForJSMockImplementation; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index a00d80de04a7..e14cb839dcd7 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -247,7 +247,7 @@ class SourceTextModule extends Module { attributes, assert: attributes, }); - const modulePromise = PromisePrototypeThen.$call(PromiseResolve.$call(linkerResult), async mod => { + const modulePromise = PromisePrototypeThen.$call(PromiseResolve(linkerResult), async mod => { if (!isModule(mod)) { throw $ERR_VM_MODULE_NOT_MODULE(); } From a58c2a90508ecb2a12cbc58cc47b713bcd95dfda Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 2 May 2025 19:38:22 -0700 Subject: [PATCH 24/76] getModuleRequests --- src/bun.js/bindings/NodeVM.cpp | 22 ++++++++++++++++++---- src/bun.js/bindings/NodeVM.h | 1 + src/js/internal/primordials.js | 23 +++++++++++++++++++++++ src/js/node/vm.ts | 6 +++--- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 89cbc6d52aaa..29bcf467a5e3 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -1916,10 +1916,14 @@ class NodeVMModule : public JSC::JSDestructibleObject { JSObject* namespace_() const { return m_namespace.get(); } void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } + const WTF::Vector& moduleRequests() const { return m_moduleRequests; } + void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); } + protected: WTF::String m_identifier; Status m_status = Status::Unlinked; mutable WriteBarrier m_namespace; + WTF::Vector m_moduleRequests; NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) : Base(vm, structure) @@ -2005,8 +2009,10 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g } JSValue contextValue = args.at(1); - if (!contextValue.isObject()) { - // TODO(@heimskr): if undefined, use current execution context + if (contextValue.isUndefined()) { + // TODO(@heimskr): should this be `globalObject->globalThis()` instead? + contextValue = globalObject; + } else if (!contextValue.isObject()) { throwArgumentTypeError(*globalObject, scope, 1, "context"_s, "Module"_s, "Module"_s, "object"_s); return nullptr; } @@ -2108,8 +2114,16 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalOb JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); + auto* thisObject = jsCast(callFrame->thisValue()); + const WTF::Vector& requests = thisObject->moduleRequests(); + + JSArray* array = constructEmptyArray(globalObject, nullptr, requests.size()); + + for (unsigned i = 0; const NodeVMModuleRequest& request : requests) { + array->putDirectIndex(globalObject, i++, request.toJS(globalObject)); + } + + return JSValue::encode(array); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 5e0e692170ba..bebac6c878c8 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -32,6 +32,7 @@ class NodeVMGlobalObject final : public Bun::GlobalScope { void finishCreation(JSC::VM&); static void destroy(JSCell* cell); void setContextifiedObject(JSC::JSObject* contextifiedObject); + JSC::JSObject* contextifiedObject() const { return m_sandbox.get(); } void clearContextifiedObject(); // Override property access to delegate to contextified object diff --git a/src/js/internal/primordials.js b/src/js/internal/primordials.js index d478237597af..9c9a79148d14 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -94,7 +94,29 @@ const arrayToSafePromiseIterable = (promises, mapFn) => ), ); const PromiseAll = Promise.all; +const PromiseResolve = Promise.resolve; const SafePromiseAll = (promises, mapFn) => PromiseAll(arrayToSafePromiseIterable(promises, mapFn)); +const SafePromiseAllReturnArrayLike = (promises, mapFn) => + new Promise((resolve, reject) => { + const { length } = promises; + + const returnVal = Array(length); + ObjectSetPrototypeOf(returnVal, null); + if (length === 0) resolve(returnVal); + + let pendingPromises = length; + for (let i = 0; i < length; i++) { + const promise = mapFn != null ? mapFn(promises[i], i) : promises[i]; + PromisePrototypeThen( + PromiseResolve(promise), + result => { + returnVal[i] = result; + if (--pendingPromises === 0) resolve(returnVal); + }, + reject, + ); + } + }); export default { Array, @@ -113,6 +135,7 @@ export default { }, ), SafePromiseAll, + SafePromiseAllReturnArrayLike, SafeSet: makeSafe( Set, class SafeSet extends Set { diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index e14cb839dcd7..558a69c7522a 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -9,7 +9,7 @@ const { validateBuffer, validateFunction, } = require("internal/validators"); -const { SafePromiseAll } = require("internal/primordials"); +const { SafePromiseAllReturnArrayLike } = require("internal/primordials"); const vm = $cpp("NodeVM.cpp", "Bun::createNodeVMBinding"); @@ -267,7 +267,7 @@ class SourceTextModule extends Module { } try { - const modules = await SafePromiseAll(modulePromises); + const modules = await SafePromiseAllReturnArrayLike(modulePromises); this[kNative].link(specifiers, modules); } catch (e) { this.#error = e; @@ -279,7 +279,7 @@ class SourceTextModule extends Module { get dependencySpecifiers() { this[kDependencySpecifiers] ??= ObjectFreeze( - ArrayPrototypeMap.$call(this[kNative].getModuleRequests(), request => request.specifier), + ArrayPrototypeMap.$call(this[kNative].getModuleRequests(), request => request[0]), ); return this[kDependencySpecifiers]; } From c7871456623283c0a02d23dd4574354898f89ec6 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 2 May 2025 20:45:21 -0700 Subject: [PATCH 25/76] Implement validateInternalField --- src/js/internal/validators.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/js/internal/validators.ts b/src/js/internal/validators.ts index e207adfef605..4d91f4563c30 100644 --- a/src/js/internal/validators.ts +++ b/src/js/internal/validators.ts @@ -2,6 +2,7 @@ const { hideFromStack } = require("internal/shared"); const RegExpPrototypeExec = RegExp.prototype.exec; const ArrayIsArray = Array.isArray; +const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; /** @@ -63,7 +64,13 @@ function validateLinkHeaderValue(hints) { `must be an array or string of format "; rel=preload; as=style"`, ); } -hideFromStack(validateLinkHeaderValue); + +function validateInternalField(object, fieldKey, className) { + if (typeof object !== "object" || object === null || !ObjectPrototypeHasOwnProperty.$call(object, fieldKey)) { + throw $ERR_INVALID_ARG_TYPE("this", className, object); + } +} +hideFromStack(validateLinkHeaderValue, validateInternalField); export default { /** (value, name) */ @@ -107,4 +114,6 @@ export default { /** `(value, name, oneOf)` */ validateOneOf: $newCppFunction("NodeValidator.cpp", "jsFunction_validateOneOf", 0), isUint8Array: value => value instanceof Uint8Array, + /** `(object, fieldKey, className)` */ + validateInternalField, }; From a0384f67be4aa44748e9f6aff7b23ccbf3b0b6db Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 2 May 2025 20:46:10 -0700 Subject: [PATCH 26/76] Custom inspection for vm.Module --- src/js/node/vm.ts | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 558a69c7522a..47c9740a03a6 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -1,4 +1,5 @@ // Hardcoded module "node:vm" +const { SafePromiseAllReturnArrayLike } = require("internal/primordials"); const { throwNotImplemented } = require("internal/shared"); const { validateObject, @@ -8,8 +9,9 @@ const { validateInt32, validateBuffer, validateFunction, + validateInternalField, } = require("internal/validators"); -const { SafePromiseAllReturnArrayLike } = require("internal/primordials"); +const util = require("node:util"); const vm = $cpp("NodeVM.cpp", "Bun::createNodeVMBinding"); @@ -19,6 +21,10 @@ const ArrayPrototypeMap = Array.prototype.map; const PromisePrototypeThen = Promise.prototype.then; const PromiseResolve = Promise.resolve; const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; +const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const ObjectSetPrototypeOf = Object.setPrototypeOf; +const ObjectGetPrototypeOf = Object.getPrototypeOf; +const SymbolToStringTag = Symbol.toStringTag; const kPerContextModuleId = Symbol("kPerContextModuleId"); const kNative = Symbol("kNative"); @@ -182,6 +188,26 @@ class Module { } await this[kNative].evaluate(timeout, breakOnSigint); } + + [util.inspect.custom](depth, options) { + validateInternalField(this, kNative, "Module"); + if (typeof depth === "number" && depth < 0) return this; + + const constructor = getConstructorOf(this) || Module; + const o = { __proto__: { constructor } }; + o.status = this.status; + o.identifier = this.identifier; + o.context = this.context; + + ObjectSetPrototypeOf(o, ObjectGetPrototypeOf(this)); + ObjectDefineProperty(o, SymbolToStringTag, { + __proto__: null, + value: constructor.name, + configurable: true, + }); + + return util.inspect(o, { ...options, customInspect: false }); + } } class SourceTextModule extends Module { @@ -343,6 +369,17 @@ function importModuleDynamicallyWrap(importModuleDynamically) { return importModuleDynamicallyWrapper; } +function getConstructorOf(obj) { + while (obj) { + const descriptor = ObjectGetOwnPropertyDescriptor(obj, "constructor"); + if (descriptor !== undefined && typeof descriptor.value === "function" && descriptor.value.name !== "") { + return descriptor.value; + } + + obj = ObjectGetPrototypeOf(obj); + } +} + export default { createContext, runInContext, From c59f7ad61f6cd9116d1092d2302b130c27cefbdd Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Mon, 5 May 2025 16:03:51 -0700 Subject: [PATCH 27/76] Reorganize node:vm native code --- src/bun.js/bindings/NodeVM.cpp | 2123 ++++------------- src/bun.js/bindings/NodeVM.h | 46 + src/bun.js/bindings/NodeVMModule.cpp | 284 +++ src/bun.js/bindings/NodeVMModule.h | 108 + src/bun.js/bindings/NodeVMScript.cpp | 575 +++++ src/bun.js/bindings/NodeVMScript.h | 111 + .../bindings/NodeVMSourceTextModule.cpp | 97 + src/bun.js/bindings/NodeVMSourceTextModule.h | 51 + src/bun.js/bindings/ZigGlobalObject.h | 8 +- src/js/node/vm.ts | 4 +- 10 files changed, 1783 insertions(+), 1624 deletions(-) create mode 100644 src/bun.js/bindings/NodeVMModule.cpp create mode 100644 src/bun.js/bindings/NodeVMModule.h create mode 100644 src/bun.js/bindings/NodeVMScript.cpp create mode 100644 src/bun.js/bindings/NodeVMScript.h create mode 100644 src/bun.js/bindings/NodeVMSourceTextModule.cpp create mode 100644 src/bun.js/bindings/NodeVMSourceTextModule.h diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 29bcf467a5e3..de58f117a5c9 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -9,6 +9,9 @@ #include "BunClientData.h" #include "NodeVM.h" +#include "NodeVMScript.h" +#include "NodeVMModule.h" +#include "NodeVMSourceTextModule.h" #include "JavaScriptCore/JSObjectInlines.h" #include "wtf/text/ExternalStringImpl.h" @@ -46,39 +49,12 @@ #include "JavaScriptCore/BytecodeCacheError.h" #include "wtf/FileHandle.h" -#include "JavaScriptCore/ProgramCodeBlock.h" -#include "JavaScriptCore/JIT.h" - -#include "JSDOMExceptionHandling.h" - namespace Bun { using namespace WebCore; -/// For vm.compileFunction we need to return an anonymous function expression -/// -/// This code is adapted/inspired from JSC::constructFunction, which is used for function declarations. -static JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr); -static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset); - -static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source); -static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source); - -NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox); - -/// For some reason Node has this error message with a grammar error and we have to match it so the tests pass: -/// `The "" argument must be an vm.Context` -JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value) -{ - WTF::StringBuilder builder; - builder.append("The \""_s); - builder.append(name); - builder.append("\" argument must be an vm.Context"_s); - - throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, builder.toString())); - return {}; -} +namespace NodeVM { -static bool extractCachedData(JSValue cachedDataValue, WTF::Vector& outCachedData) +bool extractCachedData(JSValue cachedDataValue, WTF::Vector& outCachedData) { if (!cachedDataValue.isCell()) { return false; @@ -97,399 +73,243 @@ static bool extractCachedData(JSValue cachedDataValue, WTF::Vector& out return false; } -class BaseOptions { -public: - String filename = String(); - OrdinalNumber lineOffset; - OrdinalNumber columnOffset; - bool failed; - - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) - { - JSObject* options = nullptr; - bool any = false; - - if (!optionsArg.isUndefined()) { - if (optionsArg.isObject()) { - options = asObject(optionsArg); - } else { - auto _ = ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); - return false; - } - - if (JSValue filenameOpt = options->getIfPropertyExists(globalObject, builtinNames(vm).filenamePublicName())) { - if (filenameOpt.isString()) { - this->filename = filenameOpt.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, false); - any = true; - } else if (!filenameOpt.isUndefined()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.filename"_s, "string"_s, filenameOpt); - return false; - } - } else { - this->filename = "evalmachine."_s; - } +JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName, JSC::SourceTaintedOrigin sourceTaintOrigin, TextPosition position, JSC::JSScope* scope) +{ + VM& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); - if (JSValue lineOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "lineOffset"_s))) { - if (lineOffsetOpt.isAnyInt()) { - if (!lineOffsetOpt.isInt32()) { - ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), lineOffsetOpt); - return false; - } - this->lineOffset = OrdinalNumber::fromZeroBasedInt(lineOffsetOpt.asInt32()); - any = true; - } else if (lineOffsetOpt.isNumber()) { - ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, "an integer"_s, lineOffsetOpt); - return false; - } else if (!lineOffsetOpt.isUndefined()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.lineOffset"_s, "number"_s, lineOffsetOpt); - return false; - } - } + // wrap the arguments in an anonymous function expression + int startOffset = 0; + String code = stringifyAnonymousFunction(globalObject, args, throwScope, &startOffset); + EXCEPTION_ASSERT(!!throwScope.exception() == code.isNull()); - if (JSValue columnOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "columnOffset"_s))) { - if (columnOffsetOpt.isAnyInt()) { - if (!columnOffsetOpt.isInt32()) { - ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), columnOffsetOpt); - return false; - } - int columnOffsetValue = columnOffsetOpt.asInt32(); - - this->columnOffset = OrdinalNumber::fromZeroBasedInt(columnOffsetValue); - any = true; - } else if (columnOffsetOpt.isNumber()) { - ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, "an integer"_s, columnOffsetOpt); - return false; - } else if (!columnOffsetOpt.isUndefined()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.columnOffset"_s, "number"_s, columnOffsetOpt); - return false; - } - } - } + position.m_column = OrdinalNumber::fromZeroBasedInt(position.m_column.zeroBasedInt()); + SourceCode sourceCode( + JSC::StringSourceProvider::create(code, sourceOrigin, fileName, sourceTaintOrigin, position, SourceProviderSourceType::Program), + position.m_line.oneBasedInt(), position.m_column.oneBasedInt()); - return any; - } + LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; - bool validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool& outProduceCachedData) - { - JSValue produceCachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "produceCachedData"_s)); - if (produceCachedDataOpt && !produceCachedDataOpt.isUndefined()) { - RETURN_IF_EXCEPTION(scope, {}); - if (!produceCachedDataOpt.isBoolean()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.produceCachedData"_s, "boolean"_s, produceCachedDataOpt); - return false; - } - outProduceCachedData = produceCachedDataOpt.asBoolean(); - return true; - } - return false; - } + ParserError error; + bool isEvalNode = false; - bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, WTF::Vector& outCachedData) - { - JSValue cachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "cachedData"_s)); - RETURN_IF_EXCEPTION(scope, {}); + // use default name + Identifier name; + std::unique_ptr program; - if (cachedDataOpt && !cachedDataOpt.isUndefined()) { - // Verify it's a Buffer, TypedArray or DataView and extract the data if it is. - if (extractCachedData(cachedDataOpt, outCachedData)) { - return true; - } + if (code.is8Bit()) { + Parser> parser(vm, sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, + lexicallyScopedFeatures, JSParserScriptMode::Classic, SourceParseMode::ProgramMode, + FunctionMode::None, SuperBinding::NotNeeded, ConstructorKind::None, DerivedContextType::None, + isEvalNode, EvalContextType::None, nullptr); - ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); - } + program = parser.parse(error, name, ParsingContext::Normal); + } else { + Parser> parser(vm, sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, + lexicallyScopedFeatures, JSParserScriptMode::Classic, SourceParseMode::ProgramMode, + FunctionMode::None, SuperBinding::NotNeeded, ConstructorKind::None, DerivedContextType::None, + isEvalNode, EvalContextType::None, nullptr); - return false; + program = parser.parse(error, name, ParsingContext::Normal); } - bool validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional& outTimeout) - { - JSValue timeoutOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "timeout"_s)); - if (timeoutOpt && !timeoutOpt.isUndefined()) { - if (!timeoutOpt.isNumber()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.timeout"_s, "number"_s, timeoutOpt); - return false; - } - - ssize_t timeoutValue; - V::validateInteger(scope, globalObject, timeoutOpt, "options.timeout"_s, jsNumber(1), jsNumber(std::numeric_limits().max()), &timeoutValue); - RETURN_IF_EXCEPTION(scope, {}); - - outTimeout = timeoutValue; - return true; - } - return false; + if (!program) { + RELEASE_ASSERT(error.isValid()); + auto exception = error.toErrorObject(globalObject, sourceCode, -1); + throwException(globalObject, throwScope, exception); + return nullptr; } -}; -class ScriptOptions : public BaseOptions { -public: - std::optional timeout = std::nullopt; - bool produceCachedData = false; - WTF::Vector cachedData; - - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) - { - bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); - RETURN_IF_EXCEPTION(scope, false); - - if (!optionsArg.isUndefined() && !optionsArg.isString()) { - JSObject* options = asObject(optionsArg); + // the code we passed in should be a single expression statement containing a function expression + StatementNode* statement = program->singleStatement(); + if (!statement || !statement->isExprStatement()) { + JSToken token; + error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, "Parser error"_s, -1); + auto exception = error.toErrorObject(globalObject, sourceCode, -1); + throwException(globalObject, throwScope, exception); + return nullptr; + } - // Validate contextName and contextOrigin are strings - if (JSValue contextNameOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextName"_s))) { - if (!contextNameOpt.isUndefined() && !contextNameOpt.isString()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextName"_s, "string"_s, contextNameOpt); - return false; - } - any = true; - } - RETURN_IF_EXCEPTION(scope, false); + ExprStatementNode* exprStatement = static_cast(statement); + ExpressionNode* expression = exprStatement->expr(); + if (!expression || !expression->isFuncExprNode()) { + throwSyntaxError(globalObject, throwScope, "Expected a function expression"_s); + return nullptr; + } - if (JSValue contextOriginOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextOrigin"_s))) { - if (!contextOriginOpt.isUndefined() && !contextOriginOpt.isString()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextOrigin"_s, "string"_s, contextOriginOpt); - return false; - } - any = true; - } - RETURN_IF_EXCEPTION(scope, false); + FunctionMetadataNode* metadata = static_cast(expression)->metadata(); + ASSERT(metadata); + if (!metadata) + return nullptr; - if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } + // metadata->setStartOffset(startOffset); - if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } + ConstructAbility constructAbility = constructAbilityForParseMode(metadata->parseMode()); + UnlinkedFunctionExecutable* unlinkedFunctionExecutable = UnlinkedFunctionExecutable::create( + vm, + sourceCode, + metadata, + UnlinkedNormalFunction, + constructAbility, + InlineAttribute::None, + JSParserScriptMode::Classic, + nullptr, + std::nullopt, + std::nullopt, + DerivedContextType::None, + NeedsClassFieldInitializer::No, + PrivateBrandRequirement::None); - if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } - } + unlinkedFunctionExecutable->recordParse(program->features(), metadata->lexicallyScopedFeatures(), /* hasCapturedVariables */ false); - return any; - } -}; + FunctionExecutable* functionExecutable = unlinkedFunctionExecutable->link(vm, nullptr, sourceCode, std::nullopt); -class RunningScriptOptions : public BaseOptions { -public: - bool displayErrors = true; - std::optional timeout = std::nullopt; - bool breakOnSigint = false; + JSScope* functionScope = scope ? scope : globalObject->globalScope(); - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) - { - bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); - RETURN_IF_EXCEPTION(scope, false); + Structure* structure = JSFunction::selectStructureForNewFuncExp(globalObject, functionExecutable); - if (!optionsArg.isUndefined() && !optionsArg.isString()) { - JSObject* options = asObject(optionsArg); + JSFunction* function = JSFunction::create(vm, globalObject, functionExecutable, functionScope, structure); + return function; +} - if (JSValue displayErrorsOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "displayErrors"_s))) { - RETURN_IF_EXCEPTION(scope, false); - if (!displayErrorsOpt.isBoolean()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.displayErrors"_s, "boolean"_s, displayErrorsOpt); - return false; - } - this->displayErrors = displayErrorsOpt.asBoolean(); - any = true; - } +// Helper function to create an anonymous function expression with parameters +String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset) +{ + // How we stringify functions is important for creating anonymous function expressions + String program; + if (args.isEmpty()) { + // No arguments, just an empty function body + program = "(function () {\n\n})"_s; + // program = "(function () {})"_s; + } else if (args.size() == 1) { + // Just the function body + auto body = args.at(0).toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); - if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } + program = tryMakeString("(function () {"_s, body, "})"_s); + *outOffset = "(function () {"_s.length(); - if (JSValue breakOnSigintOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "breakOnSigint"_s))) { - RETURN_IF_EXCEPTION(scope, false); - if (!breakOnSigintOpt.isBoolean()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.breakOnSigint"_s, "boolean"_s, breakOnSigintOpt); - return false; - } - this->breakOnSigint = breakOnSigintOpt.asBoolean(); - any = true; - } + if (UNLIKELY(!program)) { + throwOutOfMemoryError(globalObject, scope); + return {}; } + } else { + // Process parameters and body + unsigned parameterCount = args.size() - 1; + StringBuilder paramString; - return any; - } -}; - -class CompileFunctionOptions : public BaseOptions { -public: - WTF::Vector cachedData; - JSGlobalObject* parsingContext = nullptr; - JSValue contextExtensions; - bool produceCachedData = false; - - bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) - { - this->parsingContext = globalObject; - bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); - RETURN_IF_EXCEPTION(scope, false); - - if (!optionsArg.isUndefined() && !optionsArg.isString()) { - JSObject* options = asObject(optionsArg); - - if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } - - if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) { - RETURN_IF_EXCEPTION(scope, false); - any = true; - } - - JSValue parsingContextValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "parsingContext"_s)); + for (unsigned i = 0; i < parameterCount; ++i) { + auto param = args.at(i).toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); - if (!parsingContextValue.isEmpty() && !parsingContextValue.isUndefined()) { - if (parsingContextValue.isNull() || !parsingContextValue.isObject()) - return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); - - JSObject* context = asObject(parsingContextValue); - auto* zigGlobalObject = defaultGlobalObject(globalObject); - JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); - - if (scopeValue.isUndefined()) - return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); - - parsingContext = jsDynamicCast(scopeValue); - if (!parsingContext) - return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + if (i > 0) + paramString.append(", "_s); - any = true; - } + paramString.append(param); + } - // Handle contextExtensions option - JSValue contextExtensionsValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextExtensions"_s)); - RETURN_IF_EXCEPTION(scope, {}); + auto body = args.at(parameterCount).toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); - if (!contextExtensionsValue.isEmpty() && !contextExtensionsValue.isUndefined()) { - if (contextExtensionsValue.isNull() || !contextExtensionsValue.isObject()) - return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); - - if (auto* contextExtensionsObject = asObject(contextExtensionsValue)) { - if (!isArray(globalObject, contextExtensionsObject)) - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); - - // Validate that all items in the array are objects - auto* contextExtensionsArray = jsCast(contextExtensionsValue); - unsigned length = contextExtensionsArray->length(); - for (unsigned i = 0; i < length; i++) { - JSValue extension = contextExtensionsArray->getIndexQuickly(i); - if (!extension.isObject()) - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions[0]"_s, "object"_s, extension); - } - } else { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); - } + program = tryMakeString("(function ("_s, paramString.toString(), ") {"_s, body, "})"_s); + *outOffset = "(function ("_s.length() + paramString.length() + ") {"_s.length(); - this->contextExtensions = contextExtensionsValue; - any = true; - } + if (UNLIKELY(!program)) { + throwOutOfMemoryError(globalObject, scope); + return {}; } - - return any; } -}; -class NodeVMScriptConstructor final : public JSC::InternalFunction { -public: - using Base = JSC::InternalFunction; + return program; +} - static NodeVMScriptConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); +RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source) +{ + auto& vm = JSC::getVM(globalObject); + JSC::CodeCache* cache = vm.codeCache(); + JSC::ParserError parserError; + JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); + if (!unlinked || parserError.isValid()) { + return nullptr; + } + JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; + JSC::BytecodeCacheError bytecodeCacheError; + FileSystem::FileHandle fileHandle; + return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); +} - DECLARE_EXPORT_INFO; +JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); - } + JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); + RETURN_IF_EXCEPTION(scope, {}); -private: - NodeVMScriptConstructor(JSC::VM& vm, JSC::Structure* structure); - - void finishCreation(JSC::VM&, JSC::JSObject* prototype); -}; -STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptConstructor, JSC::InternalFunction); - -class NodeVMScript final : public JSC::JSDestructibleObject { -public: - using Base = JSC::JSDestructibleObject; - - static NodeVMScript* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options); - - DECLARE_EXPORT_INFO; - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMScript.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMScript = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForNodeVMScript.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMScript = std::forward(space); }); - } + RefPtr bytecode = getBytecode(globalObject, executable, source); + RETURN_IF_EXCEPTION(scope, {}); - static void destroy(JSC::JSCell*); - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + if (UNLIKELY(!bytecode)) { + return throwVMError(globalObject, scope, "createCachedData failed"_s); } - static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); - - JSC::ProgramExecutable* createExecutable(); - void cacheBytecode(); - JSC::JSUint8Array* getBytecodeBuffer(); - - const JSC::SourceCode& source() const { return m_source; } - WTF::Vector& cachedData() { return m_options.cachedData; } - RefPtr cachedBytecode() const { return m_cachedBytecode; } - JSC::ProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } - bool cachedDataProduced() const { return m_cachedDataProduced; } - void cachedDataProduced(bool value) { m_cachedDataProduced = value; } - TriState cachedDataRejected() const { return m_cachedDataRejected; } - void cachedDataRejected(TriState value) { m_cachedDataRejected = value; } - - DECLARE_VISIT_CHILDREN; - -private: - JSC::SourceCode m_source; - RefPtr m_cachedBytecode; - mutable JSC::WriteBarrier m_cachedBytecodeBuffer; - mutable JSC::WriteBarrier m_cachedExecutable; - ScriptOptions m_options; - bool m_cachedDataProduced = false; - TriState m_cachedDataRejected = TriState::Indeterminate; - - NodeVMScript(JSC::VM& vm, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options) - : Base(vm, structure) - , m_source(source) - , m_options(WTFMove(options)) - { - } + std::span bytes = bytecode->span(); + auto* buffer = WebCore::createBuffer(globalObject, bytes); - void finishCreation(JSC::VM&); -}; + RETURN_IF_EXCEPTION(scope, {}); + ASSERT(buffer); -NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) -{ + return JSValue::encode(buffer); } -template JSC::GCClient::IsoSubspace* NodeVMGlobalObject::subspaceFor(JSC::VM& vm) +bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr exception, ThrowScope& throwScope) { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( + if (auto* errorInstance = jsDynamicCast(exception->value())) { + errorInstance->materializeErrorInfoIfNeeded(vm, vm.propertyNames->stack); + RETURN_IF_EXCEPTION(throwScope, {}); + JSValue stack_jsval = errorInstance->get(globalObject, vm.propertyNames->stack); + RETURN_IF_EXCEPTION(throwScope, {}); + if (!stack_jsval.isString()) { + return false; + } + String stack = stack_jsval.toWTFString(globalObject); + + auto& e_stack = exception->stack(); + size_t stack_size = e_stack.size(); + if (stack_size == 0) { + return false; + } + auto& stack_frame = e_stack[0]; + auto source_url = stack_frame.sourceURL(vm); + if (source_url.isEmpty()) { + // copy what Node does: https://github.com/nodejs/node/blob/afe3909483a2d5ae6b847055f544da40571fb28d/lib/vm.js#L94 + source_url = "evalmachine."_s; + } + auto line_and_column = stack_frame.computeLineAndColumn(); + + String prepend = makeString(source_url, ":"_s, line_and_column.line, "\n"_s, stack); + errorInstance->putDirect(vm, vm.propertyNames->stack, jsString(vm, prepend), JSC::PropertyAttribute::DontEnum | 0); + + JSC::throwException(globalObject, throwScope, exception.get()); + return true; + } + return false; +} + +} // namespace NodeVM + +using namespace NodeVM; + +NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) +{ +} + +template JSC::GCClient::IsoSubspace* NodeVMGlobalObject::subspaceFor(JSC::VM& vm) +{ + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( vm, [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMGlobalObject.get(); }, [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMGlobalObject = std::forward(space); }, @@ -727,325 +547,6 @@ void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_sandbox); } -static EncodedJSValue -constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {}) -{ - VM& vm = globalObject->vm(); - 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()); - - JSValue optionsArg = args.at(1); - ScriptOptions options; - if (optionsArg.isString()) { - options.filename = optionsArg.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - } else if (!options.fromJS(globalObject, vm, scope, optionsArg)) { - RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); - options = {}; - } - - auto* zigGlobalObject = defaultGlobalObject(globalObject); - Structure* structure = zigGlobalObject->NodeVMScriptStructure(); - if (UNLIKELY(zigGlobalObject->NodeVMScript() != newTarget)) { - auto scope = DECLARE_THROW_SCOPE(vm); - if (!newTarget) { - throwTypeError(globalObject, scope, "Class constructor Script cannot be invoked without 'new'"_s); - return {}; - } - - auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject())); - RETURN_IF_EXCEPTION(scope, {}); - structure = InternalFunction::createSubclassStructure( - globalObject, newTarget.getObject(), functionGlobalObject->NodeVMScriptStructure()); - scope.release(); - } - - SourceCode source( - JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), - options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); - RETURN_IF_EXCEPTION(scope, {}); - - const bool produceCachedData = options.produceCachedData; - auto filename = options.filename; - - NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, WTFMove(options)); - - WTF::Vector& cachedData = script->cachedData(); - - if (!cachedData.isEmpty()) { - JSC::ProgramExecutable* executable = script->cachedExecutable(); - if (!executable) { - executable = script->createExecutable(); - } - ASSERT(executable); - - JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? JSC::TaintedByWithScopeLexicallyScopedFeature : JSC::NoLexicallyScopedFeatures; - JSC::SourceCodeKey key(source, {}, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); - Ref cachedBytecode = JSC::CachedBytecode::create(std::span(cachedData), nullptr, {}); - JSC::UnlinkedProgramCodeBlock* unlinkedBlock = JSC::decodeCodeBlock(vm, key, WTFMove(cachedBytecode)); - - if (!unlinkedBlock) { - script->cachedDataRejected(TriState::True); - } else { - JSC::JSScope* jsScope = globalObject->globalScope(); - JSC::CodeBlock* codeBlock = nullptr; - { - // JSC::ProgramCodeBlock::create() requires GC to be deferred. - DeferGC deferGC(vm); - codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); - } - JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); - if (compilationResult != JSC::CompilationResult::CompilationFailed) { - executable->installCode(codeBlock); - script->cachedDataRejected(TriState::False); - } else { - script->cachedDataRejected(TriState::True); - } - } - } else if (produceCachedData) { - script->cacheBytecode(); - // TODO(@heimskr): is there ever a case where bytecode production fails? - script->cachedDataProduced(true); - } - - return JSValue::encode(script); -} - -static bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr exception, ThrowScope& throwScope) -{ - if (auto* errorInstance = jsDynamicCast(exception->value())) { - errorInstance->materializeErrorInfoIfNeeded(vm, vm.propertyNames->stack); - RETURN_IF_EXCEPTION(throwScope, {}); - JSValue stack_jsval = errorInstance->get(globalObject, vm.propertyNames->stack); - RETURN_IF_EXCEPTION(throwScope, {}); - if (!stack_jsval.isString()) { - return false; - } - String stack = stack_jsval.toWTFString(globalObject); - - auto& e_stack = exception->stack(); - size_t stack_size = e_stack.size(); - if (stack_size == 0) { - return false; - } - auto& stack_frame = e_stack[0]; - auto source_url = stack_frame.sourceURL(vm); - if (source_url.isEmpty()) { - // copy what Node does: https://github.com/nodejs/node/blob/afe3909483a2d5ae6b847055f544da40571fb28d/lib/vm.js#L94 - source_url = "evalmachine."_s; - } - auto line_and_column = stack_frame.computeLineAndColumn(); - - String prepend = makeString(source_url, ":"_s, line_and_column.line, "\n"_s, stack); - errorInstance->putDirect(vm, vm.propertyNames->stack, jsString(vm, prepend), JSC::PropertyAttribute::DontEnum | 0); - - JSC::throwException(globalObject, throwScope, exception.get()); - return true; - } - return false; -} - -static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false) -{ - - auto& vm = JSC::getVM(globalObject); - auto throwScope = DECLARE_THROW_SCOPE(vm); - - RunningScriptOptions options; - if (allowStringInPlaceOfOptions && optionsArg.isString()) { - options.filename = optionsArg.toWTFString(globalObject); - RETURN_IF_EXCEPTION(throwScope, {}); - } else if (!options.fromJS(globalObject, vm, throwScope, optionsArg)) { - RETURN_IF_EXCEPTION(throwScope, {}); - options = {}; - } - - // Set the contextified object before evaluating - globalObject->setContextifiedObject(contextifiedObject); - - NakedPtr exception; - JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); - - if (UNLIKELY(exception)) { - if (handleException(globalObject, vm, exception, throwScope)) { - return {}; - } - JSC::throwException(globalObject, throwScope, exception.get()); - return {}; - } - - return JSValue::encode(result); -} - -JSC_DEFINE_HOST_FUNCTION(scriptConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - return constructScript(globalObject, callFrame); -} - -JSC_DEFINE_HOST_FUNCTION(scriptConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - return constructScript(globalObject, callFrame, callFrame->newTarget()); -} - -JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - auto* script = jsDynamicCast(thisValue); - if (UNLIKELY(!script)) { - return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); - } - - const JSC::SourceCode& source = script->source(); - return createCachedData(globalObject, source); -} - -JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - auto* script = jsDynamicCast(thisValue); - if (UNLIKELY(!script)) { - return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); - } - - ArgList args(callFrame); - JSValue contextArg = args.at(0); - if (contextArg.isUndefinedOrNull()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); - } - - if (!contextArg.isObject()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); - } - - JSObject* context = asObject(contextArg); - auto* zigGlobalObject = defaultGlobalObject(globalObject); - JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); - if (scopeValue.isUndefined()) { - return INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); - } - - NodeVMGlobalObject* nodeVmGlobalObject = jsDynamicCast(scopeValue); - if (!nodeVmGlobalObject) { - return INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); - } - - return runInContext(nodeVmGlobalObject, script, context, args.at(1)); -} - -JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - auto& vm = JSC::getVM(globalObject); - JSValue thisValue = callFrame->thisValue(); - auto* script = jsDynamicCast(thisValue); - auto throwScope = DECLARE_THROW_SCOPE(vm); - - if (UNLIKELY(!script)) { - return ERR::INVALID_ARG_VALUE(throwScope, globalObject, "this"_s, thisValue, "must be a Script"_s); - } - - JSValue contextArg = callFrame->argument(0); - if (contextArg.isUndefined()) { - contextArg = JSC::constructEmptyObject(globalObject); - } - - if (!contextArg.isObject()) { - return ERR::INVALID_ARG_TYPE(throwScope, globalObject, "context"_s, "object"_s, contextArg); - } - - RunningScriptOptions options; - if (!options.fromJS(globalObject, vm, throwScope, contextArg)) { - RETURN_IF_EXCEPTION(throwScope, {}); - options = {}; - } - - NakedPtr exception; - JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); - - if (UNLIKELY(exception)) { - if (handleException(globalObject, vm, exception, throwScope)) { - return {}; - } - JSC::throwException(globalObject, throwScope, exception.get()); - return {}; - } - - RETURN_IF_EXCEPTION(throwScope, {}); - return JSValue::encode(result); -} - -JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = JSValue::decode(thisValueEncoded); - auto* script = jsDynamicCast(thisValue); - if (UNLIKELY(!script)) { - return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); - } - - const auto& url = script->source().provider()->sourceMappingURLDirective(); - return JSValue::encode(jsString(vm, url)); -} - -JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = JSValue::decode(thisValueEncoded); - auto* script = jsDynamicCast(thisValue); - if (UNLIKELY(!script)) { - return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); - } - - if (auto* buffer = script->getBytecodeBuffer()) { - return JSValue::encode(buffer); - } - - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = JSValue::decode(thisValueEncoded); - auto* script = jsDynamicCast(thisValue); - if (UNLIKELY(!script)) { - return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); - } - - return JSValue::encode(jsBoolean(script->cachedDataProduced())); -} - -JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - JSValue thisValue = JSValue::decode(thisValueEncoded); - auto* script = jsDynamicCast(thisValue); - if (UNLIKELY(!script)) { - return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); - } - - switch (script->cachedDataRejected()) { - case TriState::True: - return JSValue::encode(jsBoolean(true)); - case TriState::False: - return JSValue::encode(jsBoolean(false)); - default: - return JSValue::encode(jsUndefined()); - } -} - JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); @@ -1073,13 +574,12 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject JSValue optionsArg = callFrame->argument(2); - ScriptOptions options; + 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)) { RETURN_IF_EXCEPTION(scope, {}); - options = {}; } auto sourceCode = SourceCode( @@ -1120,13 +620,12 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInThisContext, (JSGlobalObject * globalObjec RETURN_IF_EXCEPTION(throwScope, encodedJSUndefined()); JSValue optionsArg = callFrame->argument(1); - ScriptOptions options; + 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)) { RETURN_IF_EXCEPTION(throwScope, encodedJSUndefined()); - options = {}; } SourceCode source( @@ -1215,963 +714,184 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject JSScope* currentScope = options.parsingContext->globalScope(); // Create JSWithScope objects for each context extension - for (unsigned i = 0; i < length; i++) { - JSValue extension = contextExtensionsArray->getIndexQuickly(i); - if (extension.isObject()) { - JSObject* extensionObject = asObject(extension); - currentScope = JSWithScope::create(vm, options.parsingContext, currentScope, extensionObject); - } - } - - // Use the outermost JSWithScope as our function scope - functionScope = currentScope; - } - } - - options.parsingContext->setGlobalScopeExtension(functionScope); - - // Create the function using constructAnonymousFunction with the appropriate scope chain - JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset), functionScope); - - RETURN_IF_EXCEPTION(scope, {}); - - if (!function) - return throwVMError(globalObject, scope, "Failed to compile function"_s); - - return JSValue::encode(function); -} - -JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - auto& 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) { - throwTypeError(globalObject, scope, "this.runInContext is not a function"_s); - return {}; - } - - if (!contextObjectValue || contextObjectValue.isUndefinedOrNull()) { - contextObjectValue = JSC::constructEmptyObject(globalObject); - } - - if (UNLIKELY(!contextObjectValue || !contextObjectValue.isObject())) { - throwTypeError(globalObject, scope, "Context must be an object"_s); - return {}; - } - - // we don't care about options for now - // TODO: options - // bool didThrow = false; - - auto* zigGlobal = defaultGlobalObject(globalObject); - JSObject* context = asObject(contextObjectValue); - auto* targetContext = NodeVMGlobalObject::create( - vm, zigGlobal->NodeVMGlobalObjectStructure()); - - return runInContext(targetContext, script, context, callFrame->argument(1)); -} - -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()); - - // 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); - - JSValue contextArg = callFrame->argument(0); - if (contextArg.isUndefinedOrNull()) { - contextArg = JSC::constructEmptyObject(globalObject); - } - - if (!contextArg.isObject()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); - } - - JSValue optionsArg = callFrame->argument(1); - - // Validate options argument - if (!optionsArg.isUndefined() && !optionsArg.isObject()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); - } - - // If options is provided, validate name and origin properties - if (optionsArg.isObject()) { - JSObject* options = asObject(optionsArg); - - // Check name property - if (JSValue nameValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "name"_s))) { - RETURN_IF_EXCEPTION(scope, {}); - if (!nameValue.isUndefined() && !nameValue.isString()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.name"_s, "string"_s, nameValue); - } - } - - // Check origin property - if (JSValue originValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "origin"_s))) { - RETURN_IF_EXCEPTION(scope, {}); - if (!originValue.isUndefined() && !originValue.isString()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.origin"_s, "string"_s, originValue); - } - } - } - - JSObject* sandbox = asObject(contextArg); - - auto* targetContext = NodeVMGlobalObject::create(vm, - defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure()); - - // 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 JSValue::encode(sandbox); -} - -JSC_DEFINE_HOST_FUNCTION(vmModule_isContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - ArgList args(callFrame); - 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)); -} - -class NodeVMScriptPrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - - static NodeVMScriptPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure) - { - NodeVMScriptPrototype* ptr = new (NotNull, allocateCell(vm)) NodeVMScriptPrototype(vm, structure); - ptr->finishCreation(vm); - return ptr; - } - - DECLARE_INFO; - template - static GCClient::IsoSubspace* subspaceFor(VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, Base); - return &vm.plainObjectSpace(); - } - static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) - { - return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); - } - -private: - NodeVMScriptPrototype(VM& vm, Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(VM&); -}; -STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, NodeVMScriptPrototype::Base); - -static const struct HashTableValue scriptPrototypeTableValues[] = { - { "createCachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptCreateCachedData, 1 } }, - { "runInContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInContext, 2 } }, - { "runInNewContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInNewContext, 2 } }, - { "runInThisContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInThisContext, 2 } }, - { "sourceMapURL"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetSourceMapURL, nullptr } }, - { "cachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedData, nullptr } }, - { "cachedDataProduced"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataProduced, nullptr } }, - { "cachedDataRejected"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataRejected, nullptr } }, -}; - -// 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 NodeVMScriptPrototype::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptPrototype) }; -const ClassInfo NodeVMScript::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScript) }; -const ClassInfo NodeVMScriptConstructor::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptConstructor) }; -const ClassInfo NodeVMGlobalObject::s_info = { "NodeVMGlobalObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMGlobalObject) }; - -JSC::ProgramExecutable* NodeVMScript::createExecutable() -{ - auto& vm = JSC::getVM(globalObject()); - m_cachedExecutable.set(vm, this, JSC::ProgramExecutable::create(globalObject(), m_source)); - return m_cachedExecutable.get(); -} - -void NodeVMScript::cacheBytecode() -{ - if (!m_cachedExecutable) { - createExecutable(); - } - - m_cachedBytecode = getBytecode(globalObject(), m_cachedExecutable.get(), m_source); - m_cachedDataProduced = m_cachedBytecode != nullptr; -} - -JSC::JSUint8Array* NodeVMScript::getBytecodeBuffer() -{ - if (!m_options.produceCachedData) { - return nullptr; - } - - if (!m_cachedBytecodeBuffer) { - if (!m_cachedBytecode) { - cacheBytecode(); - } - - ASSERT(m_cachedBytecode); - - std::span bytes = m_cachedBytecode->span(); - m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject(), bytes)); - } - - ASSERT(m_cachedBytecodeBuffer); - return m_cachedBytecodeBuffer.get(); -} - -DEFINE_VISIT_CHILDREN(NodeVMScript); - -template -void NodeVMScript::visitChildrenImpl(JSCell* cell, Visitor& visitor) -{ - NodeVMScript* thisObject = jsCast(cell); - ASSERT_GC_OBJECT_INHERITS(thisObject, info()); - Base::visitChildren(thisObject, visitor); - visitor.append(thisObject->m_cachedExecutable); - visitor.append(thisObject->m_cachedBytecodeBuffer); -} - -NodeVMScriptConstructor::NodeVMScriptConstructor(VM& vm, Structure* structure) - : NodeVMScriptConstructor::Base(vm, structure, scriptConstructorCall, scriptConstructorConstruct) -{ -} - -NodeVMScriptConstructor* NodeVMScriptConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) -{ - NodeVMScriptConstructor* ptr = new (NotNull, allocateCell(vm)) NodeVMScriptConstructor(vm, structure); - ptr->finishCreation(vm, prototype); - return ptr; -} - -void NodeVMScriptConstructor::finishCreation(VM& vm, JSObject* prototype) -{ - Base::finishCreation(vm, 1, "Script"_s, PropertyAdditionMode::WithStructureTransition); - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - ASSERT(inherits(info())); -} - -void NodeVMScriptPrototype::finishCreation(VM& vm) -{ - Base::finishCreation(vm); - reifyStaticProperties(vm, NodeVMScript::info(), scriptPrototypeTableValues, *this); - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); -} - -JSObject* NodeVMScript::createPrototype(VM& vm, JSGlobalObject* globalObject) -{ - return NodeVMScriptPrototype::create(vm, globalObject, NodeVMScriptPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); -} - -NodeVMScript* NodeVMScript::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, SourceCode source, ScriptOptions options) -{ - NodeVMScript* ptr = new (NotNull, allocateCell(vm)) NodeVMScript(vm, structure, source, WTFMove(options)); - ptr->finishCreation(vm); - return ptr; -} - -void NodeVMScript::finishCreation(VM& vm) -{ - Base::finishCreation(vm); - ASSERT(inherits(info())); -} - -void NodeVMScript::destroy(JSCell* cell) -{ - static_cast(cell)->NodeVMScript::~NodeVMScript(); -} - -bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot) -{ - - auto* thisObject = jsCast(cell); - if (UNLIKELY(!thisObject->m_sandbox)) { - return Base::deleteProperty(cell, globalObject, propertyName, slot); - } - - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* sandbox = thisObject->m_sandbox.get(); - if (!sandbox->deleteProperty(sandbox, globalObject, propertyName, slot)) { - return false; - } - - RETURN_IF_EXCEPTION(scope, false); - return Base::deleteProperty(cell, globalObject, propertyName, slot); -} - -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); - } - - Base::getOwnPropertyNames(cell, globalObject, propertyNames, mode); -} - -static JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName, JSC::SourceTaintedOrigin sourceTaintOrigin, TextPosition position, JSC::JSScope* scope) -{ - VM& vm = globalObject->vm(); - auto throwScope = DECLARE_THROW_SCOPE(vm); - - // wrap the arguments in an anonymous function expression - int startOffset = 0; - String code = stringifyAnonymousFunction(globalObject, args, throwScope, &startOffset); - EXCEPTION_ASSERT(!!throwScope.exception() == code.isNull()); - - position.m_column = OrdinalNumber::fromZeroBasedInt(position.m_column.zeroBasedInt()); - SourceCode sourceCode( - JSC::StringSourceProvider::create(code, sourceOrigin, fileName, sourceTaintOrigin, position, SourceProviderSourceType::Program), - position.m_line.oneBasedInt(), position.m_column.oneBasedInt()); - - LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; - - ParserError error; - bool isEvalNode = false; - - // use default name - Identifier name; - std::unique_ptr program; - - if (code.is8Bit()) { - Parser> parser(vm, sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, - lexicallyScopedFeatures, JSParserScriptMode::Classic, SourceParseMode::ProgramMode, - FunctionMode::None, SuperBinding::NotNeeded, ConstructorKind::None, DerivedContextType::None, - isEvalNode, EvalContextType::None, nullptr); - - program = parser.parse(error, name, ParsingContext::Normal); - } else { - Parser> parser(vm, sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, - lexicallyScopedFeatures, JSParserScriptMode::Classic, SourceParseMode::ProgramMode, - FunctionMode::None, SuperBinding::NotNeeded, ConstructorKind::None, DerivedContextType::None, - isEvalNode, EvalContextType::None, nullptr); - - program = parser.parse(error, name, ParsingContext::Normal); - } - - if (!program) { - RELEASE_ASSERT(error.isValid()); - auto exception = error.toErrorObject(globalObject, sourceCode, -1); - throwException(globalObject, throwScope, exception); - return nullptr; - } - - // the code we passed in should be a single expression statement containing a function expression - StatementNode* statement = program->singleStatement(); - if (!statement || !statement->isExprStatement()) { - JSToken token; - error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, "Parser error"_s, -1); - auto exception = error.toErrorObject(globalObject, sourceCode, -1); - throwException(globalObject, throwScope, exception); - return nullptr; - } - - ExprStatementNode* exprStatement = static_cast(statement); - ExpressionNode* expression = exprStatement->expr(); - if (!expression || !expression->isFuncExprNode()) { - throwSyntaxError(globalObject, throwScope, "Expected a function expression"_s); - return nullptr; - } - - FunctionMetadataNode* metadata = static_cast(expression)->metadata(); - ASSERT(metadata); - if (!metadata) - return nullptr; - - // metadata->setStartOffset(startOffset); - - ConstructAbility constructAbility = constructAbilityForParseMode(metadata->parseMode()); - UnlinkedFunctionExecutable* unlinkedFunctionExecutable = UnlinkedFunctionExecutable::create( - vm, - sourceCode, - metadata, - UnlinkedNormalFunction, - constructAbility, - InlineAttribute::None, - JSParserScriptMode::Classic, - nullptr, - std::nullopt, - std::nullopt, - DerivedContextType::None, - NeedsClassFieldInitializer::No, - PrivateBrandRequirement::None); - - unlinkedFunctionExecutable->recordParse(program->features(), metadata->lexicallyScopedFeatures(), /* hasCapturedVariables */ false); - - FunctionExecutable* functionExecutable = unlinkedFunctionExecutable->link(vm, nullptr, sourceCode, std::nullopt); - - JSScope* functionScope = scope ? scope : globalObject->globalScope(); - - Structure* structure = JSFunction::selectStructureForNewFuncExp(globalObject, functionExecutable); - - JSFunction* function = JSFunction::create(vm, globalObject, functionExecutable, functionScope, structure); - return function; -} - -// Helper function to create an anonymous function expression with parameters -static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset) -{ - // How we stringify functions is important for creating anonymous function expressions - String program; - if (args.isEmpty()) { - // No arguments, just an empty function body - program = "(function () {\n\n})"_s; - // program = "(function () {})"_s; - } else if (args.size() == 1) { - // Just the function body - auto body = args.at(0).toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - program = tryMakeString("(function () {"_s, body, "})"_s); - *outOffset = "(function () {"_s.length(); - - if (UNLIKELY(!program)) { - throwOutOfMemoryError(globalObject, scope); - return {}; - } - } else { - // Process parameters and body - unsigned parameterCount = args.size() - 1; - StringBuilder paramString; - - for (unsigned i = 0; i < parameterCount; ++i) { - auto param = args.at(i).toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - if (i > 0) - paramString.append(", "_s); - - paramString.append(param); - } - - auto body = args.at(parameterCount).toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - program = tryMakeString("(function ("_s, paramString.toString(), ") {"_s, body, "})"_s); - *outOffset = "(function ("_s.length() + paramString.length() + ") {"_s.length(); - - if (UNLIKELY(!program)) { - throwOutOfMemoryError(globalObject, scope); - return {}; - } - } - - return program; -} - -static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source) -{ - auto& vm = JSC::getVM(globalObject); - JSC::CodeCache* cache = vm.codeCache(); - JSC::ParserError parserError; - JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); - if (!unlinked || parserError.isValid()) { - return nullptr; - } - JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; - JSC::BytecodeCacheError bytecodeCacheError; - FileSystem::FileHandle fileHandle; - return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); -} - -static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); - RETURN_IF_EXCEPTION(scope, {}); - - RefPtr bytecode = getBytecode(globalObject, executable, source); - RETURN_IF_EXCEPTION(scope, {}); - - if (UNLIKELY(!bytecode)) { - return throwVMError(globalObject, scope, "createCachedData failed"_s); - } - - std::span bytes = bytecode->span(); - auto* buffer = WebCore::createBuffer(globalObject, bytes); - - RETURN_IF_EXCEPTION(scope, {}); - ASSERT(buffer); - - return JSValue::encode(buffer); -} - -class NodeVMModuleRequest final { -public: - NodeVMModuleRequest(WTF::String specifier, WTF::HashMap importAttributes = {}) - : m_specifier(WTFMove(specifier)) - , m_importAttributes(WTFMove(importAttributes)) - { - } - - const WTF::String& specifier() const { return m_specifier; } - void specifier(WTF::String value) { m_specifier = value; } - - const WTF::HashMap& importAttributes() const { return m_importAttributes; } - - void addImportAttribute(WTF::String key, WTF::String value) - { - m_importAttributes.set(WTFMove(key), WTFMove(value)); - } - - JSArray* toJS(JSGlobalObject* globalObject) const - { - JSArray* array = JSC::constructEmptyArray(globalObject, nullptr, 2); - array->putDirectIndex(globalObject, 0, JSC::jsString(globalObject->vm(), m_specifier)); - - JSObject* attributes = JSC::constructEmptyObject(globalObject); - 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); - } - array->putDirectIndex(globalObject, 1, attributes); - - return array; - } - -private: - WTF::String m_specifier; - WTF::HashMap m_importAttributes; -}; - -JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetNamespace); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetError); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleInstantiate); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleEvaluate); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleLink); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData); -JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport); - -static const HashTableValue NodeVMModulePrototypeTableValues[] = { - { "identifier"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } }, - { "getStatusCode"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatusCode, 0 } }, - { "getStatus"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, - { "getNamespace"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetNamespace, 0 } }, - { "getError"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetError, 0 } }, - { "instantiate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleInstantiate, 0 } }, - { "evaluate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleEvaluate, 2 } }, - { "getModuleRequests"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetModuleRequests, 0 } }, - { "link"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleLink, 2 } }, - { "createCachedData"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleCreateCachedData, 0 } }, - { "setExport"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleSetExport, 2 } }, -}; - -class NodeVMModulePrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - - static NodeVMModulePrototype* create(VM& vm, Structure* structure) - { - NodeVMModulePrototype* prototype = new (NotNull, allocateCell(vm)) NodeVMModulePrototype(vm, structure); - prototype->finishCreation(vm); - return prototype; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, Base); - return &vm.plainObjectSpace(); - } - static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) - { - return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); - } - -private: - NodeVMModulePrototype(VM& vm, Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(VM& vm) - { - Base::finishCreation(vm); - ASSERT(inherits(info())); - reifyStaticProperties(vm, info(), NodeVMModulePrototypeTableValues, *this); - this->structure()->setMayBePrototype(true); - } -}; -STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, NodeVMModulePrototype::Base); - -class NodeVMModuleConstructor final : public JSC::InternalFunction { -public: - using Base = JSC::InternalFunction; - - static NodeVMModuleConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); - - DECLARE_EXPORT_INFO; - - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); - } - -private: - NodeVMModuleConstructor(JSC::VM& vm, JSC::Structure* structure); - - void finishCreation(JSC::VM&, JSC::JSObject* prototype); -}; -STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModuleConstructor, JSC::InternalFunction); - -class NodeVMModule : public JSC::JSDestructibleObject { -public: - using Base = JSC::JSDestructibleObject; - - enum class Status : uint8_t { - Unlinked, - Linking, - Linked, - Evaluating, - Evaluated, - Errored - }; - - enum class Type : uint8_t { - SourceText, - Synthetic, - }; - - static NodeVMModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args); - - const WTF::String& identifier() const { return m_identifier; } - - Status status() const { return m_status; } - void status(Status value) { m_status = value; } - - JSObject* namespace_() const { return m_namespace.get(); } - void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } - - const WTF::Vector& moduleRequests() const { return m_moduleRequests; } - void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); } - -protected: - WTF::String m_identifier; - Status m_status = Status::Unlinked; - mutable WriteBarrier m_namespace; - WTF::Vector m_moduleRequests; - - NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) - : Base(vm, structure) - , m_identifier(WTFMove(identifier)) - { - } - - DECLARE_EXPORT_INFO; - DECLARE_VISIT_CHILDREN; -}; - -class NodeVMSourceTextModule final : public NodeVMModule { -public: - using Base = NodeVMModule; - - static NodeVMSourceTextModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args); - - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMSourceTextModule.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMSourceTextModule = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForNodeVMSourceTextModule.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSourceTextModule = std::forward(space); }); - } + for (unsigned i = 0; i < length; i++) { + JSValue extension = contextExtensionsArray->getIndexQuickly(i); + if (extension.isObject()) { + JSObject* extensionObject = asObject(extension); + currentScope = JSWithScope::create(vm, options.parsingContext, currentScope, extensionObject); + } + } - static void destroy(JSC::JSCell* cell); - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + // Use the outermost JSWithScope as our function scope + functionScope = currentScope; + } } - static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); + options.parsingContext->setGlobalScopeExtension(functionScope); - const WTF::Vector& moduleRequests() const { return m_moduleRequests; } - void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); } + // Create the function using constructAnonymousFunction with the appropriate scope chain + JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset), functionScope); - DECLARE_EXPORT_INFO; + RETURN_IF_EXCEPTION(scope, {}); -private: - WTF::Vector m_moduleRequests; + if (!function) + return throwVMError(globalObject, scope, "Failed to compile function"_s); - NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) - : Base(vm, structure, WTFMove(identifier)) - { - } + return JSValue::encode(function); +} - void finishCreation(JSC::VM& vm) - { - Base::finishCreation(vm); - ASSERT(inherits(info())); - } -}; +Structure* createNodeVMGlobalObjectStructure(JSC::VM& vm) +{ + return NodeVMGlobalObject::createStructure(vm, jsNull()); +} -NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args) +NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox) { - auto scope = DECLARE_THROW_SCOPE(vm); - JSValue disambiguator = args.at(2); + auto* targetContext = NodeVMGlobalObject::create(vm, + defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure()); - if (disambiguator.isString()) { - return NodeVMSourceTextModule::create(vm, globalObject, args); - } + // Set sandbox as contextified object + targetContext->setContextifiedObject(sandbox); - if (disambiguator.inherits(JSArray::info())) { - // return NodeVMSyntheticModule::create(vm, globalObject, args); - } + // Store context in WeakMap for isContext checks + auto* zigGlobalObject = defaultGlobalObject(globalObject); + zigGlobalObject->vmModuleContextMap()->set(vm, sandbox, targetContext); - throwArgumentTypeError(*globalObject, scope, 2, "sourceText or syntheticExportNames"_s, "Module"_s, "Module"_s, "string or array"_s); - return nullptr; + return targetContext; } -NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* globalObject, ArgList args) +JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { + VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue identifierValue = args.at(0); - if (!identifierValue.isString()) { - throwArgumentTypeError(*globalObject, scope, 0, "identifier"_s, "Module"_s, "Module"_s, "string"_s); - return nullptr; - } - - JSValue contextValue = args.at(1); - if (contextValue.isUndefined()) { - // TODO(@heimskr): should this be `globalObject->globalThis()` instead? - contextValue = globalObject; - } else if (!contextValue.isObject()) { - throwArgumentTypeError(*globalObject, scope, 1, "context"_s, "Module"_s, "Module"_s, "object"_s); - return nullptr; + JSValue contextArg = callFrame->argument(0); + if (contextArg.isUndefinedOrNull()) { + contextArg = JSC::constructEmptyObject(globalObject); } - JSValue sourceTextValue = args.at(2); - if (!sourceTextValue.isString()) { - throwArgumentTypeError(*globalObject, scope, 2, "sourceText"_s, "Module"_s, "Module"_s, "string"_s); - return nullptr; + if (!contextArg.isObject()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); } - JSValue lineOffsetValue = args.at(3); - if (!lineOffsetValue.isUInt32AsAnyInt()) { - throwArgumentTypeError(*globalObject, scope, 3, "lineOffset"_s, "Module"_s, "Module"_s, "number"_s); - return nullptr; - } + JSValue optionsArg = callFrame->argument(1); - JSValue columnOffsetValue = args.at(4); - if (!columnOffsetValue.isUInt32AsAnyInt()) { - throwArgumentTypeError(*globalObject, scope, 4, "columnOffset"_s, "Module"_s, "Module"_s, "number"_s); - return nullptr; + // Validate options argument + if (!optionsArg.isUndefined() && !optionsArg.isObject()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); } - JSValue cachedDataValue = args.at(5); - WTF::Vector cachedData; - if (!cachedDataValue.isUndefined() && !extractCachedData(cachedDataValue, cachedData)) { - throwArgumentTypeError(*globalObject, scope, 5, "cachedData"_s, "Module"_s, "Module"_s, "Buffer, TypedArray, or DataView"_s); - return nullptr; - } + // If options is provided, validate name and origin properties + if (optionsArg.isObject()) { + JSObject* options = asObject(optionsArg); - auto* zigGlobalObject = defaultGlobalObject(globalObject); - NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject)); - ptr->finishCreation(vm); - return ptr; -} + // Check name property + if (JSValue nameValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "name"_s))) { + RETURN_IF_EXCEPTION(scope, {}); + if (!nameValue.isUndefined() && !nameValue.isString()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.name"_s, "string"_s, nameValue); + } + } -void NodeVMSourceTextModule::destroy(JSCell* cell) -{ - static_cast(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule(); -} + // Check origin property + if (JSValue originValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "origin"_s))) { + RETURN_IF_EXCEPTION(scope, {}); + if (!originValue.isUndefined() && !originValue.isString()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.origin"_s, "string"_s, originValue); + } + } + } -static EncodedJSValue -constructModule(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {}) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - ArgList args(callFrame); + JSObject* sandbox = asObject(contextArg); - NodeVMModule* module = NodeVMModule::create(vm, globalObject, args); + auto* targetContext = NodeVMGlobalObject::create(vm, + defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure()); - return JSValue::encode(module); -} + // Set sandbox as contextified object + targetContext->setContextifiedObject(sandbox); -JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) -{ - auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); - return JSValue::encode(JSC::jsString(globalObject->vm(), thisObject->identifier())); -} + // Store context in WeakMap for isContext checks + auto* zigGlobalObject = defaultGlobalObject(globalObject); + zigGlobalObject->vmModuleContextMap()->set(vm, sandbox, targetContext); -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(JSC::jsNumber(static_cast(thisObject->status()))); + return JSValue::encode(sandbox); } -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(vmModule_isContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { - auto* thisObject = jsCast(callFrame->thisValue()); - - using enum NodeVMModule::Status; - switch (thisObject->status()) { - case Unlinked: - return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("unlinked"_s))); - case Linking: - return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linking"_s))); - case Linked: - return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linked"_s))); - case Evaluating: - return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluating"_s))); - case Evaluated: - return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluated"_s))); - case Errored: - return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("errored"_s))); - default: - return JSC::encodedJSUndefined(); + ArgList args(callFrame); + 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)); } -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject->namespace_()); -} +// NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) +// { +// auto* obj = new (NotNull, allocateCell(vm)) NodeVMGlobalObject(vm, structure); +// obj->finishCreation(vm); +// return obj; +// } -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); -} +// void NodeVMGlobalObject::finishCreation(VM& vm, JSObject* context) +// { +// Base::finishCreation(vm); +// // We don't need to store the context anymore since we use proxies +// } -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - auto* thisObject = jsCast(callFrame->thisValue()); - const WTF::Vector& requests = thisObject->moduleRequests(); +// DEFINE_VISIT_CHILDREN(NodeVMGlobalObject); - JSArray* array = constructEmptyArray(globalObject, nullptr, requests.size()); +// 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) +{ - for (unsigned i = 0; const NodeVMModuleRequest& request : requests) { - array->putDirectIndex(globalObject, i++, request.toJS(globalObject)); + auto* thisObject = jsCast(cell); + if (UNLIKELY(!thisObject->m_sandbox)) { + return Base::deleteProperty(cell, globalObject, propertyName, slot); } - return JSValue::encode(array); -} - -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); -} + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); -} + auto* sandbox = thisObject->m_sandbox.get(); + if (!sandbox->deleteProperty(sandbox, globalObject, propertyName, slot)) { + return false; + } -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); + RETURN_IF_EXCEPTION(scope, false); + return Base::deleteProperty(cell, globalObject, propertyName, slot); } -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +void NodeVMGlobalObject::getOwnPropertyNames(JSObject* cell, JSGlobalObject* globalObject, JSC::PropertyNameArray& propertyNames, JSC::DontEnumPropertiesMode mode) { - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); -} + auto* thisObject = jsCast(cell); -JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); -} + if (thisObject->m_sandbox) { + thisObject->m_sandbox->getOwnPropertyNames( + thisObject->m_sandbox.get(), + globalObject, + propertyNames, + mode); + } -template -void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) -{ - auto* vmModule = jsCast(cell); - ASSERT_GC_OBJECT_INHERITS(vmModule, info()); - Base::visitChildren(vmModule, visitor); - visitor.append(vmModule->m_namespace); + Base::getOwnPropertyNames(cell, globalObject, propertyNames, mode); } -DEFINE_VISIT_CHILDREN(NodeVMModule); - -const JSC::ClassInfo NodeVMModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; -const JSC::ClassInfo NodeVMSourceTextModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; -const JSC::ClassInfo NodeVMModulePrototype::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModulePrototype) }; -const JSC::ClassInfo NodeVMModuleConstructor::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModuleConstructor) }; - JSC_DEFINE_HOST_FUNCTION(vmIsModuleNamespaceObject, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); @@ -2286,38 +1006,205 @@ void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) }); } -JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) +BaseVMOptions::BaseVMOptions(String filename) + : filename(WTFMove(filename)) +{ +} + +BaseVMOptions::BaseVMOptions(String filename, OrdinalNumber lineOffset, OrdinalNumber columnOffset) + : filename(WTFMove(filename)) + , lineOffset(lineOffset) + , columnOffset(columnOffset) { - return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); } -NodeVMModuleConstructor* NodeVMModuleConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) +bool BaseVMOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { - NodeVMModuleConstructor* ptr = new (NotNull, allocateCell(vm)) NodeVMModuleConstructor(vm, structure); - ptr->finishCreation(vm, prototype); - return ptr; + JSObject* options = nullptr; + bool any = false; + + if (!optionsArg.isUndefined()) { + if (optionsArg.isObject()) { + options = asObject(optionsArg); + } else { + auto _ = ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); + return false; + } + + if (JSValue filenameOpt = options->getIfPropertyExists(globalObject, builtinNames(vm).filenamePublicName())) { + if (filenameOpt.isString()) { + this->filename = filenameOpt.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, false); + any = true; + } else if (!filenameOpt.isUndefined()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.filename"_s, "string"_s, filenameOpt); + return false; + } + } else { + this->filename = "evalmachine."_s; + } + + if (JSValue lineOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "lineOffset"_s))) { + if (lineOffsetOpt.isAnyInt()) { + if (!lineOffsetOpt.isInt32()) { + ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), lineOffsetOpt); + return false; + } + this->lineOffset = OrdinalNumber::fromZeroBasedInt(lineOffsetOpt.asInt32()); + any = true; + } else if (lineOffsetOpt.isNumber()) { + ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, "an integer"_s, lineOffsetOpt); + return false; + } else if (!lineOffsetOpt.isUndefined()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.lineOffset"_s, "number"_s, lineOffsetOpt); + return false; + } + } + + if (JSValue columnOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "columnOffset"_s))) { + if (columnOffsetOpt.isAnyInt()) { + if (!columnOffsetOpt.isInt32()) { + ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), columnOffsetOpt); + return false; + } + int columnOffsetValue = columnOffsetOpt.asInt32(); + + this->columnOffset = OrdinalNumber::fromZeroBasedInt(columnOffsetValue); + any = true; + } else if (columnOffsetOpt.isNumber()) { + ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, "an integer"_s, columnOffsetOpt); + return false; + } else if (!columnOffsetOpt.isUndefined()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.columnOffset"_s, "number"_s, columnOffsetOpt); + return false; + } + } + } + + return any; } -JSC_DEFINE_HOST_FUNCTION(moduleConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame)) +bool BaseVMOptions::validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool& outProduceCachedData) { - return constructModule(globalObject, callFrame); + JSValue produceCachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "produceCachedData"_s)); + if (produceCachedDataOpt && !produceCachedDataOpt.isUndefined()) { + RETURN_IF_EXCEPTION(scope, {}); + if (!produceCachedDataOpt.isBoolean()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.produceCachedData"_s, "boolean"_s, produceCachedDataOpt); + return false; + } + outProduceCachedData = produceCachedDataOpt.asBoolean(); + return true; + } + return false; } -JSC_DEFINE_HOST_FUNCTION(moduleConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame)) +bool BaseVMOptions::validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, WTF::Vector& outCachedData) { - return constructModule(globalObject, callFrame, callFrame->newTarget()); + JSValue cachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "cachedData"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (cachedDataOpt && !cachedDataOpt.isUndefined()) { + // Verify it's a Buffer, TypedArray or DataView and extract the data if it is. + if (extractCachedData(cachedDataOpt, outCachedData)) { + return true; + } + + ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); + } + + return false; } -NodeVMModuleConstructor::NodeVMModuleConstructor(VM& vm, Structure* structure) - : NodeVMModuleConstructor::Base(vm, structure, moduleConstructorCall, moduleConstructorConstruct) +bool BaseVMOptions::validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional& outTimeout) { + JSValue timeoutOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "timeout"_s)); + if (timeoutOpt && !timeoutOpt.isUndefined()) { + if (!timeoutOpt.isNumber()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.timeout"_s, "number"_s, timeoutOpt); + return false; + } + + ssize_t timeoutValue; + V::validateInteger(scope, globalObject, timeoutOpt, "options.timeout"_s, jsNumber(1), jsNumber(std::numeric_limits().max()), &timeoutValue); + RETURN_IF_EXCEPTION(scope, {}); + + outTimeout = timeoutValue; + return true; + } + return false; } -void NodeVMModuleConstructor::finishCreation(VM& vm, JSObject* prototype) +bool CompileFunctionOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { - Base::finishCreation(vm, 1, "Module"_s, PropertyAdditionMode::WithStructureTransition); - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - ASSERT(inherits(info())); + this->parsingContext = globalObject; + bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg); + RETURN_IF_EXCEPTION(scope, false); + + if (!optionsArg.isUndefined() && !optionsArg.isString()) { + JSObject* options = asObject(optionsArg); + + if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } + + if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } + + JSValue parsingContextValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "parsingContext"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!parsingContextValue.isEmpty() && !parsingContextValue.isUndefined()) { + if (parsingContextValue.isNull() || !parsingContextValue.isObject()) + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + + JSObject* context = asObject(parsingContextValue); + auto* zigGlobalObject = defaultGlobalObject(globalObject); + JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); + + if (scopeValue.isUndefined()) + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + + parsingContext = jsDynamicCast(scopeValue); + if (!parsingContext) + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); + + any = true; + } + + // Handle contextExtensions option + JSValue contextExtensionsValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextExtensions"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!contextExtensionsValue.isEmpty() && !contextExtensionsValue.isUndefined()) { + if (contextExtensionsValue.isNull() || !contextExtensionsValue.isObject()) + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); + + if (auto* contextExtensionsObject = asObject(contextExtensionsValue)) { + if (!isArray(globalObject, contextExtensionsObject)) + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); + + // Validate that all items in the array are objects + auto* contextExtensionsArray = jsCast(contextExtensionsValue); + unsigned length = contextExtensionsArray->length(); + for (unsigned i = 0; i < length; i++) { + JSValue extension = contextExtensionsArray->getIndexQuickly(i); + if (!extension.isObject()) + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions[0]"_s, "object"_s, extension); + } + } else { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); + } + + this->contextExtensions = contextExtensionsValue; + any = true; + } + } + + return any; } } // namespace Bun diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index bebac6c878c8..0604838baf0a 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -14,6 +14,23 @@ namespace Bun { +class NodeVMGlobalObject; + +namespace NodeVM { + +RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source); +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); +/// For vm.compileFunction we need to return an anonymous function expression +/// +/// This code is adapted/inspired from JSC::constructFunction, which is used for function declarations. +JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr); + +} // namespace NodeVM + // This class represents a sandboxed global object for vm contexts class NodeVMGlobalObject final : public Bun::GlobalScope { using Base = Bun::GlobalScope; @@ -61,4 +78,33 @@ JSC_DECLARE_HOST_FUNCTION(vmModule_isContext); JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext); JSC_DECLARE_HOST_FUNCTION(vmModuleRunInThisContext); +class BaseVMOptions { +public: + String filename; + OrdinalNumber lineOffset = OrdinalNumber::fromZeroBasedInt(0); + OrdinalNumber columnOffset = OrdinalNumber::fromZeroBasedInt(0); + bool failed = false; + + BaseVMOptions() = default; + BaseVMOptions(String filename); + BaseVMOptions(String filename, OrdinalNumber lineOffset, OrdinalNumber columnOffset); + + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg); + bool validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool& outProduceCachedData); + bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, WTF::Vector& outCachedData); + bool validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional& outTimeout); +}; + +class CompileFunctionOptions : public BaseVMOptions { +public: + WTF::Vector cachedData; + JSGlobalObject* parsingContext = nullptr; + JSValue contextExtensions; + bool produceCachedData = false; + + using BaseVMOptions::BaseVMOptions; + + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg); +}; + } // namespace Bun diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp new file mode 100644 index 000000000000..6cc7cd69375e --- /dev/null +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -0,0 +1,284 @@ +#include "NodeVMModule.h" +#include "NodeVMSourceTextModule.h" + +#include "JSDOMExceptionHandling.h" + +namespace Bun { + +NodeVMModuleRequest::NodeVMModuleRequest(WTF::String specifier, WTF::HashMap importAttributes) + : m_specifier(WTFMove(specifier)) + , m_importAttributes(WTFMove(importAttributes)) +{ +} + +void NodeVMModuleRequest::addImportAttribute(WTF::String key, WTF::String value) +{ + m_importAttributes.set(WTFMove(key), WTFMove(value)); +} + +JSArray* NodeVMModuleRequest::toJS(JSGlobalObject* globalObject) const +{ + JSArray* array = JSC::constructEmptyArray(globalObject, nullptr, 2); + array->putDirectIndex(globalObject, 0, JSC::jsString(globalObject->vm(), m_specifier)); + + JSObject* attributes = JSC::constructEmptyObject(globalObject); + 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); + } + array->putDirectIndex(globalObject, 1, attributes); + + return array; +} + +NodeVMModule::NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) + : Base(vm, structure) + , m_identifier(WTFMove(identifier)) +{ +} + +NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue disambiguator = args.at(2); + + if (disambiguator.isString()) { + return NodeVMSourceTextModule::create(vm, globalObject, args); + } + + if (disambiguator.inherits(JSArray::info())) { + // return NodeVMSyntheticModule::create(vm, globalObject, args); + } + + throwArgumentTypeError(*globalObject, scope, 2, "sourceText or syntheticExportNames"_s, "Module"_s, "Module"_s, "string or array"_s); + return nullptr; +} + +JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetNamespace); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetError); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleInstantiate); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleEvaluate); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleLink); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport); + +static const HashTableValue NodeVMModulePrototypeTableValues[] = { + { "identifier"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } }, + { "getStatusCode"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatusCode, 0 } }, + { "getStatus"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, + { "getNamespace"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetNamespace, 0 } }, + { "getError"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetError, 0 } }, + { "instantiate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleInstantiate, 0 } }, + { "evaluate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleEvaluate, 2 } }, + { "getModuleRequests"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetModuleRequests, 0 } }, + { "link"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleLink, 2 } }, + { "createCachedData"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleCreateCachedData, 0 } }, + { "setExport"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleSetExport, 2 } }, +}; + +NodeVMModulePrototype* NodeVMModulePrototype::create(VM& vm, Structure* structure) +{ + NodeVMModulePrototype* prototype = new (NotNull, allocateCell(vm)) NodeVMModulePrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; +} + +Structure* NodeVMModulePrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); +} + +NodeVMModulePrototype::NodeVMModulePrototype(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void NodeVMModulePrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + reifyStaticProperties(vm, info(), NodeVMModulePrototypeTableValues, *this); + this->structure()->setMayBePrototype(true); +} + +JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) +{ + auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); + return JSValue::encode(JSC::jsString(globalObject->vm(), thisObject->identifier())); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(JSC::jsNumber(static_cast(thisObject->status()))); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + + using enum NodeVMModule::Status; + switch (thisObject->status()) { + case Unlinked: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("unlinked"_s))); + case Linking: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linking"_s))); + case Linked: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linked"_s))); + case Evaluating: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluating"_s))); + case Evaluated: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluated"_s))); + case Errored: + return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("errored"_s))); + default: + return JSC::encodedJSUndefined(); + } +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(thisObject->namespace_()); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + const WTF::Vector& requests = thisObject->moduleRequests(); + + JSArray* array = constructEmptyArray(globalObject, nullptr, requests.size()); + + for (unsigned i = 0; const NodeVMModuleRequest& request : requests) { + array->putDirectIndex(globalObject, i++, request.toJS(globalObject)); + } + + return JSValue::encode(array); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSArray* specifiers = jsDynamicCast(callFrame->argument(0)); + JSArray* moduleNatives = jsDynamicCast(callFrame->argument(1)); + + if (!specifiers) { + return throwArgumentTypeError(*globalObject, scope, 0, "specifiers"_s, "Module"_s, "Module"_s, "Array"_s); + } + + if (!moduleNatives) { + return throwArgumentTypeError(*globalObject, scope, 1, "moduleNatives"_s, "Module"_s, "Module"_s, "Array"_s); + } + + if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { + return thisObject->link(globalObject, specifiers, moduleNatives); + // } 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 {}; + } +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); +} + +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + // auto* thisObject = jsCast(callFrame->thisValue()); + return JSC::encodedJSUndefined(); +} + +template +void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* vmModule = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(vmModule, info()); + Base::visitChildren(vmModule, visitor); + + visitor.append(vmModule->m_namespace); + + auto moduleNatives = vmModule->m_resolveCache.values(); + visitor.append(moduleNatives.begin(), moduleNatives.end()); +} + +DEFINE_VISIT_CHILDREN(NodeVMModule); + +static EncodedJSValue +constructModule(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {}) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + ArgList args(callFrame); + + NodeVMModule* module = NodeVMModule::create(vm, globalObject, args); + + return JSValue::encode(module); +} + +JSC_DEFINE_HOST_FUNCTION(moduleConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return constructModule(globalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(moduleConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return constructModule(globalObject, callFrame, callFrame->newTarget()); +} + +NodeVMModuleConstructor* NodeVMModuleConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) +{ + NodeVMModuleConstructor* ptr = new (NotNull, allocateCell(vm)) NodeVMModuleConstructor(vm, structure); + ptr->finishCreation(vm, prototype); + return ptr; +} + +NodeVMModuleConstructor::NodeVMModuleConstructor(VM& vm, Structure* structure) + : NodeVMModuleConstructor::Base(vm, structure, moduleConstructorCall, moduleConstructorConstruct) +{ +} + +JSC::Structure* NodeVMModuleConstructor::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) +{ + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); +} + +void NodeVMModuleConstructor::finishCreation(VM& vm, JSObject* prototype) +{ + Base::finishCreation(vm, 1, "Module"_s, PropertyAdditionMode::WithStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +const JSC::ClassInfo NodeVMModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; +const JSC::ClassInfo NodeVMModulePrototype::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModulePrototype) }; +const JSC::ClassInfo NodeVMModuleConstructor::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModuleConstructor) }; + +} // namespace Bun diff --git a/src/bun.js/bindings/NodeVMModule.h b/src/bun.js/bindings/NodeVMModule.h new file mode 100644 index 000000000000..1077cef8478c --- /dev/null +++ b/src/bun.js/bindings/NodeVMModule.h @@ -0,0 +1,108 @@ +#pragma once + +#include "NodeVM.h" + +namespace Bun { + +class NodeVMModuleRequest final { +public: + NodeVMModuleRequest(WTF::String specifier, WTF::HashMap importAttributes = {}); + + JSArray* toJS(JSGlobalObject* globalObject) const; + void addImportAttribute(WTF::String key, WTF::String value); + + const WTF::String& specifier() const { return m_specifier; } + void specifier(WTF::String value) { m_specifier = value; } + const WTF::HashMap& importAttributes() const { return m_importAttributes; } + +private: + WTF::String m_specifier; + WTF::HashMap m_importAttributes; +}; + +class NodeVMModule : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + + enum class Status : uint8_t { + Unlinked, + Linking, + Linked, + Evaluating, + Evaluated, + Errored + }; + + enum class Type : uint8_t { + SourceText, + Synthetic, + }; + + static NodeVMModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args); + + const WTF::String& identifier() const { return m_identifier; } + + Status status() const { return m_status; } + void status(Status value) { m_status = value; } + + JSObject* namespace_() const { return m_namespace.get(); } + void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } + + const WTF::Vector& moduleRequests() const { return m_moduleRequests; } + void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); } + +protected: + WTF::String m_identifier; + Status m_status = Status::Unlinked; + mutable WriteBarrier m_namespace; + WTF::Vector m_moduleRequests; + mutable WTF::HashMap> m_resolveCache; + + NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier); + + DECLARE_EXPORT_INFO; + DECLARE_VISIT_CHILDREN; +}; + +class NodeVMModulePrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static NodeVMModulePrototype* create(VM& vm, Structure* structure); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, Base); + return &vm.plainObjectSpace(); + } + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype); + +private: + NodeVMModulePrototype(VM& vm, Structure* structure); + + void finishCreation(VM& vm); +}; + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, NodeVMModulePrototype::Base); + +class NodeVMModuleConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + DECLARE_EXPORT_INFO; + + static NodeVMModuleConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); + +private: + NodeVMModuleConstructor(JSC::VM& vm, JSC::Structure* structure); + + void finishCreation(JSC::VM&, JSC::JSObject* prototype); +}; + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModuleConstructor, JSC::InternalFunction); + +} // namespace Bun diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp new file mode 100644 index 000000000000..9e4fa894198d --- /dev/null +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -0,0 +1,575 @@ +#include "Completion.h" +#include "ErrorCode.h" +#include "JavaScriptCore/JIT.h" +#include "JavaScriptCore/JSWeakMap.h" +#include "JavaScriptCore/JSWeakMapInlines.h" +#include "JavaScriptCore/ProgramCodeBlock.h" +#include "JavaScriptCore/SourceCodeKey.h" +#include "NodeVMScript.h" + +namespace Bun { +using namespace NodeVM; + +/// For some reason Node has this error message with a grammar error and we have to match it so the tests pass: +/// `The "" argument must be an vm.Context` +static JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value) +{ + WTF::StringBuilder builder; + builder.append("The \""_s); + builder.append(name); + builder.append("\" argument must be an vm.Context"_s); + + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, builder.toString())); + return {}; +} + +bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) +{ + bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg); + RETURN_IF_EXCEPTION(scope, false); + + if (!optionsArg.isUndefined() && !optionsArg.isString()) { + JSObject* options = asObject(optionsArg); + + // Validate contextName and contextOrigin are strings + if (JSValue contextNameOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextName"_s))) { + if (!contextNameOpt.isUndefined() && !contextNameOpt.isString()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextName"_s, "string"_s, contextNameOpt); + return false; + } + any = true; + } + RETURN_IF_EXCEPTION(scope, false); + + if (JSValue contextOriginOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextOrigin"_s))) { + if (!contextOriginOpt.isUndefined() && !contextOriginOpt.isString()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextOrigin"_s, "string"_s, contextOriginOpt); + return false; + } + any = true; + } + RETURN_IF_EXCEPTION(scope, false); + + if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } + + if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } + + if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } + } + + return any; +} + +static EncodedJSValue +constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {}) +{ + VM& vm = globalObject->vm(); + 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()); + + JSValue optionsArg = args.at(1); + ScriptOptions options(""_s); + if (optionsArg.isString()) { + options.filename = optionsArg.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + } else if (!options.fromJS(globalObject, vm, scope, optionsArg)) { + RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); + } + + auto* zigGlobalObject = defaultGlobalObject(globalObject); + Structure* structure = zigGlobalObject->NodeVMScriptStructure(); + if (UNLIKELY(zigGlobalObject->NodeVMScript() != newTarget)) { + auto scope = DECLARE_THROW_SCOPE(vm); + if (!newTarget) { + throwTypeError(globalObject, scope, "Class constructor Script cannot be invoked without 'new'"_s); + return {}; + } + + auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject())); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + globalObject, newTarget.getObject(), functionGlobalObject->NodeVMScriptStructure()); + scope.release(); + } + + SourceCode source( + JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), + options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); + RETURN_IF_EXCEPTION(scope, {}); + + const bool produceCachedData = options.produceCachedData; + auto filename = options.filename; + + NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, WTFMove(options)); + + WTF::Vector& cachedData = script->cachedData(); + + if (!cachedData.isEmpty()) { + JSC::ProgramExecutable* executable = script->cachedExecutable(); + if (!executable) { + executable = script->createExecutable(); + } + ASSERT(executable); + + JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? JSC::TaintedByWithScopeLexicallyScopedFeature : JSC::NoLexicallyScopedFeatures; + JSC::SourceCodeKey key(source, {}, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); + Ref cachedBytecode = JSC::CachedBytecode::create(std::span(cachedData), nullptr, {}); + JSC::UnlinkedProgramCodeBlock* unlinkedBlock = JSC::decodeCodeBlock(vm, key, WTFMove(cachedBytecode)); + + if (!unlinkedBlock) { + script->cachedDataRejected(TriState::True); + } else { + JSC::JSScope* jsScope = globalObject->globalScope(); + JSC::CodeBlock* codeBlock = nullptr; + { + // JSC::ProgramCodeBlock::create() requires GC to be deferred. + DeferGC deferGC(vm); + codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); + } + JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); + if (compilationResult != JSC::CompilationResult::CompilationFailed) { + executable->installCode(codeBlock); + script->cachedDataRejected(TriState::False); + } else { + script->cachedDataRejected(TriState::True); + } + } + } else if (produceCachedData) { + script->cacheBytecode(); + // TODO(@heimskr): is there ever a case where bytecode production fails? + script->cachedDataProduced(true); + } + + return JSValue::encode(script); +} + +JSC_DEFINE_HOST_FUNCTION(scriptConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return constructScript(globalObject, callFrame); +} + +JSC_DEFINE_HOST_FUNCTION(scriptConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return constructScript(globalObject, callFrame, callFrame->newTarget()); +} + +JSC::ProgramExecutable* NodeVMScript::createExecutable() +{ + auto& vm = JSC::getVM(globalObject()); + m_cachedExecutable.set(vm, this, JSC::ProgramExecutable::create(globalObject(), m_source)); + return m_cachedExecutable.get(); +} + +void NodeVMScript::cacheBytecode() +{ + if (!m_cachedExecutable) { + createExecutable(); + } + + m_cachedBytecode = getBytecode(globalObject(), m_cachedExecutable.get(), m_source); + m_cachedDataProduced = m_cachedBytecode != nullptr; +} + +JSC::JSUint8Array* NodeVMScript::getBytecodeBuffer() +{ + if (!m_options.produceCachedData) { + return nullptr; + } + + if (!m_cachedBytecodeBuffer) { + if (!m_cachedBytecode) { + cacheBytecode(); + } + + ASSERT(m_cachedBytecode); + + std::span bytes = m_cachedBytecode->span(); + m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject(), bytes)); + } + + ASSERT(m_cachedBytecodeBuffer); + return m_cachedBytecodeBuffer.get(); +} + +DEFINE_VISIT_CHILDREN(NodeVMScript); + +template +void NodeVMScript::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + NodeVMScript* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_cachedExecutable); + visitor.append(thisObject->m_cachedBytecodeBuffer); +} + +NodeVMScriptConstructor::NodeVMScriptConstructor(VM& vm, Structure* structure) + : NodeVMScriptConstructor::Base(vm, structure, scriptConstructorCall, scriptConstructorConstruct) +{ +} + +NodeVMScriptConstructor* NodeVMScriptConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) +{ + NodeVMScriptConstructor* ptr = new (NotNull, allocateCell(vm)) NodeVMScriptConstructor(vm, structure); + ptr->finishCreation(vm, prototype); + return ptr; +} + +void NodeVMScriptConstructor::finishCreation(VM& vm, JSObject* prototype) +{ + Base::finishCreation(vm, 1, "Script"_s, PropertyAdditionMode::WithStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +NodeVMScript* NodeVMScript::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, SourceCode source, ScriptOptions options) +{ + NodeVMScript* ptr = new (NotNull, allocateCell(vm)) NodeVMScript(vm, structure, source, WTFMove(options)); + ptr->finishCreation(vm); + return ptr; +} + +void NodeVMScript::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +void NodeVMScript::destroy(JSCell* cell) +{ + static_cast(cell)->NodeVMScript::~NodeVMScript(); +} + +static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false) +{ + + auto& vm = JSC::getVM(globalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + RunningScriptOptions options; + if (allowStringInPlaceOfOptions && optionsArg.isString()) { + options.filename = optionsArg.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + } else if (!options.fromJS(globalObject, vm, throwScope, optionsArg)) { + RETURN_IF_EXCEPTION(throwScope, {}); + options = {}; + } + + // Set the contextified object before evaluating + globalObject->setContextifiedObject(contextifiedObject); + + NakedPtr exception; + JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); + + if (UNLIKELY(exception)) { + if (handleException(globalObject, vm, exception, throwScope)) { + return {}; + } + JSC::throwException(globalObject, throwScope, exception.get()); + return {}; + } + + return JSValue::encode(result); +} + +JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = JSC::getVM(globalObject); + JSValue thisValue = callFrame->thisValue(); + auto* script = jsDynamicCast(thisValue); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(throwScope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + JSValue contextArg = callFrame->argument(0); + if (contextArg.isUndefined()) { + contextArg = JSC::constructEmptyObject(globalObject); + } + + if (!contextArg.isObject()) { + return ERR::INVALID_ARG_TYPE(throwScope, globalObject, "context"_s, "object"_s, contextArg); + } + + RunningScriptOptions options; + if (!options.fromJS(globalObject, vm, throwScope, contextArg)) { + RETURN_IF_EXCEPTION(throwScope, {}); + options = {}; + } + + NakedPtr exception; + JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); + + if (UNLIKELY(exception)) { + if (handleException(globalObject, vm, exception, throwScope)) { + return {}; + } + JSC::throwException(globalObject, throwScope, exception.get()); + return {}; + } + + RETURN_IF_EXCEPTION(throwScope, {}); + return JSValue::encode(result); +} + +JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue thisValue = JSValue::decode(thisValueEncoded); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + const auto& url = script->source().provider()->sourceMappingURLDirective(); + return JSValue::encode(jsString(vm, url)); +} + +JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue thisValue = JSValue::decode(thisValueEncoded); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + if (auto* buffer = script->getBytecodeBuffer()) { + return JSValue::encode(buffer); + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue thisValue = JSValue::decode(thisValueEncoded); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + return JSValue::encode(jsBoolean(script->cachedDataProduced())); +} + +JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue thisValue = JSValue::decode(thisValueEncoded); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + switch (script->cachedDataRejected()) { + case TriState::True: + return JSValue::encode(jsBoolean(true)); + case TriState::False: + return JSValue::encode(jsBoolean(false)); + default: + return JSValue::encode(jsUndefined()); + } +} + +JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue thisValue = callFrame->thisValue(); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + const JSC::SourceCode& source = script->source(); + return createCachedData(globalObject, source); +} + +JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue thisValue = callFrame->thisValue(); + auto* script = jsDynamicCast(thisValue); + if (UNLIKELY(!script)) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); + } + + ArgList args(callFrame); + JSValue contextArg = args.at(0); + if (contextArg.isUndefinedOrNull()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); + } + + if (!contextArg.isObject()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); + } + + JSObject* context = asObject(contextArg); + auto* zigGlobalObject = defaultGlobalObject(globalObject); + JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); + if (scopeValue.isUndefined()) { + return INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); + } + + NodeVMGlobalObject* nodeVmGlobalObject = jsDynamicCast(scopeValue); + if (!nodeVmGlobalObject) { + return INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); + } + + return runInContext(nodeVmGlobalObject, script, context, args.at(1)); +} + +JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& 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) { + throwTypeError(globalObject, scope, "this.runInContext is not a function"_s); + return {}; + } + + if (!contextObjectValue || contextObjectValue.isUndefinedOrNull()) { + contextObjectValue = JSC::constructEmptyObject(globalObject); + } + + if (UNLIKELY(!contextObjectValue || !contextObjectValue.isObject())) { + throwTypeError(globalObject, scope, "Context must be an object"_s); + return {}; + } + + // we don't care about options for now + // TODO: options + // bool didThrow = false; + + auto* zigGlobal = defaultGlobalObject(globalObject); + JSObject* context = asObject(contextObjectValue); + auto* targetContext = NodeVMGlobalObject::create( + vm, zigGlobal->NodeVMGlobalObjectStructure()); + + return runInContext(targetContext, script, context, callFrame->argument(1)); +} + +class NodeVMScriptPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static NodeVMScriptPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure) + { + NodeVMScriptPrototype* ptr = new (NotNull, allocateCell(vm)) NodeVMScriptPrototype(vm, structure); + ptr->finishCreation(vm); + return ptr; + } + + DECLARE_INFO; + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, Base); + return &vm.plainObjectSpace(); + } + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); + } + +private: + NodeVMScriptPrototype(VM& vm, Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(VM&); +}; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, NodeVMScriptPrototype::Base); + +static const struct HashTableValue scriptPrototypeTableValues[] = { + { "createCachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptCreateCachedData, 1 } }, + { "runInContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInContext, 2 } }, + { "runInNewContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInNewContext, 2 } }, + { "runInThisContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInThisContext, 2 } }, + { "sourceMapURL"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetSourceMapURL, nullptr } }, + { "cachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedData, nullptr } }, + { "cachedDataProduced"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataProduced, nullptr } }, + { "cachedDataRejected"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataRejected, nullptr } }, +}; + +void NodeVMScriptPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, NodeVMScript::info(), scriptPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSObject* NodeVMScript::createPrototype(VM& vm, JSGlobalObject* globalObject) +{ + return NodeVMScriptPrototype::create(vm, globalObject, NodeVMScriptPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +const ClassInfo NodeVMScript::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScript) }; +const ClassInfo NodeVMScriptPrototype::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptPrototype) }; +const ClassInfo NodeVMScriptConstructor::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptConstructor) }; + +bool RunningScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) +{ + bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg); + RETURN_IF_EXCEPTION(scope, false); + + if (!optionsArg.isUndefined() && !optionsArg.isString()) { + JSObject* options = asObject(optionsArg); + + if (JSValue displayErrorsOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "displayErrors"_s))) { + RETURN_IF_EXCEPTION(scope, false); + if (!displayErrorsOpt.isBoolean()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.displayErrors"_s, "boolean"_s, displayErrorsOpt); + return false; + } + this->displayErrors = displayErrorsOpt.asBoolean(); + any = true; + } + + if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { + RETURN_IF_EXCEPTION(scope, false); + any = true; + } + + if (JSValue breakOnSigintOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "breakOnSigint"_s))) { + RETURN_IF_EXCEPTION(scope, false); + if (!breakOnSigintOpt.isBoolean()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.breakOnSigint"_s, "boolean"_s, breakOnSigintOpt); + return false; + } + this->breakOnSigint = breakOnSigintOpt.asBoolean(); + any = true; + } + } + + return any; +} + +} // namespace Bun diff --git a/src/bun.js/bindings/NodeVMScript.h b/src/bun.js/bindings/NodeVMScript.h new file mode 100644 index 000000000000..f3d907e83572 --- /dev/null +++ b/src/bun.js/bindings/NodeVMScript.h @@ -0,0 +1,111 @@ +#pragma once + +#include "NodeVM.h" + +namespace Bun { + +class ScriptOptions : public BaseVMOptions { +public: + std::optional timeout = std::nullopt; + bool produceCachedData = false; + WTF::Vector cachedData; + + using BaseVMOptions::BaseVMOptions; + + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg); +}; + +class NodeVMScriptConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static NodeVMScriptConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); + + DECLARE_EXPORT_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); + } + +private: + NodeVMScriptConstructor(JSC::VM& vm, JSC::Structure* structure); + + void finishCreation(JSC::VM&, JSC::JSObject* prototype); +}; + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptConstructor, JSC::InternalFunction); + +class NodeVMScript final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + + static NodeVMScript* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options); + + DECLARE_EXPORT_INFO; + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMScript.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMScript = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForNodeVMScript.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMScript = std::forward(space); }); + } + + static void destroy(JSC::JSCell*); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); + + JSC::ProgramExecutable* createExecutable(); + void cacheBytecode(); + JSC::JSUint8Array* getBytecodeBuffer(); + + const JSC::SourceCode& source() const { return m_source; } + WTF::Vector& cachedData() { return m_options.cachedData; } + RefPtr cachedBytecode() const { return m_cachedBytecode; } + JSC::ProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } + bool cachedDataProduced() const { return m_cachedDataProduced; } + void cachedDataProduced(bool value) { m_cachedDataProduced = value; } + TriState cachedDataRejected() const { return m_cachedDataRejected; } + void cachedDataRejected(TriState value) { m_cachedDataRejected = value; } + + DECLARE_VISIT_CHILDREN; + +private: + JSC::SourceCode m_source; + RefPtr m_cachedBytecode; + mutable JSC::WriteBarrier m_cachedBytecodeBuffer; + mutable JSC::WriteBarrier m_cachedExecutable; + ScriptOptions m_options; + bool m_cachedDataProduced = false; + TriState m_cachedDataRejected = TriState::Indeterminate; + + NodeVMScript(JSC::VM& vm, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options) + : Base(vm, structure) + , m_source(source) + , m_options(WTFMove(options)) + { + } + + void finishCreation(JSC::VM&); +}; + +class RunningScriptOptions : public BaseVMOptions { +public: + bool displayErrors = true; + std::optional timeout = std::nullopt; + bool breakOnSigint = false; + + using BaseVMOptions::BaseVMOptions; + + bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp new file mode 100644 index 000000000000..a87d4dcc6b9d --- /dev/null +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -0,0 +1,97 @@ +#include "NodeVMSourceTextModule.h" + +#include "JSDOMExceptionHandling.h" + +namespace Bun { +using namespace NodeVM; + +NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* globalObject, ArgList args) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue identifierValue = args.at(0); + if (!identifierValue.isString()) { + throwArgumentTypeError(*globalObject, scope, 0, "identifier"_s, "Module"_s, "Module"_s, "string"_s); + return nullptr; + } + + JSValue contextValue = args.at(1); + if (contextValue.isUndefined()) { + // TODO(@heimskr): should this be `globalObject->globalThis()` instead? + contextValue = globalObject; + } else if (!contextValue.isObject()) { + throwArgumentTypeError(*globalObject, scope, 1, "context"_s, "Module"_s, "Module"_s, "object"_s); + return nullptr; + } + + JSValue sourceTextValue = args.at(2); + if (!sourceTextValue.isString()) { + throwArgumentTypeError(*globalObject, scope, 2, "sourceText"_s, "Module"_s, "Module"_s, "string"_s); + return nullptr; + } + + JSValue lineOffsetValue = args.at(3); + if (!lineOffsetValue.isUInt32AsAnyInt()) { + throwArgumentTypeError(*globalObject, scope, 3, "lineOffset"_s, "Module"_s, "Module"_s, "number"_s); + return nullptr; + } + + JSValue columnOffsetValue = args.at(4); + if (!columnOffsetValue.isUInt32AsAnyInt()) { + throwArgumentTypeError(*globalObject, scope, 4, "columnOffset"_s, "Module"_s, "Module"_s, "number"_s); + return nullptr; + } + + JSValue cachedDataValue = args.at(5); + WTF::Vector cachedData; + if (!cachedDataValue.isUndefined() && !extractCachedData(cachedDataValue, cachedData)) { + throwArgumentTypeError(*globalObject, scope, 5, "cachedData"_s, "Module"_s, "Module"_s, "Buffer, TypedArray, or DataView"_s); + return nullptr; + } + + auto* zigGlobalObject = defaultGlobalObject(globalObject); + NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject)); + ptr->finishCreation(vm); + return ptr; +} + +void NodeVMSourceTextModule::destroy(JSCell* cell) +{ + static_cast(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule(); +} + +EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives) +{ + const unsigned length = specifiers->getArrayLength(); + ASSERT(length == moduleNatives->getArrayLength()); + if (length == 0) { + return JSC::encodedJSUndefined(); + } + + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + for (unsigned i = 0; i < length; i++) { + JSValue specifierValue = specifiers->getDirectIndex(globalObject, i); + JSValue moduleNativeValue = moduleNatives->getDirectIndex(globalObject, i); + + ASSERT(specifierValue.isString()); + ASSERT(moduleNativeValue.isObject()); + + WTF::String specifier = specifierValue.toWTFString(globalObject); + JSObject* moduleNative = moduleNativeValue.getObject(); + + m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); + } + + return JSC::encodedJSUndefined(); +} + +JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) +{ + return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); +} + +const JSC::ClassInfo NodeVMSourceTextModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; + +} // namespace Bun diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h new file mode 100644 index 000000000000..d775320b09ed --- /dev/null +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -0,0 +1,51 @@ +#pragma once + +#include "NodeVM.h" +#include "NodeVMModule.h" + +namespace Bun { + +class NodeVMSourceTextModule final : public NodeVMModule { +public: + using Base = NodeVMModule; + + static NodeVMSourceTextModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args); + + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMSourceTextModule.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMSourceTextModule = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForNodeVMSourceTextModule.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSourceTextModule = std::forward(space); }); + } + + static void destroy(JSC::JSCell* cell); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); + + EncodedJSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); + + DECLARE_EXPORT_INFO; + +private: + NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) + : Base(vm, structure, WTFMove(identifier)) + { + } + + void finishCreation(JSC::VM& vm) + { + Base::finishCreation(vm); + ASSERT(inherits(info())); + } +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 61b7a0e81bc2..cc66b26b4766 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -188,10 +188,10 @@ class GlobalObject : public Bun::GlobalScope { static void reportUncaughtExceptionAtEventLoop(JSGlobalObject*, JSC::Exception*); static JSGlobalObject* deriveShadowRealmGlobalObject(JSGlobalObject* globalObject); static JSC::JSInternalPromise* moduleLoaderImportModule(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSString* moduleNameValue, JSC::JSValue parameters, const JSC::SourceOrigin&); - static JSC::Identifier moduleLoaderResolve(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue keyValue, JSC::JSValue referrerValue, JSC::JSValue); - static JSC::JSInternalPromise* moduleLoaderFetch(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue); - static JSC::JSObject* moduleLoaderCreateImportMetaProperties(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSModuleRecord*, JSC::JSValue); - static JSC::JSValue moduleLoaderEvaluate(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::JSValue, JSC::JSValue); + static JSC::Identifier moduleLoaderResolve(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue key, JSC::JSValue referrer, JSC::JSValue origin); + static JSC::JSInternalPromise* moduleLoaderFetch(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue key, JSC::JSValue parameters, JSC::JSValue script); + static JSC::JSObject* moduleLoaderCreateImportMetaProperties(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSValue key, JSC::JSModuleRecord*, JSC::JSValue val); + static JSC::JSValue moduleLoaderEvaluate(JSGlobalObject*, JSC::JSModuleLoader*, JSValue key, JSValue moduleRecordValue, JSValue scriptFetcher, JSValue sentValue, JSValue resumeMode); static ScriptExecutionStatus scriptExecutionStatus(JSGlobalObject*, JSObject*); static void promiseRejectionTracker(JSGlobalObject*, JSC::JSPromise*, JSC::JSPromiseRejectionOperation); diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 47c9740a03a6..909211dbe024 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -293,8 +293,8 @@ class SourceTextModule extends Module { } try { - const modules = await SafePromiseAllReturnArrayLike(modulePromises); - this[kNative].link(specifiers, modules); + const moduleNatives = await SafePromiseAllReturnArrayLike(modulePromises); + this[kNative].link(specifiers, moduleNatives); } catch (e) { this.#error = e; throw e; From 94db5f9b693646f55e6493510515c846aea0a906 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 6 May 2025 15:26:12 -0700 Subject: [PATCH 28/76] WIP slop --- src/bun.js/bindings/NodeVMModule.cpp | 35 ++++++++ src/bun.js/bindings/NodeVMModule.h | 8 ++ .../bindings/NodeVMSourceTextModule.cpp | 81 ++++++++++++++++++- src/bun.js/bindings/NodeVMSourceTextModule.h | 10 ++- .../bindings/webcore/DOMClientIsoSubspaces.h | 2 +- src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 2 +- src/js/node/vm.ts | 4 + 7 files changed, 137 insertions(+), 5 deletions(-) diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index 6cc7cd69375e..b67e1297faed 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -37,6 +37,26 @@ NodeVMModule::NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String i { } +bool NodeVMModule::finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deque& stack, unsigned* dfsIndex) +{ + if (auto* thisObject = jsDynamicCast(this)) { + return thisObject->finishInstantiate(globalObject, stack, dfsIndex); + // } else if (auto* thisObject = jsDynamicCast(this)) { + // return thisObject->finishInstantiate(globalObject); + } + + return true; +} + +bool NodeVMModule::createModuleRecord(JSC::JSGlobalObject* globalObject) +{ + if (auto* thisObject = jsDynamicCast(this)) { + return thisObject->createModuleRecord(globalObject); + } + + return true; +} + NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -65,6 +85,7 @@ JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleLink); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport); +JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateModuleRecord); static const HashTableValue NodeVMModulePrototypeTableValues[] = { { "identifier"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } }, @@ -78,6 +99,7 @@ static const HashTableValue NodeVMModulePrototypeTableValues[] = { { "link"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleLink, 2 } }, { "createCachedData"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleCreateCachedData, 0 } }, { "setExport"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleSetExport, 2 } }, + { "createModuleRecord"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleCreateModuleRecord, 0 } }, }; NodeVMModulePrototype* NodeVMModulePrototype::create(VM& vm, Structure* structure) @@ -188,8 +210,15 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject return throwArgumentTypeError(*globalObject, scope, 1, "moduleNatives"_s, "Module"_s, "Module"_s, "Array"_s); } + // JSValue linker = callFrame->argument(0); + + // if (!linker.isCallable()) { + // return throwArgumentTypeError(*globalObject, scope, 0, "linker"_s, "Module"_s, "Module"_s, "function"_s); + // } + if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { return thisObject->link(globalObject, specifiers, moduleNatives); + // return thisObject->link(globalObject, linker); // } else if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { // return thisObject->link(globalObject, specifiers, moduleNatives); } else { @@ -216,6 +245,12 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * return JSC::encodedJSUndefined(); } +JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateModuleRecord, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* thisObject = jsCast(callFrame->thisValue()); + return JSValue::encode(JSC::jsBoolean(thisObject->createModuleRecord(globalObject))); +} + template void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) { diff --git a/src/bun.js/bindings/NodeVMModule.h b/src/bun.js/bindings/NodeVMModule.h index 1077cef8478c..e911fe394f09 100644 --- a/src/bun.js/bindings/NodeVMModule.h +++ b/src/bun.js/bindings/NodeVMModule.h @@ -4,6 +4,8 @@ namespace Bun { +class NodeVMSourceTextModule; + class NodeVMModuleRequest final { public: NodeVMModuleRequest(WTF::String specifier, WTF::HashMap importAttributes = {}); @@ -51,6 +53,12 @@ class NodeVMModule : public JSC::JSDestructibleObject { const WTF::Vector& moduleRequests() const { return m_moduleRequests; } void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); } + // Purposely not virtual. Dispatches to the correct subclass. + bool finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deque& stack, unsigned* dfsIndex); + + // Purposely not virtual. Dispatches to the correct subclass. + bool createModuleRecord(JSC::JSGlobalObject* globalObject); + protected: WTF::String m_identifier; Status m_status = Status::Unlinked; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index a87d4dcc6b9d..93c7a5596ede 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -1,6 +1,11 @@ #include "NodeVMSourceTextModule.h" +#include "ErrorCode.h" #include "JSDOMExceptionHandling.h" +#include "JSModuleRecord.h" +#include "ModuleAnalyzer.h" + +#include namespace Bun { using namespace NodeVM; @@ -17,7 +22,6 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g JSValue contextValue = args.at(1); if (contextValue.isUndefined()) { - // TODO(@heimskr): should this be `globalObject->globalThis()` instead? contextValue = globalObject; } else if (!contextValue.isObject()) { throwArgumentTypeError(*globalObject, scope, 1, "context"_s, "Module"_s, "Module"_s, "object"_s); @@ -49,8 +53,16 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g return nullptr; } + uint32_t lineOffset = lineOffsetValue.toUInt32(globalObject); + uint32_t columnOffset = columnOffsetValue.toUInt32(globalObject); + + Ref sourceProvider = StringSourceProvider::create(sourceTextValue.toWTFString(globalObject), 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)); + NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject), WTFMove(sourceCode)); ptr->finishCreation(vm); return ptr; } @@ -60,6 +72,32 @@ void NodeVMSourceTextModule::destroy(JSCell* cell) static_cast(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule(); } +bool NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) +{ + if (m_moduleRecord) { + return false; + } + + VM& vm = globalObject->vm(); + + ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); + + JSModuleRecord* moduleRecord = JSModuleRecord::create(globalObject, vm, globalObject->m_moduleRecordStructure.get(globalObject), Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); + m_moduleRecord.set(vm, this, moduleRecord); + + std::println("link synchronousness: {}", int(moduleRecord->link(globalObject, JSC::jsUndefined()))); + + const auto& requests = moduleRecord->requestedModules(); + + std::println("requests: {}", requests.size()); + + for (const auto& request : requests) { + std::println("request: {}", request.m_specifier->utf8().data()); + } + + return true; +} + EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives) { const unsigned length = specifiers->getArrayLength(); @@ -87,11 +125,50 @@ EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArra return JSC::encodedJSUndefined(); } +// EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSValue linker) +// { +// VM& vm = globalObject->vm(); +// auto scope = DECLARE_THROW_SCOPE(vm); + +// if (status() != Status::Unlinked) { +// throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_ALREADY_LINKED, "Module is already linked"_s); +// return {}; +// } + +// status(Status::Linking); + +// JSModuleRecord* moduleRecord = JSModuleRecord::create(globalObject, vm, globalObject->m_moduleRecordStructure.get(globalObject), Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); + +// Synchronousness synchronousness = moduleRecord->link(globalObject, linker); + +// std::println("synchronousness: {}", int(synchronousness)); + +// if (synchronousness == Synchronousness::Sync) { +// status(Status::Linked); +// } + +// m_moduleRecord.set(vm, this, moduleRecord); + +// return JSC::encodedJSUndefined(); +// } + JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); } +template +void NodeVMSourceTextModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* vmModule = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(vmModule, info()); + Base::visitChildren(vmModule, visitor); + + visitor.append(vmModule->m_moduleRecord); +} + +DEFINE_VISIT_CHILDREN(NodeVMSourceTextModule); + const JSC::ClassInfo NodeVMSourceTextModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; } // namespace Bun diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index d775320b09ed..872df104488d 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -31,13 +31,21 @@ class NodeVMSourceTextModule final : public NodeVMModule { static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); + bool createModuleRecord(JSGlobalObject* globalObject); + EncodedJSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); + // EncodedJSValue link(JSGlobalObject* globalObject, JSValue linker); DECLARE_EXPORT_INFO; + DECLARE_VISIT_CHILDREN; private: - NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) + WriteBarrier m_moduleRecord; + SourceCode m_sourceCode; + + NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, SourceCode sourceCode) : Base(vm, structure, WTFMove(identifier)) + , m_sourceCode(WTFMove(sourceCode)) { } diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 84c72d44db56..49e562de5c11 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -35,6 +35,7 @@ class DOMClientIsoSubspaces { std::unique_ptr m_clientSubspaceForNapiExternal; std::unique_ptr m_clientSubspaceForRequireResolveFunction; std::unique_ptr m_clientSubspaceForBundlerPlugin; + std::unique_ptr m_clientSubspaceForNodeVMGlobalObject; std::unique_ptr m_clientSubspaceForNodeVMScript; std::unique_ptr m_clientSubspaceForNodeVMSourceTextModule; std::unique_ptr m_clientSubspaceForNodeVMSyntheticModule; @@ -66,7 +67,6 @@ class DOMClientIsoSubspaces { std::unique_ptr m_clientSubspaceForFunctionTemplate; std::unique_ptr m_clientSubspaceForV8Function; std::unique_ptr m_clientSubspaceForJSNodeHTTPServerSocket; - std::unique_ptr m_clientSubspaceForNodeVMGlobalObject; std::unique_ptr m_clientSubspaceForJSS3Bucket; std::unique_ptr m_clientSubspaceForJSS3File; std::unique_ptr m_clientSubspaceForJSX509Certificate; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 68aad91ad59e..bce6f1bef495 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -35,6 +35,7 @@ class DOMIsoSubspaces { std::unique_ptr m_subspaceForImportMeta; std::unique_ptr m_subspaceForRequireResolveFunction; std::unique_ptr m_subspaceForBundlerPlugin; + std::unique_ptr m_subspaceForNodeVMGlobalObject; std::unique_ptr m_subspaceForNodeVMScript; std::unique_ptr m_subspaceForNodeVMSourceTextModule; std::unique_ptr m_subspaceForNodeVMSyntheticModule; @@ -63,7 +64,6 @@ class DOMIsoSubspaces { std::unique_ptr m_subspaceForJSMIMEParams; std::unique_ptr m_subspaceForV8Function; std::unique_ptr m_subspaceForJSNodeHTTPServerSocket; - std::unique_ptr m_subspaceForNodeVMGlobalObject; std::unique_ptr m_subspaceForJSS3Bucket; std::unique_ptr m_subspaceForJSS3File; std::unique_ptr m_subspaceForJSX509Certificate; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 909211dbe024..d117b6a34cb9 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -260,6 +260,8 @@ class SourceTextModule extends Module { async [kLink](linker) { this.#statusOverride = "linking"; + console.log({ createModuleRecord: this[kNative].createModuleRecord() }); + const moduleRequests = this[kNative].getModuleRequests(); // Iterates the module requests and links with the linker. // Specifiers should be aligned with the moduleRequests array in order. @@ -301,6 +303,8 @@ class SourceTextModule extends Module { } finally { this.#statusOverride = undefined; } + + // return await this[kNative].link(linker); } get dependencySpecifiers() { From 4359ef20789f68eccb50f4c1adca0331c3f4b1f6 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 6 May 2025 20:24:33 -0700 Subject: [PATCH 29/76] More work on vm.Module linking --- src/bun.js/bindings/NodeVMModule.cpp | 9 +- src/bun.js/bindings/NodeVMModule.h | 2 +- .../bindings/NodeVMSourceTextModule.cpp | 148 +++++++++++++----- src/bun.js/bindings/NodeVMSourceTextModule.h | 9 +- src/js/builtins/BunBuiltinNames.h | 3 + src/js/internal/primordials.js | 4 +- src/js/node/vm.ts | 8 +- 7 files changed, 122 insertions(+), 61 deletions(-) diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index b67e1297faed..8e530286bc77 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -48,13 +48,14 @@ bool NodeVMModule::finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deq return true; } -bool NodeVMModule::createModuleRecord(JSC::JSGlobalObject* globalObject) +JSValue NodeVMModule::createModuleRecord(JSC::JSGlobalObject* globalObject) { if (auto* thisObject = jsDynamicCast(this)) { return thisObject->createModuleRecord(globalObject); } - return true; + ASSERT_NOT_REACHED(); + return JSC::jsUndefined(); } NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args) @@ -217,7 +218,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject // } if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return thisObject->link(globalObject, specifiers, moduleNatives); + return JSValue::encode(thisObject->link(globalObject, specifiers, moduleNatives)); // return thisObject->link(globalObject, linker); // } else if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { // return thisObject->link(globalObject, specifiers, moduleNatives); @@ -248,7 +249,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateModuleRecord, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(JSC::jsBoolean(thisObject->createModuleRecord(globalObject))); + return JSValue::encode(thisObject->createModuleRecord(globalObject)); } template diff --git a/src/bun.js/bindings/NodeVMModule.h b/src/bun.js/bindings/NodeVMModule.h index e911fe394f09..665aa0baf4f3 100644 --- a/src/bun.js/bindings/NodeVMModule.h +++ b/src/bun.js/bindings/NodeVMModule.h @@ -57,7 +57,7 @@ class NodeVMModule : public JSC::JSDestructibleObject { bool finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deque& stack, unsigned* dfsIndex); // Purposely not virtual. Dispatches to the correct subclass. - bool createModuleRecord(JSC::JSGlobalObject* globalObject); + JSValue createModuleRecord(JSC::JSGlobalObject* globalObject); protected: WTF::String m_identifier; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index a368077c521c..6dada1bdf3c3 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -3,8 +3,8 @@ #include "ErrorCode.h" #include "JSDOMExceptionHandling.h" #include "JSModuleRecord.h" - -#include +#include "ModuleAnalyzer.h" +#include "Parser.h" namespace Bun { using namespace NodeVM; @@ -71,38 +71,127 @@ void NodeVMSourceTextModule::destroy(JSCell* cell) static_cast(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule(); } -bool NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) +JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) { + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (m_moduleRecord) { - return false; + throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_ALREADY_LINKED, "Module record already present"_s); + return {}; } - VM& vm = globalObject->vm(); + ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); - // ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); + ParserError parserError; - JSModuleRecord* moduleRecord = JSModuleRecord::create(globalObject, vm, globalObject->m_moduleRecordStructure.get(globalObject), Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); - m_moduleRecord.set(vm, this, moduleRecord); + std::unique_ptr node = parseRootNode(vm, m_sourceCode, + ImplementationVisibility::Public, + JSParserBuiltinMode::NotBuiltin, + StrictModeLexicallyScopedFeature, + JSParserScriptMode::Module, + SourceParseMode::ModuleAnalyzeMode, + parserError); - std::println("link synchronousness: {}", int(moduleRecord->link(globalObject, JSC::jsUndefined()))); + if (parserError.isValid()) { + throwException(globalObject, scope, parserError.toErrorObject(globalObject, m_sourceCode)); + return {}; + } + + RETURN_IF_EXCEPTION(scope, {}); + ASSERT(node != nullptr); + + JSModuleRecord* moduleRecord = nullptr; + + if (auto result = analyzer.analyze(*node)) { + moduleRecord = *result; + } else { + auto [type, message] = result.error(); + throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_LINK_FAILURE, message); + return {}; + } + + m_moduleRecord.set(vm, this, moduleRecord); + m_moduleRequests.clear(); const auto& requests = moduleRecord->requestedModules(); - std::println("requests: {}", requests.size()); + if (requests.isEmpty()) { + return JSC::constructEmptyArray(globalObject, nullptr, 0); + } - for (const auto& request : requests) { - std::println("request: {}", request.m_specifier->utf8().data()); + JSArray* requestsArray = JSC::constructEmptyArray(globalObject, nullptr, requests.size()); + + const auto& builtinNames = WebCore::clientData(vm)->builtinNames(); + const JSC::Identifier& specifierIdentifier = builtinNames.specifierPublicName(); + const JSC::Identifier& attributesIdentifier = builtinNames.attributesPublicName(); + const JSC::Identifier& hostDefinedImportTypeIdentifier = builtinNames.hostDefinedImportTypePublicName(); + + for (unsigned i = 0; i < requests.size(); ++i) { + const auto& request = requests[i]; + + JSString* specifierValue = JSC::jsString(vm, WTF::String(*request.m_specifier)); + + JSObject* requestObject = constructEmptyObject(globalObject, globalObject->objectPrototype(), 2); + requestObject->putDirect(vm, specifierIdentifier, specifierValue); + + WTF::String attributesTypeString = "unknown"_str; + + if (request.m_attributes) { + JSValue attributesType {}; + switch (request.m_attributes->type()) { + using AttributeType = decltype(request.m_attributes->type()); + using enum AttributeType; + case None: + attributesTypeString = "none"_str; + attributesType = JSC::jsString(vm, attributesTypeString); + break; + case JavaScript: + attributesTypeString = "javascript"_str; + attributesType = JSC::jsString(vm, attributesTypeString); + break; + case WebAssembly: + attributesTypeString = "webassembly"_str; + attributesType = JSC::jsString(vm, attributesTypeString); + break; + case JSON: + attributesTypeString = "json"_str; + attributesType = JSC::jsString(vm, attributesTypeString); + break; + default: + attributesType = JSC::jsNumber(static_cast(request.m_attributes->type())); + break; + } + + WTF::HashMap attributeMap { + { "type"_s, attributesTypeString }, + }; + + JSObject* attributesObject = constructEmptyObject(globalObject, globalObject->objectPrototype(), 1); + attributesObject->putDirect(vm, JSC::Identifier::fromString(vm, "type"_s), attributesType); + if (const String& hostDefinedImportType = request.m_attributes->hostDefinedImportType(); !hostDefinedImportType.isEmpty()) { + attributesObject->putDirect(vm, hostDefinedImportTypeIdentifier, JSC::jsString(vm, hostDefinedImportType)); + attributeMap.set("hostDefinedImportType"_s, hostDefinedImportType); + } + requestObject->putDirect(vm, attributesIdentifier, attributesObject); + addModuleRequest({ WTF::String(*request.m_specifier), WTFMove(attributeMap) }); + } else { + addModuleRequest({ WTF::String(*request.m_specifier), {} }); + requestObject->putDirect(vm, attributesIdentifier, JSC::jsNull()); + } + + requestsArray->putDirectIndex(globalObject, i, requestObject); } - return true; + return requestsArray; } -EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives) +JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives) { const unsigned length = specifiers->getArrayLength(); ASSERT(length == moduleNatives->getArrayLength()); if (length == 0) { - return JSC::encodedJSUndefined(); + return JSC::jsUndefined(); } VM& vm = globalObject->vm(); @@ -121,36 +210,9 @@ EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArra m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); } - return JSC::encodedJSUndefined(); + return JSC::jsUndefined(); } -// EncodedJSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSValue linker) -// { -// VM& vm = globalObject->vm(); -// auto scope = DECLARE_THROW_SCOPE(vm); - -// if (status() != Status::Unlinked) { -// throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_ALREADY_LINKED, "Module is already linked"_s); -// return {}; -// } - -// status(Status::Linking); - -// JSModuleRecord* moduleRecord = JSModuleRecord::create(globalObject, vm, globalObject->m_moduleRecordStructure.get(globalObject), Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); - -// Synchronousness synchronousness = moduleRecord->link(globalObject, linker); - -// std::println("synchronousness: {}", int(synchronousness)); - -// if (synchronousness == Synchronousness::Sync) { -// status(Status::Linked); -// } - -// m_moduleRecord.set(vm, this, moduleRecord); - -// return JSC::encodedJSUndefined(); -// } - JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index 872df104488d..4dce4521cd37 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -23,18 +23,15 @@ class NodeVMSourceTextModule final : public NodeVMModule { [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSourceTextModule = std::forward(space); }); } + static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); static void destroy(JSC::JSCell* cell); static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } - static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); - - bool createModuleRecord(JSGlobalObject* globalObject); - - EncodedJSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); - // EncodedJSValue link(JSGlobalObject* globalObject, JSValue linker); + JSValue createModuleRecord(JSGlobalObject* globalObject); + JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h index 39f912a38bf6..9a2602ea283c 100644 --- a/src/js/builtins/BunBuiltinNames.h +++ b/src/js/builtins/BunBuiltinNames.h @@ -49,6 +49,7 @@ using namespace JSC; macro(assignToStream) \ macro(associatedReadableByteStreamController) \ macro(atimeMs) \ + macro(attributes) \ macro(autoAllocateChunkSize) \ macro(backpressure) \ macro(backpressureChangePromise) \ @@ -131,6 +132,7 @@ using namespace JSC; macro(headers) \ macro(highWaterMark) \ macro(host) \ + macro(hostDefinedImportType) \ macro(hostname) \ macro(href) \ macro(httpOnly) \ @@ -233,6 +235,7 @@ using namespace JSC; macro(signal) \ macro(sink) \ macro(size) \ + macro(specifier) \ macro(start) \ macro(startAlgorithm) \ macro(startConsumingStream) \ diff --git a/src/js/internal/primordials.js b/src/js/internal/primordials.js index 9c9a79148d14..8eb8876333c1 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -94,7 +94,7 @@ const arrayToSafePromiseIterable = (promises, mapFn) => ), ); const PromiseAll = Promise.all; -const PromiseResolve = Promise.resolve; +const PromiseResolve = Promise.resolve.bind(Promise); const SafePromiseAll = (promises, mapFn) => PromiseAll(arrayToSafePromiseIterable(promises, mapFn)); const SafePromiseAllReturnArrayLike = (promises, mapFn) => new Promise((resolve, reject) => { @@ -107,7 +107,7 @@ const SafePromiseAllReturnArrayLike = (promises, mapFn) => let pendingPromises = length; for (let i = 0; i < length; i++) { const promise = mapFn != null ? mapFn(promises[i], i) : promises[i]; - PromisePrototypeThen( + PromisePrototypeThen.$call( PromiseResolve(promise), result => { returnVal[i] = result; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index d117b6a34cb9..54f5f5f32b68 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -19,7 +19,7 @@ const ObjectFreeze = Object.freeze; const ObjectDefineProperty = Object.defineProperty; const ArrayPrototypeMap = Array.prototype.map; const PromisePrototypeThen = Promise.prototype.then; -const PromiseResolve = Promise.resolve; +const PromiseResolve = Promise.resolve.bind(Promise); const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; const ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; const ObjectSetPrototypeOf = Object.setPrototypeOf; @@ -260,9 +260,8 @@ class SourceTextModule extends Module { async [kLink](linker) { this.#statusOverride = "linking"; - console.log({ createModuleRecord: this[kNative].createModuleRecord() }); + const moduleRequests = this[kNative].createModuleRecord(); - const moduleRequests = this[kNative].getModuleRequests(); // Iterates the module requests and links with the linker. // Specifiers should be aligned with the moduleRequests array in order. const specifiers = Array(moduleRequests.length); @@ -275,6 +274,7 @@ class SourceTextModule extends Module { attributes, assert: attributes, }); + const modulePromise = PromisePrototypeThen.$call(PromiseResolve(linkerResult), async mod => { if (!isModule(mod)) { throw $ERR_VM_MODULE_NOT_MODULE(); @@ -303,8 +303,6 @@ class SourceTextModule extends Module { } finally { this.#statusOverride = undefined; } - - // return await this[kNative].link(linker); } get dependencySpecifiers() { From d3b6ec62251f1459f35a5448d6349b5a04f0725d Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 7 May 2025 13:30:41 -0700 Subject: [PATCH 30/76] Merge kai/vm-compat --- cmake/targets/BuildBun.cmake | 5 +- src/bun.js/bindings/BunProcess.cpp | 10 +- src/bun.js/bindings/JSNextTickQueue.cpp | 8 +- src/bun.js/bindings/NodeVM.cpp | 129 ++++++++++++++---- src/bun.js/bindings/NodeVM.h | 13 +- src/bun.js/bindings/NodeVMModule.cpp | 27 +++- src/bun.js/bindings/NodeVMScript.cpp | 44 +++++- .../bindings/NodeVMSourceTextModule.cpp | 29 ++++ src/bun.js/bindings/NodeVMSourceTextModule.h | 1 + src/js/node/vm.ts | 5 +- src/vm/Semaphore.cpp | 51 +++++++ src/vm/Semaphore.h | 32 +++++ src/vm/SigintWatcher.cpp | 99 ++++++++++++++ src/vm/SigintWatcher.h | 41 ++++++ 14 files changed, 449 insertions(+), 45 deletions(-) create mode 100644 src/vm/Semaphore.cpp create mode 100644 src/vm/Semaphore.h create mode 100644 src/vm/SigintWatcher.cpp create mode 100644 src/vm/SigintWatcher.h diff --git a/cmake/targets/BuildBun.cmake b/cmake/targets/BuildBun.cmake index 80eeef57721d..e31e2927f6b9 100644 --- a/cmake/targets/BuildBun.cmake +++ b/cmake/targets/BuildBun.cmake @@ -638,6 +638,7 @@ file(GLOB BUN_CXX_SOURCES ${CONFIGURE_DEPENDS} ${CWD}/src/bun.js/bindings/v8/shim/*.cpp ${CWD}/src/bake/*.cpp ${CWD}/src/deps/*.cpp + ${CWD}/src/vm/*.cpp ${BUN_USOCKETS_SOURCE}/src/crypto/*.cpp ) @@ -798,7 +799,7 @@ target_include_directories(${bun} PRIVATE ${NODEJS_HEADERS_PATH}/include ) -if(NOT WIN32) +if(NOT WIN32) target_include_directories(${bun} PRIVATE ${CWD}/src/bun.js/bindings/libuv) endif() @@ -931,7 +932,7 @@ if(NOT WIN32) -Wno-nullability-completeness -Werror ) - + if(ENABLE_ASAN) target_compile_options(${bun} PUBLIC -fsanitize=address diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index c9afb6f6f156..9fe4f63d92f4 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -798,8 +798,8 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionChdir, (JSC::JSGlobalObject * globalObj RELEASE_AND_RETURN(scope, JSC::JSValue::encode(result)); } -static HashMap* signalNameToNumberMap = nullptr; -static HashMap* signalNumberToNameMap = nullptr; +static std::optional> signalNameToNumberMap; +static std::optional> signalNumberToNameMap; // On windows, signals need to have a handle to the uv_signal_t. When sigaction is used, this is kept track globally for you. struct SignalHandleValue { @@ -854,7 +854,7 @@ static void loadSignalNumberMap() static std::once_flag signalNameToNumberMapOnceFlag; std::call_once(signalNameToNumberMapOnceFlag, [] { auto signalNames = getSignalNames(); - signalNameToNumberMap = new HashMap(); + signalNameToNumberMap.emplace(); signalNameToNumberMap->reserveInitialCapacity(31); #if OS(WINDOWS) // libuv supported signals @@ -1066,7 +1066,7 @@ static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& e { if (Bun__isMainThreadVM()) { // IPC handlers - if (eventName.string() == "message"_s || eventName.string() == "disconnect"_s) { + if (eventName == "message" || eventName == "disconnect") { auto* global = jsCast(eventEmitter.scriptExecutionContext()->jsGlobalObject()); auto& vm = JSC::getVM(global); auto messageListenerCount = eventEmitter.listenerCount(vm.propertyNames->message); @@ -1095,7 +1095,7 @@ static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& e static std::once_flag signalNumberToNameMapOnceFlag; std::call_once(signalNumberToNameMapOnceFlag, [] { auto signalNames = getSignalNames(); - signalNumberToNameMap = new HashMap(); + signalNumberToNameMap.emplace(); signalNumberToNameMap->reserveInitialCapacity(31); signalNumberToNameMap->add(SIGHUP, signalNames[0]); signalNumberToNameMap->add(SIGINT, signalNames[1]); diff --git a/src/bun.js/bindings/JSNextTickQueue.cpp b/src/bun.js/bindings/JSNextTickQueue.cpp index 642ff260453b..93327c18451d 100644 --- a/src/bun.js/bindings/JSNextTickQueue.cpp +++ b/src/bun.js/bindings/JSNextTickQueue.cpp @@ -76,20 +76,26 @@ bool JSNextTickQueue::isEmpty() void JSNextTickQueue::drain(JSC::VM& vm, JSC::JSGlobalObject* globalObject) { + auto scope = DECLARE_THROW_SCOPE(vm); bool mustResetContext = false; if (isEmpty()) { + RETURN_IF_EXCEPTION(scope, ); vm.drainMicrotasks(); + RETURN_IF_EXCEPTION(scope, ); mustResetContext = true; } if (!isEmpty()) { + RETURN_IF_EXCEPTION(scope, ); if (mustResetContext) { globalObject->m_asyncContextData.get()->putInternalField(vm, 0, jsUndefined()); + RETURN_IF_EXCEPTION(scope, ); } auto* drainFn = internalField(2).get().getObject(); - auto throwScope = DECLARE_THROW_SCOPE(vm); + RETURN_IF_EXCEPTION(scope, ); MarkedArgumentBuffer drainArgs; JSC::call(globalObject, drainFn, drainArgs, "Failed to drain next tick queue"_s); + RETURN_IF_EXCEPTION(scope, ); } } diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index de58f117a5c9..cf357c79d6dc 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -49,6 +49,8 @@ #include "JavaScriptCore/BytecodeCacheError.h" #include "wtf/FileHandle.h" +#include "../vm/SigintWatcher.h" + namespace Bun { using namespace WebCore; @@ -186,8 +188,8 @@ String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& a auto body = args.at(0).toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); - program = tryMakeString("(function () {"_s, body, "})"_s); - *outOffset = "(function () {"_s.length(); + program = tryMakeString("(function () {\n"_s, body, "\n})"_s); + *outOffset = "(function () {\n"_s.length(); if (UNLIKELY(!program)) { throwOutOfMemoryError(globalObject, scope); @@ -211,8 +213,8 @@ String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& a auto body = args.at(parameterCount).toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); - program = tryMakeString("(function ("_s, paramString.toString(), ") {"_s, body, "})"_s); - *outOffset = "(function ("_s.length() + paramString.length() + ") {"_s.length(); + program = tryMakeString("(function ("_s, paramString.toString(), ") {\n"_s, body, "\n})"_s); + *outOffset = "(function ("_s.length() + paramString.length() + ") {\n"_s.length(); if (UNLIKELY(!program)) { throwOutOfMemoryError(globalObject, scope); @@ -296,6 +298,70 @@ bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey) +{ + outOptions = {}; + + // If options is provided, validate name and origin properties + if (!optionsArg.isObject()) { + return std::nullopt; + } + + JSObject* options = asObject(optionsArg); + + // Check name property + if (JSValue nameValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "name"_s))) { + RETURN_IF_EXCEPTION(scope, {}); + if (!nameValue.isUndefined() && !nameValue.isString()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.name"_s, "string"_s, nameValue); + } + } + + // Check origin property + if (JSValue originValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "origin"_s))) { + RETURN_IF_EXCEPTION(scope, {}); + if (!originValue.isUndefined() && !originValue.isString()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.origin"_s, "string"_s, originValue); + } + } + + if (JSValue codeGenerationValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, codeGenerationKey))) { + RETURN_IF_EXCEPTION(scope, {}); + + if (codeGenerationValue.isUndefined()) { + return std::nullopt; + } + + if (!codeGenerationValue.isObject()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, WTF::makeString("options."_s, codeGenerationKey), "object"_s, codeGenerationValue); + } + + JSObject* codeGenerationObject = asObject(codeGenerationValue); + + if (JSValue allowStringsValue = codeGenerationObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "strings"_s))) { + RETURN_IF_EXCEPTION(scope, {}); + if (!allowStringsValue.isBoolean()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, WTF::makeString("options."_s, codeGenerationKey, ".strings"_s), "boolean"_s, allowStringsValue); + } + + outOptions.allowStrings = allowStringsValue.toBoolean(globalObject); + } + + if (JSValue allowWasmValue = codeGenerationObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "wasm"_s))) { + RETURN_IF_EXCEPTION(scope, {}); + if (!allowWasmValue.isBoolean()) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, WTF::makeString("options."_s, codeGenerationKey, ".wasm"_s), "boolean"_s, allowWasmValue); + } + + outOptions.allowWasm = allowWasmValue.toBoolean(globalObject); + } + } + + return std::nullopt; +} + } // namespace NodeVM using namespace NodeVM; @@ -318,10 +384,10 @@ template JSC::GCClient::IsoSubspace* NodeVMG [](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForNodeVMGlobalObject; }); } -NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) +NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options) { auto* cell = new (NotNull, JSC::allocateCell(vm)) NodeVMGlobalObject(vm, structure); - cell->finishCreation(vm); + cell->finishCreation(vm, options); return cell; } @@ -331,9 +397,12 @@ 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&) +void NodeVMGlobalObject::finishCreation(JSC::VM& vm, NodeVMContextOptions options) { - Base::finishCreation(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); + vm.ensureTerminationException(); } void NodeVMGlobalObject::destroy(JSCell* cell) @@ -343,6 +412,7 @@ void NodeVMGlobalObject::destroy(JSCell* cell) NodeVMGlobalObject::~NodeVMGlobalObject() { + SigintWatcher::get().unregisterGlobalObject(this); } void NodeVMGlobalObject::setContextifiedObject(JSC::JSObject* contextifiedObject) @@ -355,6 +425,11 @@ void NodeVMGlobalObject::clearContextifiedObject() m_sandbox.clear(); } +void NodeVMGlobalObject::sigintReceived() +{ + vm().notifyNeedTermination(); +} + bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) { // if (!propertyName.isSymbol()) @@ -566,9 +641,18 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject JSObject* sandbox = asObject(contextArg); + JSValue contextOptionsArg = callFrame->argument(2); + + NodeVMContextOptions contextOptions {}; + + if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, contextOptionsArg, contextOptions, "contextCodeGeneration")) { + return *encodedException; + } + // Create context and run code auto* context = NodeVMGlobalObject::create(vm, - defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure()); + defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(), + contextOptions); context->setContextifiedObject(sandbox); @@ -748,7 +832,8 @@ Structure* createNodeVMGlobalObjectStructure(JSC::VM& vm) NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox) { auto* targetContext = NodeVMGlobalObject::create(vm, - defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure()); + defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(), + NodeVMContextOptions {}); // Set sandbox as contextified object targetContext->setContextifiedObject(sandbox); @@ -781,31 +866,17 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, return ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); } - // If options is provided, validate name and origin properties - if (optionsArg.isObject()) { - JSObject* options = asObject(optionsArg); - - // Check name property - if (JSValue nameValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "name"_s))) { - RETURN_IF_EXCEPTION(scope, {}); - if (!nameValue.isUndefined() && !nameValue.isString()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.name"_s, "string"_s, nameValue); - } - } + NodeVMContextOptions contextOptions {}; - // Check origin property - if (JSValue originValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "origin"_s))) { - RETURN_IF_EXCEPTION(scope, {}); - if (!originValue.isUndefined() && !originValue.isString()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.origin"_s, "string"_s, originValue); - } - } + if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, optionsArg, contextOptions, "codeGeneration")) { + return *encodedException; } JSObject* sandbox = asObject(contextArg); auto* targetContext = NodeVMGlobalObject::create(vm, - defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure()); + defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(), + contextOptions); // Set sandbox as contextified object targetContext->setContextifiedObject(sandbox); diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 0604838baf0a..d4eec2b79fdf 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -15,6 +15,7 @@ namespace Bun { class NodeVMGlobalObject; +class NodeVMContextOptions; namespace NodeVM { @@ -24,6 +25,7 @@ String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& a 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); /// For vm.compileFunction we need to return an anonymous function expression /// /// This code is adapted/inspired from JSC::constructFunction, which is used for function declarations. @@ -40,17 +42,18 @@ class NodeVMGlobalObject final : public Bun::GlobalScope { static constexpr JSC::DestructionMode needsDestruction = NeedsDestruction; template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); - static NodeVMGlobalObject* create(JSC::VM& vm, JSC::Structure* structure); + 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&); + 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&); @@ -107,4 +110,10 @@ class CompileFunctionOptions : public BaseVMOptions { bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg); }; +class NodeVMContextOptions final { +public: + bool allowStrings = true; + bool allowWasm = true; +}; + } // namespace Bun diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index 8e530286bc77..02712ec95880 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -191,8 +191,31 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue timeoutValue = callFrame->argument(0); + if (!timeoutValue.isUInt32() || timeoutValue.asUInt32() == 0) { + return throwArgumentTypeError(*globalObject, scope, 0, "timeout"_s, "Module"_s, "Module"_s, "positive integer"_s); + } + + JSValue breakOnSigintValue = callFrame->argument(1); + if (!breakOnSigintValue.isBoolean()) { + return throwArgumentTypeError(*globalObject, scope, 1, "breakOnSigint"_s, "Module"_s, "Module"_s, "boolean"_s); + } + + uint32_t timeout = timeoutValue.asUInt32(); + bool breakOnSigint = breakOnSigintValue.asBoolean(); + + if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { + return JSValue::encode(thisObject->evaluate(globalObject, timeout, breakOnSigint)); + // 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 {}; + } } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 9e4fa894198d..c22e8b5b1c20 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -7,6 +7,8 @@ #include "JavaScriptCore/SourceCodeKey.h" #include "NodeVMScript.h" +#include "../vm/SigintWatcher.h" + namespace Bun { using namespace NodeVM; @@ -252,6 +254,34 @@ void NodeVMScript::destroy(JSCell* cell) static_cast(cell)->NodeVMScript::~NodeVMScript(); } +extern "C" void Bun__ensureSignalHandler(); + +static void ensureSigintHandler() +{ +#if !OS(WINDOWS) + Bun__ensureSignalHandler(); + + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + + // Set the handler in the action struct + action.sa_handler = [](int signalNumber) { + SigintWatcher::get().signalReceived(); + }; + + // Clear the sa_mask + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGINT); + action.sa_flags = SA_RESTART; + + sigaction(SIGINT, &action, nullptr); + + SigintWatcher::get().install(); +#else + static_assert(false, "TODO(@heimskr): implement sigint handler on Windows"); +#endif +} + static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false) { @@ -270,6 +300,11 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM // Set the contextified object before evaluating globalObject->setContextifiedObject(contextifiedObject); + if (options.breakOnSigint) { + ensureSigintHandler(); + SigintWatcher::get().registerGlobalObject(globalObject); + } + NakedPtr exception; JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); @@ -310,6 +345,10 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, options = {}; } + if (options.breakOnSigint) { + ensureSigintHandler(); + } + NakedPtr exception; JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); @@ -469,8 +508,9 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, auto* zigGlobal = defaultGlobalObject(globalObject); JSObject* context = asObject(contextObjectValue); - auto* targetContext = NodeVMGlobalObject::create( - vm, zigGlobal->NodeVMGlobalObjectStructure()); + auto* targetContext = NodeVMGlobalObject::create(vm, + zigGlobal->NodeVMGlobalObjectStructure(), + {}); return runInContext(targetContext, script, context, callFrame->argument(1)); } diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 6dada1bdf3c3..1b51527c0d7e 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -1,5 +1,7 @@ #include "NodeVMSourceTextModule.h" +#include + #include "ErrorCode.h" #include "JSDOMExceptionHandling.h" #include "JSModuleRecord.h" @@ -114,6 +116,11 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) m_moduleRecord.set(vm, this, moduleRecord); m_moduleRequests.clear(); + std::println("import entries count: {}", moduleRecord->importEntries().size()); + for (const auto& [key, value] : moduleRecord->importEntries()) { + std::println("import entry({}): name={{{}}}, type={{{}}}", key->utf8().data(), value.importName.utf8().data(), int(value.type)); + } + const auto& requests = moduleRecord->requestedModules(); if (requests.isEmpty()) { @@ -190,10 +197,14 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec { const unsigned length = specifiers->getArrayLength(); ASSERT(length == moduleNatives->getArrayLength()); + if (length == 0) { + status(Status::Linked); return JSC::jsUndefined(); } + // JSModuleRecord* record = m_moduleRecord.get(); + VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -208,6 +219,24 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec JSObject* moduleNative = moduleNativeValue.getObject(); m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); + + // AbstractModuleRecord::ImportEntry entry; + + // record->addImportEntry(entry); + } + + status(Status::Linked); + return JSC::jsUndefined(); +} + +JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_status != Status::Linked && m_status != Status::Evaluated && m_status != Status::Errored) { + throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_STATUS, "Module must be linked, evaluated or errored before evaluating"_s); + return {}; } return JSC::jsUndefined(); diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index 4dce4521cd37..748332262fac 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -32,6 +32,7 @@ class NodeVMSourceTextModule final : public NodeVMModule { JSValue createModuleRecord(JSGlobalObject* globalObject); JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); + JSValue evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint); DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 54f5f5f32b68..901b9c3886bc 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -51,7 +51,7 @@ const { } = vm; function runInContext(code, context, options) { - return new Script(code, options).runInContext(context); + return new Script(code, options).runInContext(context, options); } function createScript(code, options) { @@ -182,7 +182,7 @@ class Module { } const { breakOnSigint = false } = options; validateBoolean(breakOnSigint, "options.breakOnSigint"); - const status = this[kNative].getStatus(); + const status = this[kNative].getStatusCode(); if (status !== kLinked && status !== kEvaluated && status !== kErrored) { throw $ERR_VM_MODULE_STATUS("must be one of linked, evaluated, or errored"); } @@ -298,6 +298,7 @@ class SourceTextModule extends Module { const moduleNatives = await SafePromiseAllReturnArrayLike(modulePromises); this[kNative].link(specifiers, moduleNatives); } catch (e) { + console.error("linking error:", e); this.#error = e; throw e; } finally { diff --git a/src/vm/Semaphore.cpp b/src/vm/Semaphore.cpp new file mode 100644 index 000000000000..f1eec7ef05fc --- /dev/null +++ b/src/vm/Semaphore.cpp @@ -0,0 +1,51 @@ +#include "Semaphore.h" + +namespace Bun { + +Semaphore::Semaphore(unsigned int value) +{ +#if OS(WINDOWS) + uv_sem_init(&m_semaphore, value); +#elif OS(DARWIN) + semaphore_create(mach_task_self(), &m_semaphore, SYNC_POLICY_FIFO, value); +#else + sem_init(&m_semaphore, 0, value); +#endif +} + +Semaphore::~Semaphore() +{ +#if OS(WINDOWS) + uv_sem_destroy(&m_semaphore); +#elif OS(DARWIN) + semaphore_destroy(mach_task_self(), m_semaphore); +#else + sem_destroy(&m_semaphore); +#endif +} + +bool Semaphore::signal() +{ +#if OS(WINDOWS) + uv_sem_post(&m_semaphore); + return true; +#elif OS(DARWIN) + return semaphore_signal(m_semaphore) == KERN_SUCCESS; +#else + return sem_post(&m_semaphore) == 0; +#endif +} + +bool Semaphore::wait() +{ +#if OS(WINDOWS) + uv_sem_wait(&m_semaphore); + return true; +#elif OS(DARWIN) + return semaphore_wait(m_semaphore) == KERN_SUCCESS; +#else + return sem_wait(&m_semaphore) == 0; +#endif +} + +} // namespace Bun diff --git a/src/vm/Semaphore.h b/src/vm/Semaphore.h new file mode 100644 index 000000000000..4f85976692e6 --- /dev/null +++ b/src/vm/Semaphore.h @@ -0,0 +1,32 @@ +#pragma once + +#if OS(WINDOWS) +#include +#elif OS(DARWIN) +#include +#include +#else +#include +#endif + +namespace Bun { + +class Semaphore { +public: + Semaphore(unsigned int value); + ~Semaphore(); + + bool signal(); + bool wait(); + +private: +#if OS(WINDOWS) + uv_sem_t m_semaphore; +#elif OS(DARWIN) + semaphore_t m_semaphore; +#else + sem_t m_semaphore; +#endif +}; + +} // namespace Bun diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp new file mode 100644 index 000000000000..2b3bd97e506e --- /dev/null +++ b/src/vm/SigintWatcher.cpp @@ -0,0 +1,99 @@ +#include "NodeVM.h" +#include "SigintWatcher.h" + +extern "C" void Bun__onPosixSignal(int signalNumber); + +namespace Bun { + +SigintWatcher SigintWatcher::s_instance; + +SigintWatcher::SigintWatcher() + : m_semaphore(1) +{ + m_globalObjects.reserve(16); +} + +SigintWatcher::~SigintWatcher() +{ + uninstall(); +} + +void SigintWatcher::install() +{ + if (m_installed.exchange(true)) { + return; + } + + m_thread = std::thread([this] { + while (m_installed.load()) { + bool success = m_semaphore.wait(); + ASSERT(success); + if (m_waiting.test_and_set()) { + m_waiting.clear(); + if (!signalAll()) { + Bun__onPosixSignal(SIGINT); + } + } else { + m_waiting.clear(); + } + } + }); +} + +void SigintWatcher::uninstall() +{ + if (m_installed.exchange(false)) { + m_thread.join(); + } +} + +void SigintWatcher::signalReceived() +{ + if (!m_waiting.test_and_set()) { + bool success = m_semaphore.signal(); + ASSERT(success); + } +} + +void SigintWatcher::registerGlobalObject(NodeVMGlobalObject* globalObject) +{ + std::unique_lock lock(m_globalObjectsMutex); + + m_globalObjects.push_back(globalObject); +} + +void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) +{ + std::unique_lock lock(m_globalObjectsMutex); + + auto iter = std::find(m_globalObjects.begin(), m_globalObjects.end(), globalObject); + + if (iter == m_globalObjects.end()) { + return; + } + + std::swap(*iter, m_globalObjects.back()); + m_globalObjects.pop_back(); +} + +SigintWatcher& SigintWatcher::get() +{ + return s_instance; +} + +bool SigintWatcher::signalAll() +{ + std::unique_lock lock(m_globalObjectsMutex); + + if (m_globalObjects.empty()) { + return false; + } + + for (NodeVMGlobalObject* globalObject : m_globalObjects) { + globalObject->sigintReceived(); + } + + return true; +} + +} // namespace Bun diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h new file mode 100644 index 000000000000..7f934933d794 --- /dev/null +++ b/src/vm/SigintWatcher.h @@ -0,0 +1,41 @@ +#pragma once + +#include "root.h" +#include "Semaphore.h" + +#include +#include +#include +#include + +namespace Bun { + +class NodeVMGlobalObject; + +class SigintWatcher { +public: + SigintWatcher(); + ~SigintWatcher(); + + void install(); + void uninstall(); + void signalReceived(); + void registerGlobalObject(NodeVMGlobalObject* globalObject); + void unregisterGlobalObject(NodeVMGlobalObject* globalObject); + + static SigintWatcher& get(); + +private: + std::thread m_thread; + std::atomic_bool m_installed = false; + std::atomic_flag m_waiting = false; + Semaphore m_semaphore; + std::mutex m_globalObjectsMutex; + std::vector m_globalObjects; + + bool signalAll(); + + static SigintWatcher s_instance; +}; + +} // namespace Bun From 550eea37e841b7a5f89119769bba45f9908bb77b Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 7 May 2025 14:18:49 -0700 Subject: [PATCH 31/76] Progress on SourceTextModule evaluation --- src/bun.js/bindings/NodeVM.cpp | 47 +++++++++++++ src/bun.js/bindings/NodeVM.h | 2 + src/bun.js/bindings/NodeVMModule.cpp | 19 +++--- src/bun.js/bindings/NodeVMModule.h | 3 +- src/bun.js/bindings/NodeVMScript.cpp | 67 ++----------------- .../bindings/NodeVMSourceTextModule.cpp | 48 ++++++++----- src/bun.js/bindings/NodeVMSourceTextModule.h | 4 +- src/vm/SigintWatcher.cpp | 36 ++++++++++ src/vm/SigintWatcher.h | 38 +++++++++++ 9 files changed, 177 insertions(+), 87 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index cf357c79d6dc..dfbef183e04a 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -362,6 +362,53 @@ std::optional getNodeVMContextOptions(JSGlobalObject* globa return std::nullopt; } +NodeVMGlobalObject* getGlobalObjectFromContext(JSGlobalObject* globalObject, JSValue contextValue, bool canThrow) +{ + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + + if (contextValue.isUndefinedOrNull()) { + if (canThrow) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextValue); + } + return nullptr; + } + + if (!contextValue.isObject()) { + if (canThrow) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextValue); + } + return nullptr; + } + + JSObject* context = asObject(contextValue); + auto* zigGlobalObject = defaultGlobalObject(globalObject); + JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); + if (scopeValue.isUndefined()) { + if (canThrow) { + INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); + } + return nullptr; + } + + NodeVMGlobalObject* nodeVmGlobalObject = jsDynamicCast(scopeValue); + if (!nodeVmGlobalObject) { + if (canThrow) { + INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); + } + return nullptr; + } + + return nodeVmGlobalObject; +} + +/// For some reason Node has this error message with a grammatical error and we have to match it so the tests pass: +/// `The "" argument must be an vm.Context` +JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, makeString("The \""_s, name, "\" argument must be an vm.Context"_s))); + return {}; +} + } // namespace NodeVM using namespace NodeVM; diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index d4eec2b79fdf..869ba031b65c 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -26,6 +26,8 @@ JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::So 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); +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/inspired from JSC::constructFunction, which is used for function declarations. diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index 02712ec95880..6c016c1492dd 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -31,10 +31,13 @@ JSArray* NodeVMModuleRequest::toJS(JSGlobalObject* globalObject) const return array; } -NodeVMModule::NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) +NodeVMModule::NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context) : Base(vm, structure) , m_identifier(WTFMove(identifier)) { + if (context.isObject()) { + m_context.set(vm, this, asObject(context)); + } } bool NodeVMModule::finishInstantiate(JSC::JSGlobalObject* globalObject, WTF::Deque& stack, unsigned* dfsIndex) @@ -195,18 +198,17 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalOb auto scope = DECLARE_THROW_SCOPE(vm); JSValue timeoutValue = callFrame->argument(0); - if (!timeoutValue.isUInt32() || timeoutValue.asUInt32() == 0) { - return throwArgumentTypeError(*globalObject, scope, 0, "timeout"_s, "Module"_s, "Module"_s, "positive integer"_s); + uint32_t timeout = 0; + if (timeoutValue.isUInt32()) { + timeout = timeoutValue.asUInt32(); } JSValue breakOnSigintValue = callFrame->argument(1); - if (!breakOnSigintValue.isBoolean()) { - return throwArgumentTypeError(*globalObject, scope, 1, "breakOnSigint"_s, "Module"_s, "Module"_s, "boolean"_s); + bool breakOnSigint = false; + if (breakOnSigintValue.isBoolean()) { + breakOnSigint = breakOnSigintValue.asBoolean(); } - uint32_t timeout = timeoutValue.asUInt32(); - bool breakOnSigint = breakOnSigintValue.asBoolean(); - if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { return JSValue::encode(thisObject->evaluate(globalObject, timeout, breakOnSigint)); // return thisObject->link(globalObject, linker); @@ -283,6 +285,7 @@ void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) Base::visitChildren(vmModule, visitor); visitor.append(vmModule->m_namespace); + visitor.append(vmModule->m_context); auto moduleNatives = vmModule->m_resolveCache.values(); visitor.append(moduleNatives.begin(), moduleNatives.end()); diff --git a/src/bun.js/bindings/NodeVMModule.h b/src/bun.js/bindings/NodeVMModule.h index 665aa0baf4f3..454b684683b9 100644 --- a/src/bun.js/bindings/NodeVMModule.h +++ b/src/bun.js/bindings/NodeVMModule.h @@ -63,10 +63,11 @@ class NodeVMModule : public JSC::JSDestructibleObject { WTF::String m_identifier; Status m_status = Status::Unlinked; mutable WriteBarrier m_namespace; + mutable WriteBarrier m_context; WTF::Vector m_moduleRequests; mutable WTF::HashMap> m_resolveCache; - NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier); + NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context); DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index c22e8b5b1c20..a866ed0b55a6 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -12,19 +12,6 @@ namespace Bun { using namespace NodeVM; -/// For some reason Node has this error message with a grammar error and we have to match it so the tests pass: -/// `The "" argument must be an vm.Context` -static JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value) -{ - WTF::StringBuilder builder; - builder.append("The \""_s); - builder.append(name); - builder.append("\" argument must be an vm.Context"_s); - - throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, builder.toString())); - return {}; -} - bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg); @@ -254,34 +241,6 @@ void NodeVMScript::destroy(JSCell* cell) static_cast(cell)->NodeVMScript::~NodeVMScript(); } -extern "C" void Bun__ensureSignalHandler(); - -static void ensureSigintHandler() -{ -#if !OS(WINDOWS) - Bun__ensureSignalHandler(); - - struct sigaction action; - memset(&action, 0, sizeof(struct sigaction)); - - // Set the handler in the action struct - action.sa_handler = [](int signalNumber) { - SigintWatcher::get().signalReceived(); - }; - - // Clear the sa_mask - sigemptyset(&action.sa_mask); - sigaddset(&action.sa_mask, SIGINT); - action.sa_flags = SA_RESTART; - - sigaction(SIGINT, &action, nullptr); - - SigintWatcher::get().install(); -#else - static_assert(false, "TODO(@heimskr): implement sigint handler on Windows"); -#endif -} - static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false) { @@ -301,7 +260,7 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM globalObject->setContextifiedObject(contextifiedObject); if (options.breakOnSigint) { - ensureSigintHandler(); + SigintWatcher::ensureSigintHandler(); SigintWatcher::get().registerGlobalObject(globalObject); } @@ -346,7 +305,8 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, } if (options.breakOnSigint) { - ensureSigintHandler(); + // TODO(@heimskr): register global object as appropriate + SigintWatcher::ensureSigintHandler(); } NakedPtr exception; @@ -456,25 +416,10 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, Cal ArgList args(callFrame); JSValue contextArg = args.at(0); - if (contextArg.isUndefinedOrNull()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); - } - - if (!contextArg.isObject()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); - } - + NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, contextArg, true); + RETURN_IF_EXCEPTION(scope, {}); JSObject* context = asObject(contextArg); - auto* zigGlobalObject = defaultGlobalObject(globalObject); - JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); - if (scopeValue.isUndefined()) { - return INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); - } - - NodeVMGlobalObject* nodeVmGlobalObject = jsDynamicCast(scopeValue); - if (!nodeVmGlobalObject) { - return INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); - } + ASSERT(nodeVmGlobalObject != nullptr); return runInContext(nodeVmGlobalObject, script, context, args.at(1)); } diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 1b51527c0d7e..efea4cbde5a8 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -1,13 +1,13 @@ #include "NodeVMSourceTextModule.h" -#include - #include "ErrorCode.h" #include "JSDOMExceptionHandling.h" #include "JSModuleRecord.h" #include "ModuleAnalyzer.h" #include "Parser.h" +#include "../vm/SigintWatcher.h" + namespace Bun { using namespace NodeVM; @@ -63,7 +63,7 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g SourceCode sourceCode(WTFMove(sourceProvider), lineOffset, columnOffset); auto* zigGlobalObject = defaultGlobalObject(globalObject); - NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject), WTFMove(sourceCode)); + NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject), contextValue, WTFMove(sourceCode)); ptr->finishCreation(vm); return ptr; } @@ -116,11 +116,6 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) m_moduleRecord.set(vm, this, moduleRecord); m_moduleRequests.clear(); - std::println("import entries count: {}", moduleRecord->importEntries().size()); - for (const auto& [key, value] : moduleRecord->importEntries()) { - std::println("import entry({}): name={{{}}}, type={{{}}}", key->utf8().data(), value.importName.utf8().data(), int(value.type)); - } - const auto& requests = moduleRecord->requestedModules(); if (requests.isEmpty()) { @@ -203,8 +198,6 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec return JSC::jsUndefined(); } - // JSModuleRecord* record = m_moduleRecord.get(); - VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -219,12 +212,11 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec JSObject* moduleNative = moduleNativeValue.getObject(); m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); - - // AbstractModuleRecord::ImportEntry entry; - - // record->addImportEntry(entry); } + // JSModuleRecord* record = m_moduleRecord.get(); + // record->link(globalObject, jsUndefined()); + status(Status::Linked); return JSC::jsUndefined(); } @@ -239,7 +231,33 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t return {}; } - return JSC::jsUndefined(); + JSModuleRecord* record = m_moduleRecord.get(); + JSValue result {}; + + NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false); + + auto run = [&] { + // TODO(@heimskr): top-level await support + result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast(JSGenerator::ResumeMode::NormalMode))); + }; + + if (timeout != 0 && breakOnSigint) { + // TODO(@heimskr): timeout support + auto holder = SigintWatcher::hold(nodeVmGlobalObject); + run(); + } else if (timeout != 0) { + // TODO(@heimskr): timeout support + run(); + } else if (breakOnSigint) { + auto holder = SigintWatcher::hold(nodeVmGlobalObject); + run(); + } else { + run(); + } + + RETURN_IF_EXCEPTION(scope, (status(Status::Errored), JSValue {})); + status(Status::Evaluated); + return result; } JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index 748332262fac..b1059346b9f5 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -41,8 +41,8 @@ class NodeVMSourceTextModule final : public NodeVMModule { WriteBarrier m_moduleRecord; SourceCode m_sourceCode; - NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, SourceCode sourceCode) - : Base(vm, structure, WTFMove(identifier)) + NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context, SourceCode sourceCode) + : Base(vm, structure, WTFMove(identifier), context) , m_sourceCode(WTFMove(sourceCode)) { } diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 2b3bd97e506e..910bebf9bbff 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -57,6 +57,10 @@ void SigintWatcher::signalReceived() void SigintWatcher::registerGlobalObject(NodeVMGlobalObject* globalObject) { + if (globalObject == nullptr) { + return; + } + std::unique_lock lock(m_globalObjectsMutex); m_globalObjects.push_back(globalObject); @@ -64,6 +68,10 @@ void SigintWatcher::registerGlobalObject(NodeVMGlobalObject* globalObject) void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) { + if (globalObject == nullptr) { + return; + } + std::unique_lock lock(m_globalObjectsMutex); auto iter = std::find(m_globalObjects.begin(), m_globalObjects.end(), globalObject); @@ -96,4 +104,32 @@ bool SigintWatcher::signalAll() return true; } +extern "C" void Bun__ensureSignalHandler(); + +void SigintWatcher::ensureSigintHandler() +{ +#if !OS(WINDOWS) + Bun__ensureSignalHandler(); + + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + + // Set the handler in the action struct + action.sa_handler = [](int signalNumber) { + get().signalReceived(); + }; + + // Clear the sa_mask + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGINT); + action.sa_flags = SA_RESTART; + + sigaction(SIGINT, &action, nullptr); + + get().install(); +#else + static_assert(false, "TODO(@heimskr): implement sigint handler on Windows"); +#endif +} + } // namespace Bun diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index 7f934933d794..ebd15f93a28d 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -25,6 +25,44 @@ class SigintWatcher { static SigintWatcher& get(); + class GlobalObjectHolder { + public: + GlobalObjectHolder(NodeVMGlobalObject* globalObject) + : m_globalObject(globalObject) + { + ensureSigintHandler(); + get().registerGlobalObject(globalObject); + } + + ~GlobalObjectHolder() + { + get().unregisterGlobalObject(m_globalObject); + } + + GlobalObjectHolder(const GlobalObjectHolder&) = delete; + GlobalObjectHolder(GlobalObjectHolder&& other) + : m_globalObject(std::exchange(other.m_globalObject, nullptr)) + { + } + + GlobalObjectHolder& operator=(const GlobalObjectHolder&) = delete; + GlobalObjectHolder& operator=(GlobalObjectHolder&& other) + { + m_globalObject = std::exchange(other.m_globalObject, nullptr); + return *this; + } + + private: + NodeVMGlobalObject* m_globalObject; + }; + + static GlobalObjectHolder hold(NodeVMGlobalObject* globalObject) + { + return { globalObject }; + } + + static void ensureSigintHandler(); + private: std::thread m_thread; std::atomic_bool m_installed = false; From a8854f8198779c8b0c7b66f755d1503c60b6faaf Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 7 May 2025 15:48:31 -0700 Subject: [PATCH 32/76] Fix contextual store detection --- src/bun.js/bindings/NodeVM.cpp | 13 +++--- .../bindings/NodeVMSourceTextModule.cpp | 41 +++++++++++-------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index dfbef183e04a..9791c1583c89 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -479,8 +479,6 @@ void NodeVMGlobalObject::sigintReceived() bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) { - // if (!propertyName.isSymbol()) - // printf("put called for %s\n", propertyName.publicName()->utf8().data()); auto* thisObject = jsCast(cell); if (!thisObject->m_sandbox) { @@ -492,7 +490,9 @@ bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, Propert auto& vm = JSC::getVM(globalObject); JSValue thisValue = slot.thisValue(); bool isContextualStore = thisValue != JSValue(globalObject); - (void)isContextualStore; + if (auto* proxy = jsDynamicCast(thisValue); proxy && proxy->target() == globalObject) { + isContextualStore = false; + } bool isDeclaredOnGlobalObject = slot.type() == JSC::PutPropertySlot::NewProperty; auto scope = DECLARE_THROW_SCOPE(vm); PropertySlot getter(sandbox, PropertySlot::InternalMethodType::Get, nullptr); @@ -531,10 +531,7 @@ static const ASCIILiteral s_proxyAlreadyRevokedErrorMessage { "Proxy has already bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) { - // if (!propertyName.isSymbol()) - // printf("getOwnPropertySlot called for %s\n", propertyName.publicName()->utf8().data()); - - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* thisObject = jsCast(cell); @@ -975,9 +972,9 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_isContext, (JSGlobalObject * globalObject, Cal // // 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) { - auto* thisObject = jsCast(cell); if (UNLIKELY(!thisObject->m_sandbox)) { return Base::deleteProperty(cell, globalObject, propertyName, slot); diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index efea4cbde5a8..2c1d29f8fa0b 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -193,29 +193,34 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec const unsigned length = specifiers->getArrayLength(); ASSERT(length == moduleNatives->getArrayLength()); - if (length == 0) { - status(Status::Linked); - return JSC::jsUndefined(); - } + if (length != 0) { + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); + for (unsigned i = 0; i < length; i++) { + JSValue specifierValue = specifiers->getDirectIndex(globalObject, i); + JSValue moduleNativeValue = moduleNatives->getDirectIndex(globalObject, i); - for (unsigned i = 0; i < length; i++) { - JSValue specifierValue = specifiers->getDirectIndex(globalObject, i); - JSValue moduleNativeValue = moduleNatives->getDirectIndex(globalObject, i); + ASSERT(specifierValue.isString()); + ASSERT(moduleNativeValue.isObject()); - ASSERT(specifierValue.isString()); - ASSERT(moduleNativeValue.isObject()); + WTF::String specifier = specifierValue.toWTFString(globalObject); + JSObject* moduleNative = moduleNativeValue.getObject(); - WTF::String specifier = specifierValue.toWTFString(globalObject); - JSObject* moduleNative = moduleNativeValue.getObject(); + m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); + } + } - m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); + if (NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false)) { + globalObject = nodeVmGlobalObject; } - // JSModuleRecord* record = m_moduleRecord.get(); - // record->link(globalObject, jsUndefined()); + JSModuleRecord* record = m_moduleRecord.get(); + Synchronousness sync = record->link(globalObject, jsUndefined()); + + if (sync == Synchronousness::Async) { + ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async module linking"); + } status(Status::Linked); return JSC::jsUndefined(); @@ -236,6 +241,10 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false); + if (nodeVmGlobalObject) { + globalObject = nodeVmGlobalObject; + } + auto run = [&] { // TODO(@heimskr): top-level await support result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast(JSGenerator::ResumeMode::NormalMode))); From 68866ab5c60b2ebe68658419a13ca3d7b533391c Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 8 May 2025 18:35:03 -0700 Subject: [PATCH 33/76] Get breakOnSigint working for SourceTextModule --- src/bun.js/bindings/NodeVMModule.cpp | 1 - src/bun.js/bindings/NodeVMScript.cpp | 5 +- .../bindings/NodeVMSourceTextModule.cpp | 8 ++ src/vm/SigintWatcher.cpp | 87 ++++++++++++------- src/vm/SigintWatcher.h | 22 +++-- 5 files changed, 80 insertions(+), 43 deletions(-) diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index 6c016c1492dd..fac2765e7f18 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -211,7 +211,6 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalOb if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { return JSValue::encode(thisObject->evaluate(globalObject, timeout, breakOnSigint)); - // return thisObject->link(globalObject, linker); // } else if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { // return thisObject->link(globalObject, specifiers, moduleNatives); } else { diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index a866ed0b55a6..21d10ebd39de 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -260,7 +260,7 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM globalObject->setContextifiedObject(contextifiedObject); if (options.breakOnSigint) { - SigintWatcher::ensureSigintHandler(); + // TODO(@heimskr): set up a GlobalObjectHolder SigintWatcher::get().registerGlobalObject(globalObject); } @@ -298,6 +298,8 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, return ERR::INVALID_ARG_TYPE(throwScope, globalObject, "context"_s, "object"_s, contextArg); } + // TODO(@heimskr): try to retrieve the NodeVMGlobalObject from the context + RunningScriptOptions options; if (!options.fromJS(globalObject, vm, throwScope, contextArg)) { RETURN_IF_EXCEPTION(throwScope, {}); @@ -306,7 +308,6 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, if (options.breakOnSigint) { // TODO(@heimskr): register global object as appropriate - SigintWatcher::ensureSigintHandler(); } NakedPtr exception; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 2c1d29f8fa0b..585a121669f7 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -264,6 +264,14 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t run(); } + if (vm.hasPendingTerminationException()) { + scope.clearException(); + vm.clearHasTerminationRequest(); + status(Status::Errored); + throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); + return {}; + } + RETURN_IF_EXCEPTION(scope, (status(Status::Errored), JSValue {})); status(Status::Evaluated); return result; diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 910bebf9bbff..0e7adf48d0a6 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -2,6 +2,7 @@ #include "SigintWatcher.h" extern "C" void Bun__onPosixSignal(int signalNumber); +extern "C" void Bun__ensureSignalHandler(); namespace Bun { @@ -10,7 +11,7 @@ SigintWatcher SigintWatcher::s_instance; SigintWatcher::SigintWatcher() : m_semaphore(1) { - m_globalObjects.reserve(16); + m_globalObjects.reserveInitialCapacity(16); } SigintWatcher::~SigintWatcher() @@ -20,6 +21,25 @@ SigintWatcher::~SigintWatcher() void SigintWatcher::install() { +#if !OS(WINDOWS) + Bun__ensureSignalHandler(); + + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + + action.sa_handler = [](int signalNumber) { + get().signalReceived(); + }; + + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGINT); + action.sa_flags = 0; + + sigaction(SIGINT, &action, nullptr); +#else + static_assert(false, "TODO(@heimskr): implement sigint handler on Windows"); +#endif + if (m_installed.exchange(true)) { return; } @@ -27,6 +47,9 @@ void SigintWatcher::install() m_thread = std::thread([this] { while (m_installed.load()) { bool success = m_semaphore.wait(); + if (!m_installed) { + return; + } ASSERT(success); if (m_waiting.test_and_set()) { m_waiting.clear(); @@ -43,6 +66,17 @@ void SigintWatcher::install() void SigintWatcher::uninstall() { if (m_installed.exchange(false)) { + ASSERT(m_thread.get_id() != std::this_thread::get_id()); + + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + action.sa_handler = SIG_DFL; + sigemptyset(&action.sa_mask); + sigaddset(&action.sa_mask, SIGINT); + action.sa_flags = SA_RESTART; + sigaction(SIGINT, &action, nullptr); + + m_semaphore.signal(); m_thread.join(); } } @@ -63,7 +97,7 @@ void SigintWatcher::registerGlobalObject(NodeVMGlobalObject* globalObject) std::unique_lock lock(m_globalObjectsMutex); - m_globalObjects.push_back(globalObject); + m_globalObjects.append(globalObject); } void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) @@ -80,8 +114,23 @@ void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) return; } - std::swap(*iter, m_globalObjects.back()); - m_globalObjects.pop_back(); + std::swap(*iter, m_globalObjects.last()); + m_globalObjects.removeLast(); +} + +void SigintWatcher::ref() +{ + if (m_refCount++ == 0) { + install(); + } +} + +void SigintWatcher::deref() +{ + ASSERT(m_refCount > 0); + if (--m_refCount == 0) { + uninstall(); + } } SigintWatcher& SigintWatcher::get() @@ -93,7 +142,7 @@ bool SigintWatcher::signalAll() { std::unique_lock lock(m_globalObjectsMutex); - if (m_globalObjects.empty()) { + if (m_globalObjects.isEmpty()) { return false; } @@ -104,32 +153,4 @@ bool SigintWatcher::signalAll() return true; } -extern "C" void Bun__ensureSignalHandler(); - -void SigintWatcher::ensureSigintHandler() -{ -#if !OS(WINDOWS) - Bun__ensureSignalHandler(); - - struct sigaction action; - memset(&action, 0, sizeof(struct sigaction)); - - // Set the handler in the action struct - action.sa_handler = [](int signalNumber) { - get().signalReceived(); - }; - - // Clear the sa_mask - sigemptyset(&action.sa_mask); - sigaddset(&action.sa_mask, SIGINT); - action.sa_flags = SA_RESTART; - - sigaction(SIGINT, &action, nullptr); - - get().install(); -#else - static_assert(false, "TODO(@heimskr): implement sigint handler on Windows"); -#endif -} - } // namespace Bun diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index ebd15f93a28d..2444859fc5cc 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -22,6 +22,10 @@ class SigintWatcher { void signalReceived(); void registerGlobalObject(NodeVMGlobalObject* globalObject); void unregisterGlobalObject(NodeVMGlobalObject* globalObject); + /** Installs the signal handler if it's not already installed and increments the ref count. */ + void ref(); + /** Decrements the ref count and uninstalls the signal handler if the ref count reaches 0. */ + void deref(); static SigintWatcher& get(); @@ -30,13 +34,18 @@ class SigintWatcher { GlobalObjectHolder(NodeVMGlobalObject* globalObject) : m_globalObject(globalObject) { - ensureSigintHandler(); - get().registerGlobalObject(globalObject); + if (m_globalObject) { + get().ref(); + get().registerGlobalObject(globalObject); + } } ~GlobalObjectHolder() { - get().unregisterGlobalObject(m_globalObject); + if (m_globalObject) { + get().unregisterGlobalObject(m_globalObject); + get().deref(); + } } GlobalObjectHolder(const GlobalObjectHolder&) = delete; @@ -53,7 +62,7 @@ class SigintWatcher { } private: - NodeVMGlobalObject* m_globalObject; + NodeVMGlobalObject* m_globalObject = nullptr; }; static GlobalObjectHolder hold(NodeVMGlobalObject* globalObject) @@ -61,15 +70,14 @@ class SigintWatcher { return { globalObject }; } - static void ensureSigintHandler(); - private: std::thread m_thread; std::atomic_bool m_installed = false; std::atomic_flag m_waiting = false; Semaphore m_semaphore; std::mutex m_globalObjectsMutex; - std::vector m_globalObjects; + WTF::Vector m_globalObjects; + uint32_t m_refCount = 0; bool signalAll(); From 1b0733661eb1bbd3deebe90043086c6b53f04c76 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 8 May 2025 19:38:49 -0700 Subject: [PATCH 34/76] Support timeouts in SourceTextModule evaluation --- .../bindings/NodeVMSourceTextModule.cpp | 34 ++++++++++++------ src/bun.js/bindings/NodeVMSourceTextModule.h | 2 ++ src/vm/SigintWatcher.cpp | 35 +++++++++++++++++-- src/vm/SigintWatcher.h | 23 ++++++++++-- 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 585a121669f7..610cba73ca35 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -5,6 +5,7 @@ #include "JSModuleRecord.h" #include "ModuleAnalyzer.h" #include "Parser.h" +#include "Watchdog.h" #include "../vm/SigintWatcher.h" @@ -250,15 +251,17 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast(JSGenerator::ResumeMode::NormalMode))); }; - if (timeout != 0 && breakOnSigint) { - // TODO(@heimskr): timeout support - auto holder = SigintWatcher::hold(nodeVmGlobalObject); - run(); - } else if (timeout != 0) { - // TODO(@heimskr): timeout support - run(); - } else if (breakOnSigint) { - auto holder = SigintWatcher::hold(nodeVmGlobalObject); + m_terminatedWithSigint = false; + + if (timeout != 0) { + JSC::JSLockHolder locker(vm); + JSC::Watchdog& dog = vm.ensureWatchdog(); + dog.enteredVM(); + dog.setTimeLimit(WTF::Seconds::fromMilliseconds(timeout)); + } + + if (breakOnSigint) { + auto holder = SigintWatcher::hold(nodeVmGlobalObject, this); run(); } else { run(); @@ -268,15 +271,26 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t scope.clearException(); vm.clearHasTerminationRequest(); status(Status::Errored); - throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); + if (m_terminatedWithSigint) { + m_terminatedWithSigint = false; + throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); + } else { + throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, timeout, "ms"_s)); + } return {}; } + m_terminatedWithSigint = false; RETURN_IF_EXCEPTION(scope, (status(Status::Errored), JSValue {})); status(Status::Evaluated); return result; } +void NodeVMSourceTextModule::sigintReceived() +{ + m_terminatedWithSigint = true; +} + JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index b1059346b9f5..e3d8b353973a 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -33,6 +33,7 @@ class NodeVMSourceTextModule final : public NodeVMModule { JSValue createModuleRecord(JSGlobalObject* globalObject); JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); JSValue evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint); + void sigintReceived(); DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; @@ -40,6 +41,7 @@ class NodeVMSourceTextModule final : public NodeVMModule { private: WriteBarrier m_moduleRecord; SourceCode m_sourceCode; + bool m_terminatedWithSigint = false; NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context, SourceCode sourceCode) : Base(vm, structure, WTFMove(identifier), context) diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 0e7adf48d0a6..2b6d8cd76988 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -1,4 +1,5 @@ #include "NodeVM.h" +#include "NodeVMSourceTextModule.h" #include "SigintWatcher.h" extern "C" void Bun__onPosixSignal(int signalNumber); @@ -96,8 +97,7 @@ void SigintWatcher::registerGlobalObject(NodeVMGlobalObject* globalObject) } std::unique_lock lock(m_globalObjectsMutex); - - m_globalObjects.append(globalObject); + m_globalObjects.appendIfNotContains(globalObject); } void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) @@ -109,7 +109,6 @@ void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) std::unique_lock lock(m_globalObjectsMutex); auto iter = std::find(m_globalObjects.begin(), m_globalObjects.end(), globalObject); - if (iter == m_globalObjects.end()) { return; } @@ -118,6 +117,29 @@ void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) m_globalObjects.removeLast(); } +void SigintWatcher::registerModule(NodeVMSourceTextModule* module) +{ + if (module == nullptr) { + return; + } + + std::unique_lock lock(m_modulesMutex); + m_modules.appendIfNotContains(module); +} + +void SigintWatcher::unregisterModule(NodeVMSourceTextModule* module) +{ + std::unique_lock lock(m_modulesMutex); + + auto iter = std::find(m_modules.begin(), m_modules.end(), module); + if (iter == m_modules.end()) { + return; + } + + std::swap(*iter, m_modules.last()); + m_modules.removeLast(); +} + void SigintWatcher::ref() { if (m_refCount++ == 0) { @@ -140,6 +162,13 @@ SigintWatcher& SigintWatcher::get() bool SigintWatcher::signalAll() { + { + std::unique_lock lock(m_modulesMutex); + for (NodeVMSourceTextModule* module : m_modules) { + module->sigintReceived(); + } + } + std::unique_lock lock(m_globalObjectsMutex); if (m_globalObjects.isEmpty()) { diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index 2444859fc5cc..6bbb79818744 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -11,6 +11,7 @@ namespace Bun { class NodeVMGlobalObject; +class NodeVMSourceTextModule; class SigintWatcher { public: @@ -22,6 +23,8 @@ class SigintWatcher { void signalReceived(); void registerGlobalObject(NodeVMGlobalObject* globalObject); void unregisterGlobalObject(NodeVMGlobalObject* globalObject); + void registerModule(NodeVMSourceTextModule* module); + void unregisterModule(NodeVMSourceTextModule* module); /** Installs the signal handler if it's not already installed and increments the ref count. */ void ref(); /** Decrements the ref count and uninstalls the signal handler if the ref count reaches 0. */ @@ -31,17 +34,26 @@ class SigintWatcher { class GlobalObjectHolder { public: - GlobalObjectHolder(NodeVMGlobalObject* globalObject) + GlobalObjectHolder(NodeVMGlobalObject* globalObject, NodeVMSourceTextModule* module) : m_globalObject(globalObject) + , m_module(module) { if (m_globalObject) { get().ref(); get().registerGlobalObject(globalObject); } + + if (m_module) { + get().registerModule(m_module); + } } ~GlobalObjectHolder() { + if (m_module) { + get().unregisterModule(m_module); + } + if (m_globalObject) { get().unregisterGlobalObject(m_globalObject); get().deref(); @@ -51,6 +63,7 @@ class SigintWatcher { GlobalObjectHolder(const GlobalObjectHolder&) = delete; GlobalObjectHolder(GlobalObjectHolder&& other) : m_globalObject(std::exchange(other.m_globalObject, nullptr)) + , m_module(std::exchange(other.m_module, nullptr)) { } @@ -58,16 +71,18 @@ class SigintWatcher { GlobalObjectHolder& operator=(GlobalObjectHolder&& other) { m_globalObject = std::exchange(other.m_globalObject, nullptr); + m_module = std::exchange(other.m_module, nullptr); return *this; } private: NodeVMGlobalObject* m_globalObject = nullptr; + NodeVMSourceTextModule* m_module = nullptr; }; - static GlobalObjectHolder hold(NodeVMGlobalObject* globalObject) + static GlobalObjectHolder hold(NodeVMGlobalObject* globalObject, NodeVMSourceTextModule* module) { - return { globalObject }; + return { globalObject, module }; } private: @@ -76,7 +91,9 @@ class SigintWatcher { std::atomic_flag m_waiting = false; Semaphore m_semaphore; std::mutex m_globalObjectsMutex; + std::mutex m_modulesMutex; WTF::Vector m_globalObjects; + WTF::Vector m_modules; uint32_t m_refCount = 0; bool signalAll(); From dd0d08e842c28ef422774db47d7ad2109115d310 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 8 May 2025 19:41:34 -0700 Subject: [PATCH 35/76] Add test-vm-timeout-escape-promise-module.js --- .../test-vm-timeout-escape-promise-module.js | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/js/node/test/parallel/test-vm-timeout-escape-promise-module.js diff --git a/test/js/node/test/parallel/test-vm-timeout-escape-promise-module.js b/test/js/node/test/parallel/test-vm-timeout-escape-promise-module.js new file mode 100644 index 000000000000..4b0169a7789d --- /dev/null +++ b/test/js/node/test/parallel/test-vm-timeout-escape-promise-module.js @@ -0,0 +1,42 @@ +// Flags: --experimental-vm-modules +'use strict'; + +// https://github.com/nodejs/node/issues/3020 +// Promises used to allow code to escape the timeout +// set for runInContext, runInNewContext, and runInThisContext. + +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const NS_PER_MS = 1000000n; + +const hrtime = process.hrtime.bigint; + +function loop() { + const start = hrtime(); + while (1) { + const current = hrtime(); + const span = (current - start) / NS_PER_MS; + if (span >= 2000n) { + throw new Error( + `escaped timeout at ${span} milliseconds!`); + } + } +} + +assert.rejects(async () => { + const module = new vm.SourceTextModule( + 'Promise.resolve().then(() => loop()); loop();', + { + context: vm.createContext({ + hrtime, + loop + }, { microtaskMode: 'afterEvaluate' }) + }); + await module.link(common.mustNotCall()); + await module.evaluate({ timeout: 5 }); +}, { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 5ms' +}).then(common.mustCall()); From a7e3f31ef1df7b488223d8729db09937246cb8c0 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Mon, 12 May 2025 23:07:14 -0700 Subject: [PATCH 36/76] Safety nitpicks in ZigGlobalObject.cpp that probably make no difference in practice --- src/bun.js/bindings/ZigGlobalObject.cpp | 50 ++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 84f10bf7c0c2..0369def79b54 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -1325,7 +1325,7 @@ void GlobalObject::promiseRejectionTracker(JSGlobalObject* obj, JSC::JSPromise* // obj, prom, reject == JSC::JSPromiseRejectionOperation::Reject ? 0 : 1); // Do this in C++ for now - auto* globalObj = reinterpret_cast(obj); + auto* globalObj = static_cast(obj); switch (operation) { case JSPromiseRejectionOperation::Reject: globalObj->m_aboutToBeNotifiedRejectedPromises.append(JSC::Strong(obj->vm(), promise)); @@ -1767,7 +1767,7 @@ extern "C" JSC::EncodedJSValue Bun__createUint8ArrayForCopy(JSC::JSGlobalObject* VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - auto* subclassStructure = isBuffer ? reinterpret_cast(globalObject)->JSBufferSubclassStructure() : globalObject->typedArrayStructureWithTypedArrayType(); + auto* subclassStructure = isBuffer ? static_cast(globalObject)->JSBufferSubclassStructure() : globalObject->typedArrayStructureWithTypedArrayType(); JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, subclassStructure, len); if (UNLIKELY(!array)) { @@ -1877,7 +1877,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionDispatchEvent, (JSGlobalObject * lexicalGloba JSC_DEFINE_CUSTOM_GETTER(getterSubtleCrypto, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) { - return JSValue::encode(reinterpret_cast(lexicalGlobalObject)->subtleCrypto()); + return JSValue::encode(static_cast(lexicalGlobalObject)->subtleCrypto()); } extern "C" JSC::EncodedJSValue ExpectMatcherUtils_createSigleton(JSC::JSGlobalObject* lexicalGlobalObject); @@ -2243,7 +2243,7 @@ static inline JSC::EncodedJSValue ZigGlobalObject__readableStreamToArrayBufferBo extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToArrayBuffer(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue); extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToArrayBuffer(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue) { - return ZigGlobalObject__readableStreamToArrayBufferBody(reinterpret_cast(globalObject), readableStreamValue); + return ZigGlobalObject__readableStreamToArrayBufferBody(static_cast(globalObject), readableStreamValue); } extern "C" JSC::EncodedJSValue ZigGlobalObject__readableStreamToBytes(Zig::GlobalObject* globalObject, JSC::EncodedJSValue readableStreamValue); @@ -2386,7 +2386,7 @@ JSC_DEFINE_HOST_FUNCTION(functionReadableStreamToArrayBuffer, (JSGlobalObject * } auto readableStreamValue = callFrame->uncheckedArgument(0); - return ZigGlobalObject__readableStreamToArrayBufferBody(reinterpret_cast(globalObject), JSValue::encode(readableStreamValue)); + return ZigGlobalObject__readableStreamToArrayBufferBody(static_cast(globalObject), JSValue::encode(readableStreamValue)); } JSC_DECLARE_HOST_FUNCTION(functionReadableStreamToBytes); @@ -2401,7 +2401,7 @@ JSC_DEFINE_HOST_FUNCTION(functionReadableStreamToBytes, (JSGlobalObject * global } auto readableStreamValue = callFrame->uncheckedArgument(0); - return ZigGlobalObject__readableStreamToBytes(reinterpret_cast(globalObject), JSValue::encode(readableStreamValue)); + return ZigGlobalObject__readableStreamToBytes(static_cast(globalObject), JSValue::encode(readableStreamValue)); } JSC_DEFINE_HOST_FUNCTION(jsFunctionPerformMicrotask, (JSGlobalObject * globalObject, CallFrame* callframe)) @@ -2539,7 +2539,7 @@ void GlobalObject::createCallSitesFromFrames(Zig::GlobalObject* globalObject, JS JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncAppendStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { - GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); + GlobalObject* globalObject = static_cast(lexicalGlobalObject); auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); @@ -2623,7 +2623,7 @@ JSC_DEFINE_CUSTOM_SETTER(errorInstanceLazyStackCustomSetter, (JSGlobalObject * g JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { - GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); + GlobalObject* globalObject = static_cast(lexicalGlobalObject); auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); @@ -2845,7 +2845,7 @@ void GlobalObject::finishCreation(VM& vm) m_commonJSModuleObjectStructure.initLater( [](const Initializer& init) { - init.set(Bun::createCommonJSModuleStructure(reinterpret_cast(init.owner))); + init.set(Bun::createCommonJSModuleStructure(static_cast(init.owner))); }); m_JSSocketAddressDTOStructure.initLater( @@ -2891,7 +2891,7 @@ void GlobalObject::finishCreation(VM& vm) [](const JSC::LazyProperty::Initializer& init) { init.set( createMemoryFootprintStructure( - init.vm, reinterpret_cast(init.owner))); + init.vm, static_cast(init.owner))); }); m_errorConstructorPrepareStackTraceInternalValue.initLater( @@ -2914,14 +2914,14 @@ void GlobalObject::finishCreation(VM& vm) m_JSBufferSubclassStructure.initLater( [](const Initializer& init) { - auto* globalObject = reinterpret_cast(init.owner); + auto* globalObject = static_cast(init.owner); auto* baseStructure = globalObject->typedArrayStructureWithTypedArrayType(); JSC::Structure* subclassStructure = JSC::InternalFunction::createSubclassStructure(globalObject, globalObject->JSBufferConstructor(), baseStructure); init.set(subclassStructure); }); m_JSResizableOrGrowableSharedBufferSubclassStructure.initLater( [](const Initializer& init) { - auto* globalObject = reinterpret_cast(init.owner); + auto* globalObject = static_cast(init.owner); auto* baseStructure = globalObject->resizableOrGrowableSharedTypedArrayStructureWithTypedArrayType(); JSC::Structure* subclassStructure = JSC::InternalFunction::createSubclassStructure(globalObject, globalObject->JSBufferConstructor(), baseStructure); init.set(subclassStructure); @@ -3053,17 +3053,17 @@ void GlobalObject::finishCreation(VM& vm) m_ServerRouteListStructure.initLater( [](const JSC::LazyProperty::Initializer& init) { - init.set(Bun::createServerRouteListStructure(init.vm, reinterpret_cast(init.owner))); + init.set(Bun::createServerRouteListStructure(init.vm, static_cast(init.owner))); }); m_JSBunRequestParamsPrototype.initLater( [](const JSC::LazyProperty::Initializer& init) { - init.set(Bun::createJSBunRequestParamsPrototype(init.vm, reinterpret_cast(init.owner))); + init.set(Bun::createJSBunRequestParamsPrototype(init.vm, static_cast(init.owner))); }); m_JSBunRequestStructure.initLater( [](const JSC::LazyProperty::Initializer& init) { - init.set(Bun::createJSBunRequestStructure(init.vm, reinterpret_cast(init.owner))); + init.set(Bun::createJSBunRequestStructure(init.vm, static_cast(init.owner))); }); m_NapiHandleScopeImplStructure.initLater([](const JSC::LazyProperty::Initializer& init) { @@ -3086,7 +3086,7 @@ void GlobalObject::finishCreation(VM& vm) m_subtleCryptoObject.initLater( [](const JSC::LazyProperty::Initializer& init) { - auto& global = *reinterpret_cast(init.owner); + auto& global = *static_cast(init.owner); if (!global.m_subtleCrypto) { global.m_subtleCrypto = &WebCore::SubtleCrypto::create(global.scriptExecutionContext()).leakRef(); @@ -3132,13 +3132,13 @@ void GlobalObject::finishCreation(VM& vm) m_performanceObject.initLater( [](const JSC::LazyProperty::Initializer& init) { - auto* globalObject = reinterpret_cast(init.owner); + auto* globalObject = static_cast(init.owner); init.set(toJS(init.owner, globalObject, globalObject->performance().get()).getObject()); }); m_processEnvObject.initLater( [](const JSC::LazyProperty::Initializer& init) { - init.set(Bun::createEnvironmentVariablesMap(reinterpret_cast(init.owner)).getObject()); + init.set(Bun::createEnvironmentVariablesMap(static_cast(init.owner)).getObject()); }); m_processObject.initLater( @@ -3297,7 +3297,7 @@ void GlobalObject::finishCreation(VM& vm) m_JSCryptoKey.initLater( [](const JSC::LazyProperty::Initializer& init) { - Zig::GlobalObject* globalObject = reinterpret_cast(init.owner); + Zig::GlobalObject* globalObject = static_cast(init.owner); auto* prototype = JSCryptoKey::createPrototype(init.vm, *globalObject); auto* structure = JSCryptoKey::createStructure(init.vm, init.owner, JSValue(prototype)); init.set(structure); @@ -3555,7 +3555,7 @@ JSC_DEFINE_CUSTOM_GETTER(functionLazyNavigatorGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) { - return JSC::JSValue::encode(reinterpret_cast(globalObject)->navigatorObject()); + return JSC::JSValue::encode(static_cast(globalObject)->navigatorObject()); } JSC::GCClient::IsoSubspace* GlobalObject::subspaceForImpl(JSC::VM& vm) @@ -3968,7 +3968,7 @@ void GlobalObject::reload() extern "C" void JSC__JSGlobalObject__reload(JSC::JSGlobalObject* arg0) { - Zig::GlobalObject* globalObject = reinterpret_cast(arg0); + Zig::GlobalObject* globalObject = static_cast(arg0); globalObject->reload(); } @@ -3977,14 +3977,14 @@ extern "C" void JSC__JSGlobalObject__queueMicrotaskCallback(Zig::GlobalObject* g JSFunction* function = globalObject->nativeMicrotaskTrampoline(); // Do not use JSCell* here because the GC will try to visit it. - globalObject->queueMicrotask(function, JSValue(__bit_cast(reinterpret_cast(ptr))), JSValue(__bit_cast(reinterpret_cast(callback))), jsUndefined(), jsUndefined()); + globalObject->queueMicrotask(function, JSValue(JSValue::EncodeAsDouble, __bit_cast(reinterpret_cast(ptr))), JSValue(JSValue::EncodeAsDouble, __bit_cast(reinterpret_cast(callback))), jsUndefined(), jsUndefined()); } JSC::Identifier GlobalObject::moduleLoaderResolve(JSGlobalObject* jsGlobalObject, JSModuleLoader* loader, JSValue key, JSValue referrer, JSValue origin) { - Zig::GlobalObject* globalObject = reinterpret_cast(jsGlobalObject); + Zig::GlobalObject* globalObject = static_cast(jsGlobalObject); ErrorableString res; res.success = false; @@ -4040,7 +4040,7 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderImportModule(JSGlobalObject* j JSValue parameters, const SourceOrigin& sourceOrigin) { - auto* globalObject = reinterpret_cast(jsGlobalObject); + auto* globalObject = static_cast(jsGlobalObject); auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSC::Identifier resolvedIdentifier; @@ -4204,7 +4204,7 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalOb res.result.err.ptr = nullptr; JSValue result = Bun::fetchESMSourceCodeAsync( - reinterpret_cast(globalObject), + static_cast(globalObject), moduleKeyJS, &res, &moduleKeyBun, From 72d0151520a4b1a5897063da35da8f94e9fd2a21 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Mon, 12 May 2025 23:26:14 -0700 Subject: [PATCH 37/76] Pass declared/lexical module variables --- src/bun.js/bindings/NodeVMModule.cpp | 23 ++- src/bun.js/bindings/NodeVMModule.h | 5 + .../bindings/NodeVMSourceTextModule.cpp | 159 ++++++++++++++++-- src/bun.js/bindings/NodeVMSourceTextModule.h | 8 +- src/js/node/vm.ts | 7 +- 5 files changed, 179 insertions(+), 23 deletions(-) diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index fac2765e7f18..5da7de9384bf 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -61,6 +61,16 @@ JSValue NodeVMModule::createModuleRecord(JSC::JSGlobalObject* globalObject) return JSC::jsUndefined(); } +AbstractModuleRecord* NodeVMModule::moduleRecord(JSC::JSGlobalObject* globalObject) +{ + if (auto* thisObject = jsDynamicCast(this)) { + return thisObject->moduleRecord(globalObject); + } + + ASSERT_NOT_REACHED(); + return nullptr; +} + NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -181,6 +191,11 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalOb JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = jsCast(callFrame->thisValue()); + + if (auto* sourceTextModule = jsDynamicCast(callFrame->thisValue())) { + sourceTextModule->ensureModuleRecord(globalObject); + } + const WTF::Vector& requests = thisObject->moduleRequests(); JSArray* array = constructEmptyArray(globalObject, nullptr, requests.size()); @@ -235,14 +250,8 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject return throwArgumentTypeError(*globalObject, scope, 1, "moduleNatives"_s, "Module"_s, "Module"_s, "Array"_s); } - // JSValue linker = callFrame->argument(0); - - // if (!linker.isCallable()) { - // return throwArgumentTypeError(*globalObject, scope, 0, "linker"_s, "Module"_s, "Module"_s, "function"_s); - // } - if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { - return JSValue::encode(thisObject->link(globalObject, specifiers, moduleNatives)); + 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); diff --git a/src/bun.js/bindings/NodeVMModule.h b/src/bun.js/bindings/NodeVMModule.h index 454b684683b9..79057345eadd 100644 --- a/src/bun.js/bindings/NodeVMModule.h +++ b/src/bun.js/bindings/NodeVMModule.h @@ -2,6 +2,8 @@ #include "NodeVM.h" +#include "JavaScriptCore/AbstractModuleRecord.h" + namespace Bun { class NodeVMSourceTextModule; @@ -59,6 +61,9 @@ class NodeVMModule : public JSC::JSDestructibleObject { // Purposely not virtual. Dispatches to the correct subclass. JSValue createModuleRecord(JSC::JSGlobalObject* globalObject); + // Purposely not virtual. Dispatches to the correct subclass. + AbstractModuleRecord* moduleRecord(JSC::JSGlobalObject* globalObject); + protected: WTF::String m_identifier; Status m_status = Status::Unlinked; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 610cba73ca35..03478ab0e619 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -2,13 +2,119 @@ #include "ErrorCode.h" #include "JSDOMExceptionHandling.h" +#include "JSModuleLoader.h" #include "JSModuleRecord.h" +#include "JSSourceCode.h" #include "ModuleAnalyzer.h" #include "Parser.h" #include "Watchdog.h" +#include "wtf/Scope.h" #include "../vm/SigintWatcher.h" +#include + +extern "C" BunString Bun__inspect(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value); + +template<> +struct std::formatter { + constexpr auto parse(std::format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(const WTF::ASCIILiteral& literal, std::format_context& ctx) const + { + return std::format_to(ctx.out(), "{}", literal.characters()); + } +}; + +template<> +struct std::formatter { + constexpr auto parse(std::format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(const WTF::String& string, std::format_context& ctx) const + { + return std::format_to(ctx.out(), "{}", string.utf8().data()); + } +}; + +template<> +struct std::formatter { + constexpr auto parse(std::format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(const JSC::Identifier& identifier, std::format_context& ctx) const + { + return std::format_to(ctx.out(), "{}", identifier.utf8().data()); + } +}; + +template<> +struct std::formatter { + constexpr auto parse(std::format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(const WTF::StringPrintStream& stream, std::format_context& ctx) const + { + return std::format_to(ctx.out(), "{}", stream.toString()); + } +}; + +template<> +struct std::formatter { + constexpr auto parse(std::format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(const JSC::JSValue& value, std::format_context& ctx) const + { + auto* global = defaultGlobalObject(); + if (auto* error = jsDynamicCast(value)) { + return std::format_to(ctx.out(), "{}", error->sanitizedMessageString(global)); + } + return std::format_to(ctx.out(), "{}", Bun__inspect(global, JSC::JSValue::encode(value)).transferToWTFString()); + } +}; + +template<> +struct std::formatter> { + constexpr auto parse(std::format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(const WTF::Vector& frames, std::format_context& ctx) const + { + for (unsigned i = 0; const JSC::StackFrame& frame : frames) { + std::format_to(ctx.out(), "{: 2} | {}\n", i++, frame.toString(defaultGlobalObject()->vm())); + } + + return ctx.out(); + } +}; + +template<> +struct std::formatter { + constexpr auto parse(std::format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(JSC::Exception* exception, std::format_context& ctx) const + { + return std::format_to(ctx.out(), "{}\n{}", exception->value(), exception->stack()); + } +}; + namespace Bun { using namespace NodeVM; @@ -76,15 +182,12 @@ void NodeVMSourceTextModule::destroy(JSCell* cell) JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) { - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (m_moduleRecord) { - throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_ALREADY_LINKED, "Module record already present"_s); - return {}; + if (m_moduleRequestsArray) { + return m_moduleRequestsArray.get(); } - ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, {}, {}, AllFeatures); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); ParserError parserError; @@ -101,6 +204,8 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) return {}; } + ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, node->varDeclarations(), node->lexicalVariables(), AllFeatures); + RETURN_IF_EXCEPTION(scope, {}); ASSERT(node != nullptr); @@ -125,6 +230,8 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) JSArray* requestsArray = JSC::constructEmptyArray(globalObject, nullptr, requests.size()); + // MarkedArgumentBuffer buffer; + const auto& builtinNames = WebCore::clientData(vm)->builtinNames(); const JSC::Identifier& specifierIdentifier = builtinNames.specifierPublicName(); const JSC::Identifier& attributesIdentifier = builtinNames.attributesPublicName(); @@ -186,18 +293,35 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) requestsArray->putDirectIndex(globalObject, i, requestObject); } + m_moduleRequestsArray.set(vm, this, requestsArray); return requestsArray; } -JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives) +void NodeVMSourceTextModule::ensureModuleRecord(JSGlobalObject* globalObject) +{ + if (!m_moduleRecord) { + createModuleRecord(globalObject); + } +} + +AbstractModuleRecord* NodeVMSourceTextModule::moduleRecord(JSGlobalObject* globalObject) +{ + ensureModuleRecord(globalObject); + return m_moduleRecord.get(); +} + +JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives, JSValue scriptFetcher) { const unsigned length = specifiers->getArrayLength(); + ASSERT(length == moduleNatives->getArrayLength()); - if (length != 0) { - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSModuleRecord* record = m_moduleRecord.get(); + if (length != 0) { for (unsigned i = 0; i < length; i++) { JSValue specifierValue = specifiers->getDirectIndex(globalObject, i); JSValue moduleNativeValue = moduleNatives->getDirectIndex(globalObject, i); @@ -207,7 +331,9 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec WTF::String specifier = specifierValue.toWTFString(globalObject); JSObject* moduleNative = moduleNativeValue.getObject(); + auto* resolvedRecord = jsCast(moduleNative)->moduleRecord(globalObject); + record->setImportedModule(globalObject, Identifier::fromString(vm, specifier), resolvedRecord); m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); } } @@ -216,13 +342,20 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec globalObject = nodeVmGlobalObject; } - JSModuleRecord* record = m_moduleRecord.get(); - Synchronousness sync = record->link(globalObject, jsUndefined()); + Synchronousness sync = record->link(globalObject, scriptFetcher); + + if (auto* exception = scope.exception()) { + scope.clearException(); + std::println("Exception: {}", exception); + scope.throwException(globalObject, exception); + } if (sync == Synchronousness::Async) { ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async module linking"); } + RETURN_IF_EXCEPTION(scope, {}); + status(Status::Linked); return JSC::jsUndefined(); } diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index e3d8b353973a..2c8cc2d52799 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -31,15 +31,21 @@ class NodeVMSourceTextModule final : public NodeVMModule { } JSValue createModuleRecord(JSGlobalObject* globalObject); - JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives); + void ensureModuleRecord(JSGlobalObject* globalObject); + bool hasModuleRecord() const { return !!m_moduleRecord; } + AbstractModuleRecord* moduleRecord(JSGlobalObject* globalObject); + JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives, JSValue scriptFetcher); JSValue evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint); void sigintReceived(); + const SourceCode& sourceCode() const { return m_sourceCode; } + DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; private: WriteBarrier m_moduleRecord; + WriteBarrier m_moduleRequestsArray; SourceCode m_sourceCode; bool m_terminatedWithSigint = false; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 901b9c3886bc..f24e284ee1f5 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -258,8 +258,11 @@ class SourceTextModule extends Module { } async [kLink](linker) { - this.#statusOverride = "linking"; + if (this[kNative].getStatusCode() >= kLinked) { + throw $ERR_VM_MODULE_ALREADY_LINKED(); + } + this.#statusOverride = "linking"; const moduleRequests = this[kNative].createModuleRecord(); // Iterates the module requests and links with the linker. @@ -296,7 +299,7 @@ class SourceTextModule extends Module { try { const moduleNatives = await SafePromiseAllReturnArrayLike(modulePromises); - this[kNative].link(specifiers, moduleNatives); + this[kNative].link(specifiers, moduleNatives, 0); } catch (e) { console.error("linking error:", e); this.#error = e; From d12d0b9483a45955f7d4dc10fad12133f0f36a56 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Mon, 12 May 2025 23:28:56 -0700 Subject: [PATCH 38/76] Remove some garbage --- .../bindings/NodeVMSourceTextModule.cpp | 109 +----------------- 1 file changed, 1 insertion(+), 108 deletions(-) diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 03478ab0e619..a0ba4ee6cd4f 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -12,109 +12,6 @@ #include "../vm/SigintWatcher.h" -#include - -extern "C" BunString Bun__inspect(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value); - -template<> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(const WTF::ASCIILiteral& literal, std::format_context& ctx) const - { - return std::format_to(ctx.out(), "{}", literal.characters()); - } -}; - -template<> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(const WTF::String& string, std::format_context& ctx) const - { - return std::format_to(ctx.out(), "{}", string.utf8().data()); - } -}; - -template<> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(const JSC::Identifier& identifier, std::format_context& ctx) const - { - return std::format_to(ctx.out(), "{}", identifier.utf8().data()); - } -}; - -template<> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(const WTF::StringPrintStream& stream, std::format_context& ctx) const - { - return std::format_to(ctx.out(), "{}", stream.toString()); - } -}; - -template<> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(const JSC::JSValue& value, std::format_context& ctx) const - { - auto* global = defaultGlobalObject(); - if (auto* error = jsDynamicCast(value)) { - return std::format_to(ctx.out(), "{}", error->sanitizedMessageString(global)); - } - return std::format_to(ctx.out(), "{}", Bun__inspect(global, JSC::JSValue::encode(value)).transferToWTFString()); - } -}; - -template<> -struct std::formatter> { - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(const WTF::Vector& frames, std::format_context& ctx) const - { - for (unsigned i = 0; const JSC::StackFrame& frame : frames) { - std::format_to(ctx.out(), "{: 2} | {}\n", i++, frame.toString(defaultGlobalObject()->vm())); - } - - return ctx.out(); - } -}; - -template<> -struct std::formatter { - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(JSC::Exception* exception, std::format_context& ctx) const - { - return std::format_to(ctx.out(), "{}\n{}", exception->value(), exception->stack()); - } -}; - namespace Bun { using namespace NodeVM; @@ -344,11 +241,7 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec Synchronousness sync = record->link(globalObject, scriptFetcher); - if (auto* exception = scope.exception()) { - scope.clearException(); - std::println("Exception: {}", exception); - scope.throwException(globalObject, exception); - } + RETURN_IF_EXCEPTION(scope, {}); if (sync == Synchronousness::Async) { ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async module linking"); From e2d5bfad85e97a9dc48adfb262a64cd5943362c7 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 13 May 2025 19:12:29 -0700 Subject: [PATCH 39/76] Recursive module evaluation support (sync-only for now) + namespace object fixes --- src/bun.js/bindings/NodeVMModule.cpp | 26 +++++++++++++++++-- src/bun.js/bindings/NodeVMModule.h | 9 ++++--- .../bindings/NodeVMSourceTextModule.cpp | 22 +++++++++++----- src/js/node/vm.ts | 3 --- 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index 5da7de9384bf..662c19205184 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -88,6 +88,28 @@ NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObjec return nullptr; } +JSModuleNamespaceObject* NodeVMModule::namespaceObject(JSC::JSGlobalObject* globalObject) +{ + JSModuleNamespaceObject* object = m_namespaceObject.get(); + if (object) { + return object; + } + + if (auto* thisObject = jsDynamicCast(this)) { + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + object = thisObject->moduleRecord(globalObject)->getModuleNamespace(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + if (object) { + namespaceObject(vm, object); + } + } else { + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("NodeVMModule::namespaceObject called on an unsupported module type (%s)", info()->className.characters()); + } + + return object; +} + JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus); @@ -179,7 +201,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalO JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = jsCast(callFrame->thisValue()); - return JSValue::encode(thisObject->namespace_()); + return JSValue::encode(thisObject->namespaceObject(globalObject)); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) @@ -292,7 +314,7 @@ void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) ASSERT_GC_OBJECT_INHERITS(vmModule, info()); Base::visitChildren(vmModule, visitor); - visitor.append(vmModule->m_namespace); + visitor.append(vmModule->m_namespaceObject); visitor.append(vmModule->m_context); auto moduleNatives = vmModule->m_resolveCache.values(); diff --git a/src/bun.js/bindings/NodeVMModule.h b/src/bun.js/bindings/NodeVMModule.h index 79057345eadd..c0226c9cb337 100644 --- a/src/bun.js/bindings/NodeVMModule.h +++ b/src/bun.js/bindings/NodeVMModule.h @@ -2,7 +2,8 @@ #include "NodeVM.h" -#include "JavaScriptCore/AbstractModuleRecord.h" +#include "AbstractModuleRecord.h" +#include "JSModuleNamespaceObject.h" namespace Bun { @@ -49,8 +50,8 @@ class NodeVMModule : public JSC::JSDestructibleObject { Status status() const { return m_status; } void status(Status value) { m_status = value; } - JSObject* namespace_() const { return m_namespace.get(); } - void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } + JSModuleNamespaceObject* namespaceObject(JSC::JSGlobalObject* globalObject); + void namespaceObject(JSC::VM& vm, JSModuleNamespaceObject* value) { m_namespaceObject.set(vm, this, value); } const WTF::Vector& moduleRequests() const { return m_moduleRequests; } void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); } @@ -67,7 +68,7 @@ class NodeVMModule : public JSC::JSDestructibleObject { protected: WTF::String m_identifier; Status m_status = Status::Unlinked; - mutable WriteBarrier m_namespace; + mutable WriteBarrier m_namespaceObject; mutable WriteBarrier m_context; WTF::Vector m_moduleRequests; mutable WTF::HashMap> m_resolveCache; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index a0ba4ee6cd4f..00c876399576 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -2,8 +2,8 @@ #include "ErrorCode.h" #include "JSDOMExceptionHandling.h" -#include "JSModuleLoader.h" #include "JSModuleRecord.h" +#include "JSPromise.h" #include "JSSourceCode.h" #include "ModuleAnalyzer.h" #include "Parser.h" @@ -228,7 +228,7 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec WTF::String specifier = specifierValue.toWTFString(globalObject); JSObject* moduleNative = moduleNativeValue.getObject(); - auto* resolvedRecord = jsCast(moduleNative)->moduleRecord(globalObject); + AbstractModuleRecord* resolvedRecord = jsCast(moduleNative)->moduleRecord(globalObject); record->setImportedModule(globalObject, Identifier::fromString(vm, specifier), resolvedRecord); m_resolveCache.set(WTFMove(specifier), WriteBarrier { vm, this, moduleNative }); @@ -244,11 +244,9 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec RETURN_IF_EXCEPTION(scope, {}); if (sync == Synchronousness::Async) { - ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async module linking"); + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): async module linking"); } - RETURN_IF_EXCEPTION(scope, {}); - status(Status::Linked); return JSC::jsUndefined(); } @@ -273,7 +271,19 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t } auto run = [&] { - // TODO(@heimskr): top-level await support + status(Status::Evaluating); + + for (const auto& request : record->requestedModules()) { + if (auto iter = m_resolveCache.find(WTF::String(*request.m_specifier)); iter != m_resolveCache.end()) { + if (auto* dependency = jsDynamicCast(iter->value.get())) { + if (dependency->status() == Status::Linked) { + JSValue dependencyResult = dependency->evaluate(globalObject, timeout, breakOnSigint); + RELEASE_ASSERT_WITH_MESSAGE(jsDynamicCast(dependencyResult) == nullptr, "TODO(@heimskr): implement async support for node:vm SourceTextModule dependencies"); + } + } + } + } + result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast(JSGenerator::ResumeMode::NormalMode))); }; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index f24e284ee1f5..e8cf3abebc24 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -9,7 +9,6 @@ const { validateInt32, validateBuffer, validateFunction, - validateInternalField, } = require("internal/validators"); const util = require("node:util"); @@ -190,7 +189,6 @@ class Module { } [util.inspect.custom](depth, options) { - validateInternalField(this, kNative, "Module"); if (typeof depth === "number" && depth < 0) return this; const constructor = getConstructorOf(this) || Module; @@ -301,7 +299,6 @@ class SourceTextModule extends Module { const moduleNatives = await SafePromiseAllReturnArrayLike(modulePromises); this[kNative].link(specifiers, moduleNatives, 0); } catch (e) { - console.error("linking error:", e); this.#error = e; throw e; } finally { From dbf38979361e875f434872cb9be5a6ca2ca992c6 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Tue, 13 May 2025 20:38:03 -0700 Subject: [PATCH 40/76] test-vm-module-link.js --- .../bindings/NodeVMSourceTextModule.cpp | 47 +++-- .../node/test/parallel/test-vm-module-link.js | 168 ++++++++++++++++++ 2 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 test/js/node/test/parallel/test-vm-module-link.js diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 00c876399576..5927c4b35eec 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -127,13 +127,32 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) JSArray* requestsArray = JSC::constructEmptyArray(globalObject, nullptr, requests.size()); - // MarkedArgumentBuffer buffer; - const auto& builtinNames = WebCore::clientData(vm)->builtinNames(); const JSC::Identifier& specifierIdentifier = builtinNames.specifierPublicName(); const JSC::Identifier& attributesIdentifier = builtinNames.attributesPublicName(); const JSC::Identifier& hostDefinedImportTypeIdentifier = builtinNames.hostDefinedImportTypePublicName(); + WTF::Vector attributesNodes; + attributesNodes.reserveInitialCapacity(requests.size()); + + for (StatementNode* statement = node->statements()->firstStatement(); statement; statement = statement->next()) { + // Assumption: module declarations occur here in the same order they occur in `requestedModules`. + if (statement->isModuleDeclarationNode()) { + ModuleDeclarationNode* moduleDeclaration = static_cast(statement); + if (moduleDeclaration->isImportDeclarationNode()) { + ImportDeclarationNode* importDeclaration = static_cast(moduleDeclaration); + ASSERT_WITH_MESSAGE(attributesNodes.size() < requests.size(), "More attributes nodes than requests"); + ASSERT_WITH_MESSAGE(importDeclaration->moduleName()->moduleName().string().string() == WTF::String(*requests.at(attributesNodes.size()).m_specifier), "Module name mismatch"); + attributesNodes.append(importDeclaration->attributesList()); + } else if (moduleDeclaration->hasAttributesList()) { + // Necessary to make the indices of `attributesNodes` and `requests` match up + attributesNodes.append(nullptr); + } + } + } + + ASSERT_WITH_MESSAGE(attributesNodes.size() == requests.size(), "Attributes node count doesn't match request count (%zu != %zu)", attributesNodes.size(), requests.size()); + for (unsigned i = 0; i < requests.size(); ++i) { const auto& request = requests[i]; @@ -144,6 +163,9 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) WTF::String attributesTypeString = "unknown"_str; + WTF::HashMap attributeMap; + JSObject* attributesObject = constructEmptyObject(globalObject); + if (request.m_attributes) { JSValue attributesType {}; switch (request.m_attributes->type()) { @@ -170,23 +192,24 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) break; } - WTF::HashMap attributeMap { - { "type"_s, attributesTypeString }, - }; - - JSObject* attributesObject = constructEmptyObject(globalObject, globalObject->objectPrototype(), 1); + attributeMap.set("type"_s, WTFMove(attributesTypeString)); attributesObject->putDirect(vm, JSC::Identifier::fromString(vm, "type"_s), attributesType); + if (const String& hostDefinedImportType = request.m_attributes->hostDefinedImportType(); !hostDefinedImportType.isEmpty()) { attributesObject->putDirect(vm, hostDefinedImportTypeIdentifier, JSC::jsString(vm, hostDefinedImportType)); attributeMap.set("hostDefinedImportType"_s, hostDefinedImportType); } - requestObject->putDirect(vm, attributesIdentifier, attributesObject); - addModuleRequest({ WTF::String(*request.m_specifier), WTFMove(attributeMap) }); - } else { - addModuleRequest({ WTF::String(*request.m_specifier), {} }); - requestObject->putDirect(vm, attributesIdentifier, JSC::jsNull()); } + if (ImportAttributesListNode* attributesNode = attributesNodes.at(i)) { + for (auto [key, value] : attributesNode->attributes()) { + attributeMap.set(key->string(), value->string()); + attributesObject->putDirect(vm, *key, JSC::jsString(vm, value->string())); + } + } + + requestObject->putDirect(vm, attributesIdentifier, attributesObject); + addModuleRequest({ WTF::String(*request.m_specifier), WTFMove(attributeMap) }); requestsArray->putDirectIndex(globalObject, i, requestObject); } diff --git a/test/js/node/test/parallel/test-vm-module-link.js b/test/js/node/test/parallel/test-vm-module-link.js new file mode 100644 index 000000000000..26dcb69885ba --- /dev/null +++ b/test/js/node/test/parallel/test-vm-module-link.js @@ -0,0 +1,168 @@ +'use strict'; + +// Flags: --experimental-vm-modules --harmony-import-attributes + +const common = require('../common'); + +const assert = require('assert'); + +const { SourceTextModule } = require('vm'); + +async function simple() { + const foo = new SourceTextModule('export default 5;'); + await foo.link(common.mustNotCall()); + + globalThis.fiveResult = undefined; + const bar = new SourceTextModule('import five from "foo"; fiveResult = five'); + + assert.deepStrictEqual(bar.dependencySpecifiers, ['foo']); + + await bar.link(common.mustCall((specifier, module) => { + assert.strictEqual(module, bar); + assert.strictEqual(specifier, 'foo'); + return foo; + })); + + await bar.evaluate(); + assert.strictEqual(globalThis.fiveResult, 5); + delete globalThis.fiveResult; +} + +async function invalidLinkValue() { + const invalidValues = [ + undefined, + null, + {}, + SourceTextModule.prototype, + ]; + + for (const value of invalidValues) { + const module = new SourceTextModule('import "foo"'); + await assert.rejects(module.link(() => value), { + code: 'ERR_VM_MODULE_NOT_MODULE', + }); + } +} + +async function depth() { + const foo = new SourceTextModule('export default 5'); + await foo.link(common.mustNotCall()); + + async function getProxy(parentName, parentModule) { + const mod = new SourceTextModule(` + import ${parentName} from '${parentName}'; + export default ${parentName}; + `); + await mod.link(common.mustCall((specifier, module) => { + assert.strictEqual(module, mod); + assert.strictEqual(specifier, parentName); + return parentModule; + })); + return mod; + } + + const bar = await getProxy('foo', foo); + const baz = await getProxy('bar', bar); + const barz = await getProxy('baz', baz); + + await barz.evaluate(); + + assert.strictEqual(barz.namespace.default, 5); +} + +async function circular() { + const foo = new SourceTextModule(` + import getFoo from 'bar'; + export let foo = 42; + export default getFoo(); + `); + const bar = new SourceTextModule(` + import { foo } from 'foo'; + export default function getFoo() { + return foo; + } + `); + await foo.link(common.mustCall(async (specifier, module) => { + if (specifier === 'bar') { + assert.strictEqual(module, foo); + return bar; + } + assert.strictEqual(specifier, 'foo'); + assert.strictEqual(module, bar); + assert.strictEqual(foo.status, 'linking'); + return foo; + }, 2)); + + assert.strictEqual(bar.status, 'linked'); + + await foo.evaluate(); + assert.strictEqual(foo.namespace.default, 42); +} + +async function circular2() { + const sourceMap = { + 'root': ` + import * as a from './a.mjs'; + import * as b from './b.mjs'; + if (!('fromA' in a)) + throw new Error(); + if (!('fromB' in a)) + throw new Error(); + if (!('fromA' in b)) + throw new Error(); + if (!('fromB' in b)) + throw new Error(); + `, + './a.mjs': ` + export * from './b.mjs'; + export let fromA; + `, + './b.mjs': ` + export * from './a.mjs'; + export let fromB; + ` + }; + const moduleMap = new Map(); + const rootModule = new SourceTextModule(sourceMap.root, { + identifier: 'vm:root', + }); + async function link(specifier, referencingModule) { + if (moduleMap.has(specifier)) { + return moduleMap.get(specifier); + } + const mod = new SourceTextModule(sourceMap[specifier], { + identifier: new URL(specifier, 'file:///').href, + }); + moduleMap.set(specifier, mod); + return mod; + } + await rootModule.link(link); + await rootModule.evaluate(); +} + +async function asserts() { + const m = new SourceTextModule(` + import "foo" with { n1: 'v1', n2: 'v2' }; + `, { identifier: 'm' }); + await m.link((s, r, p) => { + assert.strictEqual(s, 'foo'); + assert.strictEqual(r.identifier, 'm'); + assert.strictEqual(p.attributes.n1, 'v1'); + assert.strictEqual(p.assert.n1, 'v1'); + assert.strictEqual(p.attributes.n2, 'v2'); + assert.strictEqual(p.assert.n2, 'v2'); + return new SourceTextModule(''); + }); +} + +const finished = common.mustCall(); + +(async function main() { + await simple(); + await invalidLinkValue(); + await depth(); + await circular(); + await circular2(); + await asserts(); + finished(); +})().then(common.mustCall()); From d6c1de3e1335b2740cfd2ed6eeda5a221059615d Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 14 May 2025 14:33:56 -0700 Subject: [PATCH 41/76] Fix vm.runInContext options --- src/js/node/vm.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index e8cf3abebc24..33decc7e8bf4 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -50,6 +50,10 @@ const { } = vm; function runInContext(code, context, options) { + validateContext(context); + if (typeof options === "string") { + options = { filename: options }; + } return new Script(code, options).runInContext(context, options); } @@ -61,6 +65,12 @@ function measureMemory() { throwNotImplemented("node:vm measureMemory"); } +function validateContext(contextifiedObject) { + if (!isContext(contextifiedObject)) { + throw $ERR_INVALID_ARG_TYPE("contextifiedObject", "vm.Context", contextifiedObject); + } +} + let globalModuleId = 0; const defaultModuleName = "vm:module"; From b72cf78067a30fd13ca64e6685b9701ff80e5fb5 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 14 May 2025 14:37:26 -0700 Subject: [PATCH 42/76] Match Node's weird error message --- src/js/node/vm.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 33decc7e8bf4..02d30c127cda 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -67,7 +67,10 @@ function measureMemory() { function validateContext(contextifiedObject) { if (!isContext(contextifiedObject)) { - throw $ERR_INVALID_ARG_TYPE("contextifiedObject", "vm.Context", contextifiedObject); + const error = new Error('The "contextifiedObject" argument must be an vm.Context'); + error.code = "ERR_INVALID_ARG_TYPE"; + error.name = "TypeError"; + throw error; } } From be5e817a2cf116ae4a674a5cdb653bf067d7fa0c Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 14 May 2025 15:53:41 -0700 Subject: [PATCH 43/76] vm.compileFunction: handle parser errors better --- src/bun.js/bindings/NodeVM.cpp | 46 ++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 9791c1583c89..c4a12f035bad 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -80,18 +80,55 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c VM& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); + LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; + + // First try parsing the code as is without wrapping it in an anonymous function expression. + // This is to reject cases where the user passes in a string like "});(function() {". + if (!args.isEmpty() && args.at(0).isString()) { + ParserError error; + String code = args.at(0).toWTFString(globalObject); + + SourceCode sourceCode( + JSC::StringSourceProvider::create(code, sourceOrigin, fileName, sourceTaintOrigin, position, SourceProviderSourceType::Program), + position.m_line.oneBasedInt(), position.m_column.oneBasedInt()); + + if (!checkSyntax(vm, sourceCode, error)) { + ASSERT(error.isValid()); + + bool actuallyValid = true; + + if (error.type() == ParserError::ErrorType::SyntaxError && error.syntaxErrorType() == ParserError::SyntaxErrorIrrecoverable) { + String message = error.message(); + if (message == "Return statements are only valid inside functions.") { + actuallyValid = false; + } else { + const JSToken& token = error.token(); + int start = token.m_startPosition.offset; + int end = token.m_endPosition.offset; + if (start >= 0 && start < end) { + StringView tokenView = sourceCode.view().substring(start, end - start); + error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, makeString("Unexpected token '"_s, tokenView, '\''), error.line()); + } + } + } + + if (actuallyValid) { + auto exception = error.toErrorObject(globalObject, sourceCode, -1); + throwException(globalObject, throwScope, exception); + return nullptr; + } + } + } + // wrap the arguments in an anonymous function expression int startOffset = 0; String code = stringifyAnonymousFunction(globalObject, args, throwScope, &startOffset); EXCEPTION_ASSERT(!!throwScope.exception() == code.isNull()); - position.m_column = OrdinalNumber::fromZeroBasedInt(position.m_column.zeroBasedInt()); SourceCode sourceCode( JSC::StringSourceProvider::create(code, sourceOrigin, fileName, sourceTaintOrigin, position, SourceProviderSourceType::Program), position.m_line.oneBasedInt(), position.m_column.oneBasedInt()); - LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; - ParserError error; bool isEvalNode = false; @@ -125,8 +162,7 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c // the code we passed in should be a single expression statement containing a function expression StatementNode* statement = program->singleStatement(); if (!statement || !statement->isExprStatement()) { - JSToken token; - error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, "Parser error"_s, -1); + error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, JSToken {}, "Parser error"_s, -1); auto exception = error.toErrorObject(globalObject, sourceCode, -1); throwException(globalObject, throwScope, exception); return nullptr; From 6fb80d297c85e26f98dbbcf4e2bc5cd416c71826 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 14 May 2025 19:16:19 -0700 Subject: [PATCH 44/76] Support produceCachedData option in vm.compileFunction --- src/bun.js/bindings/NodeVM.cpp | 52 +++++++++++++++++++++++++++------- src/bun.js/bindings/NodeVM.h | 4 ++- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index c4a12f035bad..dd5d2b6a4032 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -47,6 +47,7 @@ #include "JavaScriptCore/JSCInlines.h" #include "JavaScriptCore/CodeCache.h" #include "JavaScriptCore/BytecodeCacheError.h" +#include "JavaScriptCore/FunctionCodeBlock.h" #include "wtf/FileHandle.h" #include "../vm/SigintWatcher.h" @@ -177,8 +178,9 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c FunctionMetadataNode* metadata = static_cast(expression)->metadata(); ASSERT(metadata); - if (!metadata) + if (!metadata) { return nullptr; + } // metadata->setStartOffset(startOffset); @@ -263,7 +265,7 @@ String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& a RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); JSC::CodeCache* cache = vm.codeCache(); JSC::ParserError parserError; JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); @@ -276,9 +278,26 @@ RefPtr getBytecode(JSGlobalObject* globalObject, JSC::Progr return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); } +std::optional> getBytecode(JSGlobalObject* globalObject, JSC::JSFunction* function, CodeBlock*& codeBlock) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + FunctionExecutable* executable = function->jsExecutable(); + executable->prepareForExecution(vm, function, function->scope(), CodeSpecializationKind::CodeForCall, codeBlock); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + if (!codeBlock) { + return std::nullopt; + } + + const JSInstructionStream& instructionStream = codeBlock->instructions(); + return std::span { reinterpret_cast(instructionStream.rawPointer()), instructionStream.sizeInBytes() }; +} + JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); @@ -292,7 +311,7 @@ JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::So } std::span bytes = bytecode->span(); - auto* buffer = WebCore::createBuffer(globalObject, bytes); + JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, bytes); RETURN_IF_EXCEPTION(scope, {}); ASSERT(buffer); @@ -523,7 +542,7 @@ bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, Propert auto* sandbox = thisObject->m_sandbox.get(); - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); JSValue thisValue = slot.thisValue(); bool isContextualStore = thisValue != JSValue(globalObject); if (auto* proxy = jsDynamicCast(thisValue); proxy && proxy->target() == globalObject) { @@ -662,7 +681,7 @@ bool NodeVMGlobalObject::defineOwnProperty(JSObject* cell, JSGlobalObject* globa } auto* contextifiedObject = thisObject->m_sandbox.get(); - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); PropertySlot slot(globalObject, PropertySlot::InternalMethodType::GetOwnProperty, nullptr); @@ -772,7 +791,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject JSC_DEFINE_HOST_FUNCTION(vmModuleRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto sourceStringValue = callFrame->argument(0); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -867,7 +886,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject SourceOrigin sourceOrigin = JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)); // Process contextExtensions if they exist - JSScope* functionScope = !!options.parsingContext ? options.parsingContext : globalObject; + JSScope* functionScope = options.parsingContext ? options.parsingContext : globalObject; if (!options.contextExtensions.isUndefinedOrNull() && !options.contextExtensions.isEmpty() && options.contextExtensions.isObject() && isArray(globalObject, options.contextExtensions)) { auto* contextExtensionsArray = jsCast(options.contextExtensions); @@ -896,10 +915,23 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject // Create the function using constructAnonymousFunction with the appropriate scope chain JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset), functionScope); + if (options.produceCachedData) { + CodeBlock* codeBlock = nullptr; + std::optional> bytecode = getBytecode(globalObject, function, codeBlock); + if (bytecode) { + JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, *bytecode); + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedData"_s), buffer); + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(true)); + } else { + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(false)); + } + } + RETURN_IF_EXCEPTION(scope, {}); - if (!function) + if (!function) { return throwVMError(globalObject, scope, "Failed to compile function"_s); + } return JSValue::encode(function); } @@ -1016,7 +1048,7 @@ bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObje return Base::deleteProperty(cell, globalObject, propertyName, slot); } - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* sandbox = thisObject->m_sandbox.get(); diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 869ba031b65c..2a764c802d3c 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -20,6 +20,8 @@ class NodeVMContextOptions; namespace NodeVM { RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source); +// The lifetime of the return value is tied to the lifetime of the pointer in `codeBlock`. That's why the caller has to pass it by reference. +std::optional> getBytecode(JSGlobalObject* globalObject, JSC::JSFunction* function, CodeBlock*& codeBlock); 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); @@ -30,7 +32,7 @@ NodeVMGlobalObject* getGlobalObjectFromContext(JSGlobalObject* globalObject, JSV 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/inspired from JSC::constructFunction, which is used for function declarations. +/// 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, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr); } // namespace NodeVM From 4e5cd8f04287ea954ddd195198ab93153a16389f Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 14:44:30 -0700 Subject: [PATCH 45/76] Don't support produceCachedData option in vm.compileFunction (oops) --- src/bun.js/bindings/NodeVM.cpp | 95 +++++++++++++++++++++++----------- src/bun.js/bindings/NodeVM.h | 3 +- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index dd5d2b6a4032..fa102e548c35 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -45,9 +45,10 @@ #include "NodeValidator.h" #include "JavaScriptCore/JSCInlines.h" -#include "JavaScriptCore/CodeCache.h" #include "JavaScriptCore/BytecodeCacheError.h" +#include "JavaScriptCore/CodeCache.h" #include "JavaScriptCore/FunctionCodeBlock.h" +#include "JavaScriptCore/JIT.h" #include "wtf/FileHandle.h" #include "../vm/SigintWatcher.h" @@ -76,11 +77,14 @@ bool extractCachedData(JSValue cachedDataValue, WTF::Vector& outCachedD return false; } -JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName, JSC::SourceTaintedOrigin sourceTaintOrigin, TextPosition position, JSC::JSScope* scope) +JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, CompileFunctionOptions&& options, JSC::SourceTaintedOrigin sourceTaintOrigin, JSC::JSScope* scope) { + ASSERT(scope); + VM& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); + TextPosition position(options.lineOffset, options.columnOffset); LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; // First try parsing the code as is without wrapping it in an anonymous function expression. @@ -90,7 +94,7 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c String code = args.at(0).toWTFString(globalObject); SourceCode sourceCode( - JSC::StringSourceProvider::create(code, sourceOrigin, fileName, sourceTaintOrigin, position, SourceProviderSourceType::Program), + JSC::StringSourceProvider::create(code, sourceOrigin, options.filename, sourceTaintOrigin, position, SourceProviderSourceType::Program), position.m_line.oneBasedInt(), position.m_column.oneBasedInt()); if (!checkSyntax(vm, sourceCode, error)) { @@ -127,7 +131,7 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c EXCEPTION_ASSERT(!!throwScope.exception() == code.isNull()); SourceCode sourceCode( - JSC::StringSourceProvider::create(code, sourceOrigin, fileName, sourceTaintOrigin, position, SourceProviderSourceType::Program), + JSC::StringSourceProvider::create(code, sourceOrigin, WTFMove(options.filename), sourceTaintOrigin, position, SourceProviderSourceType::Program), position.m_line.oneBasedInt(), position.m_column.oneBasedInt()); ParserError error; @@ -205,10 +209,49 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c FunctionExecutable* functionExecutable = unlinkedFunctionExecutable->link(vm, nullptr, sourceCode, std::nullopt); JSScope* functionScope = scope ? scope : globalObject->globalScope(); - Structure* structure = JSFunction::selectStructureForNewFuncExp(globalObject, functionExecutable); + TriState bytecodeAccepted = TriState::Indeterminate; + + if (false && !options.cachedData.isEmpty()) { + Ref bytecode = CachedBytecode::create(std::span(options.cachedData), nullptr, {}); + SourceCodeKey key(sourceCode, {}, JSC::SourceCodeType::FunctionType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); + JSC::UnlinkedFunctionCodeBlock* unlinked = JSC::decodeCodeBlock(vm, key, WTFMove(bytecode)); + if (!unlinked) { + bytecodeAccepted = TriState::False; + } else { + CodeBlock* linked = nullptr; + { + // JSC::FunctionCodeBlock::create() requires GC to be deferred. + DeferGC deferGC(vm); + linked = JSC::FunctionCodeBlock::create(vm, functionExecutable, unlinked, scope); + } + JSC::CompilationResult compilationResult = JIT::compileSync(vm, linked, JITCompilationEffort::JITCompilationCanFail); + if (compilationResult != JSC::CompilationResult::CompilationFailed) { + functionExecutable->installCode(linked); + bytecodeAccepted = TriState::True; + } else { + bytecodeAccepted = TriState::False; + } + } + } + JSFunction* function = JSFunction::create(vm, globalObject, functionExecutable, functionScope, structure); + + if (bytecodeAccepted != TriState::Indeterminate) { + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataRejected"_s), jsBoolean(bytecodeAccepted == TriState::False)); + } else if (false && options.produceCachedData) { + CodeBlock* codeBlock = nullptr; + std::optional> bytecode = getBytecode(globalObject, function, codeBlock); + if (bytecode) { + JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, *bytecode); + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedData"_s), buffer); + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(true)); + } else { + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(false)); + } + } + return function; } @@ -278,21 +321,27 @@ RefPtr getBytecode(JSGlobalObject* globalObject, JSC::Progr return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); } -std::optional> getBytecode(JSGlobalObject* globalObject, JSC::JSFunction* function, CodeBlock*& codeBlock) +std::optional> getBytecode(JSGlobalObject* globalObject, JSC::JSFunction* function, JSC::CodeBlock*& codeBlock) { - VM& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); + // TODO(@heimskr): JSC bytecode for functions is just raw memory, with pointers to limited-lifetime data and everything. + // If it's not properly in the CodeCache, it can easily be UAF'd without warning. + // There doesn't appear to be any way to cache a function's bytecode. + // A workaround might be to compile an entire program containing just the function and then return the bytecode from that program. + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("getBytecode for functions is not implemented"); + return std::nullopt; + // VM& vm = JSC::getVM(globalObject); + // auto scope = DECLARE_THROW_SCOPE(vm); - FunctionExecutable* executable = function->jsExecutable(); - executable->prepareForExecution(vm, function, function->scope(), CodeSpecializationKind::CodeForCall, codeBlock); - RETURN_IF_EXCEPTION(scope, std::nullopt); + // FunctionExecutable* executable = function->jsExecutable(); + // executable->prepareForExecution(vm, function, function->scope(), CodeSpecializationKind::CodeForCall, codeBlock); + // RETURN_IF_EXCEPTION(scope, std::nullopt); - if (!codeBlock) { - return std::nullopt; - } + // if (!codeBlock) { + // return std::nullopt; + // } - const JSInstructionStream& instructionStream = codeBlock->instructions(); - return std::span { reinterpret_cast(instructionStream.rawPointer()), instructionStream.sizeInBytes() }; + // const JSInstructionStream& instructionStream = codeBlock->instructions(); + // return std::span { reinterpret_cast(instructionStream.rawPointer()), instructionStream.sizeInBytes() }; } JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) @@ -913,19 +962,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject options.parsingContext->setGlobalScopeExtension(functionScope); // Create the function using constructAnonymousFunction with the appropriate scope chain - JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset), functionScope); - - if (options.produceCachedData) { - CodeBlock* codeBlock = nullptr; - std::optional> bytecode = getBytecode(globalObject, function, codeBlock); - if (bytecode) { - JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, *bytecode); - function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedData"_s), buffer); - function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(true)); - } else { - function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(false)); - } - } + JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, WTFMove(options), JSC::SourceTaintedOrigin::Untainted, functionScope); RETURN_IF_EXCEPTION(scope, {}); diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 2a764c802d3c..9d61d6ff4f1a 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -16,6 +16,7 @@ namespace Bun { class NodeVMGlobalObject; class NodeVMContextOptions; +class CompileFunctionOptions; namespace NodeVM { @@ -33,7 +34,7 @@ JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, /// 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, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr); +JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, CompileFunctionOptions&& options, JSC::SourceTaintedOrigin sourceTaintOrigin, JSC::JSScope* scope); } // namespace NodeVM From a0ba651846a7777501b6b9e44f655f3be54afdbb Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 15:31:16 -0700 Subject: [PATCH 46/76] it's a miracle --- src/bun.js/bindings/NodeVM.cpp | 160 +++------ src/bun.js/bindings/NodeVM.h | 6 +- test/js/node/test/parallel/test-vm-basic.js | 350 ++++++++++++++++++++ 3 files changed, 392 insertions(+), 124 deletions(-) create mode 100644 test/js/node/test/parallel/test-vm-basic.js diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index fa102e548c35..d42bc1fe048d 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -48,6 +48,7 @@ #include "JavaScriptCore/BytecodeCacheError.h" #include "JavaScriptCore/CodeCache.h" #include "JavaScriptCore/FunctionCodeBlock.h" +#include "JavaScriptCore/ProgramCodeBlock.h" #include "JavaScriptCore/JIT.h" #include "wtf/FileHandle.h" @@ -134,122 +135,66 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c JSC::StringSourceProvider::create(code, sourceOrigin, WTFMove(options.filename), sourceTaintOrigin, position, SourceProviderSourceType::Program), position.m_line.oneBasedInt(), position.m_column.oneBasedInt()); - ParserError error; - bool isEvalNode = false; + CodeCache* cache = vm.codeCache(); + ProgramExecutable* programExecutable = ProgramExecutable::create(globalObject, sourceCode); - // use default name - Identifier name; - std::unique_ptr program; + UnlinkedProgramCodeBlock* unlinkedProgramCodeBlock = nullptr; + RefPtr cachedBytecode; - if (code.is8Bit()) { - Parser> parser(vm, sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, - lexicallyScopedFeatures, JSParserScriptMode::Classic, SourceParseMode::ProgramMode, - FunctionMode::None, SuperBinding::NotNeeded, ConstructorKind::None, DerivedContextType::None, - isEvalNode, EvalContextType::None, nullptr); + TriState bytecodeAccepted = TriState::Indeterminate; - program = parser.parse(error, name, ParsingContext::Normal); - } else { - Parser> parser(vm, sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, - lexicallyScopedFeatures, JSParserScriptMode::Classic, SourceParseMode::ProgramMode, - FunctionMode::None, SuperBinding::NotNeeded, ConstructorKind::None, DerivedContextType::None, - isEvalNode, EvalContextType::None, nullptr); + if (!options.cachedData.isEmpty()) { + cachedBytecode = CachedBytecode::create(std::span(options.cachedData), nullptr, {}); + SourceCodeKey key(sourceCode, {}, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); + unlinkedProgramCodeBlock = JSC::decodeCodeBlock(vm, key, *cachedBytecode); + if (unlinkedProgramCodeBlock == nullptr) { + bytecodeAccepted = TriState::False; + } else { + bytecodeAccepted = TriState::True; + } + } + + ParserError error; - program = parser.parse(error, name, ParsingContext::Normal); + if (unlinkedProgramCodeBlock == nullptr) { + unlinkedProgramCodeBlock = cache->getUnlinkedProgramCodeBlock(vm, programExecutable, sourceCode, {}, error); } - if (!program) { - RELEASE_ASSERT(error.isValid()); - auto exception = error.toErrorObject(globalObject, sourceCode, -1); - throwException(globalObject, throwScope, exception); + if (!unlinkedProgramCodeBlock || error.isValid()) { return nullptr; } - // the code we passed in should be a single expression statement containing a function expression - StatementNode* statement = program->singleStatement(); - if (!statement || !statement->isExprStatement()) { - error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, JSToken {}, "Parser error"_s, -1); - auto exception = error.toErrorObject(globalObject, sourceCode, -1); - throwException(globalObject, throwScope, exception); - return nullptr; + ProgramCodeBlock* programCodeBlock = nullptr; + { + DeferGC deferGC(vm); + programCodeBlock = ProgramCodeBlock::create(vm, programExecutable, unlinkedProgramCodeBlock, scope); } - ExprStatementNode* exprStatement = static_cast(statement); - ExpressionNode* expression = exprStatement->expr(); - if (!expression || !expression->isFuncExprNode()) { - throwSyntaxError(globalObject, throwScope, "Expected a function expression"_s); + if (!programCodeBlock || programCodeBlock->numberOfFunctionExprs() == 0) { return nullptr; } - FunctionMetadataNode* metadata = static_cast(expression)->metadata(); - ASSERT(metadata); - if (!metadata) { + FunctionExecutable* functionExecutable = programCodeBlock->functionExpr(0); + if (!functionExecutable) { return nullptr; } - // metadata->setStartOffset(startOffset); - - ConstructAbility constructAbility = constructAbilityForParseMode(metadata->parseMode()); - UnlinkedFunctionExecutable* unlinkedFunctionExecutable = UnlinkedFunctionExecutable::create( - vm, - sourceCode, - metadata, - UnlinkedNormalFunction, - constructAbility, - InlineAttribute::None, - JSParserScriptMode::Classic, - nullptr, - std::nullopt, - std::nullopt, - DerivedContextType::None, - NeedsClassFieldInitializer::No, - PrivateBrandRequirement::None); - - unlinkedFunctionExecutable->recordParse(program->features(), metadata->lexicallyScopedFeatures(), /* hasCapturedVariables */ false); - - FunctionExecutable* functionExecutable = unlinkedFunctionExecutable->link(vm, nullptr, sourceCode, std::nullopt); - - JSScope* functionScope = scope ? scope : globalObject->globalScope(); Structure* structure = JSFunction::selectStructureForNewFuncExp(globalObject, functionExecutable); - - TriState bytecodeAccepted = TriState::Indeterminate; - - if (false && !options.cachedData.isEmpty()) { - Ref bytecode = CachedBytecode::create(std::span(options.cachedData), nullptr, {}); - SourceCodeKey key(sourceCode, {}, JSC::SourceCodeType::FunctionType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); - JSC::UnlinkedFunctionCodeBlock* unlinked = JSC::decodeCodeBlock(vm, key, WTFMove(bytecode)); - if (!unlinked) { - bytecodeAccepted = TriState::False; - } else { - CodeBlock* linked = nullptr; - { - // JSC::FunctionCodeBlock::create() requires GC to be deferred. - DeferGC deferGC(vm); - linked = JSC::FunctionCodeBlock::create(vm, functionExecutable, unlinked, scope); - } - JSC::CompilationResult compilationResult = JIT::compileSync(vm, linked, JITCompilationEffort::JITCompilationCanFail); - if (compilationResult != JSC::CompilationResult::CompilationFailed) { - functionExecutable->installCode(linked); - bytecodeAccepted = TriState::True; + JSFunction* function = JSFunction::create(vm, globalObject, functionExecutable, scope, structure); + + if (bytecodeAccepted == TriState::Indeterminate) { + if (options.produceCachedData) { + RefPtr producedBytecode = getBytecode(globalObject, programExecutable, sourceCode); + if (producedBytecode) { + JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, producedBytecode->span()); + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedData"_s), buffer); + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(true)); } else { - bytecodeAccepted = TriState::False; + function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(false)); } } - } - - JSFunction* function = JSFunction::create(vm, globalObject, functionExecutable, functionScope, structure); - - if (bytecodeAccepted != TriState::Indeterminate) { + } else { function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataRejected"_s), jsBoolean(bytecodeAccepted == TriState::False)); - } else if (false && options.produceCachedData) { - CodeBlock* codeBlock = nullptr; - std::optional> bytecode = getBytecode(globalObject, function, codeBlock); - if (bytecode) { - JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, *bytecode); - function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedData"_s), buffer); - function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(true)); - } else { - function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(false)); - } } return function; @@ -263,7 +208,6 @@ String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& a if (args.isEmpty()) { // No arguments, just an empty function body program = "(function () {\n\n})"_s; - // program = "(function () {})"_s; } else if (args.size() == 1) { // Just the function body auto body = args.at(0).toWTFString(globalObject); @@ -285,8 +229,9 @@ String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& a auto param = args.at(i).toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); - if (i > 0) + if (i > 0) { paramString.append(", "_s); + } paramString.append(param); } @@ -321,29 +266,6 @@ RefPtr getBytecode(JSGlobalObject* globalObject, JSC::Progr return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); } -std::optional> getBytecode(JSGlobalObject* globalObject, JSC::JSFunction* function, JSC::CodeBlock*& codeBlock) -{ - // TODO(@heimskr): JSC bytecode for functions is just raw memory, with pointers to limited-lifetime data and everything. - // If it's not properly in the CodeCache, it can easily be UAF'd without warning. - // There doesn't appear to be any way to cache a function's bytecode. - // A workaround might be to compile an entire program containing just the function and then return the bytecode from that program. - RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("getBytecode for functions is not implemented"); - return std::nullopt; - // VM& vm = JSC::getVM(globalObject); - // auto scope = DECLARE_THROW_SCOPE(vm); - - // FunctionExecutable* executable = function->jsExecutable(); - // executable->prepareForExecution(vm, function, function->scope(), CodeSpecializationKind::CodeForCall, codeBlock); - // RETURN_IF_EXCEPTION(scope, std::nullopt); - - // if (!codeBlock) { - // return std::nullopt; - // } - - // const JSInstructionStream& instructionStream = codeBlock->instructions(); - // return std::span { reinterpret_cast(instructionStream.rawPointer()), instructionStream.sizeInBytes() }; -} - JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) { VM& vm = JSC::getVM(globalObject); diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 9d61d6ff4f1a..3379dc873b11 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -21,8 +21,6 @@ class CompileFunctionOptions; namespace NodeVM { RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source); -// The lifetime of the return value is tied to the lifetime of the pointer in `codeBlock`. That's why the caller has to pass it by reference. -std::optional> getBytecode(JSGlobalObject* globalObject, JSC::JSFunction* function, CodeBlock*& codeBlock); 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); @@ -31,9 +29,7 @@ bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey); 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. +// 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); } // namespace NodeVM diff --git a/test/js/node/test/parallel/test-vm-basic.js b/test/js/node/test/parallel/test-vm-basic.js new file mode 100644 index 000000000000..f743d5317052 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-basic.js @@ -0,0 +1,350 @@ +// 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'; +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +// vm.runInNewContext +{ + const sandbox = {}; + const result = vm.runInNewContext( + 'foo = "bar"; this.typeofProcess = typeof process; typeof Object;', + sandbox + ); + assert.deepStrictEqual(sandbox, { + foo: 'bar', + typeofProcess: 'undefined', + }); + assert.strictEqual(result, 'function'); +} + +// vm.runInContext +{ + const sandbox = { foo: 'bar' }; + const context = vm.createContext(sandbox); + const result = vm.runInContext( + 'baz = foo; this.typeofProcess = typeof process; typeof Object;', + context + ); + assert.deepStrictEqual(sandbox, { + foo: 'bar', + baz: 'bar', + typeofProcess: 'undefined' + }); + assert.strictEqual(result, 'function'); +} + +// vm.runInThisContext +{ + const result = vm.runInThisContext( + 'vmResult = "foo"; Object.prototype.toString.call(process);' + ); + assert.strictEqual(global.vmResult, 'foo'); + assert.strictEqual(result, '[object process]'); + delete global.vmResult; +} + +// vm.runInNewContext +{ + const result = vm.runInNewContext( + 'vmResult = "foo"; typeof process;' + ); + assert.strictEqual(global.vmResult, undefined); + assert.strictEqual(result, 'undefined'); +} + +// vm.createContext +{ + const sandbox = {}; + const context = vm.createContext(sandbox); + assert.strictEqual(sandbox, context); +} + +// Run script with filename +{ + const script = 'throw new Error("boom")'; + const filename = 'test-boom-error'; + const context = vm.createContext(); + + function checkErr(err) { + return err.stack.startsWith('test-boom-error:1'); + } + + assert.throws(() => vm.runInContext(script, context, filename), checkErr); + assert.throws(() => vm.runInNewContext(script, context, filename), checkErr); + assert.throws(() => vm.runInThisContext(script, filename), checkErr); +} + +// Invalid arguments +[null, 'string'].forEach((input) => { + assert.throws(() => { + vm.createContext({}, input); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options" argument must be of type object.' + + common.invalidArgTypeHelper(input) + }); +}); + +['name', 'origin'].forEach((propertyName) => { + assert.throws(() => { + vm.createContext({}, { [propertyName]: null }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: `The "options.${propertyName}" property must be of type string. ` + + 'Received null' + }); +}); + +['contextName', 'contextOrigin'].forEach((propertyName) => { + assert.throws(() => { + vm.runInNewContext('', {}, { [propertyName]: null }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: `The "options.${propertyName}" property must be of type string. ` + + 'Received null' + }); +}); + +// vm.compileFunction +{ + assert.strictEqual( + vm.compileFunction('console.log("Hello, World!")').toString(), + 'function () {\nconsole.log("Hello, World!")\n}' + ); + + assert.strictEqual( + vm.compileFunction( + 'return p + q + r + s + t', + ['p', 'q', 'r', 's', 't'] + )('ab', 'cd', 'ef', 'gh', 'ij'), + 'abcdefghij' + ); + + vm.compileFunction('return'); // Should not throw on 'return' + + assert.throws(() => { + vm.compileFunction( + '});\n\n(function() {\nconsole.log(1);\n})();\n\n(function() {' + ); + }, { + name: 'SyntaxError', + message: "Unexpected token '}'" + }); + + // Tests for failed argument validation + assert.throws(() => vm.compileFunction(), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "code" argument must be of type string. ' + + 'Received undefined' + }); + + vm.compileFunction(''); // Should pass without params or options + + assert.throws(() => vm.compileFunction('', null), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "params" argument must be an instance of Array. ' + + 'Received null' + }); + + // vm.compileFunction('', undefined, null); + + const optionTypes = { + 'filename': 'string', + 'columnOffset': 'number', + 'lineOffset': 'number', + 'cachedData': 'Buffer, TypedArray, or DataView', + 'produceCachedData': 'boolean', + }; + + for (const option in optionTypes) { + const typeErrorMessage = `The "options.${option}" property must be ` + + (option === 'cachedData' ? 'an instance of' : 'of type'); + assert.throws(() => { + vm.compileFunction('', undefined, { [option]: null }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: typeErrorMessage + + ` ${optionTypes[option]}. Received null` + }); + } + + // Testing for context-based failures + [Boolean(), Number(), null, String(), Symbol(), {}].forEach( + (value) => { + assert.throws(() => { + vm.compileFunction('', undefined, { parsingContext: value }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options.parsingContext" property must be an instance ' + + `of Context.${common.invalidArgTypeHelper(value)}` + }); + } + ); + + // Testing for non Array type-based failures + [Boolean(), Number(), null, Object(), Symbol(), {}].forEach( + (value) => { + assert.throws(() => { + vm.compileFunction('', value); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "params" argument must be an instance of Array.' + + common.invalidArgTypeHelper(value) + }); + } + ); + + assert.strictEqual( + vm.compileFunction( + 'return a;', + undefined, + { contextExtensions: [{ a: 5 }] } + )(), + 5 + ); + + assert.throws(() => { + vm.compileFunction('', undefined, { contextExtensions: null }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options.contextExtensions" property must be an instance of' + + ' Array. Received null' + }); + + assert.throws(() => { + vm.compileFunction('', undefined, { contextExtensions: [0] }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options.contextExtensions[0]" property must be of type ' + + 'object. Received type number (0)' + }); + + const oldLimit = Error.stackTraceLimit; + // Setting value to run the last three tests + Error.stackTraceLimit = 1; + + assert.throws(() => { + vm.compileFunction('throw new Error("Sample Error")')(); + }, { + message: 'Sample Error', + stack: 'Error: Sample Error\n at (file:///:2:16)' // from :1:7 to match Bun's stack trace formatting + }); + + assert.throws(() => { + vm.compileFunction( + 'throw new Error("Sample Error")', + [], + { lineOffset: 3 } + )(); + }, { + message: 'Sample Error', + stack: 'Error: Sample Error\n at (evalmachine.:5:16)' // modified from :4:7 to match Bun's stack trace formatting + }); + + assert.throws(() => { + vm.compileFunction( + 'throw new Error("Sample Error")', + [], + { columnOffset: 3 } + )(); + }, { + message: 'Sample Error', + stack: 'Error: Sample Error\n at (evalmachine.:2:16)' // modified from :1:10 to match Bun's stack trace formatting + }); + + assert.strictEqual( + vm.compileFunction( + 'return varInContext', + [], + { + parsingContext: vm.createContext({ varInContext: 'abc' }) + } + )(), + 'abc' + ); + + assert.throws(() => { + vm.compileFunction( + 'return varInContext', + [] + )(); + }, { + message: 'varInContext is not defined', + stack: 'ReferenceError: varInContext is not defined\n at (file:///:2:20)' // modified from :1:1 to match Bun's stack trace formatting + }); + + assert.notDeepStrictEqual( + vm.compileFunction( + 'return global', + [], + { + parsingContext: vm.createContext({ global: {} }) + } + )(), + global + ); + + assert.deepStrictEqual( + vm.compileFunction( + 'return global', + [] + )(), + global + ); + + { + const source = 'console.log("Hello, World!")'; + // Test compileFunction produceCachedData option + const result = vm.compileFunction(source, [], { + produceCachedData: true, + }); + + assert.ok(result.cachedDataProduced); + assert.ok(result.cachedData.length > 0); + + // Test compileFunction cachedData consumption + const result2 = vm.compileFunction(source, [], { + cachedData: result.cachedData + }); + assert.strictEqual(result2.cachedDataRejected, false); + + const result3 = vm.compileFunction('console.log("wrong source")', [], { + cachedData: result.cachedData + }); + assert.strictEqual(result3.cachedDataRejected, true); + } + + // Resetting value + Error.stackTraceLimit = oldLimit; +} From 305fccde9e246699b4eeac0b413a5e0c0da4c987 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 16:43:25 -0700 Subject: [PATCH 47/76] Support options.breakOnSigint in vm.Script.runInContext --- src/bun.js/bindings/NodeVMScript.cpp | 74 +++++++++++++------ src/bun.js/bindings/NodeVMScript.h | 4 +- .../bindings/NodeVMSourceTextModule.cpp | 17 ++--- src/bun.js/bindings/NodeVMSourceTextModule.h | 6 +- src/vm/SigintReceiver.h | 21 ++++++ src/vm/SigintWatcher.cpp | 33 ++++----- src/vm/SigintWatcher.h | 66 ++++++++++------- 7 files changed, 142 insertions(+), 79 deletions(-) create mode 100644 src/vm/SigintReceiver.h diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 21d10ebd39de..9f36d5f644f3 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -156,7 +156,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptConstructorConstruct, (JSGlobalObject * globalObj JSC::ProgramExecutable* NodeVMScript::createExecutable() { - auto& vm = JSC::getVM(globalObject()); + VM& vm = JSC::getVM(globalObject()); m_cachedExecutable.set(vm, this, JSC::ProgramExecutable::create(globalObject(), m_source)); return m_cachedExecutable.get(); } @@ -243,35 +243,67 @@ void NodeVMScript::destroy(JSCell* cell) static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false) { - - auto& vm = JSC::getVM(globalObject); - auto throwScope = DECLARE_THROW_SCOPE(vm); + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); RunningScriptOptions options; if (allowStringInPlaceOfOptions && optionsArg.isString()) { options.filename = optionsArg.toWTFString(globalObject); - RETURN_IF_EXCEPTION(throwScope, {}); - } else if (!options.fromJS(globalObject, vm, throwScope, optionsArg)) { - RETURN_IF_EXCEPTION(throwScope, {}); + RETURN_IF_EXCEPTION(scope, {}); + } else if (!options.fromJS(globalObject, vm, scope, optionsArg)) { + RETURN_IF_EXCEPTION(scope, {}); options = {}; } // Set the contextified object before evaluating globalObject->setContextifiedObject(contextifiedObject); + NakedPtr exception; + JSValue result {}; + auto run = [&] { + result = JSC::evaluate(globalObject, script->source(), globalObject, exception); + }; + + if (options.timeout) { + JSC::JSLockHolder locker(vm); + JSC::Watchdog& dog = vm.ensureWatchdog(); + dog.enteredVM(); + dog.setTimeLimit(WTF::Seconds::fromMilliseconds(*options.timeout)); + } + + script->setSigintReceived(false); + if (options.breakOnSigint) { - // TODO(@heimskr): set up a GlobalObjectHolder - SigintWatcher::get().registerGlobalObject(globalObject); + auto holder = SigintWatcher::hold(globalObject, script); + run(); + } else { + run(); } - NakedPtr exception; - JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); + if (options.timeout) { + vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit); + } + + if (vm.hasPendingTerminationException() || vm.hasTerminationRequest()) { + vm.clearHasTerminationRequest(); + if (script->getSigintReceived()) { + script->setSigintReceived(false); + throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); + } else if (options.timeout) { + throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, *options.timeout, "ms"_s)); + } else { + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("vm.Script terminated due neither to sigint nor to timeout"); + } + return {}; + } + + script->setSigintReceived(false); if (UNLIKELY(exception)) { - if (handleException(globalObject, vm, exception, throwScope)) { + if (handleException(globalObject, vm, exception, scope)) { return {}; } - JSC::throwException(globalObject, throwScope, exception.get()); + JSC::throwException(globalObject, scope, exception.get()); return {}; } @@ -280,7 +312,7 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); JSValue thisValue = callFrame->thisValue(); auto* script = jsDynamicCast(thisValue); auto throwScope = DECLARE_THROW_SCOPE(vm); @@ -327,7 +359,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); @@ -341,7 +373,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); @@ -358,7 +390,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JS JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); @@ -371,7 +403,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalOb JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); @@ -391,7 +423,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalOb JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, CallFrame* callFrame)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); @@ -406,7 +438,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); @@ -427,7 +459,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, Cal JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { - auto& vm = JSC::getVM(globalObject); + VM& vm = JSC::getVM(globalObject); NodeVMScript* script = jsDynamicCast(callFrame->thisValue()); JSValue contextObjectValue = callFrame->argument(0); // TODO: options diff --git a/src/bun.js/bindings/NodeVMScript.h b/src/bun.js/bindings/NodeVMScript.h index f3d907e83572..d0e49af125d3 100644 --- a/src/bun.js/bindings/NodeVMScript.h +++ b/src/bun.js/bindings/NodeVMScript.h @@ -2,6 +2,8 @@ #include "NodeVM.h" +#include "../vm/SigintReceiver.h" + namespace Bun { class ScriptOptions : public BaseVMOptions { @@ -36,7 +38,7 @@ class NodeVMScriptConstructor final : public JSC::InternalFunction { STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptConstructor, JSC::InternalFunction); -class NodeVMScript final : public JSC::JSDestructibleObject { +class NodeVMScript final : public JSC::JSDestructibleObject, public SigintReceiver { public: using Base = JSC::JSDestructibleObject; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 5927c4b35eec..32dae223fefc 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -310,7 +310,7 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast(JSGenerator::ResumeMode::NormalMode))); }; - m_terminatedWithSigint = false; + setSigintReceived(false); if (timeout != 0) { JSC::JSLockHolder locker(vm); @@ -326,12 +326,16 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t run(); } + if (timeout != 0) { + vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit); + } + if (vm.hasPendingTerminationException()) { scope.clearException(); vm.clearHasTerminationRequest(); status(Status::Errored); - if (m_terminatedWithSigint) { - m_terminatedWithSigint = false; + if (getSigintReceived()) { + setSigintReceived(false); throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); } else { throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, timeout, "ms"_s)); @@ -339,17 +343,12 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t return {}; } - m_terminatedWithSigint = false; + setSigintReceived(false); RETURN_IF_EXCEPTION(scope, (status(Status::Errored), JSValue {})); status(Status::Evaluated); return result; } -void NodeVMSourceTextModule::sigintReceived() -{ - m_terminatedWithSigint = true; -} - JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index 2c8cc2d52799..4b4a2bdf605e 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -3,9 +3,11 @@ #include "NodeVM.h" #include "NodeVMModule.h" +#include "../vm/SigintReceiver.h" + namespace Bun { -class NodeVMSourceTextModule final : public NodeVMModule { +class NodeVMSourceTextModule final : public NodeVMModule, public SigintReceiver { public: using Base = NodeVMModule; @@ -36,7 +38,6 @@ class NodeVMSourceTextModule final : public NodeVMModule { AbstractModuleRecord* moduleRecord(JSGlobalObject* globalObject); JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives, JSValue scriptFetcher); JSValue evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint); - void sigintReceived(); const SourceCode& sourceCode() const { return m_sourceCode; } @@ -47,7 +48,6 @@ class NodeVMSourceTextModule final : public NodeVMModule { WriteBarrier m_moduleRecord; WriteBarrier m_moduleRequestsArray; SourceCode m_sourceCode; - bool m_terminatedWithSigint = false; NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context, SourceCode sourceCode) : Base(vm, structure, WTFMove(identifier), context) diff --git a/src/vm/SigintReceiver.h b/src/vm/SigintReceiver.h new file mode 100644 index 000000000000..14062d2ecf22 --- /dev/null +++ b/src/vm/SigintReceiver.h @@ -0,0 +1,21 @@ +#pragma once + +namespace Bun { +class SigintReceiver { +public: + SigintReceiver() = default; + + void setSigintReceived(bool value = true) + { + m_sigintReceived = value; + } + + bool getSigintReceived() + { + return m_sigintReceived; + } + +protected: + bool m_sigintReceived = false; +}; +} diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 2b6d8cd76988..74df588f4037 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -1,5 +1,4 @@ #include "NodeVM.h" -#include "NodeVMSourceTextModule.h" #include "SigintWatcher.h" extern "C" void Bun__onPosixSignal(int signalNumber); @@ -90,7 +89,7 @@ void SigintWatcher::signalReceived() } } -void SigintWatcher::registerGlobalObject(NodeVMGlobalObject* globalObject) +void SigintWatcher::registerGlobalObject(JSGlobalObject* globalObject) { if (globalObject == nullptr) { return; @@ -100,7 +99,7 @@ void SigintWatcher::registerGlobalObject(NodeVMGlobalObject* globalObject) m_globalObjects.appendIfNotContains(globalObject); } -void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) +void SigintWatcher::unregisterGlobalObject(JSGlobalObject* globalObject) { if (globalObject == nullptr) { return; @@ -117,27 +116,27 @@ void SigintWatcher::unregisterGlobalObject(NodeVMGlobalObject* globalObject) m_globalObjects.removeLast(); } -void SigintWatcher::registerModule(NodeVMSourceTextModule* module) +void SigintWatcher::registerReceiver(SigintReceiver* module) { if (module == nullptr) { return; } - std::unique_lock lock(m_modulesMutex); - m_modules.appendIfNotContains(module); + std::unique_lock lock(m_receiversMutex); + m_receivers.appendIfNotContains(module); } -void SigintWatcher::unregisterModule(NodeVMSourceTextModule* module) +void SigintWatcher::unregisterReceiver(SigintReceiver* module) { - std::unique_lock lock(m_modulesMutex); + std::unique_lock lock(m_receiversMutex); - auto iter = std::find(m_modules.begin(), m_modules.end(), module); - if (iter == m_modules.end()) { + auto iter = std::find(m_receivers.begin(), m_receivers.end(), module); + if (iter == m_receivers.end()) { return; } - std::swap(*iter, m_modules.last()); - m_modules.removeLast(); + std::swap(*iter, m_receivers.last()); + m_receivers.removeLast(); } void SigintWatcher::ref() @@ -163,9 +162,9 @@ SigintWatcher& SigintWatcher::get() bool SigintWatcher::signalAll() { { - std::unique_lock lock(m_modulesMutex); - for (NodeVMSourceTextModule* module : m_modules) { - module->sigintReceived(); + std::unique_lock lock(m_receiversMutex); + for (auto* receiver : m_receivers) { + receiver->setSigintReceived(); } } @@ -175,8 +174,8 @@ bool SigintWatcher::signalAll() return false; } - for (NodeVMGlobalObject* globalObject : m_globalObjects) { - globalObject->sigintReceived(); + for (JSGlobalObject* globalObject : m_globalObjects) { + globalObject->vm().notifyNeedTermination(); } return true; diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index 6bbb79818744..22dbd9a7db73 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -2,6 +2,7 @@ #include "root.h" #include "Semaphore.h" +#include "SigintReceiver.h" #include #include @@ -10,8 +11,8 @@ namespace Bun { -class NodeVMGlobalObject; -class NodeVMSourceTextModule; +template +concept SigintHoldable = std::derived_from || std::derived_from; class SigintWatcher { public: @@ -21,10 +22,10 @@ class SigintWatcher { void install(); void uninstall(); void signalReceived(); - void registerGlobalObject(NodeVMGlobalObject* globalObject); - void unregisterGlobalObject(NodeVMGlobalObject* globalObject); - void registerModule(NodeVMSourceTextModule* module); - void unregisterModule(NodeVMSourceTextModule* module); + void registerGlobalObject(JSC::JSGlobalObject* globalObject); + void unregisterGlobalObject(JSC::JSGlobalObject* globalObject); + void registerReceiver(SigintReceiver* module); + void unregisterReceiver(SigintReceiver* module); /** Installs the signal handler if it's not already installed and increments the ref count. */ void ref(); /** Decrements the ref count and uninstalls the signal handler if the ref count reaches 0. */ @@ -34,24 +35,16 @@ class SigintWatcher { class GlobalObjectHolder { public: - GlobalObjectHolder(NodeVMGlobalObject* globalObject, NodeVMSourceTextModule* module) - : m_globalObject(globalObject) - , m_module(module) + template + ALWAYS_INLINE GlobalObjectHolder(Ts*... held) { - if (m_globalObject) { - get().ref(); - get().registerGlobalObject(globalObject); - } - - if (m_module) { - get().registerModule(m_module); - } + (assign(held), ...); } ~GlobalObjectHolder() { - if (m_module) { - get().unregisterModule(m_module); + for (auto* receiver : m_receivers) { + get().unregisterReceiver(receiver); } if (m_globalObject) { @@ -63,7 +56,7 @@ class SigintWatcher { GlobalObjectHolder(const GlobalObjectHolder&) = delete; GlobalObjectHolder(GlobalObjectHolder&& other) : m_globalObject(std::exchange(other.m_globalObject, nullptr)) - , m_module(std::exchange(other.m_module, nullptr)) + , m_receivers(WTFMove(other.m_receivers)) { } @@ -71,18 +64,35 @@ class SigintWatcher { GlobalObjectHolder& operator=(GlobalObjectHolder&& other) { m_globalObject = std::exchange(other.m_globalObject, nullptr); - m_module = std::exchange(other.m_module, nullptr); + m_receivers = WTFMove(other.m_receivers); return *this; } + void ALWAYS_INLINE assign(SigintHoldable auto* ptr) + { + using T = std::remove_pointer_t; + if constexpr (std::derived_from) { + if ((m_globalObject = ptr)) { + get().ref(); + get().registerGlobalObject(m_globalObject); + } + } else if constexpr (std::derived_from) { + m_receivers.append(ptr); + get().registerReceiver(ptr); + } else { + static_assert(false, "Invalid held type"); + } + } + private: - NodeVMGlobalObject* m_globalObject = nullptr; - NodeVMSourceTextModule* m_module = nullptr; + JSC::JSGlobalObject* m_globalObject = nullptr; + WTF::Vector m_receivers; }; - static GlobalObjectHolder hold(NodeVMGlobalObject* globalObject, NodeVMSourceTextModule* module) + template + ALWAYS_INLINE static GlobalObjectHolder hold(Ts*... held) { - return { globalObject, module }; + return { held... }; } private: @@ -91,9 +101,9 @@ class SigintWatcher { std::atomic_flag m_waiting = false; Semaphore m_semaphore; std::mutex m_globalObjectsMutex; - std::mutex m_modulesMutex; - WTF::Vector m_globalObjects; - WTF::Vector m_modules; + std::mutex m_receiversMutex; + WTF::Vector m_globalObjects; + WTF::Vector m_receivers; uint32_t m_refCount = 0; bool signalAll(); From 09648f39579fe549012dd74545bda8cc77a16a11 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 16:55:34 -0700 Subject: [PATCH 48/76] Support options.breakOnSigint in vm.Script.runInThisContext --- src/bun.js/bindings/NodeVMScript.cpp | 90 +++++++++++++++++++--------- src/vm/SigintReceiver.h | 4 +- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 9f36d5f644f3..464804692406 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -241,6 +241,34 @@ void NodeVMScript::destroy(JSCell* cell) static_cast(cell)->NodeVMScript::~NodeVMScript(); } +static bool checkForTermination(JSGlobalObject* globalObject, ThrowScope& scope, NodeVMScript* script, RunningScriptOptions& options) +{ + VM& vm = JSC::getVM(globalObject); + + if (vm.hasPendingTerminationException() || vm.hasTerminationRequest()) { + vm.clearHasTerminationRequest(); + if (script->getSigintReceived()) { + script->setSigintReceived(false); + throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); + } else if (options.timeout) { + throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, *options.timeout, "ms"_s)); + } else { + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("vm.Script terminated due neither to sigint nor to timeout"); + } + return true; + } + + return false; +} + +static void setupWatchdog(VM& vm, int64_t timeout) +{ + JSC::JSLockHolder locker(vm); + JSC::Watchdog& dog = vm.ensureWatchdog(); + dog.enteredVM(); + dog.setTimeLimit(WTF::Seconds::fromMilliseconds(timeout)); +} + static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false) { VM& vm = JSC::getVM(globalObject); @@ -265,10 +293,7 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM }; if (options.timeout) { - JSC::JSLockHolder locker(vm); - JSC::Watchdog& dog = vm.ensureWatchdog(); - dog.enteredVM(); - dog.setTimeLimit(WTF::Seconds::fromMilliseconds(*options.timeout)); + setupWatchdog(vm, *options.timeout); } script->setSigintReceived(false); @@ -284,16 +309,7 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit); } - if (vm.hasPendingTerminationException() || vm.hasTerminationRequest()) { - vm.clearHasTerminationRequest(); - if (script->getSigintReceived()) { - script->setSigintReceived(false); - throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); - } else if (options.timeout) { - throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, *options.timeout, "ms"_s)); - } else { - RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("vm.Script terminated due neither to sigint nor to timeout"); - } + if (checkForTermination(globalObject, scope, script, options)) { return {}; } @@ -315,10 +331,10 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, VM& vm = JSC::getVM(globalObject); JSValue thisValue = callFrame->thisValue(); auto* script = jsDynamicCast(thisValue); - auto throwScope = DECLARE_THROW_SCOPE(vm); + auto scope = DECLARE_THROW_SCOPE(vm); if (UNLIKELY(!script)) { - return ERR::INVALID_ARG_VALUE(throwScope, globalObject, "this"_s, thisValue, "must be a Script"_s); + return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } JSValue contextArg = callFrame->argument(0); @@ -327,33 +343,53 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, } if (!contextArg.isObject()) { - return ERR::INVALID_ARG_TYPE(throwScope, globalObject, "context"_s, "object"_s, contextArg); + return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); } - // TODO(@heimskr): try to retrieve the NodeVMGlobalObject from the context - RunningScriptOptions options; - if (!options.fromJS(globalObject, vm, throwScope, contextArg)) { - RETURN_IF_EXCEPTION(throwScope, {}); + if (!options.fromJS(globalObject, vm, scope, contextArg)) { + RETURN_IF_EXCEPTION(scope, {}); options = {}; } + NakedPtr exception; + JSValue result {}; + auto run = [&] { + result = JSC::evaluate(globalObject, script->source(), globalObject, exception); + }; + + if (options.timeout) { + setupWatchdog(vm, *options.timeout); + } + + script->setSigintReceived(false); + if (options.breakOnSigint) { - // TODO(@heimskr): register global object as appropriate + auto holder = SigintWatcher::hold(globalObject, script); + run(); + } else { + run(); } - NakedPtr exception; - JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); + if (options.timeout) { + vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit); + } + + if (checkForTermination(globalObject, scope, script, options)) { + return {}; + } + + script->setSigintReceived(false); if (UNLIKELY(exception)) { - if (handleException(globalObject, vm, exception, throwScope)) { + if (handleException(globalObject, vm, exception, scope)) { return {}; } - JSC::throwException(globalObject, throwScope, exception.get()); + JSC::throwException(globalObject, scope, exception.get()); return {}; } - RETURN_IF_EXCEPTION(throwScope, {}); + RETURN_IF_EXCEPTION(scope, {}); return JSValue::encode(result); } diff --git a/src/vm/SigintReceiver.h b/src/vm/SigintReceiver.h index 14062d2ecf22..fc66d43b3c58 100644 --- a/src/vm/SigintReceiver.h +++ b/src/vm/SigintReceiver.h @@ -1,6 +1,7 @@ #pragma once namespace Bun { + class SigintReceiver { public: SigintReceiver() = default; @@ -18,4 +19,5 @@ class SigintReceiver { protected: bool m_sigintReceived = false; }; -} + +} // namespace Bun From e68ada09b3ee98b7e622cf07504c34021c75bf38 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 18:06:36 -0700 Subject: [PATCH 49/76] test-vm-sigint.js --- src/bun.js/bindings/NodeVMScript.cpp | 24 ++++----- src/js/node/vm.ts | 9 +++- test/js/node/test/parallel/test-vm-sigint.js | 52 ++++++++++++++++++++ 3 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 test/js/node/test/parallel/test-vm-sigint.js diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 464804692406..52648bc4c408 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -245,7 +245,7 @@ static bool checkForTermination(JSGlobalObject* globalObject, ThrowScope& scope, { VM& vm = JSC::getVM(globalObject); - if (vm.hasPendingTerminationException() || vm.hasTerminationRequest()) { + if (vm.hasTerminationRequest()) { vm.clearHasTerminationRequest(); if (script->getSigintReceived()) { script->setSigintReceived(false); @@ -253,7 +253,7 @@ static bool checkForTermination(JSGlobalObject* globalObject, ThrowScope& scope, } else if (options.timeout) { throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, *options.timeout, "ms"_s)); } else { - RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("vm.Script terminated due neither to sigint nor to timeout"); + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("vm.Script terminated due neither to SIGINT nor to timeout"); } return true; } @@ -309,7 +309,7 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit); } - if (checkForTermination(globalObject, scope, script, options)) { + if (options.breakOnSigint && checkForTermination(globalObject, scope, script, options)) { return {}; } @@ -329,25 +329,18 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = JSC::getVM(globalObject); - JSValue thisValue = callFrame->thisValue(); - auto* script = jsDynamicCast(thisValue); auto scope = DECLARE_THROW_SCOPE(vm); + JSValue thisValue = callFrame->thisValue(); + auto* script = jsDynamicCast(thisValue); if (UNLIKELY(!script)) { return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } - JSValue contextArg = callFrame->argument(0); - if (contextArg.isUndefined()) { - contextArg = JSC::constructEmptyObject(globalObject); - } - - if (!contextArg.isObject()) { - return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); - } + JSValue optionsArg = callFrame->argument(0); RunningScriptOptions options; - if (!options.fromJS(globalObject, vm, scope, contextArg)) { + if (!options.fromJS(globalObject, vm, scope, optionsArg)) { RETURN_IF_EXCEPTION(scope, {}); options = {}; } @@ -366,6 +359,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, if (options.breakOnSigint) { auto holder = SigintWatcher::hold(globalObject, script); + vm.ensureTerminationException(); run(); } else { run(); @@ -375,7 +369,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit); } - if (checkForTermination(globalObject, scope, script, options)) { + if (options.breakOnSigint && checkForTermination(globalObject, scope, script, options)) { return {}; } diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 02d30c127cda..2024f24f4eba 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -40,7 +40,7 @@ const { createContext, isContext, runInNewContext, - runInThisContext, + runInThisContext: moduleRunInThisContext, compileFunction, isModuleNamespaceObject, kUnlinked, @@ -57,6 +57,13 @@ function runInContext(code, context, options) { return new Script(code, options).runInContext(context, options); } +function runInThisContext(code, options) { + if (typeof options === "string") { + options = { filename: options }; + } + return new Script(code, options).runInThisContext(options); +} + function createScript(code, options) { return new Script(code, options); } diff --git a/test/js/node/test/parallel/test-vm-sigint.js b/test/js/node/test/parallel/test-vm-sigint.js new file mode 100644 index 000000000000..66f22ba87895 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-sigint.js @@ -0,0 +1,52 @@ +'use strict'; +const common = require('../common'); +if (common.isWindows) { + // No way to send CTRL_C_EVENT to processes from JS right now. + common.skip('platform not supported'); +} + +const assert = require('assert'); +const vm = require('vm'); +const spawn = require('child_process').spawn; + +if (process.argv[2] === 'child') { + const method = process.argv[3]; + const listeners = +process.argv[4]; + assert.ok(method); + assert.ok(Number.isInteger(listeners)); + + const script = `process.send('${method}'); while(true) {}`; + const args = method === 'runInContext' ? + [vm.createContext({ process })] : + []; + const options = { breakOnSigint: true }; + + for (let i = 0; i < listeners; i++) + process.on('SIGINT', common.mustNotCall()); + + assert.throws( + () => { vm[method](script, ...args, options); }, + { + code: 'ERR_SCRIPT_EXECUTION_INTERRUPTED', + message: 'Script execution was interrupted by `SIGINT`' + }); + return; +} + +for (const method of ['runInThisContext', 'runInContext']) { + for (const listeners of [0, 1, 2]) { + const args = [__filename, 'child', method, listeners]; + const child = spawn(process.execPath, args, { + stdio: [null, 'pipe', 'inherit', 'ipc'] + }); + + child.on('message', common.mustCall(() => { + process.kill(child.pid, 'SIGINT'); + })); + + child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(signal, null); + assert.strictEqual(code, 0); + })); + } +} From 761b5aa639b3faf3c29d7668fa7167f4d130c8de Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 18:13:36 -0700 Subject: [PATCH 50/76] oops --- src/js/node/vm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 2024f24f4eba..9925a1617248 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -40,7 +40,7 @@ const { createContext, isContext, runInNewContext, - runInThisContext: moduleRunInThisContext, + // runInThisContext: moduleRunInThisContext, compileFunction, isModuleNamespaceObject, kUnlinked, From f2c1d226b854197e7c0ca32ceb84f998e8859974 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 20:24:47 -0700 Subject: [PATCH 51/76] test-vm-module-cached-data.js --- src/bun.js/bindings/NodeVM.cpp | 15 ++++ src/bun.js/bindings/NodeVM.h | 1 + src/bun.js/bindings/NodeVMModule.cpp | 11 ++- src/bun.js/bindings/NodeVMScript.h | 4 +- .../bindings/NodeVMSourceTextModule.cpp | 85 ++++++++++++++++--- src/bun.js/bindings/NodeVMSourceTextModule.h | 6 ++ .../parallel/test-vm-module-cached-data.js | 30 +++++++ 7 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 test/js/node/test/parallel/test-vm-module-cached-data.js diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index d42bc1fe048d..1e7b21e5cfb9 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -266,6 +266,21 @@ RefPtr getBytecode(JSGlobalObject* globalObject, JSC::Progr return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); } +RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ModuleProgramExecutable* executable, const JSC::SourceCode& source) +{ + VM& vm = JSC::getVM(globalObject); + JSC::CodeCache* cache = vm.codeCache(); + JSC::ParserError parserError; + JSC::UnlinkedModuleProgramCodeBlock* unlinked = cache->getUnlinkedModuleProgramCodeBlock(vm, executable, source, {}, parserError); + if (!unlinked || parserError.isValid()) { + return nullptr; + } + JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; + JSC::BytecodeCacheError bytecodeCacheError; + FileSystem::FileHandle fileHandle; + return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); +} + JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) { VM& vm = JSC::getVM(globalObject); diff --git a/src/bun.js/bindings/NodeVM.h b/src/bun.js/bindings/NodeVM.h index 3379dc873b11..13a913eafe74 100644 --- a/src/bun.js/bindings/NodeVM.h +++ b/src/bun.js/bindings/NodeVM.h @@ -21,6 +21,7 @@ class CompileFunctionOptions; namespace NodeVM { RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source); +RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ModuleProgramExecutable* executable, const JSC::SourceCode& source); 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); diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index 662c19205184..cd30a83b84d4 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -297,8 +297,15 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalO JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (auto* thisObject = jsDynamicCast(callFrame->thisValue())) { + return JSValue::encode(thisObject->cachedData(globalObject)); + } + + throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule"_s); + return {}; } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateModuleRecord, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) diff --git a/src/bun.js/bindings/NodeVMScript.h b/src/bun.js/bindings/NodeVMScript.h index d0e49af125d3..11e13ce844a3 100644 --- a/src/bun.js/bindings/NodeVMScript.h +++ b/src/bun.js/bindings/NodeVMScript.h @@ -83,8 +83,8 @@ class NodeVMScript final : public JSC::JSDestructibleObject, public SigintReceiv private: JSC::SourceCode m_source; RefPtr m_cachedBytecode; - mutable JSC::WriteBarrier m_cachedBytecodeBuffer; - mutable JSC::WriteBarrier m_cachedExecutable; + JSC::WriteBarrier m_cachedBytecodeBuffer; + JSC::WriteBarrier m_cachedExecutable; ScriptOptions m_options; bool m_cachedDataProduced = false; TriState m_cachedDataRejected = TriState::Indeterminate; diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 32dae223fefc..ffa1077827fb 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -2,14 +2,19 @@ #include "ErrorCode.h" #include "JSDOMExceptionHandling.h" -#include "JSModuleRecord.h" -#include "JSPromise.h" -#include "JSSourceCode.h" -#include "ModuleAnalyzer.h" -#include "Parser.h" -#include "Watchdog.h" + #include "wtf/Scope.h" +#include "JavaScriptCore/JIT.h" +#include "JavaScriptCore/JSModuleRecord.h" +#include "JavaScriptCore/JSPromise.h" +#include "JavaScriptCore/JSSourceCode.h" +#include "JavaScriptCore/ModuleAnalyzer.h" +#include "JavaScriptCore/ModuleProgramCodeBlock.h" +#include "JavaScriptCore/Parser.h" +#include "JavaScriptCore/SourceCodeKey.h" +#include "JavaScriptCore/Watchdog.h" + #include "../vm/SigintWatcher.h" namespace Bun { @@ -69,7 +74,37 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g auto* zigGlobalObject = defaultGlobalObject(globalObject); NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject), contextValue, WTFMove(sourceCode)); ptr->finishCreation(vm); - return ptr; + + if (cachedData.isEmpty()) { + return ptr; + } + + ModuleProgramExecutable* executable = ModuleProgramExecutable::create(globalObject, ptr->sourceCode()); + ptr->m_cachedExecutable.set(vm, ptr, executable); + 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, {}); + UnlinkedModuleProgramCodeBlock* unlinkedBlock = decodeCodeBlock(vm, key, WTFMove(cachedBytecode)); + + if (unlinkedBlock) { + JSScope* jsScope = globalObject->globalScope(); + CodeBlock* codeBlock = nullptr; + { + // JSC::ProgramCodeBlock::create() requires GC to be deferred. + DeferGC deferGC(vm); + codeBlock = ModuleProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); + } + if (codeBlock) { + CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); + if (compilationResult != CompilationResult::CompilationFailed) { + executable->installCode(codeBlock); + return ptr; + } + } + } + + throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_CACHED_DATA_REJECTED, "cachedData buffer was rejected"_s); + return nullptr; } void NodeVMSourceTextModule::destroy(JSCell* cell) @@ -122,15 +157,15 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) const auto& requests = moduleRecord->requestedModules(); if (requests.isEmpty()) { - return JSC::constructEmptyArray(globalObject, nullptr, 0); + return constructEmptyArray(globalObject, nullptr, 0); } - JSArray* requestsArray = JSC::constructEmptyArray(globalObject, nullptr, requests.size()); + JSArray* requestsArray = constructEmptyArray(globalObject, nullptr, requests.size()); const auto& builtinNames = WebCore::clientData(vm)->builtinNames(); - const JSC::Identifier& specifierIdentifier = builtinNames.specifierPublicName(); - const JSC::Identifier& attributesIdentifier = builtinNames.attributesPublicName(); - const JSC::Identifier& hostDefinedImportTypeIdentifier = builtinNames.hostDefinedImportTypePublicName(); + const Identifier& specifierIdentifier = builtinNames.specifierPublicName(); + const Identifier& attributesIdentifier = builtinNames.attributesPublicName(); + const Identifier& hostDefinedImportTypeIdentifier = builtinNames.hostDefinedImportTypePublicName(); WTF::Vector attributesNodes; attributesNodes.reserveInitialCapacity(requests.size()); @@ -349,6 +384,29 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t return result; } +RefPtr NodeVMSourceTextModule::bytecode(JSGlobalObject* globalObject) +{ + if (!m_bytecode) { + if (!m_cachedExecutable) { + m_cachedExecutable.set(globalObject->vm(), this, JSC::ModuleProgramExecutable::create(globalObject, m_sourceCode)); + } + m_bytecode = getBytecode(globalObject, m_cachedExecutable.get(), m_sourceCode); + } + + return m_bytecode; +} + +JSUint8Array* NodeVMSourceTextModule::cachedData(JSGlobalObject* globalObject) +{ + if (!m_cachedBytecodeBuffer) { + RefPtr cachedBytecode = bytecode(globalObject); + std::span bytes = cachedBytecode->span(); + m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject, bytes)); + } + + return m_cachedBytecodeBuffer.get(); +} + JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); @@ -362,6 +420,9 @@ void NodeVMSourceTextModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) Base::visitChildren(vmModule, visitor); visitor.append(vmModule->m_moduleRecord); + visitor.append(vmModule->m_moduleRequestsArray); + visitor.append(vmModule->m_cachedExecutable); + visitor.append(vmModule->m_cachedBytecodeBuffer); } DEFINE_VISIT_CHILDREN(NodeVMSourceTextModule); diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index 4b4a2bdf605e..41009923ed31 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -38,8 +38,11 @@ class NodeVMSourceTextModule final : public NodeVMModule, public SigintReceiver AbstractModuleRecord* moduleRecord(JSGlobalObject* globalObject); JSValue link(JSGlobalObject* globalObject, JSArray* specifiers, JSArray* moduleNatives, JSValue scriptFetcher); JSValue evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint); + RefPtr bytecode(JSGlobalObject* globalObject); + JSUint8Array* cachedData(JSGlobalObject* globalObject); const SourceCode& sourceCode() const { return m_sourceCode; } + ModuleProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; @@ -47,6 +50,9 @@ class NodeVMSourceTextModule final : public NodeVMModule, public SigintReceiver private: WriteBarrier m_moduleRecord; WriteBarrier m_moduleRequestsArray; + WriteBarrier m_cachedExecutable; + WriteBarrier m_cachedBytecodeBuffer; + RefPtr m_bytecode; SourceCode m_sourceCode; NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier, JSValue context, SourceCode sourceCode) diff --git a/test/js/node/test/parallel/test-vm-module-cached-data.js b/test/js/node/test/parallel/test-vm-module-cached-data.js new file mode 100644 index 000000000000..fef68a8c1890 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-module-cached-data.js @@ -0,0 +1,30 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +const common = require('../common'); + +const assert = require('assert'); +const { SourceTextModule } = require('vm'); + +{ + const m = new SourceTextModule('const a = 1'); + const cachedData = m.createCachedData(); + + new SourceTextModule('const a = 1', { cachedData }); + + assert.throws(() => { + new SourceTextModule('differentSource', { cachedData }); + }, { + code: 'ERR_VM_MODULE_CACHED_DATA_REJECTED', + }); +} + +assert.rejects(async () => { + const m = new SourceTextModule('const a = 1'); + await m.link(() => {}); + m.evaluate(); + m.createCachedData(); +}, { + code: 'ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA', +}).then(common.mustCall()); From 50a97286bb37972edf62f6640933ce2ecc72b90f Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 20:53:13 -0700 Subject: [PATCH 52/76] vm.Module.error --- src/bun.js/bindings/NodeVMModule.cpp | 16 ++++++++++++++-- src/bun.js/bindings/NodeVMSourceTextModule.cpp | 16 +++++++++++++--- src/bun.js/bindings/NodeVMSourceTextModule.h | 2 ++ src/js/node/vm.ts | 2 ++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index cd30a83b84d4..66ce7c94b20b 100644 --- a/src/bun.js/bindings/NodeVMModule.cpp +++ b/src/bun.js/bindings/NodeVMModule.cpp @@ -1,6 +1,7 @@ #include "NodeVMModule.h" #include "NodeVMSourceTextModule.h" +#include "ErrorCode.h" #include "JSDOMExceptionHandling.h" namespace Bun { @@ -206,8 +207,19 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * glob JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { - // auto* thisObject = jsCast(callFrame->thisValue()); - return JSC::encodedJSUndefined(); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (auto* thisObject = jsCast(callFrame->thisValue())) { + if (JSC::Exception* exception = thisObject->evaluationException()) { + return JSValue::encode(exception->value()); + } + throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_STATUS, "Module status must be errored"_s); + return {}; + } + + throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s); + return {}; } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index ffa1077827fb..417b297829be 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -274,6 +274,11 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + if (m_status != Status::Unlinked) { + throwError(globalObject, scope, ErrorCode::ERR_VM_MODULE_STATUS, "Module must be unlinked before linking"_s); + return {}; + } + JSModuleRecord* record = m_moduleRecord.get(); if (length != 0) { @@ -368,18 +373,22 @@ JSValue NodeVMSourceTextModule::evaluate(JSGlobalObject* globalObject, uint32_t if (vm.hasPendingTerminationException()) { scope.clearException(); vm.clearHasTerminationRequest(); - status(Status::Errored); if (getSigintReceived()) { setSigintReceived(false); throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); } else { throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, timeout, "ms"_s)); } + } else { + setSigintReceived(false); + } + + if (JSC::Exception* exception = scope.exception()) { + status(Status::Errored); + m_evaluationException.set(vm, this, exception); return {}; } - setSigintReceived(false); - RETURN_IF_EXCEPTION(scope, (status(Status::Errored), JSValue {})); status(Status::Evaluated); return result; } @@ -423,6 +432,7 @@ void NodeVMSourceTextModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(vmModule->m_moduleRequestsArray); visitor.append(vmModule->m_cachedExecutable); visitor.append(vmModule->m_cachedBytecodeBuffer); + visitor.append(vmModule->m_evaluationException); } DEFINE_VISIT_CHILDREN(NodeVMSourceTextModule); diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.h b/src/bun.js/bindings/NodeVMSourceTextModule.h index 41009923ed31..6a58f1123900 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -40,6 +40,7 @@ class NodeVMSourceTextModule final : public NodeVMModule, public SigintReceiver JSValue evaluate(JSGlobalObject* globalObject, uint32_t timeout, bool breakOnSigint); RefPtr bytecode(JSGlobalObject* globalObject); JSUint8Array* cachedData(JSGlobalObject* globalObject); + Exception* evaluationException() const { return m_evaluationException.get(); } const SourceCode& sourceCode() const { return m_sourceCode; } ModuleProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } @@ -52,6 +53,7 @@ class NodeVMSourceTextModule final : public NodeVMModule, public SigintReceiver WriteBarrier m_moduleRequestsArray; WriteBarrier m_cachedExecutable; WriteBarrier m_cachedBytecodeBuffer; + WriteBarrier m_evaluationException; RefPtr m_bytecode; SourceCode m_sourceCode; diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 9925a1617248..03fc0240e88a 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -178,6 +178,8 @@ class Module { } async link(linker) { + validateFunction(linker, "linker"); + if (this[kNative].getStatusCode() === kLinked) { throw $ERR_VM_MODULE_ALREADY_LINKED(); } From aec3c9b131b6d7575f29773e49164579f76d9f38 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Thu, 15 May 2025 21:08:57 -0700 Subject: [PATCH 53/76] test-vm-module-errors.js --- .../bindings/NodeVMSourceTextModule.cpp | 2 +- src/js/node/vm.ts | 22 ++ .../test/parallel/test-vm-module-errors.js | 276 ++++++++++++++++++ 3 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 test/js/node/test/parallel/test-vm-module-errors.js diff --git a/src/bun.js/bindings/NodeVMSourceTextModule.cpp b/src/bun.js/bindings/NodeVMSourceTextModule.cpp index 417b297829be..f42d81373ac6 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.cpp +++ b/src/bun.js/bindings/NodeVMSourceTextModule.cpp @@ -59,7 +59,7 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g JSValue cachedDataValue = args.at(5); WTF::Vector cachedData; if (!cachedDataValue.isUndefined() && !extractCachedData(cachedDataValue, cachedData)) { - throwArgumentTypeError(*globalObject, scope, 5, "cachedData"_s, "Module"_s, "Module"_s, "Buffer, TypedArray, or DataView"_s); + Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataValue); return nullptr; } diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 03fc0240e88a..ed073e6582ec 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -81,6 +81,15 @@ function validateContext(contextifiedObject) { } } +function validateModule(module, typename = "Module") { + if (!isModule(module)) { + const error = new Error('The "this" argument must be an instance of ' + typename); + error.code = "ERR_INVALID_ARG_TYPE"; + error.name = "TypeError"; + throw error; + } +} + let globalModuleId = 0; const defaultModuleName = "vm:module"; @@ -150,18 +159,22 @@ class Module { } get identifier() { + validateModule(this); return this[kNative].identifier; } get context() { + validateModule(this); return this[kContext]; } get status() { + validateModule(this); return this[kNative].getStatus(); } get namespace() { + validateModule(this); if (this[kNative].getStatusCode() < kLinked) { throw $ERR_VM_MODULE_STATUS("must not be unlinked or linking"); } @@ -170,6 +183,7 @@ class Module { } get error() { + validateModule(this); if (this[kNative].getStatusCode() !== kErrored) { throw $ERR_VM_MODULE_STATUS("must be errored"); } @@ -178,6 +192,7 @@ class Module { } async link(linker) { + validateModule(this); validateFunction(linker, "linker"); if (this[kNative].getStatusCode() === kLinked) { @@ -193,6 +208,7 @@ class Module { } async evaluate(options = kEmptyObject) { + validateModule(this); validateObject(options, "options"); let timeout = options.timeout; @@ -278,6 +294,8 @@ class SourceTextModule extends Module { } async [kLink](linker) { + validateModule(this, "SourceTextModule"); + if (this[kNative].getStatusCode() >= kLinked) { throw $ERR_VM_MODULE_ALREADY_LINKED(); } @@ -329,6 +347,7 @@ class SourceTextModule extends Module { } get dependencySpecifiers() { + validateModule(this, "SourceTextModule"); this[kDependencySpecifiers] ??= ObjectFreeze( ArrayPrototypeMap.$call(this[kNative].getModuleRequests(), request => request[0]), ); @@ -336,6 +355,7 @@ class SourceTextModule extends Module { } get status() { + validateModule(this, "SourceTextModule"); if (this.#error !== kNoError) { return "errored"; } @@ -346,6 +366,7 @@ class SourceTextModule extends Module { } get error() { + validateModule(this, "SourceTextModule"); if (this.#error !== kNoError) { return this.#error; } @@ -353,6 +374,7 @@ class SourceTextModule extends Module { } createCachedData() { + validateModule(this, "SourceTextModule"); const { status } = this; if (status === "evaluating" || status === "evaluated" || status === "errored") { throw $ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA(); diff --git a/test/js/node/test/parallel/test-vm-module-errors.js b/test/js/node/test/parallel/test-vm-module-errors.js new file mode 100644 index 000000000000..61f9c68fa39b --- /dev/null +++ b/test/js/node/test/parallel/test-vm-module-errors.js @@ -0,0 +1,276 @@ +'use strict'; + +// Flags: --experimental-vm-modules + +const common = require('../common'); + +const assert = require('assert'); + +const { SourceTextModule, createContext, Module } = require('vm'); + +async function createEmptyLinkedModule() { + const m = new SourceTextModule(''); + await m.link(common.mustNotCall()); + return m; +} + +async function checkArgType() { + assert.throws(() => { + new SourceTextModule(); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + + for (const invalidOptions of [ + 0, 1, null, true, 'str', () => {}, { identifier: 0 }, Symbol.iterator, + { context: null }, { context: 'hucairz' }, { context: {} }, + ]) { + assert.throws(() => { + new SourceTextModule('', invalidOptions); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + } + + for (const invalidLinker of [ + 0, 1, undefined, null, true, 'str', {}, Symbol.iterator, + ]) { + await assert.rejects(async () => { + const m = new SourceTextModule(''); + await m.link(invalidLinker); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + } +} + +// Check methods/properties can only be used under a specific state. +async function checkModuleState() { + await assert.rejects(async () => { + const m = new SourceTextModule(''); + await m.link(common.mustNotCall()); + assert.strictEqual(m.status, 'linked'); + await m.link(common.mustNotCall()); + }, { + code: 'ERR_VM_MODULE_ALREADY_LINKED' + }); + + await assert.rejects(async () => { + const m = new SourceTextModule(''); + m.link(common.mustNotCall()); + assert.strictEqual(m.status, 'linking'); + await m.link(common.mustNotCall()); + }, { + code: 'ERR_VM_MODULE_STATUS' + }); + + await assert.rejects(async () => { + const m = new SourceTextModule(''); + await m.evaluate(); + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must be one of linked, evaluated, or errored' + }); + + await assert.rejects(async () => { + const m = new SourceTextModule(''); + await m.evaluate(false); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "options" argument must be of type object. ' + + 'Received type boolean (false)' + }); + + assert.throws(() => { + const m = new SourceTextModule(''); + m.error; // eslint-disable-line no-unused-expressions + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must be errored' + }); + + await assert.rejects(async () => { + const m = await createEmptyLinkedModule(); + await m.evaluate(); + m.error; // eslint-disable-line no-unused-expressions + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must be errored' + }); + + assert.throws(() => { + const m = new SourceTextModule(''); + m.namespace; // eslint-disable-line no-unused-expressions + }, { + code: 'ERR_VM_MODULE_STATUS', + message: 'Module status must not be unlinked or linking' + }); +} + +// Check link() fails when the returned module is not valid. +async function checkLinking() { + await assert.rejects(async () => { + const m = new SourceTextModule('import "foo";'); + try { + await m.link(common.mustCall(() => ({}))); + } catch (err) { + assert.strictEqual(m.status, 'errored'); + throw err; + } + }, { + code: 'ERR_VM_MODULE_NOT_MODULE' + }); + + await assert.rejects(async () => { + const c = createContext({ a: 1 }); + const foo = new SourceTextModule('', { context: c }); + await foo.link(common.mustNotCall()); + const bar = new SourceTextModule('import "foo";'); + try { + await bar.link(common.mustCall(() => foo)); + } catch (err) { + assert.strictEqual(bar.status, 'errored'); + throw err; + } + }, { + code: 'ERR_VM_MODULE_DIFFERENT_CONTEXT' + }); + + const error = new Error(); + await assert.rejects(async () => { + globalThis.error = error; + const erroredModule = new SourceTextModule('throw error;'); + await erroredModule.link(common.mustNotCall()); + try { + await erroredModule.evaluate(); + } catch { + // ignored + } + delete globalThis.error; + + assert.strictEqual(erroredModule.status, 'errored'); + + const rootModule = new SourceTextModule('import "errored";'); + await rootModule.link(common.mustCall(() => erroredModule)); + }, { + code: 'ERR_VM_MODULE_LINK_FAILURE', + cause: error, + }); +} + +assert.throws(() => { + new SourceTextModule('', { + importModuleDynamically: 'hucairz' + }); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options.importModuleDynamically" property must be of type ' + + "function. Received type string ('hucairz')" +}); + +// Check the JavaScript engine deals with exceptions correctly +async function checkExecution() { + await (async () => { + const m = new SourceTextModule('import { nonexistent } from "module";'); + + // There is no code for this exception since it is thrown by the JavaScript + // engine. + await assert.rejects(() => { + return m.link(common.mustCall(() => new SourceTextModule(''))); + }, SyntaxError); + })(); + + await (async () => { + const m = new SourceTextModule('throw new Error();'); + await m.link(common.mustNotCall()); + try { + await m.evaluate(); + } catch (err) { + assert.strictEqual(m.error, err); + assert.strictEqual(m.status, 'errored'); + return; + } + assert.fail('Missing expected exception'); + })(); +} + +// Check for error thrown when breakOnSigint is not a boolean for evaluate() +async function checkInvalidOptionForEvaluate() { + await assert.rejects(async () => { + const m = new SourceTextModule('export const a = 1; export let b = 2'); + await m.evaluate({ breakOnSigint: 'a-string' }); + }, { + name: 'TypeError', + message: + 'The "options.breakOnSigint" property must be of type boolean. ' + + "Received type string ('a-string')", + code: 'ERR_INVALID_ARG_TYPE' + }); + + { + ['link', 'evaluate'].forEach(async (method) => { + await assert.rejects(async () => { + await Module.prototype[method](); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "this" argument must be an instance of Module/ + }); + }); + } +} + +function checkInvalidCachedData() { + [true, false, 'foo', {}, Array, function() {}].forEach((invalidArg) => { + const message = 'The "options.cachedData" property must be of ' + + 'type Buffer, TypedArray, or DataView.' + + common.invalidArgTypeHelper(invalidArg); + assert.throws( + () => new SourceTextModule('import "foo";', { cachedData: invalidArg }), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message, + } + ); + }); +} + +function checkGettersErrors() { + const expectedError = { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "this" argument must be an instance of (?:Module|SourceTextModule)/, + }; + const getters = ['identifier', 'context', 'namespace', 'status', 'error']; + getters.forEach((getter) => { + assert.throws(() => { + // eslint-disable-next-line no-unused-expressions + Module.prototype[getter]; + }, expectedError); + assert.throws(() => { + // eslint-disable-next-line no-unused-expressions + SourceTextModule.prototype[getter]; + }, expectedError); + }); + // `dependencySpecifiers` getter is just part of SourceTextModule + assert.throws(() => { + // eslint-disable-next-line no-unused-expressions + SourceTextModule.prototype.dependencySpecifiers; + }, expectedError); +} + +const finished = common.mustCall(); + +(async function main() { + await checkArgType(); + await checkModuleState(); + await checkLinking(); + await checkExecution(); + await checkInvalidOptionForEvaluate(); + checkInvalidCachedData(); + checkGettersErrors(); + finished(); +})().then(common.mustCall()); From afe98167cb74983a5338946da06cc9f63c8c9d06 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 16 May 2025 14:51:44 -0700 Subject: [PATCH 54/76] Support timeouts better, fix runInNewContext, add test-vm-timeout.js --- src/bun.js/bindings/NodeVMScript.cpp | 41 +++++++--- src/js/node/vm.ts | 11 ++- test/js/node/test/parallel/test-vm-timeout.js | 81 +++++++++++++++++++ 3 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 test/js/node/test/parallel/test-vm-timeout.js diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 52648bc4c408..c41a9a57aac0 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -241,7 +241,7 @@ void NodeVMScript::destroy(JSCell* cell) static_cast(cell)->NodeVMScript::~NodeVMScript(); } -static bool checkForTermination(JSGlobalObject* globalObject, ThrowScope& scope, NodeVMScript* script, RunningScriptOptions& options) +static bool checkForTermination(JSGlobalObject* globalObject, ThrowScope& scope, NodeVMScript* script, std::optional timeout) { VM& vm = JSC::getVM(globalObject); @@ -250,8 +250,8 @@ static bool checkForTermination(JSGlobalObject* globalObject, ThrowScope& scope, if (script->getSigintReceived()) { script->setSigintReceived(false); throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_INTERRUPTED, "Script execution was interrupted by `SIGINT`"_s); - } else if (options.timeout) { - throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, *options.timeout, "ms"_s)); + } else if (timeout) { + throwError(globalObject, scope, ErrorCode::ERR_SCRIPT_EXECUTION_TIMEOUT, makeString("Script execution timed out after "_s, *timeout, "ms"_s)); } else { RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("vm.Script terminated due neither to SIGINT nor to timeout"); } @@ -261,12 +261,27 @@ static bool checkForTermination(JSGlobalObject* globalObject, ThrowScope& scope, return false; } -static void setupWatchdog(VM& vm, int64_t timeout) +static void setupWatchdog(VM& vm, double timeout, double* oldTimeout, double* newTimeout) { JSC::JSLockHolder locker(vm); JSC::Watchdog& dog = vm.ensureWatchdog(); dog.enteredVM(); - dog.setTimeLimit(WTF::Seconds::fromMilliseconds(timeout)); + + Seconds oldLimit = dog.getTimeLimit(); + + if (oldTimeout) { + *oldTimeout = oldLimit.milliseconds(); + } + + if (oldLimit.isInfinity() || timeout < oldLimit.milliseconds()) { + dog.setTimeLimit(WTF::Seconds::fromMilliseconds(timeout)); + } else { + timeout = oldLimit.milliseconds(); + } + + if (newTimeout) { + *newTimeout = timeout; + } } static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false) @@ -292,8 +307,10 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM result = JSC::evaluate(globalObject, script->source(), globalObject, exception); }; + std::optional oldLimit, newLimit; + if (options.timeout) { - setupWatchdog(vm, *options.timeout); + setupWatchdog(vm, *options.timeout, &oldLimit.emplace(), &newLimit.emplace()); } script->setSigintReceived(false); @@ -306,10 +323,10 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM } if (options.timeout) { - vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit); + vm.watchdog()->setTimeLimit(WTF::Seconds::fromMilliseconds(*oldLimit)); } - if (options.breakOnSigint && checkForTermination(globalObject, scope, script, options)) { + if (checkForTermination(globalObject, scope, script, newLimit)) { return {}; } @@ -351,8 +368,10 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, result = JSC::evaluate(globalObject, script->source(), globalObject, exception); }; + std::optional oldLimit, newLimit; + if (options.timeout) { - setupWatchdog(vm, *options.timeout); + setupWatchdog(vm, *options.timeout, &oldLimit.emplace(), &newLimit.emplace()); } script->setSigintReceived(false); @@ -366,10 +385,10 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, } if (options.timeout) { - vm.watchdog()->setTimeLimit(JSC::Watchdog::noTimeLimit); + vm.watchdog()->setTimeLimit(WTF::Seconds::fromMilliseconds(*oldLimit)); } - if (options.breakOnSigint && checkForTermination(globalObject, scope, script, options)) { + if (checkForTermination(globalObject, scope, script, newLimit)) { return {}; } diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index ed073e6582ec..19103f096947 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -39,7 +39,7 @@ const { Module: ModuleNative, createContext, isContext, - runInNewContext, + // runInNewContext: moduleRunInNewContext, // runInThisContext: moduleRunInThisContext, compileFunction, isModuleNamespaceObject, @@ -64,6 +64,14 @@ function runInThisContext(code, options) { return new Script(code, options).runInThisContext(options); } +function runInNewContext(code, contextObject, options) { + if (typeof options === "string") { + options = { filename: options }; + } + contextObject = createContext(contextObject, options); + return createScript(code, options).runInNewContext(contextObject, options); +} + function createScript(code, options) { return new Script(code, options); } @@ -227,6 +235,7 @@ class Module { } [util.inspect.custom](depth, options) { + validateModule(this); if (typeof depth === "number" && depth < 0) return this; const constructor = getConstructorOf(this) || Module; diff --git a/test/js/node/test/parallel/test-vm-timeout.js b/test/js/node/test/parallel/test-vm-timeout.js new file mode 100644 index 000000000000..426d1e069255 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-timeout.js @@ -0,0 +1,81 @@ +// 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'); + +// Timeout of 100ms executing endless loop +assert.throws( + function() { + vm.runInThisContext('while(true) {}', { timeout: 100 }); + }, + { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 100ms' + }); + +// Timeout of 1000ms, script finishes first +vm.runInThisContext('', { timeout: 1000 }); + +// Nested vm timeouts, inner timeout propagates out +assert.throws( + function() { + const context = { + log: console.log, + runInVM: function(timeout) { + vm.runInNewContext('while(true) {}', context, { timeout }); + } + }; + vm.runInNewContext('runInVM(10)', context, { timeout: 10000 }); + throw new Error('Test 5 failed'); + }, + { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 10ms' + }); + +// Nested vm timeouts, outer timeout is shorter and fires first. +assert.throws( + function() { + const context = { + runInVM: function(timeout) { + vm.runInNewContext('while(true) {}', context, { timeout }); + } + }; + vm.runInNewContext('runInVM(10000)', context, { timeout: 100 }); + throw new Error('Test 6 failed'); + }, + { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 100ms' + }); + +// Nested vm timeouts, inner script throws an error. +assert.throws(function() { + const context = { + runInVM: function(timeout) { + vm.runInNewContext('throw new Error(\'foobar\')', context, { timeout }); + } + }; + vm.runInNewContext('runInVM(10000)', context, { timeout: 100000 }); +}, /foobar/); From bbf99ad40e84e8682c5dd391d05a228ed633cecd Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 16 May 2025 14:54:57 -0700 Subject: [PATCH 55/76] Add test-vm-timeout-escape-promise.js --- .../test-vm-timeout-escape-promise.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/js/node/test/parallel/test-vm-timeout-escape-promise.js diff --git a/test/js/node/test/parallel/test-vm-timeout-escape-promise.js b/test/js/node/test/parallel/test-vm-timeout-escape-promise.js new file mode 100644 index 000000000000..36a76001af34 --- /dev/null +++ b/test/js/node/test/parallel/test-vm-timeout-escape-promise.js @@ -0,0 +1,39 @@ +'use strict'; + +// https://github.com/nodejs/node/issues/3020 +// Promises used to allow code to escape the timeout +// set for runInContext, runInNewContext, and runInThisContext. + +require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +const NS_PER_MS = 1000000n; + +const hrtime = process.hrtime.bigint; + +function loop() { + const start = hrtime(); + while (1) { + const current = hrtime(); + const span = (current - start) / NS_PER_MS; + if (span >= 2000n) { + throw new Error( + `escaped timeout at ${span} milliseconds!`); + } + } +} + +assert.throws(() => { + vm.runInNewContext( + 'Promise.resolve().then(() => loop()); loop();', + { + hrtime, + loop + }, + { timeout: 5, microtaskMode: 'afterEvaluate' } + ); +}, { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + message: 'Script execution timed out after 5ms' +}); From b0ba847e6ad9c2785379a343cd647fc762f89301 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 16 May 2025 15:06:08 -0700 Subject: [PATCH 56/76] =?UTF-8?q?static=20=E2=80=93>=20runtime=20assert=20?= =?UTF-8?q?in=20SigintWatcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vm/SigintWatcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 74df588f4037..94bd7eadda8d 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -37,7 +37,7 @@ void SigintWatcher::install() sigaction(SIGINT, &action, nullptr); #else - static_assert(false, "TODO(@heimskr): implement sigint handler on Windows"); + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): implement sigint handler on Windows"); #endif if (m_installed.exchange(true)) { From 6c67a500ebc94700c4adb1ebcab2d48cca3bcda5 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 16 May 2025 15:06:47 -0700 Subject: [PATCH 57/76] Change WebKit version --- cmake/tools/SetupWebKit.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/tools/SetupWebKit.cmake b/cmake/tools/SetupWebKit.cmake index 421b28aae507..ca6bd55dd035 100644 --- a/cmake/tools/SetupWebKit.cmake +++ b/cmake/tools/SetupWebKit.cmake @@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use") option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading") if(NOT WEBKIT_VERSION) - set(WEBKIT_VERSION 017930ebf915121f8f593bef61cbbca82d78132d) + set(WEBKIT_VERSION f2cab73b2c132ff498837bd91b442f5947acef48) endif() string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX) From 1c6a18d2a6498e3094fc0c0395a7e8dd544e712a Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 21 May 2025 14:30:40 -0700 Subject: [PATCH 58/76] Update WebKit version --- cmake/tools/SetupWebKit.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/tools/SetupWebKit.cmake b/cmake/tools/SetupWebKit.cmake index 48ceb7307605..652958830d36 100644 --- a/cmake/tools/SetupWebKit.cmake +++ b/cmake/tools/SetupWebKit.cmake @@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use") option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading") if(NOT WEBKIT_VERSION) - set(WEBKIT_VERSION f2cab73b2c132ff498837bd91b442f5947acef48) + set(WEBKIT_VERSION 3db06352c224f419f1e2e52d39fd5e26141063e1) endif() string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX) From a71b08063951f04b90e4f90baddacc237fad1046 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Wed, 21 May 2025 14:32:44 -0700 Subject: [PATCH 59/76] Update WebKit version again --- cmake/tools/SetupWebKit.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/tools/SetupWebKit.cmake b/cmake/tools/SetupWebKit.cmake index 652958830d36..f3ec1093a595 100644 --- a/cmake/tools/SetupWebKit.cmake +++ b/cmake/tools/SetupWebKit.cmake @@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use") option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading") if(NOT WEBKIT_VERSION) - set(WEBKIT_VERSION 3db06352c224f419f1e2e52d39fd5e26141063e1) + set(WEBKIT_VERSION 6ac94f7524dd6bfc1f4a565c68f799c731eb2d1e) endif() string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX) From 659b1986cdb2aae483bd2c5af289b27ae1d2f0f0 Mon Sep 17 00:00:00 2001 From: heimskr <13513421+heimskr@users.noreply.github.com> Date: Fri, 23 May 2025 20:35:14 +0000 Subject: [PATCH 60/76] `bun scripts/glob-sources.mjs` --- cmake/sources/CxxSources.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmake/sources/CxxSources.txt b/cmake/sources/CxxSources.txt index 318dd51ec918..4d20100eae47 100644 --- a/cmake/sources/CxxSources.txt +++ b/cmake/sources/CxxSources.txt @@ -469,5 +469,3 @@ src/bun.js/modules/NodeUtilTypesModule.cpp src/bun.js/modules/ObjectModule.cpp src/deps/libuwsockets.cpp src/io/io_darwin.cpp -src/vm/Semaphore.cpp -src/vm/SigintWatcher.cpp From 4ba13362325b8ee11d56e55680b61626d3338a4b Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 13:41:28 -0700 Subject: [PATCH 61/76] Fix some includes, probably --- src/bun.js/bindings/NodeVMModule.h | 4 ++-- src/bun.js/bindings/NodeVMScript.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bun.js/bindings/NodeVMModule.h b/src/bun.js/bindings/NodeVMModule.h index c0226c9cb337..96f5c26d43ef 100644 --- a/src/bun.js/bindings/NodeVMModule.h +++ b/src/bun.js/bindings/NodeVMModule.h @@ -2,8 +2,8 @@ #include "NodeVM.h" -#include "AbstractModuleRecord.h" -#include "JSModuleNamespaceObject.h" +#include "JavaScriptCore/AbstractModuleRecord.h" +#include "JavaScriptCore/JSModuleNamespaceObject.h" namespace Bun { diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 5d850e89df6f..44c2245be784 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -1,5 +1,5 @@ -#include "Completion.h" #include "ErrorCode.h" +#include "JavaScriptCore/Completion.h" #include "JavaScriptCore/JIT.h" #include "JavaScriptCore/JSWeakMap.h" #include "JavaScriptCore/JSWeakMapInlines.h" From cb4736070b207173f1af13ea6f36ce4688220050 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 13:52:16 -0700 Subject: [PATCH 62/76] Can't initialize std::atomic_flag with a boolean --- src/vm/SigintWatcher.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index 22dbd9a7db73..e0b0449682bf 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -98,7 +98,7 @@ class SigintWatcher { private: std::thread m_thread; std::atomic_bool m_installed = false; - std::atomic_flag m_waiting = false; + std::atomic_flag m_waiting {}; Semaphore m_semaphore; std::mutex m_globalObjectsMutex; std::mutex m_receiversMutex; From dc509c75c6b141c7446f267f0dab1c1b6e65e053 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 14:10:03 -0700 Subject: [PATCH 63/76] pls don't tear out my new .cpps this time --- cmake/Sources.json | 1 + cmake/sources/CxxSources.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/cmake/Sources.json b/cmake/Sources.json index d8e6f3b1173d..ac6bd4c85927 100644 --- a/cmake/Sources.json +++ b/cmake/Sources.json @@ -48,6 +48,7 @@ "src/bun.js/bindings/v8/shim/*.cpp", "src/bake/*.cpp", "src/deps/*.cpp", + "src/vm/*.cpp", "packages/bun-usockets/src/crypto/*.cpp" ] }, diff --git a/cmake/sources/CxxSources.txt b/cmake/sources/CxxSources.txt index 4d20100eae47..f7d5a26334be 100644 --- a/cmake/sources/CxxSources.txt +++ b/cmake/sources/CxxSources.txt @@ -469,3 +469,5 @@ src/bun.js/modules/NodeUtilTypesModule.cpp src/bun.js/modules/ObjectModule.cpp src/deps/libuwsockets.cpp src/io/io_darwin.cpp +src/vm/Semaphore.cpp +src/vm/SigintWatcher.cpp \ No newline at end of file From fc44f65b7ed46c1f4597f8c436fc021c6c7ae14b Mon Sep 17 00:00:00 2001 From: heimskr <13513421+heimskr@users.noreply.github.com> Date: Fri, 23 May 2025 21:11:23 +0000 Subject: [PATCH 64/76] `bun scripts/glob-sources.mjs` --- cmake/sources/CxxSources.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/sources/CxxSources.txt b/cmake/sources/CxxSources.txt index f7d5a26334be..318dd51ec918 100644 --- a/cmake/sources/CxxSources.txt +++ b/cmake/sources/CxxSources.txt @@ -470,4 +470,4 @@ src/bun.js/modules/ObjectModule.cpp src/deps/libuwsockets.cpp src/io/io_darwin.cpp src/vm/Semaphore.cpp -src/vm/SigintWatcher.cpp \ No newline at end of file +src/vm/SigintWatcher.cpp From e27507cfffa0c3e27d9e1abcbd79e17c8b73b160 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 14:17:53 -0700 Subject: [PATCH 65/76] Fix missing root.h include --- src/vm/Semaphore.h | 2 ++ src/vm/SigintWatcher.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/vm/Semaphore.h b/src/vm/Semaphore.h index 4f85976692e6..cb3ae7d93c6b 100644 --- a/src/vm/Semaphore.h +++ b/src/vm/Semaphore.h @@ -1,5 +1,7 @@ #pragma once +#include "root.h" + #if OS(WINDOWS) #include #elif OS(DARWIN) diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index e0b0449682bf..cf5a9aa2b0bb 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -1,6 +1,7 @@ #pragma once #include "root.h" + #include "Semaphore.h" #include "SigintReceiver.h" From c463154d98fe2cb0c920124b47f1e4f9af131c73 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 14:32:40 -0700 Subject: [PATCH 66/76] "fix" SigintWatcher --- src/vm/SigintWatcher.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 94bd7eadda8d..3bb1b5c8e385 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -21,7 +21,9 @@ SigintWatcher::~SigintWatcher() void SigintWatcher::install() { -#if !OS(WINDOWS) +#if OS(WINDOWS) + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): implement sigint handler on Windows"); +#else Bun__ensureSignalHandler(); struct sigaction action; @@ -36,8 +38,6 @@ void SigintWatcher::install() action.sa_flags = 0; sigaction(SIGINT, &action, nullptr); -#else - RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): implement sigint handler on Windows"); #endif if (m_installed.exchange(true)) { @@ -68,6 +68,9 @@ void SigintWatcher::uninstall() if (m_installed.exchange(false)) { ASSERT(m_thread.get_id() != std::this_thread::get_id()); +#if OS(WINDOWS) + RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): implement sigint handler on Windows"); +#else struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler = SIG_DFL; @@ -75,6 +78,7 @@ void SigintWatcher::uninstall() sigaddset(&action.sa_mask, SIGINT); action.sa_flags = SA_RESTART; sigaction(SIGINT, &action, nullptr); +#endif m_semaphore.signal(); m_thread.join(); From 6c08fd855afd5c04cb2cfdd263016601ae27ea3c Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 15:31:13 -0700 Subject: [PATCH 67/76] SigintWatcher on Windows, perchance? --- src/vm/SigintWatcher.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 3bb1b5c8e385..c20615044cdb 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -1,11 +1,27 @@ #include "NodeVM.h" #include "SigintWatcher.h" +#if OS(WINDOWS) +#include +#endif + extern "C" void Bun__onPosixSignal(int signalNumber); extern "C" void Bun__ensureSignalHandler(); namespace Bun { +#if OS(WINDOWS) +static BOOL WindowsCtrlHandler(DWORD signal) +{ + if (signal == CTRL_C_EVENT) { + SigintWatcher::get().signalReceived(); + return true; + } + + return false; +} +#endif + SigintWatcher SigintWatcher::s_instance; SigintWatcher::SigintWatcher() @@ -22,7 +38,7 @@ SigintWatcher::~SigintWatcher() void SigintWatcher::install() { #if OS(WINDOWS) - RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): implement sigint handler on Windows"); + SetConsoleCtrlHandler(WindowsCtrlHandler, true); #else Bun__ensureSignalHandler(); @@ -53,9 +69,13 @@ void SigintWatcher::install() ASSERT(success); if (m_waiting.test_and_set()) { m_waiting.clear(); +#if !OS(WINDOWS) if (!signalAll()) { Bun__onPosixSignal(SIGINT); } +#else + signalAll(); +#endif } else { m_waiting.clear(); } @@ -69,7 +89,7 @@ void SigintWatcher::uninstall() ASSERT(m_thread.get_id() != std::this_thread::get_id()); #if OS(WINDOWS) - RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("TODO(@heimskr): implement sigint handler on Windows"); + SetConsoleCtrlHandler(WindowsCtrlHandler, false); #else struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); From 59541fd8abecbb2ee615081b42c581c0ea2f108e Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 15:33:15 -0700 Subject: [PATCH 68/76] Fix options.displayErrors validation (allow it to be present but undefined) --- src/bun.js/bindings/NodeVMScript.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 44c2245be784..1c04cc4b625e 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -612,12 +612,14 @@ bool RunningScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm if (JSValue displayErrorsOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "displayErrors"_s))) { RETURN_IF_EXCEPTION(scope, false); - if (!displayErrorsOpt.isBoolean()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.displayErrors"_s, "boolean"_s, displayErrorsOpt); - return false; + if (!displayErrorsOpt.isUndefined()) { + if (!displayErrorsOpt.isBoolean()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.displayErrors"_s, "boolean"_s, displayErrorsOpt); + return false; + } + this->displayErrors = displayErrorsOpt.asBoolean(); + any = true; } - this->displayErrors = displayErrorsOpt.asBoolean(); - any = true; } if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { From e34fd75328d695fc0eb80ba28de2d08a73fc2d96 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 16:02:02 -0700 Subject: [PATCH 69/76] Remove a static initializer --- src/vm/SigintWatcher.cpp | 8 ++++++-- src/vm/SigintWatcher.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index c20615044cdb..127cae42d4ad 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -22,7 +22,7 @@ static BOOL WindowsCtrlHandler(DWORD signal) } #endif -SigintWatcher SigintWatcher::s_instance; +SigintWatcher* SigintWatcher::s_instance = nullptr; SigintWatcher::SigintWatcher() : m_semaphore(1) @@ -180,7 +180,11 @@ void SigintWatcher::deref() SigintWatcher& SigintWatcher::get() { - return s_instance; + if (!s_instance) { + s_instance = new SigintWatcher(); + } + + return *s_instance; } bool SigintWatcher::signalAll() diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index cf5a9aa2b0bb..637a45adb251 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -109,7 +109,7 @@ class SigintWatcher { bool signalAll(); - static SigintWatcher s_instance; + static SigintWatcher* s_instance; }; } // namespace Bun From e930e315c0b12b5850689e1ea83d5a18e7447ca5 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 16:07:22 -0700 Subject: [PATCH 70/76] Or I could just do this --- src/vm/SigintWatcher.cpp | 9 ++------- src/vm/SigintWatcher.h | 2 -- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 127cae42d4ad..8aa4ce2bab55 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -22,8 +22,6 @@ static BOOL WindowsCtrlHandler(DWORD signal) } #endif -SigintWatcher* SigintWatcher::s_instance = nullptr; - SigintWatcher::SigintWatcher() : m_semaphore(1) { @@ -180,11 +178,8 @@ void SigintWatcher::deref() SigintWatcher& SigintWatcher::get() { - if (!s_instance) { - s_instance = new SigintWatcher(); - } - - return *s_instance; + static SigintWatcher instance; + return instance; } bool SigintWatcher::signalAll() diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index 637a45adb251..e2d7aad8d2f7 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -108,8 +108,6 @@ class SigintWatcher { uint32_t m_refCount = 0; bool signalAll(); - - static SigintWatcher* s_instance; }; } // namespace Bun From be560939bce17f8c66938b8e4ffeb81fcb8f6050 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 16:32:20 -0700 Subject: [PATCH 71/76] breakOnSigint too I suppose --- src/bun.js/bindings/NodeVMScript.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 1c04cc4b625e..649cf6ad7e27 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -629,12 +629,14 @@ bool RunningScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm if (JSValue breakOnSigintOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "breakOnSigint"_s))) { RETURN_IF_EXCEPTION(scope, false); - if (!breakOnSigintOpt.isBoolean()) { - ERR::INVALID_ARG_TYPE(scope, globalObject, "options.breakOnSigint"_s, "boolean"_s, breakOnSigintOpt); - return false; + if (!breakOnSigintOpt.isUndefined()) { + if (!breakOnSigintOpt.isBoolean()) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.breakOnSigint"_s, "boolean"_s, breakOnSigintOpt); + return false; + } + this->breakOnSigint = breakOnSigintOpt.asBoolean(); + any = true; } - this->breakOnSigint = breakOnSigintOpt.asBoolean(); - any = true; } } From ed8f5764b57dbe18b6fc6c795ca32e84aa786c93 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 17:15:08 -0700 Subject: [PATCH 72/76] Remove another static initializer --- src/bun.js/bindings/BunProcess.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 36f243f7f18a..61fe74327093 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -875,8 +875,8 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionChdir, (JSC::JSGlobalObject * globalObj RELEASE_AND_RETURN(scope, JSC::JSValue::encode(result)); } -static std::optional> signalNameToNumberMap; -static std::optional> signalNumberToNameMap; +static HashMap* signalNumberToNameMap = nullptr; +static HashMap* signalNameToNumberMap = nullptr; // On windows, signals need to have a handle to the uv_signal_t. When sigaction is used, this is kept track globally for you. struct SignalHandleValue { @@ -931,7 +931,7 @@ static void loadSignalNumberMap() static std::once_flag signalNameToNumberMapOnceFlag; std::call_once(signalNameToNumberMapOnceFlag, [] { auto signalNames = getSignalNames(); - signalNameToNumberMap.emplace(); + signalNameToNumberMap = new HashMap(); signalNameToNumberMap->reserveInitialCapacity(31); #if OS(WINDOWS) // libuv supported signals @@ -1172,7 +1172,7 @@ static void onDidChangeListeners(EventEmitter& eventEmitter, const Identifier& e static std::once_flag signalNumberToNameMapOnceFlag; std::call_once(signalNumberToNameMapOnceFlag, [] { auto signalNames = getSignalNames(); - signalNumberToNameMap.emplace(); + signalNumberToNameMap = new HashMap(); signalNumberToNameMap->reserveInitialCapacity(31); signalNumberToNameMap->add(SIGHUP, signalNames[0]); signalNumberToNameMap->add(SIGINT, signalNames[1]); From c2ba7efa5388edcac2680b6cdf774006ccdcceed Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 17:15:25 -0700 Subject: [PATCH 73/76] Don't allow null context values --- src/bun.js/bindings/NodeVMScript.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/NodeVMScript.cpp b/src/bun.js/bindings/NodeVMScript.cpp index 649cf6ad7e27..1bcccff09cb8 100644 --- a/src/bun.js/bindings/NodeVMScript.cpp +++ b/src/bun.js/bindings/NodeVMScript.cpp @@ -520,7 +520,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, return {}; } - if (!contextObjectValue || contextObjectValue.isUndefinedOrNull()) { + if (contextObjectValue.isUndefined()) { contextObjectValue = JSC::constructEmptyObject(globalObject); } From 4a083662e72d3e8a158e099631e8d6f249d6f0cb Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 17:59:16 -0700 Subject: [PATCH 74/76] Don't allow null context values in createContext --- src/bun.js/bindings/NodeVM.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/NodeVM.cpp b/src/bun.js/bindings/NodeVM.cpp index 13ae33296dc0..d233549e626b 100644 --- a/src/bun.js/bindings/NodeVM.cpp +++ b/src/bun.js/bindings/NodeVM.cpp @@ -937,7 +937,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, auto scope = DECLARE_THROW_SCOPE(vm); JSValue contextArg = callFrame->argument(0); - if (contextArg.isUndefinedOrNull()) { + if (contextArg.isUndefined()) { contextArg = JSC::constructEmptyObject(globalObject); } From dbdc63eb4d4897ec74b75362ab02cfcd97126398 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 22:19:07 -0700 Subject: [PATCH 75/76] Use WTF::Thread, WTF::Lock and WTF::Locker instead of std::thread, std::mutex and std::unique_lock --- src/vm/SigintWatcher.cpp | 19 ++++++++++--------- src/vm/SigintWatcher.h | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/vm/SigintWatcher.cpp b/src/vm/SigintWatcher.cpp index 8aa4ce2bab55..8956fe29db01 100644 --- a/src/vm/SigintWatcher.cpp +++ b/src/vm/SigintWatcher.cpp @@ -58,7 +58,7 @@ void SigintWatcher::install() return; } - m_thread = std::thread([this] { + m_thread = WTF::Thread::create("SigintWatcher"_s, [this] { while (m_installed.load()) { bool success = m_semaphore.wait(); if (!m_installed) { @@ -84,7 +84,8 @@ void SigintWatcher::install() void SigintWatcher::uninstall() { if (m_installed.exchange(false)) { - ASSERT(m_thread.get_id() != std::this_thread::get_id()); + WTF::Thread* currentThread = WTF::Thread::currentMayBeNull(); + ASSERT(!currentThread || m_thread->uid() != currentThread->uid()); #if OS(WINDOWS) SetConsoleCtrlHandler(WindowsCtrlHandler, false); @@ -99,7 +100,7 @@ void SigintWatcher::uninstall() #endif m_semaphore.signal(); - m_thread.join(); + m_thread->waitForCompletion(); } } @@ -117,7 +118,7 @@ void SigintWatcher::registerGlobalObject(JSGlobalObject* globalObject) return; } - std::unique_lock lock(m_globalObjectsMutex); + WTF::Locker lock(m_globalObjectsMutex); m_globalObjects.appendIfNotContains(globalObject); } @@ -127,7 +128,7 @@ void SigintWatcher::unregisterGlobalObject(JSGlobalObject* globalObject) return; } - std::unique_lock lock(m_globalObjectsMutex); + WTF::Locker lock(m_globalObjectsMutex); auto iter = std::find(m_globalObjects.begin(), m_globalObjects.end(), globalObject); if (iter == m_globalObjects.end()) { @@ -144,13 +145,13 @@ void SigintWatcher::registerReceiver(SigintReceiver* module) return; } - std::unique_lock lock(m_receiversMutex); + WTF::Locker lock(m_receiversMutex); m_receivers.appendIfNotContains(module); } void SigintWatcher::unregisterReceiver(SigintReceiver* module) { - std::unique_lock lock(m_receiversMutex); + WTF::Locker lock(m_receiversMutex); auto iter = std::find(m_receivers.begin(), m_receivers.end(), module); if (iter == m_receivers.end()) { @@ -185,13 +186,13 @@ SigintWatcher& SigintWatcher::get() bool SigintWatcher::signalAll() { { - std::unique_lock lock(m_receiversMutex); + WTF::Locker lock(m_receiversMutex); for (auto* receiver : m_receivers) { receiver->setSigintReceived(); } } - std::unique_lock lock(m_globalObjectsMutex); + WTF::Locker lock(m_globalObjectsMutex); if (m_globalObjects.isEmpty()) { return false; diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index e2d7aad8d2f7..3bd929a2d01e 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -97,12 +97,12 @@ class SigintWatcher { } private: - std::thread m_thread; + RefPtr m_thread; std::atomic_bool m_installed = false; std::atomic_flag m_waiting {}; Semaphore m_semaphore; - std::mutex m_globalObjectsMutex; - std::mutex m_receiversMutex; + WTF::Lock m_globalObjectsMutex; + WTF::Lock m_receiversMutex; WTF::Vector m_globalObjects; WTF::Vector m_receivers; uint32_t m_refCount = 0; From 76a3ed533ad129a1db3c40287fd45001f308d886 Mon Sep 17 00:00:00 2001 From: Kai Tamkun Date: Fri, 23 May 2025 22:19:54 -0700 Subject: [PATCH 76/76] Remove some unused includes --- src/vm/SigintWatcher.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vm/SigintWatcher.h b/src/vm/SigintWatcher.h index 3bd929a2d01e..dbdf33c1c444 100644 --- a/src/vm/SigintWatcher.h +++ b/src/vm/SigintWatcher.h @@ -6,9 +6,6 @@ #include "SigintReceiver.h" #include -#include -#include -#include namespace Bun {