#include "root.h" #include "NodeVMScript.h" #include "JavaScriptCore/JSObjectInlines.h" #include "wtf/text/ExternalStringImpl.h" #include "JavaScriptCore/FunctionPrototype.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 "Buffer.h" #include "GCDefferalContext.h" #include "Buffer.h" #include #include "DOMJITIDLConvert.h" #include "DOMJITIDLType.h" #include "DOMJITIDLTypeFilter.h" #include "DOMJITHelpers.h" #include #include namespace WebCore { using namespace JSC; class ScriptOptions { public: String filename; OrdinalNumber lineOffset; OrdinalNumber columnOffset; String cachedData; bool produceCachedData; bool importModuleDynamically; static std::optional fromJS(JSC::JSGlobalObject* globalObject, JSC::JSValue optionsArg, bool& failed) { auto& vm = globalObject->vm(); ScriptOptions opts; bool any = false; if (!optionsArg.isUndefined()) { if (!optionsArg.isObject()) { auto scope = DECLARE_THROW_SCOPE(vm); throwVMTypeError(globalObject, scope, "options must be an object"_s); failed = true; return std::nullopt; } JSObject* options = asObject(optionsArg); if (JSValue filenameOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "filename"_s))) { if (filenameOpt.isString()) { opts.filename = filenameOpt.toWTFString(globalObject); any = true; } } if (JSValue lineOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "lineOffset"_s))) { if (lineOffsetOpt.isAnyInt()) { opts.lineOffset = OrdinalNumber::fromZeroBasedInt(lineOffsetOpt.asAnyInt()); any = true; } } if (JSValue columnOffsetOpt = options->getIfPropertyExists(globalObject, Identifier::fromString(vm, "columnOffset"_s))) { if (columnOffsetOpt.isAnyInt()) { opts.columnOffset = OrdinalNumber::fromZeroBasedInt(columnOffsetOpt.asAnyInt()); any = true; } } // TODO: cachedData // TODO: importModuleDynamically } if (any) return opts; return std::nullopt; } }; static EncodedJSValue constructScript(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue newTarget = JSValue()) { VM& vm = globalObject->vm(); JSValue callee = callFrame->jsCallee(); ArgList args(callFrame); JSValue sourceArg = args.at(0); String sourceString = sourceArg.isUndefined() ? emptyString() : sourceArg.toWTFString(globalObject); JSValue optionsArg = args.at(1); bool didThrow = false; ScriptOptions options; if (auto scriptOptions = ScriptOptions::fromJS(globalObject, optionsArg, didThrow)) { options = scriptOptions.value(); } if (didThrow) return JSValue::encode(jsUndefined()); auto* zigGlobalObject = reinterpret_cast(globalObject); Structure* structure = zigGlobalObject->NodeVMScriptStructure(); if (UNLIKELY(zigGlobalObject->NodeVMScript() != newTarget)) { auto scope = DECLARE_THROW_SCOPE(vm); JSObject* targetObj = asObject(newTarget); auto* functionGlobalObject = reinterpret_cast(getFunctionRealm(globalObject, targetObj)); RETURN_IF_EXCEPTION(scope, {}); structure = InternalFunction::createSubclassStructure( globalObject, targetObj, functionGlobalObject->NodeVMScriptStructure()); scope.release(); } auto scope = DECLARE_THROW_SCOPE(vm); 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, {}); NodeVMScript* script = NodeVMScript::create(vm, globalObject, structure, source); return JSValue::encode(JSValue(script)); } static EncodedJSValue runInContext(JSGlobalObject* globalObject, NodeVMScript* script, JSObject* globalThis, JSScope* scope, JSValue optionsArg) { auto& vm = globalObject->vm(); auto throwScope = DECLARE_THROW_SCOPE(vm); JSC::DirectEvalExecutable* executable = nullptr; if (JSC::DirectEvalExecutable* existingEval = script->m_cachedDirectExecutable.get()) { executable = existingEval; } if (executable == nullptr) { // Note: it accepts a JSGlobalObject, but it just reads stuff from JSC::VM. executable = JSC::DirectEvalExecutable::create( globalObject, script->source(), DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None, false, false, EvalContextType::None, nullptr, nullptr, ECMAMode::sloppy()); RETURN_IF_EXCEPTION(throwScope, {}); script->m_cachedDirectExecutable.set(vm, script, executable); } auto catchScope = DECLARE_CATCH_SCOPE(vm); JSValue result = vm.interpreter.executeEval(executable, globalObject, scope); if (UNLIKELY(catchScope.exception())) { auto returnedException = catchScope.exception(); catchScope.clearException(); JSC::throwException(globalObject, throwScope, returnedException); return JSValue::encode({}); } 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_CUSTOM_GETTER(scriptGetCachedDataRejected, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) { auto& vm = globalObject->vm(); return JSValue::encode(jsBoolean(true)); // TODO } JSC_DEFINE_HOST_FUNCTION(scriptCreateCachedData, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); return throwVMError(globalObject, scope, "TODO: Script.createCachedData"_s); } JSC_DEFINE_HOST_FUNCTION(scriptRunInContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); JSValue thisValue = callFrame->thisValue(); auto* script = jsDynamicCast(thisValue); auto scope = DECLARE_THROW_SCOPE(vm); if (UNLIKELY(!script)) { throwVMTypeError(globalObject, scope, "Script.prototype.runInContext can only be called on a Script object"_s); return JSValue::encode({}); } ArgList args(callFrame); JSValue contextArg = args.at(0); if (!UNLIKELY(contextArg.isObject())) { throwVMTypeError(globalObject, scope, "context parameter must be a contextified object"_s); return JSValue::encode({}); } JSObject* context = asObject(contextArg); auto* zigGlobalObject = reinterpret_cast(globalObject); JSValue scopeVal = zigGlobalObject->vmModuleContextMap()->get(context); if (UNLIKELY(scopeVal.isUndefined())) { throwVMTypeError(globalObject, scope, "context parameter must be a contextified object"_s); return JSValue::encode({}); } JSScope* jsScope = jsDynamicCast(scopeVal); if (UNLIKELY(!jsScope)) { throwVMTypeError(globalObject, scope, "context parameter must be a contextified object"_s); return JSValue::encode({}); } JSGlobalProxy* globalProxy = jsDynamicCast(context->getPrototypeDirect()); if (!globalProxy) { auto scope = DECLARE_THROW_SCOPE(vm); throwVMTypeError(globalObject, scope, "context parameter must be a contextified object"_s); return JSValue::encode({}); } return runInContext(globalProxy->target(), script, context, jsScope, args.at(1)); } JSC_DEFINE_HOST_FUNCTION(vmModuleRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); auto sourceStringValue = callFrame->argument(0); JSValue contextObjectValue = callFrame->argument(1); JSValue optionsObjectValue = callFrame->argument(2); auto throwScope = DECLARE_THROW_SCOPE(vm); if (!sourceStringValue.isString()) { throwTypeError(globalObject, throwScope, "Script code must be a string"_s); return JSValue::encode({}); } auto sourceString = sourceStringValue.toWTFString(globalObject); if (!contextObjectValue || contextObjectValue.isUndefinedOrNull()) { contextObjectValue = JSC::constructEmptyObject(globalObject); } if (UNLIKELY(!contextObjectValue || !contextObjectValue.isObject())) { throwTypeError(globalObject, throwScope, "Context must be an object"_s); return JSValue::encode({}); } // we don't care about options for now ScriptOptions options; { bool didThrow = false; if (auto scriptOptions = ScriptOptions::fromJS(globalObject, optionsObjectValue, didThrow)) { options = scriptOptions.value(); } if (UNLIKELY(didThrow)) { return JSValue::encode({}); } } 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()); auto* zigGlobal = reinterpret_cast(globalObject); JSObject* context = asObject(contextObjectValue); auto* targetContext = JSC::JSGlobalObject::create( vm, zigGlobal->globalObjectStructure()); auto* executable = JSC::DirectEvalExecutable::create( targetContext, source, DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None, false, false, EvalContextType::None, nullptr, nullptr, ECMAMode::sloppy()); RETURN_IF_EXCEPTION(throwScope, {}); auto proxyStructure = JSGlobalProxy::createStructure(vm, globalObject, JSC::jsNull()); auto proxy = JSGlobalProxy::create(vm, proxyStructure); proxy->setTarget(vm, targetContext); context->setPrototypeDirect(vm, proxy); JSScope* contextScope = JSWithScope::create(vm, targetContext, targetContext->globalScope(), context); auto catchScope = DECLARE_CATCH_SCOPE(vm); JSValue result = vm.interpreter.executeEval(executable, targetContext, contextScope); if (UNLIKELY(catchScope.exception())) { auto returnedException = catchScope.exception(); catchScope.clearException(); JSC::throwException(globalObject, throwScope, returnedException); } RETURN_IF_EXCEPTION(throwScope, {}); return JSValue::encode(result); } JSC_DEFINE_HOST_FUNCTION(vmModuleRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); auto sourceStringValue = callFrame->argument(0); JSValue contextObjectValue = callFrame->argument(1); JSValue optionsObjectValue = callFrame->argument(2); auto throwScope = DECLARE_THROW_SCOPE(vm); if (!sourceStringValue.isString()) { throwTypeError(globalObject, throwScope, "Script code must be a string"_s); return JSValue::encode({}); } auto sourceString = sourceStringValue.toWTFString(globalObject); if (!contextObjectValue || contextObjectValue.isUndefinedOrNull()) { contextObjectValue = JSC::constructEmptyObject(globalObject); } if (UNLIKELY(!contextObjectValue || !contextObjectValue.isObject())) { throwTypeError(globalObject, throwScope, "Context must be an object"_s); return JSValue::encode({}); } ScriptOptions options; { bool didThrow = false; if (auto scriptOptions = ScriptOptions::fromJS(globalObject, optionsObjectValue, didThrow)) { options = scriptOptions.value(); } if (UNLIKELY(didThrow)) { return JSValue::encode({}); } } 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()); auto* zigGlobal = reinterpret_cast(globalObject); JSObject* context = asObject(contextObjectValue); auto proxyStructure = zigGlobal->globalProxyStructure(); auto proxy = JSGlobalProxy::create(vm, proxyStructure); proxy->setTarget(vm, globalObject); context->setPrototypeDirect(vm, proxy); auto* executable = JSC::DirectEvalExecutable::create( globalObject, source, DerivedContextType::None, NeedsClassFieldInitializer::No, PrivateBrandRequirement::None, false, false, EvalContextType::None, nullptr, nullptr, ECMAMode::sloppy()); RETURN_IF_EXCEPTION(throwScope, {}); JSScope* contextScope = JSWithScope::create(vm, globalObject, globalObject->globalScope(), context); auto catchScope = DECLARE_CATCH_SCOPE(vm); JSValue result = vm.interpreter.executeEval(executable, globalObject, contextScope); if (UNLIKELY(catchScope.exception())) { auto returnedException = catchScope.exception(); catchScope.clearException(); JSC::throwException(globalObject, throwScope, returnedException); } RETURN_IF_EXCEPTION(throwScope, {}); return JSValue::encode(result); } JSC_DEFINE_HOST_FUNCTION(scriptRunInNewContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); NodeVMScript* script = jsDynamicCast(callFrame->thisValue()); JSValue contextObjectValue = callFrame->argument(0); JSValue optionsObjectValue = callFrame->argument(1); auto scope = DECLARE_THROW_SCOPE(vm); if (!script) { throwTypeError(globalObject, scope, "Script.prototype.runInNewContext can only be called on a Script object"_s); return JSValue::encode({}); } if (!contextObjectValue || contextObjectValue.isUndefinedOrNull()) { contextObjectValue = JSC::constructEmptyObject(globalObject); } if (UNLIKELY(!contextObjectValue || !contextObjectValue.isObject())) { throwTypeError(globalObject, scope, "Context must be an object"_s); return JSValue::encode({}); } // we don't care about options for now bool didThrow = false; auto* zigGlobal = reinterpret_cast(globalObject); JSObject* context = asObject(contextObjectValue); auto* targetContext = JSC::JSGlobalObject::create( vm, zigGlobal->globalObjectStructure()); // auto proxyStructure = JSGlobalProxy::createStructure(vm, globalObject, JSC::jsNull()); // auto proxy = JSGlobalProxy::create(vm, proxyStructure); // proxy->setTarget(vm, targetContext); // context->setPrototypeDirect(vm, proxy); JSScope* contextScope = JSWithScope::create(vm, targetContext, targetContext->globalScope(), context); return runInContext(globalObject, script, targetContext, contextScope, callFrame->argument(0)); } JSC_DEFINE_HOST_FUNCTION(scriptRunInThisContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); JSValue thisValue = callFrame->thisValue(); auto* script = jsDynamicCast(thisValue); auto throwScope = DECLARE_THROW_SCOPE(vm); if (UNLIKELY(!script)) { return throwVMTypeError(globalObject, throwScope, "Script.prototype.runInThisContext can only be called on a Script object"_s); } JSValue contextArg = callFrame->argument(0); if (!contextArg || contextArg.isUndefinedOrNull()) { contextArg = JSC::constructEmptyObject(globalObject); } if (!contextArg.isObject()) { return throwVMTypeError(globalObject, throwScope, "context must be an object"_s); } JSObject* context = asObject(contextArg); JSWithScope* contextScope = JSWithScope::create(vm, globalObject, globalObject->globalScope(), context); return runInContext(globalObject, script, globalObject->globalThis(), contextScope, callFrame->argument(1)); } JSC_DEFINE_CUSTOM_GETTER(scriptGetSourceMapURL, (JSGlobalObject * globalObject, EncodedJSValue thisValueEncoded, PropertyName)) { auto& vm = globalObject->vm(); JSValue thisValue = JSValue::decode(thisValueEncoded); auto* script = jsDynamicCast(thisValue); if (UNLIKELY(!script)) { auto scope = DECLARE_THROW_SCOPE(vm); return throwVMTypeError(globalObject, scope, "Script.prototype.sourceMapURL getter can only be called on a Script object"_s); } // FIXME: doesn't seem to work? Just returns undefined const auto& url = script->source().provider()->sourceMappingURLDirective(); return JSValue::encode(jsString(vm, url)); } JSC_DEFINE_HOST_FUNCTION(vmModule_createContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { auto& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSValue contextArg = callFrame->argument(0); if (contextArg.isEmpty() || contextArg.isUndefinedOrNull()) { contextArg = JSC::constructEmptyObject(globalObject); } if (!contextArg.isObject()) { return throwVMTypeError(globalObject, scope, "parameter to createContext must be an object"_s); } JSObject* context = asObject(contextArg); auto* zigGlobalObject = reinterpret_cast(globalObject); auto* targetContext = JSC::JSGlobalObject::create( vm, zigGlobalObject->globalObjectStructure()); auto proxyStructure = zigGlobalObject->globalProxyStructure(); auto proxy = JSGlobalProxy::create(vm, proxyStructure); proxy->setTarget(vm, targetContext); context->setPrototypeDirect(vm, proxy); JSScope* contextScope = JSWithScope::create(vm, targetContext, targetContext->globalScope(), context); zigGlobalObject->vmModuleContextMap()->set(vm, context, contextScope); return JSValue::encode(context); } JSC_DEFINE_HOST_FUNCTION(vmModule_isContext, (JSGlobalObject * globalObject, CallFrame* callFrame)) { ArgList args(callFrame); JSValue contextArg = callFrame->argument(0); bool isContext; if (!contextArg || !contextArg.isObject()) { isContext = false; } else { auto* zigGlobalObject = reinterpret_cast(globalObject); isContext = zigGlobalObject->vmModuleContextMap()->has(asObject(contextArg)); } return JSValue::encode(jsBoolean(isContext)); } class NodeVMScriptPrototype final : public JSNonFinalObject { public: using Base = 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) { 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[] = { { "cachedDataRejected"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, scriptGetCachedDataRejected, nullptr } }, { "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 } }, }; 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) }; 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_cachedDirectExecutable); } 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) { NodeVMScript* ptr = new (NotNull, allocateCell(vm)) NodeVMScript(vm, structure, source); 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(); } }