#include "root.h" #include "JavaScriptCore/PropertySlot.h" #include "JavaScriptCore/ExecutableInfo.h" #include "JavaScriptCore/WriteBarrierInlines.h" #include "ErrorCode.h" #include #include #include "BunClientData.h" #include "NodeVM.h" #include "JavaScriptCore/JSObjectInlines.h" #include "wtf/text/ExternalStringImpl.h" #include "JavaScriptCore/FunctionPrototype.h" #include "JavaScriptCore/FunctionConstructor.h" #include "JavaScriptCore/HeapAnalyzer.h" #include "JavaScriptCore/JSDestructibleObjectHeapCellType.h" #include "JavaScriptCore/SlotVisitorMacros.h" #include "JavaScriptCore/ObjectConstructor.h" #include "JavaScriptCore/SubspaceInlines.h" #include "wtf/GetPtr.h" #include "wtf/PointerPreparations.h" #include "wtf/URL.h" #include "JavaScriptCore/TypedArrayInlines.h" #include "JavaScriptCore/PropertyNameArray.h" #include "JavaScriptCore/JSWeakMap.h" #include "JavaScriptCore/JSWeakMapInlines.h" #include "JavaScriptCore/JSWithScope.h" #include "JavaScriptCore/JSGlobalProxyInlines.h" #include "GCDefferalContext.h" #include "JSBuffer.h" #include #include #include #include "JavaScriptCore/LazyClassStructureInlines.h" #include "JavaScriptCore/Parser.h" #include "JavaScriptCore/SourceCodeKey.h" #include "JavaScriptCore/UnlinkedFunctionExecutable.h" #include "NodeValidator.h" #include "JavaScriptCore/JSCInlines.h" #include "JavaScriptCore/CodeCache.h" #include "JavaScriptCore/BytecodeCacheError.h" #include "wtf/FileHandle.h" #include "JavaScriptCore/ProgramCodeBlock.h" #include "JavaScriptCore/JIT.h" #include "JSDOMExceptionHandling.h" namespace Bun { using namespace WebCore; /// For vm.compileFunction we need to return an anonymous function expression /// /// This code is adapted/inspired from JSC::constructFunction, which is used for function declarations. static JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName = String(), JSC::SourceTaintedOrigin sourceTaintOrigin = JSC::SourceTaintedOrigin::Untainted, TextPosition position = TextPosition(), JSC::JSScope* scope = nullptr); static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset); static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source); static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source); NodeVMGlobalObject* createContextImpl(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* sandbox); /// For some reason Node has this error message with a grammar error and we have to match it so the tests pass: /// `The "" argument must be an vm.Context` JSC::EncodedJSValue INVALID_ARG_VALUE_VM_VARIATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value) { WTF::StringBuilder builder; builder.append("The \""_s); builder.append(name); builder.append("\" argument must be an vm.Context"_s); throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, builder.toString())); return {}; } static bool extractCachedData(JSValue cachedDataValue, WTF::Vector& outCachedData) { if (!cachedDataValue.isCell()) { return false; } if (auto* arrayBufferView = JSC::jsDynamicCast(cachedDataValue)) { if (!arrayBufferView->isDetached()) { outCachedData = arrayBufferView->span(); return true; } } else if (auto* arrayBuffer = JSC::jsDynamicCast(cachedDataValue); arrayBuffer && arrayBuffer->impl()) { outCachedData = arrayBuffer->impl()->toVector(); return true; } return false; } class BaseOptions { public: String filename = String(); OrdinalNumber lineOffset; OrdinalNumber columnOffset; bool failed; bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { JSObject* options = nullptr; bool any = false; if (!optionsArg.isUndefined()) { if (optionsArg.isObject()) { options = asObject(optionsArg); } else { auto _ = ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); return false; } if (JSValue filenameOpt = options->getIfPropertyExists(globalObject, builtinNames(vm).filenamePublicName())) { if (filenameOpt.isString()) { this->filename = filenameOpt.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, false); any = true; } else if (!filenameOpt.isUndefined()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.filename"_s, "string"_s, filenameOpt); return false; } } else { this->filename = "evalmachine."_s; } if (JSValue lineOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "lineOffset"_s))) { if (lineOffsetOpt.isAnyInt()) { if (!lineOffsetOpt.isInt32()) { ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), lineOffsetOpt); return false; } this->lineOffset = OrdinalNumber::fromZeroBasedInt(lineOffsetOpt.asInt32()); any = true; } else if (lineOffsetOpt.isNumber()) { ERR::OUT_OF_RANGE(scope, globalObject, "options.lineOffset"_s, "an integer"_s, lineOffsetOpt); return false; } else if (!lineOffsetOpt.isUndefined()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.lineOffset"_s, "number"_s, lineOffsetOpt); return false; } } if (JSValue columnOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "columnOffset"_s))) { if (columnOffsetOpt.isAnyInt()) { if (!columnOffsetOpt.isInt32()) { ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, std::numeric_limits().min(), std::numeric_limits().max(), columnOffsetOpt); return false; } int columnOffsetValue = columnOffsetOpt.asInt32(); this->columnOffset = OrdinalNumber::fromZeroBasedInt(columnOffsetValue); any = true; } else if (columnOffsetOpt.isNumber()) { ERR::OUT_OF_RANGE(scope, globalObject, "options.columnOffset"_s, "an integer"_s, columnOffsetOpt); return false; } else if (!columnOffsetOpt.isUndefined()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.columnOffset"_s, "number"_s, columnOffsetOpt); return false; } } } return any; } bool validateProduceCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, bool& outProduceCachedData) { JSValue produceCachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "produceCachedData"_s)); if (produceCachedDataOpt && !produceCachedDataOpt.isUndefined()) { RETURN_IF_EXCEPTION(scope, {}); if (!produceCachedDataOpt.isBoolean()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.produceCachedData"_s, "boolean"_s, produceCachedDataOpt); return false; } outProduceCachedData = produceCachedDataOpt.asBoolean(); return true; } return false; } bool validateCachedData(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, WTF::Vector& outCachedData) { JSValue cachedDataOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "cachedData"_s)); RETURN_IF_EXCEPTION(scope, {}); if (cachedDataOpt && !cachedDataOpt.isUndefined()) { // Verify it's a Buffer, TypedArray or DataView and extract the data if it is. if (extractCachedData(cachedDataOpt, outCachedData)) { return true; } ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.cachedData"_s, "Buffer, TypedArray, or DataView"_s, cachedDataOpt); } return false; } bool validateTimeout(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSObject* options, std::optional& outTimeout) { JSValue timeoutOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "timeout"_s)); if (timeoutOpt && !timeoutOpt.isUndefined()) { if (!timeoutOpt.isNumber()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.timeout"_s, "number"_s, timeoutOpt); return false; } ssize_t timeoutValue; V::validateInteger(scope, globalObject, timeoutOpt, "options.timeout"_s, jsNumber(1), jsNumber(std::numeric_limits().max()), &timeoutValue); RETURN_IF_EXCEPTION(scope, {}); outTimeout = timeoutValue; return true; } return false; } }; class ScriptOptions : public BaseOptions { public: std::optional timeout = std::nullopt; bool produceCachedData = false; WTF::Vector cachedData; bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); RETURN_IF_EXCEPTION(scope, false); if (!optionsArg.isUndefined() && !optionsArg.isString()) { JSObject* options = asObject(optionsArg); // Validate contextName and contextOrigin are strings if (JSValue contextNameOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextName"_s))) { if (!contextNameOpt.isUndefined() && !contextNameOpt.isString()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextName"_s, "string"_s, contextNameOpt); return false; } any = true; } RETURN_IF_EXCEPTION(scope, false); if (JSValue contextOriginOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextOrigin"_s))) { if (!contextOriginOpt.isUndefined() && !contextOriginOpt.isString()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextOrigin"_s, "string"_s, contextOriginOpt); return false; } any = true; } RETURN_IF_EXCEPTION(scope, false); if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { RETURN_IF_EXCEPTION(scope, false); any = true; } if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) { RETURN_IF_EXCEPTION(scope, false); any = true; } if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) { RETURN_IF_EXCEPTION(scope, false); any = true; } } return any; } }; class RunningScriptOptions : public BaseOptions { public: bool displayErrors = true; std::optional timeout = std::nullopt; bool breakOnSigint = false; bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); RETURN_IF_EXCEPTION(scope, false); if (!optionsArg.isUndefined() && !optionsArg.isString()) { JSObject* options = asObject(optionsArg); if (JSValue displayErrorsOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "displayErrors"_s))) { RETURN_IF_EXCEPTION(scope, false); if (!displayErrorsOpt.isBoolean()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.displayErrors"_s, "boolean"_s, displayErrorsOpt); return false; } this->displayErrors = displayErrorsOpt.asBoolean(); any = true; } if (validateTimeout(globalObject, vm, scope, options, this->timeout)) { RETURN_IF_EXCEPTION(scope, false); any = true; } if (JSValue breakOnSigintOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "breakOnSigint"_s))) { RETURN_IF_EXCEPTION(scope, false); if (!breakOnSigintOpt.isBoolean()) { ERR::INVALID_ARG_TYPE(scope, globalObject, "options.breakOnSigint"_s, "boolean"_s, breakOnSigintOpt); return false; } this->breakOnSigint = breakOnSigintOpt.asBoolean(); any = true; } } return any; } }; class CompileFunctionOptions : public BaseOptions { public: WTF::Vector cachedData; JSGlobalObject* parsingContext = nullptr; JSValue contextExtensions; bool produceCachedData = false; bool fromJS(JSC::JSGlobalObject* globalObject, JSC::VM& vm, JSC::ThrowScope& scope, JSC::JSValue optionsArg) { this->parsingContext = globalObject; bool any = BaseOptions::fromJS(globalObject, vm, scope, optionsArg); RETURN_IF_EXCEPTION(scope, false); if (!optionsArg.isUndefined() && !optionsArg.isString()) { JSObject* options = asObject(optionsArg); if (validateProduceCachedData(globalObject, vm, scope, options, this->produceCachedData)) { RETURN_IF_EXCEPTION(scope, false); any = true; } if (validateCachedData(globalObject, vm, scope, options, this->cachedData)) { RETURN_IF_EXCEPTION(scope, false); any = true; } JSValue parsingContextValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "parsingContext"_s)); RETURN_IF_EXCEPTION(scope, {}); if (!parsingContextValue.isEmpty() && !parsingContextValue.isUndefined()) { if (parsingContextValue.isNull() || !parsingContextValue.isObject()) return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); JSObject* context = asObject(parsingContextValue); auto* zigGlobalObject = defaultGlobalObject(globalObject); JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); if (scopeValue.isUndefined()) return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); parsingContext = jsDynamicCast(scopeValue); if (!parsingContext) return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.parsingContext"_s, "Context"_s, parsingContextValue); any = true; } // Handle contextExtensions option JSValue contextExtensionsValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "contextExtensions"_s)); RETURN_IF_EXCEPTION(scope, {}); if (!contextExtensionsValue.isEmpty() && !contextExtensionsValue.isUndefined()) { if (contextExtensionsValue.isNull() || !contextExtensionsValue.isObject()) return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); if (auto* contextExtensionsObject = asObject(contextExtensionsValue)) { if (!isArray(globalObject, contextExtensionsObject)) return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); // Validate that all items in the array are objects auto* contextExtensionsArray = jsCast(contextExtensionsValue); unsigned length = contextExtensionsArray->length(); for (unsigned i = 0; i < length; i++) { JSValue extension = contextExtensionsArray->getIndexQuickly(i); if (!extension.isObject()) return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions[0]"_s, "object"_s, extension); } } else { return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.contextExtensions"_s, "Array"_s, contextExtensionsValue); } this->contextExtensions = contextExtensionsValue; any = true; } } return any; } }; class NodeVMScriptConstructor final : public JSC::InternalFunction { public: using Base = JSC::InternalFunction; static NodeVMScriptConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); DECLARE_EXPORT_INFO; static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); } private: NodeVMScriptConstructor(JSC::VM& vm, JSC::Structure* structure); void finishCreation(JSC::VM&, JSC::JSObject* prototype); }; STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptConstructor, JSC::InternalFunction); class NodeVMScript final : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; static NodeVMScript* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options); DECLARE_EXPORT_INFO; template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { if constexpr (mode == JSC::SubspaceAccess::Concurrently) return nullptr; return WebCore::subspaceForImpl( vm, [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMScript.get(); }, [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMScript = std::forward(space); }, [](auto& spaces) { return spaces.m_subspaceForNodeVMScript.get(); }, [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMScript = std::forward(space); }); } static void destroy(JSC::JSCell*); static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); JSC::ProgramExecutable* createExecutable(); void cacheBytecode(); JSC::JSUint8Array* getBytecodeBuffer(); const JSC::SourceCode& source() const { return m_source; } WTF::Vector& cachedData() { return m_options.cachedData; } RefPtr cachedBytecode() const { return m_cachedBytecode; } JSC::ProgramExecutable* cachedExecutable() const { return m_cachedExecutable.get(); } bool cachedDataProduced() const { return m_cachedDataProduced; } void cachedDataProduced(bool value) { m_cachedDataProduced = value; } TriState cachedDataRejected() const { return m_cachedDataRejected; } void cachedDataRejected(TriState value) { m_cachedDataRejected = value; } DECLARE_VISIT_CHILDREN; private: JSC::SourceCode m_source; RefPtr m_cachedBytecode; mutable JSC::WriteBarrier m_cachedBytecodeBuffer; mutable JSC::WriteBarrier m_cachedExecutable; ScriptOptions m_options; bool m_cachedDataProduced = false; TriState m_cachedDataRejected = TriState::Indeterminate; NodeVMScript(JSC::VM& vm, JSC::Structure* structure, JSC::SourceCode source, ScriptOptions options) : Base(vm, structure) , m_source(source) , m_options(WTFMove(options)) { } void finishCreation(JSC::VM&); }; NodeVMGlobalObject::NodeVMGlobalObject(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure) { } template JSC::GCClient::IsoSubspace* NodeVMGlobalObject::subspaceFor(JSC::VM& vm) { if constexpr (mode == JSC::SubspaceAccess::Concurrently) return nullptr; return WebCore::subspaceForImpl( vm, [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMGlobalObject.get(); }, [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMGlobalObject = std::forward(space); }, [](auto& spaces) { return spaces.m_subspaceForNodeVMGlobalObject.get(); }, [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMGlobalObject = std::forward(space); }, [](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForNodeVMGlobalObject; }); } NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) { auto* cell = new (NotNull, JSC::allocateCell(vm)) NodeVMGlobalObject(vm, structure); cell->finishCreation(vm); return cell; } Structure* NodeVMGlobalObject::createStructure(JSC::VM& vm, JSC::JSValue prototype) { // ~IsImmutablePrototypeExoticObject is necessary for JSDOM to work (it relies on __proto__ = on the GlobalObject). return JSC::Structure::create(vm, nullptr, prototype, JSC::TypeInfo(JSC::GlobalObjectType, StructureFlags & ~IsImmutablePrototypeExoticObject), info()); } void NodeVMGlobalObject::finishCreation(JSC::VM&) { Base::finishCreation(vm()); } void NodeVMGlobalObject::destroy(JSCell* cell) { static_cast(cell)->~NodeVMGlobalObject(); } NodeVMGlobalObject::~NodeVMGlobalObject() { } void NodeVMGlobalObject::setContextifiedObject(JSC::JSObject* contextifiedObject) { m_sandbox.set(vm(), this, contextifiedObject); } void NodeVMGlobalObject::clearContextifiedObject() { m_sandbox.clear(); } bool NodeVMGlobalObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) { // if (!propertyName.isSymbol()) // printf("put called for %s\n", propertyName.publicName()->utf8().data()); auto* thisObject = jsCast(cell); if (!thisObject->m_sandbox) { return Base::put(cell, globalObject, propertyName, value, slot); } auto* sandbox = thisObject->m_sandbox.get(); auto& vm = JSC::getVM(globalObject); JSValue thisValue = slot.thisValue(); bool isContextualStore = thisValue != JSValue(globalObject); (void)isContextualStore; bool isDeclaredOnGlobalObject = slot.type() == JSC::PutPropertySlot::NewProperty; auto scope = DECLARE_THROW_SCOPE(vm); PropertySlot getter(sandbox, PropertySlot::InternalMethodType::Get, nullptr); bool isDeclaredOnSandbox = sandbox->getPropertySlot(globalObject, propertyName, getter); RETURN_IF_EXCEPTION(scope, false); bool isDeclared = isDeclaredOnGlobalObject || isDeclaredOnSandbox; bool isFunction = value.isCallable(); if (slot.isStrictMode() && !isDeclared && isContextualStore && !isFunction) { return Base::put(cell, globalObject, propertyName, value, slot); } if (!isDeclared && value.isSymbol()) { return Base::put(cell, globalObject, propertyName, value, slot); } slot.setThisValue(sandbox); if (!sandbox->methodTable()->put(sandbox, globalObject, propertyName, value, slot)) { return false; } RETURN_IF_EXCEPTION(scope, false); if (isDeclaredOnSandbox && getter.isAccessor() and (getter.attributes() & PropertyAttribute::DontEnum) == 0) { return true; } slot.setThisValue(thisValue); return 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 NodeVMGlobalObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) { // if (!propertyName.isSymbol()) // printf("getOwnPropertySlot called for %s\n", propertyName.publicName()->utf8().data()); auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* thisObject = jsCast(cell); if (thisObject->m_sandbox) { auto* contextifiedObject = thisObject->m_sandbox.get(); slot.setThisValue(contextifiedObject); // Unfortunately we must special case ProxyObjects. Why? // // When we run this: // // ```js // vm.runInNewContext("String", new Proxy({}, {})) // ``` // // It always returns undefined (it should return the String constructor function). // // This is because JSC seems to always return true when calling // `contextifiedObject->methodTable()->getOwnPropertySlot` for ProxyObjects, so // we never fall through to call `Base::getOwnPropertySlot` to fetch it from the globalObject. // // This only happens when `slot.internalMethodType() == JSC::PropertySlot::InternalMethodType::Get` // and there is no `get` trap set on the proxy object. if (slot.internalMethodType() == JSC::PropertySlot::InternalMethodType::Get && contextifiedObject->type() == JSC::ProxyObjectType) { JSC::ProxyObject* proxyObject = jsCast(contextifiedObject); JSValue handlerValue = proxyObject->handler(); if (handlerValue.isNull()) return throwTypeError(globalObject, scope, s_proxyAlreadyRevokedErrorMessage); JSObject* handler = jsCast(handlerValue); CallData callData; JSObject* getHandler = proxyObject->getHandlerTrap(globalObject, handler, callData, vm.propertyNames->get, ProxyObject::HandlerTrap::Get); RETURN_IF_EXCEPTION(scope, {}); // If there is a `get` trap, we don't need to our special handling if (getHandler) { if (contextifiedObject->methodTable()->getOwnPropertySlot(contextifiedObject, globalObject, propertyName, slot)) { return true; } goto try_from_global; } // A lot of this is copy-pasted from JSC's `ProxyObject::getOwnPropertySlotCommon` function in // ProxyObject.cpp, need to make sure we keep this in sync when we update JSC... slot.disableCaching(); slot.setIsTaintedByOpaqueObject(); if (slot.isVMInquiry()) { goto try_from_global; } JSValue receiver = slot.thisValue(); // We're going to have to look this up ourselves PropertySlot target_slot(receiver, PropertySlot::InternalMethodType::Get); JSObject* target = proxyObject->target(); bool hasProperty = target->getPropertySlot(globalObject, propertyName, target_slot); EXCEPTION_ASSERT(!scope.exception() || !hasProperty); if (hasProperty) { unsigned ignoredAttributes = 0; JSValue result = target_slot.getValue(globalObject, propertyName); RETURN_IF_EXCEPTION(scope, {}); slot.setValue(proxyObject, ignoredAttributes, result); RETURN_IF_EXCEPTION(scope, {}); return true; } goto try_from_global; } if (contextifiedObject->getPropertySlot(globalObject, propertyName, slot)) { return true; } try_from_global: slot.setThisValue(globalObject); RETURN_IF_EXCEPTION(scope, false); } return Base::getOwnPropertySlot(cell, globalObject, propertyName, slot); } 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(cell); if (!thisObject->m_sandbox) { return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow); } auto* contextifiedObject = thisObject->m_sandbox.get(); auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); 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); } if (descriptor.isAccessorDescriptor()) { return contextifiedObject->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); } if (!contextifiedObject->defineOwnProperty(contextifiedObject, contextifiedObject->globalObject(), propertyName, descriptor, shouldThrow)) { return false; } return Base::defineOwnProperty(cell, globalObject, propertyName, descriptor, shouldThrow); } DEFINE_VISIT_CHILDREN(NodeVMGlobalObject); template void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) { Base::visitChildren(cell, visitor); auto* thisObject = jsCast(cell); visitor.append(thisObject->m_sandbox); } static EncodedJSValue constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {}) { VM& vm = globalObject->vm(); 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()); JSValue optionsArg = args.at(1); ScriptOptions options; if (optionsArg.isString()) { options.filename = optionsArg.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); } else if (!options.fromJS(globalObject, vm, scope, optionsArg)) { RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); options = {}; } auto* zigGlobalObject = defaultGlobalObject(globalObject); Structure* structure = zigGlobalObject->NodeVMScriptStructure(); if (UNLIKELY(zigGlobalObject->NodeVMScript() != newTarget)) { auto scope = DECLARE_THROW_SCOPE(vm); if (!newTarget) { throwTypeError(globalObject, scope, "Class constructor Script cannot be invoked without 'new'"_s); return {}; } auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject())); RETURN_IF_EXCEPTION(scope, {}); structure = InternalFunction::createSubclassStructure( globalObject, newTarget.getObject(), functionGlobalObject->NodeVMScriptStructure()); scope.release(); } SourceCode source( JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); RETURN_IF_EXCEPTION(scope, {}); const bool produceCachedData = options.produceCachedData; auto filename = options.filename; NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source, WTFMove(options)); WTF::Vector& cachedData = script->cachedData(); if (!cachedData.isEmpty()) { JSC::ProgramExecutable* executable = script->cachedExecutable(); if (!executable) { executable = script->createExecutable(); } ASSERT(executable); JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? JSC::TaintedByWithScopeLexicallyScopedFeature : JSC::NoLexicallyScopedFeatures; JSC::SourceCodeKey key(source, {}, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSC::JSParserScriptMode::Classic, JSC::DerivedContextType::None, JSC::EvalContextType::None, false, {}, std::nullopt); Ref cachedBytecode = JSC::CachedBytecode::create(std::span(cachedData), nullptr, {}); JSC::UnlinkedProgramCodeBlock* unlinkedBlock = JSC::decodeCodeBlock(vm, key, WTFMove(cachedBytecode)); if (!unlinkedBlock) { script->cachedDataRejected(TriState::True); } else { JSC::JSScope* jsScope = globalObject->globalScope(); JSC::CodeBlock* codeBlock = nullptr; { // JSC::ProgramCodeBlock::create() requires GC to be deferred. DeferGC deferGC(vm); codeBlock = JSC::ProgramCodeBlock::create(vm, executable, unlinkedBlock, jsScope); } JSC::CompilationResult compilationResult = JIT::compileSync(vm, codeBlock, JITCompilationEffort::JITCompilationCanFail); if (compilationResult != JSC::CompilationResult::CompilationFailed) { executable->installCode(codeBlock); script->cachedDataRejected(TriState::False); } else { script->cachedDataRejected(TriState::True); } } } else if (produceCachedData) { script->cacheBytecode(); // TODO(@heimskr): is there ever a case where bytecode production fails? script->cachedDataProduced(true); } return JSValue::encode(script); } static bool handleException(JSGlobalObject* globalObject, VM& vm, NakedPtr exception, ThrowScope& throwScope) { if (auto* errorInstance = jsDynamicCast(exception->value())) { errorInstance->materializeErrorInfoIfNeeded(vm, vm.propertyNames->stack); RETURN_IF_EXCEPTION(throwScope, {}); JSValue stack_jsval = errorInstance->get(globalObject, vm.propertyNames->stack); RETURN_IF_EXCEPTION(throwScope, {}); if (!stack_jsval.isString()) { return false; } String stack = stack_jsval.toWTFString(globalObject); auto& e_stack = exception->stack(); size_t stack_size = e_stack.size(); if (stack_size == 0) { return false; } auto& stack_frame = e_stack[0]; auto source_url = stack_frame.sourceURL(vm); if (source_url.isEmpty()) { // copy what Node does: https://github.com/nodejs/node/blob/afe3909483a2d5ae6b847055f544da40571fb28d/lib/vm.js#L94 source_url = "evalmachine."_s; } auto line_and_column = stack_frame.computeLineAndColumn(); String prepend = makeString(source_url, ":"_s, line_and_column.line, "\n"_s, stack); errorInstance->putDirect(vm, vm.propertyNames->stack, jsString(vm, prepend), JSC::PropertyAttribute::DontEnum | 0); JSC::throwException(globalObject, throwScope, exception.get()); return true; } return false; } static JSC::EncodedJSValue runInContext(NodeVMGlobalObject* globalObject, NodeVMScript* script, JSObject* contextifiedObject, JSValue optionsArg, bool allowStringInPlaceOfOptions = false) { auto& vm = JSC::getVM(globalObject); auto throwScope = DECLARE_THROW_SCOPE(vm); RunningScriptOptions options; if (allowStringInPlaceOfOptions && optionsArg.isString()) { options.filename = optionsArg.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); } else if (!options.fromJS(globalObject, vm, throwScope, optionsArg)) { RETURN_IF_EXCEPTION(throwScope, {}); options = {}; } // Set the contextified object before evaluating globalObject->setContextifiedObject(contextifiedObject); NakedPtr exception; JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); if (UNLIKELY(exception)) { if (handleException(globalObject, vm, exception, throwScope)) { return {}; } JSC::throwException(globalObject, throwScope, exception.get()); return {}; } return JSValue::encode(result); } JSC_DEFINE_HOST_FUNCTION(scriptConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame)) { return constructScript(globalObject, callFrame); } JSC_DEFINE_HOST_FUNCTION(scriptConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame)) { return constructScript(globalObject, callFrame, callFrame->newTarget()); } JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); auto* script = jsDynamicCast(thisValue); if (UNLIKELY(!script)) { return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } const JSC::SourceCode& source = script->source(); return createCachedData(globalObject, source); } JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); auto* script = jsDynamicCast(thisValue); if (UNLIKELY(!script)) { return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } ArgList args(callFrame); JSValue contextArg = args.at(0); if (contextArg.isUndefinedOrNull()) { return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); } if (!contextArg.isObject()) { return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); } JSObject* context = asObject(contextArg); auto* zigGlobalObject = defaultGlobalObject(globalObject); JSValue scopeValue = zigGlobalObject->vmModuleContextMap()->get(context); if (scopeValue.isUndefined()) { return INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); } NodeVMGlobalObject* nodeVmGlobalObject = jsDynamicCast(scopeValue); if (!nodeVmGlobalObject) { return INVALID_ARG_VALUE_VM_VARIATION(scope, globalObject, "contextifiedObject"_s, context); } return runInContext(nodeVmGlobalObject, script, context, args.at(1)); } JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = JSC::getVM(globalObject); JSValue thisValue = callFrame->thisValue(); auto* script = jsDynamicCast(thisValue); auto throwScope = DECLARE_THROW_SCOPE(vm); if (UNLIKELY(!script)) { return ERR::INVALID_ARG_VALUE(throwScope, globalObject, "this"_s, thisValue, "must be a Script"_s); } JSValue contextArg = callFrame->argument(0); if (contextArg.isUndefined()) { contextArg = JSC::constructEmptyObject(globalObject); } if (!contextArg.isObject()) { return ERR::INVALID_ARG_TYPE(throwScope, globalObject, "context"_s, "object"_s, contextArg); } RunningScriptOptions options; if (!options.fromJS(globalObject, vm, throwScope, contextArg)) { RETURN_IF_EXCEPTION(throwScope, {}); options = {}; } NakedPtr exception; JSValue result = JSC::evaluate(globalObject, script->source(), globalObject, exception); if (UNLIKELY(exception)) { if (handleException(globalObject, vm, exception, throwScope)) { return {}; } JSC::throwException(globalObject, throwScope, exception.get()); return {}; } RETURN_IF_EXCEPTION(throwScope, {}); return JSValue::encode(result); } JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); if (UNLIKELY(!script)) { return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } const auto& url = script->source().provider()->sourceMappingURLDirective(); return JSValue::encode(jsString(vm, url)); } JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedData, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); if (UNLIKELY(!script)) { return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } if (auto* buffer = script->getBytecodeBuffer()) { return JSValue::encode(buffer); } return JSValue::encode(jsUndefined()); } JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataProduced, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); if (UNLIKELY(!script)) { return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } return JSValue::encode(jsBoolean(script->cachedDataProduced())); } JSC_DEFINE_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValueEncoded, PropertyName)) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); if (UNLIKELY(!script)) { return ERR::INVALID_ARG_VALUE(scope, globalObject, "this"_s, thisValue, "must be a Script"_s); } switch (script->cachedDataRejected()) { case TriState::True: return JSValue::encode(jsBoolean(true)); case TriState::False: return JSValue::encode(jsBoolean(false)); default: return JSValue::encode(jsUndefined()); } } JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSValue code = callFrame->argument(0); if (!code.isString()) return ERR::INVALID_ARG_TYPE(scope, globalObject, "code"_s, "string"_s, code); JSValue contextArg = callFrame->argument(1); if (contextArg.isUndefined()) { contextArg = JSC::constructEmptyObject(globalObject); } if (!contextArg.isObject()) return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); JSObject* sandbox = asObject(contextArg); // Create context and run code auto* context = NodeVMGlobalObject::create(vm, defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure()); context->setContextifiedObject(sandbox); JSValue optionsArg = callFrame->argument(2); ScriptOptions options; if (optionsArg.isString()) { options.filename = optionsArg.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); } else if (!options.fromJS(globalObject, vm, scope, optionsArg)) { RETURN_IF_EXCEPTION(scope, {}); options = {}; } auto sourceCode = SourceCode( JSC::StringSourceProvider::create( code.toString(globalObject)->value(globalObject), JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); NakedPtr exception; JSValue result = JSC::evaluate(context, sourceCode, context, exception); if (UNLIKELY(exception)) { if (handleException(globalObject, vm, exception, scope)) { return {}; } JSC::throwException(globalObject, scope, exception.get()); return {}; } return JSValue::encode(result); } JSC_DEFINE_HOST_FUNCTION(vmModuleRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = JSC::getVM(globalObject); auto sourceStringValue = callFrame->argument(0); auto throwScope = DECLARE_THROW_SCOPE(vm); if (!sourceStringValue.isString()) { return ERR::INVALID_ARG_TYPE(throwScope, globalObject, "code"_s, "string"_s, sourceStringValue); } auto sourceString = sourceStringValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, encodedJSUndefined()); JSValue optionsArg = callFrame->argument(1); ScriptOptions options; if (optionsArg.isString()) { options.filename = optionsArg.toWTFString(globalObject); RETURN_IF_EXCEPTION(throwScope, {}); } else if (!options.fromJS(globalObject, vm, throwScope, optionsArg)) { RETURN_IF_EXCEPTION(throwScope, encodedJSUndefined()); options = {}; } SourceCode source( JSC::StringSourceProvider::create(sourceString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)), options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset)), options.lineOffset.zeroBasedInt(), options.columnOffset.zeroBasedInt()); WTF::NakedPtr exception; JSValue result = JSC::evaluate(globalObject, source, globalObject, exception); if (UNLIKELY(exception)) { if (handleException(globalObject, vm, exception, throwScope)) { return {}; } JSC::throwException(globalObject, throwScope, exception.get()); return {}; } return JSValue::encode(result); } JSC_DEFINE_HOST_FUNCTION(vmModuleCompileFunction, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); // Step 1: Argument validation // Get code argument (required) JSValue codeArg = callFrame->argument(0); if (!codeArg || !codeArg.isString()) return ERR::INVALID_ARG_TYPE(scope, globalObject, "code"_s, "string"_s, codeArg); // Get params argument (optional array of strings) MarkedArgumentBuffer parameters; JSValue paramsArg = callFrame->argument(1); if (paramsArg && !paramsArg.isUndefined()) { if (!paramsArg.isObject() || !isArray(globalObject, paramsArg)) return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "params"_s, "Array"_s, paramsArg); auto* paramsArray = jsCast(paramsArg); unsigned length = paramsArray->length(); for (unsigned i = 0; i < length; i++) { JSValue param = paramsArray->getIndexQuickly(i); if (!param.isString()) return ERR::INVALID_ARG_TYPE(scope, globalObject, "params"_s, "Array"_s, paramsArg); parameters.append(param); } } // Get options argument JSValue optionsArg = callFrame->argument(2); CompileFunctionOptions options; if (!options.fromJS(globalObject, vm, scope, optionsArg)) { RETURN_IF_EXCEPTION(scope, {}); options = {}; options.parsingContext = globalObject; } // Step 3: Create a new function // Prepare the function code by combining the parameters and body String sourceString = codeArg.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); // Create an ArgList with the parameters and function body for constructFunction MarkedArgumentBuffer constructFunctionArgs; // Add all parameters for (unsigned i = 0; i < parameters.size(); i++) { constructFunctionArgs.append(parameters.at(i)); } // Add the function body constructFunctionArgs.append(jsString(vm, sourceString)); // Create the source origin SourceOrigin sourceOrigin = JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(options.filename)); // Process contextExtensions if they exist JSScope* functionScope = !!options.parsingContext ? options.parsingContext : globalObject; if (!options.contextExtensions.isUndefinedOrNull() && !options.contextExtensions.isEmpty() && options.contextExtensions.isObject() && isArray(globalObject, options.contextExtensions)) { auto* contextExtensionsArray = jsCast(options.contextExtensions); unsigned length = contextExtensionsArray->length(); if (length > 0) { // Get the global scope from the parsing context JSScope* currentScope = options.parsingContext->globalScope(); // Create JSWithScope objects for each context extension for (unsigned i = 0; i < length; i++) { JSValue extension = contextExtensionsArray->getIndexQuickly(i); if (extension.isObject()) { JSObject* extensionObject = asObject(extension); currentScope = JSWithScope::create(vm, options.parsingContext, currentScope, extensionObject); } } // Use the outermost JSWithScope as our function scope functionScope = currentScope; } } options.parsingContext->setGlobalScopeExtension(functionScope); // Create the function using constructAnonymousFunction with the appropriate scope chain JSFunction* function = constructAnonymousFunction(globalObject, ArgList(constructFunctionArgs), sourceOrigin, options.filename, JSC::SourceTaintedOrigin::Untainted, TextPosition(options.lineOffset, options.columnOffset), functionScope); RETURN_IF_EXCEPTION(scope, {}); if (!function) return throwVMError(globalObject, scope, "Failed to compile function"_s); return JSValue::encode(function); } JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = JSC::getVM(globalObject); NodeVMScript* script = jsDynamicCast(callFrame->thisValue()); JSValue contextObjectValue = callFrame->argument(0); // TODO: options // JSValue optionsObjectValue = callFrame->argument(1); auto scope = DECLARE_THROW_SCOPE(vm); if (!script) { throwTypeError(globalObject, scope, "this.runInContext is not a function"_s); return {}; } if (!contextObjectValue || contextObjectValue.isUndefinedOrNull()) { contextObjectValue = JSC::constructEmptyObject(globalObject); } if (UNLIKELY(!contextObjectValue || !contextObjectValue.isObject())) { throwTypeError(globalObject, scope, "Context must be an object"_s); return {}; } // we don't care about options for now // TODO: options // bool didThrow = false; auto* zigGlobal = defaultGlobalObject(globalObject); JSObject* context = asObject(contextObjectValue); auto* targetContext = NodeVMGlobalObject::create( vm, zigGlobal->NodeVMGlobalObjectStructure()); return runInContext(targetContext, script, context, callFrame->argument(1)); } 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()); // 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); JSValue contextArg = callFrame->argument(0); if (contextArg.isUndefinedOrNull()) { contextArg = JSC::constructEmptyObject(globalObject); } if (!contextArg.isObject()) { return ERR::INVALID_ARG_TYPE(scope, globalObject, "context"_s, "object"_s, contextArg); } JSValue optionsArg = callFrame->argument(1); // Validate options argument if (!optionsArg.isUndefined() && !optionsArg.isObject()) { return ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsArg); } // If options is provided, validate name and origin properties if (optionsArg.isObject()) { JSObject* options = asObject(optionsArg); // Check name property if (JSValue nameValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "name"_s))) { RETURN_IF_EXCEPTION(scope, {}); if (!nameValue.isUndefined() && !nameValue.isString()) { return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.name"_s, "string"_s, nameValue); } } // Check origin property if (JSValue originValue = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "origin"_s))) { RETURN_IF_EXCEPTION(scope, {}); if (!originValue.isUndefined() && !originValue.isString()) { return ERR::INVALID_ARG_TYPE(scope, globalObject, "options.origin"_s, "string"_s, originValue); } } } JSObject* sandbox = asObject(contextArg); auto* targetContext = NodeVMGlobalObject::create(vm, defaultGlobalObject(globalObject)->NodeVMGlobalObjectStructure()); // 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 JSValue::encode(sandbox); } JSC_DEFINE_HOST_FUNCTION(vmModule_isContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { ArgList args(callFrame); 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)); } class NodeVMScriptPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; static NodeVMScriptPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure) { NodeVMScriptPrototype* ptr = new (NotNull, allocateCell(vm)) NodeVMScriptPrototype(vm, structure); ptr->finishCreation(vm); return ptr; } DECLARE_INFO; template static GCClient::IsoSubspace* subspaceFor(VM& vm) { STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, Base); return &vm.plainObjectSpace(); } static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); } private: NodeVMScriptPrototype(VM& vm, Structure* structure) : Base(vm, structure) { } void finishCreation(VM&); }; STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMScriptPrototype, NodeVMScriptPrototype::Base); static const struct HashTableValue scriptPrototypeTableValues[] = { { "createCachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptCreateCachedData, 1 } }, { "runInContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInContext, 2 } }, { "runInNewContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInNewContext, 2 } }, { "runInThisContext"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, scriptRunInThisContext, 2 } }, { "sourceMapURL"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetSourceMapURL, nullptr } }, { "cachedData"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedData, nullptr } }, { "cachedDataProduced"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataProduced, nullptr } }, { "cachedDataRejected"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataRejected, nullptr } }, }; // NodeVMGlobalObject* NodeVMGlobalObject::create(JSC::VM& vm, JSC::Structure* structure) // { // auto* obj = new (NotNull, allocateCell(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 // void NodeVMGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) // { // Base::visitChildren(cell, visitor); // // auto* thisObject = jsCast(cell); // // visitor.append(thisObject->m_proxyTarget); // } const ClassInfo NodeVMScriptPrototype::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptPrototype) }; const ClassInfo NodeVMScript::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScript) }; const ClassInfo NodeVMScriptConstructor::s_info = { "Script"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMScriptConstructor) }; const ClassInfo NodeVMGlobalObject::s_info = { "NodeVMGlobalObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMGlobalObject) }; JSC::ProgramExecutable* NodeVMScript::createExecutable() { auto& vm = JSC::getVM(globalObject()); m_cachedExecutable.set(vm, this, JSC::ProgramExecutable::create(globalObject(), m_source)); return m_cachedExecutable.get(); } void NodeVMScript::cacheBytecode() { if (!m_cachedExecutable) { createExecutable(); } m_cachedBytecode = getBytecode(globalObject(), m_cachedExecutable.get(), m_source); m_cachedDataProduced = m_cachedBytecode != nullptr; } JSC::JSUint8Array* NodeVMScript::getBytecodeBuffer() { if (!m_options.produceCachedData) { return nullptr; } if (!m_cachedBytecodeBuffer) { if (!m_cachedBytecode) { cacheBytecode(); } ASSERT(m_cachedBytecode); std::span bytes = m_cachedBytecode->span(); m_cachedBytecodeBuffer.set(vm(), this, WebCore::createBuffer(globalObject(), bytes)); } ASSERT(m_cachedBytecodeBuffer); return m_cachedBytecodeBuffer.get(); } DEFINE_VISIT_CHILDREN(NodeVMScript); template void NodeVMScript::visitChildrenImpl(JSCell* cell, Visitor& visitor) { NodeVMScript* thisObject = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); visitor.append(thisObject->m_cachedExecutable); visitor.append(thisObject->m_cachedBytecodeBuffer); } NodeVMScriptConstructor::NodeVMScriptConstructor(VM& vm, Structure* structure) : NodeVMScriptConstructor::Base(vm, structure, scriptConstructorCall, scriptConstructorConstruct) { } NodeVMScriptConstructor* NodeVMScriptConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) { NodeVMScriptConstructor* ptr = new (NotNull, allocateCell(vm)) NodeVMScriptConstructor(vm, structure); ptr->finishCreation(vm, prototype); return ptr; } void NodeVMScriptConstructor::finishCreation(VM& vm, JSObject* prototype) { Base::finishCreation(vm, 1, "Script"_s, PropertyAdditionMode::WithStructureTransition); putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); ASSERT(inherits(info())); } void NodeVMScriptPrototype::finishCreation(VM& vm) { Base::finishCreation(vm); reifyStaticProperties(vm, NodeVMScript::info(), scriptPrototypeTableValues, *this); JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); } JSObject* NodeVMScript::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMScriptPrototype::create(vm, globalObject, NodeVMScriptPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); } NodeVMScript* NodeVMScript::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, SourceCode source, ScriptOptions options) { NodeVMScript* ptr = new (NotNull, allocateCell(vm)) NodeVMScript(vm, structure, source, WTFMove(options)); ptr->finishCreation(vm); return ptr; } void NodeVMScript::finishCreation(VM& vm) { Base::finishCreation(vm); ASSERT(inherits(info())); } void NodeVMScript::destroy(JSCell* cell) { static_cast(cell)->NodeVMScript::~NodeVMScript(); } bool NodeVMGlobalObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSC::DeletePropertySlot& slot) { auto* thisObject = jsCast(cell); if (UNLIKELY(!thisObject->m_sandbox)) { return Base::deleteProperty(cell, globalObject, propertyName, slot); } auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* sandbox = thisObject->m_sandbox.get(); if (!sandbox->deleteProperty(sandbox, globalObject, propertyName, slot)) { return false; } RETURN_IF_EXCEPTION(scope, false); return Base::deleteProperty(cell, globalObject, propertyName, slot); } void NodeVMGlobalObject::getOwnPropertyNames(JSObject* cell, JSGlobalObject* globalObject, JSC::PropertyNameArray& propertyNames, JSC::DontEnumPropertiesMode mode) { auto* thisObject = jsCast(cell); if (thisObject->m_sandbox) { thisObject->m_sandbox->getOwnPropertyNames( thisObject->m_sandbox.get(), globalObject, propertyNames, mode); } Base::getOwnPropertyNames(cell, globalObject, propertyNames, mode); } static JSC::JSFunction* constructAnonymousFunction(JSC::JSGlobalObject* globalObject, const ArgList& args, const SourceOrigin& sourceOrigin, const String& fileName, JSC::SourceTaintedOrigin sourceTaintOrigin, TextPosition position, JSC::JSScope* scope) { VM& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); // wrap the arguments in an anonymous function expression int startOffset = 0; String code = stringifyAnonymousFunction(globalObject, args, throwScope, &startOffset); EXCEPTION_ASSERT(!!throwScope.exception() == code.isNull()); position.m_column = OrdinalNumber::fromZeroBasedInt(position.m_column.zeroBasedInt()); SourceCode sourceCode( JSC::StringSourceProvider::create(code, sourceOrigin, fileName, sourceTaintOrigin, position, SourceProviderSourceType::Program), position.m_line.oneBasedInt(), position.m_column.oneBasedInt()); LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; ParserError error; bool isEvalNode = false; // use default name Identifier name; std::unique_ptr program; if (code.is8Bit()) { Parser> parser(vm, sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, lexicallyScopedFeatures, JSParserScriptMode::Classic, SourceParseMode::ProgramMode, FunctionMode::None, SuperBinding::NotNeeded, ConstructorKind::None, DerivedContextType::None, isEvalNode, EvalContextType::None, nullptr); program = parser.parse(error, name, ParsingContext::Normal); } else { Parser> parser(vm, sourceCode, ImplementationVisibility::Public, JSParserBuiltinMode::NotBuiltin, lexicallyScopedFeatures, JSParserScriptMode::Classic, SourceParseMode::ProgramMode, FunctionMode::None, SuperBinding::NotNeeded, ConstructorKind::None, DerivedContextType::None, isEvalNode, EvalContextType::None, nullptr); program = parser.parse(error, name, ParsingContext::Normal); } if (!program) { RELEASE_ASSERT(error.isValid()); auto exception = error.toErrorObject(globalObject, sourceCode, -1); throwException(globalObject, throwScope, exception); return nullptr; } // the code we passed in should be a single expression statement containing a function expression StatementNode* statement = program->singleStatement(); if (!statement || !statement->isExprStatement()) { JSToken token; error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, "Parser error"_s, -1); auto exception = error.toErrorObject(globalObject, sourceCode, -1); throwException(globalObject, throwScope, exception); return nullptr; } ExprStatementNode* exprStatement = static_cast(statement); ExpressionNode* expression = exprStatement->expr(); if (!expression || !expression->isFuncExprNode()) { throwSyntaxError(globalObject, throwScope, "Expected a function expression"_s); return nullptr; } FunctionMetadataNode* metadata = static_cast(expression)->metadata(); ASSERT(metadata); if (!metadata) return nullptr; // metadata->setStartOffset(startOffset); ConstructAbility constructAbility = constructAbilityForParseMode(metadata->parseMode()); UnlinkedFunctionExecutable* unlinkedFunctionExecutable = UnlinkedFunctionExecutable::create( vm, sourceCode, metadata, UnlinkedNormalFunction, constructAbility, InlineAttribute::None, JSParserScriptMode::Classic, nullptr, std::nullopt, std::nullopt, DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None); unlinkedFunctionExecutable->recordParse(program->features(), metadata->lexicallyScopedFeatures(), /* hasCapturedVariables */ false); FunctionExecutable* functionExecutable = unlinkedFunctionExecutable->link(vm, nullptr, sourceCode, std::nullopt); JSScope* functionScope = scope ? scope : globalObject->globalScope(); Structure* structure = JSFunction::selectStructureForNewFuncExp(globalObject, functionExecutable); JSFunction* function = JSFunction::create(vm, globalObject, functionExecutable, functionScope, structure); return function; } // Helper function to create an anonymous function expression with parameters static String stringifyAnonymousFunction(JSGlobalObject* globalObject, const ArgList& args, ThrowScope& scope, int* outOffset) { // How we stringify functions is important for creating anonymous function expressions String program; if (args.isEmpty()) { // No arguments, just an empty function body program = "(function () {\n\n})"_s; // program = "(function () {})"_s; } else if (args.size() == 1) { // Just the function body auto body = args.at(0).toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); program = tryMakeString("(function () {"_s, body, "})"_s); *outOffset = "(function () {"_s.length(); if (UNLIKELY(!program)) { throwOutOfMemoryError(globalObject, scope); return {}; } } else { // Process parameters and body unsigned parameterCount = args.size() - 1; StringBuilder paramString; for (unsigned i = 0; i < parameterCount; ++i) { auto param = args.at(i).toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); if (i > 0) paramString.append(", "_s); paramString.append(param); } auto body = args.at(parameterCount).toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); program = tryMakeString("(function ("_s, paramString.toString(), ") {"_s, body, "})"_s); *outOffset = "(function ("_s.length() + paramString.length() + ") {"_s.length(); if (UNLIKELY(!program)) { throwOutOfMemoryError(globalObject, scope); return {}; } } return program; } static RefPtr getBytecode(JSGlobalObject* globalObject, JSC::ProgramExecutable* executable, const JSC::SourceCode& source) { auto& vm = JSC::getVM(globalObject); JSC::CodeCache* cache = vm.codeCache(); JSC::ParserError parserError; JSC::UnlinkedProgramCodeBlock* unlinked = cache->getUnlinkedProgramCodeBlock(vm, executable, source, {}, parserError); if (!unlinked || parserError.isValid()) { return nullptr; } JSC::LexicallyScopedFeatures lexicallyScopedFeatures = globalObject->globalScopeExtension() ? TaintedByWithScopeLexicallyScopedFeature : NoLexicallyScopedFeatures; JSC::BytecodeCacheError bytecodeCacheError; FileSystem::FileHandle fileHandle; return JSC::serializeBytecode(vm, unlinked, source, JSC::SourceCodeType::ProgramType, lexicallyScopedFeatures, JSParserScriptMode::Classic, fileHandle, bytecodeCacheError, {}); } static JSC::EncodedJSValue createCachedData(JSGlobalObject* globalObject, const JSC::SourceCode& source) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSC::ProgramExecutable* executable = JSC::ProgramExecutable::create(globalObject, source); RETURN_IF_EXCEPTION(scope, {}); RefPtr bytecode = getBytecode(globalObject, executable, source); RETURN_IF_EXCEPTION(scope, {}); if (UNLIKELY(!bytecode)) { return throwVMError(globalObject, scope, "createCachedData failed"_s); } std::span bytes = bytecode->span(); auto* buffer = WebCore::createBuffer(globalObject, bytes); RETURN_IF_EXCEPTION(scope, {}); ASSERT(buffer); return JSValue::encode(buffer); } class NodeVMModuleRequest final { public: NodeVMModuleRequest(WTF::String specifier, WTF::HashMap importAttributes = {}) : m_specifier(WTFMove(specifier)) , m_importAttributes(WTFMove(importAttributes)) { } const WTF::String& specifier() const { return m_specifier; } void specifier(WTF::String value) { m_specifier = value; } const WTF::HashMap& importAttributes() const { return m_importAttributes; } void addImportAttribute(WTF::String key, WTF::String value) { m_importAttributes.set(WTFMove(key), WTFMove(value)); } JSArray* toJS(JSGlobalObject* globalObject) const { JSArray* array = JSC::constructEmptyArray(globalObject, nullptr, 2); array->putDirectIndex(globalObject, 0, JSC::jsString(globalObject->vm(), m_specifier)); JSObject* attributes = JSC::constructEmptyObject(globalObject); 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); } array->putDirectIndex(globalObject, 1, attributes); return array; } private: WTF::String m_specifier; WTF::HashMap m_importAttributes; }; JSC_DECLARE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetStatus); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetNamespace); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetError); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleInstantiate); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleEvaluate); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleLink); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData); JSC_DECLARE_HOST_FUNCTION(jsNodeVmModuleSetExport); static const HashTableValue NodeVMModulePrototypeTableValues[] = { { "identifier"_s, static_cast(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsNodeVmModuleGetterIdentifier, nullptr } }, { "getStatusCode"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatusCode, 0 } }, { "getStatus"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetStatus, 0 } }, { "getNamespace"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetNamespace, 0 } }, { "getError"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetError, 0 } }, { "instantiate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleInstantiate, 0 } }, { "evaluate"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleEvaluate, 2 } }, { "getModuleRequests"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleGetModuleRequests, 0 } }, { "link"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleLink, 2 } }, { "createCachedData"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleCreateCachedData, 0 } }, { "setExport"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsNodeVmModuleSetExport, 2 } }, }; class NodeVMModulePrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; static NodeVMModulePrototype* create(VM& vm, Structure* structure) { NodeVMModulePrototype* prototype = new (NotNull, allocateCell(vm)) NodeVMModulePrototype(vm, structure); prototype->finishCreation(vm); return prototype; } DECLARE_INFO; template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, Base); return &vm.plainObjectSpace(); } static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); } private: NodeVMModulePrototype(VM& vm, Structure* structure) : Base(vm, structure) { } void finishCreation(VM& vm) { Base::finishCreation(vm); ASSERT(inherits(info())); reifyStaticProperties(vm, info(), NodeVMModulePrototypeTableValues, *this); this->structure()->setMayBePrototype(true); } }; STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModulePrototype, NodeVMModulePrototype::Base); class NodeVMModuleConstructor final : public JSC::InternalFunction { public: using Base = JSC::InternalFunction; static NodeVMModuleConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype); DECLARE_EXPORT_INFO; static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, Base::StructureFlags), info()); } private: NodeVMModuleConstructor(JSC::VM& vm, JSC::Structure* structure); void finishCreation(JSC::VM&, JSC::JSObject* prototype); }; STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(NodeVMModuleConstructor, JSC::InternalFunction); class NodeVMModule : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; enum class Status : uint8_t { Unlinked, Linking, Linked, Evaluating, Evaluated, Errored }; enum class Type : uint8_t { SourceText, Synthetic, }; static NodeVMModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args); const WTF::String& identifier() const { return m_identifier; } Status status() const { return m_status; } void status(Status value) { m_status = value; } JSObject* namespace_() const { return m_namespace.get(); } void namespace_(VM& vm, JSObject* value) { m_namespace.set(vm, this, value); } protected: WTF::String m_identifier; Status m_status = Status::Unlinked; mutable WriteBarrier m_namespace; NodeVMModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) : Base(vm, structure) , m_identifier(WTFMove(identifier)) { } DECLARE_EXPORT_INFO; DECLARE_VISIT_CHILDREN; }; class NodeVMSourceTextModule final : public NodeVMModule { public: using Base = NodeVMModule; static NodeVMSourceTextModule* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args); template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { if constexpr (mode == JSC::SubspaceAccess::Concurrently) return nullptr; return WebCore::subspaceForImpl( vm, [](auto& spaces) { return spaces.m_clientSubspaceForNodeVMSourceTextModule.get(); }, [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForNodeVMSourceTextModule = std::forward(space); }, [](auto& spaces) { return spaces.m_subspaceForNodeVMSourceTextModule.get(); }, [](auto& spaces, auto&& space) { spaces.m_subspaceForNodeVMSourceTextModule = std::forward(space); }); } static void destroy(JSC::JSCell* cell); static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } static JSObject* createPrototype(VM& vm, JSGlobalObject* globalObject); const WTF::Vector& moduleRequests() const { return m_moduleRequests; } void addModuleRequest(NodeVMModuleRequest request) { m_moduleRequests.append(WTFMove(request)); } DECLARE_EXPORT_INFO; private: WTF::Vector m_moduleRequests; NodeVMSourceTextModule(JSC::VM& vm, JSC::Structure* structure, WTF::String identifier) : Base(vm, structure, WTFMove(identifier)) { } void finishCreation(JSC::VM& vm) { Base::finishCreation(vm); ASSERT(inherits(info())); } }; NodeVMModule* NodeVMModule::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ArgList args) { auto scope = DECLARE_THROW_SCOPE(vm); JSValue disambiguator = args.at(2); if (disambiguator.isString()) { return NodeVMSourceTextModule::create(vm, globalObject, args); } if (disambiguator.inherits(JSArray::info())) { // return NodeVMSyntheticModule::create(vm, globalObject, args); } throwArgumentTypeError(*globalObject, scope, 2, "sourceText or syntheticExportNames"_s, "Module"_s, "Module"_s, "string or array"_s); return nullptr; } NodeVMSourceTextModule* NodeVMSourceTextModule::create(VM& vm, JSGlobalObject* globalObject, ArgList args) { auto scope = DECLARE_THROW_SCOPE(vm); JSValue identifierValue = args.at(0); if (!identifierValue.isString()) { throwArgumentTypeError(*globalObject, scope, 0, "identifier"_s, "Module"_s, "Module"_s, "string"_s); return nullptr; } JSValue contextValue = args.at(1); if (!contextValue.isObject()) { // TODO(@heimskr): if undefined, use current execution context throwArgumentTypeError(*globalObject, scope, 1, "context"_s, "Module"_s, "Module"_s, "object"_s); return nullptr; } JSValue sourceTextValue = args.at(2); if (!sourceTextValue.isString()) { throwArgumentTypeError(*globalObject, scope, 2, "sourceText"_s, "Module"_s, "Module"_s, "string"_s); return nullptr; } JSValue lineOffsetValue = args.at(3); if (!lineOffsetValue.isUInt32AsAnyInt()) { throwArgumentTypeError(*globalObject, scope, 3, "lineOffset"_s, "Module"_s, "Module"_s, "number"_s); return nullptr; } JSValue columnOffsetValue = args.at(4); if (!columnOffsetValue.isUInt32AsAnyInt()) { throwArgumentTypeError(*globalObject, scope, 4, "columnOffset"_s, "Module"_s, "Module"_s, "number"_s); return nullptr; } JSValue cachedDataValue = args.at(5); WTF::Vector cachedData; if (!cachedDataValue.isUndefined() && !extractCachedData(cachedDataValue, cachedData)) { throwArgumentTypeError(*globalObject, scope, 5, "cachedData"_s, "Module"_s, "Module"_s, "Buffer, TypedArray, or DataView"_s); return nullptr; } auto* zigGlobalObject = defaultGlobalObject(globalObject); NodeVMSourceTextModule* ptr = new (NotNull, allocateCell(vm)) NodeVMSourceTextModule(vm, zigGlobalObject->NodeVMSourceTextModuleStructure(), identifierValue.toWTFString(globalObject)); ptr->finishCreation(vm); return ptr; } void NodeVMSourceTextModule::destroy(JSCell* cell) { static_cast(cell)->NodeVMSourceTextModule::~NodeVMSourceTextModule(); } static EncodedJSValue constructModule(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = {}) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); ArgList args(callFrame); NodeVMModule* module = NodeVMModule::create(vm, globalObject, args); return JSValue::encode(module); } JSC_DEFINE_CUSTOM_GETTER(jsNodeVmModuleGetterIdentifier, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) { auto* thisObject = jsCast(JSC::JSValue::decode(thisValue)); return JSValue::encode(JSC::jsString(globalObject->vm(), thisObject->identifier())); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatusCode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(JSC::jsNumber(static_cast(thisObject->status()))); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetStatus, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = jsCast(callFrame->thisValue()); using enum NodeVMModule::Status; switch (thisObject->status()) { case Unlinked: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("unlinked"_s))); case Linking: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linking"_s))); case Linked: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("linked"_s))); case Evaluating: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluating"_s))); case Evaluated: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("evaluated"_s))); case Errored: return JSValue::encode(JSC::jsString(globalObject->vm(), WTF::String("errored"_s))); default: return JSC::encodedJSUndefined(); } } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetNamespace, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto* thisObject = jsCast(callFrame->thisValue()); return JSValue::encode(thisObject->namespace_()); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { // auto* thisObject = jsCast(callFrame->thisValue()); return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleGetModuleRequests, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { // auto* thisObject = jsCast(callFrame->thisValue()); return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleEvaluate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { // auto* thisObject = jsCast(callFrame->thisValue()); return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleLink, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { // auto* thisObject = jsCast(callFrame->thisValue()); return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleInstantiate, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { // auto* thisObject = jsCast(callFrame->thisValue()); return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleSetExport, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { // auto* thisObject = jsCast(callFrame->thisValue()); return JSC::encodedJSUndefined(); } JSC_DEFINE_HOST_FUNCTION(jsNodeVmModuleCreateCachedData, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { // auto* thisObject = jsCast(callFrame->thisValue()); return JSC::encodedJSUndefined(); } template void NodeVMModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) { auto* vmModule = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(vmModule, info()); Base::visitChildren(vmModule, visitor); visitor.append(vmModule->m_namespace); } DEFINE_VISIT_CHILDREN(NodeVMModule); const JSC::ClassInfo NodeVMModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; const JSC::ClassInfo NodeVMSourceTextModule::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMSourceTextModule) }; const JSC::ClassInfo NodeVMModulePrototype::s_info = { "NodeVMSourceTextModule"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModulePrototype) }; const JSC::ClassInfo NodeVMModuleConstructor::s_info = { "Module"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(NodeVMModuleConstructor) }; JSC_DEFINE_HOST_FUNCTION(vmIsModuleNamespaceObject, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); return JSValue::encode(JSC::jsBoolean(false)); // TODO(@heimskr): implement // JSValue argument = callFrame->argument(0); // if (!argument.isObject()) { // return JSValue::encode(JSC::jsBoolean(false)); // } // JSObject* object = asObject(argument); } JSC::JSValue createNodeVMBinding(Zig::GlobalObject* globalObject) { VM& vm = globalObject->vm(); auto* obj = constructEmptyObject(globalObject); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Script"_s)), defaultGlobalObject(globalObject)->NodeVMScript(), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "Module"_s)), 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); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "isContext"_s)), JSC::JSFunction::create(vm, globalObject, 0, "isContext"_s, vmModule_isContext, ImplementationVisibility::Public), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "runInNewContext"_s)), JSC::JSFunction::create(vm, globalObject, 0, "runInNewContext"_s, vmModuleRunInNewContext, ImplementationVisibility::Public), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "runInThisContext"_s)), JSC::JSFunction::create(vm, globalObject, 0, "runInThisContext"_s, vmModuleRunInThisContext, ImplementationVisibility::Public), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "compileFunction"_s)), JSC::JSFunction::create(vm, globalObject, 0, "compileFunction"_s, vmModuleCompileFunction, ImplementationVisibility::Public), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "isModuleNamespaceObject"_s)), JSC::JSFunction::create(vm, globalObject, 0, "isModuleNamespaceObject"_s, vmIsModuleNamespaceObject, ImplementationVisibility::Public), 1); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kUnlinked"_s)), JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Unlinked)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kLinking"_s)), JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Linking)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kLinked"_s)), JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Linked)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kEvaluating"_s)), JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Evaluating)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kEvaluated"_s)), JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Evaluated)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kErrored"_s)), JSC::jsNumber(static_cast(NodeVMSourceTextModule::Status::Errored)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kSourceText"_s)), JSC::jsNumber(static_cast(NodeVMModule::Type::SourceText)), 0); obj->putDirect( vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "kSynthetic"_s)), JSC::jsNumber(static_cast(NodeVMModule::Type::Synthetic)), 0); return obj; } void configureNodeVM(JSC::VM& vm, Zig::GlobalObject* globalObject) { globalObject->m_NodeVMScriptClassStructure.initLater( [](LazyClassStructure::Initializer& init) { auto prototype = NodeVMScript::createPrototype(init.vm, init.global); auto* structure = NodeVMScript::createStructure(init.vm, init.global, prototype); auto* constructorStructure = NodeVMScriptConstructor::createStructure( init.vm, init.global, init.global->m_functionPrototype.get()); auto* constructor = NodeVMScriptConstructor::create( init.vm, init.global, constructorStructure, prototype); init.setPrototype(prototype); init.setStructure(structure); init.setConstructor(constructor); }); globalObject->m_NodeVMSourceTextModuleClassStructure.initLater( [](LazyClassStructure::Initializer& init) { auto prototype = NodeVMSourceTextModule::createPrototype(init.vm, init.global); auto* structure = NodeVMSourceTextModule::createStructure(init.vm, init.global, prototype); auto* constructorStructure = NodeVMModuleConstructor::createStructure( init.vm, init.global, init.global->m_functionPrototype.get()); auto* constructor = NodeVMModuleConstructor::create( init.vm, init.global, constructorStructure, prototype); init.setPrototype(prototype); init.setStructure(structure); init.setConstructor(constructor); }); // globalObject->m_NodeVMSyntheticModuleClassStructure.initLater( // [](LazyClassStructure::Initializer& init) { // auto prototype = NodeVMSyntheticModule::createPrototype(init.vm, init.global); // auto* structure = NodeVMSyntheticModule::createStructure(init.vm, init.global, prototype); // auto* constructorStructure = NodeVMModuleConstructor::createStructure( // init.vm, init.global, init.global->m_functionPrototype.get()); // auto* constructor = NodeVMModuleConstructor::create( // init.vm, init.global, constructorStructure, prototype); // }); globalObject->m_cachedNodeVMGlobalObjectStructure.initLater( [](const JSC::LazyProperty::Initializer& init) { init.set(createNodeVMGlobalObjectStructure(init.vm)); }); } JSObject* NodeVMSourceTextModule::createPrototype(VM& vm, JSGlobalObject* globalObject) { return NodeVMModulePrototype::create(vm, NodeVMModulePrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); } NodeVMModuleConstructor* NodeVMModuleConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) { NodeVMModuleConstructor* ptr = new (NotNull, allocateCell(vm)) NodeVMModuleConstructor(vm, structure); ptr->finishCreation(vm, prototype); return ptr; } JSC_DEFINE_HOST_FUNCTION(moduleConstructorCall, (JSGlobalObject * globalObject, CallFrame* callFrame)) { return constructModule(globalObject, callFrame); } JSC_DEFINE_HOST_FUNCTION(moduleConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame)) { return constructModule(globalObject, callFrame, callFrame->newTarget()); } NodeVMModuleConstructor::NodeVMModuleConstructor(VM& vm, Structure* structure) : NodeVMModuleConstructor::Base(vm, structure, moduleConstructorCall, moduleConstructorConstruct) { } void NodeVMModuleConstructor::finishCreation(VM& vm, JSObject* prototype) { Base::finishCreation(vm, 1, "Module"_s, PropertyAdditionMode::WithStructureTransition); putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); ASSERT(inherits(info())); } } // namespace Bun