/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "builtin/ModuleObject.h" #include "mozilla/DebugOnly.h" #include "mozilla/EnumSet.h" #include "mozilla/ScopeExit.h" #include "builtin/Promise.h" #include "builtin/SelfHostingDefines.h" #include "frontend/ParseNode.h" #include "frontend/ParserAtom.h" // TaggedParserAtomIndex, ParserAtomsTable, ParserAtom #include "frontend/SharedContext.h" #include "frontend/Stencil.h" #include "gc/GCContext.h" #include "gc/Tracer.h" #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin, JS::LimitedColumnNumberOneOrigin #include "js/friend/ErrorMessages.h" // JSMSG_* #include "js/Modules.h" // JS::GetModulePrivate, JS::ModuleDynamicImportHook, JS::ModuleType #include "vm/EqualityOperations.h" // js::SameValue #include "vm/Interpreter.h" // Execute, Lambda, ReportRuntimeLexicalError #include "vm/ModuleBuilder.h" // js::ModuleBuilder #include "vm/Modules.h" #include "vm/PlainObject.h" // js::PlainObject #include "vm/PromiseObject.h" // js::PromiseObject #include "vm/SharedStencil.h" // js::GCThingIndex #include "builtin/HandlerFunction-inl.h" // js::ExtraValueFromHandler, js::NewHandler{,WithExtraValue}, js::TargetFromHandler #include "gc/GCContext-inl.h" #include "vm/EnvironmentObject-inl.h" // EnvironmentObject::setAliasedBinding #include "vm/JSObject-inl.h" #include "vm/JSScript-inl.h" #include "vm/List-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using mozilla::Maybe; using mozilla::Nothing; using mozilla::Some; using mozilla::Span; static_assert(ModuleStatus::Unlinked < ModuleStatus::Linking && ModuleStatus::Linking < ModuleStatus::Linked && ModuleStatus::Linked < ModuleStatus::Evaluating && ModuleStatus::Evaluating < ModuleStatus::EvaluatingAsync && ModuleStatus::EvaluatingAsync < ModuleStatus::Evaluated && ModuleStatus::Evaluated < ModuleStatus::Evaluated_Error, "Module statuses are ordered incorrectly"); static Value StringOrNullValue(JSString* maybeString) { return maybeString ? StringValue(maybeString) : NullValue(); } #define DEFINE_ATOM_ACCESSOR_METHOD(cls, name, slot) \ JSAtom* cls::name() const { \ Value value = getReservedSlot(slot); \ return &value.toString()->asAtom(); \ } #define DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(cls, name, slot) \ JSAtom* cls::name() const { \ Value value = getReservedSlot(slot); \ if (value.isNull()) { \ return nullptr; \ } \ return &value.toString()->asAtom(); \ } #define DEFINE_UINT32_ACCESSOR_METHOD(cls, name, slot) \ uint32_t cls::name() const { \ Value value = getReservedSlot(slot); \ MOZ_ASSERT(value.toNumber() >= 0); \ if (value.isInt32()) { \ return value.toInt32(); \ } \ return JS::ToUint32(value.toDouble()); \ } /////////////////////////////////////////////////////////////////////////// // ImportEntry ImportEntry::ImportEntry(Handle moduleRequest, Handle maybeImportName, Handle localName, uint32_t lineNumber, JS::ColumnNumberOneOrigin columnNumber) : moduleRequest_(moduleRequest), importName_(maybeImportName), localName_(localName), lineNumber_(lineNumber), columnNumber_(columnNumber) {} void ImportEntry::trace(JSTracer* trc) { TraceEdge(trc, &moduleRequest_, "ImportEntry::moduleRequest_"); TraceNullableEdge(trc, &importName_, "ImportEntry::importName_"); TraceNullableEdge(trc, &localName_, "ImportEntry::localName_"); } /////////////////////////////////////////////////////////////////////////// // ExportEntry ExportEntry::ExportEntry(Handle maybeExportName, Handle moduleRequest, Handle maybeImportName, Handle maybeLocalName, uint32_t lineNumber, JS::ColumnNumberOneOrigin columnNumber) : exportName_(maybeExportName), moduleRequest_(moduleRequest), importName_(maybeImportName), localName_(maybeLocalName), lineNumber_(lineNumber), columnNumber_(columnNumber) { // Line and column numbers are optional for export entries since direct // entries are checked at parse time. } void ExportEntry::trace(JSTracer* trc) { TraceNullableEdge(trc, &exportName_, "ExportEntry::exportName_"); TraceNullableEdge(trc, &moduleRequest_, "ExportEntry::moduleRequest_"); TraceNullableEdge(trc, &importName_, "ExportEntry::importName_"); TraceNullableEdge(trc, &localName_, "ExportEntry::localName_"); } /////////////////////////////////////////////////////////////////////////// // RequestedModule /* static */ RequestedModule::RequestedModule(Handle moduleRequest, uint32_t lineNumber, JS::ColumnNumberOneOrigin columnNumber) : moduleRequest_(moduleRequest), lineNumber_(lineNumber), columnNumber_(columnNumber) {} void RequestedModule::trace(JSTracer* trc) { TraceEdge(trc, &moduleRequest_, "ExportEntry::moduleRequest_"); } /////////////////////////////////////////////////////////////////////////// // ResolvedBindingObject /* static */ const JSClass ResolvedBindingObject::class_ = { "ResolvedBinding", JSCLASS_HAS_RESERVED_SLOTS(ResolvedBindingObject::SlotCount)}; ModuleObject* ResolvedBindingObject::module() const { Value value = getReservedSlot(ModuleSlot); return &value.toObject().as(); } JSAtom* ResolvedBindingObject::bindingName() const { Value value = getReservedSlot(BindingNameSlot); return &value.toString()->asAtom(); } /* static */ bool ResolvedBindingObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is(); } /* static */ ResolvedBindingObject* ResolvedBindingObject::create( JSContext* cx, Handle module, Handle bindingName) { ResolvedBindingObject* self = NewObjectWithGivenProto(cx, nullptr); if (!self) { return nullptr; } self->initReservedSlot(ModuleSlot, ObjectValue(*module)); self->initReservedSlot(BindingNameSlot, StringValue(bindingName)); return self; } /////////////////////////////////////////////////////////////////////////// // ModuleRequestObject /* static */ const JSClass ModuleRequestObject::class_ = { "ModuleRequest", JSCLASS_HAS_RESERVED_SLOTS(ModuleRequestObject::SlotCount)}; DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ModuleRequestObject, specifier, SpecifierSlot) ArrayObject* ModuleRequestObject::attributes() const { JSObject* obj = getReservedSlot(AttributesSlot).toObjectOrNull(); if (!obj) { return nullptr; } return &obj->as(); } bool ModuleRequestObject::hasAttributes() const { return !getReservedSlot(ModuleRequestObject::AttributesSlot) .isNullOrUndefined(); } /* static */ bool ModuleRequestObject::getModuleType( JSContext* cx, const Handle moduleRequest, JS::ModuleType& moduleType) { if (!moduleRequest->hasAttributes()) { moduleType = JS::ModuleType::JavaScript; return true; } Rooted attributesArray(cx, moduleRequest->attributes()); RootedObject attributeObject(cx); RootedId typeId(cx, NameToId(cx->names().type)); RootedValue value(cx); uint32_t numberOfAttributes = attributesArray->length(); for (uint32_t i = 0; i < numberOfAttributes; i++) { attributeObject = &attributesArray->getDenseElement(i).toObject(); if (!GetProperty(cx, attributeObject, attributeObject, typeId, &value)) { continue; } int32_t isJsonString; if (!js::CompareStrings(cx, cx->names().json, value.toString(), &isJsonString)) { return false; } if (isJsonString == 0) { moduleType = JS::ModuleType::JSON; return true; } moduleType = JS::ModuleType::Unknown; return true; } moduleType = JS::ModuleType::JavaScript; return true; } /* static */ bool ModuleRequestObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is(); } /* static */ ModuleRequestObject* ModuleRequestObject::create( JSContext* cx, Handle specifier, Handle maybeAttributes) { ModuleRequestObject* self = NewObjectWithGivenProto(cx, nullptr); if (!self) { return nullptr; } self->initReservedSlot(SpecifierSlot, StringOrNullValue(specifier)); self->initReservedSlot(AttributesSlot, ObjectOrNullValue(maybeAttributes)); return self; } /////////////////////////////////////////////////////////////////////////// // IndirectBindingMap IndirectBindingMap::Binding::Binding(ModuleEnvironmentObject* environment, jsid targetName, PropertyInfo prop) : environment(environment), #ifdef DEBUG targetName(targetName), #endif prop(prop) { } void IndirectBindingMap::trace(JSTracer* trc) { if (!map_) { return; } for (Map::Enum e(*map_); !e.empty(); e.popFront()) { Binding& b = e.front().value(); TraceEdge(trc, &b.environment, "module bindings environment"); #ifdef DEBUG TraceEdge(trc, &b.targetName, "module bindings target name"); #endif mozilla::DebugOnly prev(e.front().key()); TraceEdge(trc, &e.front().mutableKey(), "module bindings binding name"); MOZ_ASSERT(e.front().key() == prev); } } bool IndirectBindingMap::put(JSContext* cx, HandleId name, Handle environment, HandleId targetName) { if (!map_) { map_.emplace(cx->zone()); } mozilla::Maybe prop = environment->lookup(cx, targetName); MOZ_ASSERT(prop.isSome()); if (!map_->put(name, Binding(environment, targetName, *prop))) { ReportOutOfMemory(cx); return false; } return true; } bool IndirectBindingMap::lookup(jsid name, ModuleEnvironmentObject** envOut, mozilla::Maybe* propOut) const { if (!map_) { return false; } auto ptr = map_->lookup(name); if (!ptr) { return false; } const Binding& binding = ptr->value(); MOZ_ASSERT(binding.environment); MOZ_ASSERT( binding.environment->containsPure(binding.targetName, binding.prop)); *envOut = binding.environment; *propOut = Some(binding.prop); return true; } /////////////////////////////////////////////////////////////////////////// // ModuleNamespaceObject /* static */ const ModuleNamespaceObject::ProxyHandler ModuleNamespaceObject::proxyHandler; /* static */ bool ModuleNamespaceObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is(); } /* static */ ModuleNamespaceObject* ModuleNamespaceObject::create( JSContext* cx, Handle module, MutableHandle> exports, MutableHandle> bindings) { RootedValue priv(cx, ObjectValue(*module)); ProxyOptions options; options.setLazyProto(true); RootedObject object( cx, NewProxyObject(cx, &proxyHandler, priv, nullptr, options)); if (!object) { return nullptr; } SetProxyReservedSlot(object, ExportsSlot, PrivateValue(exports.get().release())); AddCellMemory(object, sizeof(ExportNameVector), MemoryUse::ModuleExports); SetProxyReservedSlot(object, BindingsSlot, PrivateValue(bindings.get().release())); AddCellMemory(object, sizeof(IndirectBindingMap), MemoryUse::ModuleBindingMap); return &object->as(); } ModuleObject& ModuleNamespaceObject::module() { return GetProxyPrivate(this).toObject().as(); } const ExportNameVector& ModuleNamespaceObject::exports() const { Value value = GetProxyReservedSlot(this, ExportsSlot); auto* exports = static_cast(value.toPrivate()); MOZ_ASSERT(exports); return *exports; } ExportNameVector& ModuleNamespaceObject::mutableExports() { // Get a non-const reference for tracing/destruction. Do not actually mutate // this vector! This would be incorrect without adding barriers. return const_cast(exports()); } IndirectBindingMap& ModuleNamespaceObject::bindings() { Value value = GetProxyReservedSlot(this, BindingsSlot); auto* bindings = static_cast(value.toPrivate()); MOZ_ASSERT(bindings); return *bindings; } bool ModuleNamespaceObject::hasExports() const { // Exports may not be present if we hit OOM in initialization. return !GetProxyReservedSlot(this, ExportsSlot).isUndefined(); } bool ModuleNamespaceObject::hasBindings() const { // Import bindings may not be present if we hit OOM in initialization. return !GetProxyReservedSlot(this, BindingsSlot).isUndefined(); } bool ModuleNamespaceObject::addBinding(JSContext* cx, Handle exportedName, Handle targetModule, Handle targetName) { Rooted environment( cx, &targetModule->initialEnvironment()); RootedId exportedNameId(cx, AtomToId(exportedName)); RootedId targetNameId(cx, AtomToId(targetName)); return bindings().put(cx, exportedNameId, environment, targetNameId); } const char ModuleNamespaceObject::ProxyHandler::family = 0; ModuleNamespaceObject::ProxyHandler::ProxyHandler() : BaseProxyHandler(&family, false) {} bool ModuleNamespaceObject::ProxyHandler::getPrototype( JSContext* cx, HandleObject proxy, MutableHandleObject protop) const { protop.set(nullptr); return true; } bool ModuleNamespaceObject::ProxyHandler::setPrototype( JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result) const { if (!proto) { return result.succeed(); } return result.failCantSetProto(); } bool ModuleNamespaceObject::ProxyHandler::getPrototypeIfOrdinary( JSContext* cx, HandleObject proxy, bool* isOrdinary, MutableHandleObject protop) const { *isOrdinary = false; return true; } bool ModuleNamespaceObject::ProxyHandler::setImmutablePrototype( JSContext* cx, HandleObject proxy, bool* succeeded) const { *succeeded = true; return true; } bool ModuleNamespaceObject::ProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const { *extensible = false; return true; } bool ModuleNamespaceObject::ProxyHandler::preventExtensions( JSContext* cx, HandleObject proxy, ObjectOpResult& result) const { result.succeed(); return true; } bool ModuleNamespaceObject::ProxyHandler::getOwnPropertyDescriptor( JSContext* cx, HandleObject proxy, HandleId id, MutableHandle> desc) const { Rooted ns(cx, &proxy->as()); if (id.isSymbol()) { if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) { desc.set(Some(PropertyDescriptor::Data(StringValue(cx->names().Module)))); return true; } desc.reset(); return true; } const IndirectBindingMap& bindings = ns->bindings(); ModuleEnvironmentObject* env; mozilla::Maybe prop; if (!bindings.lookup(id, &env, &prop)) { // Not found. desc.reset(); return true; } RootedValue value(cx, env->getSlot(prop->slot())); if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) { ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id); return false; } desc.set( Some(PropertyDescriptor::Data(value, {JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable}))); return true; } static bool ValidatePropertyDescriptor( JSContext* cx, Handle desc, bool expectedWritable, bool expectedEnumerable, bool expectedConfigurable, HandleValue expectedValue, ObjectOpResult& result) { if (desc.isAccessorDescriptor()) { return result.fail(JSMSG_CANT_REDEFINE_PROP); } if (desc.hasWritable() && desc.writable() != expectedWritable) { return result.fail(JSMSG_CANT_REDEFINE_PROP); } if (desc.hasEnumerable() && desc.enumerable() != expectedEnumerable) { return result.fail(JSMSG_CANT_REDEFINE_PROP); } if (desc.hasConfigurable() && desc.configurable() != expectedConfigurable) { return result.fail(JSMSG_CANT_REDEFINE_PROP); } if (desc.hasValue()) { bool same; if (!SameValue(cx, desc.value(), expectedValue, &same)) { return false; } if (!same) { return result.fail(JSMSG_CANT_REDEFINE_PROP); } } return result.succeed(); } bool ModuleNamespaceObject::ProxyHandler::defineProperty( JSContext* cx, HandleObject proxy, HandleId id, Handle desc, ObjectOpResult& result) const { if (id.isSymbol()) { if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) { RootedValue value(cx, StringValue(cx->names().Module)); return ValidatePropertyDescriptor(cx, desc, false, false, false, value, result); } return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE); } const IndirectBindingMap& bindings = proxy->as().bindings(); ModuleEnvironmentObject* env; mozilla::Maybe prop; if (!bindings.lookup(id, &env, &prop)) { return result.fail(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE); } RootedValue value(cx, env->getSlot(prop->slot())); if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) { ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id); return false; } return ValidatePropertyDescriptor(cx, desc, true, true, false, value, result); } bool ModuleNamespaceObject::ProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const { Rooted ns(cx, &proxy->as()); if (id.isSymbol()) { *bp = id.isWellKnownSymbol(JS::SymbolCode::toStringTag); return true; } *bp = ns->bindings().has(id); return true; } bool ModuleNamespaceObject::ProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, MutableHandleValue vp) const { Rooted ns(cx, &proxy->as()); if (id.isSymbol()) { if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) { vp.setString(cx->names().Module); return true; } vp.setUndefined(); return true; } ModuleEnvironmentObject* env; mozilla::Maybe prop; if (!ns->bindings().lookup(id, &env, &prop)) { vp.setUndefined(); return true; } RootedValue value(cx, env->getSlot(prop->slot())); if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) { ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id); return false; } vp.set(value); return true; } bool ModuleNamespaceObject::ProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const { return result.failReadOnly(); } bool ModuleNamespaceObject::ProxyHandler::delete_( JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) const { Rooted ns(cx, &proxy->as()); if (id.isSymbol()) { if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) { return result.failCantDelete(); } return result.succeed(); } if (ns->bindings().has(id)) { return result.failCantDelete(); } return result.succeed(); } bool ModuleNamespaceObject::ProxyHandler::ownPropertyKeys( JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const { Rooted ns(cx, &proxy->as()); uint32_t count = ns->exports().length(); if (!props.reserve(props.length() + count + 1)) { return false; } for (JSAtom* atom : ns->exports()) { props.infallibleAppend(AtomToId(atom)); } props.infallibleAppend( PropertyKey::Symbol(cx->wellKnownSymbols().toStringTag)); return true; } void ModuleNamespaceObject::ProxyHandler::trace(JSTracer* trc, JSObject* proxy) const { auto& self = proxy->as(); if (self.hasExports()) { self.mutableExports().trace(trc); } if (self.hasBindings()) { self.bindings().trace(trc); } } void ModuleNamespaceObject::ProxyHandler::finalize(JS::GCContext* gcx, JSObject* proxy) const { auto& self = proxy->as(); if (self.hasExports()) { gcx->delete_(proxy, &self.mutableExports(), MemoryUse::ModuleExports); } if (self.hasBindings()) { gcx->delete_(proxy, &self.bindings(), MemoryUse::ModuleBindingMap); } } /////////////////////////////////////////////////////////////////////////// // SyntheticModuleFields // The fields of a synthetic module record, as described in: // https://tc39.es/proposal-json-modules/#sec-synthetic-module-records class js::SyntheticModuleFields { public: ExportNameVector exportNames; public: void trace(JSTracer* trc); }; void SyntheticModuleFields::trace(JSTracer* trc) { exportNames.trace(trc); } /////////////////////////////////////////////////////////////////////////// // CyclicModuleFields // The fields of a cyclic module record, as described in: // https://tc39.es/ecma262/#sec-cyclic-module-records class js::CyclicModuleFields { public: ModuleStatus status = ModuleStatus::Unlinked; bool hasTopLevelAwait : 1; private: // Flag bits that determine whether other fields are present. bool hasDfsIndex : 1; bool hasDfsAncestorIndex : 1; bool isAsyncEvaluating : 1; bool hasPendingAsyncDependencies : 1; // Fields whose presence is conditional on the flag bits above. uint32_t dfsIndex = 0; uint32_t dfsAncestorIndex = 0; uint32_t asyncEvaluatingPostOrder = 0; uint32_t pendingAsyncDependencies = 0; // Fields describing the layout of exportEntries. uint32_t indirectExportEntriesStart = 0; uint32_t starExportEntriesStart = 0; public: HeapPtr evaluationError; HeapPtr metaObject; HeapPtr scriptSourceObject; RequestedModuleVector requestedModules; ImportEntryVector importEntries; ExportEntryVector exportEntries; IndirectBindingMap importBindings; UniquePtr functionDeclarations; HeapPtr topLevelCapability; HeapPtr asyncParentModules; HeapPtr cycleRoot; public: CyclicModuleFields(); void trace(JSTracer* trc); void initExportEntries(MutableHandle allEntries, uint32_t localExportCount, uint32_t indirectExportCount, uint32_t starExportCount); Span localExportEntries() const; Span indirectExportEntries() const; Span starExportEntries() const; void setDfsIndex(uint32_t index); Maybe maybeDfsIndex() const; void setDfsAncestorIndex(uint32_t index); Maybe maybeDfsAncestorIndex() const; void clearDfsIndexes(); void setAsyncEvaluating(uint32_t postOrder); bool getIsAsyncEvaluating() const; Maybe maybeAsyncEvaluatingPostOrder() const; void clearAsyncEvaluatingPostOrder(); void setPendingAsyncDependencies(uint32_t newValue); Maybe maybePendingAsyncDependencies() const; }; CyclicModuleFields::CyclicModuleFields() : hasTopLevelAwait(false), hasDfsIndex(false), hasDfsAncestorIndex(false), isAsyncEvaluating(false), hasPendingAsyncDependencies(false) {} void CyclicModuleFields::trace(JSTracer* trc) { TraceEdge(trc, &evaluationError, "CyclicModuleFields::evaluationError"); TraceNullableEdge(trc, &metaObject, "CyclicModuleFields::metaObject"); TraceNullableEdge(trc, &scriptSourceObject, "CyclicModuleFields::scriptSourceObject"); requestedModules.trace(trc); importEntries.trace(trc); exportEntries.trace(trc); importBindings.trace(trc); TraceNullableEdge(trc, &topLevelCapability, "CyclicModuleFields::topLevelCapability"); TraceNullableEdge(trc, &asyncParentModules, "CyclicModuleFields::asyncParentModules"); TraceNullableEdge(trc, &cycleRoot, "CyclicModuleFields::cycleRoot"); } void CyclicModuleFields::initExportEntries( MutableHandle allEntries, uint32_t localExportCount, uint32_t indirectExportCount, uint32_t starExportCount) { MOZ_ASSERT(allEntries.length() == localExportCount + indirectExportCount + starExportCount); exportEntries = std::move(allEntries.get()); indirectExportEntriesStart = localExportCount; starExportEntriesStart = indirectExportEntriesStart + indirectExportCount; } Span CyclicModuleFields::localExportEntries() const { MOZ_ASSERT(indirectExportEntriesStart <= exportEntries.length()); return Span(exportEntries.begin(), exportEntries.begin() + indirectExportEntriesStart); } Span CyclicModuleFields::indirectExportEntries() const { MOZ_ASSERT(indirectExportEntriesStart <= starExportEntriesStart); MOZ_ASSERT(starExportEntriesStart <= exportEntries.length()); return Span(exportEntries.begin() + indirectExportEntriesStart, exportEntries.begin() + starExportEntriesStart); } Span CyclicModuleFields::starExportEntries() const { MOZ_ASSERT(starExportEntriesStart <= exportEntries.length()); return Span(exportEntries.begin() + starExportEntriesStart, exportEntries.end()); } void CyclicModuleFields::setDfsIndex(uint32_t index) { dfsIndex = index; hasDfsIndex = true; } Maybe CyclicModuleFields::maybeDfsIndex() const { return hasDfsIndex ? Some(dfsIndex) : Nothing(); } void CyclicModuleFields::setDfsAncestorIndex(uint32_t index) { dfsAncestorIndex = index; hasDfsAncestorIndex = true; } Maybe CyclicModuleFields::maybeDfsAncestorIndex() const { return hasDfsAncestorIndex ? Some(dfsAncestorIndex) : Nothing(); } void CyclicModuleFields::clearDfsIndexes() { dfsIndex = 0; hasDfsIndex = false; dfsAncestorIndex = 0; hasDfsAncestorIndex = false; } void CyclicModuleFields::setAsyncEvaluating(uint32_t postOrder) { isAsyncEvaluating = true; asyncEvaluatingPostOrder = postOrder; } bool CyclicModuleFields::getIsAsyncEvaluating() const { return isAsyncEvaluating; } Maybe CyclicModuleFields::maybeAsyncEvaluatingPostOrder() const { if (!isAsyncEvaluating || asyncEvaluatingPostOrder == ASYNC_EVALUATING_POST_ORDER_CLEARED) { return Nothing(); } return Some(asyncEvaluatingPostOrder); } void CyclicModuleFields::clearAsyncEvaluatingPostOrder() { asyncEvaluatingPostOrder = ASYNC_EVALUATING_POST_ORDER_CLEARED; } void CyclicModuleFields::setPendingAsyncDependencies(uint32_t newValue) { pendingAsyncDependencies = newValue; hasPendingAsyncDependencies = true; } Maybe CyclicModuleFields::maybePendingAsyncDependencies() const { return hasPendingAsyncDependencies ? Some(pendingAsyncDependencies) : Nothing(); } /////////////////////////////////////////////////////////////////////////// // ModuleObject /* static */ const JSClassOps ModuleObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve ModuleObject::finalize, // finalize nullptr, // call nullptr, // construct ModuleObject::trace, // trace }; /* static */ const JSClass ModuleObject::class_ = { "Module", JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) | JSCLASS_BACKGROUND_FINALIZE, &ModuleObject::classOps_}; /* static */ bool ModuleObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is(); } bool ModuleObject::hasCyclicModuleFields() const { // This currently only returns false if we GC during initialization. return !getReservedSlot(CyclicModuleFieldsSlot).isUndefined(); } CyclicModuleFields* ModuleObject::cyclicModuleFields() { void* ptr = getReservedSlot(CyclicModuleFieldsSlot).toPrivate(); MOZ_ASSERT(ptr); return static_cast(ptr); } const CyclicModuleFields* ModuleObject::cyclicModuleFields() const { return const_cast(this)->cyclicModuleFields(); } Span ModuleObject::requestedModules() const { return cyclicModuleFields()->requestedModules; } Span ModuleObject::importEntries() const { return cyclicModuleFields()->importEntries; } Span ModuleObject::localExportEntries() const { return cyclicModuleFields()->localExportEntries(); } Span ModuleObject::indirectExportEntries() const { return cyclicModuleFields()->indirectExportEntries(); } Span ModuleObject::starExportEntries() const { return cyclicModuleFields()->starExportEntries(); } const ExportNameVector& ModuleObject::syntheticExportNames() const { return syntheticModuleFields()->exportNames; } void ModuleObject::initFunctionDeclarations( UniquePtr decls) { cyclicModuleFields()->functionDeclarations = std::move(decls); } /* static */ ModuleObject* ModuleObject::create(JSContext* cx) { Rooted> fields(cx); fields = cx->make_unique(); if (!fields) { return nullptr; } Rooted self( cx, NewObjectWithGivenProto(cx, nullptr)); if (!self) { return nullptr; } InitReservedSlot(self, CyclicModuleFieldsSlot, fields.release(), MemoryUse::ModuleCyclicFields); return self; } /* static */ ModuleObject* ModuleObject::createSynthetic( JSContext* cx, MutableHandle exportNames) { Rooted> syntheticFields(cx); syntheticFields = cx->make_unique(); if (!syntheticFields) { return nullptr; } Rooted self( cx, NewObjectWithGivenProto(cx, nullptr)); if (!self) { return nullptr; } InitReservedSlot(self, SyntheticModuleFieldsSlot, syntheticFields.release(), MemoryUse::ModuleSyntheticFields); self->syntheticModuleFields()->exportNames = std::move(exportNames.get()); return self; } /* static */ void ModuleObject::finalize(JS::GCContext* gcx, JSObject* obj) { ModuleObject* self = &obj->as(); if (self->hasCyclicModuleFields()) { gcx->delete_(obj, self->cyclicModuleFields(), MemoryUse::ModuleCyclicFields); } if (self->hasSyntheticModuleFields()) { gcx->delete_(obj, self->syntheticModuleFields(), MemoryUse::ModuleSyntheticFields); } } ModuleEnvironmentObject& ModuleObject::initialEnvironment() const { Value value = getReservedSlot(EnvironmentSlot); return value.toObject().as(); } ModuleEnvironmentObject* ModuleObject::environment() const { // Note that this it's valid to call this even if there was an error // evaluating the module. // According to the spec the environment record is created during linking, but // we create it earlier than that. if (status() < ModuleStatus::Linked) { return nullptr; } return &initialEnvironment(); } IndirectBindingMap& ModuleObject::importBindings() { return cyclicModuleFields()->importBindings; } ModuleNamespaceObject* ModuleObject::namespace_() { Value value = getReservedSlot(NamespaceSlot); if (value.isUndefined()) { return nullptr; } return &value.toObject().as(); } ScriptSourceObject* ModuleObject::scriptSourceObject() const { return cyclicModuleFields()->scriptSourceObject; } void ModuleObject::initAsyncSlots(JSContext* cx, bool hasTopLevelAwait, Handle asyncParentModules) { cyclicModuleFields()->hasTopLevelAwait = hasTopLevelAwait; cyclicModuleFields()->asyncParentModules = asyncParentModules; } static uint32_t NextPostOrder(JSRuntime* rt) { uint32_t ordinal = rt->moduleAsyncEvaluatingPostOrder; MOZ_ASSERT(ordinal != ASYNC_EVALUATING_POST_ORDER_CLEARED); MOZ_ASSERT(ordinal < MAX_UINT32); rt->moduleAsyncEvaluatingPostOrder++; return ordinal; } // Reset the runtime's moduleAsyncEvaluatingPostOrder counter when the last // module that was async evaluating is finished. // // The graph is not re-entrant and any future modules will be independent from // this one. static void MaybeResetPostOrderCounter(JSRuntime* rt, uint32_t finishedPostOrder) { if (rt->moduleAsyncEvaluatingPostOrder == finishedPostOrder + 1) { rt->moduleAsyncEvaluatingPostOrder = ASYNC_EVALUATING_POST_ORDER_INIT; } } void ModuleObject::setAsyncEvaluating() { MOZ_ASSERT(!isAsyncEvaluating()); uint32_t postOrder = NextPostOrder(runtimeFromMainThread()); cyclicModuleFields()->setAsyncEvaluating(postOrder); } void ModuleObject::initScriptSlots(HandleScript script) { MOZ_ASSERT(script); MOZ_ASSERT(script->sourceObject()); MOZ_ASSERT(script->filename()); initReservedSlot(ScriptSlot, PrivateGCThingValue(script)); cyclicModuleFields()->scriptSourceObject = script->sourceObject(); } void ModuleObject::setInitialEnvironment( Handle initialEnvironment) { initReservedSlot(EnvironmentSlot, ObjectValue(*initialEnvironment)); } void ModuleObject::initImportExportData( MutableHandle requestedModules, MutableHandle importEntries, MutableHandle exportEntries, uint32_t localExportCount, uint32_t indirectExportCount, uint32_t starExportCount) { cyclicModuleFields()->requestedModules = std::move(requestedModules.get()); cyclicModuleFields()->importEntries = std::move(importEntries.get()); cyclicModuleFields()->initExportEntries(exportEntries, localExportCount, indirectExportCount, starExportCount); } /* static */ bool ModuleObject::Freeze(JSContext* cx, Handle self) { return FreezeObject(cx, self); } #ifdef DEBUG /* static */ inline bool ModuleObject::AssertFrozen( JSContext* cx, Handle self) { bool frozen = false; if (!TestIntegrityLevel(cx, self, IntegrityLevel::Frozen, &frozen)) { return false; } MOZ_ASSERT(frozen); return true; } #endif JSScript* ModuleObject::maybeScript() const { Value value = getReservedSlot(ScriptSlot); if (value.isUndefined()) { return nullptr; } BaseScript* script = value.toGCThing()->as(); MOZ_ASSERT(script->hasBytecode(), "Module scripts should always have bytecode"); return script->asJSScript(); } JSScript* ModuleObject::script() const { JSScript* ptr = maybeScript(); MOZ_RELEASE_ASSERT(ptr); return ptr; } const char* ModuleObject::filename() const { // The ScriptSlot will be cleared once the module is evaluated, so we try to // get the filename from cyclicModuleFields(). // TODO: Bug 1885483: Provide filename for JSON modules if (!hasCyclicModuleFields()) { return "(JSON module)"; } return cyclicModuleFields()->scriptSourceObject->source()->filename(); } static inline void AssertValidModuleStatus(ModuleStatus status) { MOZ_ASSERT(status >= ModuleStatus::Unlinked && status <= ModuleStatus::Evaluated_Error); } ModuleStatus ModuleObject::status() const { // Always return `ModuleStatus::Evaluated` so we can assert a module's status // without checking which kind it is, even though synthetic modules don't have // this field according to the spec. if (hasSyntheticModuleFields()) { return ModuleStatus::Evaluated; } ModuleStatus status = cyclicModuleFields()->status; AssertValidModuleStatus(status); if (status == ModuleStatus::Evaluated_Error) { return ModuleStatus::Evaluated; } return status; } void ModuleObject::setStatus(ModuleStatus newStatus) { AssertValidModuleStatus(newStatus); // Note that under OOM conditions we can fail the module linking process even // after modules have been marked as linked. MOZ_ASSERT((status() <= ModuleStatus::Linked && newStatus == ModuleStatus::Unlinked) || newStatus > status(), "New module status inconsistent with current status"); cyclicModuleFields()->status = newStatus; } bool ModuleObject::hasTopLevelAwait() const { return cyclicModuleFields()->hasTopLevelAwait; } bool ModuleObject::isAsyncEvaluating() const { return cyclicModuleFields()->getIsAsyncEvaluating(); } Maybe ModuleObject::maybeDfsIndex() const { return cyclicModuleFields()->maybeDfsIndex(); } uint32_t ModuleObject::dfsIndex() const { return maybeDfsIndex().value(); } void ModuleObject::setDfsIndex(uint32_t index) { cyclicModuleFields()->setDfsIndex(index); } Maybe ModuleObject::maybeDfsAncestorIndex() const { return cyclicModuleFields()->maybeDfsAncestorIndex(); } uint32_t ModuleObject::dfsAncestorIndex() const { return maybeDfsAncestorIndex().value(); } void ModuleObject::setDfsAncestorIndex(uint32_t index) { cyclicModuleFields()->setDfsAncestorIndex(index); } void ModuleObject::clearDfsIndexes() { cyclicModuleFields()->clearDfsIndexes(); } PromiseObject* ModuleObject::maybeTopLevelCapability() const { return cyclicModuleFields()->topLevelCapability; } PromiseObject* ModuleObject::topLevelCapability() const { PromiseObject* capability = maybeTopLevelCapability(); MOZ_RELEASE_ASSERT(capability); return capability; } // static PromiseObject* ModuleObject::createTopLevelCapability( JSContext* cx, Handle module) { MOZ_ASSERT(!module->maybeTopLevelCapability()); Rooted resultPromise(cx, CreatePromiseObjectForAsync(cx)); if (!resultPromise) { return nullptr; } module->setInitialTopLevelCapability(resultPromise); return resultPromise; } void ModuleObject::setInitialTopLevelCapability( Handle capability) { cyclicModuleFields()->topLevelCapability = capability; } ListObject* ModuleObject::asyncParentModules() const { return cyclicModuleFields()->asyncParentModules; } bool ModuleObject::appendAsyncParentModule(JSContext* cx, Handle self, Handle parent) { Rooted parentValue(cx, ObjectValue(*parent)); return self->asyncParentModules()->append(cx, parentValue); } Maybe ModuleObject::maybePendingAsyncDependencies() const { return cyclicModuleFields()->maybePendingAsyncDependencies(); } uint32_t ModuleObject::pendingAsyncDependencies() const { return maybePendingAsyncDependencies().value(); } Maybe ModuleObject::maybeAsyncEvaluatingPostOrder() const { return cyclicModuleFields()->maybeAsyncEvaluatingPostOrder(); } uint32_t ModuleObject::getAsyncEvaluatingPostOrder() const { return cyclicModuleFields()->maybeAsyncEvaluatingPostOrder().value(); } void ModuleObject::clearAsyncEvaluatingPostOrder() { MOZ_ASSERT(status() == ModuleStatus::Evaluated); JSRuntime* rt = runtimeFromMainThread(); MaybeResetPostOrderCounter(rt, getAsyncEvaluatingPostOrder()); cyclicModuleFields()->clearAsyncEvaluatingPostOrder(); } void ModuleObject::setPendingAsyncDependencies(uint32_t newValue) { cyclicModuleFields()->setPendingAsyncDependencies(newValue); } void ModuleObject::setCycleRoot(ModuleObject* cycleRoot) { cyclicModuleFields()->cycleRoot = cycleRoot; } ModuleObject* ModuleObject::getCycleRoot() const { MOZ_RELEASE_ASSERT(cyclicModuleFields()->cycleRoot); return cyclicModuleFields()->cycleRoot; } bool ModuleObject::hasSyntheticModuleFields() const { bool result = !getReservedSlot(SyntheticModuleFieldsSlot).isUndefined(); MOZ_ASSERT_IF(result, !hasCyclicModuleFields()); return result; } SyntheticModuleFields* ModuleObject::syntheticModuleFields() { MOZ_ASSERT(!hasCyclicModuleFields()); void* ptr = getReservedSlot(SyntheticModuleFieldsSlot).toPrivate(); MOZ_ASSERT(ptr); return static_cast(ptr); } const SyntheticModuleFields* ModuleObject::syntheticModuleFields() const { return const_cast(this)->syntheticModuleFields(); } bool ModuleObject::hasTopLevelCapability() const { return cyclicModuleFields()->topLevelCapability; } bool ModuleObject::hadEvaluationError() const { ModuleStatus fullStatus = cyclicModuleFields()->status; return fullStatus == ModuleStatus::Evaluated_Error; } void ModuleObject::setEvaluationError(HandleValue newValue) { MOZ_ASSERT(status() != ModuleStatus::Unlinked); MOZ_ASSERT(!hadEvaluationError()); cyclicModuleFields()->status = ModuleStatus::Evaluated_Error; cyclicModuleFields()->evaluationError = newValue; MOZ_ASSERT(status() == ModuleStatus::Evaluated); MOZ_ASSERT(hadEvaluationError()); } Value ModuleObject::maybeEvaluationError() const { return cyclicModuleFields()->evaluationError; } Value ModuleObject::evaluationError() const { MOZ_ASSERT(hadEvaluationError()); return maybeEvaluationError(); } JSObject* ModuleObject::metaObject() const { return cyclicModuleFields()->metaObject; } void ModuleObject::setMetaObject(JSObject* obj) { MOZ_ASSERT(obj); MOZ_ASSERT(!metaObject()); cyclicModuleFields()->metaObject = obj; } /* static */ void ModuleObject::trace(JSTracer* trc, JSObject* obj) { ModuleObject& module = obj->as(); if (module.hasCyclicModuleFields()) { module.cyclicModuleFields()->trace(trc); } if (module.hasSyntheticModuleFields()) { module.syntheticModuleFields()->trace(trc); } } /* static */ bool ModuleObject::instantiateFunctionDeclarations(JSContext* cx, Handle self) { #ifdef DEBUG MOZ_ASSERT(self->status() == ModuleStatus::Linking); if (!AssertFrozen(cx, self)) { return false; } #endif // |self| initially manages this vector. UniquePtr& funDecls = self->cyclicModuleFields()->functionDeclarations; if (!funDecls) { JS_ReportErrorASCII( cx, "Module function declarations have already been instantiated"); return false; } Rooted env(cx, &self->initialEnvironment()); RootedObject obj(cx); RootedValue value(cx); RootedFunction fun(cx); Rooted name(cx); for (GCThingIndex funIndex : *funDecls) { fun.set(self->script()->getFunction(funIndex)); obj = Lambda(cx, fun, env); if (!obj) { return false; } name = fun->fullExplicitName()->asPropertyName(); value = ObjectValue(*obj); if (!SetProperty(cx, env, name, value)) { return false; } } // Free the vector, now its contents are no longer needed. funDecls.reset(); return true; } /* static */ bool ModuleObject::execute(JSContext* cx, Handle self) { #ifdef DEBUG MOZ_ASSERT(self->status() == ModuleStatus::Evaluating || self->status() == ModuleStatus::EvaluatingAsync || self->status() == ModuleStatus::Evaluated); MOZ_ASSERT(!self->hadEvaluationError()); if (!AssertFrozen(cx, self)) { return false; } #endif RootedScript script(cx, self->script()); auto guardA = mozilla::MakeScopeExit([&] { if (self->hasTopLevelAwait()) { // Handled in AsyncModuleExecutionFulfilled and // AsyncModuleExecutionRejected. return; } ModuleObject::onTopLevelEvaluationFinished(self); }); Rooted env(cx, self->environment()); if (!env) { JS_ReportErrorASCII(cx, "Module declarations have not yet been instantiated"); return false; } Rooted ignored(cx); return Execute(cx, script, env, &ignored); } /* static */ void ModuleObject::onTopLevelEvaluationFinished(ModuleObject* module) { // ScriptSlot is used by debugger to access environments during evaluating // the top-level script. // Clear the reference at exit to prevent us keeping this alive unnecessarily. module->setReservedSlot(ScriptSlot, UndefinedValue()); } /* static */ ModuleNamespaceObject* ModuleObject::createNamespace( JSContext* cx, Handle self, MutableHandle> exports) { MOZ_ASSERT(!self->namespace_()); Rooted> bindings(cx); bindings = cx->make_unique(); if (!bindings) { return nullptr; } auto* ns = ModuleNamespaceObject::create(cx, self, exports, &bindings); if (!ns) { return nullptr; } self->initReservedSlot(NamespaceSlot, ObjectValue(*ns)); return ns; } /* static */ bool ModuleObject::createEnvironment(JSContext* cx, Handle self) { Rooted env( cx, ModuleEnvironmentObject::create(cx, self)); if (!env) { return false; } self->setInitialEnvironment(env); return true; } /*static*/ bool ModuleObject::createSyntheticEnvironment(JSContext* cx, Handle self, Handle> values) { Rooted env( cx, ModuleEnvironmentObject::createSynthetic(cx, self)); if (!env) { return false; } MOZ_ASSERT(env->shape()->propMapLength() == values.length()); for (uint32_t i = 0; i < values.length(); i++) { env->setAliasedBinding(env->firstSyntheticValueSlot() + i, values[i]); } self->setInitialEnvironment(env); return true; } /////////////////////////////////////////////////////////////////////////// // ModuleBuilder ModuleBuilder::ModuleBuilder(FrontendContext* fc, const frontend::EitherParser& eitherParser) : fc_(fc), eitherParser_(eitherParser), requestedModuleSpecifiers_(fc), importEntries_(fc), exportEntries_(fc), exportNames_(fc) {} bool ModuleBuilder::noteFunctionDeclaration(FrontendContext* fc, uint32_t funIndex) { if (!functionDecls_.emplaceBack(funIndex)) { js::ReportOutOfMemory(fc); return false; } return true; } void ModuleBuilder::noteAsync(frontend::StencilModuleMetadata& metadata) { metadata.isAsync = true; } bool ModuleBuilder::buildTables(frontend::StencilModuleMetadata& metadata) { // https://tc39.es/ecma262/#sec-parsemodule // 15.2.1.17.1 ParseModule, Steps 4-11. // Step 4. metadata.moduleRequests = std::move(moduleRequests_); metadata.requestedModules = std::move(requestedModules_); // Step 5. if (!metadata.importEntries.reserve(importEntries_.count())) { js::ReportOutOfMemory(fc_); return false; } for (auto r = importEntries_.all(); !r.empty(); r.popFront()) { frontend::StencilModuleEntry& entry = r.front().value(); metadata.importEntries.infallibleAppend(entry); } // Steps 6-11. for (const frontend::StencilModuleEntry& exp : exportEntries_) { if (!exp.moduleRequest) { frontend::StencilModuleEntry* importEntry = importEntryFor(exp.localName); if (!importEntry) { if (!metadata.localExportEntries.append(exp)) { js::ReportOutOfMemory(fc_); return false; } } else { if (!importEntry->importName) { if (!metadata.localExportEntries.append(exp)) { js::ReportOutOfMemory(fc_); return false; } } else { // All names should have already been marked as used-by-stencil. auto entry = frontend::StencilModuleEntry::exportFromEntry( importEntry->moduleRequest, importEntry->importName, exp.exportName, exp.lineno, exp.column); if (!metadata.indirectExportEntries.append(entry)) { js::ReportOutOfMemory(fc_); return false; } } } } else if (!exp.importName && !exp.exportName) { if (!metadata.starExportEntries.append(exp)) { js::ReportOutOfMemory(fc_); return false; } } else { if (!metadata.indirectExportEntries.append(exp)) { js::ReportOutOfMemory(fc_); return false; } } } return true; } void ModuleBuilder::finishFunctionDecls( frontend::StencilModuleMetadata& metadata) { metadata.functionDecls = std::move(functionDecls_); } bool frontend::StencilModuleMetadata::createModuleRequestObjects( JSContext* cx, CompilationAtomCache& atomCache, MutableHandle output) const { if (!output.reserve(moduleRequests.length())) { ReportOutOfMemory(cx); return false; } Rooted object(cx); for (const StencilModuleRequest& request : moduleRequests) { object = createModuleRequestObject(cx, atomCache, request); if (!object) { return false; } output.infallibleEmplaceBack(object); } return true; } ModuleRequestObject* frontend::StencilModuleMetadata::createModuleRequestObject( JSContext* cx, CompilationAtomCache& atomCache, const StencilModuleRequest& request) const { Rooted assertionArray(cx); uint32_t numberOfAssertions = request.assertions.length(); if (numberOfAssertions > 0) { assertionArray = NewDenseFullyAllocatedArray(cx, numberOfAssertions); if (!assertionArray) { return nullptr; } assertionArray->ensureDenseInitializedLength(0, numberOfAssertions); Rooted assertionObject(cx); RootedId assertionKey(cx); RootedValue assertionValue(cx); for (uint32_t j = 0; j < numberOfAssertions; ++j) { assertionObject = NewPlainObject(cx); if (!assertionObject) { return nullptr; } JSAtom* jsatom = atomCache.getExistingAtomAt(cx, request.assertions[j].key); MOZ_ASSERT(jsatom); assertionKey = AtomToId(jsatom); jsatom = atomCache.getExistingAtomAt(cx, request.assertions[j].value); MOZ_ASSERT(jsatom); assertionValue = StringValue(jsatom); if (!DefineDataProperty(cx, assertionObject, assertionKey, assertionValue, JSPROP_ENUMERATE)) { return nullptr; } assertionArray->initDenseElement(j, ObjectValue(*assertionObject)); } } Rooted specifier(cx, atomCache.getExistingAtomAt(cx, request.specifier)); MOZ_ASSERT(specifier); return ModuleRequestObject::create(cx, specifier, assertionArray); } bool frontend::StencilModuleMetadata::createImportEntries( JSContext* cx, CompilationAtomCache& atomCache, Handle moduleRequests, MutableHandle output) const { if (!output.reserve(importEntries.length())) { ReportOutOfMemory(cx); return false; } for (const StencilModuleEntry& entry : importEntries) { Rooted moduleRequest(cx); moduleRequest = moduleRequests[entry.moduleRequest.value()].get(); MOZ_ASSERT(moduleRequest); Rooted localName(cx); if (entry.localName) { localName = atomCache.getExistingAtomAt(cx, entry.localName); MOZ_ASSERT(localName); } Rooted importName(cx); if (entry.importName) { importName = atomCache.getExistingAtomAt(cx, entry.importName); MOZ_ASSERT(importName); } MOZ_ASSERT(!entry.exportName); output.infallibleEmplaceBack(moduleRequest, importName, localName, entry.lineno, entry.column); } return true; } bool frontend::StencilModuleMetadata::createExportEntries( JSContext* cx, frontend::CompilationAtomCache& atomCache, Handle moduleRequests, const frontend::StencilModuleMetadata::EntryVector& input, MutableHandle output) const { if (!output.reserve(output.length() + input.length())) { ReportOutOfMemory(cx); return false; } for (const frontend::StencilModuleEntry& entry : input) { Rooted exportName(cx); if (entry.exportName) { exportName = atomCache.getExistingAtomAt(cx, entry.exportName); MOZ_ASSERT(exportName); } Rooted moduleRequestObject(cx); if (entry.moduleRequest) { moduleRequestObject = moduleRequests[entry.moduleRequest.value()].get(); MOZ_ASSERT(moduleRequestObject); } Rooted localName(cx); if (entry.localName) { localName = atomCache.getExistingAtomAt(cx, entry.localName); MOZ_ASSERT(localName); } Rooted importName(cx); if (entry.importName) { importName = atomCache.getExistingAtomAt(cx, entry.importName); MOZ_ASSERT(importName); } output.infallibleEmplaceBack(exportName, moduleRequestObject, importName, localName, entry.lineno, entry.column); } return true; } bool frontend::StencilModuleMetadata::createRequestedModules( JSContext* cx, CompilationAtomCache& atomCache, Handle moduleRequests, MutableHandle output) const { if (!output.reserve(requestedModules.length())) { ReportOutOfMemory(cx); return false; } for (const frontend::StencilModuleEntry& entry : requestedModules) { Rooted moduleRequest(cx); moduleRequest = moduleRequests[entry.moduleRequest.value()].get(); MOZ_ASSERT(moduleRequest); MOZ_ASSERT(!entry.localName); MOZ_ASSERT(!entry.importName); MOZ_ASSERT(!entry.exportName); output.infallibleEmplaceBack(moduleRequest, entry.lineno, entry.column); } return true; } // Use StencilModuleMetadata data to fill in ModuleObject bool frontend::StencilModuleMetadata::initModule( JSContext* cx, FrontendContext* fc, frontend::CompilationAtomCache& atomCache, JS::Handle module) const { Rooted moduleRequestsVector(cx); if (!createModuleRequestObjects(cx, atomCache, &moduleRequestsVector)) { return false; } Rooted requestedModulesVector(cx); if (!createRequestedModules(cx, atomCache, moduleRequestsVector, &requestedModulesVector)) { return false; } Rooted importEntriesVector(cx); if (!createImportEntries(cx, atomCache, moduleRequestsVector, &importEntriesVector)) { return false; } Rooted exportEntriesVector(cx); if (!createExportEntries(cx, atomCache, moduleRequestsVector, localExportEntries, &exportEntriesVector)) { return false; } Rooted indirectExportEntriesVector(cx); if (!createExportEntries(cx, atomCache, moduleRequestsVector, indirectExportEntries, &exportEntriesVector)) { return false; } Rooted starExportEntriesVector(cx); if (!createExportEntries(cx, atomCache, moduleRequestsVector, starExportEntries, &exportEntriesVector)) { return false; } // Copy the vector of declarations to the ModuleObject. auto functionDeclsCopy = MakeUnique(); if (!functionDeclsCopy || !functionDeclsCopy->appendAll(functionDecls)) { js::ReportOutOfMemory(fc); return false; } module->initFunctionDeclarations(std::move(functionDeclsCopy)); Rooted asyncParentModulesList(cx, ListObject::create(cx)); if (!asyncParentModulesList) { return false; } module->initAsyncSlots(cx, isAsync, asyncParentModulesList); module->initImportExportData( &requestedModulesVector, &importEntriesVector, &exportEntriesVector, localExportEntries.length(), indirectExportEntries.length(), starExportEntries.length()); return true; } bool ModuleBuilder::isAssertionSupported(frontend::TaggedParserAtomIndex key) { if (!key.isWellKnownAtomId()) { return false; } return key.toWellKnownAtomId() == WellKnownAtomId::type; } bool ModuleBuilder::processAssertions(frontend::StencilModuleRequest& request, frontend::ListNode* assertionList) { using namespace js::frontend; for (ParseNode* assertionItem : assertionList->contents()) { BinaryNode* assertion = &assertionItem->as(); MOZ_ASSERT(assertion->isKind(ParseNodeKind::ImportAttribute)); auto key = assertion->left()->as().atom(); auto value = assertion->right()->as().atom(); if (isAssertionSupported(key)) { markUsedByStencil(key); markUsedByStencil(value); StencilModuleAssertion assertionStencil(key, value); if (!request.assertions.append(assertionStencil)) { js::ReportOutOfMemory(fc_); return false; } } } return true; } bool ModuleBuilder::processImport(frontend::BinaryNode* importNode) { using namespace js::frontend; MOZ_ASSERT(importNode->isKind(ParseNodeKind::ImportDecl)); auto* specList = &importNode->left()->as(); MOZ_ASSERT(specList->isKind(ParseNodeKind::ImportSpecList)); auto* moduleRequest = &importNode->right()->as(); MOZ_ASSERT(moduleRequest->isKind(ParseNodeKind::ImportModuleRequest)); auto* moduleSpec = &moduleRequest->left()->as(); MOZ_ASSERT(moduleSpec->isKind(ParseNodeKind::StringExpr)); auto* assertionList = &moduleRequest->right()->as(); MOZ_ASSERT(assertionList->isKind(ParseNodeKind::ImportAttributeList)); auto specifier = moduleSpec->atom(); MaybeModuleRequestIndex moduleRequestIndex = appendModuleRequest(specifier, assertionList); if (!moduleRequestIndex.isSome()) { return false; } if (!maybeAppendRequestedModule(moduleRequestIndex, moduleSpec)) { return false; } for (ParseNode* item : specList->contents()) { uint32_t line; JS::LimitedColumnNumberOneOrigin column; eitherParser_.computeLineAndColumn(item->pn_pos.begin, &line, &column); StencilModuleEntry entry; TaggedParserAtomIndex localName; if (item->isKind(ParseNodeKind::ImportSpec)) { auto* spec = &item->as(); auto* importNameNode = &spec->left()->as(); auto* localNameNode = &spec->right()->as(); auto importName = importNameNode->atom(); localName = localNameNode->atom(); markUsedByStencil(localName); markUsedByStencil(importName); entry = StencilModuleEntry::importEntry( moduleRequestIndex, localName, importName, line, JS::ColumnNumberOneOrigin(column)); } else { MOZ_ASSERT(item->isKind(ParseNodeKind::ImportNamespaceSpec)); auto* spec = &item->as(); auto* localNameNode = &spec->kid()->as(); localName = localNameNode->atom(); markUsedByStencil(localName); entry = StencilModuleEntry::importNamespaceEntry( moduleRequestIndex, localName, line, JS::ColumnNumberOneOrigin(column)); } if (!importEntries_.put(localName, entry)) { return false; } } return true; } bool ModuleBuilder::processExport(frontend::ParseNode* exportNode) { using namespace js::frontend; MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportStmt) || exportNode->isKind(ParseNodeKind::ExportDefaultStmt)); bool isDefault = exportNode->isKind(ParseNodeKind::ExportDefaultStmt); ParseNode* kid = isDefault ? exportNode->as().left() : exportNode->as().kid(); if (isDefault && exportNode->as().right()) { // This is an export default containing an expression. auto localName = TaggedParserAtomIndex::WellKnown::default_(); auto exportName = TaggedParserAtomIndex::WellKnown::default_(); return appendExportEntry(exportName, localName); } switch (kid->getKind()) { case ParseNodeKind::ExportSpecList: { MOZ_ASSERT(!isDefault); for (ParseNode* item : kid->as().contents()) { BinaryNode* spec = &item->as(); MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportSpec)); NameNode* localNameNode = &spec->left()->as(); NameNode* exportNameNode = &spec->right()->as(); auto localName = localNameNode->atom(); auto exportName = exportNameNode->atom(); if (!appendExportEntry(exportName, localName, spec)) { return false; } } break; } case ParseNodeKind::ClassDecl: { const ClassNode& cls = kid->as(); MOZ_ASSERT(cls.names()); auto localName = cls.names()->innerBinding()->atom(); auto exportName = isDefault ? TaggedParserAtomIndex::WellKnown::default_() : localName; if (!appendExportEntry(exportName, localName)) { return false; } break; } case ParseNodeKind::VarStmt: case ParseNodeKind::ConstDecl: case ParseNodeKind::LetDecl: { for (ParseNode* binding : kid->as().contents()) { if (binding->isKind(ParseNodeKind::AssignExpr)) { binding = binding->as().left(); } else { MOZ_ASSERT(binding->isKind(ParseNodeKind::Name)); } if (binding->isKind(ParseNodeKind::Name)) { auto localName = binding->as().atom(); auto exportName = isDefault ? TaggedParserAtomIndex::WellKnown::default_() : localName; if (!appendExportEntry(exportName, localName)) { return false; } } else if (binding->isKind(ParseNodeKind::ArrayExpr)) { if (!processExportArrayBinding(&binding->as())) { return false; } } else { MOZ_ASSERT(binding->isKind(ParseNodeKind::ObjectExpr)); if (!processExportObjectBinding(&binding->as())) { return false; } } } break; } case ParseNodeKind::Function: { FunctionBox* box = kid->as().funbox(); MOZ_ASSERT(!box->isArrow()); auto localName = box->explicitName(); auto exportName = isDefault ? TaggedParserAtomIndex::WellKnown::default_() : localName; if (!appendExportEntry(exportName, localName)) { return false; } break; } default: MOZ_CRASH("Unexpected parse node"); } return true; } bool ModuleBuilder::processExportBinding(frontend::ParseNode* binding) { using namespace js::frontend; if (binding->isKind(ParseNodeKind::Name)) { auto name = binding->as().atom(); return appendExportEntry(name, name); } if (binding->isKind(ParseNodeKind::ArrayExpr)) { return processExportArrayBinding(&binding->as()); } MOZ_ASSERT(binding->isKind(ParseNodeKind::ObjectExpr)); return processExportObjectBinding(&binding->as()); } bool ModuleBuilder::processExportArrayBinding(frontend::ListNode* array) { using namespace js::frontend; MOZ_ASSERT(array->isKind(ParseNodeKind::ArrayExpr)); for (ParseNode* node : array->contents()) { if (node->isKind(ParseNodeKind::Elision)) { continue; } if (node->isKind(ParseNodeKind::Spread)) { node = node->as().kid(); } else if (node->isKind(ParseNodeKind::AssignExpr)) { node = node->as().left(); } if (!processExportBinding(node)) { return false; } } return true; } bool ModuleBuilder::processExportObjectBinding(frontend::ListNode* obj) { using namespace js::frontend; MOZ_ASSERT(obj->isKind(ParseNodeKind::ObjectExpr)); for (ParseNode* node : obj->contents()) { MOZ_ASSERT(node->isKind(ParseNodeKind::MutateProto) || node->isKind(ParseNodeKind::PropertyDefinition) || node->isKind(ParseNodeKind::Shorthand) || node->isKind(ParseNodeKind::Spread)); ParseNode* target; if (node->isKind(ParseNodeKind::Spread)) { target = node->as().kid(); } else { if (node->isKind(ParseNodeKind::MutateProto)) { target = node->as().kid(); } else { target = node->as().right(); } if (target->isKind(ParseNodeKind::AssignExpr)) { target = target->as().left(); } } if (!processExportBinding(target)) { return false; } } return true; } bool ModuleBuilder::processExportFrom(frontend::BinaryNode* exportNode) { using namespace js::frontend; MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportFromStmt)); auto* specList = &exportNode->left()->as(); MOZ_ASSERT(specList->isKind(ParseNodeKind::ExportSpecList)); auto* moduleRequest = &exportNode->right()->as(); MOZ_ASSERT(moduleRequest->isKind(ParseNodeKind::ImportModuleRequest)); auto* moduleSpec = &moduleRequest->left()->as(); MOZ_ASSERT(moduleSpec->isKind(ParseNodeKind::StringExpr)); auto* assertionList = &moduleRequest->right()->as(); MOZ_ASSERT(assertionList->isKind(ParseNodeKind::ImportAttributeList)); auto specifier = moduleSpec->atom(); MaybeModuleRequestIndex moduleRequestIndex = appendModuleRequest(specifier, assertionList); if (!moduleRequestIndex.isSome()) { return false; } if (!maybeAppendRequestedModule(moduleRequestIndex, moduleSpec)) { return false; } for (ParseNode* spec : specList->contents()) { uint32_t line; JS::LimitedColumnNumberOneOrigin column; eitherParser_.computeLineAndColumn(spec->pn_pos.begin, &line, &column); StencilModuleEntry entry; TaggedParserAtomIndex exportName; if (spec->isKind(ParseNodeKind::ExportSpec)) { auto* importNameNode = &spec->as().left()->as(); auto* exportNameNode = &spec->as().right()->as(); auto importName = importNameNode->atom(); exportName = exportNameNode->atom(); markUsedByStencil(importName); markUsedByStencil(exportName); entry = StencilModuleEntry::exportFromEntry( moduleRequestIndex, importName, exportName, line, JS::ColumnNumberOneOrigin(column)); } else if (spec->isKind(ParseNodeKind::ExportNamespaceSpec)) { auto* exportNameNode = &spec->as().kid()->as(); exportName = exportNameNode->atom(); markUsedByStencil(exportName); entry = StencilModuleEntry::exportNamespaceFromEntry( moduleRequestIndex, exportName, line, JS::ColumnNumberOneOrigin(column)); } else { MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportBatchSpecStmt)); entry = StencilModuleEntry::exportBatchFromEntry( moduleRequestIndex, line, JS::ColumnNumberOneOrigin(column)); } if (!exportEntries_.append(entry)) { return false; } if (exportName && !exportNames_.put(exportName)) { return false; } } return true; } frontend::StencilModuleEntry* ModuleBuilder::importEntryFor( frontend::TaggedParserAtomIndex localName) const { MOZ_ASSERT(localName); auto ptr = importEntries_.lookup(localName); if (!ptr) { return nullptr; } return &ptr->value(); } bool ModuleBuilder::hasExportedName( frontend::TaggedParserAtomIndex name) const { MOZ_ASSERT(name); return exportNames_.has(name); } bool ModuleBuilder::appendExportEntry( frontend::TaggedParserAtomIndex exportName, frontend::TaggedParserAtomIndex localName, frontend::ParseNode* node) { uint32_t line = 0; JS::LimitedColumnNumberOneOrigin column; if (node) { eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column); } markUsedByStencil(localName); markUsedByStencil(exportName); auto entry = frontend::StencilModuleEntry::exportAsEntry( localName, exportName, line, JS::ColumnNumberOneOrigin(column)); if (!exportEntries_.append(entry)) { return false; } if (!exportNames_.put(exportName)) { return false; } return true; } frontend::MaybeModuleRequestIndex ModuleBuilder::appendModuleRequest( frontend::TaggedParserAtomIndex specifier, frontend::ListNode* assertionList) { markUsedByStencil(specifier); auto request = frontend::StencilModuleRequest(specifier); if (!processAssertions(request, assertionList)) { return MaybeModuleRequestIndex(); } uint32_t index = moduleRequests_.length(); if (!moduleRequests_.append(request)) { js::ReportOutOfMemory(fc_); return MaybeModuleRequestIndex(); } return MaybeModuleRequestIndex(index); } bool ModuleBuilder::maybeAppendRequestedModule( MaybeModuleRequestIndex moduleRequest, frontend::ParseNode* node) { auto specifier = moduleRequests_[moduleRequest.value()].specifier; if (requestedModuleSpecifiers_.has(specifier)) { return true; } uint32_t line; JS::LimitedColumnNumberOneOrigin column; eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column); auto entry = frontend::StencilModuleEntry::requestedModule( moduleRequest, line, JS::ColumnNumberOneOrigin(column)); if (!requestedModules_.append(entry)) { js::ReportOutOfMemory(fc_); return false; } return requestedModuleSpecifiers_.put(specifier); } void ModuleBuilder::markUsedByStencil(frontend::TaggedParserAtomIndex name) { // Imported/exported identifiers must be atomized. eitherParser_.parserAtoms().markUsedByStencil( name, frontend::ParserAtom::Atomize::Yes); } JSObject* js::GetOrCreateModuleMetaObject(JSContext* cx, HandleObject moduleArg) { Handle module = moduleArg.as(); if (JSObject* obj = module->metaObject()) { return obj; } RootedObject metaObject(cx, NewPlainObjectWithProto(cx, nullptr)); if (!metaObject) { return nullptr; } JS::ModuleMetadataHook func = cx->runtime()->moduleMetadataHook; if (!func) { JS_ReportErrorASCII(cx, "Module metadata hook not set"); return nullptr; } RootedValue modulePrivate(cx, JS::GetModulePrivate(module)); if (!func(cx, modulePrivate, metaObject)) { return nullptr; } module->setMetaObject(metaObject); return metaObject; } ModuleObject* js::CallModuleResolveHook(JSContext* cx, HandleValue referencingPrivate, HandleObject moduleRequest) { JS::ModuleResolveHook moduleResolveHook = cx->runtime()->moduleResolveHook; if (!moduleResolveHook) { JS_ReportErrorASCII(cx, "Module resolve hook not set"); return nullptr; } RootedObject result(cx, moduleResolveHook(cx, referencingPrivate, moduleRequest)); if (!result) { return nullptr; } if (!result->is()) { JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object"); return nullptr; } return &result->as(); } bool ModuleObject::topLevelCapabilityResolve(JSContext* cx, Handle module) { RootedValue rval(cx); Rooted promise( cx, &module->topLevelCapability()->as()); return AsyncFunctionReturned(cx, promise, rval); } bool ModuleObject::topLevelCapabilityReject(JSContext* cx, Handle module, HandleValue error) { Rooted promise( cx, &module->topLevelCapability()->as()); return AsyncFunctionThrown(cx, promise, error); } // https://tc39.es/proposal-import-assertions/#sec-evaluate-import-call // NOTE: The caller needs to handle the promise. static bool EvaluateDynamicImportOptions( JSContext* cx, HandleValue optionsArg, MutableHandle assertionArrayArg) { // Step 10. If options is not undefined, then. if (optionsArg.isUndefined()) { return true; } // Step 10.a. If Type(options) is not Object, if (!optionsArg.isObject()) { JS_ReportErrorNumberASCII( cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "import", "object or undefined", InformalValueTypeName(optionsArg)); return false; } RootedObject assertWrapperObject(cx, &optionsArg.toObject()); RootedValue assertValue(cx); // Step 10.b. Let attributesObj be Completion(Get(options, "with")). RootedId withId(cx, NameToId(cx->names().with)); if (!GetProperty(cx, assertWrapperObject, assertWrapperObject, withId, &assertValue)) { return false; } if (assertValue.isUndefined() && cx->options().importAttributesAssertSyntax()) { // Step 10.b. Let assertionsObj be Get(options, "assert"). RootedId assertId(cx, NameToId(cx->names().assert_)); if (!GetProperty(cx, assertWrapperObject, assertWrapperObject, assertId, &assertValue)) { return false; } } // Step 10.d. If assertionsObj is not undefined. if (assertValue.isUndefined()) { return true; } // Step 10.d.i. If Type(assertionsObj) is not Object. if (!assertValue.isObject()) { JS_ReportErrorNumberASCII( cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "import", "object or undefined", InformalValueTypeName(assertValue)); return false; } // Step 10.d.i. Let keys be EnumerableOwnPropertyNames(assertionsObj, key). RootedObject assertObject(cx, &assertValue.toObject()); RootedIdVector assertions(cx); if (!GetPropertyKeys(cx, assertObject, JSITER_OWNONLY, &assertions)) { return false; } uint32_t numberOfAssertions = assertions.length(); if (numberOfAssertions == 0) { return true; } // Step 9 (reordered). Let assertions be a new empty List. Rooted assertionArray( cx, NewDenseFullyAllocatedArray(cx, numberOfAssertions)); if (!assertionArray) { return false; } assertionArray->ensureDenseInitializedLength(0, numberOfAssertions); // Step 10.d.iv. Let supportedAssertions be // !HostGetSupportedImportAssertions(). // Note: This should be driven by a host hook, howver the infrastructure of // said host hook is deeply unclear, and so right now embedders will // not have the ability to alter or extend the set of supported // assertion types. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1840723. size_t numberOfValidAssertions = 0; // Step 10.d.v. For each String key of keys, RootedId key(cx); for (size_t i = 0; i < numberOfAssertions; i++) { key = assertions[i]; // Step 10.d.v.1. Let value be Get(assertionsObj, key). RootedValue value(cx); if (!GetProperty(cx, assertObject, assertObject, key, &value)) { return false; } // Step 10.d.v.3. If Type(value) is not String, then. if (!value.isString()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "import", "string", InformalValueTypeName(value)); return false; } // Step 10.d.v.4. If supportedAssertions contains key, then Append { // [[Key]]: key, [[Value]]: value } to assertions. // Note: We only currently support the "type" assertion; this will need // extension bool supported = key.isAtom() ? key.toAtom() == cx->names().type : false; if (supported) { Rooted assertionObj(cx, NewPlainObject(cx)); if (!assertionObj) { return false; } if (!DefineDataProperty(cx, assertionObj, key, value, JSPROP_ENUMERATE)) { return false; } assertionArray->initDenseElement(numberOfValidAssertions, ObjectValue(*assertionObj)); ++numberOfValidAssertions; } } if (numberOfValidAssertions == 0) { return true; } assertionArray->setLength(numberOfValidAssertions); assertionArrayArg.set(assertionArray); return true; } // ShadowRealmImportValue duplicates some of this, so be sure to keep these in // sync. JSObject* js::StartDynamicModuleImport(JSContext* cx, HandleScript script, HandleValue specifierArg, HandleValue optionsArg) { RootedObject promiseConstructor(cx, JS::GetPromiseConstructor(cx)); if (!promiseConstructor) { return nullptr; } RootedObject promiseObject(cx, JS::NewPromiseObject(cx, nullptr)); if (!promiseObject) { return nullptr; } Handle promise = promiseObject.as(); JS::ModuleDynamicImportHook importHook = cx->runtime()->moduleDynamicImportHook; if (!importHook) { // Dynamic import can be disabled by a pref and is not supported in all // contexts (e.g. web workers). JS_ReportErrorASCII( cx, "Dynamic module import is disabled or not supported in this context"); if (!RejectPromiseWithPendingError(cx, promise)) { return nullptr; } return promise; } RootedString specifier(cx, ToString(cx, specifierArg)); if (!specifier) { if (!RejectPromiseWithPendingError(cx, promise)) { return nullptr; } return promise; } Rooted specifierAtom(cx, AtomizeString(cx, specifier)); if (!specifierAtom) { if (!RejectPromiseWithPendingError(cx, promise)) { return nullptr; } return promise; } Rooted assertionArray(cx); if (!EvaluateDynamicImportOptions(cx, optionsArg, &assertionArray)) { if (!RejectPromiseWithPendingError(cx, promise)) { return nullptr; } return promise; } RootedObject moduleRequest( cx, ModuleRequestObject::create(cx, specifierAtom, assertionArray)); if (!moduleRequest) { if (!RejectPromiseWithPendingError(cx, promise)) { return nullptr; } return promise; } RootedValue referencingPrivate(cx, script->sourceObject()->getPrivate()); if (!importHook(cx, referencingPrivate, moduleRequest, promise)) { // If there's no exception pending then the script is terminating // anyway, so just return nullptr. if (!cx->isExceptionPending() || !RejectPromiseWithPendingError(cx, promise)) { return nullptr; } return promise; } return promise; } static bool OnRootModuleRejected(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); HandleValue error = args.get(0); ReportExceptionClosure reportExn(error); PrepareScriptEnvironmentAndInvoke(cx, cx->global(), reportExn); args.rval().setUndefined(); return true; }; bool js::OnModuleEvaluationFailure(JSContext* cx, HandleObject evaluationPromise, JS::ModuleErrorBehaviour errorBehaviour) { if (evaluationPromise == nullptr) { return false; } // To allow module evaluation to happen synchronously throw the error // immediately. This assumes that any error will already have caused the // promise to be rejected, and doesn't support top-level await. if (errorBehaviour == JS::ThrowModuleErrorsSync) { JS::PromiseState state = JS::GetPromiseState(evaluationPromise); MOZ_DIAGNOSTIC_ASSERT(state == JS::PromiseState::Rejected || state == JS::PromiseState::Fulfilled); JS::SetSettledPromiseIsHandled(cx, evaluationPromise); if (state == JS::PromiseState::Fulfilled) { return true; } RootedValue error(cx, JS::GetPromiseResult(evaluationPromise)); JS_SetPendingException(cx, error); return false; } RootedFunction onRejected( cx, NewHandler(cx, OnRootModuleRejected, evaluationPromise)); if (!onRejected) { return false; } return JS::AddPromiseReactions(cx, evaluationPromise, nullptr, onRejected); } // This is used to marshal some of the arguments to FinishDynamicModuleImport // and pass them through to the promise resolve and reject callbacks. It holds a // reference to the referencing private to keep it alive until it is needed. class DynamicImportContextObject : public NativeObject { public: enum { ReferencingPrivateSlot = 0, SpecifierSlot, SlotCount }; static const JSClass class_; static const JSClassOps classOps_; [[nodiscard]] static DynamicImportContextObject* create( JSContext* cx, Handle referencingPrivate, Handle specifier); Value referencingPrivate() const; JSString* specifier() const; static void clearReferencingPrivate(JSRuntime* runtime, DynamicImportContextObject* ic); static void finalize(JS::GCContext* gcx, JSObject* obj); }; /* static */ const JSClass DynamicImportContextObject::class_ = { "DynamicImportContextObject", JSCLASS_HAS_RESERVED_SLOTS(DynamicImportContextObject::SlotCount) | JSCLASS_SLOT0_IS_NSISUPPORTS | JSCLASS_FOREGROUND_FINALIZE, &DynamicImportContextObject::classOps_}; static_assert(DynamicImportContextObject::ReferencingPrivateSlot == 0); /* static */ const JSClassOps DynamicImportContextObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve DynamicImportContextObject::finalize, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; /* static */ DynamicImportContextObject* DynamicImportContextObject::create( JSContext* cx, Handle referencingPrivate, Handle specifier) { Rooted self( cx, NewObjectWithGivenProto(cx, nullptr)); if (!self) { return nullptr; } cx->runtime()->addRefScriptPrivate(referencingPrivate); self->initReservedSlot(ReferencingPrivateSlot, referencingPrivate); self->initReservedSlot(SpecifierSlot, StringValue(specifier)); return self; } Value DynamicImportContextObject::referencingPrivate() const { return getReservedSlot(ReferencingPrivateSlot); } JSString* DynamicImportContextObject::specifier() const { Value value = getReservedSlot(SpecifierSlot); if (value.isUndefined()) { return nullptr; } return value.toString(); } /* static */ void DynamicImportContextObject::finalize(JS::GCContext* gcx, JSObject* obj) { auto* context = &obj->as(); clearReferencingPrivate(gcx->runtime(), context); } /* static */ void DynamicImportContextObject::clearReferencingPrivate( JSRuntime* runtime, DynamicImportContextObject* context) { Value value = context->referencingPrivate(); if (!value.isUndefined()) { context->setReservedSlot(ReferencingPrivateSlot, UndefinedValue()); runtime->releaseScriptPrivate(value); } } // Adjustment for Top-level await; // See: https://github.com/tc39/proposal-dynamic-import/pull/71/files static bool OnResolvedDynamicModule(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.get(0).isUndefined()); Rooted context( cx, ExtraFromHandler(args)); auto clearRef = mozilla::MakeScopeExit([&] { DynamicImportContextObject::clearReferencingPrivate(cx->runtime(), context); }); RootedValue referencingPrivate(cx, context->referencingPrivate()); Rooted specifier(cx, AtomizeString(cx, context->specifier())); if (!specifier) { return false; } Rooted promise(cx, TargetFromHandler(args)); RootedObject moduleRequest( cx, ModuleRequestObject::create(cx, specifier, nullptr)); if (!moduleRequest) { return RejectPromiseWithPendingError(cx, promise); } RootedObject result( cx, CallModuleResolveHook(cx, referencingPrivate, moduleRequest)); if (!result) { return RejectPromiseWithPendingError(cx, promise); } Rooted module(cx, &result->as()); if (module->status() != ModuleStatus::EvaluatingAsync && module->status() != ModuleStatus::Evaluated) { JS_ReportErrorASCII( cx, "Unevaluated or errored module returned by module resolve hook"); return RejectPromiseWithPendingError(cx, promise); } MOZ_ASSERT_IF(module->hasCyclicModuleFields(), module->getCycleRoot() ->topLevelCapability() ->as() .state() == JS::PromiseState::Fulfilled); RootedObject ns(cx, GetOrCreateModuleNamespace(cx, module)); if (!ns) { return RejectPromiseWithPendingError(cx, promise); } args.rval().setUndefined(); RootedValue value(cx, ObjectValue(*ns)); return PromiseObject::resolve(cx, promise, value); }; static bool OnRejectedDynamicModule(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); HandleValue error = args.get(0); Rooted context( cx, ExtraFromHandler(args)); auto clearRef = mozilla::MakeScopeExit([&] { DynamicImportContextObject::clearReferencingPrivate(cx->runtime(), context); }); RootedValue referencingPrivate(cx, context->referencingPrivate()); Rooted promise(cx, TargetFromHandler(args)); args.rval().setUndefined(); return PromiseObject::reject(cx, promise, error); }; bool js::FinishDynamicModuleImport(JSContext* cx, HandleObject evaluationPromise, HandleValue referencingPrivate, HandleObject moduleRequest, HandleObject promise) { // If we do not have an evaluation promise or a module request for the module, // we can assume that evaluation has failed or been interrupted -- we can // reject the dynamic module. if (!evaluationPromise || !moduleRequest) { return RejectPromiseWithPendingError(cx, promise.as()); } Rooted specifier( cx, moduleRequest->as().specifier()); Rooted context( cx, DynamicImportContextObject::create(cx, referencingPrivate, specifier)); if (!context) { return false; } Rooted contextValue(cx, ObjectValue(*context)); RootedFunction onResolved( cx, NewHandlerWithExtraValue(cx, OnResolvedDynamicModule, promise, contextValue)); if (!onResolved) { return false; } RootedFunction onRejected( cx, NewHandlerWithExtraValue(cx, OnRejectedDynamicModule, promise, contextValue)); if (!onRejected) { return false; } if (!JS::AddPromiseReactionsIgnoringUnhandledRejection( cx, evaluationPromise, onResolved, onRejected)) { return false; } return true; }