/* -*- 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/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 "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::New < ModuleStatus::Unlinked && 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(); } static Value ModuleTypeToValue(JS::ModuleType moduleType) { static_assert(size_t(JS::ModuleType::Limit) <= INT32_MAX); return Int32Value(int32_t(moduleType)); } static JS::ModuleType ValueToModuleType(const Value& value) { int32_t i = value.toInt32(); MOZ_ASSERT(i >= 0 && i <= int32_t(JS::ModuleType::Limit)); return static_cast(i); } #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; } /////////////////////////////////////////////////////////////////////////// // ImportAttribute ImportAttribute::ImportAttribute(Handle key, Handle value) : key_(key), value_(value) {} void ImportAttribute::trace(JSTracer* trc) { TraceNullableEdge(trc, &key_, "ImportAttribute::key_"); TraceNullableEdge(trc, &value_, "ImportAttribute::value_"); } /////////////////////////////////////////////////////////////////////////// // ModuleRequestObject /* static */ const JSClass ModuleRequestObject::class_ = { "ModuleRequest", JSCLASS_HAS_RESERVED_SLOTS(ModuleRequestObject::SlotCount), }; DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ModuleRequestObject, specifier, SpecifierSlot) JS::ModuleType ModuleRequestObject::moduleType() const { return ValueToModuleType(getReservedSlot(ModuleTypeSlot)); } static bool GetModuleType(JSContext* cx, Handle maybeAttributes, JS::ModuleType& moduleType) { for (const ImportAttribute& importAttribute : maybeAttributes) { if (importAttribute.key() == cx->names().type) { Rooted typeStr( cx, importAttribute.value()->ensureLinear(cx)); if (!typeStr) { return false; } if (js::EqualStrings(typeStr, cx->names().json)) { moduleType = JS::ModuleType::JSON; } else if (js::EqualStrings(typeStr, cx->names().css)) { moduleType = JS::ModuleType::CSS; } else if (js::EqualStrings(typeStr, cx->names().bytes)) { moduleType = JS::ModuleType::Bytes; } else { 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) { JS::ModuleType moduleType = JS::ModuleType::JavaScript; if (!GetModuleType(cx, maybeAttributes, moduleType)) { return nullptr; } return create(cx, specifier, moduleType); } /* static */ ModuleRequestObject* ModuleRequestObject::create(JSContext* cx, Handle specifier, JS::ModuleType moduleType) { ModuleRequestObject* self = NewObjectWithGivenProto(cx, nullptr); if (!self) { return nullptr; } self->initReservedSlot(SpecifierSlot, StringOrNullValue(specifier)); self->initReservedSlot(ModuleTypeSlot, ModuleTypeToValue(moduleType)); return self; } void ModuleRequestObject::setFirstUnsupportedAttributeKey(Handle key) { initReservedSlot(FirstUnsupportedAttributeKeySlot, StringOrNullValue(key)); } bool ModuleRequestObject::hasFirstUnsupportedAttributeKey() const { return !getReservedSlot(FirstUnsupportedAttributeKeySlot).isNullOrUndefined(); } JSAtom* ModuleRequestObject::getFirstUnsupportedAttributeKey() const { if (!hasFirstUnsupportedAttributeKey()) { return nullptr; } return &getReservedSlot(FirstUnsupportedAttributeKeySlot) .toString() ->asAtom(); } /////////////////////////////////////////////////////////////////////////// // 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 */ constexpr 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); } constexpr char ModuleNamespaceObject::ProxyHandler::family = 0; 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); } } // https://tc39.es/ecma262/#sec-IncrementModuleAsyncEvaluationCount // 9.6.3 IncrementModuleAsyncEvaluationCount() static uint32_t IncrementModuleAsyncEvaluationCount(JSRuntime* rt) { if (rt->pendingAsyncModuleEvaluations == 0) { // From the spec NOTE: // An implementation may unobservably reset [[ModuleAsyncEvaluationCount]] // to 0 whenever there are no pending modules. rt->moduleAsyncEvaluatingPostOrder = 0; } uint32_t ordinal = rt->moduleAsyncEvaluatingPostOrder; MOZ_ASSERT(ordinal != ASYNC_EVALUATING_POST_ORDER_DONE); MOZ_ASSERT(ordinal != ASYNC_EVALUATING_POST_ORDER_UNSET); MOZ_ASSERT(ordinal < ASYNC_EVALUATING_POST_ORDER_MAX_VALUE); rt->moduleAsyncEvaluatingPostOrder++; MOZ_ASSERT(rt->pendingAsyncModuleEvaluations < MAX_UINT32); rt->pendingAsyncModuleEvaluations++; return ordinal; } bool AsyncEvaluationOrder::isUnset() const { return value == ASYNC_EVALUATING_POST_ORDER_UNSET; } bool AsyncEvaluationOrder::isDone() const { return value == ASYNC_EVALUATING_POST_ORDER_DONE; } bool AsyncEvaluationOrder::isInteger() const { return value <= ASYNC_EVALUATING_POST_ORDER_MAX_VALUE; } uint32_t AsyncEvaluationOrder::get() const { MOZ_ASSERT(isInteger()); return value; } void AsyncEvaluationOrder::set(JSRuntime* rt) { MOZ_ASSERT(isUnset()); value = IncrementModuleAsyncEvaluationCount(rt); } void AsyncEvaluationOrder::setDone(JSRuntime* rt) { MOZ_ASSERT(isInteger()); MOZ_ASSERT(rt->pendingAsyncModuleEvaluations > 0); rt->pendingAsyncModuleEvaluations--; value = ASYNC_EVALUATING_POST_ORDER_DONE; } /////////////////////////////////////////////////////////////////////////// // 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::New; bool hasTopLevelAwait : 1; private: // Flag bits that determine whether other fields are present. bool hasDfsAncestorIndex : 1; bool hasPendingAsyncDependencies : 1; // Fields whose presence is conditional on the flag bits above. uint32_t dfsAncestorIndex = 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; LoadedModuleMap loadedModules; ImportEntryVector importEntries; ExportEntryVector exportEntries; IndirectBindingMap importBindings; UniquePtr functionDeclarations; AsyncEvaluationOrder asyncEvaluationOrder; 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 setDfsAncestorIndex(uint32_t index); Maybe maybeDfsAncestorIndex() const; void clearDfsAncestorIndex(); void setPendingAsyncDependencies(uint32_t newValue); Maybe maybePendingAsyncDependencies() const; }; CyclicModuleFields::CyclicModuleFields() : hasTopLevelAwait(false), hasDfsAncestorIndex(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); loadedModules.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::setDfsAncestorIndex(uint32_t index) { dfsAncestorIndex = index; hasDfsAncestorIndex = true; } Maybe CyclicModuleFields::maybeDfsAncestorIndex() const { return hasDfsAncestorIndex ? Some(dfsAncestorIndex) : Nothing(); } void CyclicModuleFields::clearDfsAncestorIndex() { dfsAncestorIndex = 0; hasDfsAncestorIndex = false; } 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() { MOZ_ASSERT(hasCyclicModuleFields()); 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; } ModuleObject* self = 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; } ModuleObject* self = 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; } 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::New && 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; } AsyncEvaluationOrder& ModuleObject::asyncEvaluationOrder() { return cyclicModuleFields()->asyncEvaluationOrder; } AsyncEvaluationOrder const& ModuleObject::asyncEvaluationOrder() const { return cyclicModuleFields()->asyncEvaluationOrder; } 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::clearDfsAncestorIndex() { cyclicModuleFields()->clearDfsAncestorIndex(); } 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(); } 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; } LoadedModuleMap& ModuleObject::loadedModules() { return cyclicModuleFields()->loadedModules; } const LoadedModuleMap& ModuleObject::loadedModules() const { return cyclicModuleFields()->loadedModules; } 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 { if (hasSyntheticModuleFields()) { return false; } ModuleStatus fullStatus = cyclicModuleFields()->status; return fullStatus == ModuleStatus::Evaluated_Error; } void ModuleObject::setEvaluationError(HandleValue newValue) { MOZ_ASSERT(status() != ModuleStatus::Unlinked && status() != ModuleStatus::New); 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, JS::HandleVector 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; } /////////////////////////////////////////////////////////////////////////// // GraphLoadingStateRecordObject GraphLoadingStateRecord::GraphLoadingStateRecord( JS::LoadModuleResolvedCallback resolved, JS::LoadModuleRejectedCallback rejected) : resolved(resolved), rejected(rejected) {} void GraphLoadingStateRecord::trace(JSTracer* trc) { visited.trace(trc); } /* static */ const JSClass GraphLoadingStateRecordObject::class_ = { "GraphLoadingStateRecordObject", JSCLASS_HAS_RESERVED_SLOTS(GraphLoadingStateRecordObject::SlotCount) | JSCLASS_BACKGROUND_FINALIZE, &GraphLoadingStateRecordObject::classOps_, }; static_assert(GraphLoadingStateRecordObject::StateSlot == 0); /* static */ const JSClassOps GraphLoadingStateRecordObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate nullptr, // newEnumerate nullptr, // resolve nullptr, // mayResolve GraphLoadingStateRecordObject::finalize, // finalize nullptr, // call nullptr, // construct GraphLoadingStateRecordObject::trace, // trace }; /* static */ GraphLoadingStateRecordObject* GraphLoadingStateRecordObject::create( JSContext* cx, bool isLoading, uint32_t pendingModulesCount, JS::LoadModuleResolvedCallback resolved, JS::LoadModuleRejectedCallback rejected, Handle hostDefined) { Rooted self( cx, NewObjectWithGivenProto(cx, nullptr)); if (!self) { return nullptr; } auto* state = cx->new_(resolved, rejected); if (!state) { ReportOutOfMemory(cx); return nullptr; } InitReservedSlot(self, StateSlot, state, MemoryUse::GraphLoadingStateRecord); self->initReservedSlot(IsLoadingSlot, Int32Value(isLoading)); self->initReservedSlot(PendingModulesCountSlot, Int32Value(pendingModulesCount)); self->initReservedSlot(HostDefinedSlot, hostDefined); return self; } /* static */ GraphLoadingStateRecordObject* GraphLoadingStateRecordObject::create( JSContext* cx, bool isLoading, uint32_t pendingModulesCount, Handle promise, Handle hostDefined) { Rooted self( cx, NewObjectWithGivenProto(cx, nullptr)); if (!self) { return nullptr; } auto* state = cx->new_(); if (!state) { ReportOutOfMemory(cx); return nullptr; } InitReservedSlot(self, StateSlot, state, MemoryUse::GraphLoadingStateRecord); self->initReservedSlot(PromiseSlot, ObjectValue(*promise)); self->initReservedSlot(IsLoadingSlot, Int32Value(isLoading)); self->initReservedSlot(PendingModulesCountSlot, Int32Value(pendingModulesCount)); self->initReservedSlot(HostDefinedSlot, hostDefined); return self; } VisitedModuleSet& GraphLoadingStateRecordObject::visited() { GraphLoadingStateRecord* state = static_cast( getReservedSlot(StateSlot).toPrivate()); MOZ_ASSERT(state); return state->visited; } PromiseObject* GraphLoadingStateRecordObject::promise() { if (getReservedSlot(PromiseSlot).isUndefined()) { return nullptr; } return &getReservedSlot(PromiseSlot).toObject().as(); } bool GraphLoadingStateRecordObject::isLoading() { return getReservedSlot(IsLoadingSlot).toInt32(); } void GraphLoadingStateRecordObject::setIsLoading(bool isLoading) { setReservedSlot(IsLoadingSlot, Int32Value(isLoading)); } uint32_t GraphLoadingStateRecordObject::pendingModulesCount() { return getReservedSlot(PendingModulesCountSlot).toInt32(); } void GraphLoadingStateRecordObject::setPendingModulesCount(uint32_t count) { setReservedSlot(PendingModulesCountSlot, Int32Value(count)); } Value GraphLoadingStateRecordObject::hostDefined() { return getReservedSlot(HostDefinedSlot); } bool GraphLoadingStateRecordObject::resolved( JSContext* cx, JS::Handle hostDefined) { if (promise()) { Rooted promiseObj(cx, promise()); return AsyncFunctionReturned(cx, promiseObj, UndefinedHandleValue); } GraphLoadingStateRecord* state = static_cast( getReservedSlot(StateSlot).toPrivate()); MOZ_ASSERT(state); MOZ_ASSERT(state->resolved); return state->resolved(cx, hostDefined); } bool GraphLoadingStateRecordObject::rejected(JSContext* cx, JS::Handle hostDefined, Handle error) { if (promise()) { Rooted promiseObj(cx, promise()); return AsyncFunctionThrown(cx, promiseObj, error); } GraphLoadingStateRecord* state = static_cast( getReservedSlot(StateSlot).toPrivate()); MOZ_ASSERT(state); MOZ_ASSERT(state->rejected); return state->rejected(cx, hostDefined, error); } /* static */ void GraphLoadingStateRecordObject::finalize(JS::GCContext* gcx, JSObject* obj) { auto* self = &obj->as(); Value stateValue = self->getReservedSlot(StateSlot); if (!stateValue.isUndefined()) { auto* state = static_cast(stateValue.toPrivate()); gcx->delete_(obj, state, MemoryUse::GraphLoadingStateRecord); } } /* static */ void GraphLoadingStateRecordObject::trace(JSTracer* trc, JSObject* obj) { GraphLoadingStateRecordObject* self = &obj->as(); Value stateValue = self->getReservedSlot(StateSlot); if (!stateValue.isUndefined()) { GraphLoadingStateRecord* state = static_cast(stateValue.toPrivate()); state->trace(trc); } } /////////////////////////////////////////////////////////////////////////// // ModuleBuilder ModuleBuilder::ModuleBuilder(FrontendContext* fc, const frontend::EitherParser& eitherParser) : fc_(fc), eitherParser_(eitherParser), requestedModuleIndexes_(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 { uint32_t numberOfAttributes = request.attributes.length(); Rooted attributes(cx); if (numberOfAttributes > 0) { if (!attributes.reserve(numberOfAttributes)) { ReportOutOfMemory(cx); return nullptr; } Rooted attributeKey(cx); Rooted attributeValue(cx); for (uint32_t j = 0; j < numberOfAttributes; ++j) { attributeKey = atomCache.getExistingAtomAt(cx, request.attributes[j].key); attributeValue = atomCache.getExistingAtomAt(cx, request.attributes[j].value); attributes.infallibleEmplaceBack(attributeKey, attributeValue); } } Rooted specifier(cx, atomCache.getExistingAtomAt(cx, request.specifier)); MOZ_ASSERT(specifier); Rooted moduleRequestObject( cx, ModuleRequestObject::create(cx, specifier, attributes)); if (!moduleRequestObject) { return nullptr; } if (request.firstUnsupportedAttributeKey) { Rooted unsupportedAttributeKey( cx, atomCache.getExistingAtomAt(cx, request.firstUnsupportedAttributeKey)); moduleRequestObject->setFirstUnsupportedAttributeKey( unsupportedAttributeKey); } return moduleRequestObject; } 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::processAttributes(frontend::StencilModuleRequest& request, frontend::ListNode* attributeList) { using namespace js::frontend; for (ParseNode* attributeItem : attributeList->contents()) { BinaryNode* attribute = &attributeItem->as(); MOZ_ASSERT(attribute->isKind(ParseNodeKind::ImportAttribute)); auto key = attribute->left()->as().atom(); markUsedByStencil(key); // Note: This should be driven by a host hook // (HostGetSupportedImportAttributes), however 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 attributes. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1840723. if (key == TaggedParserAtomIndex::WellKnown::type()) { auto value = attribute->right()->as().atom(); markUsedByStencil(value); StencilModuleImportAttribute attributeStencil(key, value); if (!request.attributes.append(attributeStencil)) { js::ReportOutOfMemory(fc_); return false; } } else { if (!request.firstUnsupportedAttributeKey) { request.firstUnsupportedAttributeKey = key; } } } 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* attributeList = &moduleRequest->right()->as(); MOZ_ASSERT(attributeList->isKind(ParseNodeKind::ImportAttributeList)); auto specifier = moduleSpec->atom(); MaybeModuleRequestIndex moduleRequestIndex = appendModuleRequest(specifier, attributeList); 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* attributeList = &moduleRequest->right()->as(); MOZ_ASSERT(attributeList->isKind(ParseNodeKind::ImportAttributeList)); auto specifier = moduleSpec->atom(); MaybeModuleRequestIndex moduleRequestIndex = appendModuleRequest(specifier, attributeList); 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; if (spec->isKind(ParseNodeKind::ExportSpec)) { auto* importNameNode = &spec->as().left()->as(); auto* exportNameNode = &spec->as().right()->as(); auto importName = importNameNode->atom(); auto exportName = exportNameNode->atom(); MOZ_ASSERT(exportNames_.has(exportName)); 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(); auto exportName = exportNameNode->atom(); MOZ_ASSERT(exportNames_.has(exportName)); 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; } } 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(); } ModuleBuilder::NoteExportedNameResult ModuleBuilder::noteExportedName( frontend::TaggedParserAtomIndex name) { MOZ_ASSERT(name); auto addPtr = exportNames_.lookupForAdd(name); if (addPtr) { return NoteExportedNameResult::AlreadyDeclared; } if (!exportNames_.add(addPtr, name)) { return NoteExportedNameResult::OutOfMemory; } return NoteExportedNameResult::Success; } bool ModuleBuilder::appendExportEntry( frontend::TaggedParserAtomIndex exportName, frontend::TaggedParserAtomIndex localName, frontend::ParseNode* node) { MOZ_ASSERT(exportNames_.has(exportName)); 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)); return exportEntries_.append(entry); } frontend::MaybeModuleRequestIndex ModuleBuilder::appendModuleRequest( frontend::TaggedParserAtomIndex specifier, frontend::ListNode* attributeList) { markUsedByStencil(specifier); auto request = frontend::StencilModuleRequest(specifier); if (!processAttributes(request, attributeList)) { return MaybeModuleRequestIndex(); } if (auto ptr = moduleRequestIndexes_.lookup(request)) { return MaybeModuleRequestIndex(ptr->value()); } uint32_t index = moduleRequests_.length(); if (!moduleRequests_.append(request) || !moduleRequestIndexes_.put(request, index)) { js::ReportOutOfMemory(fc_); return MaybeModuleRequestIndex(); } return MaybeModuleRequestIndex(index); } bool ModuleBuilder::maybeAppendRequestedModule( MaybeModuleRequestIndex moduleRequest, frontend::ParseNode* node) { uint32_t index = moduleRequest.value(); if (requestedModuleIndexes_.has(index)) { 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 requestedModuleIndexes_.put(index); } 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; } 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); }