Add support for vm.constants.DONT_CONTEXTIFY (#20088)

This commit is contained in:
Kai Tamkun
2025-07-07 19:29:53 -07:00
committed by GitHub
parent 75902e6a21
commit 05e8a6dd4d
22 changed files with 1212 additions and 276 deletions

View File

@@ -2499,6 +2499,8 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject
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));
case ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING:
return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, "A dynamic import callback was not specified."_s));
default: {
break;

View File

@@ -296,6 +296,7 @@ const errors: ErrorCodeMapping = [
["ERR_VM_MODULE_DIFFERENT_CONTEXT", Error],
["ERR_VM_MODULE_LINK_FAILURE", Error],
["ERR_VM_MODULE_CACHED_DATA_REJECTED", Error],
["ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING", TypeError],
["HPE_INVALID_HEADER_TOKEN", Error],
["HPE_HEADER_OVERFLOW", Error],
];

View File

@@ -55,16 +55,23 @@
#include "JavaScriptCore/FunctionCodeBlock.h"
#include "JavaScriptCore/JIT.h"
#include "JavaScriptCore/ProgramCodeBlock.h"
#include "JavaScriptCore/GlobalObjectMethodTable.h"
#include "NodeVMScriptFetcher.h"
#include "wtf/FileHandle.h"
#include "../vm/SigintWatcher.h"
#include "JavaScriptCore/GetterSetter.h"
namespace Bun {
using namespace WebCore;
static JSInternalPromise* moduleLoaderImportModuleInner(NodeVMGlobalObject* globalObject, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin);
namespace NodeVM {
static JSInternalPromise* importModuleInner(JSGlobalObject* globalObject, JSString* moduleName, JSValue parameters, const SourceOrigin& sourceOrigin, JSValue dynamicImportCallback, JSValue owner);
bool extractCachedData(JSValue cachedDataValue, WTF::Vector<uint8_t>& outCachedData)
{
if (!cachedDataValue.isCell()) {
@@ -126,6 +133,8 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c
if (actuallyValid) {
auto exception = error.toErrorObject(globalObject, sourceCode, -1);
RETURN_IF_EXCEPTION(throwScope, nullptr);
throwException(globalObject, throwScope, exception);
return nullptr;
}
@@ -174,6 +183,7 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c
{
DeferGC deferGC(vm);
programCodeBlock = ProgramCodeBlock::create(vm, programExecutable, unlinkedProgramCodeBlock, scope);
RETURN_IF_EXCEPTION(throwScope, nullptr);
}
if (!programCodeBlock || programCodeBlock->numberOfFunctionExprs() == 0) {
@@ -193,6 +203,7 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c
RefPtr<JSC::CachedBytecode> producedBytecode = getBytecode(globalObject, programExecutable, sourceCode);
if (producedBytecode) {
JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, producedBytecode->span());
RETURN_IF_EXCEPTION(throwScope, nullptr);
function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedData"_s), buffer);
function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataProduced"_s), jsBoolean(true));
} else {
@@ -201,39 +212,84 @@ JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, c
}
} else {
function->putDirect(vm, JSC::Identifier::fromString(vm, "cachedDataRejected"_s), jsBoolean(bytecodeAccepted == TriState::False));
RETURN_IF_EXCEPTION(throwScope, nullptr);
}
return function;
}
JSInternalPromise* importModule(JSGlobalObject* globalObject, JSString* moduleNameValue, JSValue parameters, const SourceOrigin& sourceOrigin)
JSInternalPromise* importModule(JSGlobalObject* globalObject, JSString* moduleName, JSValue parameters, const SourceOrigin& sourceOrigin)
{
if (auto* fetcher = sourceOrigin.fetcher(); !fetcher || fetcher->fetcherType() != ScriptFetcher::Type::NodeVM) {
return nullptr;
}
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* fetcher = static_cast<NodeVMScriptFetcher*>(sourceOrigin.fetcher());
JSValue dynamicImportCallback = fetcher->dynamicImportCallback();
if (!dynamicImportCallback || !dynamicImportCallback.isCallable()) {
if (auto* fetcher = sourceOrigin.fetcher(); !fetcher || fetcher->fetcherType() != ScriptFetcher::Type::NodeVM) {
if (!sourceOrigin.url().isEmpty()) {
if (auto* nodeVmGlobalObject = jsDynamicCast<NodeVMGlobalObject*>(globalObject)) {
if (nodeVmGlobalObject->dynamicImportCallback()) {
RELEASE_AND_RETURN(scope, moduleLoaderImportModuleInner(nodeVmGlobalObject, globalObject->moduleLoader(), moduleName, parameters, sourceOrigin));
}
}
}
return nullptr;
}
JSFunction* owner = fetcher->owner();
auto* fetcher = static_cast<NodeVMScriptFetcher*>(sourceOrigin.fetcher());
if (fetcher->isUsingDefaultLoader()) {
return nullptr;
}
JSValue dynamicImportCallback = fetcher->dynamicImportCallback();
if (isUseMainContextDefaultLoaderConstant(globalObject, dynamicImportCallback)) {
auto defer = fetcher->temporarilyUseDefaultLoader();
Zig::GlobalObject* zigGlobalObject = defaultGlobalObject(globalObject);
RELEASE_AND_RETURN(scope, zigGlobalObject->moduleLoaderImportModule(zigGlobalObject, zigGlobalObject->moduleLoader(), moduleName, parameters, sourceOrigin));
} else if (!dynamicImportCallback || !dynamicImportCallback.isCallable()) {
throwException(globalObject, scope, createError(globalObject, ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, "A dynamic import callback was not specified."_s));
return nullptr;
}
RELEASE_AND_RETURN(scope, importModuleInner(globalObject, moduleName, parameters, sourceOrigin, dynamicImportCallback, fetcher->owner()));
}
static JSInternalPromise* importModuleInner(JSGlobalObject* globalObject, JSString* moduleName, JSValue parameters, const SourceOrigin& sourceOrigin, JSValue dynamicImportCallback, JSValue owner)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (parameters.isObject()) {
if (JSValue with = asObject(parameters)->getIfPropertyExists(globalObject, vm.propertyNames->with)) {
parameters = with;
}
RETURN_IF_EXCEPTION(scope, nullptr);
}
MarkedArgumentBuffer args;
args.append(moduleNameValue);
args.append(owner ? owner : jsUndefined());
args.append(moduleName);
if (owner) {
args.append(owner);
} else if (auto* nodeVmGlobalObject = jsDynamicCast<NodeVMGlobalObject*>(globalObject)) {
if (nodeVmGlobalObject->isNotContextified()) {
args.append(nodeVmGlobalObject->specialSandbox());
} else {
args.append(nodeVmGlobalObject->contextifiedObject());
}
} else {
args.append(jsUndefined());
}
args.append(parameters);
JSValue result = AsyncContextFrame::call(globalObject, dynamicImportCallback, jsUndefined(), args);
RETURN_IF_EXCEPTION(scope, nullptr);
if (result.isUndefinedOrNull()) {
throwException(globalObject, scope, createError(globalObject, ErrorCode::ERR_VM_MODULE_NOT_MODULE, "Provided module is not an instance of Module"_s));
return nullptr;
}
if (auto* promise = jsDynamicCast<JSInternalPromise*>(result)) {
return promise;
}
@@ -267,7 +323,7 @@ JSInternalPromise* importModule(JSGlobalObject* globalObject, JSString* moduleNa
promise = promise->then(globalObject, transformer, nullptr);
RETURN_IF_EXCEPTION(scope, nullptr);
return promise;
RELEASE_AND_RETURN(scope, promise);
}
// Helper function to create an anonymous function expression with parameters
@@ -368,9 +424,11 @@ JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::So
std::span<const uint8_t> bytes = bytecode->span();
JSC::JSUint8Array* buffer = WebCore::createBuffer(globalObject, bytes);
RETURN_IF_EXCEPTION(scope, {});
ASSERT(buffer);
if (!buffer) {
return throwVMError(globalObject, scope, "Failed to create buffer"_s);
}
return JSValue::encode(buffer);
}
@@ -386,6 +444,7 @@ bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr<JSC::Excepti
return false;
}
String stack = stack_jsval.toWTFString(globalObject);
RETURN_IF_EXCEPTION(throwScope, {});
auto& e_stack = exception->stack();
size_t stack_size = e_stack.size();
@@ -411,8 +470,12 @@ bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr<JSC::Excepti
// Returns an encoded exception if the options are invalid.
// Otherwise, returns an empty optional.
std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey)
std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey, JSValue* importer)
{
if (importer) {
*importer = jsUndefined();
}
outOptions = {};
// If options is provided, validate name and origin properties
@@ -440,10 +503,19 @@ std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globa
}
}
auto codeGenerationValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, codeGenerationKey));
JSValue importModuleDynamicallyValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "importModuleDynamically"_s));
RETURN_IF_EXCEPTION(scope, {});
if (codeGenerationValue) {
if (importModuleDynamicallyValue) {
if (importer && importModuleDynamicallyValue && (importModuleDynamicallyValue.isCallable() || isUseMainContextDefaultLoaderConstant(globalObject, importModuleDynamicallyValue))) {
*importer = importModuleDynamicallyValue;
}
}
JSValue codeGenerationValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, codeGenerationKey));
RETURN_IF_EXCEPTION(scope, {});
if (codeGenerationValue) {
if (codeGenerationValue.isUndefined()) {
return std::nullopt;
}
@@ -462,6 +534,7 @@ std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globa
}
outOptions.allowStrings = allowStringsValue.toBoolean(globalObject);
RETURN_IF_EXCEPTION(scope, {});
}
auto allowWasmValue = codeGenerationObject->getIfPropertyExists(globalObject, Identifier::fromString(vm, "wasm"_s));
@@ -472,6 +545,7 @@ std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globa
}
outOptions.allowWasm = allowWasmValue.toBoolean(globalObject);
RETURN_IF_EXCEPTION(scope, {});
}
}
@@ -500,13 +574,28 @@ NodeVMGlobalObject* getGlobalObjectFromContext(JSGlobalObject* globalObject, JSV
auto* zigGlobalObject = defaultGlobalObject(globalObject);
JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context);
if (scopeValue.isUndefined()) {
if (auto* specialSandbox = jsDynamicCast<NodeVMSpecialSandbox*>(context)) {
return specialSandbox->parentGlobal();
}
if (auto* proxy = jsDynamicCast<JSGlobalProxy*>(context)) {
if (auto* nodeVmGlobalObject = jsDynamicCast<NodeVMGlobalObject*>(proxy->target())) {
return nodeVmGlobalObject;
}
}
if (canThrow) {
INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context);
}
return nullptr;
}
NodeVMGlobalObject* nodeVmGlobalObject = jsDynamicCast<NodeVMGlobalObject*>(scopeValue);
auto* nodeVmGlobalObject = jsDynamicCast<NodeVMGlobalObject*>(scopeValue);
if (!nodeVmGlobalObject) {
nodeVmGlobalObject = jsDynamicCast<NodeVMGlobalObject*>(context);
}
if (!nodeVmGlobalObject) {
if (canThrow) {
INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context);
@@ -525,12 +614,98 @@ JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope,
return {};
}
bool isContext(JSGlobalObject* globalObject, JSValue value)
{
auto* zigGlobalObject = defaultGlobalObject(globalObject);
if (zigGlobalObject->vmModuleContextMap()->has(asObject(value))) {
return true;
}
if (value.inherits(NodeVMSpecialSandbox::info())) {
return true;
}
if (auto* proxy = jsDynamicCast<JSGlobalProxy*>(value); proxy && proxy->target()) {
return proxy->target()->inherits(NodeVMGlobalObject::info());
}
return false;
}
bool getContextArg(JSGlobalObject* globalObject, JSValue& contextArg)
{
if (contextArg.isUndefined()) {
contextArg = JSC::constructEmptyObject(globalObject);
} else if (contextArg.isSymbol()) {
Zig::GlobalObject* zigGlobalObject = defaultGlobalObject(globalObject);
if (contextArg == zigGlobalObject->m_nodeVMDontContextify.get(zigGlobalObject)) {
contextArg = JSC::constructEmptyObject(globalObject);
return true;
}
}
return false;
}
bool isUseMainContextDefaultLoaderConstant(JSGlobalObject* globalObject, JSValue value)
{
if (value.isSymbol()) {
Zig::GlobalObject* zigGlobalObject = defaultGlobalObject(globalObject);
if (value == zigGlobalObject->m_nodeVMUseMainContextDefaultLoader.get(zigGlobalObject)) {
return true;
}
}
return false;
}
} // namespace NodeVM
using namespace NodeVM;
NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure)
template<typename, JSC::SubspaceAccess mode> JSC::GCClient::IsoSubspace* NodeVMSpecialSandbox::subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<NodeVMSpecialSandbox, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForNodeVMSpecialSandbox.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMSpecialSandbox = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForNodeVMSpecialSandbox.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSpecialSandbox = std::forward<decltype(space)>(space); });
}
NodeVMSpecialSandbox* NodeVMSpecialSandbox::create(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject)
{
NodeVMSpecialSandbox* ptr = new (NotNull, allocateCell<NodeVMSpecialSandbox>(vm)) NodeVMSpecialSandbox(vm, structure, globalObject);
ptr->finishCreation(vm);
return ptr;
}
JSC::Structure* NodeVMSpecialSandbox::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
NodeVMSpecialSandbox::NodeVMSpecialSandbox(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject)
: Base(vm, structure)
{
m_parentGlobal.set(vm, this, globalObject);
}
void NodeVMSpecialSandbox::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}
const JSC::ClassInfo NodeVMSpecialSandbox::s_info = { "NodeVMSpecialSandbox"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSpecialSandbox) };
NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions contextOptions, JSValue importer)
: Base(vm, structure, &globalObjectMethodTable())
, m_dynamicImportCallback(vm, this, importer)
, m_contextOptions(contextOptions)
{
}
@@ -547,10 +722,10 @@ template<typename, JSC::SubspaceAccess mode> JSC::GCClient::IsoSubspace* NodeVMG
[](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForNodeVMGlobalObject; });
}
NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options)
NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options, JSValue importer)
{
auto* cell = new (NotNull, JSC::allocateCell<NodeVMGlobalObject>(vm)) NodeVMGlobalObject(vm, structure);
cell->finishCreation(vm, options);
auto* cell = new (NotNull, JSC::allocateCell<NodeVMGlobalObject>(vm)) NodeVMGlobalObject(vm, structure, options, importer);
cell->finishCreation(vm);
return cell;
}
@@ -560,11 +735,40 @@ 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& vm, NodeVMContextOptions options)
const JSC::GlobalObjectMethodTable& NodeVMGlobalObject::globalObjectMethodTable()
{
static const JSC::GlobalObjectMethodTable table {
&supportsRichSourceInfo,
&shouldInterruptScript,
&javaScriptRuntimeFlags,
nullptr, // queueTaskToEventLoop
nullptr, // shouldInterruptScriptBeforeTimeout,
&moduleLoaderImportModule,
nullptr, // moduleLoaderResolve
nullptr, // moduleLoaderFetch
nullptr, // moduleLoaderCreateImportMetaProperties
nullptr, // moduleLoaderEvaluate
nullptr, // promiseRejectionTracker
&reportUncaughtExceptionAtEventLoop,
&currentScriptExecutionOwner,
&scriptExecutionStatus,
nullptr, // reportViolationForUnsafeEval
nullptr, // defaultLanguage
nullptr, // compileStreaming
nullptr, // instantiateStreaming
nullptr,
&codeForEval,
&canCompileStrings,
&trustedScriptStructure,
};
return table;
}
void NodeVMGlobalObject::finishCreation(JSC::VM& 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);
setEvalEnabled(m_contextOptions.allowStrings, "Code generation from strings disallowed for this context"_s);
setWebAssemblyEnabled(m_contextOptions.allowWasm, "Wasm code generation disallowed by embedder"_s);
vm.ensureTerminationException();
}
@@ -619,18 +823,27 @@ bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, Propert
bool isFunction = value.isCallable();
if (slot.isStrictMode() && !isDeclared && isContextualStore && !isFunction) {
return Base::put(cell, globalObject, propertyName, value, slot);
RELEASE_AND_RETURN(scope, Base::put(cell, globalObject, propertyName, value, slot));
}
if (!isDeclared && value.isSymbol()) {
return Base::put(cell, globalObject, propertyName, value, slot);
RELEASE_AND_RETURN(scope, Base::put(cell, globalObject, propertyName, value, slot));
}
if (thisObject->m_contextOptions.notContextified) {
JSObject* specialSandbox = thisObject->specialSandbox();
slot.setThisValue(specialSandbox);
RELEASE_AND_RETURN(scope, specialSandbox->putInline(globalObject, propertyName, value, slot));
}
slot.setThisValue(sandbox);
bool result = sandbox->methodTable()->put(sandbox, globalObject, propertyName, value, slot);
RETURN_IF_EXCEPTION(scope, false);
if (!sandbox->methodTable()->put(sandbox, globalObject, propertyName, value, slot)) {
if (!result) {
return false;
}
RETURN_IF_EXCEPTION(scope, false);
if (isDeclaredOnSandbox && getter.isAccessor() and (getter.attributes() & PropertyAttribute::DontEnum) == 0) {
@@ -638,21 +851,54 @@ bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, Propert
}
slot.setThisValue(thisValue);
return Base::put(cell, globalObject, propertyName, value, slot);
RELEASE_AND_RETURN(scope, Base::put(cell, globalObject, propertyName, value, slot));
}
// This is copy-pasted from JSC's ProxyObject.cpp
static const ASCIILiteral s_proxyAlreadyRevokedErrorMessage { "Proxy has already been revoked. No more operations are allowed to be performed on it"_s };
bool NodeVMSpecialSandbox::getOwnPropertySlot(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
{
VM& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsCast<NodeVMSpecialSandbox*>(cell);
NodeVMGlobalObject* parentGlobal = thisObject->parentGlobal();
if (propertyName.uid()->utf8() == "globalThis") [[unlikely]] {
slot.disableCaching();
slot.setThisValue(thisObject);
slot.setValue(thisObject, slot.attributes(), thisObject);
return true;
}
bool result = parentGlobal->getOwnPropertySlot(parentGlobal, globalObject, propertyName, slot);
RETURN_IF_EXCEPTION(scope, false);
if (result) {
return true;
}
RELEASE_AND_RETURN(scope, Base::getOwnPropertySlot(cell, globalObject, propertyName, slot));
}
bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
{
VM& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsCast<NodeVMGlobalObject*>(cell);
if (thisObject->m_sandbox) {
auto* contextifiedObject = thisObject->m_sandbox.get();
bool notContextified = thisObject->isNotContextified();
if (notContextified && propertyName.uid()->utf8() == "globalThis") [[unlikely]] {
slot.disableCaching();
slot.setThisValue(thisObject);
slot.setValue(thisObject, slot.attributes(), thisObject->specialSandbox());
return true;
}
if (JSObject* contextifiedObject = thisObject->contextifiedObject()) {
slot.setThisValue(contextifiedObject);
// Unfortunately we must special case ProxyObjects. Why?
//
@@ -719,8 +965,12 @@ bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* glob
goto try_from_global;
}
if (contextifiedObject->getPropertySlot(globalObject, propertyName, slot)) {
return true;
if (!notContextified) {
bool result = contextifiedObject->getPropertySlot(globalObject, propertyName, slot);
RETURN_IF_EXCEPTION(scope, false);
if (result) {
return true;
}
}
try_from_global:
@@ -729,47 +979,61 @@ bool NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* glob
RETURN_IF_EXCEPTION(scope, false);
}
return Base::getOwnPropertySlot(cell, globalObject, propertyName, slot);
bool result = Base::getOwnPropertySlot(cell, globalObject, propertyName, slot);
RETURN_IF_EXCEPTION(scope, false);
if (result) {
return true;
}
if (thisObject->m_contextOptions.notContextified) {
JSObject* specialSandbox = thisObject->specialSandbox();
RELEASE_AND_RETURN(scope, JSObject::getOwnPropertySlot(specialSandbox, globalObject, propertyName, slot));
}
return false;
}
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<NodeVMGlobalObject*>(cell);
if (!thisObject->m_sandbox) {
return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow);
}
auto* contextifiedObject = thisObject->m_sandbox.get();
VM& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsCast<NodeVMGlobalObject*>(cell);
if (!thisObject->m_sandbox) {
RELEASE_AND_RETURN(scope, Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow));
}
auto* contextifiedObject = thisObject->isNotContextified() ? thisObject->specialSandbox() : thisObject->m_sandbox.get();
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);
RELEASE_AND_RETURN(scope, Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow));
}
if (descriptor.isAccessorDescriptor()) {
return contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow);
RELEASE_AND_RETURN(scope, JSObject::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);
RELEASE_AND_RETURN(scope, JSObject::defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow));
}
if (!contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow)) {
bool result = JSObject::defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow);
RETURN_IF_EXCEPTION(scope, false);
if (!result) {
return false;
}
return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow);
RELEASE_AND_RETURN(scope, Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow));
}
DEFINE_VISIT_CHILDREN(NodeVMGlobalObject);
@@ -780,6 +1044,8 @@ void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
Base::visitChildren(cell, visitor);
auto* thisObject = jsCast<NodeVMGlobalObject*>(cell);
visitor.append(thisObject->m_sandbox);
visitor.append(thisObject->m_specialSandbox);
visitor.append(thisObject->m_dynamicImportCallback);
}
JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame))
@@ -792,41 +1058,44 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject
return ERR::INVALID_ARG_TYPE(scope, globalObject, "code"_s, "string"_s, code);
JSValue contextArg = callFrame->argument(1);
if (contextArg.isUndefined()) {
contextArg = JSC::constructEmptyObject(globalObject);
}
bool notContextified = getContextArg(globalObject, contextArg);
if (!contextArg.isObject())
if (!contextArg.isObject()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg);
}
JSObject* sandbox = asObject(contextArg);
JSValue contextOptionsArg = callFrame->argument(2);
NodeVMContextOptions contextOptions {};
if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, contextOptionsArg, contextOptions, "contextCodeGeneration")) {
JSValue globalObjectDynamicImportCallback;
if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, contextOptionsArg, contextOptions, "contextCodeGeneration", &globalObjectDynamicImportCallback)) {
return *encodedException;
}
contextOptions.notContextified = notContextified;
// Create context and run code
auto* context = NodeVMGlobalObject::create(vm,
defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(),
contextOptions);
contextOptions, globalObjectDynamicImportCallback);
context->setContextifiedObject(sandbox);
JSValue optionsArg = callFrame->argument(2);
JSValue scriptDynamicImportCallback;
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)) {
} else if (!options.fromJS(globalObject, vm, scope, optionsArg, &scriptDynamicImportCallback)) {
RETURN_IF_EXCEPTION(scope, {});
}
RefPtr fetcher(NodeVMScriptFetcher::create(vm, options.importer));
RefPtr fetcher(NodeVMScriptFetcher::create(vm, scriptDynamicImportCallback, jsUndefined()));
SourceCode sourceCode(
JSC::StringSourceProvider::create(
@@ -862,19 +1131,21 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleRunInThisContext, (JSGlobalObject * globalObjec
return ERR::INVALID_ARG_TYPE(throwScope, globalObject, "code"_s, "string"_s, sourceStringValue);
}
auto sourceString = sourceStringValue.toWTFString(globalObject);
String sourceString = sourceStringValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(throwScope, encodedJSUndefined());
JSValue importer;
JSValue optionsArg = callFrame->argument(1);
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)) {
} else if (!options.fromJS(globalObject, vm, throwScope, optionsArg, &importer)) {
RETURN_IF_EXCEPTION(throwScope, encodedJSUndefined());
}
RefPtr fetcher(NodeVMScriptFetcher::create(vm, options.importer));
RefPtr fetcher(NodeVMScriptFetcher::create(vm, importer, jsUndefined()));
SourceCode source(
JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename), *fetcher), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)),
@@ -927,7 +1198,9 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject
// Get options argument
JSValue optionsArg = callFrame->argument(2);
CompileFunctionOptions options;
if (!options.fromJS(globalObject, vm, scope, optionsArg)) {
JSValue importer;
if (!options.fromJS(globalObject, vm, scope, optionsArg, &importer)) {
RETURN_IF_EXCEPTION(scope, {});
options = {};
options.parsingContext = globalObject;
@@ -949,7 +1222,7 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject
// Add the function body
constructFunctionArgs.append(jsString(vm, sourceString));
RefPtr fetcher(NodeVMScriptFetcher::create(vm, options.importer));
RefPtr fetcher(NodeVMScriptFetcher::create(vm, importer, jsUndefined()));
// Create the source origin
SourceOrigin sourceOrigin { WTF::URL::fileURLWithFileSystemPath(options.filename), *fetcher };
@@ -983,14 +1256,18 @@ JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject
// Create the function using constructAnonymousFunction with the appropriate scope chain
JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, WTFMove(options), JSC::SourceTaintedOrigin::Untainted, functionScope);
fetcher->owner(vm, function);
RETURN_IF_EXCEPTION(scope, {});
if (!function) {
return throwVMError(globalObject, scope, "Failed to compile function"_s);
}
fetcher->owner(vm, function);
if (!function) {
return throwVMError(globalObject, scope, "Failed to compile function"_s);
}
return JSValue::encode(function);
}
@@ -999,31 +1276,16 @@ 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(),
NodeVMContextOptions {});
// 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);
NodeVMContextOptions contextOptions {};
JSValue contextArg = callFrame->argument(0);
if (contextArg.isUndefined()) {
contextArg = JSC::constructEmptyObject(globalObject);
}
bool notContextified = getContextArg(globalObject, contextArg);
RETURN_IF_EXCEPTION(scope, {});
if (!contextArg.isObject()) {
return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg);
@@ -1036,25 +1298,48 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject,
return ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg);
}
NodeVMContextOptions contextOptions {};
JSValue importer;
if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, optionsArg, contextOptions, "codeGeneration")) {
if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, optionsArg, contextOptions, "codeGeneration", &importer)) {
return *encodedException;
}
contextOptions.notContextified = notContextified;
JSObject* sandbox = asObject(contextArg);
if (isContext(globalObject, sandbox)) {
if (auto* proxy = jsDynamicCast<JSC::JSGlobalProxy*>(sandbox)) {
if (auto* targetContext = jsDynamicCast<NodeVMGlobalObject*>(proxy->target())) {
if (targetContext->isNotContextified()) {
return JSValue::encode(targetContext->specialSandbox());
}
}
}
return JSValue::encode(sandbox);
}
auto* zigGlobalObject = defaultGlobalObject(globalObject);
auto* targetContext = NodeVMGlobalObject::create(vm,
defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure(),
contextOptions);
zigGlobalObject->NodeVMGlobalObjectStructure(),
contextOptions, importer);
RETURN_IF_EXCEPTION(scope, {});
// 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);
if (notContextified) {
auto* specialSandbox = NodeVMSpecialSandbox::create(vm, zigGlobalObject->NodeVMSpecialSandboxStructure(), targetContext);
RETURN_IF_EXCEPTION(scope, {});
targetContext->setSpecialSandbox(specialSandbox);
return JSValue::encode(targetContext->specialSandbox());
}
return JSValue::encode(sandbox);
}
@@ -1064,39 +1349,12 @@ JSC_DEFINE_HOST_FUNCTION(vmModule_isContext, (JSGlobalObject * globalObject, Cal
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));
return JSValue::encode(jsBoolean(isContext(globalObject, contextArg)));
}
// NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure)
// {
// auto* obj = new (NotNull, allocateCell<NodeVMGlobalObject>(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<typename Visitor>
// void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
// {
// Base::visitChildren(cell, visitor);
// // auto* thisObject = jsCast<NodeVMGlobalObject*>(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)
@@ -1118,19 +1376,55 @@ bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObje
return Base::deleteProperty(cell, globalObject, propertyName, slot);
}
static JSInternalPromise* moduleLoaderImportModuleInner(NodeVMGlobalObject* globalObject, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure());
if (sourceOrigin.fetcher() == nullptr && sourceOrigin.url().isEmpty()) {
if (globalObject->dynamicImportCallback().isCallable()) {
return NodeVM::importModuleInner(globalObject, moduleName, parameters, sourceOrigin, globalObject->dynamicImportCallback(), JSValue {});
}
promise->reject(globalObject, createError(globalObject, ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, "A dynamic import callback was not specified."_s));
return promise;
}
// Default behavior copied from JSModuleLoader::importModule
auto moduleNameString = moduleName->value(globalObject);
RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope));
scope.release();
promise->reject(globalObject, createError(globalObject, makeString("Could not import the module '"_s, moduleNameString.data, "'."_s)));
return promise;
}
JSInternalPromise* NodeVMGlobalObject::moduleLoaderImportModule(JSGlobalObject* globalObject, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin)
{
auto* nodeVmGlobalObject = static_cast<NodeVMGlobalObject*>(globalObject);
if (JSInternalPromise* result = NodeVM::importModule(nodeVmGlobalObject, moduleName, parameters, sourceOrigin)) {
return result;
}
return moduleLoaderImportModuleInner(nodeVmGlobalObject, moduleLoader, moduleName, parameters, sourceOrigin);
}
void NodeVMGlobalObject::getOwnPropertyNames(JSObject* cell, JSGlobalObject* globalObject, JSC::PropertyNameArray& propertyNames, JSC::DontEnumPropertiesMode mode)
{
auto* thisObject = jsCast<NodeVMGlobalObject*>(cell);
if (thisObject->m_sandbox) {
thisObject->m_sandbox->getOwnPropertyNames(
thisObject->m_sandbox.get(),
globalObject,
propertyNames,
mode);
VM& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
if (thisObject->m_sandbox) [[likely]] {
thisObject->m_sandbox->getOwnPropertyNames(thisObject->m_sandbox.get(), globalObject, propertyNames, mode);
RETURN_IF_EXCEPTION(scope, );
}
Base::getOwnPropertyNames(cell, globalObject, propertyNames, mode);
RELEASE_AND_RETURN(scope, Base::getOwnPropertyNames(cell, globalObject, propertyNames, mode));
}
JSC_DEFINE_HOST_FUNCTION(vmIsModuleNamespaceObject, (JSGlobalObject * globalObject, CallFrame* callFrame))
@@ -1150,7 +1444,7 @@ JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject)
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);
JSC::JSFunction::create(vm, globalObject, 0, "createContext"_s, vmModule_createContext, ImplementationVisibility::Public, Intrinsic::NoIntrinsic, vmModule_createContext), 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);
@@ -1190,11 +1484,24 @@ JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject)
obj->putDirect(
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kSynthetic"_s)),
JSC::jsNumber(static_cast<unsigned>(NodeVMModule::Type::Synthetic)), 0);
obj->putDirect(
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "DONT_CONTEXTIFY"_s)),
globalObject->m_nodeVMDontContextify.get(globalObject), 0);
obj->putDirect(
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "USE_MAIN_CONTEXT_DEFAULT_LOADER"_s)),
globalObject->m_nodeVMUseMainContextDefaultLoader.get(globalObject), 0);
return obj;
}
void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject)
{
globalObject->m_nodeVMDontContextify.initLater([](const LazyProperty<JSC::JSGlobalObject, Symbol>::Initializer& init) {
init.set(JSC::Symbol::createWithDescription(init.vm, "vm_dont_contextify"_s));
});
globalObject->m_nodeVMUseMainContextDefaultLoader.initLater([](const LazyProperty<JSC::JSGlobalObject, Symbol>::Initializer& init) {
init.set(JSC::Symbol::createWithDescription(init.vm, "vm_use_main_context_default_loader"_s));
});
globalObject->m_NodeVMScriptClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
auto prototype = NodeVMScript::createPrototype(init.vm, init.global);
@@ -1238,6 +1545,11 @@ void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject)
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
init.set(createNodeVMGlobalObjectStructure(init.vm));
});
globalObject->m_cachedNodeVMSpecialSandboxStructure.initLater(
[](const JSC::LazyProperty<JSC::JSGlobalObject, Structure>::Initializer& init) {
init.set(NodeVMSpecialSandbox::createStructure(init.vm, init.owner, init.owner->objectPrototype())); // TODO(@heimskr): or maybe jsNull() for the prototype?
});
}
BaseVMOptions::BaseVMOptions(String filename)
@@ -1376,8 +1688,12 @@ bool BaseVMOptions::validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM&
return false;
}
bool CompileFunctionOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg)
bool CompileFunctionOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg, JSValue* importer)
{
if (importer) {
*importer = jsUndefined();
}
this->parsingContext = globalObject;
bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg);
RETURN_IF_EXCEPTION(scope, false);
@@ -1448,8 +1764,10 @@ bool CompileFunctionOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM&
JSValue importModuleDynamicallyValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "importModuleDynamically"_s));
RETURN_IF_EXCEPTION(scope, {});
if (importModuleDynamicallyValue && importModuleDynamicallyValue.isCallable()) {
this->importer = importModuleDynamicallyValue;
if (importModuleDynamicallyValue && (importModuleDynamicallyValue.isCallable() || isUseMainContextDefaultLoaderConstant(globalObject, importModuleDynamicallyValue))) {
if (importer) {
*importer = importModuleDynamicallyValue;
}
any = true;
}
}

View File

@@ -25,65 +25,19 @@ RefPtr<JSC::CachedBytecode> getBytecode(JSGlobalObject* globalObject, JSC::Modul
bool extractCachedData(JSValue cachedDataValue, WTF::Vector<uint8_t>& 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<JSC::Exception> exception, ThrowScope& throwScope);
std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey);
std::optional<JSC::EncodedJSValue> getNodeVMContextOptions(JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSValue optionsArg, NodeVMContextOptions& outOptions, ASCIILiteral codeGenerationKey, JSValue* importer);
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.
JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, CompileFunctionOptions&& options, JSC::SourceTaintedOrigin sourceTaintOrigin, JSC::JSScope* scope);
JSInternalPromise* importModule(JSGlobalObject* globalObject, JSString* moduleNameValue, JSValue parameters, const SourceOrigin& sourceOrigin);
bool isContext(JSC::JSGlobalObject* globalObject, JSValue);
bool getContextArg(JSC::JSGlobalObject* globalObject, JSValue& contextArg);
bool isUseMainContextDefaultLoaderConstant(JSC::JSGlobalObject* globalObject, JSValue value);
} // namespace NodeVM
// This class represents a sandboxed global object for vm contexts
class NodeVMGlobalObject final : public Bun::GlobalScope {
using Base = Bun::GlobalScope;
public:
static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesGetOwnPropertySlot | JSC::OverridesPut | JSC::OverridesGetOwnPropertyNames | JSC::GetOwnPropertySlotMayBeWrongAboutDontEnum | JSC::ProhibitsPropertyCaching;
static constexpr JSC::DestructionMode needsDestruction = NeedsDestruction;
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm);
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&, 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&);
static bool put(JSCell*, JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&);
static void getOwnPropertyNames(JSObject*, JSGlobalObject*, JSC::PropertyNameArray&, JSC::DontEnumPropertiesMode);
static bool defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow);
static bool deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot);
private:
NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure);
~NodeVMGlobalObject();
// The contextified object that acts as the global proxy
mutable JSC::WriteBarrier<JSC::JSObject> m_sandbox;
};
// Helper functions to create vm contexts and run code
JSC::JSValue createNodeVMBinding(Zig::GlobalObject*);
Structure* createNodeVMGlobalObjectStructure(JSC::VM&);
void configureNodeVM(JSC::VM&, Zig::GlobalObject*);
// VM module functions
JSC_DECLARE_HOST_FUNCTION(vmModule_createContext);
JSC_DECLARE_HOST_FUNCTION(vmModule_isContext);
JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext);
JSC_DECLARE_HOST_FUNCTION(vmModuleRunInThisContext);
class BaseVMOptions {
public:
String filename;
@@ -106,18 +60,103 @@ public:
WTF::Vector<uint8_t> cachedData;
JSGlobalObject* parsingContext = nullptr;
JSValue contextExtensions {};
JSValue importer {};
bool produceCachedData = false;
using BaseVMOptions::BaseVMOptions;
bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg);
bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg, JSValue* importer);
};
class NodeVMContextOptions final {
public:
bool allowStrings = true;
bool allowWasm = true;
bool notContextified = false;
};
class NodeVMGlobalObject;
class NodeVMSpecialSandbox final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesGetOwnPropertySlot;
static NodeVMSpecialSandbox* create(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject);
DECLARE_INFO;
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm);
static Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype);
static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&);
NodeVMGlobalObject* parentGlobal() const { return m_parentGlobal.get(); }
private:
WriteBarrier<NodeVMGlobalObject> m_parentGlobal;
NodeVMSpecialSandbox(VM& vm, Structure* structure, NodeVMGlobalObject* globalObject);
void finishCreation(VM&);
};
// This class represents a sandboxed global object for vm contexts
class NodeVMGlobalObject final : public Bun::GlobalScope {
public:
using Base = Bun::GlobalScope;
static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesGetOwnPropertySlot | JSC::OverridesPut | JSC::OverridesGetOwnPropertyNames | JSC::GetOwnPropertySlotMayBeWrongAboutDontEnum | JSC::ProhibitsPropertyCaching;
static constexpr JSC::DestructionMode needsDestruction = NeedsDestruction;
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm);
static NodeVMGlobalObject* create(JSC::VM& vm, JSC::Structure* structure, NodeVMContextOptions options, JSValue importer);
static Structure* createStructure(JSC::VM& vm, JSC::JSValue prototype);
static const JSC::GlobalObjectMethodTable& globalObjectMethodTable();
DECLARE_INFO;
DECLARE_VISIT_CHILDREN;
~NodeVMGlobalObject();
void finishCreation(JSC::VM&);
static void destroy(JSCell* cell);
void setContextifiedObject(JSC::JSObject* contextifiedObject);
JSObject* contextifiedObject() const { return m_sandbox.get(); }
void clearContextifiedObject();
void sigintReceived();
bool isNotContextified() const { return m_contextOptions.notContextified; }
NodeVMSpecialSandbox* specialSandbox() const { return m_specialSandbox.get(); }
void setSpecialSandbox(NodeVMSpecialSandbox* sandbox) { m_specialSandbox.set(vm(), this, sandbox); }
JSValue dynamicImportCallback() const { return m_dynamicImportCallback.get(); }
// Override property access to delegate to contextified object
static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, JSC::PropertyName, JSC::PropertySlot&);
static bool put(JSCell*, JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&);
static void getOwnPropertyNames(JSObject*, JSGlobalObject*, JSC::PropertyNameArray&, JSC::DontEnumPropertiesMode);
static bool defineOwnProperty(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow);
static bool deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot);
static JSC::JSInternalPromise* moduleLoaderImportModule(JSGlobalObject*, JSC::JSModuleLoader*, JSC::JSString* moduleNameValue, JSC::JSValue parameters, const JSC::SourceOrigin&);
private:
// The contextified object that acts as the global proxy
WriteBarrier<JSObject> m_sandbox;
// A special object used when the context is not contextified.
WriteBarrier<NodeVMSpecialSandbox> m_specialSandbox;
WriteBarrier<Unknown> m_dynamicImportCallback;
NodeVMContextOptions m_contextOptions {};
NodeVMGlobalObject(VM& vm, Structure* structure, NodeVMContextOptions contextOptions, JSValue importer);
};
// Helper functions to create vm contexts and run code
JSC::JSValue createNodeVMBinding(Zig::GlobalObject*);
Structure* createNodeVMGlobalObjectStructure(JSC::VM&);
void configureNodeVM(JSC::VM&, Zig::GlobalObject*);
// VM module functions
JSC_DECLARE_HOST_FUNCTION(vmModule_createContext);
JSC_DECLARE_HOST_FUNCTION(vmModule_isContext);
JSC_DECLARE_HOST_FUNCTION(vmModuleRunInNewContext);
JSC_DECLARE_HOST_FUNCTION(vmModuleRunInThisContext);
} // namespace Bun

View File

@@ -29,15 +29,20 @@ JSArray* NodeVMModuleRequest::toJS(JSGlobalObject* globalObject) const
JSArray* array = JSC::constructEmptyArray(globalObject, nullptr, 2);
RETURN_IF_EXCEPTION(scope, {});
array->putDirectIndex(globalObject, 0, JSC::jsString(globalObject->vm(), m_specifier));
RETURN_IF_EXCEPTION(scope, {});
JSObject* attributes = JSC::constructEmptyObject(globalObject);
RETURN_IF_EXCEPTION(scope, {});
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);
RETURN_IF_EXCEPTION(scope, {});
}
array->putDirectIndex(globalObject, 1, attributes);
RETURN_IF_EXCEPTION(scope, {});
return array;
}
@@ -73,6 +78,7 @@ JSValue NodeVMModule::evaluate(JSGlobalObject* globalObject, uint32_t timeout, b
JSValue result {};
NodeVMGlobalObject* nodeVmGlobalObject = NodeVM::getGlobalObjectFromContext(globalObject, m_context.get(), false);
RETURN_IF_EXCEPTION(scope, {});
if (nodeVmGlobalObject) {
globalObject = nodeVmGlobalObject;
@@ -82,13 +88,12 @@ JSValue NodeVMModule::evaluate(JSGlobalObject* globalObject, uint32_t timeout, b
if (sourceTextThis) {
status(Status::Evaluating);
evaluateDependencies(globalObject, record, timeout, breakOnSigint);
RETURN_IF_EXCEPTION(scope, );
sourceTextThis->initializeImportMeta(globalObject);
} else if (syntheticThis) {
syntheticThis->evaluate(globalObject);
}
if (scope.exception()) [[unlikely]] {
return;
}
RETURN_IF_EXCEPTION(scope, );
result = record->evaluate(globalObject, jsUndefined(), jsNumber(static_cast<int32_t>(JSGenerator::ResumeMode::NormalMode)));
};
@@ -206,11 +211,11 @@ NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObjec
JSValue disambiguator = args.at(2);
if (disambiguator.isString()) {
return NodeVMSourceTextModule::create(vm, globalObject, args);
RELEASE_AND_RETURN(scope, NodeVMSourceTextModule::create(vm, globalObject, args));
}
if (disambiguator.inherits(JSArray::info())) {
return NodeVMSyntheticModule::create(vm, globalObject, args);
RELEASE_AND_RETURN(scope, NodeVMSyntheticModule::create(vm, globalObject, args));
}
throwArgumentTypeError(*globalObject, scope, 2, "sourceText or syntheticExportNames"_s, "Module"_s, "Module"_s, "string or array"_s);
@@ -227,11 +232,14 @@ JSModuleNamespaceObject* NodeVMModule::namespaceObject(JSC::JSGlobalObject* glob
if (auto* thisObject = jsDynamicCast<NodeVMModule*>(this)) {
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
object = thisObject->moduleRecord(globalObject)->getModuleNamespace(globalObject);
AbstractModuleRecord* record = thisObject->moduleRecord(globalObject);
RETURN_IF_EXCEPTION(scope, {});
object = record->getModuleNamespace(globalObject);
RETURN_IF_EXCEPTION(scope, {});
if (object) {
namespaceObject(vm, object);
}
RETURN_IF_EXCEPTION(scope, {});
} else {
RELEASE_ASSERT_NOT_REACHED_WITH_MESSAGE("NodeVMModule::namespaceObject called on an unsupported module type (%s)", classInfo()->className.characters());
}
@@ -333,7 +341,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * glob
auto scope = DECLARE_THROW_SCOPE(vm);
if (auto* thisObject = jsDynamicCast<NodeVMModule*>(callFrame->thisValue())) {
return JSValue::encode(thisObject->namespaceObject(globalObject));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->namespaceObject(globalObject)));
}
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s);
@@ -366,6 +374,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject *
if (auto* sourceTextModule = jsDynamicCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
sourceTextModule->ensureModuleRecord(globalObject);
RETURN_IF_EXCEPTION(scope, {});
}
const WTF::Vector<NodeVMModuleRequest>& requests = thisObject->moduleRequests();
@@ -399,11 +408,11 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalOb
}
if (auto* thisObject = jsDynamicCast<NodeVMModule*>(callFrame->thisValue())) {
return JSValue::encode(thisObject->evaluate(globalObject, timeout, breakOnSigint));
} else {
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s);
return {};
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->evaluate(globalObject, timeout, breakOnSigint)));
}
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))
@@ -423,14 +432,11 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject
}
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
return JSValue::encode(thisObject->link(globalObject, specifiers, moduleNatives, callFrame->argument(2)));
// return thisObject->link(globalObject, linker);
// } else if (auto* thisObject = jsDynamicCast<NodeVMSyntheticModule*>(callFrame->thisValue())) {
// return thisObject->link(globalObject, specifiers, moduleNatives);
} else {
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s);
return {};
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->link(globalObject, specifiers, moduleNatives, callFrame->argument(2))));
}
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule"_s);
return {};
}
JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
@@ -439,11 +445,11 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globa
auto scope = DECLARE_THROW_SCOPE(vm);
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
return JSValue::encode(thisObject->instantiate(globalObject));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->instantiate(globalObject)));
}
if (auto* thisObject = jsDynamicCast<NodeVMSyntheticModule*>(callFrame->thisValue())) {
return JSValue::encode(thisObject->instantiate(globalObject));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->instantiate(globalObject)));
}
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule or SyntheticModule"_s);
@@ -455,7 +461,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalO
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (auto* thisObject = jsCast<NodeVMSyntheticModule*>(callFrame->thisValue())) {
if (auto* thisObject = jsDynamicCast<NodeVMSyntheticModule*>(callFrame->thisValue())) {
JSValue nameValue = callFrame->argument(0);
if (!nameValue.isString()) {
Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "name"_str, "string"_s, nameValue);
@@ -478,7 +484,7 @@ JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject *
auto scope = DECLARE_THROW_SCOPE(vm);
if (auto* thisObject = jsDynamicCast<NodeVMSourceTextModule*>(callFrame->thisValue())) {
return JSValue::encode(thisObject->cachedData(globalObject));
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->cachedData(globalObject)));
}
throwTypeError(globalObject, scope, "This function must be called on a SourceTextModule"_s);
@@ -517,6 +523,7 @@ constructModule(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT
ArgList args(callFrame);
NodeVMModule* module = NodeVMModule::create(vm, globalObject, args);
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(module);
}

View File

@@ -9,6 +9,7 @@
#include "JavaScriptCore/ProgramCodeBlock.h"
#include "JavaScriptCore/SourceCodeKey.h"
#include "NodeVMScriptFetcher.h"
#include "../vm/SigintWatcher.h"
#include <bit>
@@ -16,8 +17,12 @@
namespace Bun {
using namespace NodeVM;
bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg)
bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg, JSValue* importer)
{
if (importer) {
*importer = jsUndefined();
}
bool any = BaseVMOptions::fromJS(globalObject, vm, scope, optionsArg);
RETURN_IF_EXCEPTION(scope, false);
@@ -64,9 +69,16 @@ bool ScriptOptions::fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::
JSValue importModuleDynamicallyValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "importModuleDynamically"_s));
RETURN_IF_EXCEPTION(scope, {});
if (importModuleDynamicallyValue && importModuleDynamicallyValue.isCallable()) {
this->importer = importModuleDynamicallyValue;
any = true;
if (importModuleDynamicallyValue) {
if ((importModuleDynamicallyValue.isCallable() || isUseMainContextDefaultLoaderConstant(globalObject, importModuleDynamicallyValue))) {
if (importer) {
*importer = importModuleDynamicallyValue;
}
any = true;
} else if (!importModuleDynamicallyValue.isUndefined()) {
ERR::INVALID_ARG_TYPE(scope, globalObject, "options.importModuleDynamically"_s, "function"_s, importModuleDynamicallyValue);
return false;
}
}
}
@@ -80,15 +92,22 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT
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());
String sourceString;
if (sourceArg.isUndefined()) {
sourceString = emptyString();
} else {
sourceString = sourceArg.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, encodedJSUndefined());
}
JSValue optionsArg = args.at(1);
ScriptOptions options(""_s);
JSValue importer;
if (optionsArg.isString()) {
options.filename = optionsArg.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
} else if (!options.fromJS(globalObject, vm, scope, optionsArg)) {
} else if (!options.fromJS(globalObject, vm, scope, optionsArg, &importer)) {
RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
}
@@ -108,13 +127,18 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT
scope.release();
}
SourceCode source = makeSource(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), JSC::SourceTaintedOrigin::Untainted, options.filename, TextPosition(options.lineOffset, options.columnOffset));
RefPtr fetcher(NodeVMScriptFetcher::create(vm, importer, jsUndefined()));
SourceCode source = makeSource(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename), *fetcher), JSC::SourceTaintedOrigin::Untainted, options.filename, TextPosition(options.lineOffset, options.columnOffset));
RETURN_IF_EXCEPTION(scope, {});
const bool produceCachedData = options.produceCachedData;
auto filename = options.filename;
NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, WTFMove(source), WTFMove(options));
RETURN_IF_EXCEPTION(scope, {});
fetcher->owner(vm, script);
WTF::Vector<uint8_t>& cachedData = script->cachedData();
@@ -139,6 +163,7 @@ constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newT
// JSC::ProgramCodeBlock::create() requires GC to be deferred.
DeferGC deferGC(vm);
codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope);
RETURN_IF_EXCEPTION(scope, {});
}
JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail);
if (compilationResult != JSC::CompilationResult::CompilationFailed) {
@@ -199,6 +224,9 @@ JSC::JSUint8Array* NodeVMScript::getBytecodeBuffer()
std::span<const uint8_t> bytes = m_cachedBytecode->span();
m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject(), bytes));
if (!m_cachedBytecodeBuffer) {
return nullptr;
}
}
ASSERT(m_cachedBytecodeBuffer);
@@ -333,6 +361,8 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM
run();
}
RETURN_IF_EXCEPTION(scope, {});
if (options.timeout) {
vm.watchdog()->setTimeLimit(WTF::Seconds::fromMilliseconds(*oldLimit));
}
@@ -351,7 +381,8 @@ static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVM
return {};
}
return JSValue::encode(result);
RETURN_IF_EXCEPTION(scope, {});
RELEASE_AND_RETURN(scope, JSValue::encode(result));
}
JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame))
@@ -414,7 +445,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject,
}
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(result);
RELEASE_AND_RETURN(scope, JSValue::encode(result));
}
JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName))
@@ -433,7 +464,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject,
return encodedJSUndefined();
}
return JSValue::encode(jsString(vm, url));
RELEASE_AND_RETURN(scope, JSValue::encode(jsString(vm, url)));
}
JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName))
@@ -447,10 +478,10 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JS
}
if (auto* buffer = script->getBytecodeBuffer()) {
return JSValue::encode(buffer);
RELEASE_AND_RETURN(scope, JSValue::encode(buffer));
}
return JSValue::encode(jsUndefined());
RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined()));
}
JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName))
@@ -463,7 +494,7 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalOb
return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s);
}
return JSValue::encode(jsBoolean(script->cachedDataProduced()));
RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(script->cachedDataProduced())));
}
JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName))
@@ -478,11 +509,11 @@ JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalOb
switch (script->cachedDataRejected()) {
case TriState::True:
return JSValue::encode(jsBoolean(true));
RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(true)));
case TriState::False:
return JSValue::encode(jsBoolean(false));
RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(false)));
default:
return JSValue::encode(jsUndefined());
RELEASE_AND_RETURN(scope, encodedJSUndefined());
}
}
@@ -498,7 +529,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject,
}
const JSC::SourceCode& source = script->source();
return createCachedData(globalObject, source);
RELEASE_AND_RETURN(scope, createCachedData(globalObject, source));
}
JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame))
@@ -519,7 +550,7 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, Cal
JSObject* context = asObject(contextArg);
ASSERT(nodeVmGlobalObject != nullptr);
return runInContext(nodeVmGlobalObject, script, context, args.at(1));
RELEASE_AND_RETURN(scope, runInContext(nodeVmGlobalObject, script, context, args.at(1)));
}
JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame))
@@ -527,8 +558,6 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject,
VM& vm = JSC::getVM(globalObject);
NodeVMScript* script = jsDynamicCast<NodeVMScript*>(callFrame->thisValue());
JSValue contextObjectValue = callFrame->argument(0);
// TODO: options
// JSValue optionsObjectValue = callFrame->argument(1);
auto scope = DECLARE_THROW_SCOPE(vm);
if (!script) {
@@ -536,24 +565,36 @@ JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject,
return {};
}
if (contextObjectValue.isUndefined()) {
contextObjectValue = JSC::constructEmptyObject(globalObject);
}
bool notContextified = NodeVM::getContextArg(globalObject, contextObjectValue);
if (!contextObjectValue || !contextObjectValue.isObject()) [[unlikely]] {
throwTypeError(globalObject, scope, "Context must be an object"_s);
return {};
}
// we don't care about options for now
// TODO: options
// bool didThrow = false;
JSValue contextOptionsArg = callFrame->argument(1);
NodeVMContextOptions contextOptions {};
JSValue importer;
auto* zigGlobal = defaultGlobalObject(globalObject);
if (auto encodedException = getNodeVMContextOptions(globalObject, vm, scope, contextOptionsArg, contextOptions, "contextCodeGeneration", &importer)) {
return *encodedException;
}
contextOptions.notContextified = notContextified;
auto* zigGlobalObject = defaultGlobalObject(globalObject);
JSObject* context = asObject(contextObjectValue);
auto* targetContext = NodeVMGlobalObject::create(vm,
zigGlobal->NodeVMGlobalObjectStructure(),
{});
zigGlobalObject->NodeVMGlobalObjectStructure(),
contextOptions, importer);
RETURN_IF_EXCEPTION(scope, {});
if (notContextified) {
auto* specialSandbox = NodeVMSpecialSandbox::create(vm, zigGlobalObject->NodeVMSpecialSandboxStructure(), targetContext);
RETURN_IF_EXCEPTION(scope, {});
targetContext->setSpecialSandbox(specialSandbox);
RELEASE_AND_RETURN(scope, runInContext(targetContext, script, targetContext->specialSandbox(), callFrame->argument(1)));
}
RELEASE_AND_RETURN(scope, runInContext(targetContext, script, context, callFrame->argument(1)));
}

View File

@@ -10,12 +10,11 @@ class ScriptOptions : public BaseVMOptions {
public:
WTF::Vector<uint8_t> cachedData;
std::optional<int64_t> timeout = std::nullopt;
JSValue importer {};
bool produceCachedData = false;
using BaseVMOptions::BaseVMOptions;
bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg);
bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg, JSValue* importer);
};
class NodeVMScriptConstructor final : public JSC::InternalFunction {

View File

@@ -3,27 +3,39 @@
#include "root.h"
#include <JavaScriptCore/ScriptFetcher.h>
#include <wtf/Scope.h>
namespace Bun {
// The presence of this class in a JSFunction's sourceOrigin indicates that the function was compiled by Bun's node:vm implementation.
class NodeVMScriptFetcher : public JSC::ScriptFetcher {
public:
static Ref<NodeVMScriptFetcher> create(JSC::VM& vm, JSC::JSValue dynamicImportCallback) { return adoptRef(*new NodeVMScriptFetcher(vm, dynamicImportCallback)); }
static Ref<NodeVMScriptFetcher> create(JSC::VM& vm, JSC::JSValue dynamicImportCallback, JSC::JSValue owner) { return adoptRef(*new NodeVMScriptFetcher(vm, dynamicImportCallback, owner)); }
Type fetcherType() const final { return Type::NodeVM; }
JSC::JSValue dynamicImportCallback() const { return m_dynamicImportCallback.get(); }
JSC::JSFunction* owner() const { return m_owner.get(); }
void owner(JSC::VM& vm, JSC::JSFunction* value) { m_owner.set(vm, value); }
JSC::JSValue owner() const { return m_owner.get(); }
void owner(JSC::VM& vm, JSC::JSValue value) { m_owner.set(vm, value); }
bool isUsingDefaultLoader() const { return m_isUsingDefaultLoader; }
auto temporarilyUseDefaultLoader()
{
m_isUsingDefaultLoader = true;
return makeScopeExit([this] {
m_isUsingDefaultLoader = false;
});
}
private:
JSC::Strong<JSC::Unknown> m_dynamicImportCallback;
JSC::Strong<JSC::JSFunction> m_owner;
JSC::Strong<JSC::Unknown> m_owner;
bool m_isUsingDefaultLoader = false;
NodeVMScriptFetcher(JSC::VM& vm, JSC::JSValue dynamicImportCallback)
NodeVMScriptFetcher(JSC::VM& vm, JSC::JSValue dynamicImportCallback, JSC::JSValue owner)
: m_dynamicImportCallback(vm, dynamicImportCallback)
, m_owner(vm, owner)
{
}
};

View File

@@ -1,3 +1,4 @@
#include "NodeVMScriptFetcher.h"
#include "NodeVMSourceTextModule.h"
#include "NodeVMSyntheticModule.h"
@@ -77,16 +78,35 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g
return nullptr;
}
uint32_t lineOffset = lineOffsetValue.toUInt32(globalObject);
uint32_t columnOffset = columnOffsetValue.toUInt32(globalObject);
JSValue dynamicImportCallback = args.at(8);
if (!dynamicImportCallback.isUndefined() && !dynamicImportCallback.isCallable()) {
throwArgumentTypeError(*globalObject, scope, 8, "dynamicImportCallback"_s, "Module"_s, "Module"_s, "function"_s);
return nullptr;
}
Ref<StringSourceProvider> sourceProvider = StringSourceProvider::create(sourceTextValue.toWTFString(globalObject), SourceOrigin {}, String {}, SourceTaintedOrigin::Untainted,
uint32_t lineOffset = lineOffsetValue.toUInt32(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
uint32_t columnOffset = columnOffsetValue.toUInt32(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
RefPtr fetcher(NodeVMScriptFetcher::create(vm, dynamicImportCallback, moduleWrapper));
RETURN_IF_EXCEPTION(scope, nullptr);
SourceOrigin sourceOrigin { {}, *fetcher };
WTF::String sourceText = sourceTextValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
Ref<StringSourceProvider> sourceProvider = StringSourceProvider::create(WTFMove(sourceText), 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), moduleWrapper);
WTF::String identifier = identifierValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
NodeVMSourceTextModule* ptr = new (NotNull, allocateCell<NodeVMSourceTextModule>(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), WTFMove(identifier), contextValue, WTFMove(sourceCode), moduleWrapper);
RETURN_IF_EXCEPTION(scope, nullptr);
ptr->finishCreation(vm);
if (!initializeImportMeta.isUndefined()) {
@@ -111,7 +131,9 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g
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 = CachedBytecode::create(std::span(cachedData), nullptr, {});
RETURN_IF_EXCEPTION(scope, nullptr);
UnlinkedModuleProgramCodeBlock* unlinkedBlock = decodeCodeBlock<UnlinkedModuleProgramCodeBlock>(vm, key, WTFMove(cachedBytecode));
RETURN_IF_EXCEPTION(scope, nullptr);
if (unlinkedBlock) {
JSScope* jsScope = globalObject->globalScope();
@@ -120,9 +142,11 @@ NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* g
// JSC::ProgramCodeBlock::create() requires GC to be deferred.
DeferGC deferGC(vm);
codeBlock = ModuleProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope);
RETURN_IF_EXCEPTION(scope, nullptr);
}
if (codeBlock) {
CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail);
RETURN_IF_EXCEPTION(scope, nullptr);
if (compilationResult != CompilationResult::CompilationFailed) {
executable->installCode(codeBlock);
return ptr;
@@ -184,7 +208,9 @@ JSValue NodeVMSourceTextModule::createModuleRecord(JSGlobalObject* globalObject)
const auto& requests = moduleRecord->requestedModules();
if (requests.isEmpty()) {
return constructEmptyArray(globalObject, nullptr, 0);
JSArray* requestsArray = constructEmptyArray(globalObject, nullptr, 0);
RETURN_IF_EXCEPTION(scope, {});
return requestsArray;
}
JSArray* requestsArray = constructEmptyArray(globalObject, nullptr, requests.size());
@@ -312,26 +338,35 @@ JSValue NodeVMSourceTextModule::link(JSGlobalObject* globalObject, JSArray* spec
if (length != 0) {
for (unsigned i = 0; i < length; i++) {
JSValue specifierValue = specifiers->getDirectIndex(globalObject, i);
RETURN_IF_EXCEPTION(scope, {});
JSValue moduleNativeValue = moduleNatives->getDirectIndex(globalObject, i);
RETURN_IF_EXCEPTION(scope, {});
ASSERT(specifierValue.isString());
ASSERT(moduleNativeValue.isObject());
WTF::String specifier = specifierValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
JSObject* moduleNative = moduleNativeValue.getObject();
RETURN_IF_EXCEPTION(scope, {});
AbstractModuleRecord* resolvedRecord = jsCast<NodeVMModule*>(moduleNative)->moduleRecord(globalObject);
RETURN_IF_EXCEPTION(scope, {});
record->setImportedModule(globalObject, Identifier::fromString(vm, specifier), resolvedRecord);
RETURN_IF_EXCEPTION(scope, {});
m_resolveCache.set(WTFMove(specifier), WriteBarrier<JSObject> { vm, this, moduleNative });
RETURN_IF_EXCEPTION(scope, {});
}
}
if (NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false)) {
NodeVMGlobalObject* nodeVmGlobalObject = getGlobalObjectFromContext(globalObject, m_context.get(), false);
RETURN_IF_EXCEPTION(scope, {});
if (nodeVmGlobalObject) {
globalObject = nodeVmGlobalObject;
}
Synchronousness sync = record->link(globalObject, scriptFetcher);
RETURN_IF_EXCEPTION(scope, {});
if (sync == Synchronousness::Async) {
@@ -355,6 +390,7 @@ RefPtr<CachedBytecode> NodeVMSourceTextModule::bytecode(JSGlobalObject* globalOb
if (!m_bytecode) {
if (!m_cachedExecutable) {
ModuleProgramExecutable* executable = ModuleProgramExecutable::tryCreate(globalObject, m_sourceCode);
RETURN_IF_EXCEPTION(scope, nullptr);
if (!executable) {
if (!scope.exception()) {
throwSyntaxError(globalObject, scope, "Failed to create cached executable"_s);
@@ -364,6 +400,7 @@ RefPtr<CachedBytecode> NodeVMSourceTextModule::bytecode(JSGlobalObject* globalOb
m_cachedExecutable.set(vm, this, executable);
}
m_bytecode = getBytecode(globalObject, m_cachedExecutable.get(), m_sourceCode);
RETURN_IF_EXCEPTION(scope, nullptr);
}
return m_bytecode;
@@ -371,10 +408,16 @@ RefPtr<CachedBytecode> NodeVMSourceTextModule::bytecode(JSGlobalObject* globalOb
JSUint8Array* NodeVMSourceTextModule::cachedData(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!m_cachedBytecodeBuffer) {
RefPtr<CachedBytecode> cachedBytecode = bytecode(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
std::span<const uint8_t> bytes = cachedBytecode->span();
m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject, bytes));
JSUint8Array* buffer = WebCore::createBuffer(globalObject, bytes);
RETURN_IF_EXCEPTION(scope, nullptr);
m_cachedBytecodeBuffer.set(vm, this, buffer);
}
return m_cachedBytecodeBuffer.get();
@@ -389,7 +432,11 @@ void NodeVMSourceTextModule::initializeImportMeta(JSGlobalObject* globalObject)
JSModuleEnvironment* moduleEnvironment = m_moduleRecord->moduleEnvironmentMayBeNull();
ASSERT(moduleEnvironment != nullptr);
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue metaValue = moduleEnvironment->get(globalObject, globalObject->vm().propertyNames->builtinNames().metaPrivateName());
RETURN_IF_EXCEPTION(scope, );
ASSERT(metaValue);
ASSERT(metaValue.isObject());
@@ -400,6 +447,7 @@ void NodeVMSourceTextModule::initializeImportMeta(JSGlobalObject* globalObject)
args.append(m_moduleWrapper.get());
JSC::call(globalObject, m_initializeImportMeta.get(), callData, jsUndefined(), args);
RETURN_IF_EXCEPTION(scope, );
}
JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject)

View File

@@ -67,15 +67,20 @@ NodeVMSyntheticModule* NodeVMSyntheticModule::create(VM& vm, JSGlobalObject* glo
WTF::HashSet<String> exportNames;
for (unsigned i = 0; i < exportNamesArray->getArrayLength(); i++) {
JSValue exportNameValue = exportNamesArray->getIndex(globalObject, i);
RETURN_IF_EXCEPTION(scope, nullptr);
if (!exportNameValue.isString()) {
throwArgumentTypeError(*globalObject, scope, 2, "exportNames"_s, "Module"_s, "Module"_s, "string[]"_s);
return nullptr;
}
exportNames.addVoid(exportNameValue.toWTFString(globalObject));
RETURN_IF_EXCEPTION(scope, nullptr);
}
auto* zigGlobalObject = defaultGlobalObject(globalObject);
auto* structure = zigGlobalObject->NodeVMSyntheticModuleStructure();
auto* ptr = new (NotNull, allocateCell<NodeVMSyntheticModule>(vm)) NodeVMSyntheticModule(vm, structure, identifierValue.toWTFString(globalObject), contextValue, moduleWrapperValue, WTFMove(exportNames), syntheticEvaluationStepsValue);
WTF::String identifier = identifierValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
auto* ptr = new (NotNull, allocateCell<NodeVMSyntheticModule>(vm)) NodeVMSyntheticModule(vm, structure, WTFMove(identifier), contextValue, moduleWrapperValue, WTFMove(exportNames), syntheticEvaluationStepsValue);
ptr->finishCreation(vm);
return ptr;
}
@@ -204,8 +209,11 @@ void NodeVMSyntheticModule::setExport(JSGlobalObject* globalObject, WTF::String
}
ensureModuleRecord(globalObject);
RETURN_IF_EXCEPTION(scope, );
JSModuleNamespaceObject* namespaceObject = m_moduleRecord->getModuleNamespace(globalObject, false);
RETURN_IF_EXCEPTION(scope, );
namespaceObject->overrideExportValue(globalObject, Identifier::fromString(vm, exportName), value);
RETURN_IF_EXCEPTION(scope, );
}
JSObject* NodeVMSyntheticModule::createPrototype(VM& vm, JSGlobalObject* globalObject)

View File

@@ -4136,22 +4136,26 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderImportModule(JSGlobalObject* j
{
auto* globalObject = static_cast<Zig::GlobalObject*>(jsGlobalObject);
if (JSC::JSInternalPromise* result = NodeVM::importModule(globalObject, moduleNameValue, parameters, sourceOrigin)) {
return result;
VM& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
{
JSC::JSInternalPromise* result = NodeVM::importModule(globalObject, moduleNameValue, parameters, sourceOrigin);
RETURN_IF_EXCEPTION(scope, nullptr);
if (result) {
return result;
}
}
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSC::Identifier resolvedIdentifier;
auto moduleName = moduleNameValue->value(globalObject);
RETURN_IF_EXCEPTION(scope, {});
RETURN_IF_EXCEPTION(scope, nullptr);
if (globalObject->onLoadPlugins.hasVirtualModules()) {
if (auto resolution = globalObject->onLoadPlugins.resolveVirtualModule(moduleName, sourceOrigin.url().protocolIsFile() ? sourceOrigin.url().fileSystemPath() : String())) {
resolvedIdentifier = JSC::Identifier::fromString(vm, resolution.value());
auto result = JSC::importModule(globalObject, resolvedIdentifier,
JSC::jsUndefined(), parameters, JSC::jsUndefined());
auto result = JSC::importModule(globalObject, resolvedIdentifier, JSC::jsUndefined(), parameters, JSC::jsUndefined());
if (scope.exception()) [[unlikely]] {
auto* promise = JSC::JSInternalPromise::create(vm, globalObject->internalPromiseStructure());
return promise->rejectWithCaughtException(globalObject, scope);

View File

@@ -289,6 +289,7 @@ public:
JSC::JSFunction* requireESMFromHijackedExtension() const { return m_commonJSRequireESMFromHijackedExtensionFunction.getInitializedOnMainThread(this); }
Structure* NodeVMGlobalObjectStructure() const { return m_cachedNodeVMGlobalObjectStructure.getInitializedOnMainThread(this); }
Structure* NodeVMSpecialSandboxStructure() const { return m_cachedNodeVMSpecialSandboxStructure.getInitializedOnMainThread(this); }
Structure* globalProxyStructure() const { return m_cachedGlobalProxyStructure.getInitializedOnMainThread(this); }
JSObject* lazyTestModuleObject() const { return m_lazyTestModuleObject.getInitializedOnMainThread(this); }
JSObject* lazyPreloadTestModuleObject() const { return m_lazyPreloadTestModuleObject.getInitializedOnMainThread(this); }
@@ -576,6 +577,7 @@ public:
V(private, LazyPropertyOfGlobalObject<JSObject>, m_lazyPreloadTestModuleObject) \
V(public, LazyPropertyOfGlobalObject<JSObject>, m_testMatcherUtilsObject) \
V(public, LazyPropertyOfGlobalObject<Structure>, m_cachedNodeVMGlobalObjectStructure) \
V(public, LazyPropertyOfGlobalObject<Structure>, m_cachedNodeVMSpecialSandboxStructure) \
V(private, LazyPropertyOfGlobalObject<Structure>, m_cachedGlobalProxyStructure) \
V(private, LazyPropertyOfGlobalObject<Structure>, m_commonJSModuleObjectStructure) \
V(private, LazyPropertyOfGlobalObject<Structure>, m_JSSocketAddressDTOStructure) \
@@ -617,7 +619,9 @@ public:
V(public, LazyPropertyOfGlobalObject<JSFloat64Array>, m_statValues) \
V(public, LazyPropertyOfGlobalObject<JSBigInt64Array>, m_bigintStatValues) \
V(public, LazyPropertyOfGlobalObject<JSFloat64Array>, m_statFsValues) \
V(public, LazyPropertyOfGlobalObject<JSBigInt64Array>, m_bigintStatFsValues)
V(public, LazyPropertyOfGlobalObject<JSBigInt64Array>, m_bigintStatFsValues) \
V(public, LazyPropertyOfGlobalObject<Symbol>, m_nodeVMDontContextify) \
V(public, LazyPropertyOfGlobalObject<Symbol>, m_nodeVMUseMainContextDefaultLoader)
#define DECLARE_GLOBALOBJECT_GC_MEMBER(visibility, T, name) \
visibility: \

View File

@@ -36,6 +36,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForRequireResolveFunction;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBundlerPlugin;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMGlobalObject;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMSpecialSandbox;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMScript;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMSourceTextModule;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForNodeVMSyntheticModule;

View File

@@ -36,6 +36,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForRequireResolveFunction;
std::unique_ptr<IsoSubspace> m_subspaceForBundlerPlugin;
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMGlobalObject;
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMSpecialSandbox;
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMScript;
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMSourceTextModule;
std::unique_ptr<IsoSubspace> m_subspaceForNodeVMSyntheticModule;

View File

@@ -43,14 +43,14 @@ const {
Module: ModuleNative,
createContext,
isContext,
// runInNewContext: moduleRunInNewContext,
// runInThisContext: moduleRunInThisContext,
compileFunction,
isModuleNamespaceObject,
kUnlinked,
kLinked,
kEvaluated,
kErrored,
DONT_CONTEXTIFY,
USE_MAIN_CONTEXT_DEFAULT_LOADER,
} = vm;
function runInContext(code, context, options) {
@@ -88,7 +88,7 @@ function measureMemory() {
}
function validateContext(contextifiedObject) {
if (!isContext(contextifiedObject) && contextifiedObject !== constants.DONT_CONTEXTIFY) {
if (contextifiedObject !== constants.DONT_CONTEXTIFY && !isContext(contextifiedObject)) {
const error = new Error('The "contextifiedObject" argument must be an vm.Context');
error.code = "ERR_INVALID_ARG_TYPE";
error.name = "TypeError";
@@ -143,7 +143,6 @@ class Module {
});
}
let registry: any = { __proto__: null };
if (sourceText !== undefined) {
this[kNative] = new ModuleNative(
identifier,
@@ -154,19 +153,8 @@ class Module {
options.cachedData,
options.initializeImportMeta,
this,
options.importModuleDynamically ? importModuleDynamicallyWrap(options.importModuleDynamically) : undefined,
);
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);
@@ -442,8 +430,8 @@ class SyntheticModule extends Module {
const constants = {
__proto__: null,
USE_MAIN_CONTEXT_DEFAULT_LOADER: Symbol("vm_dynamic_import_main_context_default"),
DONT_CONTEXTIFY: Symbol("vm_context_no_contextify"),
USE_MAIN_CONTEXT_DEFAULT_LOADER,
DONT_CONTEXTIFY,
};
function isModule(object) {
@@ -452,7 +440,7 @@ function isModule(object) {
function importModuleDynamicallyWrap(importModuleDynamically) {
const importModuleDynamicallyWrapper = async (...args) => {
const m: any = importModuleDynamically.$apply(this, args);
const m: any = await importModuleDynamically.$apply(this, args);
if (isModuleNamespaceObject(m)) {
return m;
}

View File

@@ -0,0 +1,185 @@
'use strict';
// Check vm.constants.DONT_CONTEXTIFY works.
const common = require('../common');
const assert = require('assert');
const vm = require('vm');
const fixtures = require('../common/fixtures');
{
// Check identity of the returned object.
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
// The globalThis in the new context should be reference equal to the returned object.
assert.strictEqual(vm.runInContext('globalThis', context), context);
assert(vm.isContext(context));
assert.strictEqual(typeof context.Array, 'function'); // Can access builtins directly.
assert.deepStrictEqual(Object.keys(context), []); // Properties on the global proxy are not enumerable
}
{
// Check that vm.createContext can return the original context if re-passed.
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
const context2 = new vm.createContext(context);
assert.strictEqual(context, context2);
}
{
// Check that the context is vanilla and that Script.runInContext works.
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
const result =
new vm.Script('globalThis.hey = 1; Object.freeze(globalThis); globalThis.process')
.runInContext(context);
assert.strictEqual(globalThis.hey, undefined); // Should not leak into current context.
assert.strictEqual(result, undefined); // Vanilla context has no Node.js globals
}
{
// Check Script.runInNewContext works.
const result =
new vm.Script('globalThis.hey = 1; Object.freeze(globalThis); globalThis.process')
.runInNewContext(vm.constants.DONT_CONTEXTIFY);
assert.strictEqual(globalThis.hey, undefined); // Should not leak into current context.
assert.strictEqual(result, undefined); // Vanilla context has no Node.js globals
}
{
// Check that vm.runInNewContext() works
const result = vm.runInNewContext(
'globalThis.hey = 1; Object.freeze(globalThis); globalThis.process',
vm.constants.DONT_CONTEXTIFY);
assert.strictEqual(globalThis.hey, undefined); // Should not leak into current context.
assert.strictEqual(result, undefined); // Vanilla context has no Node.js globals
}
{
// Check that the global object of vanilla contexts work as expected.
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
// Check mutation via globalThis.
vm.runInContext('globalThis.foo = 1;', context);
assert.strictEqual(globalThis.foo, undefined); // Should not pollute the current context.
assert.strictEqual(context.foo, 1);
assert.strictEqual(vm.runInContext('globalThis.foo', context), 1);
assert.strictEqual(vm.runInContext('foo', context), 1);
// Check mutation from outside.
context.foo = 2;
assert.strictEqual(context.foo, 2);
assert.strictEqual(vm.runInContext('globalThis.foo', context), 2);
assert.strictEqual(vm.runInContext('foo', context), 2);
// Check contextual mutation.
vm.runInContext('bar = 1;', context);
assert.strictEqual(globalThis.bar, undefined); // Should not pollute the current context.
assert.strictEqual(context.bar, 1);
assert.strictEqual(vm.runInContext('globalThis.bar', context), 1);
assert.strictEqual(vm.runInContext('bar', context), 1);
// Check adding new property from outside.
context.baz = 1;
assert.strictEqual(context.baz, 1);
assert.strictEqual(vm.runInContext('globalThis.baz', context), 1);
assert.strictEqual(vm.runInContext('baz', context), 1);
// Check mutation via Object.defineProperty().
vm.runInContext('Object.defineProperty(globalThis, "qux", {' +
'enumerable: false, configurable: false, get() { return 1; } })', context);
assert.strictEqual(globalThis.qux, undefined); // Should not pollute the current context.
assert.strictEqual(context.qux, 1);
assert.strictEqual(vm.runInContext('qux', context), 1);
const desc = Object.getOwnPropertyDescriptor(context, 'qux');
assert.strictEqual(desc.enumerable, false);
assert.strictEqual(desc.configurable, false);
assert.strictEqual(typeof desc.get, 'function');
assert.throws(() => { context.qux = 1; }, { name: 'TypeError' });
assert.throws(() => { Object.defineProperty(context, 'qux', { value: 1 }); }, { name: 'TypeError' });
// Setting a value without a setter fails silently.
assert.strictEqual(vm.runInContext('qux = 2; qux', context), 1);
assert.throws(() => {
vm.runInContext('Object.defineProperty(globalThis, "qux", { value: 1 });');
}, { name: 'TypeError' });
}
function checkFrozen(context) {
// Check mutation via globalThis.
vm.runInContext('globalThis.foo = 1', context); // Invoking setters on freezed object fails silently.
assert.strictEqual(context.foo, undefined);
assert.strictEqual(vm.runInContext('globalThis.foo', context), undefined);
assert.throws(() => {
vm.runInContext('foo', context); // It should not be looked up contextually.
}, {
name: 'ReferenceError'
});
// Check mutation from outside.
assert.throws(() => {
context.foo = 2;
}, { name: 'TypeError' });
assert.strictEqual(context.foo, undefined);
assert.strictEqual(vm.runInContext('globalThis.foo', context), undefined);
assert.throws(() => {
vm.runInContext('foo', context); // It should not be looked up contextually.
}, {
name: 'ReferenceError'
});
// Check contextual mutation.
vm.runInContext('bar = 1', context); // Invoking setters on freezed object fails silently.
assert.strictEqual(context.bar, undefined);
assert.strictEqual(vm.runInContext('globalThis.bar', context), undefined);
assert.throws(() => {
vm.runInContext('bar', context); // It should not be looked up contextually.
}, {
name: 'ReferenceError'
});
// Check mutation via Object.defineProperty().
assert.throws(() => {
vm.runInContext('Object.defineProperty(globalThis, "qux", {' +
'enumerable: false, configurable: false, get() { return 1; } })', context);
}, {
name: 'TypeError'
});
assert.strictEqual(context.qux, undefined);
assert.strictEqual(vm.runInContext('globalThis.qux', context), undefined);
assert.strictEqual(Object.getOwnPropertyDescriptor(context, 'qux'), undefined);
assert.throws(() => { Object.defineProperty(context, 'qux', { value: 1 }); }, { name: 'TypeError' });
assert.throws(() => {
vm.runInContext('qux', context);
}, {
name: 'ReferenceError'
});
}
{
// Check freezing the vanilla context's global object from within the context.
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
// Only vanilla contexts' globals can be freezed. Contextified global objects cannot be freezed
// due to the presence of interceptors.
vm.runInContext('Object.freeze(globalThis)', context);
checkFrozen(context);
}
{
// Check freezing the vanilla context's global object from outside the context.
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
Object.freeze(context);
checkFrozen(context);
}
// Check importModuleDynamically works.
(async function() {
{
const moduleUrl = fixtures.fileURL('es-modules', 'message.mjs');
const namespace = await import(moduleUrl.href);
// Check dynamic import works
const context = vm.createContext(vm.constants.DONT_CONTEXTIFY);
const script = new vm.Script(`import(${JSON.stringify(moduleUrl)})`, {
importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
});
const promise = script.runInContext(context);
assert.strictEqual(await promise, namespace);
}
})().catch(common.mustNotCall());

View File

@@ -0,0 +1,117 @@
'use strict';
// Flags: --experimental-vm-modules
const common = require('../common');
const assert = require('assert');
const { Script, SourceTextModule } = require('vm');
async function testNoCallback() {
const m = new SourceTextModule(`
globalThis.importResult = import("foo");
globalThis.importResult.catch(() => {});
`);
await m.link(common.mustNotCall());
await m.evaluate();
let threw = false;
try {
await globalThis.importResult;
} catch (err) {
threw = true;
assert.strictEqual(err.code, 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING');
}
delete globalThis.importResult;
assert(threw);
}
async function test() {
const foo = new SourceTextModule('export const a = 1;');
await foo.link(common.mustNotCall());
await foo.evaluate();
{
const s = new Script('import("foo")', {
importModuleDynamically: common.mustCall((specifier, wrap) => {
assert.strictEqual(specifier, 'foo');
assert.strictEqual(wrap, s);
return foo;
}),
});
const result = s.runInThisContext();
assert.strictEqual(await result, foo.namespace);
}
{
const m = new SourceTextModule('globalThis.fooResult = import("foo")', {
importModuleDynamically: common.mustCall((specifier, wrap) => {
assert.strictEqual(specifier, 'foo');
assert.strictEqual(wrap, m);
return foo;
}),
});
await m.link(common.mustNotCall());
await m.evaluate();
assert.strictEqual(await globalThis.fooResult, foo.namespace);
delete globalThis.fooResult;
}
{
const s = new Script('import("foo", { with: { key: "value" } })', {
importModuleDynamically: common.mustCall((specifier, wrap, attributes) => {
assert.strictEqual(specifier, 'foo');
assert.strictEqual(wrap, s);
assert.deepStrictEqual(attributes, { __proto__: null, key: 'value' });
return foo;
}),
});
const result = s.runInThisContext();
assert.strictEqual(await result, foo.namespace);
}
}
async function testInvalid() {
const m = new SourceTextModule('globalThis.fooResult = import("foo")', {
importModuleDynamically: common.mustCall((specifier, wrap) => {
return 5;
}),
});
await m.link(common.mustNotCall());
await m.evaluate();
await globalThis.fooResult.catch(common.mustCall((e) => {
assert.strictEqual(e.code, 'ERR_VM_MODULE_NOT_MODULE');
}));
delete globalThis.fooResult;
const s = new Script('import("bar")', {
importModuleDynamically: common.mustCall((specifier, wrap) => {
return undefined;
}),
});
let threw = false;
try {
await s.runInThisContext();
} catch (e) {
threw = true;
assert.strictEqual(e.code, 'ERR_VM_MODULE_NOT_MODULE');
}
assert(threw);
}
async function testInvalidimportModuleDynamically() {
assert.throws(
() => new Script(
'import("foo")',
{ importModuleDynamically: false }),
{ code: 'ERR_INVALID_ARG_TYPE' }
);
}
(async function() {
await testNoCallback();
await test();
await testInvalid();
await testInvalidimportModuleDynamically();
}()).then(common.mustCall());

View File

@@ -0,0 +1,26 @@
'use strict';
// Flags: --experimental-vm-modules
const common = require('../common');
const assert = require('assert');
const { types } = require('util');
const { SourceTextModule } = require('vm');
(async () => {
const m = new SourceTextModule('globalThis.importResult = import("");', {
importModuleDynamically: common.mustCall(async (specifier, wrap) => {
const m = new SourceTextModule('');
await m.link(() => 0);
await m.evaluate();
return m.namespace;
}),
});
await m.link(() => 0);
await m.evaluate();
const ns = await globalThis.importResult;
delete globalThis.importResult;
assert.ok(types.isModuleNamespaceObject(ns));
})().then(common.mustCall());

View File

@@ -0,0 +1,70 @@
// Flags: --experimental-vm-modules
import * as common from '../common/index.mjs';
import assert from 'node:assert';
import { Script, SourceTextModule, createContext } from 'node:vm';
async function test() {
const foo = new SourceTextModule('export const a = 1;');
await foo.link(common.mustNotCall());
await foo.evaluate();
const ctx = createContext({}, {
importModuleDynamically: common.mustCall((specifier, wrap) => {
assert.strictEqual(specifier, 'foo');
assert.strictEqual(wrap, ctx);
return foo;
}, 2),
});
{
const s = new Script('Promise.resolve("import(\'foo\')").then(eval)', {
importModuleDynamically: common.mustNotCall(),
});
const result = s.runInContext(ctx);
assert.strictEqual(await result, foo.namespace);
}
{
const m = new SourceTextModule('globalThis.fooResult = Promise.resolve("import(\'foo\')").then(eval)', {
context: ctx,
importModuleDynamically: common.mustNotCall(),
});
await m.link(common.mustNotCall());
await m.evaluate();
assert.strictEqual(await ctx.fooResult, foo.namespace);
delete ctx.fooResult;
}
}
async function testMissing() {
const ctx = createContext({});
{
const s = new Script('Promise.resolve("import(\'foo\')").then(eval)', {
importModuleDynamically: common.mustNotCall(),
});
const result = s.runInContext(ctx);
await assert.rejects(result, {
code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING',
});
}
{
const m = new SourceTextModule('globalThis.fooResult = Promise.resolve("import(\'foo\')").then(eval)', {
context: ctx,
importModuleDynamically: common.mustNotCall(),
});
await m.link(common.mustNotCall());
await m.evaluate();
await assert.rejects(ctx.fooResult, {
code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING',
});
delete ctx.fooResult;
}
}
await Promise.all([
test(),
testMissing(),
]).then(common.mustCall());

View File

@@ -0,0 +1,20 @@
'use strict';
const common = require('../common');
const { Script, compileFunction } = require('vm');
const assert = require('assert');
assert.rejects(async () => {
const script = new Script('import("fs")');
const imported = script.runInThisContext();
await imported;
}, {
code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING'
}).then(common.mustCall());
assert.rejects(async () => {
const imported = compileFunction('return import("fs")')();
await imported;
}, {
code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING'
}).then(common.mustCall());

View File

@@ -0,0 +1,44 @@
// 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');
const spawn = require('child_process').spawn;
if (process.argv[2] === 'child') {
const code = 'while(true);';
const ctx = vm.createContext();
vm.runInContext(code, ctx, { timeout: 1 });
} else {
const proc = spawn(process.execPath, process.argv.slice(1).concat('child'));
let err = '';
proc.stderr.on('data', function(data) {
err += data;
});
process.on('exit', function() {
assert.match(err, /Script execution timed out after 1ms/);
});
}

View File

@@ -1522,6 +1522,7 @@ test/js/node/test/parallel/test-vm-module-errors.js
test/js/node/test/parallel/test-vm-module-import-meta.js
test/js/node/test/parallel/test-vm-module-link.js
test/js/node/test/parallel/test-vm-module-reevaluate.js
test/js/node/test/parallel/test-vm-module-referrer-realm.mjs
test/js/node/test/parallel/test-vm-module-synthetic.js
test/js/node/test/parallel/test-vm-new-script-context.js
test/js/node/test/parallel/test-vm-new-script-new-context.js