/* -*- 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/. */ #ifndef builtin_ModuleObject_h #define builtin_ModuleObject_h #include "mozilla/HashTable.h" // mozilla::{HashMap, DefaultHasher} #include "mozilla/Maybe.h" // mozilla::Maybe #include "mozilla/Span.h" #include // UINT32_MAX #include // size_t #include // int32_t, uint32_t #include "gc/Barrier.h" // HeapPtr #include "gc/ZoneAllocator.h" // CellAllocPolicy #include "js/Class.h" // JSClass, ObjectOpResult #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin #include "js/GCVector.h" #include "js/Id.h" // jsid #include "js/Modules.h" #include "js/Proxy.h" // BaseProxyHandler #include "js/RootingAPI.h" // Rooted, Handle, MutableHandle #include "js/TypeDecls.h" // HandleValue, HandleId, HandleObject, HandleScript, MutableHandleValue, MutableHandleIdVector, MutableHandleObject #include "js/UniquePtr.h" // UniquePtr #include "vm/JSObject.h" // JSObject #include "vm/NativeObject.h" // NativeObject #include "vm/ProxyObject.h" // ProxyObject #include "vm/SharedStencil.h" // FunctionDeclarationVector class JSAtom; class JSScript; class JSTracer; namespace JS { class PropertyDescriptor; class Value; } // namespace JS namespace js { class ArrayObject; class CyclicModuleFields; class SyntheticModuleFields; class ListObject; class ModuleEnvironmentObject; class ModuleObject; class PromiseObject; class ScriptSourceObject; class ImportAttribute { const HeapPtr key_; const HeapPtr value_; public: ImportAttribute(Handle key, Handle value); JSAtom* key() const { return key_; } JSString* value() const { return value_; } void trace(JSTracer* trc); }; using ImportAttributeVector = GCVector; enum class ImportPhase : uint8_t { Evaluation, Limit }; class ModuleRequestObject : public NativeObject { public: enum { SpecifierSlot = 0, FirstUnsupportedAttributeKeySlot, ModuleTypeSlot, PhaseSlot, SlotCount }; static const JSClass class_; static bool isInstance(HandleValue value); [[nodiscard]] static ModuleRequestObject* create( JSContext* cx, Handle specifier, Handle maybeAttributes, ImportPhase phase = ImportPhase::Evaluation); [[nodiscard]] static ModuleRequestObject* create( JSContext* cx, Handle specifier, JS::ModuleType moduleType, ImportPhase phase = ImportPhase::Evaluation); JSAtom* specifier() const; JS::ModuleType moduleType() const; ImportPhase phase() const; // We process import attributes earlier in the process, but according to the // spec, we should error during module evaluation if we encounter an // unsupported attribute. We want to generate a nice error message, so we need // to keep track of the first unsupported key we encounter. void setFirstUnsupportedAttributeKey(Handle key); bool hasFirstUnsupportedAttributeKey() const; JSAtom* getFirstUnsupportedAttributeKey() const; }; using ModuleRequestVector = GCVector, 0, SystemAllocPolicy>; class ImportEntry { const HeapPtr moduleRequest_; const HeapPtr importName_; const HeapPtr localName_; // Line number (1-origin). const uint32_t lineNumber_; // Column number in UTF-16 code units. const JS::ColumnNumberOneOrigin columnNumber_; public: ImportEntry(Handle moduleRequest, Handle maybeImportName, Handle localName, uint32_t lineNumber, JS::ColumnNumberOneOrigin columnNumber); ModuleRequestObject* moduleRequest() const { return moduleRequest_; } JSAtom* importName() const { return importName_; } JSAtom* localName() const { return localName_; } uint32_t lineNumber() const { return lineNumber_; } JS::ColumnNumberOneOrigin columnNumber() const { return columnNumber_; } void trace(JSTracer* trc); }; using ImportEntryVector = GCVector; class ExportEntry { const HeapPtr exportName_; const HeapPtr moduleRequest_; const HeapPtr importName_; const HeapPtr localName_; // Line number (1-origin). const uint32_t lineNumber_; // Column number in UTF-16 code units. const JS::ColumnNumberOneOrigin columnNumber_; public: ExportEntry(Handle maybeExportName, Handle maybeModuleRequest, Handle maybeImportName, Handle maybeLocalName, uint32_t lineNumber, JS::ColumnNumberOneOrigin columnNumber); JSAtom* exportName() const { return exportName_; } ModuleRequestObject* moduleRequest() const { return moduleRequest_; } JSAtom* importName() const { return importName_; } JSAtom* localName() const { return localName_; } uint32_t lineNumber() const { return lineNumber_; } JS::ColumnNumberOneOrigin columnNumber() const { return columnNumber_; } void trace(JSTracer* trc); }; using ExportEntryVector = GCVector; class RequestedModule { const HeapPtr moduleRequest_; // Line number (1-origin). const uint32_t lineNumber_; // Column number in UTF-16 code units. const JS::ColumnNumberOneOrigin columnNumber_; public: RequestedModule(Handle moduleRequest, uint32_t lineNumber, JS::ColumnNumberOneOrigin columnNumber); ModuleRequestObject* moduleRequest() const { return moduleRequest_; } uint32_t lineNumber() const { return lineNumber_; } JS::ColumnNumberOneOrigin columnNumber() const { return columnNumber_; } void trace(JSTracer* trc); }; using RequestedModuleVector = GCVector; class ResolvedBindingObject : public NativeObject { public: enum { ModuleSlot = 0, BindingNameSlot, SlotCount }; static const JSClass class_; static bool isInstance(HandleValue value); static ResolvedBindingObject* create(JSContext* cx, Handle module, Handle bindingName); ModuleObject* module() const; JSAtom* bindingName() const; }; class IndirectBindingMap { public: void trace(JSTracer* trc); bool put(JSContext* cx, HandleId name, Handle environment, HandleId targetName); size_t count() const { return map_ ? map_->count() : 0; } bool has(jsid name) const { return map_ ? map_->has(name) : false; } bool lookup(jsid name, ModuleEnvironmentObject** envOut, mozilla::Maybe* propOut) const; template void forEachExportedName(Func func) const { if (!map_) { return; } for (auto iter = map_->iter(); !iter.done(); iter.next()) { func(iter.get().key()); } } private: struct Binding { Binding(ModuleEnvironmentObject* environment, jsid targetName, PropertyInfo prop); HeapPtr environment; #ifdef DEBUG HeapPtr targetName; #endif PropertyInfo prop; }; using Map = mozilla::HashMap, Binding, mozilla::DefaultHasher>, CellAllocPolicy>; mozilla::Maybe map_; }; // Vector of atoms representing the names exported from a module namespace. // // This is used both on the stack and in the heap. using ExportNameVector = GCVector, 0, SystemAllocPolicy>; class ModuleNamespaceObject : public ProxyObject { public: enum ModuleNamespaceSlot { ExportsSlot = 0, BindingsSlot }; static bool isInstance(HandleValue value); static ModuleNamespaceObject* create( JSContext* cx, Handle module, MutableHandle> exports, MutableHandle> bindings); ModuleObject& module(); const ExportNameVector& exports() const; IndirectBindingMap& bindings(); bool addBinding(JSContext* cx, Handle exportedName, Handle targetModule, Handle targetName); private: struct ProxyHandler : public BaseProxyHandler { constexpr ProxyHandler() : BaseProxyHandler(&family, false) {} bool getOwnPropertyDescriptor( JSContext* cx, HandleObject proxy, HandleId id, MutableHandle> desc) const override; bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle desc, ObjectOpResult& result) const override; bool ownPropertyKeys(JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const override; bool delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) const override; bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const override; bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result) const override; bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary, MutableHandleObject protop) const override; bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const override; bool preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const override; bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override; bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override; bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, MutableHandleValue vp) const override; bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const override; void trace(JSTracer* trc, JSObject* proxy) const override; void finalize(JS::GCContext* gcx, JSObject* proxy) const override; static const char family; }; bool hasBindings() const; bool hasExports() const; ExportNameVector& mutableExports(); public: static const ProxyHandler proxyHandler; }; // Value types of [[Status]] in a Cyclic Module Record // https://tc39.es/ecma262/#table-cyclic-module-fields enum class ModuleStatus : int8_t { New, Unlinked, Linking, Linked, Evaluating, EvaluatingAsync, Evaluated, // Sub-state of Evaluated with error value set. // // This is not returned from ModuleObject::status(); use hadEvaluationError() // to check this. Evaluated_Error }; // Special values for CyclicModuleFields' asyncEvaluationOrderSlot field, // which represents the AsyncEvaluationOrder field of cyclic module records. // // AsyncEvaluationOrder can have three states: // - a positive integer, represented by values <= // ASYNC_EVALUATING_POST_ORDER_MAX_VALUE // - ~unset~, represented by ASYNC_EVALUATING_POST_ORDER_UNSET // - ~done~, represented by ASYNC_EVALUATING_POST_ORDER_DONE // // See https://tc39.es/ecma262/#sec-cyclic-module-records for field defintion. // See https://tc39.es/ecma262/#sec-async-module-execution-fulfilled for sort // requirement. // Value that the field is initially set to. constexpr uint32_t ASYNC_EVALUATING_POST_ORDER_UNSET = UINT32_MAX; // Value that the field is set to after being cleared. constexpr uint32_t ASYNC_EVALUATING_POST_ORDER_DONE = UINT32_MAX - 1; constexpr uint32_t ASYNC_EVALUATING_POST_ORDER_MAX_VALUE = UINT32_MAX - 2; class AsyncEvaluationOrder { private: uint32_t value = ASYNC_EVALUATING_POST_ORDER_UNSET; public: bool isUnset() const; bool isInteger() const; bool isDone() const; uint32_t get() const; void set(JSRuntime* rt); void setDone(JSRuntime* rt); }; // The map used by [[LoadedModules]] in Realm Record Fields, Script Record // Fields, and additional fields of Cyclic Module Records. // https://tc39.es/ecma262/#table-realm-record-fields // https://tc39.es/ecma262/#table-script-records // https://tc39.es/ecma262/#table-cyclic-module-fields // // For Import attributes proposal, this map maps from ModuleRequest records to // Module records. // https://tc39.es/proposal-import-attributes/#sec-cyclic-module-records // // TODO: // Bug 1968874 : Implement [[LoadedModules]] in Realm Records and Script Records using LoadedModuleMap = GCHashMap, HeapPtr, StableCellHasher>, SystemAllocPolicy>; // Currently, the ModuleObject class is used to represent both the Source Text // Module Record and the Synthetic Module Record. Ideally, this is something // that should be refactored to follow the same hierarchy as in the spec. // TODO: See Bug 1880519. class ModuleObject : public NativeObject { public: // Module fields including those for AbstractModuleRecords described by: // https://tc39.es/ecma262/#sec-abstract-module-records enum ModuleSlot { ScriptSlot = 0, EnvironmentSlot, NamespaceSlot, CyclicModuleFieldsSlot, // `SyntheticModuleFields` if a synthetic module. Otherwise `undefined`. SyntheticModuleFieldsSlot, #ifdef DEBUG PreloadSlot, #endif SlotCount }; static const JSClass class_; static bool isInstance(HandleValue value); static ModuleObject* create(JSContext* cx); static ModuleObject* createSynthetic( JSContext* cx, MutableHandle exportNames); // Initialize the slots on this object that are dependent on the script. void initScriptSlots(HandleScript script); void setInitialEnvironment( Handle initialEnvironment); void initFunctionDeclarations(UniquePtr decls); void initImportExportData( MutableHandle requestedModules, MutableHandle importEntries, MutableHandle exportEntries, uint32_t localExportCount, uint32_t indirectExportCount, uint32_t starExportCount); static bool Freeze(JSContext* cx, Handle self); #ifdef DEBUG static bool AssertFrozen(JSContext* cx, Handle self); #endif JSScript* maybeScript() const; JSScript* script() const; const char* filename() const; ModuleEnvironmentObject& initialEnvironment() const; ModuleEnvironmentObject* environment() const; ModuleNamespaceObject* namespace_(); ModuleStatus status() const; mozilla::Maybe maybeDfsAncestorIndex() const; uint32_t dfsAncestorIndex() const; bool hadEvaluationError() const; Value maybeEvaluationError() const; Value evaluationError() const; JSObject* metaObject() const; ScriptSourceObject* scriptSourceObject() const; mozilla::Span requestedModules() const; mozilla::Span importEntries() const; mozilla::Span localExportEntries() const; mozilla::Span indirectExportEntries() const; mozilla::Span starExportEntries() const; const ExportNameVector& syntheticExportNames() const; IndirectBindingMap& importBindings(); void setStatus(ModuleStatus newStatus); void setDfsAncestorIndex(uint32_t index); void clearDfsAncestorIndex(); static PromiseObject* createTopLevelCapability(JSContext* cx, Handle module); bool hasTopLevelAwait() const; void setEvaluationError(HandleValue newValue); void setPendingAsyncDependencies(uint32_t newValue); void setInitialTopLevelCapability(Handle capability); bool hasTopLevelCapability() const; PromiseObject* maybeTopLevelCapability() const; PromiseObject* topLevelCapability() const; ListObject* asyncParentModules() const; mozilla::Maybe maybePendingAsyncDependencies() const; uint32_t pendingAsyncDependencies() const; AsyncEvaluationOrder& asyncEvaluationOrder(); AsyncEvaluationOrder const& asyncEvaluationOrder() const; void setCycleRoot(ModuleObject* cycleRoot); ModuleObject* getCycleRoot() const; bool hasCyclicModuleFields() const; bool hasSyntheticModuleFields() const; LoadedModuleMap& loadedModules(); const LoadedModuleMap& loadedModules() const; static void onTopLevelEvaluationFinished(ModuleObject* module); static bool appendAsyncParentModule(JSContext* cx, Handle self, Handle parent); [[nodiscard]] static bool topLevelCapabilityResolve( JSContext* cx, Handle module); [[nodiscard]] static bool topLevelCapabilityReject( JSContext* cx, Handle module, HandleValue error); void setMetaObject(JSObject* obj); static bool instantiateFunctionDeclarations(JSContext* cx, Handle self); static bool execute(JSContext* cx, Handle self); static ModuleNamespaceObject* createNamespace( JSContext* cx, Handle self, MutableHandle> exports); static bool createEnvironment(JSContext* cx, Handle self); static bool createSyntheticEnvironment(JSContext* cx, Handle self, JS::HandleVector values); void initAsyncSlots(JSContext* cx, bool hasTopLevelAwait, Handle asyncParentModules); #ifdef DEBUG void setPreload(bool isPreload); bool isPreload() const; #endif private: static const JSClassOps classOps_; static void trace(JSTracer* trc, JSObject* obj); static void finalize(JS::GCContext* gcx, JSObject* obj); CyclicModuleFields* cyclicModuleFields(); const CyclicModuleFields* cyclicModuleFields() const; SyntheticModuleFields* syntheticModuleFields(); const SyntheticModuleFields* syntheticModuleFields() const; }; using VisitedModuleSet = GCHashSet, DefaultHasher>, SystemAllocPolicy>; // The fields of a GraphLoadingState Record, as described in: // https://tc39.es/ecma262/#graphloadingstate-record struct GraphLoadingStateRecord { GraphLoadingStateRecord() = default; GraphLoadingStateRecord(JS::LoadModuleResolvedCallback resolved, JS::LoadModuleRejectedCallback rejected); void trace(JSTracer* trc); // [[Visited]] : a List of Cyclic Module Records VisitedModuleSet visited; JS::LoadModuleResolvedCallback resolved; JS::LoadModuleRejectedCallback rejected; }; class GraphLoadingStateRecordObject : public NativeObject { public: enum { StateSlot = 0, PromiseSlot, IsLoadingSlot, PendingModulesCountSlot, HostDefinedSlot, SlotCount }; static const JSClass class_; static const JSClassOps classOps_; [[nodiscard]] static GraphLoadingStateRecordObject* create( JSContext* cx, bool isLoading, uint32_t pendingModulesCount, JS::LoadModuleResolvedCallback resolved, JS::LoadModuleRejectedCallback rejected, Handle hostDefined); [[nodiscard]] static GraphLoadingStateRecordObject* create( JSContext* cx, bool isLoading, uint32_t pendingModulesCount, Handle promise, Handle hostDefined); static void finalize(JS::GCContext* gcx, JSObject* obj); static void trace(JSTracer* trc, JSObject* obj); // [[PromiseCapability]] : a PromiseCapability Record PromiseObject* promise(); // [[IsLoading]] : a Boolean bool isLoading(); void setIsLoading(bool isLoading); // [[PendingModulesCount]] : a non-negative integer uint32_t pendingModulesCount(); void setPendingModulesCount(uint32_t count); VisitedModuleSet& visited(); Value hostDefined(); bool resolved(JSContext* cx, JS::Handle hostDefined); bool rejected(JSContext* cx, JS::Handle hostDefined, Handle error); }; JSObject* GetOrCreateModuleMetaObject(JSContext* cx, HandleObject module); JSObject* StartDynamicModuleImport(JSContext* cx, HandleScript script, HandleValue specifier, HandleValue options); #ifdef ENABLE_SOURCE_PHASE_IMPORTS JSObject* StartDynamicModuleImportSource(JSContext* cx, HandleScript script, HandleValue specifier); #endif } // namespace js template <> inline bool JSObject::is() const { return js::IsDerivedProxyObject(this, &js::ModuleNamespaceObject::proxyHandler); } #endif /* builtin_ModuleObject_h */