/* * Copyright (C) 2015-2019 Apple Inc. All Rights Reserved. * Copyright (C) 2016 Yusuke Suzuki <utatane.tea@gmail.com>. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "JSModuleLoader.h" #include "BuiltinNames.h" #include "CatchScope.h" #include "JSCInlines.h" #include "JSInternalPromise.h" #include "JSMap.h" #include "JSModuleNamespaceObject.h" #include "JSModuleRecord.h" #include "JSSourceCode.h" #include "JSWebAssembly.h" #include "ModuleAnalyzer.h" #include "Nodes.h" #include "ObjectConstructor.h" #include "Parser.h" #include "ParserError.h" namespace JSC { static JSC_DECLARE_HOST_FUNCTION(moduleLoaderParseModule); static JSC_DECLARE_HOST_FUNCTION(moduleLoaderRequestedModules); static JSC_DECLARE_HOST_FUNCTION(moduleLoaderEvaluate); static JSC_DECLARE_HOST_FUNCTION(moduleLoaderModuleDeclarationInstantiation); static JSC_DECLARE_HOST_FUNCTION(moduleLoaderResolve); static JSC_DECLARE_HOST_FUNCTION(moduleLoaderResolveSync); static JSC_DECLARE_HOST_FUNCTION(moduleLoaderFetch); static JSC_DECLARE_HOST_FUNCTION(moduleLoaderGetModuleNamespaceObject); } #include "JSModuleLoader.lut.h" namespace JSC { STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSModuleLoader); const ClassInfo JSModuleLoader::s_info = { "ModuleLoader", &Base::s_info, &moduleLoaderTable, nullptr, CREATE_METHOD_TABLE(JSModuleLoader) }; /* Source for JSModuleLoader.lut.h @begin moduleLoaderTable ensureRegistered JSBuiltin DontEnum|Function 1 forceFulfillPromise JSBuiltin DontEnum|Function 2 fulfillFetch JSBuiltin DontEnum|Function 2 requestFetch JSBuiltin DontEnum|Function 3 requestInstantiate JSBuiltin DontEnum|Function 3 requestSatisfy JSBuiltin DontEnum|Function 3 link JSBuiltin DontEnum|Function 2 moduleDeclarationInstantiation moduleLoaderModuleDeclarationInstantiation DontEnum|Function 2 moduleEvaluation JSBuiltin DontEnum|Function 2 evaluate moduleLoaderEvaluate DontEnum|Function 3 provideFetch JSBuiltin DontEnum|Function 2 loadAndEvaluateModule JSBuiltin DontEnum|Function 3 loadModule JSBuiltin DontEnum|Function 3 linkAndEvaluateModule JSBuiltin DontEnum|Function 2 requestImportModule JSBuiltin DontEnum|Function 3 dependencyKeysIfEvaluated JSBuiltin DontEnum|Function 1 getModuleNamespaceObject moduleLoaderGetModuleNamespaceObject DontEnum|Function 1 parseModule moduleLoaderParseModule DontEnum|Function 2 requestedModules moduleLoaderRequestedModules DontEnum|Function 1 resolve moduleLoaderResolve DontEnum|Function 2 resolveSync moduleLoaderResolveSync DontEnum|Function 2 fetch moduleLoaderFetch DontEnum|Function 3 @end */ JSModuleLoader::JSModuleLoader(VM& vm, Structure* structure) : JSNonFinalObject(vm, structure) { } void JSModuleLoader::finishCreation(JSGlobalObject* globalObject, VM& vm) { auto scope = DECLARE_CATCH_SCOPE(vm); Base::finishCreation(vm); ASSERT(inherits(vm, info())); JSMap* map = JSMap::create(globalObject, vm, globalObject->mapStructure()); scope.releaseAssertNoException(); putDirect(vm, Identifier::fromString(vm, "registry"), map); } // ------------------------------ Functions -------------------------------- static String printableModuleKey(JSGlobalObject* globalObject, JSValue key) { VM& vm = globalObject->vm(); auto scope = DECLARE_CATCH_SCOPE(vm); if (key.isString() || key.isSymbol()) { auto propertyName = key.toPropertyKey(globalObject); scope.assertNoException(); // This is OK since this function is just for debugging purpose. return propertyName.impl(); } return vm.propertyNames->emptyIdentifier.impl(); } static JSInternalPromise* reject(JSGlobalObject* globalObject, CatchScope& catchScope, JSInternalPromise* promise) { VM& vm = globalObject->vm(); Exception* exception = catchScope.exception(); ASSERT(exception); if (UNLIKELY(isTerminatedExecutionException(vm, exception))) return promise; catchScope.clearException(); promise->reject(globalObject, exception->value()); return promise; } JSArray* JSModuleLoader::dependencyKeysIfEvaluated(JSGlobalObject* globalObject, JSValue key) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSObject* function = jsCast<JSObject*>(get(globalObject, vm.propertyNames->builtinNames().dependencyKeysIfEvaluatedPublicName())); RETURN_IF_EXCEPTION(scope, nullptr); auto callData = JSC::getCallData(vm, function); ASSERT(callData.type != CallData::Type::None); MarkedArgumentBuffer arguments; arguments.append(key); JSValue result = call(globalObject, function, callData, this, arguments); RETURN_IF_EXCEPTION(scope, nullptr); return jsDynamicCast<JSArray*>(vm, result); } JSValue JSModuleLoader::provideFetch(JSGlobalObject* globalObject, JSValue key, const SourceCode& sourceCode) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSObject* function = jsCast<JSObject*>(get(globalObject, vm.propertyNames->builtinNames().provideFetchPublicName())); RETURN_IF_EXCEPTION(scope, { }); auto callData = JSC::getCallData(vm, function); ASSERT(callData.type != CallData::Type::None); SourceCode source { sourceCode }; MarkedArgumentBuffer arguments; arguments.append(key); arguments.append(JSSourceCode::create(vm, WTFMove(source))); ASSERT(!arguments.hasOverflowed()); RELEASE_AND_RETURN(scope, call(globalObject, function, callData, this, arguments)); } JSInternalPromise* JSModuleLoader::loadAndEvaluateModule(JSGlobalObject* globalObject, JSValue moduleName, JSValue parameters, JSValue scriptFetcher) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSObject* function = jsCast<JSObject*>(get(globalObject, vm.propertyNames->builtinNames().loadAndEvaluateModulePublicName())); RETURN_IF_EXCEPTION(scope, nullptr); auto callData = JSC::getCallData(vm, function); ASSERT(callData.type != CallData::Type::None); MarkedArgumentBuffer arguments; arguments.append(moduleName); arguments.append(parameters); arguments.append(scriptFetcher); ASSERT(!arguments.hasOverflowed()); JSValue promise = call(globalObject, function, callData, this, arguments); RETURN_IF_EXCEPTION(scope, nullptr); return jsCast<JSInternalPromise*>(promise); } JSInternalPromise* JSModuleLoader::loadModule(JSGlobalObject* globalObject, JSValue moduleName, JSValue parameters, JSValue scriptFetcher) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSObject* function = jsCast<JSObject*>(get(globalObject, vm.propertyNames->builtinNames().loadModulePublicName())); RETURN_IF_EXCEPTION(scope, nullptr); auto callData = JSC::getCallData(vm, function); ASSERT(callData.type != CallData::Type::None); MarkedArgumentBuffer arguments; arguments.append(moduleName); arguments.append(parameters); arguments.append(scriptFetcher); ASSERT(!arguments.hasOverflowed()); JSValue promise = call(globalObject, function, callData, this, arguments); RETURN_IF_EXCEPTION(scope, nullptr); return jsCast<JSInternalPromise*>(promise); } JSValue JSModuleLoader::linkAndEvaluateModule(JSGlobalObject* globalObject, JSValue moduleKey, JSValue scriptFetcher) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSObject* function = jsCast<JSObject*>(get(globalObject, vm.propertyNames->builtinNames().linkAndEvaluateModulePublicName())); RETURN_IF_EXCEPTION(scope, { }); auto callData = JSC::getCallData(vm, function); ASSERT(callData.type != CallData::Type::None); MarkedArgumentBuffer arguments; arguments.append(moduleKey); arguments.append(scriptFetcher); ASSERT(!arguments.hasOverflowed()); RELEASE_AND_RETURN(scope, call(globalObject, function, callData, this, arguments)); } JSInternalPromise* JSModuleLoader::requestImportModule(JSGlobalObject* globalObject, const Identifier& moduleKey, JSValue parameters, JSValue scriptFetcher) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* function = jsCast<JSObject*>(get(globalObject, vm.propertyNames->builtinNames().requestImportModulePublicName())); RETURN_IF_EXCEPTION(scope, nullptr); auto callData = JSC::getCallData(vm, function); ASSERT(callData.type != CallData::Type::None); MarkedArgumentBuffer arguments; arguments.append(jsString(vm, moduleKey.impl())); arguments.append(parameters); arguments.append(scriptFetcher); ASSERT(!arguments.hasOverflowed()); JSValue promise = call(globalObject, function, callData, this, arguments); RETURN_IF_EXCEPTION(scope, nullptr); return jsCast<JSInternalPromise*>(promise); } JSInternalPromise* JSModuleLoader::importModule(JSGlobalObject* globalObject, JSString* moduleName, JSValue parameters, const SourceOrigin& referrer) { dataLogLnIf(Options::dumpModuleLoadingState(), "Loader [import] ", printableModuleKey(globalObject, moduleName)); VM& vm = globalObject->vm(); auto catchScope = DECLARE_CATCH_SCOPE(vm); if (globalObject->globalObjectMethodTable()->moduleLoaderImportModule) return globalObject->globalObjectMethodTable()->moduleLoaderImportModule(globalObject, this, moduleName, parameters, referrer); auto* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); String moduleNameString = moduleName->value(globalObject); if (UNLIKELY(catchScope.exception())) return reject(globalObject, catchScope, promise); promise->reject(globalObject, createError(globalObject, makeString("Could not import the module '", moduleNameString, "'."))); return promise; } Identifier JSModuleLoader::resolveSync(JSGlobalObject* globalObject, JSValue name, JSValue referrer, JSValue scriptFetcher) { dataLogLnIf(Options::dumpModuleLoadingState(), "Loader [resolve] ", printableModuleKey(globalObject, name)); if (globalObject->globalObjectMethodTable()->moduleLoaderResolve) return globalObject->globalObjectMethodTable()->moduleLoaderResolve(globalObject, this, name, referrer, scriptFetcher); return name.toPropertyKey(globalObject); } JSInternalPromise* JSModuleLoader::resolve(JSGlobalObject* globalObject, JSValue name, JSValue referrer, JSValue scriptFetcher) { VM& vm = globalObject->vm(); auto catchScope = DECLARE_CATCH_SCOPE(vm); auto* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); const Identifier moduleKey = resolveSync(globalObject, name, referrer, scriptFetcher); if (UNLIKELY(catchScope.exception())) return reject(globalObject, catchScope, promise); promise->resolve(globalObject, identifierToJSValue(vm, moduleKey)); return promise; } JSInternalPromise* JSModuleLoader::fetch(JSGlobalObject* globalObject, JSValue key, JSValue parameters, JSValue scriptFetcher) { dataLogLnIf(Options::dumpModuleLoadingState(), "Loader [fetch] ", printableModuleKey(globalObject, key)); VM& vm = globalObject->vm(); auto catchScope = DECLARE_CATCH_SCOPE(vm); if (globalObject->globalObjectMethodTable()->moduleLoaderFetch) return globalObject->globalObjectMethodTable()->moduleLoaderFetch(globalObject, this, key, parameters, scriptFetcher); auto* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); String moduleKey = key.toWTFString(globalObject); if (UNLIKELY(catchScope.exception())) return reject(globalObject, catchScope, promise); promise->reject(globalObject, createError(globalObject, makeString("Could not open the module '", moduleKey, "'."))); return promise; } JSObject* JSModuleLoader::createImportMetaProperties(JSGlobalObject* globalObject, JSValue key, JSModuleRecord* moduleRecord, JSValue scriptFetcher) { if (globalObject->globalObjectMethodTable()->moduleLoaderCreateImportMetaProperties) return globalObject->globalObjectMethodTable()->moduleLoaderCreateImportMetaProperties(globalObject, this, key, moduleRecord, scriptFetcher); return constructEmptyObject(globalObject->vm(), globalObject->nullPrototypeObjectStructure()); } JSValue JSModuleLoader::evaluate(JSGlobalObject* globalObject, JSValue key, JSValue moduleRecordValue, JSValue scriptFetcher) { dataLogLnIf(Options::dumpModuleLoadingState(), "Loader [evaluate] ", printableModuleKey(globalObject, key)); if (globalObject->globalObjectMethodTable()->moduleLoaderEvaluate) return globalObject->globalObjectMethodTable()->moduleLoaderEvaluate(globalObject, this, key, moduleRecordValue, scriptFetcher); return evaluateNonVirtual(globalObject, key, moduleRecordValue, scriptFetcher); } JSValue JSModuleLoader::evaluateNonVirtual(JSGlobalObject* globalObject, JSValue, JSValue moduleRecordValue, JSValue) { if (auto* moduleRecord = jsDynamicCast<AbstractModuleRecord*>(globalObject->vm(), moduleRecordValue)) return moduleRecord->evaluate(globalObject); return jsUndefined(); } JSModuleNamespaceObject* JSModuleLoader::getModuleNamespaceObject(JSGlobalObject* globalObject, JSValue moduleRecordValue) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* moduleRecord = jsDynamicCast<AbstractModuleRecord*>(vm, moduleRecordValue); if (!moduleRecord) { throwTypeError(globalObject, scope); return nullptr; } RELEASE_AND_RETURN(scope, moduleRecord->getModuleNamespace(globalObject)); } // ------------------------------ Functions -------------------------------- JSC_DEFINE_HOST_FUNCTION(moduleLoaderParseModule, (JSGlobalObject* globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); auto catchScope = DECLARE_CATCH_SCOPE(vm); auto rejectWithError = [&](JSValue error) { promise->reject(globalObject, error); return promise; }; const Identifier moduleKey = callFrame->argument(0).toPropertyKey(globalObject); if (UNLIKELY(catchScope.exception())) return JSValue::encode(reject(globalObject, catchScope, promise)); JSValue source = callFrame->argument(1); auto* jsSourceCode = jsCast<JSSourceCode*>(source); SourceCode sourceCode = jsSourceCode->sourceCode(); #if ENABLE(WEBASSEMBLY) if (sourceCode.provider()->sourceType() == SourceProviderSourceType::WebAssembly) return JSValue::encode(JSWebAssembly::instantiate(globalObject, promise, moduleKey, jsSourceCode)); #endif ParserError error; std::unique_ptr<ModuleProgramNode> moduleProgramNode = parse<ModuleProgramNode>( vm, sourceCode, Identifier(), JSParserBuiltinMode::NotBuiltin, JSParserStrictMode::Strict, JSParserScriptMode::Module, SourceParseMode::ModuleAnalyzeMode, SuperBinding::NotNeeded, error); if (error.isValid()) return JSValue::encode(rejectWithError(error.toErrorObject(globalObject, sourceCode))); ASSERT(moduleProgramNode); ModuleAnalyzer moduleAnalyzer(globalObject, moduleKey, sourceCode, moduleProgramNode->varDeclarations(), moduleProgramNode->lexicalVariables()); if (UNLIKELY(catchScope.exception())) return JSValue::encode(reject(globalObject, catchScope, promise)); promise->resolve(globalObject, moduleAnalyzer.analyze(*moduleProgramNode)); catchScope.clearException(); return JSValue::encode(promise); } JSC_DEFINE_HOST_FUNCTION(moduleLoaderRequestedModules, (JSGlobalObject* globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* moduleRecord = jsDynamicCast<AbstractModuleRecord*>(vm, callFrame->argument(0)); if (!moduleRecord) RELEASE_AND_RETURN(scope, JSValue::encode(constructEmptyArray(globalObject, nullptr))); JSArray* result = constructEmptyArray(globalObject, nullptr, moduleRecord->requestedModules().size()); RETURN_IF_EXCEPTION(scope, encodedJSValue()); size_t i = 0; for (auto& key : moduleRecord->requestedModules()) { result->putDirectIndex(globalObject, i++, jsString(vm, key.get())); RETURN_IF_EXCEPTION(scope, encodedJSValue()); } return JSValue::encode(result); } JSC_DEFINE_HOST_FUNCTION(moduleLoaderModuleDeclarationInstantiation, (JSGlobalObject* globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* moduleRecord = jsDynamicCast<AbstractModuleRecord*>(vm, callFrame->argument(0)); if (!moduleRecord) return JSValue::encode(jsUndefined()); dataLogLnIf(Options::dumpModuleLoadingState(), "Loader [link] ", moduleRecord->moduleKey()); moduleRecord->link(globalObject, callFrame->argument(1)); RETURN_IF_EXCEPTION(scope, encodedJSValue()); return JSValue::encode(jsUndefined()); } // ------------------------------ Hook Functions --------------------------- JSC_DEFINE_HOST_FUNCTION(moduleLoaderResolve, (JSGlobalObject* globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); // Hook point, Loader.resolve. // https://whatwg.github.io/loader/#browser-resolve // Take the name and resolve it to the unique identifier for the resource location. // For example, take the "jquery" and return the URL for the resource. JSModuleLoader* loader = jsDynamicCast<JSModuleLoader*>(vm, callFrame->thisValue()); if (!loader) return JSValue::encode(jsUndefined()); return JSValue::encode(loader->resolve(globalObject, callFrame->argument(0), callFrame->argument(1), callFrame->argument(2))); } JSC_DEFINE_HOST_FUNCTION(moduleLoaderResolveSync, (JSGlobalObject* globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSModuleLoader* loader = jsDynamicCast<JSModuleLoader*>(vm, callFrame->thisValue()); if (!loader) return JSValue::encode(jsUndefined()); auto result = loader->resolveSync(globalObject, callFrame->argument(0), callFrame->argument(1), callFrame->argument(2)); RETURN_IF_EXCEPTION(scope, encodedJSValue()); return JSValue::encode(identifierToJSValue(vm, result)); } JSC_DEFINE_HOST_FUNCTION(moduleLoaderFetch, (JSGlobalObject* globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); // Hook point, Loader.fetch // https://whatwg.github.io/loader/#browser-fetch // Take the key and fetch the resource actually. // For example, JavaScriptCore shell can provide the hook fetching the resource // from the local file system. JSModuleLoader* loader = jsDynamicCast<JSModuleLoader*>(vm, callFrame->thisValue()); if (!loader) return JSValue::encode(jsUndefined()); return JSValue::encode(loader->fetch(globalObject, callFrame->argument(0), callFrame->argument(1), callFrame->argument(2))); } JSC_DEFINE_HOST_FUNCTION(moduleLoaderGetModuleNamespaceObject, (JSGlobalObject* globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* loader = jsDynamicCast<JSModuleLoader*>(vm, callFrame->thisValue()); if (!loader) return JSValue::encode(jsUndefined()); auto* moduleNamespaceObject = loader->getModuleNamespaceObject(globalObject, callFrame->argument(0)); RETURN_IF_EXCEPTION(scope, encodedJSValue()); return JSValue::encode(moduleNamespaceObject); } // ------------------- Additional Hook Functions --------------------------- JSC_DEFINE_HOST_FUNCTION(moduleLoaderEvaluate, (JSGlobalObject* globalObject, CallFrame* callFrame)) { // To instrument and retrieve the errors raised from the module execution, // we inserted the hook point here. VM& vm = globalObject->vm(); JSModuleLoader* loader = jsDynamicCast<JSModuleLoader*>(vm, callFrame->thisValue()); if (!loader) return JSValue::encode(jsUndefined()); return JSValue::encode(loader->evaluate(globalObject, callFrame->argument(0), callFrame->argument(1), callFrame->argument(2))); } } // namespace JSC