#include "NodeVMSourceTextModule.h" #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; 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()) { 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; } 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), contextValue, WTFMove(sourceCode)); ptr->finishCreation(vm); return ptr; } void NodeVMSourceTextModule::destroy(JSCell* cell) { static_cast(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule(); } JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject) { if (m_moduleRequestsArray) { return m_moduleRequestsArray.get(); } VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); ParserError parserError; std::unique_ptr node = parseRootNode(vm, m_sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, StrictModeLexicallyScopedFeature, JSParserScriptMode::Module, SourceParseMode::ModuleAnalyzeMode, parserError); if (parserError.isValid()) { throwException(globalObject, scope, parserError.toErrorObject(globalObject, m_sourceCode)); return {}; } ModuleAnalyzer analyzer(globalObject, Identifier::fromString(vm, m_identifier), m_sourceCode, node->varDeclarations(), node->lexicalVariables(), AllFeatures); 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(); if (requests.isEmpty()) { return JSC::constructEmptyArray(globalObject, nullptr, 0); } 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(); 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); } m_moduleRequestsArray.set(vm, this, requestsArray); return requestsArray; } 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()); 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); ASSERT(specifierValue.isString()); ASSERT(moduleNativeValue.isObject()); 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 }); } } if (NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false)) { globalObject = nodeVmGlobalObject; } 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(); } 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 {}; } JSModuleRecord* record = m_moduleRecord.get(); JSValue result {}; 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))); }; 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(); } if (vm.hasPendingTerminationException()) { scope.clearException(); vm.clearHasTerminationRequest(); status(Status::Errored); 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())); } 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