From a920d4b2e64f07814031f917fe5fc94c70874619 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Mon, 24 Aug 2015 15:58:36 +0100 Subject: [PATCH] Bug 930414 - Add module fields relating to exports r=shu --- js/src/builtin/ModuleObject.cpp | 288 +++++++++++++++++- js/src/builtin/ModuleObject.h | 51 +++- js/src/gc/Marking.cpp | 3 +- .../jit-test/tests/modules/export-entries.js | 121 ++++++++ js/src/jsprototypes.h | 1 + 5 files changed, 450 insertions(+), 14 deletions(-) create mode 100644 js/src/jit-test/tests/modules/export-entries.js diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index ee89cf872b30..a0c9f877d125 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -13,6 +13,7 @@ using namespace js; typedef JS::Rooted RootedImportEntry; +typedef JS::Rooted RootedExportEntry; template static bool @@ -50,6 +51,16 @@ ModuleValueGetter(JSContext* cx, unsigned argc, Value* vp) return &value.toString()->asAtom(); \ } +#define DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(cls, name) \ + JSAtom* \ + cls::name() \ + { \ + Value value = cls##_##name##Value(this); \ + if (value.isNull()) \ + return nullptr; \ + return &value.toString()->asAtom(); \ + } + /////////////////////////////////////////////////////////////////////////// // ImportEntryObject @@ -119,6 +130,86 @@ ImportEntryObject::create(JSContext* cx, return self; } +/////////////////////////////////////////////////////////////////////////// +// ExportEntryObject + +/* static */ const Class +ExportEntryObject::class_ = { + "ExportEntry", + JSCLASS_HAS_RESERVED_SLOTS(ExportEntryObject::SlotCount) | + JSCLASS_HAS_CACHED_PROTO(JSProto_ExportEntry) | + JSCLASS_IS_ANONYMOUS | + JSCLASS_IMPLEMENTS_BARRIERS +}; + +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_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 */ JSObject* +ExportEntryObject::initClass(JSContext* cx, HandleObject obj) +{ + 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 + }; + + Rooted global(cx, &obj->as()); + RootedObject proto(cx, global->createBlankPrototype(cx)); + if (!proto) + return nullptr; + + if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, nullptr)) + return nullptr; + + global->setPrototype(JSProto_ExportEntry, ObjectValue(*proto)); + return proto; +} + +JSObject* +js::InitExportEntryClass(JSContext* cx, HandleObject obj) +{ + return ExportEntryObject::initClass(cx, obj); +} + +static Value +StringOrNullValue(JSString* maybeString) +{ + return maybeString ? StringValue(maybeString) : NullValue(); +} + +/* static */ ExportEntryObject* +ExportEntryObject::create(JSContext* cx, + HandleAtom maybeExportName, + HandleAtom maybeModuleRequest, + HandleAtom maybeImportName, + HandleAtom maybeLocalName) +{ + RootedExportEntry self(cx, NewBuiltinClassInstance(cx)); + if (!self) + return nullptr; + self->initReservedSlot(ExportNameSlot, StringOrNullValue(maybeExportName)); + self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest)); + self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName)); + self->initReservedSlot(LocalNameSlot, StringOrNullValue(maybeLocalName)); + return self; +} + /////////////////////////////////////////////////////////////////////////// // ModuleObject @@ -153,6 +244,9 @@ ModuleObject::class_ = { 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) @@ -174,10 +268,16 @@ ModuleObject::init(HandleScript script) void ModuleObject::initImportExportData(HandleArrayObject requestedModules, - HandleArrayObject importEntries) + 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)); } JSScript* @@ -197,6 +297,9 @@ ModuleObject::trace(JSTracer* trc, JSObject* obj) 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) JSObject* js::InitModuleClass(JSContext* cx, HandleObject obj) @@ -204,6 +307,9 @@ js::InitModuleClass(JSContext* cx, HandleObject obj) static const JSPropertySpec protoAccessors[] = { 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 }; @@ -231,7 +337,11 @@ ModuleBuilder::ModuleBuilder(JSContext* cx) : cx_(cx), requestedModules_(cx, AtomVector(cx)), importedBoundNames_(cx, AtomVector(cx)), - importEntries_(cx, ImportEntryVector(cx)) + importEntries_(cx, ImportEntryVector(cx)), + exportEntries_(cx, ExportEntryVector(cx)), + localExportEntries_(cx, ExportEntryVector(cx)), + indirectExportEntries_(cx, ExportEntryVector(cx)), + starExportEntries_(cx, ExportEntryVector(cx)) {} bool @@ -251,6 +361,9 @@ ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject break; case PNK_EXPORT: + case PNK_EXPORT_DEFAULT: + if (!processExport(pn)) + return false; break; case PNK_EXPORT_FROM: @@ -258,14 +371,45 @@ ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject return false; break; - case PNK_EXPORT_DEFAULT: - break; - default: break; } } + for (const auto& e : exportEntries_) { + RootedExportEntry exp(cx_, e); + if (!exp->moduleRequest()) { + RootedImportEntry 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()); + RootedExportEntry 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; + } + } + RootedArrayObject requestedModules(cx_, createArray(requestedModules_)); if (!requestedModules) return false; @@ -274,8 +418,24 @@ ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject 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); + importEntries, + localExportEntries, + indirectExportEntries, + starExportEntries); return true; } @@ -304,31 +464,135 @@ ModuleBuilder::processImport(frontend::ParseNode* pn) RootedImportEntry importEntry(cx_); importEntry = ImportEntryObject::create(cx_, module, importName, localName); - if (!importEntry) + if (!importEntry || !importEntries_.append(importEntry)) return false; - - if (!importEntries_.append(importEntry)) { - ReportOutOfMemory(cx_); - return false; - } } return true; } +bool +ModuleBuilder::processExport(frontend::ParseNode* pn) +{ + MOZ_ASSERT(pn->isArity(PN_UNARY)); + + ParseNode* kid = pn->pn_kid; + bool isDefault = pn->getKind() == PNK_EXPORT_DEFAULT; + + 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 (!appendLocalExportEntry(exportName, localName)) + return false; + } + break; + + case PNK_FUNCTION: { + RootedFunction func(cx_, kid->pn_funbox->function()); + RootedAtom localName(cx_, func->atom()); + RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); + if (!appendLocalExportEntry(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 (!appendLocalExportEntry(exportName, localName)) + return false; + break; + } + + case PNK_VAR: + case PNK_CONST: + case PNK_GLOBALCONST: + 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 (!appendLocalExportEntry(exportName, localName)) + return false; + } + break; + } + + default: + MOZ_ASSERT(isDefault); + RootedAtom localName(cx_, cx_->names().starDefaultStar); + RootedAtom exportName(cx_, cx_->names().default_); + if (!appendLocalExportEntry(exportName, localName)) + return false; + break; + } + return true; +} + bool ModuleBuilder::processExportFrom(frontend::ParseNode* pn) { 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 (!appendIndirectExportEntry(exportName, module, bindingName)) + return false; + } else { + MOZ_ASSERT(spec->isKind(PNK_EXPORT_BATCH_SPEC)); + RootedAtom importName(cx_, cx_->names().star); + if (!appendIndirectExportEntry(nullptr, module, importName)) + return false; + } + } + return true; } +ImportEntryObject* +ModuleBuilder::importEntryFor(JSAtom* localName) +{ + for (auto import : importEntries_) { + if (import->localName() == localName) + return import; + } + return nullptr; +} + +bool +ModuleBuilder::appendLocalExportEntry(HandleAtom exportName, HandleAtom localName) +{ + Rooted exportEntry(cx_); + exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName); + return exportEntry && exportEntries_.append(exportEntry); +} + +bool +ModuleBuilder::appendIndirectExportEntry(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) { diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index cfb273c51708..fc02041e4bff 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -42,6 +42,32 @@ class ImportEntryObject : public NativeObject JSAtom* localName(); }; +class ExportEntryObject : public NativeObject +{ + public: + enum + { + ExportNameSlot = 0, + ModuleRequestSlot, + ImportNameSlot, + LocalNameSlot, + SlotCount + }; + + static const Class class_; + static JSObject* initClass(JSContext* cx, HandleObject obj); + static bool isInstance(HandleValue value); + static ExportEntryObject* create(JSContext* cx, + HandleAtom maybeExportName, + HandleAtom maybeModuleRequest, + HandleAtom maybeImportName, + HandleAtom maybeLocalName); + JSAtom* exportName(); + JSAtom* moduleRequest(); + JSAtom* importName(); + JSAtom* localName(); +}; + class ModuleObject : public NativeObject { public: @@ -50,6 +76,9 @@ class ModuleObject : public NativeObject ScriptSlot = 0, RequestedModulesSlot, ImportEntriesSlot, + LocalExportEntriesSlot, + IndirectExportEntriesSlot, + StarExportEntriesSlot, SlotCount }; @@ -60,11 +89,17 @@ class ModuleObject : public NativeObject static ModuleObject* create(ExclusiveContext* cx); void init(HandleScript script); void initImportExportData(HandleArrayObject requestedModules, - HandleArrayObject importEntries); + HandleArrayObject importEntries, + HandleArrayObject localExportEntries, + HandleArrayObject indiretExportEntries, + HandleArrayObject starExportEntries); JSScript* script() const; ArrayObject& requestedModules() const; ArrayObject& importEntries() const; + ArrayObject& localExportEntries() const; + ArrayObject& indirectExportEntries() const; + ArrayObject& starExportEntries() const; private: static void trace(JSTracer* trc, JSObject* obj); @@ -87,15 +122,28 @@ class MOZ_STACK_CLASS ModuleBuilder using RootedAtomVector = JS::Rooted; using ImportEntryVector = TraceableVector; using RootedImportEntryVector = JS::Rooted; + using ExportEntryVector = TraceableVector ; + using RootedExportEntryVector = JS::Rooted ; JSContext* cx_; RootedAtomVector requestedModules_; RootedAtomVector importedBoundNames_; RootedImportEntryVector importEntries_; + RootedExportEntryVector exportEntries_; + RootedExportEntryVector localExportEntries_; + RootedExportEntryVector indirectExportEntries_; + RootedExportEntryVector starExportEntries_; bool processImport(frontend::ParseNode* pn); + bool processExport(frontend::ParseNode* pn); bool processExportFrom(frontend::ParseNode* pn); + ImportEntryObject* importEntryFor(JSAtom* localName); + + bool appendLocalExportEntry(HandleAtom exportName, HandleAtom localName); + bool appendIndirectExportEntry(HandleAtom exportName, HandleAtom moduleRequest, + HandleAtom importName); + bool maybeAppendRequestedModule(HandleAtom module); template @@ -104,6 +152,7 @@ class MOZ_STACK_CLASS ModuleBuilder JSObject* InitModuleClass(JSContext* cx, HandleObject obj); JSObject* InitImportEntryClass(JSContext* cx, HandleObject obj); +JSObject* InitExportEntryClass(JSContext* cx, HandleObject obj); } // namespace js diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 5f6f6ded115d..0b9884f57e62 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -370,7 +370,8 @@ AssertRootMarkingPhase(JSTracer* trc) D(ScriptSourceObject*) \ D(SharedArrayBufferObject*) \ D(SharedTypedArrayObject*) \ - D(ImportEntryObject*) \ + D(ImportEntryObject*) \ + D(ExportEntryObject*) \ D(JSScript*) \ D(LazyScript*) \ D(Shape*) \ diff --git a/js/src/jit-test/tests/modules/export-entries.js b/js/src/jit-test/tests/modules/export-entries.js new file mode 100644 index 000000000000..ea0d6fd3b787 --- /dev/null +++ b/js/src/jit-test/tests/modules/export-entries.js @@ -0,0 +1,121 @@ +// Test localExportEntries property + +function testArrayContents(actual, expected) { + assertEq(actual.length, expected.length); + for (var i = 0; i < actual.length; i++) { + for (var property in expected[i]) { + assertEq(actual[i][property], expected[i][property]); + } + } +} + +function testLocalExportEntries(source, expected) { + var module = parseModule(source); + testArrayContents(module.localExportEntries, expected); +} + +testLocalExportEntries( + 'export var v;', + [{exportName: 'v', moduleRequest: null, importName: null, localName: 'v'}]); + +testLocalExportEntries( + 'export var v = 0;', + [{exportName: 'v', moduleRequest: null, importName: null, localName: 'v'}]); + +testLocalExportEntries( + 'export let x = 1;', + [{exportName: 'x', moduleRequest: null, importName: null, localName: 'x'}]); + +testLocalExportEntries( + 'export const x = 1;', + [{exportName: 'x', moduleRequest: null, importName: null, localName: 'x'}]); + +testLocalExportEntries( + 'export class foo { constructor() {} };', + [{exportName: 'foo', moduleRequest: null, importName: null, localName: 'foo'}]); + +testLocalExportEntries( + 'export default function f() {};', + [{exportName: 'default', moduleRequest: null, importName: null, localName: 'f'}]); + +testLocalExportEntries( + 'export default function() {};', + [{exportName: 'default', moduleRequest: null, importName: null, localName: '*default*'}]); + +testLocalExportEntries( + 'export default 42;', + [{exportName: 'default', moduleRequest: null, importName: null, localName: '*default*'}]); + +testLocalExportEntries( + 'let x = 1; export {x};', + [{exportName: 'x', moduleRequest: null, importName: null, localName: 'x'}]); + +testLocalExportEntries( + 'let v = 1; export {v as x};', + [{exportName: 'x', moduleRequest: null, importName: null, localName: 'v'}]); + +testLocalExportEntries( + 'export {x} from "mod";', + []); + +testLocalExportEntries( + 'export {v as x} from "mod";', + []); + +testLocalExportEntries( + 'export * from "mod";', + []); + +// Test indirectExportEntries property + +function testIndirectExportEntries(source, expected) { + var module = parseModule(source); + testArrayContents(module.indirectExportEntries, expected); +} + +testIndirectExportEntries( + 'export default function f() {};', + []); + +testIndirectExportEntries( + 'let x = 1; export {x};', + []); + +testIndirectExportEntries( + 'export {x} from "mod";', + [{exportName: 'x', moduleRequest: 'mod', importName: 'x', localName: null}]); + +testIndirectExportEntries( + 'export {v as x} from "mod";', + [{exportName: 'x', moduleRequest: 'mod', importName: 'v', localName: null}]); + +testIndirectExportEntries( + 'export * from "mod";', + []); + +testIndirectExportEntries( + 'import {v as x} from "mod"; export {x as y};', + [{exportName: 'y', moduleRequest: 'mod', importName: 'v', localName: null}]); + +// Test starExportEntries property + +function testStarExportEntries(source, expected) { + var module = parseModule(source); + testArrayContents(module.starExportEntries, expected); +} + +testStarExportEntries( + 'export default function f() {};', + []); + +testStarExportEntries( + 'let x = 1; export {x};', + []); + +testStarExportEntries( + 'export {x} from "mod";', + []); + +testStarExportEntries( + 'export * from "mod";', + [{exportName: null, moduleRequest: 'mod', importName: '*', localName: null}]); diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 59b7a8412bb9..26ae92e6061c 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -115,6 +115,7 @@ IF_SAB(real,imaginary)(Atomics, 53, InitAtomicsClass, OCLASP real(Reflect, 55, InitReflect, nullptr) \ real(Module, 56, InitModuleClass, OCLASP(Module)) \ real(ImportEntry, 57, InitImportEntryClass, OCLASP(ImportEntry)) \ + real(ExportEntry, 58, InitExportEntryClass, OCLASP(ExportEntry)) \ #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)