mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 11:29:02 +00:00
447 lines
15 KiB
C++
447 lines
15 KiB
C++
#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 <print>
|
|
|
|
extern "C" BunString Bun__inspect(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value);
|
|
|
|
template<>
|
|
struct std::formatter<WTF::ASCIILiteral> {
|
|
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<WTF::String> {
|
|
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<JSC::Identifier> {
|
|
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<WTF::StringPrintStream> {
|
|
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<JSC::JSValue> {
|
|
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<JSC::ErrorInstance*>(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<WTF::Vector<JSC::StackFrame>> {
|
|
constexpr auto parse(std::format_parse_context& ctx)
|
|
{
|
|
return ctx.begin();
|
|
}
|
|
|
|
auto format(const WTF::Vector<JSC::StackFrame>& 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<JSC::Exception*> {
|
|
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<uint8_t> 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<StringSourceProvider> 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<NodeVMSourceTextModule>(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject), contextValue, WTFMove(sourceCode));
|
|
ptr->finishCreation(vm);
|
|
return ptr;
|
|
}
|
|
|
|
void NodeVMSourceTextModule::destroy(JSCell* cell)
|
|
{
|
|
static_cast<NodeVMSourceTextModule*>(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<ModuleProgramNode> node = parseRootNode<ModuleProgramNode>(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<uint8_t>(request.m_attributes->type()));
|
|
break;
|
|
}
|
|
|
|
WTF::HashMap<WTF::String, WTF::String> 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<NodeVMModule*>(moduleNative)->moduleRecord(globalObject);
|
|
|
|
record->setImportedModule(globalObject, Identifier::fromString(vm, specifier), resolvedRecord);
|
|
m_resolveCache.set(WTFMove(specifier), WriteBarrier<JSObject> { 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<int32_t>(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<typename Visitor>
|
|
void NodeVMSourceTextModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
|
{
|
|
auto* vmModule = jsCast<NodeVMSourceTextModule*>(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
|