diff --git a/src/bun.js/bindings/NodeVMModule.cpp b/src/bun.js/bindings/NodeVMModule.cpp index fac2765e7f..5da7de9384 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 454b684683..79057345ea 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 @@ public: // 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 610cba73ca..03478ab0e6 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 e3d8b35397..2c8cc2d527 100644 --- a/src/bun.js/bindings/NodeVMSourceTextModule.h +++ b/src/bun.js/bindings/NodeVMSourceTextModule.h @@ -31,15 +31,21 @@ public: } 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 901b9c3886..f24e284ee1 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;