/* 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 <cstdint>
// UINT32_MAX
#include <stddef.h>
// size_t
#include <stdint.h>
// 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<JSAtom*> key_;
const HeapPtr<JSString*> value_;
public:
ImportAttribute(Handle<JSAtom*> key, Handle<JSString*> value);
JSAtom* key()
const {
return key_; }
JSString* value()
const {
return value_; }
void trace(JSTracer* trc);
};
using ImportAttributeVector = GCVector<ImportAttribute,
0, SystemAllocPolicy>;
// https://tc39.es/proposal-source-phase-imports/#sec-modulerequest-record
enum class ImportPhase : uint8_t { Source, 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<JSAtom*> specifier,
Handle<ImportAttributeVector> maybeAttributes,
ImportPhase phase = ImportPhase::Evaluation);
[[nodiscard]]
static ModuleRequestObject* create(
JSContext* cx, Handle<JSAtom*> 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<JSAtom*> key);
bool hasFirstUnsupportedAttributeKey()
const;
JSAtom* getFirstUnsupportedAttributeKey()
const;
};
using ModuleRequestVector =
GCVector<HeapPtr<ModuleRequestObject*>,
0, SystemAllocPolicy>;
class ImportEntry {
const HeapPtr<ModuleRequestObject*> moduleRequest_;
const HeapPtr<JSAtom*> importName_;
const HeapPtr<JSAtom*> localName_;
// Line number (1-origin).
const uint32_t lineNumber_;
// Column number in UTF-16 code units.
const JS::ColumnNumberOneOrigin columnNumber_;
public:
ImportEntry(Handle<ModuleRequestObject*> moduleRequest,
Handle<JSAtom*> maybeImportName, Handle<JSAtom*> 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<ImportEntry,
0, SystemAllocPolicy>;
class ExportEntry {
const HeapPtr<JSAtom*> exportName_;
const HeapPtr<ModuleRequestObject*> moduleRequest_;
const HeapPtr<JSAtom*> importName_;
const HeapPtr<JSAtom*> localName_;
// Line number (1-origin).
const uint32_t lineNumber_;
// Column number in UTF-16 code units.
const JS::ColumnNumberOneOrigin columnNumber_;
public:
ExportEntry(Handle<JSAtom*> maybeExportName,
Handle<ModuleRequestObject*> maybeModuleRequest,
Handle<JSAtom*> maybeImportName, Handle<JSAtom*> 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<ExportEntry,
0, SystemAllocPolicy>;
class RequestedModule {
const HeapPtr<ModuleRequestObject*> moduleRequest_;
// Line number (1-origin).
const uint32_t lineNumber_;
// Column number in UTF-16 code units.
const JS::ColumnNumberOneOrigin columnNumber_;
public:
RequestedModule(Handle<ModuleRequestObject*> 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<RequestedModule,
0, SystemAllocPolicy>;
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<ModuleObject*> module,
Handle<JSAtom*> bindingName);
ModuleObject* module()
const;
JSAtom* bindingName()
const;
};
class IndirectBindingMap {
public:
void trace(JSTracer* trc);
bool put(JSContext* cx, HandleId name,
Handle<ModuleEnvironmentObject*> 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<PropertyInfo>* propOut)
const;
template <
typename Func>
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<ModuleEnvironmentObject*> environment;
#ifdef DEBUG
HeapPtr<jsid> targetName;
#endif
PropertyInfo prop;
};
using Map = mozilla::HashMap<PreBarriered<jsid>, Binding,
mozilla::DefaultHasher<PreBarriered<jsid>>,
CellAllocPolicy>;
mozilla::Maybe<Map> 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<HeapPtr<JSAtom*>,
0, SystemAllocPolicy>;
class ModuleNamespaceObject :
public ProxyObject {
public:
enum ModuleNamespaceSlot { ExportsSlot =
0, BindingsSlot };
static bool isInstance(HandleValue value);
static ModuleNamespaceObject* create(
JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<UniquePtr<ExportNameVector>> exports,
MutableHandle<UniquePtr<IndirectBindingMap>> bindings);
ModuleObject& module();
const ExportNameVector& exports()
const;
IndirectBindingMap& bindings();
bool addBinding(JSContext* cx, Handle<JSAtom*> exportedName,
Handle<ModuleObject*> targetModule,
Handle<JSAtom*> targetName);
private:
struct ProxyHandler :
public BaseProxyHandler {
constexpr ProxyHandler() : BaseProxyHandler(&family,
false) {}
bool getOwnPropertyDescriptor(
JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc)
const override;
bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
Handle<PropertyDescriptor> 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;
};
// https://tc39.es/proposal-source-phase-imports/#sec-properties-of-the-%abstractmodulesource%-intrinsic-object
class AbstractModuleSourceObject :
public NativeObject {
public:
static const JSClass class_;
};
// 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<JSObject*>, HeapPtr<ModuleObject*>,
StableCellHasher<HeapPtr<JSObject*>>, 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
// Module Source object for source phase imports. Otherwise `undefined`.
ModuleSourceSlot,
SlotCount
};
static const JSClass class_;
static bool isInstance(HandleValue value);
static ModuleObject* create(JSContext* cx);
static ModuleObject* createSynthetic(
JSContext* cx, MutableHandle<ExportNameVector> exportNames);
// Initialize the slots on this object that are dependent on the script.
void initScriptSlots(HandleScript script);
void initModuleSourceSlot(HandleObject moduleSource);
void initScriptSourceObject(ScriptSourceObject* sso);
void setInitialEnvironment(
Handle<ModuleEnvironmentObject*> initialEnvironment);
void initFunctionDeclarations(UniquePtr<FunctionDeclarationVector> decls);
void initImportExportData(
MutableHandle<RequestedModuleVector> requestedModules,
MutableHandle<ImportEntryVector> importEntries,
MutableHandle<ExportEntryVector> exportEntries, uint32_t localExportCount,
uint32_t indirectExportCount, uint32_t starExportCount);
static bool Freeze(JSContext* cx, Handle<ModuleObject*> self);
#ifdef DEBUG
static bool AssertFrozen(JSContext* cx, Handle<ModuleObject*> self);
#endif
JSScript* maybeScript()
const;
JSScript* script()
const;
const char* filename()
const;
ModuleEnvironmentObject& initialEnvironment()
const;
ModuleEnvironmentObject* environment()
const;
ModuleNamespaceObject* namespace_();
JSObject* moduleSource()
const;
bool isSourcePhaseModule()
const {
return moduleSource() != nullptr; }
ModuleStatus status()
const;
mozilla::Maybe<uint32_t> maybeDfsAncestorIndex()
const;
uint32_t dfsAncestorIndex()
const;
bool hadEvaluationError()
const;
Value maybeEvaluationError()
const;
Value evaluationError()
const;
JSObject* metaObject()
const;
ScriptSourceObject* scriptSourceObject()
const;
mozilla::Span<
const RequestedModule> requestedModules()
const;
mozilla::Span<
const ImportEntry> importEntries()
const;
mozilla::Span<
const ExportEntry> localExportEntries()
const;
mozilla::Span<
const ExportEntry> indirectExportEntries()
const;
mozilla::Span<
const ExportEntry> 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<ModuleObject*> module);
bool hasTopLevelAwait()
const;
void setEvaluationError(HandleValue newValue);
void setPendingAsyncDependencies(uint32_t newValue);
void setInitialTopLevelCapability(Handle<PromiseObject*> capability);
bool hasTopLevelCapability()
const;
PromiseObject* maybeTopLevelCapability()
const;
PromiseObject* topLevelCapability()
const;
ListObject* asyncParentModules()
const;
mozilla::Maybe<uint32_t> maybePendingAsyncDependencies()
const;
uint32_t pendingAsyncDependencies()
const;
AsyncEvaluationOrder& asyncEvaluationOrder();
AsyncEvaluationOrder
const& asyncEvaluationOrder()
const;
void setCycleRoot(ModuleObject* cycleRoot);
ModuleObject* getCycleRoot()
const;
bool hasCycleRoot()
const;
bool hasCyclicModuleFields()
const;
bool hasSyntheticModuleFields()
const;
LoadedModuleMap& loadedModules();
const LoadedModuleMap& loadedModules()
const;
static void onTopLevelEvaluationFinished(ModuleObject* module);
static bool appendAsyncParentModule(JSContext* cx, Handle<ModuleObject*> self,
Handle<ModuleObject*> parent);
[[nodiscard]]
static bool topLevelCapabilityResolve(
JSContext* cx, Handle<ModuleObject*> module);
[[nodiscard]]
static bool topLevelCapabilityReject(
JSContext* cx, Handle<ModuleObject*> module, HandleValue error);
void setMetaObject(JSObject* obj);
static bool instantiateFunctionDeclarations(JSContext* cx,
Handle<ModuleObject*> self);
static bool execute(JSContext* cx, Handle<ModuleObject*> self);
static ModuleNamespaceObject* createNamespace(
JSContext* cx, Handle<ModuleObject*> self,
MutableHandle<UniquePtr<ExportNameVector>> exports);
void clearNamespaceOnFailure();
static bool createEnvironment(JSContext* cx, Handle<ModuleObject*> self);
static bool createSyntheticEnvironment(JSContext* cx,
Handle<ModuleObject*> self,
JS::HandleVector<Value> values);
static bool createWasmEnvironment(JSContext* cx, Handle<ModuleObject*> self);
void initAsyncSlots(JSContext* cx,
bool hasTopLevelAwait,
Handle<ListObject*> 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<HeapPtr<JSObject*>, StableCellHasher<HeapPtr<JSObject*>>,
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<Value> hostDefined);
[[nodiscard]]
static GraphLoadingStateRecordObject* create(
JSContext* cx,
bool isLoading, uint32_t pendingModulesCount,
Handle<PromiseObject*> promise, Handle<Value> 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<JS::Value> hostDefined);
bool rejected(JSContext* cx, JS::Handle<JS::Value> hostDefined,
Handle<JS::Value> error);
};
JSObject* GetOrCreateModuleMetaObject(JSContext* cx, HandleObject module);
JSObject* StartDynamicModuleImport(JSContext* cx, HandleScript script,
HandleValue specifier, HandleValue options,
ImportPhase phase);
}
// namespace js
template <>
inline bool JSObject::is<js::ModuleNamespaceObject>()
const {
return js::IsDerivedProxyObject(
this,
&js::ModuleNamespaceObject::proxyHandler);
}
#endif /* builtin_ModuleObject_h */