From 0b2e8c48181761afba03a0c4afffc87d3a93ac90 Mon Sep 17 00:00:00 2001 From: Martok Date: Wed, 22 Mar 2023 23:46:47 +0100 Subject: [PATCH] Issue #2173 - Fix exporting array and object binding patterns Examples: export const [name1, name2] = [1, 2]; export var {...name} = get; Based-on: m-c 1446811, m-c 1462286 --- js/src/builtin/ModuleObject.cpp | 96 ++++++++++++++++++++++--- js/src/builtin/ModuleObject.h | 4 ++ js/src/frontend/Parser.cpp | 120 +++++++++++++++++++++++++++++--- js/src/frontend/Parser.h | 3 + 4 files changed, 207 insertions(+), 16 deletions(-) diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 2790b1c444..c21660c039 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -1238,14 +1238,25 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) 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; + for (ParseNode* binding = kid->pn_head; binding; binding = binding->pn_next) { + if (binding->isKind(PNK_ASSIGN)) + binding = binding->pn_left; + else + MOZ_ASSERT(binding->isKind(PNK_NAME)); + + if (binding->isKind(PNK_NAME)) { + RootedAtom localName(cx_, binding->pn_atom); + RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); + if (!appendExportEntry(exportName, localName)) + return false; + } else if (binding->isKind(PNK_ARRAY)) { + if (!processExportArrayBinding(binding)) + return false; + } else { + MOZ_ASSERT(binding->isKind(PNK_OBJECT)); + if (!processExportObjectBinding(binding)) + return false; + } } break; } @@ -1268,6 +1279,75 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) return true; } +bool +ModuleBuilder::processExportBinding(frontend::ParseNode* binding) +{ + if (binding->isKind(PNK_NAME)) { + RootedAtom name(cx_, binding->pn_atom); + return appendExportEntry(name, name); + } + + if (binding->isKind(PNK_ARRAY)) + return processExportArrayBinding(binding); + + MOZ_ASSERT(binding->isKind(PNK_OBJECT)); + return processExportObjectBinding(binding); +} + +bool +ModuleBuilder::processExportArrayBinding(frontend::ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_ARRAY)); + MOZ_ASSERT(pn->isArity(PN_LIST)); + + for (ParseNode* node = pn->pn_head; node; node = node->pn_next) { + if (node->isKind(PNK_ELISION)) + continue; + + if (node->isKind(PNK_SPREAD)) + node = node->pn_kid; + else if (node->isKind(PNK_ASSIGN)) + node = node->pn_left; + + if (!processExportBinding(node)) + return false; + } + + return true; +} + +bool +ModuleBuilder::processExportObjectBinding(frontend::ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_OBJECT)); + MOZ_ASSERT(pn->isArity(PN_LIST)); + + for (ParseNode* node = pn->pn_head; node; node = node->pn_next) { + MOZ_ASSERT(node->isKind(PNK_MUTATEPROTO) || + node->isKind(PNK_COLON) || + node->isKind(PNK_SHORTHAND) || + node->isKind(PNK_SPREAD)); + + ParseNode* target; + if (node->isKind(PNK_SPREAD)) { + target = node->pn_kid; + } else { + if (node->isKind(PNK_MUTATEPROTO)) + target = node->pn_kid; + else + target = node->pn_right; + + if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + } + + if (!processExportBinding(target)) + return false; + } + + return true; +} + bool ModuleBuilder::processExportFrom(frontend::ParseNode* pn) { diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index dc078e6b28..f2d94a9c5d 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -352,6 +352,10 @@ class MOZ_STACK_CLASS ModuleBuilder ImportEntryObject* importEntryFor(JSAtom* localName) const; + bool processExportBinding(frontend::ParseNode* pn); + bool processExportArrayBinding(frontend::ParseNode* pn); + bool processExportObjectBinding(frontend::ParseNode* pn); + bool appendExportEntry(HandleAtom exportName, HandleAtom localName, frontend::ParseNode* node = nullptr); bool appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest, diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index cb84a939f6..379455de7e 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -5189,16 +5189,94 @@ Parser::checkExportedName(JSAtom* exportName) return false; } +template<> +bool +Parser::checkExportedNamesForArrayBinding(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_ARRAY)); + MOZ_ASSERT(pn->isArity(PN_LIST)); + + for (ParseNode* node = pn->pn_head; node; node = node->pn_next) { + if (node->isKind(PNK_ELISION)) + continue; + + ParseNode* binding; + if (node->isKind(PNK_SPREAD)) + binding = node->pn_kid; + else if (node->isKind(PNK_ASSIGN)) + binding = node->pn_left; + else + binding = node; + + if (!checkExportedNamesForDeclaration(binding)) + return false; + } + + return true; +} + +template<> +inline bool +Parser::checkExportedNamesForArrayBinding(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +bool +Parser::checkExportedNamesForObjectBinding(ParseNode* pn) +{ + MOZ_ASSERT(pn->isKind(PNK_OBJECT)); + MOZ_ASSERT(pn->isArity(PN_LIST)); + + for (ParseNode* node = pn->pn_head; node; node = node->pn_next) { + MOZ_ASSERT(node->isKind(PNK_MUTATEPROTO) || + node->isKind(PNK_COLON) || + node->isKind(PNK_SHORTHAND) || + node->isKind(PNK_SPREAD)); + + ParseNode* target; + if (node->isKind(PNK_SPREAD)) { + target = node->pn_kid; + } else { + if (node->isKind(PNK_MUTATEPROTO)) + target = node->pn_kid; + else + target = node->pn_right; + + if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + } + + if (!checkExportedNamesForDeclaration(target)) + return false; + } + + return true; +} + +template<> +inline bool +Parser::checkExportedNamesForObjectBinding(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + template<> bool Parser::checkExportedNamesForDeclaration(ParseNode* node) { - MOZ_ASSERT(node->isArity(PN_LIST)); - for (ParseNode* binding = node->pn_head; binding; binding = binding->pn_next) { - if (binding->isKind(PNK_ASSIGN)) - binding = binding->pn_left; - MOZ_ASSERT(binding->isKind(PNK_NAME)); - if (!checkExportedName(binding->pn_atom)) + if (node->isKind(PNK_NAME)) { + if (!checkExportedName(node->pn_atom)) + return false; + } else if (node->isKind(PNK_ARRAY)) { + if (!checkExportedNamesForArrayBinding(node)) + return false; + } else { + MOZ_ASSERT(node->isKind(PNK_OBJECT)); + if (!checkExportedNamesForObjectBinding(node)) return false; } @@ -5213,6 +5291,32 @@ Parser::checkExportedNamesForDeclaration(Node node) return false; } +template<> +bool +Parser::checkExportedNamesForDeclarationList(ParseNode* node) +{ + MOZ_ASSERT(node->isArity(PN_LIST)); + for (ParseNode* binding = node->pn_head; binding; binding = binding->pn_next) { + if (binding->isKind(PNK_ASSIGN)) + binding = binding->pn_left; + else + MOZ_ASSERT(binding->isKind(PNK_NAME)); + + if (!checkExportedNamesForDeclaration(binding)) + return false; + } + + return true; +} + +template<> +bool +Parser::checkExportedNamesForDeclarationList(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + template<> bool Parser::checkExportedNameForClause(ParseNode* node) @@ -5486,7 +5590,7 @@ Parser::exportVariableStatement(uint32_t begin) return null(); if (!matchOrInsertSemicolonAfterExpression()) return null(); - if (!checkExportedNamesForDeclaration(kid)) + if (!checkExportedNamesForDeclarationList(kid)) return null(); Node node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); @@ -5565,7 +5669,7 @@ Parser::exportLexicalDeclaration(uint32_t begin, DeclarationKind k Node kid = lexicalDeclaration(YieldIsName, kind); if (!kid) return null(); - if (!checkExportedNamesForDeclaration(kid)) + if (!checkExportedNamesForDeclarationList(kid)) return null(); Node node = handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 54eb7f4168..4e5e18a265 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1383,7 +1383,10 @@ class Parser final : public ParserBase, private JS::AutoGCRooter bool namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet); bool checkExportedName(JSAtom* exportName); + bool checkExportedNamesForArrayBinding(Node node); + bool checkExportedNamesForObjectBinding(Node node); bool checkExportedNamesForDeclaration(Node node); + bool checkExportedNamesForDeclarationList(Node node); bool checkExportedNameForClause(Node node); bool checkExportedNameForFunction(Node node);