/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "builtin/SelfHostingDefines.h" #include "frontend/ParseNode.h" #include "frontend/SharedContext.h" #include "gc/Policy.h" #include "gc/Tracer.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" using namespace js; using namespace js::frontend; static_assert(MODULE_STATE_FAILED < MODULE_STATE_PARSED && MODULE_STATE_PARSED < MODULE_STATE_INSTANTIATED && MODULE_STATE_INSTANTIATED < MODULE_STATE_EVALUATED, "Module states are ordered incorrectly"); template static bool ModuleValueGetterImpl(JSContext* cx, const CallArgs& args) { args.rval().set(ValueGetter(&args.thisv().toObject().as())); return true; } template static bool ModuleValueGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod>(cx, args); } #define DEFINE_GETTER_FUNCTIONS(cls, name, slot) \ static Value \ cls##_##name##Value(const cls* obj) { \ return obj->getFixedSlot(cls::slot); \ } \ \ static bool \ cls##_##name##Getter(JSContext* cx, unsigned argc, Value* vp) \ { \ return ModuleValueGetter(cx, argc, vp); \ } #define DEFINE_ATOM_ACCESSOR_METHOD(cls, name) \ JSAtom* \ cls::name() const \ { \ Value value = cls##_##name##Value(this); \ return &value.toString()->asAtom(); \ } #define DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(cls, name) \ JSAtom* \ cls::name() const \ { \ Value value = cls##_##name##Value(this); \ if (value.isNull()) \ return nullptr; \ return &value.toString()->asAtom(); \ } /////////////////////////////////////////////////////////////////////////// // ImportEntryObject /* static */ const Class ImportEntryObject::class_ = { "ImportEntry", JSCLASS_HAS_RESERVED_SLOTS(ImportEntryObject::SlotCount) | JSCLASS_IS_ANONYMOUS }; DEFINE_GETTER_FUNCTIONS(ImportEntryObject, moduleRequest, ModuleRequestSlot) DEFINE_GETTER_FUNCTIONS(ImportEntryObject, importName, ImportNameSlot) DEFINE_GETTER_FUNCTIONS(ImportEntryObject, localName, LocalNameSlot) DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, moduleRequest) DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, importName) DEFINE_ATOM_ACCESSOR_METHOD(ImportEntryObject, localName) /* static */ bool ImportEntryObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is(); } /* static */ bool GlobalObject::initImportEntryProto(JSContext* cx, Handle global) { static const JSPropertySpec protoAccessors[] = { JS_PSG("moduleRequest", ImportEntryObject_moduleRequestGetter, 0), JS_PSG("importName", ImportEntryObject_importNameGetter, 0), JS_PSG("localName", ImportEntryObject_localNameGetter, 0), JS_PS_END }; RootedObject proto(cx, global->createBlankPrototype(cx)); if (!proto) return false; if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, nullptr)) return false; global->setReservedSlot(IMPORT_ENTRY_PROTO, ObjectValue(*proto)); return true; } /* static */ ImportEntryObject* ImportEntryObject::create(ExclusiveContext* cx, HandleAtom moduleRequest, HandleAtom importName, HandleAtom localName) { RootedObject proto(cx, cx->global()->getImportEntryPrototype()); RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto)); if (!obj) return nullptr; RootedImportEntryObject self(cx, &obj->as()); self->initReservedSlot(ModuleRequestSlot, StringValue(moduleRequest)); self->initReservedSlot(ImportNameSlot, StringValue(importName)); self->initReservedSlot(LocalNameSlot, StringValue(localName)); return self; } /////////////////////////////////////////////////////////////////////////// // ExportEntryObject /* static */ const Class ExportEntryObject::class_ = { "ExportEntry", JSCLASS_HAS_RESERVED_SLOTS(ExportEntryObject::SlotCount) | JSCLASS_IS_ANONYMOUS }; DEFINE_GETTER_FUNCTIONS(ExportEntryObject, exportName, ExportNameSlot) DEFINE_GETTER_FUNCTIONS(ExportEntryObject, moduleRequest, ModuleRequestSlot) DEFINE_GETTER_FUNCTIONS(ExportEntryObject, importName, ImportNameSlot) DEFINE_GETTER_FUNCTIONS(ExportEntryObject, localName, LocalNameSlot) DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, exportName) DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, moduleRequest) DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, importName) DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, localName) /* static */ bool ExportEntryObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is(); } /* static */ bool GlobalObject::initExportEntryProto(JSContext* cx, Handle global) { static const JSPropertySpec protoAccessors[] = { JS_PSG("exportName", ExportEntryObject_exportNameGetter, 0), JS_PSG("moduleRequest", ExportEntryObject_moduleRequestGetter, 0), JS_PSG("importName", ExportEntryObject_importNameGetter, 0), JS_PSG("localName", ExportEntryObject_localNameGetter, 0), JS_PS_END }; RootedObject proto(cx, global->createBlankPrototype(cx)); if (!proto) return false; if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, nullptr)) return false; global->setReservedSlot(EXPORT_ENTRY_PROTO, ObjectValue(*proto)); return true; } static Value StringOrNullValue(JSString* maybeString) { return maybeString ? StringValue(maybeString) : NullValue(); } /* static */ ExportEntryObject* ExportEntryObject::create(ExclusiveContext* cx, HandleAtom maybeExportName, HandleAtom maybeModuleRequest, HandleAtom maybeImportName, HandleAtom maybeLocalName) { RootedObject proto(cx, cx->global()->getExportEntryPrototype()); RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto)); if (!obj) return nullptr; RootedExportEntryObject self(cx, &obj->as()); self->initReservedSlot(ExportNameSlot, StringOrNullValue(maybeExportName)); self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest)); self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName)); self->initReservedSlot(LocalNameSlot, StringOrNullValue(maybeLocalName)); return self; } /////////////////////////////////////////////////////////////////////////// // IndirectBindingMap IndirectBindingMap::Binding::Binding(ModuleEnvironmentObject* environment, Shape* shape) : environment(environment), shape(shape) {} IndirectBindingMap::IndirectBindingMap(Zone* zone) : map_(ZoneAllocPolicy(zone)) { } bool IndirectBindingMap::init() { return map_.init(); } void IndirectBindingMap::trace(JSTracer* trc) { for (Map::Enum e(map_); !e.empty(); e.popFront()) { Binding& b = e.front().value(); TraceEdge(trc, &b.environment, "module bindings environment"); TraceEdge(trc, &b.shape, "module bindings shape"); jsid bindingName = e.front().key(); TraceManuallyBarrieredEdge(trc, &bindingName, "module bindings binding name"); MOZ_ASSERT(bindingName == e.front().key()); } } bool IndirectBindingMap::putNew(JSContext* cx, HandleId name, HandleModuleEnvironmentObject environment, HandleId localName) { RootedShape shape(cx, environment->lookup(cx, localName)); MOZ_ASSERT(shape); if (!map_.putNew(name, Binding(environment, shape))) { ReportOutOfMemory(cx); return false; } return true; } bool IndirectBindingMap::lookup(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut) const { auto ptr = map_.lookup(name); if (!ptr) return false; const Binding& binding = ptr->value(); MOZ_ASSERT(binding.environment); MOZ_ASSERT(!binding.environment->inDictionaryMode()); MOZ_ASSERT(binding.environment->containsPure(binding.shape)); *envOut = binding.environment; *shapeOut = binding.shape; return true; } /////////////////////////////////////////////////////////////////////////// // ModuleNamespaceObject /* static */ const ModuleNamespaceObject::ProxyHandler ModuleNamespaceObject::proxyHandler; /* static */ bool ModuleNamespaceObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is(); } /* static */ ModuleNamespaceObject* ModuleNamespaceObject::create(JSContext* cx, HandleModuleObject module) { RootedValue priv(cx, ObjectValue(*module)); ProxyOptions options; options.setLazyProto(true); options.setSingleton(true); RootedObject object(cx, NewProxyObject(cx, &proxyHandler, priv, nullptr, options)); if (!object) return nullptr; return &object->as(); } ModuleObject& ModuleNamespaceObject::module() { return GetProxyPrivate(this).toObject().as(); } JSObject& ModuleNamespaceObject::exports() { JSObject* exports = module().namespaceExports(); MOZ_ASSERT(exports); return *exports; } IndirectBindingMap& ModuleNamespaceObject::bindings() { IndirectBindingMap* bindings = module().namespaceBindings(); MOZ_ASSERT(bindings); return *bindings; } bool ModuleNamespaceObject::addBinding(JSContext* cx, HandleAtom exportedName, HandleModuleObject targetModule, HandleAtom localName) { IndirectBindingMap* bindings(this->module().namespaceBindings()); MOZ_ASSERT(bindings); RootedModuleEnvironmentObject environment(cx, &targetModule->initialEnvironment()); RootedId exportedNameId(cx, AtomToId(exportedName)); RootedId localNameId(cx, AtomToId(localName)); return bindings->putNew(cx, exportedNameId, environment, localNameId); } const char ModuleNamespaceObject::ProxyHandler::family = 0; ModuleNamespaceObject::ProxyHandler::ProxyHandler() : BaseProxyHandler(&family, false) {} bool ModuleNamespaceObject::ProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const { protop.set(nullptr); return true; } bool ModuleNamespaceObject::ProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, ObjectOpResult& result) const { if (!proto) return result.succeed(); return result.failCantSetProto(); } bool ModuleNamespaceObject::ProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary, MutableHandleObject protop) const { *isOrdinary = false; return true; } bool ModuleNamespaceObject::ProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const { *succeeded = true; return true; } bool ModuleNamespaceObject::ProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const { *extensible = false; return true; } bool ModuleNamespaceObject::ProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const { result.succeed(); return true; } bool ModuleNamespaceObject::ProxyHandler::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) const { Rooted ns(cx, &proxy->as()); if (JSID_IS_SYMBOL(id)) { Rooted symbol(cx, JSID_TO_SYMBOL(id)); if (symbol == cx->wellKnownSymbols().toStringTag) { RootedValue value(cx, StringValue(cx->names().Module)); desc.object().set(proxy); desc.setWritable(false); desc.setEnumerable(false); desc.setConfigurable(false); desc.setValue(value); return true; } return true; } const IndirectBindingMap& bindings = ns->bindings(); ModuleEnvironmentObject* env; Shape* shape; if (!bindings.lookup(id, &env, &shape)) return true; RootedValue value(cx, env->getSlot(shape->slot())); if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) { ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id); return false; } desc.object().set(env); desc.setConfigurable(false); desc.setEnumerable(true); desc.setValue(value); return true; } bool ModuleNamespaceObject::ProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle desc, ObjectOpResult& result) const { return result.failReadOnly(); } bool ModuleNamespaceObject::ProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const { Rooted ns(cx, &proxy->as()); if (JSID_IS_SYMBOL(id)) { Rooted symbol(cx, JSID_TO_SYMBOL(id)); *bp = symbol == cx->wellKnownSymbols().toStringTag; return true; } *bp = ns->bindings().has(id); return true; } bool ModuleNamespaceObject::ProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, MutableHandleValue vp) const { Rooted ns(cx, &proxy->as()); if (JSID_IS_SYMBOL(id)) { Rooted symbol(cx, JSID_TO_SYMBOL(id)); if (symbol == cx->wellKnownSymbols().toStringTag) { vp.setString(cx->names().Module); return true; } vp.setUndefined(); return true; } ModuleEnvironmentObject* env; Shape* shape; if (!ns->bindings().lookup(id, &env, &shape)) { vp.setUndefined(); return true; } RootedValue value(cx, env->getSlot(shape->slot())); if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) { ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id); return false; } vp.set(value); return true; } bool ModuleNamespaceObject::ProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const { return result.failReadOnly(); } bool ModuleNamespaceObject::ProxyHandler::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) const { Rooted ns(cx, &proxy->as()); if (ns->bindings().has(id)) return result.failReadOnly(); return result.succeed(); } bool ModuleNamespaceObject::ProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const { Rooted ns(cx, &proxy->as()); RootedObject exports(cx, &ns->exports()); uint32_t count; if (!GetLengthProperty(cx, exports, &count) || !props.reserve(props.length() + count + 1)) return false; Rooted names(cx, ValueVector(cx)); if (!names.resize(count) || !GetElements(cx, exports, count, names.begin())) return false; for (uint32_t i = 0; i < count; i++) props.infallibleAppend(AtomToId(&names[i].toString()->asAtom())); props.infallibleAppend(SYMBOL_TO_JSID(cx->wellKnownSymbols().toStringTag)); return true; } /////////////////////////////////////////////////////////////////////////// // FunctionDeclaration FunctionDeclaration::FunctionDeclaration(HandleAtom name, HandleFunction fun) : name(name), fun(fun) {} void FunctionDeclaration::trace(JSTracer* trc) { TraceEdge(trc, &name, "FunctionDeclaration name"); TraceEdge(trc, &fun, "FunctionDeclaration fun"); } /////////////////////////////////////////////////////////////////////////// // ModuleObject /* static */ const ClassOps ModuleObject::classOps_ = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ ModuleObject::finalize, nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ ModuleObject::trace }; /* static */ const Class ModuleObject::class_ = { "Module", JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) | JSCLASS_IS_ANONYMOUS | JSCLASS_BACKGROUND_FINALIZE, &ModuleObject::classOps_ }; #define DEFINE_ARRAY_SLOT_ACCESSOR(cls, name, slot) \ ArrayObject& \ cls::name() const \ { \ return getFixedSlot(cls::slot).toObject().as(); \ } DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, requestedModules, RequestedModulesSlot) DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, importEntries, ImportEntriesSlot) DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, localExportEntries, LocalExportEntriesSlot) DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot) DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, starExportEntries, StarExportEntriesSlot) /* static */ bool ModuleObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is(); } /* static */ ModuleObject* ModuleObject::create(ExclusiveContext* cx) { RootedObject proto(cx, cx->global()->getModulePrototype()); RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto)); if (!obj) return nullptr; RootedModuleObject self(cx, &obj->as()); Zone* zone = cx->zone(); IndirectBindingMap* bindings = zone->new_(zone); if (!bindings || !bindings->init()) { ReportOutOfMemory(cx); js_delete(bindings); return nullptr; } self->initReservedSlot(ImportBindingsSlot, PrivateValue(bindings)); FunctionDeclarationVector* funDecls = zone->new_(zone); if (!funDecls) { ReportOutOfMemory(cx); return nullptr; } self->initReservedSlot(FunctionDeclarationsSlot, PrivateValue(funDecls)); return self; } /* static */ void ModuleObject::finalize(js::FreeOp* fop, JSObject* obj) { MOZ_ASSERT(fop->maybeOffMainThread()); ModuleObject* self = &obj->as(); if (self->hasImportBindings()) fop->delete_(&self->importBindings()); if (IndirectBindingMap* bindings = self->namespaceBindings()) fop->delete_(bindings); if (FunctionDeclarationVector* funDecls = self->functionDeclarations()) fop->delete_(funDecls); } ModuleEnvironmentObject* ModuleObject::environment() const { Value value = getReservedSlot(EnvironmentSlot); if (value.isUndefined()) return nullptr; return &value.toObject().as(); } bool ModuleObject::hasImportBindings() const { // Import bindings may not be present if we hit OOM in initialization. return !getReservedSlot(ImportBindingsSlot).isUndefined(); } IndirectBindingMap& ModuleObject::importBindings() { return *static_cast(getReservedSlot(ImportBindingsSlot).toPrivate()); } JSObject* ModuleObject::namespaceExports() { Value value = getReservedSlot(NamespaceExportsSlot); if (value.isUndefined()) return nullptr; return &value.toObject(); } IndirectBindingMap* ModuleObject::namespaceBindings() { Value value = getReservedSlot(NamespaceBindingsSlot); if (value.isUndefined()) return nullptr; return static_cast(value.toPrivate()); } ModuleNamespaceObject* ModuleObject::namespace_() { Value value = getReservedSlot(NamespaceSlot); if (value.isUndefined()) return nullptr; return &value.toObject().as(); } FunctionDeclarationVector* ModuleObject::functionDeclarations() { Value value = getReservedSlot(FunctionDeclarationsSlot); if (value.isUndefined()) return nullptr; return static_cast(value.toPrivate()); } void ModuleObject::init(HandleScript script) { initReservedSlot(ScriptSlot, PrivateValue(script)); initReservedSlot(StateSlot, Int32Value(MODULE_STATE_FAILED)); } void ModuleObject::setInitialEnvironment(HandleModuleEnvironmentObject initialEnvironment) { initReservedSlot(InitialEnvironmentSlot, ObjectValue(*initialEnvironment)); } void ModuleObject::initImportExportData(HandleArrayObject requestedModules, HandleArrayObject importEntries, HandleArrayObject localExportEntries, HandleArrayObject indirectExportEntries, HandleArrayObject starExportEntries) { initReservedSlot(RequestedModulesSlot, ObjectValue(*requestedModules)); initReservedSlot(ImportEntriesSlot, ObjectValue(*importEntries)); initReservedSlot(LocalExportEntriesSlot, ObjectValue(*localExportEntries)); initReservedSlot(IndirectExportEntriesSlot, ObjectValue(*indirectExportEntries)); initReservedSlot(StarExportEntriesSlot, ObjectValue(*starExportEntries)); setReservedSlot(StateSlot, Int32Value(MODULE_STATE_PARSED)); } static bool FreezeObjectProperty(JSContext* cx, HandleNativeObject obj, uint32_t slot) { RootedObject property(cx, &obj->getSlot(slot).toObject()); return FreezeObject(cx, property); } /* static */ bool ModuleObject::Freeze(JSContext* cx, HandleModuleObject self) { return FreezeObjectProperty(cx, self, RequestedModulesSlot) && FreezeObjectProperty(cx, self, ImportEntriesSlot) && FreezeObjectProperty(cx, self, LocalExportEntriesSlot) && FreezeObjectProperty(cx, self, IndirectExportEntriesSlot) && FreezeObjectProperty(cx, self, StarExportEntriesSlot) && FreezeObject(cx, self); } #ifdef DEBUG static inline bool IsObjectFrozen(JSContext* cx, HandleObject obj) { bool frozen = false; MOZ_ALWAYS_TRUE(TestIntegrityLevel(cx, obj, IntegrityLevel::Frozen, &frozen)); return frozen; } static inline bool IsObjectPropertyFrozen(JSContext* cx, HandleNativeObject obj, uint32_t slot) { RootedObject property(cx, &obj->getSlot(slot).toObject()); return IsObjectFrozen(cx, property); } /* static */ inline bool ModuleObject::IsFrozen(JSContext* cx, HandleModuleObject self) { return IsObjectPropertyFrozen(cx, self, RequestedModulesSlot) && IsObjectPropertyFrozen(cx, self, ImportEntriesSlot) && IsObjectPropertyFrozen(cx, self, LocalExportEntriesSlot) && IsObjectPropertyFrozen(cx, self, IndirectExportEntriesSlot) && IsObjectPropertyFrozen(cx, self, StarExportEntriesSlot) && IsObjectFrozen(cx, self); } #endif inline static void AssertModuleScopesMatch(ModuleObject* module) { MOZ_ASSERT(module->enclosingScope()->is()); MOZ_ASSERT(IsGlobalLexicalEnvironment(&module->initialEnvironment().enclosingEnvironment())); } void ModuleObject::fixEnvironmentsAfterCompartmentMerge(JSContext* cx) { AssertModuleScopesMatch(this); initialEnvironment().fixEnclosingEnvironmentAfterCompartmentMerge(script()->global()); AssertModuleScopesMatch(this); } bool ModuleObject::hasScript() const { // When modules are parsed via the Reflect.parse() API, the module object // doesn't have a script. return !getReservedSlot(ScriptSlot).isUndefined(); } JSScript* ModuleObject::script() const { return static_cast(getReservedSlot(ScriptSlot).toPrivate()); } static inline void AssertValidModuleState(ModuleState state) { MOZ_ASSERT(state >= MODULE_STATE_FAILED && state <= MODULE_STATE_EVALUATED); } ModuleState ModuleObject::state() const { ModuleState state = getReservedSlot(StateSlot).toInt32(); AssertValidModuleState(state); return state; } Value ModuleObject::hostDefinedField() const { return getReservedSlot(HostDefinedSlot); } void ModuleObject::setHostDefinedField(const JS::Value& value) { setReservedSlot(HostDefinedSlot, value); } ModuleEnvironmentObject& ModuleObject::initialEnvironment() const { return getReservedSlot(InitialEnvironmentSlot).toObject().as(); } Scope* ModuleObject::enclosingScope() const { return script()->enclosingScope(); } /* static */ void ModuleObject::trace(JSTracer* trc, JSObject* obj) { ModuleObject& module = obj->as(); if (module.hasScript()) { JSScript* script = module.script(); TraceManuallyBarrieredEdge(trc, &script, "Module script"); module.setReservedSlot(ScriptSlot, PrivateValue(script)); } if (module.hasImportBindings()) module.importBindings().trace(trc); if (IndirectBindingMap* bindings = module.namespaceBindings()) bindings->trace(trc); if (FunctionDeclarationVector* funDecls = module.functionDeclarations()) funDecls->trace(trc); } void ModuleObject::createEnvironment() { // The environment has already been created, we just neet to set it in the // right slot. MOZ_ASSERT(!getReservedSlot(InitialEnvironmentSlot).isUndefined()); MOZ_ASSERT(getReservedSlot(EnvironmentSlot).isUndefined()); setReservedSlot(EnvironmentSlot, getReservedSlot(InitialEnvironmentSlot)); } bool ModuleObject::noteFunctionDeclaration(ExclusiveContext* cx, HandleAtom name, HandleFunction fun) { FunctionDeclarationVector* funDecls = functionDeclarations(); if (!funDecls->emplaceBack(name, fun)) { ReportOutOfMemory(cx); return false; } return true; } /* static */ bool ModuleObject::instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self) { MOZ_ASSERT(IsFrozen(cx, self)); FunctionDeclarationVector* funDecls = self->functionDeclarations(); if (!funDecls) { JS_ReportErrorASCII(cx, "Module function declarations have already been instantiated"); return false; } RootedModuleEnvironmentObject env(cx, &self->initialEnvironment()); RootedFunction fun(cx); RootedValue value(cx); for (const auto& funDecl : *funDecls) { fun = funDecl.fun; RootedObject obj(cx, Lambda(cx, fun, env)); if (!obj) return false; value = ObjectValue(*fun); if (!SetProperty(cx, env, funDecl.name->asPropertyName(), value)) return false; } js_delete(funDecls); self->setReservedSlot(FunctionDeclarationsSlot, UndefinedValue()); return true; } void ModuleObject::setState(int32_t newState) { AssertValidModuleState(newState); MOZ_ASSERT(state() != MODULE_STATE_FAILED); MOZ_ASSERT(newState == MODULE_STATE_FAILED || newState > state()); setReservedSlot(StateSlot, Int32Value(newState)); } /* static */ bool ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval) { MOZ_ASSERT(IsFrozen(cx, self)); RootedScript script(cx, self->script()); RootedModuleEnvironmentObject scope(cx, self->environment()); if (!scope) { JS_ReportErrorASCII(cx, "Module declarations have not yet been instantiated"); return false; } return Execute(cx, script, *scope, rval.address()); } /* static */ ModuleNamespaceObject* ModuleObject::createNamespace(JSContext* cx, HandleModuleObject self, HandleObject exports) { MOZ_ASSERT(!self->namespace_()); MOZ_ASSERT(exports->is() || exports->is()); RootedModuleNamespaceObject ns(cx, ModuleNamespaceObject::create(cx, self)); if (!ns) return nullptr; Zone* zone = cx->zone(); IndirectBindingMap* bindings = zone->new_(zone); if (!bindings || !bindings->init()) { ReportOutOfMemory(cx); js_delete(bindings); return nullptr; } self->initReservedSlot(NamespaceSlot, ObjectValue(*ns)); self->initReservedSlot(NamespaceExportsSlot, ObjectValue(*exports)); self->initReservedSlot(NamespaceBindingsSlot, PrivateValue(bindings)); return ns; } static bool InvokeSelfHostedMethod(JSContext* cx, HandleModuleObject self, HandlePropertyName name) { RootedValue fval(cx); if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), name, name, 0, &fval)) return false; RootedValue ignored(cx); return Call(cx, fval, self, &ignored); } /* static */ bool ModuleObject::DeclarationInstantiation(JSContext* cx, HandleModuleObject self) { return InvokeSelfHostedMethod(cx, self, cx->names().ModuleDeclarationInstantiation); } /* static */ bool ModuleObject::Evaluation(JSContext* cx, HandleModuleObject self) { return InvokeSelfHostedMethod(cx, self, cx->names().ModuleEvaluation); } DEFINE_GETTER_FUNCTIONS(ModuleObject, namespace_, NamespaceSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, state, StateSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot) /* static */ bool GlobalObject::initModuleProto(JSContext* cx, Handle global) { static const JSPropertySpec protoAccessors[] = { JS_PSG("namespace", ModuleObject_namespace_Getter, 0), JS_PSG("state", ModuleObject_stateGetter, 0), JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0), JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0), JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0), JS_PSG("indirectExportEntries", ModuleObject_indirectExportEntriesGetter, 0), JS_PSG("starExportEntries", ModuleObject_starExportEntriesGetter, 0), JS_PS_END }; static const JSFunctionSpec protoFunctions[] = { JS_SELF_HOSTED_FN("getExportedNames", "ModuleGetExportedNames", 1, 0), JS_SELF_HOSTED_FN("resolveExport", "ModuleResolveExport", 3, 0), JS_SELF_HOSTED_FN("declarationInstantiation", "ModuleDeclarationInstantiation", 0, 0), JS_SELF_HOSTED_FN("evaluation", "ModuleEvaluation", 0, 0), JS_FS_END }; RootedObject proto(cx, global->createBlankPrototype(cx)); if (!proto) return false; if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, protoFunctions)) return false; global->setReservedSlot(MODULE_PROTO, ObjectValue(*proto)); return true; } #undef DEFINE_GETTER_FUNCTIONS #undef DEFINE_STRING_ACCESSOR_METHOD #undef DEFINE_ARRAY_SLOT_ACCESSOR /////////////////////////////////////////////////////////////////////////// // ModuleBuilder ModuleBuilder::ModuleBuilder(ExclusiveContext* cx, HandleModuleObject module) : cx_(cx), module_(cx, module), requestedModules_(cx, AtomVector(cx)), importedBoundNames_(cx, AtomVector(cx)), importEntries_(cx, ImportEntryVector(cx)), exportEntries_(cx, ExportEntryVector(cx)), localExportEntries_(cx, ExportEntryVector(cx)), indirectExportEntries_(cx, ExportEntryVector(cx)), starExportEntries_(cx, ExportEntryVector(cx)) {} bool ModuleBuilder::buildTables() { for (const auto& e : exportEntries_) { RootedExportEntryObject exp(cx_, e); if (!exp->moduleRequest()) { RootedImportEntryObject importEntry(cx_, importEntryFor(exp->localName())); if (!importEntry) { if (!localExportEntries_.append(exp)) return false; } else { if (importEntry->importName() == cx_->names().star) { if (!localExportEntries_.append(exp)) return false; } else { RootedAtom exportName(cx_, exp->exportName()); RootedAtom moduleRequest(cx_, importEntry->moduleRequest()); RootedAtom importName(cx_, importEntry->importName()); RootedExportEntryObject exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr); if (!exportEntry || !indirectExportEntries_.append(exportEntry)) return false; } } } else if (exp->importName() == cx_->names().star) { if (!starExportEntries_.append(exp)) return false; } else { if (!indirectExportEntries_.append(exp)) return false; } } return true; } bool ModuleBuilder::initModule() { RootedArrayObject requestedModules(cx_, createArray(requestedModules_)); if (!requestedModules) return false; RootedArrayObject importEntries(cx_, createArray(importEntries_)); if (!importEntries) return false; RootedArrayObject localExportEntries(cx_, createArray(localExportEntries_)); if (!localExportEntries) return false; RootedArrayObject indirectExportEntries(cx_); indirectExportEntries = createArray(indirectExportEntries_); if (!indirectExportEntries) return false; RootedArrayObject starExportEntries(cx_, createArray(starExportEntries_)); if (!starExportEntries) return false; module_->initImportExportData(requestedModules, importEntries, localExportEntries, indirectExportEntries, starExportEntries); return true; } bool ModuleBuilder::processImport(frontend::ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_IMPORT)); MOZ_ASSERT(pn->isArity(PN_BINARY)); MOZ_ASSERT(pn->pn_left->isKind(PNK_IMPORT_SPEC_LIST)); MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING)); RootedAtom module(cx_, pn->pn_right->pn_atom); if (!maybeAppendRequestedModule(module)) return false; for (ParseNode* spec = pn->pn_left->pn_head; spec; spec = spec->pn_next) { MOZ_ASSERT(spec->isKind(PNK_IMPORT_SPEC)); MOZ_ASSERT(spec->pn_left->isArity(PN_NAME)); MOZ_ASSERT(spec->pn_right->isArity(PN_NAME)); RootedAtom importName(cx_, spec->pn_left->pn_atom); RootedAtom localName(cx_, spec->pn_right->pn_atom); if (!importedBoundNames_.append(localName)) return false; RootedImportEntryObject importEntry(cx_); importEntry = ImportEntryObject::create(cx_, module, importName, localName); if (!importEntry || !importEntries_.append(importEntry)) return false; } return true; } bool ModuleBuilder::processExport(frontend::ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_EXPORT) || pn->isKind(PNK_EXPORT_DEFAULT)); MOZ_ASSERT(pn->getArity() == (pn->isKind(PNK_EXPORT) ? PN_UNARY : PN_BINARY)); bool isDefault = pn->getKind() == PNK_EXPORT_DEFAULT; ParseNode* kid = isDefault ? pn->pn_left : pn->pn_kid; if (isDefault && pn->pn_right) { // This is an export default containing an expression. RootedAtom localName(cx_, cx_->names().starDefaultStar); RootedAtom exportName(cx_, cx_->names().default_); return appendExportEntry(exportName, localName); } switch (kid->getKind()) { case PNK_EXPORT_SPEC_LIST: MOZ_ASSERT(!isDefault); for (ParseNode* spec = kid->pn_head; spec; spec = spec->pn_next) { MOZ_ASSERT(spec->isKind(PNK_EXPORT_SPEC)); RootedAtom localName(cx_, spec->pn_left->pn_atom); RootedAtom exportName(cx_, spec->pn_right->pn_atom); if (!appendExportEntry(exportName, localName)) return false; } break; case PNK_CLASS: { const ClassNode& cls = kid->as(); MOZ_ASSERT(cls.names()); RootedAtom localName(cx_, cls.names()->innerBinding()->pn_atom); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); if (!appendExportEntry(exportName, localName)) return false; break; } case PNK_VAR: case PNK_CONST: case PNK_LET: { MOZ_ASSERT(kid->isArity(PN_LIST)); for (ParseNode* var = kid->pn_head; var; var = var->pn_next) { if (var->isKind(PNK_ASSIGN)) var = var->pn_left; MOZ_ASSERT(var->isKind(PNK_NAME)); RootedAtom localName(cx_, var->pn_atom); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); if (!appendExportEntry(exportName, localName)) return false; } break; } case PNK_FUNCTION: { RootedFunction func(cx_, kid->pn_funbox->function()); MOZ_ASSERT(!func->isArrow()); RootedAtom localName(cx_, func->explicitName()); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); MOZ_ASSERT_IF(isDefault, localName); if (!appendExportEntry(exportName, localName)) return false; break; } default: MOZ_CRASH("Unexpected parse node"); } return true; } bool ModuleBuilder::processExportFrom(frontend::ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_EXPORT_FROM)); MOZ_ASSERT(pn->isArity(PN_BINARY)); MOZ_ASSERT(pn->pn_left->isKind(PNK_EXPORT_SPEC_LIST)); MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING)); RootedAtom module(cx_, pn->pn_right->pn_atom); if (!maybeAppendRequestedModule(module)) return false; for (ParseNode* spec = pn->pn_left->pn_head; spec; spec = spec->pn_next) { if (spec->isKind(PNK_EXPORT_SPEC)) { RootedAtom bindingName(cx_, spec->pn_left->pn_atom); RootedAtom exportName(cx_, spec->pn_right->pn_atom); if (!appendExportFromEntry(exportName, module, bindingName)) return false; } else { MOZ_ASSERT(spec->isKind(PNK_EXPORT_BATCH_SPEC)); RootedAtom importName(cx_, cx_->names().star); if (!appendExportFromEntry(nullptr, module, importName)) return false; } } return true; } ImportEntryObject* ModuleBuilder::importEntryFor(JSAtom* localName) const { for (auto import : importEntries_) { if (import->localName() == localName) return import; } return nullptr; } bool ModuleBuilder::hasExportedName(JSAtom* name) const { for (auto entry : exportEntries_) { if (entry->exportName() == name) return true; } return false; } bool ModuleBuilder::appendExportEntry(HandleAtom exportName, HandleAtom localName) { Rooted exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName); return exportEntry && exportEntries_.append(exportEntry); } bool ModuleBuilder::appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest, HandleAtom importName) { Rooted exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr); return exportEntry && exportEntries_.append(exportEntry); } bool ModuleBuilder::maybeAppendRequestedModule(HandleAtom module) { for (auto m : requestedModules_) { if (m == module) return true; } return requestedModules_.append(module); } static Value MakeElementValue(JSString *string) { return StringValue(string); } static Value MakeElementValue(JSObject *object) { return ObjectValue(*object); } template ArrayObject* ModuleBuilder::createArray(const GCVector& vector) { uint32_t length = vector.length(); RootedArrayObject array(cx_, NewDenseFullyAllocatedArray(cx_, length)); if (!array) return nullptr; array->setDenseInitializedLength(length); for (uint32_t i = 0; i < length; i++) array->initDenseElement(i, MakeElementValue(vector[i])); return array; }