diff options
Diffstat (limited to 'js/src/builtin/ModuleObject.cpp')
-rw-r--r-- | js/src/builtin/ModuleObject.cpp | 2548 |
1 files changed, 2548 insertions, 0 deletions
diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp new file mode 100644 index 0000000000..f227728f98 --- /dev/null +++ b/js/src/builtin/ModuleObject.cpp @@ -0,0 +1,2548 @@ +/* -*- 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/friend/ErrorMessages.h" // JSMSG_* +#include "js/Modules.h" // JS::GetModulePrivate, JS::ModuleDynamicImportHook +#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/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<ModuleRequestObject*> moduleRequest, + Handle<JSAtom*> maybeImportName, + Handle<JSAtom*> localName, uint32_t lineNumber, + uint32_t 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<JSAtom*> maybeExportName, + Handle<ModuleRequestObject*> moduleRequest, + Handle<JSAtom*> maybeImportName, + Handle<JSAtom*> maybeLocalName, uint32_t lineNumber, + uint32_t 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<ModuleRequestObject*> moduleRequest, + uint32_t lineNumber, uint32_t 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<ModuleObject>(); +} + +JSAtom* ResolvedBindingObject::bindingName() const { + Value value = getReservedSlot(BindingNameSlot); + return &value.toString()->asAtom(); +} + +/* static */ +bool ResolvedBindingObject::isInstance(HandleValue value) { + return value.isObject() && value.toObject().is<ResolvedBindingObject>(); +} + +/* static */ +ResolvedBindingObject* ResolvedBindingObject::create( + JSContext* cx, Handle<ModuleObject*> module, Handle<JSAtom*> bindingName) { + ResolvedBindingObject* self = + NewObjectWithGivenProto<ResolvedBindingObject>(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::assertions() const { + JSObject* obj = getReservedSlot(AssertionSlot).toObjectOrNull(); + if (!obj) { + return nullptr; + } + + return &obj->as<ArrayObject>(); +} + +/* static */ +bool ModuleRequestObject::isInstance(HandleValue value) { + return value.isObject() && value.toObject().is<ModuleRequestObject>(); +} + +/* static */ +ModuleRequestObject* ModuleRequestObject::create( + JSContext* cx, Handle<JSAtom*> specifier, + Handle<ArrayObject*> maybeAssertions) { + ModuleRequestObject* self = + NewObjectWithGivenProto<ModuleRequestObject>(cx, nullptr); + if (!self) { + return nullptr; + } + + self->initReservedSlot(SpecifierSlot, StringOrNullValue(specifier)); + self->initReservedSlot(AssertionSlot, ObjectOrNullValue(maybeAssertions)); + 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<jsid> 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<ModuleEnvironmentObject*> environment, + HandleId targetName) { + if (!map_) { + map_.emplace(cx->zone()); + } + + mozilla::Maybe<PropertyInfo> 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<PropertyInfo>* 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<ModuleNamespaceObject>(); +} + +/* static */ +ModuleNamespaceObject* ModuleNamespaceObject::create( + JSContext* cx, Handle<ModuleObject*> module, + MutableHandle<UniquePtr<ExportNameVector>> exports, + MutableHandle<UniquePtr<IndirectBindingMap>> 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<ModuleNamespaceObject>(); +} + +ModuleObject& ModuleNamespaceObject::module() { + return GetProxyPrivate(this).toObject().as<ModuleObject>(); +} + +const ExportNameVector& ModuleNamespaceObject::exports() const { + Value value = GetProxyReservedSlot(this, ExportsSlot); + auto* exports = static_cast<ExportNameVector*>(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<ExportNameVector&>(exports()); +} + +IndirectBindingMap& ModuleNamespaceObject::bindings() { + Value value = GetProxyReservedSlot(this, BindingsSlot); + auto* bindings = static_cast<IndirectBindingMap*>(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<JSAtom*> exportedName, + Handle<ModuleObject*> targetModule, + Handle<JSAtom*> targetName) { + Rooted<ModuleEnvironmentObject*> 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<mozilla::Maybe<PropertyDescriptor>> desc) const { + Rooted<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>()); + 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<PropertyInfo> 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<PropertyDescriptor> 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<PropertyDescriptor> 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<ModuleNamespaceObject>().bindings(); + ModuleEnvironmentObject* env; + mozilla::Maybe<PropertyInfo> 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<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>()); + 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<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>()); + if (id.isSymbol()) { + if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) { + vp.setString(cx->names().Module); + return true; + } + + vp.setUndefined(); + return true; + } + + ModuleEnvironmentObject* env; + mozilla::Maybe<PropertyInfo> 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<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>()); + 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<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>()); + 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<ModuleNamespaceObject>(); + + 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<ModuleNamespaceObject>(); + + if (self.hasExports()) { + gcx->delete_(proxy, &self.mutableExports(), MemoryUse::ModuleExports); + } + + if (self.hasBindings()) { + gcx->delete_(proxy, &self.bindings(), MemoryUse::ModuleBindingMap); + } +} + +/////////////////////////////////////////////////////////////////////////// +// 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<Value> evaluationError; + HeapPtr<JSObject*> metaObject; + HeapPtr<ScriptSourceObject*> scriptSourceObject; + RequestedModuleVector requestedModules; + ImportEntryVector importEntries; + ExportEntryVector exportEntries; + IndirectBindingMap importBindings; + UniquePtr<FunctionDeclarationVector> functionDeclarations; + HeapPtr<PromiseObject*> topLevelCapability; + HeapPtr<ListObject*> asyncParentModules; + HeapPtr<ModuleObject*> cycleRoot; + + public: + CyclicModuleFields(); + + void trace(JSTracer* trc); + + void initExportEntries(MutableHandle<ExportEntryVector> allEntries, + uint32_t localExportCount, + uint32_t indirectExportCount, + uint32_t starExportCount); + Span<const ExportEntry> localExportEntries() const; + Span<const ExportEntry> indirectExportEntries() const; + Span<const ExportEntry> starExportEntries() const; + + void setDfsIndex(uint32_t index); + Maybe<uint32_t> maybeDfsIndex() const; + void setDfsAncestorIndex(uint32_t index); + Maybe<uint32_t> maybeDfsAncestorIndex() const; + void clearDfsIndexes(); + + void setAsyncEvaluating(uint32_t postOrder); + bool getIsAsyncEvaluating() const; + Maybe<uint32_t> maybeAsyncEvaluatingPostOrder() const; + void clearAsyncEvaluatingPostOrder(); + + void setPendingAsyncDependencies(uint32_t newValue); + Maybe<uint32_t> 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<ExportEntryVector> 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<const ExportEntry> CyclicModuleFields::localExportEntries() const { + MOZ_ASSERT(indirectExportEntriesStart <= exportEntries.length()); + return Span(exportEntries.begin(), + exportEntries.begin() + indirectExportEntriesStart); +} + +Span<const ExportEntry> CyclicModuleFields::indirectExportEntries() const { + MOZ_ASSERT(indirectExportEntriesStart <= starExportEntriesStart); + MOZ_ASSERT(starExportEntriesStart <= exportEntries.length()); + return Span(exportEntries.begin() + indirectExportEntriesStart, + exportEntries.begin() + starExportEntriesStart); +} + +Span<const ExportEntry> 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<uint32_t> CyclicModuleFields::maybeDfsIndex() const { + return hasDfsIndex ? Some(dfsIndex) : Nothing(); +} + +void CyclicModuleFields::setDfsAncestorIndex(uint32_t index) { + dfsAncestorIndex = index; + hasDfsAncestorIndex = true; +} + +Maybe<uint32_t> 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<uint32_t> 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<uint32_t> 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<ModuleObject>(); +} + +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<CyclicModuleFields*>(ptr); +} +const CyclicModuleFields* ModuleObject::cyclicModuleFields() const { + return const_cast<ModuleObject*>(this)->cyclicModuleFields(); +} + +Span<const RequestedModule> ModuleObject::requestedModules() const { + return cyclicModuleFields()->requestedModules; +} + +Span<const ImportEntry> ModuleObject::importEntries() const { + return cyclicModuleFields()->importEntries; +} + +Span<const ExportEntry> ModuleObject::localExportEntries() const { + return cyclicModuleFields()->localExportEntries(); +} + +Span<const ExportEntry> ModuleObject::indirectExportEntries() const { + return cyclicModuleFields()->indirectExportEntries(); +} + +Span<const ExportEntry> ModuleObject::starExportEntries() const { + return cyclicModuleFields()->starExportEntries(); +} + +void ModuleObject::initFunctionDeclarations( + UniquePtr<FunctionDeclarationVector> decls) { + cyclicModuleFields()->functionDeclarations = std::move(decls); +} + +/* static */ +ModuleObject* ModuleObject::create(JSContext* cx) { + Rooted<UniquePtr<CyclicModuleFields>> fields(cx); + fields = cx->make_unique<CyclicModuleFields>(); + if (!fields) { + return nullptr; + } + + Rooted<ModuleObject*> self( + cx, NewObjectWithGivenProto<ModuleObject>(cx, nullptr)); + if (!self) { + return nullptr; + } + + InitReservedSlot(self, CyclicModuleFieldsSlot, fields.release(), + MemoryUse::ModuleCyclicFields); + + return self; +} + +/* static */ +void ModuleObject::finalize(JS::GCContext* gcx, JSObject* obj) { + ModuleObject* self = &obj->as<ModuleObject>(); + if (self->hasCyclicModuleFields()) { + gcx->delete_(obj, self->cyclicModuleFields(), + MemoryUse::ModuleCyclicFields); + } +} + +ModuleEnvironmentObject& ModuleObject::initialEnvironment() const { + Value value = getReservedSlot(EnvironmentSlot); + return value.toObject().as<ModuleEnvironmentObject>(); +} + +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<ModuleNamespaceObject>(); +} + +ScriptSourceObject* ModuleObject::scriptSourceObject() const { + return cyclicModuleFields()->scriptSourceObject; +} + +void ModuleObject::initAsyncSlots(JSContext* cx, bool hasTopLevelAwait, + Handle<ListObject*> 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()); + initReservedSlot(ScriptSlot, PrivateGCThingValue(script)); + cyclicModuleFields()->scriptSourceObject = script->sourceObject(); +} + +void ModuleObject::setInitialEnvironment( + Handle<ModuleEnvironmentObject*> initialEnvironment) { + initReservedSlot(EnvironmentSlot, ObjectValue(*initialEnvironment)); +} + +void ModuleObject::initImportExportData( + MutableHandle<RequestedModuleVector> requestedModules, + MutableHandle<ImportEntryVector> importEntries, + MutableHandle<ExportEntryVector> 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<ModuleObject*> self) { + return FreezeObject(cx, self); +} + +#ifdef DEBUG +/* static */ inline bool ModuleObject::AssertFrozen( + JSContext* cx, Handle<ModuleObject*> 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<BaseScript>(); + 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; +} + +static inline void AssertValidModuleStatus(ModuleStatus status) { + MOZ_ASSERT(status >= ModuleStatus::Unlinked && + status <= ModuleStatus::Evaluated_Error); +} + +ModuleStatus ModuleObject::status() const { + // TODO: When implementing synthetic module records it may be convenient to + // make this method always return a ModuleStatus::Evaluated for such a module + // 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. + + 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<uint32_t> ModuleObject::maybeDfsIndex() const { + return cyclicModuleFields()->maybeDfsIndex(); +} + +uint32_t ModuleObject::dfsIndex() const { return maybeDfsIndex().value(); } + +void ModuleObject::setDfsIndex(uint32_t index) { + cyclicModuleFields()->setDfsIndex(index); +} + +Maybe<uint32_t> 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<ModuleObject*> module) { + MOZ_ASSERT(!module->maybeTopLevelCapability()); + + Rooted<PromiseObject*> resultPromise(cx, CreatePromiseObjectForAsync(cx)); + if (!resultPromise) { + return nullptr; + } + + module->setInitialTopLevelCapability(resultPromise); + return resultPromise; +} + +void ModuleObject::setInitialTopLevelCapability( + Handle<PromiseObject*> capability) { + cyclicModuleFields()->topLevelCapability = capability; +} + +ListObject* ModuleObject::asyncParentModules() const { + return cyclicModuleFields()->asyncParentModules; +} + +bool ModuleObject::appendAsyncParentModule(JSContext* cx, + Handle<ModuleObject*> self, + Handle<ModuleObject*> parent) { + Rooted<Value> parentValue(cx, ObjectValue(*parent)); + return self->asyncParentModules()->append(cx, parentValue); +} + +Maybe<uint32_t> ModuleObject::maybePendingAsyncDependencies() const { + return cyclicModuleFields()->maybePendingAsyncDependencies(); +} + +uint32_t ModuleObject::pendingAsyncDependencies() const { + return maybePendingAsyncDependencies().value(); +} + +Maybe<uint32_t> 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::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<ModuleObject>(); + if (module.hasCyclicModuleFields()) { + module.cyclicModuleFields()->trace(trc); + } +} + +/* static */ +bool ModuleObject::instantiateFunctionDeclarations(JSContext* cx, + Handle<ModuleObject*> self) { +#ifdef DEBUG + MOZ_ASSERT(self->status() == ModuleStatus::Linking); + if (!AssertFrozen(cx, self)) { + return false; + } +#endif + // |self| initially manages this vector. + UniquePtr<FunctionDeclarationVector>& funDecls = + self->cyclicModuleFields()->functionDeclarations; + if (!funDecls) { + JS_ReportErrorASCII( + cx, "Module function declarations have already been instantiated"); + return false; + } + + Rooted<ModuleEnvironmentObject*> env(cx, &self->initialEnvironment()); + RootedObject obj(cx); + RootedValue value(cx); + RootedFunction fun(cx); + Rooted<PropertyName*> name(cx); + + for (GCThingIndex funIndex : *funDecls) { + fun.set(self->script()->getFunction(funIndex)); + obj = Lambda(cx, fun, env); + if (!obj) { + return false; + } + + name = fun->explicitName()->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<ModuleObject*> 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<ModuleEnvironmentObject*> env(cx, self->environment()); + if (!env) { + JS_ReportErrorASCII(cx, + "Module declarations have not yet been instantiated"); + return false; + } + + Rooted<Value> 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<ModuleObject*> self, + MutableHandle<UniquePtr<ExportNameVector>> exports) { + MOZ_ASSERT(!self->namespace_()); + + Rooted<UniquePtr<IndirectBindingMap>> bindings(cx); + bindings = cx->make_unique<IndirectBindingMap>(); + 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<ModuleObject*> self) { + Rooted<ModuleEnvironmentObject*> env( + cx, ModuleEnvironmentObject::create(cx, self)); + if (!env) { + return false; + } + + 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<ModuleRequestVector> output) const { + if (!output.reserve(moduleRequests.length())) { + ReportOutOfMemory(cx); + return false; + } + + Rooted<ModuleRequestObject*> 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<ArrayObject*> assertionArray(cx); + uint32_t numberOfAssertions = request.assertions.length(); + if (numberOfAssertions > 0) { + assertionArray = NewDenseFullyAllocatedArray(cx, numberOfAssertions); + if (!assertionArray) { + return nullptr; + } + assertionArray->ensureDenseInitializedLength(0, numberOfAssertions); + + Rooted<PlainObject*> 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<JSAtom*> 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<ModuleRequestVector> moduleRequests, + MutableHandle<ImportEntryVector> output) const { + if (!output.reserve(importEntries.length())) { + ReportOutOfMemory(cx); + return false; + } + + for (const StencilModuleEntry& entry : importEntries) { + Rooted<ModuleRequestObject*> moduleRequest(cx); + moduleRequest = moduleRequests[entry.moduleRequest.value()].get(); + MOZ_ASSERT(moduleRequest); + + Rooted<JSAtom*> localName(cx); + if (entry.localName) { + localName = atomCache.getExistingAtomAt(cx, entry.localName); + MOZ_ASSERT(localName); + } + + Rooted<JSAtom*> 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<ModuleRequestVector> moduleRequests, + const frontend::StencilModuleMetadata::EntryVector& input, + MutableHandle<ExportEntryVector> output) const { + if (!output.reserve(output.length() + input.length())) { + ReportOutOfMemory(cx); + return false; + } + + for (const frontend::StencilModuleEntry& entry : input) { + Rooted<JSAtom*> exportName(cx); + if (entry.exportName) { + exportName = atomCache.getExistingAtomAt(cx, entry.exportName); + MOZ_ASSERT(exportName); + } + + Rooted<ModuleRequestObject*> moduleRequestObject(cx); + if (entry.moduleRequest) { + moduleRequestObject = moduleRequests[entry.moduleRequest.value()].get(); + MOZ_ASSERT(moduleRequestObject); + } + + Rooted<JSAtom*> localName(cx); + if (entry.localName) { + localName = atomCache.getExistingAtomAt(cx, entry.localName); + MOZ_ASSERT(localName); + } + + Rooted<JSAtom*> 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<ModuleRequestVector> moduleRequests, + MutableHandle<RequestedModuleVector> output) const { + if (!output.reserve(requestedModules.length())) { + ReportOutOfMemory(cx); + return false; + } + + for (const frontend::StencilModuleEntry& entry : requestedModules) { + Rooted<ModuleRequestObject*> 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<ModuleObject*> module) const { + Rooted<ModuleRequestVector> moduleRequestsVector(cx); + if (!createModuleRequestObjects(cx, atomCache, &moduleRequestsVector)) { + return false; + } + + Rooted<RequestedModuleVector> requestedModulesVector(cx); + if (!createRequestedModules(cx, atomCache, moduleRequestsVector, + &requestedModulesVector)) { + return false; + } + + Rooted<ImportEntryVector> importEntriesVector(cx); + if (!createImportEntries(cx, atomCache, moduleRequestsVector, + &importEntriesVector)) { + return false; + } + + Rooted<ExportEntryVector> exportEntriesVector(cx); + if (!createExportEntries(cx, atomCache, moduleRequestsVector, + localExportEntries, &exportEntriesVector)) { + return false; + } + + Rooted<ExportEntryVector> indirectExportEntriesVector(cx); + if (!createExportEntries(cx, atomCache, moduleRequestsVector, + indirectExportEntries, &exportEntriesVector)) { + return false; + } + + Rooted<ExportEntryVector> starExportEntriesVector(cx); + if (!createExportEntries(cx, atomCache, moduleRequestsVector, + starExportEntries, &exportEntriesVector)) { + return false; + } + + // Copy the vector of declarations to the ModuleObject. + auto functionDeclsCopy = MakeUnique<FunctionDeclarationVector>(); + if (!functionDeclsCopy || !functionDeclsCopy->appendAll(functionDecls)) { + js::ReportOutOfMemory(fc); + return false; + } + module->initFunctionDeclarations(std::move(functionDeclsCopy)); + + Rooted<ListObject*> 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(JS::ImportAssertion supportedAssertion, + frontend::TaggedParserAtomIndex key) { + if (!key.isWellKnownAtomId()) { + return false; + } + + bool result = false; + + switch (supportedAssertion) { + case JS::ImportAssertion::Type: + result = key.toWellKnownAtomId() == WellKnownAtomId::type; + break; + } + + return result; +} + +bool ModuleBuilder::processAssertions(frontend::StencilModuleRequest& request, + frontend::ListNode* assertionList) { + using namespace js::frontend; + + for (ParseNode* assertionItem : assertionList->contents()) { + BinaryNode* assertion = &assertionItem->as<BinaryNode>(); + MOZ_ASSERT(assertion->isKind(ParseNodeKind::ImportAssertion)); + + auto key = assertion->left()->as<NameNode>().atom(); + auto value = assertion->right()->as<NameNode>().atom(); + + for (JS::ImportAssertion assertion : fc_->getSupportedImportAssertions()) { + if (isAssertionSupported(assertion, 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<ListNode>(); + MOZ_ASSERT(specList->isKind(ParseNodeKind::ImportSpecList)); + + auto* moduleRequest = &importNode->right()->as<BinaryNode>(); + MOZ_ASSERT(moduleRequest->isKind(ParseNodeKind::ImportModuleRequest)); + + auto* moduleSpec = &moduleRequest->left()->as<NameNode>(); + MOZ_ASSERT(moduleSpec->isKind(ParseNodeKind::StringExpr)); + + auto* assertionList = &moduleRequest->right()->as<ListNode>(); + MOZ_ASSERT(assertionList->isKind(ParseNodeKind::ImportAssertionList)); + + 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; + uint32_t column; + eitherParser_.computeLineAndColumn(item->pn_pos.begin, &line, &column); + + StencilModuleEntry entry; + TaggedParserAtomIndex localName; + if (item->isKind(ParseNodeKind::ImportSpec)) { + auto* spec = &item->as<BinaryNode>(); + + auto* importNameNode = &spec->left()->as<NameNode>(); + auto* localNameNode = &spec->right()->as<NameNode>(); + + auto importName = importNameNode->atom(); + localName = localNameNode->atom(); + + markUsedByStencil(localName); + markUsedByStencil(importName); + entry = StencilModuleEntry::importEntry(moduleRequestIndex, localName, + importName, line, column); + } else { + MOZ_ASSERT(item->isKind(ParseNodeKind::ImportNamespaceSpec)); + auto* spec = &item->as<UnaryNode>(); + + auto* localNameNode = &spec->kid()->as<NameNode>(); + + localName = localNameNode->atom(); + + markUsedByStencil(localName); + entry = StencilModuleEntry::importNamespaceEntry(moduleRequestIndex, + localName, line, 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<BinaryNode>().left() + : exportNode->as<UnaryNode>().kid(); + + if (isDefault && exportNode->as<BinaryNode>().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<ListNode>().contents()) { + BinaryNode* spec = &item->as<BinaryNode>(); + MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportSpec)); + + NameNode* localNameNode = &spec->left()->as<NameNode>(); + NameNode* exportNameNode = &spec->right()->as<NameNode>(); + + auto localName = localNameNode->atom(); + auto exportName = exportNameNode->atom(); + + if (!appendExportEntry(exportName, localName, spec)) { + return false; + } + } + break; + } + + case ParseNodeKind::ClassDecl: { + const ClassNode& cls = kid->as<ClassNode>(); + 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<ListNode>().contents()) { + if (binding->isKind(ParseNodeKind::AssignExpr)) { + binding = binding->as<AssignmentNode>().left(); + } else { + MOZ_ASSERT(binding->isKind(ParseNodeKind::Name)); + } + + if (binding->isKind(ParseNodeKind::Name)) { + auto localName = binding->as<NameNode>().atom(); + auto exportName = isDefault + ? TaggedParserAtomIndex::WellKnown::default_() + : localName; + if (!appendExportEntry(exportName, localName)) { + return false; + } + } else if (binding->isKind(ParseNodeKind::ArrayExpr)) { + if (!processExportArrayBinding(&binding->as<ListNode>())) { + return false; + } + } else { + MOZ_ASSERT(binding->isKind(ParseNodeKind::ObjectExpr)); + if (!processExportObjectBinding(&binding->as<ListNode>())) { + return false; + } + } + } + break; + } + + case ParseNodeKind::Function: { + FunctionBox* box = kid->as<FunctionNode>().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<NameNode>().atom(); + return appendExportEntry(name, name); + } + + if (binding->isKind(ParseNodeKind::ArrayExpr)) { + return processExportArrayBinding(&binding->as<ListNode>()); + } + + MOZ_ASSERT(binding->isKind(ParseNodeKind::ObjectExpr)); + return processExportObjectBinding(&binding->as<ListNode>()); +} + +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<UnaryNode>().kid(); + } else if (node->isKind(ParseNodeKind::AssignExpr)) { + node = node->as<AssignmentNode>().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<UnaryNode>().kid(); + } else { + if (node->isKind(ParseNodeKind::MutateProto)) { + target = node->as<UnaryNode>().kid(); + } else { + target = node->as<BinaryNode>().right(); + } + + if (target->isKind(ParseNodeKind::AssignExpr)) { + target = target->as<AssignmentNode>().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<ListNode>(); + MOZ_ASSERT(specList->isKind(ParseNodeKind::ExportSpecList)); + + auto* moduleRequest = &exportNode->right()->as<BinaryNode>(); + MOZ_ASSERT(moduleRequest->isKind(ParseNodeKind::ImportModuleRequest)); + + auto* moduleSpec = &moduleRequest->left()->as<NameNode>(); + MOZ_ASSERT(moduleSpec->isKind(ParseNodeKind::StringExpr)); + + auto* assertionList = &moduleRequest->right()->as<ListNode>(); + MOZ_ASSERT(assertionList->isKind(ParseNodeKind::ImportAssertionList)); + + 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; + uint32_t column; + eitherParser_.computeLineAndColumn(spec->pn_pos.begin, &line, &column); + + StencilModuleEntry entry; + TaggedParserAtomIndex exportName; + if (spec->isKind(ParseNodeKind::ExportSpec)) { + auto* importNameNode = &spec->as<BinaryNode>().left()->as<NameNode>(); + auto* exportNameNode = &spec->as<BinaryNode>().right()->as<NameNode>(); + + auto importName = importNameNode->atom(); + exportName = exportNameNode->atom(); + + markUsedByStencil(importName); + markUsedByStencil(exportName); + entry = StencilModuleEntry::exportFromEntry( + moduleRequestIndex, importName, exportName, line, column); + } else if (spec->isKind(ParseNodeKind::ExportNamespaceSpec)) { + auto* exportNameNode = &spec->as<UnaryNode>().kid()->as<NameNode>(); + + exportName = exportNameNode->atom(); + + markUsedByStencil(exportName); + entry = StencilModuleEntry::exportNamespaceFromEntry( + moduleRequestIndex, exportName, line, column); + } else { + MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportBatchSpecStmt)); + + entry = StencilModuleEntry::exportBatchFromEntry(moduleRequestIndex, line, + 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; + uint32_t column = 0; + if (node) { + eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column); + } + + markUsedByStencil(localName); + markUsedByStencil(exportName); + auto entry = frontend::StencilModuleEntry::exportAsEntry( + localName, exportName, line, 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; + uint32_t column; + eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column); + + auto entry = frontend::StencilModuleEntry::requestedModule(moduleRequest, + line, 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<ModuleObject*> module = moduleArg.as<ModuleObject>(); + 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<ModuleObject>()) { + JS_ReportErrorASCII(cx, "Module resolve hook did not return Module object"); + return nullptr; + } + + return &result->as<ModuleObject>(); +} + +bool ModuleObject::topLevelCapabilityResolve(JSContext* cx, + Handle<ModuleObject*> module) { + RootedValue rval(cx); + Rooted<PromiseObject*> promise( + cx, &module->topLevelCapability()->as<PromiseObject>()); + return AsyncFunctionReturned(cx, promise, rval); +} + +bool ModuleObject::topLevelCapabilityReject(JSContext* cx, + Handle<ModuleObject*> module, + HandleValue error) { + Rooted<PromiseObject*> promise( + cx, &module->topLevelCapability()->as<PromiseObject>()); + 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<ArrayObject*> 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 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<ArrayObject*> assertionArray( + cx, NewDenseFullyAllocatedArray(cx, numberOfAssertions)); + if (!assertionArray) { + return false; + } + assertionArray->ensureDenseInitializedLength(0, numberOfAssertions); + + // Step 10.d.iv. Let supportedAssertions be + // !HostGetSupportedImportAssertions(). + const JS::ImportAssertionVector& supportedAssertions = + cx->runtime()->supportedImportAssertions; + + 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. + for (JS::ImportAssertion assertion : supportedAssertions) { + bool supported = false; + switch (assertion) { + case JS::ImportAssertion::Type: { + supported = key.toAtom() == cx->names().type; + } break; + } + + if (supported) { + Rooted<PlainObject*> 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; +} + +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<PromiseObject*> promise = promiseObject.as<PromiseObject>(); + + 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; + } + + RootedValue referencingPrivate(cx, script->sourceObject()->getPrivate()); + cx->runtime()->addRefScriptPrivate(referencingPrivate); + + Rooted<JSAtom*> specifierAtom(cx, AtomizeString(cx, specifier)); + if (!specifierAtom) { + if (!RejectPromiseWithPendingError(cx, promise)) { + return nullptr; + } + return promise; + } + + Rooted<ArrayObject*> 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; + } + + if (!importHook(cx, referencingPrivate, moduleRequest, promise)) { + cx->runtime()->releaseScriptPrivate(referencingPrivate); + + // 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); +} + +// 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()); + + // This is a hack to allow us to have the 2 extra variables needed + // for FinishDynamicModuleImport in the resolve callback. + Rooted<ListObject*> resolvedModuleParams(cx, + ExtraFromHandler<ListObject>(args)); + MOZ_ASSERT(resolvedModuleParams->length() == 2); + RootedValue referencingPrivate(cx, resolvedModuleParams->get(0)); + + Rooted<JSAtom*> specifier( + cx, AtomizeString(cx, resolvedModuleParams->get(1).toString())); + if (!specifier) { + return false; + } + + Rooted<PromiseObject*> promise(cx, TargetFromHandler<PromiseObject>(args)); + + auto releasePrivate = mozilla::MakeScopeExit( + [&] { cx->runtime()->releaseScriptPrivate(referencingPrivate); }); + + 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<ModuleObject*> module(cx, &result->as<ModuleObject>()); + 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(module->getCycleRoot() + ->topLevelCapability() + ->as<PromiseObject>() + .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); + + RootedValue referencingPrivate(cx, ExtraValueFromHandler(args)); + Rooted<PromiseObject*> promise(cx, TargetFromHandler<PromiseObject>(args)); + + auto releasePrivate = mozilla::MakeScopeExit( + [&] { cx->runtime()->releaseScriptPrivate(referencingPrivate); }); + + args.rval().setUndefined(); + return PromiseObject::reject(cx, promise, error); +}; + +bool FinishDynamicModuleImport_impl(JSContext* cx, + HandleObject evaluationPromise, + HandleValue referencingPrivate, + HandleObject moduleRequest, + HandleObject promiseArg) { + Rooted<ListObject*> resolutionArgs(cx, ListObject::create(cx)); + if (!resolutionArgs->append(cx, referencingPrivate)) { + return false; + } + Rooted<Value> stringValue( + cx, StringValue(moduleRequest->as<ModuleRequestObject>().specifier())); + if (!resolutionArgs->append(cx, stringValue)) { + return false; + } + + Rooted<Value> resolutionArgsValue(cx, ObjectValue(*resolutionArgs)); + + RootedFunction onResolved( + cx, NewHandlerWithExtraValue(cx, OnResolvedDynamicModule, promiseArg, + resolutionArgsValue)); + if (!onResolved) { + return false; + } + + RootedFunction onRejected( + cx, NewHandlerWithExtraValue(cx, OnRejectedDynamicModule, promiseArg, + referencingPrivate)); + if (!onRejected) { + return false; + } + + return JS::AddPromiseReactionsIgnoringUnhandledRejection( + cx, evaluationPromise, onResolved, onRejected); +} + +bool js::FinishDynamicModuleImport(JSContext* cx, + HandleObject evaluationPromise, + HandleValue referencingPrivate, + HandleObject moduleRequest, + HandleObject promiseArg) { + // 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. + auto releasePrivate = mozilla::MakeScopeExit( + [&] { cx->runtime()->releaseScriptPrivate(referencingPrivate); }); + + if (!evaluationPromise || !moduleRequest) { + Handle<PromiseObject*> promise = promiseArg.as<PromiseObject>(); + return RejectPromiseWithPendingError(cx, promise); + } + + if (!FinishDynamicModuleImport_impl(cx, evaluationPromise, referencingPrivate, + moduleRequest, promiseArg)) { + return false; + } + + releasePrivate.release(); + return true; +} |