From 725f28c7b597a807a2271b1b9f80d74c9949d6be Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Fri, 4 Sep 2015 14:14:43 +0100 Subject: [PATCH] Bug 930414 - Check for duplicate module exports r=shu --- js/src/frontend/ParseNode.cpp | 1 + js/src/frontend/Parser.cpp | 59 +++++++++++++++++-- js/src/frontend/Parser.h | 1 + js/src/frontend/SharedContext.h | 1 + .../tests/modules/duplicate-exports.js | 20 +++++++ js/src/js.msg | 1 + js/src/vm/Xdr.h | 4 +- 7 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 js/src/jit-test/tests/modules/duplicate-exports.js diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 92e9590da151..6bfec5b73f40 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -1172,6 +1172,7 @@ ObjectBox::trace(JSTracer* trc) } else if (box->isModuleBox()) { ModuleBox* modulebox = box->asModuleBox(); modulebox->bindings.trace(trc); + modulebox->exportNames.trace(trc); } box = box->traceLink; } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 39f17ed3918a..1ba69961f460 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -697,7 +697,8 @@ ModuleBox::ModuleBox(ExclusiveContext* cx, ObjectBox* traceListHead, ModuleObjec ParseContext* outerpc) : ObjectBox(module, traceListHead), SharedContext(cx, Directives(true), false), - bindings() + bindings(), + exportNames(cx) {} template @@ -4553,7 +4554,7 @@ Parser::importDeclaration() { MOZ_ASSERT(tokenStream.currentToken().type == TOK_IMPORT); - if (pc->sc->isFunctionBox() || !pc->atBodyLevel()) { + if (!pc->atModuleLevel()) { report(ParseError, false, null(), JSMSG_IMPORT_DECL_AT_TOP_LEVEL); return null(); } @@ -4649,13 +4650,39 @@ Parser::classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); + +template<> +bool +Parser::addExportName(JSAtom* exportName) +{ + TraceableVector& exportNames = pc->sc->asModuleBox()->exportNames; + for (JSAtom* name : exportNames) { + if (name == exportName) { + JSAutoByteString str; + if (AtomToPrintableString(context, exportName, &str)) + report(ParseError, false, null(), JSMSG_DUPLICATE_EXPORT_NAME, str.ptr()); + return false; + } + } + + return exportNames.append(exportName); +} + +template<> +bool +Parser::addExportName(JSAtom* exportName) +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + template<> ParseNode* Parser::exportDeclaration() { MOZ_ASSERT(tokenStream.currentToken().type == TOK_EXPORT); - if (pc->sc->isFunctionBox() || !pc->atBodyLevel()) { + if (!pc->atModuleLevel()) { report(ParseError, false, null(), JSMSG_EXPORT_DECL_AT_TOP_LEVEL); return null(); } @@ -4705,6 +4732,9 @@ Parser::exportDeclaration() if (!exportName) return null(); + if (!addExportName(exportName->pn_atom)) + return null(); + Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName); if (!exportSpec) return null(); @@ -4756,13 +4786,22 @@ Parser::exportDeclaration() kid = functionStmt(YieldIsKeyword, NameRequired); if (!kid) return null(); + + if (!addExportName(kid->pn_funbox->function()->atom())) + return null(); break; - case TOK_CLASS: + case TOK_CLASS: { kid = classDefinition(YieldIsKeyword, ClassStatement, NameRequired); if (!kid) return null(); + + const ClassNode& cls = kid->as(); + MOZ_ASSERT(cls.names()); + if (!addExportName(cls.names()->innerBinding()->pn_atom)) + return null(); break; + } case TOK_VAR: kid = variables(YieldIsName, PNK_VAR, NotInForInit); @@ -4773,6 +4812,15 @@ Parser::exportDeclaration() kid = MatchOrInsertSemicolon(tokenStream) ? kid : nullptr; if (!kid) return null(); + + 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)); + if (!addExportName(var->pn_atom)) + return null(); + } break; case TOK_DEFAULT: { @@ -4798,6 +4846,9 @@ Parser::exportDeclaration() if (!kid) return null(); + if (!addExportName(context->names().default_)) + return null(); + return handler.newExportDefaultDeclaration(kid, TokenPos(begin, pos().end)); } diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 45cc79fa2605..46105c616763 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -739,6 +739,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter TokenKind tt, unsigned msg); bool namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet); + bool addExportName(JSAtom* exportName); enum ClassContext { ClassStatement, ClassExpression }; Node classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index ad831ccd8ded..00e9d605961a 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -381,6 +381,7 @@ class ModuleBox : public ObjectBox, public SharedContext { public: Bindings bindings; + TraceableVector exportNames; template ModuleBox(ExclusiveContext* cx, ObjectBox* traceListHead, ModuleObject* module, diff --git a/js/src/jit-test/tests/modules/duplicate-exports.js b/js/src/jit-test/tests/modules/duplicate-exports.js new file mode 100644 index 000000000000..2dfabebc1338 --- /dev/null +++ b/js/src/jit-test/tests/modules/duplicate-exports.js @@ -0,0 +1,20 @@ +// Test errors due to duplicate exports + +load(libdir + "asserts.js"); + +function testSyntaxError(source) { + assertThrowsInstanceOf(function () { + parseModule(source); + }, SyntaxError); +} + +testSyntaxError("export var v; export var v;"); +testSyntaxError("export var x, y, z; export var y;"); +testSyntaxError("export default 1; export default 2;"); +testSyntaxError("export var default; export default 1;"); +testSyntaxError("export var default; export default function() {};"); +testSyntaxError("export var default; export default function foo() {};"); +testSyntaxError("export var default; export default export class { constructor() {} };"); +testSyntaxError("export var default; export default export class foo { constructor() {} };"); +testSyntaxError("var v; export {v}; export {v};"); +testSyntaxError("var v, x; export {v}; export {x as v};"); diff --git a/js/src/js.msg b/js/src/js.msg index 5da44fb4b8bd..36b77fe6ce66 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -244,6 +244,7 @@ MSG_DEF(JSMSG_DEPRECATED_LET_BLOCK, 0, JSEXN_NONE, "JavaScript 1.7's let bl MSG_DEF(JSMSG_DEPRECATED_FOR_EACH, 0, JSEXN_NONE, "JavaScript 1.6's for-each-in loops are deprecated; consider using ES6 for-of instead") MSG_DEF(JSMSG_DEPRECATED_OCTAL, 0, JSEXN_SYNTAXERR, "octal literals and octal escape sequences are deprecated") MSG_DEF(JSMSG_DEPRECATED_PRAGMA, 1, JSEXN_NONE, "Using //@ to indicate {0} pragmas is deprecated. Use //# instead") +MSG_DEF(JSMSG_DUPLICATE_EXPORT_NAME, 1, JSEXN_SYNTAXERR, "duplicate export name '{0}'") MSG_DEF(JSMSG_DUPLICATE_FORMAL, 1, JSEXN_SYNTAXERR, "duplicate formal argument {0}") MSG_DEF(JSMSG_DUPLICATE_LABEL, 0, JSEXN_SYNTAXERR, "duplicate label") MSG_DEF(JSMSG_DUPLICATE_PROPERTY, 1, JSEXN_SYNTAXERR, "property name {0} appears more than once in object literal") diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index fa085c47b727..a15784c8819f 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,11 +29,11 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 305; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 306; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 406, +static_assert(JSErr_Limit == 407, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "