Progress on SourceTextModule evaluation

This commit is contained in:
Kai Tamkun
2025-05-07 14:18:49 -07:00
parent d3b6ec6225
commit 550eea37e8
9 changed files with 177 additions and 87 deletions

View File

@@ -362,6 +362,53 @@ std::optional<JSC::EncodedJSValue> 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<NodeVMGlobalObject*>(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 "<name>" 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;

View File

@@ -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<JSC::Exception> exception, ThrowScope& throwScope);
std::optional<JSC::EncodedJSValue> 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.

View File

@@ -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<NodeVMSourceTextModule*>& 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<NodeVMSourceTextModule*>(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());

View File

@@ -63,10 +63,11 @@ protected:
WTF::String m_identifier;
Status m_status = Status::Unlinked;
mutable WriteBarrier<JSObject> m_namespace;
mutable WriteBarrier<JSObject> m_context;
WTF::Vector<NodeVMModuleRequest> m_moduleRequests;
mutable WTF::HashMap<WTF::String, WriteBarrier<JSObject>> 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;

View File

@@ -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 "<name>" 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<NodeVMScript*>(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<JSC::Exception> 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<NodeVMGlobalObject*>(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));
}

View File

@@ -1,13 +1,13 @@
#include "NodeVMSourceTextModule.h"
#include <print>
#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<NodeVMSourceTextModule>(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject), WTFMove(sourceCode));
NodeVMSourceTextModule* ptr = new (NotNull, allocateCell<NodeVMSourceTextModule>(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<JSObject> { 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<int32_t>(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)

View File

@@ -41,8 +41,8 @@ private:
WriteBarrier<JSModuleRecord> 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))
{
}

View File

@@ -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

View File

@@ -25,6 +25,44 @@ public:
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;