diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 2a1429d99..e95c37df1 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -1890,7 +1890,7 @@ ASTSerializer::unop(ParseNodeKind kind, JSOp op) if (IsDeleteKind(kind)) return UNOP_DELETE; - if (kind == PNK_TYPEOFNAME || kind == PNK_TYPEOFEXPR) + if (IsTypeofKind(kind)) return UNOP_TYPEOF; switch (op) { diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index fd5590079..23e2110db 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -245,6 +245,21 @@ class FullParseHandler return new_(kind, op, pos, kid); } + ParseNode* newUpdate(ParseNodeKind kind, uint32_t begin, ParseNode* kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(kind, JSOP_NOP, pos, kid); + } + + ParseNode* newSpread(uint32_t begin, ParseNode* kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(PNK_SPREAD, JSOP_NOP, pos, kid); + } + + ParseNode* newArrayPush(uint32_t begin, ParseNode* kid) { + TokenPos pos(begin, kid->pn_pos.end); + return new_(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, pos, kid); + } + ParseNode* newBinary(ParseNodeKind kind, JSOp op = JSOP_NOP) { return new_(kind, op, pos(), (ParseNode*) nullptr, (ParseNode*) nullptr); } @@ -734,6 +749,15 @@ class FullParseHandler return false; } + bool isUnparenthesizedUnaryExpression(ParseNode* node) { + if (!node->isInParens()) { + ParseNodeKind kind = node->getKind(); + return kind == PNK_VOID || kind == PNK_NOT || kind == PNK_BITNOT || kind == PNK_POS || + kind == PNK_NEG || IsTypeofKind(kind) || IsDeleteKind(kind); + } + return false; + } + bool isReturnStatement(ParseNode* node) { return node->isKind(PNK_RETURN); } diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 71308c165..67de5661d 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -259,6 +259,12 @@ IsDeleteKind(ParseNodeKind kind) return PNK_DELETENAME <= kind && kind <= PNK_DELETEEXPR; } +inline bool +IsTypeofKind(ParseNodeKind kind) +{ + return PNK_TYPEOFNAME <= kind && kind <= PNK_TYPEOFEXPR; +} + /* * Label Variant Members * ----- ------- ------- @@ -432,7 +438,6 @@ IsDeleteKind(ParseNodeKind kind) * PNK_DELETENAME unary pn_kid: PNK_NAME expr * PNK_DELETEPROP unary pn_kid: PNK_DOT expr * PNK_DELETEELEM unary pn_kid: PNK_ELEM expr - * PNK_DELETESUPERELEM unary pn_kid: PNK_SUPERELEM expr * PNK_DELETEEXPR unary pn_kid: MEMBER expr that's evaluated, then the * overall delete evaluates to true; can't be a kind * for a more-specific PNK_DELETE* unless constant diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 1fe9be615..764bf48b2 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -7350,6 +7350,11 @@ Parser::orExpr1(InHandling inHandling, YieldHandling yieldHandling ParseNodeKind pnk; if (tok == TOK_IN ? inHandling == InAllowed : TokenKindIsBinaryOp(tok)) { + // Report an error for unary expressions on the LHS of **. + if (tok == TOK_POW && handler.isUnparenthesizedUnaryExpression(pn)) { + report(ParseError, false, null(), JSMSG_BAD_POW_LEFTSIDE); + return null(); + } pnk = BinaryOpTokenKindToParseNodeKind(tok); } else { tok = TOK_EOF; @@ -7792,10 +7797,9 @@ Parser::unaryExpr(YieldHandling yieldHandling, TripledotHandling t AssignmentFlavor flavor = (tt == TOK_INC) ? IncrementAssignment : DecrementAssignment; if (!checkAndMarkAsIncOperand(pn2, flavor)) return null(); - return handler.newUnary((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT, - JSOP_NOP, - begin, - pn2); + return handler.newUpdate((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT, + begin, + pn2); } case TOK_DELETE: { @@ -7828,10 +7832,9 @@ Parser::unaryExpr(YieldHandling yieldHandling, TripledotHandling t AssignmentFlavor flavor = (tt == TOK_INC) ? IncrementAssignment : DecrementAssignment; if (!checkAndMarkAsIncOperand(pn, flavor)) return null(); - return handler.newUnary((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT, - JSOP_NOP, - begin, - pn); + return handler.newUpdate((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT, + begin, + pn); } return pn; } @@ -8667,7 +8670,7 @@ Parser::comprehensionTail(GeneratorKind comprehensionKind) return null(); if (comprehensionKind == NotGenerator) - return handler.newUnary(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, begin, bodyExpr); + return handler.newArrayPush(begin, bodyExpr); MOZ_ASSERT(comprehensionKind == StarGenerator); Node yieldExpr = newYieldExpression(begin, bodyExpr); @@ -8794,7 +8797,7 @@ Parser::argumentList(YieldHandling yieldHandling, Node listNode, b if (!argNode) return false; if (spread) { - argNode = handler.newUnary(PNK_SPREAD, JSOP_NOP, begin, argNode); + argNode = handler.newSpread(begin, argNode); if (!argNode) return false; } diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 0af35febb..687ec59e2 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -131,6 +131,11 @@ class SyntaxParseHandler // eventually enforce extraWarnings and will require this then.) NodeUnparenthesizedAssignment, + // This node is necessary to determine if the base operand in an + // exponentiation operation is an unparenthesized unary expression. + // We want to reject |-2 ** 3|, but still need to allow |(-2) ** 3|. + NodeUnparenthesizedUnary, + // This node is necessary to determine if the LHS of a property access is // super related. NodeSuperBase @@ -235,14 +240,26 @@ class SyntaxParseHandler } Node newDelete(uint32_t begin, Node expr) { - return NodeGeneric; + return NodeUnparenthesizedUnary; } Node newTypeof(uint32_t begin, Node kid) { - return NodeGeneric; + return NodeUnparenthesizedUnary; } Node newUnary(ParseNodeKind kind, JSOp op, uint32_t begin, Node kid) { + return NodeUnparenthesizedUnary; + } + + Node newUpdate(ParseNodeKind kind, uint32_t begin, Node kid) { + return NodeGeneric; + } + + Node newSpread(uint32_t begin, Node kid) { + return NodeGeneric; + } + + Node newArrayPush(uint32_t begin, Node kid) { return NodeGeneric; } @@ -429,6 +446,10 @@ class SyntaxParseHandler return node == NodeUnparenthesizedAssignment; } + bool isUnparenthesizedUnaryExpression(Node node) { + return node == NodeUnparenthesizedUnary; + } + bool isReturnStatement(Node node) { return node == NodeReturn; } @@ -467,7 +488,8 @@ class SyntaxParseHandler if (node == NodeUnparenthesizedString || node == NodeUnparenthesizedCommaExpr || node == NodeUnparenthesizedYieldExpr || - node == NodeUnparenthesizedAssignment) + node == NodeUnparenthesizedAssignment || + node == NodeUnparenthesizedUnary) { return NodeGeneric; } diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index 147c8128d..770d77d5b 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1495,11 +1495,9 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) goto out; case '*': -#ifdef JS_HAS_EXPONENTIATION if (matchChar('*')) tp->type = matchChar('=') ? TOK_POWASSIGN : TOK_POW; else -#endif tp->type = matchChar('=') ? TOK_MULASSIGN : TOK_MUL; goto out; diff --git a/js/src/js.msg b/js/src/js.msg index a8e94a693..82405333e 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -185,7 +185,7 @@ MSG_DEF(JSMSG_UNKNOWN_FORMAT, 1, JSEXN_INTERNALERR, "unknown bytecode f // Frontend MSG_DEF(JSMSG_ACCESSOR_WRONG_ARGS, 3, JSEXN_SYNTAXERR, "{0} functions must have {1} argument{2}") MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE, 0, JSEXN_SYNTAXERR, "invalid array comprehension left-hand side") -MSG_DEF(JSMSG_ARRAY_INIT_TOO_BIG, 0, JSEXN_INTERNALERR, "array initialiser too large") +MSG_DEF(JSMSG_ARRAY_INIT_TOO_BIG, 0, JSEXN_INTERNALERR, "array initializer too large") MSG_DEF(JSMSG_AS_AFTER_IMPORT_STAR, 0, JSEXN_SYNTAXERR, "missing keyword 'as' after import *") MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD, 1, JSEXN_SYNTAXERR, "missing keyword 'as' after reserved word '{0}'") MSG_DEF(JSMSG_BAD_ANON_GENERATOR_RETURN, 0, JSEXN_TYPEERR, "anonymous generator function returns a value") @@ -208,6 +208,7 @@ MSG_DEF(JSMSG_BAD_INCOP_OPERAND, 0, JSEXN_REFERENCEERR, "invalid increment MSG_DEF(JSMSG_BAD_METHOD_DEF, 0, JSEXN_SYNTAXERR, "bad method definition") MSG_DEF(JSMSG_BAD_OCTAL, 1, JSEXN_SYNTAXERR, "{0} is not a legal ECMA-262 octal constant") MSG_DEF(JSMSG_BAD_OPERAND, 1, JSEXN_SYNTAXERR, "invalid {0} operand") +MSG_DEF(JSMSG_BAD_POW_LEFTSIDE, 0, JSEXN_SYNTAXERR, "unparenthesized unary expression can't appear on the left-hand side of '**'") MSG_DEF(JSMSG_BAD_PROP_ID, 0, JSEXN_SYNTAXERR, "invalid property id") MSG_DEF(JSMSG_BAD_RETURN_OR_YIELD, 1, JSEXN_SYNTAXERR, "{0} not in function") MSG_DEF(JSMSG_BAD_STRICT_ASSIGN, 1, JSEXN_SYNTAXERR, "can't assign to {0} in strict mode") diff --git a/js/src/jsversion.h b/js/src/jsversion.h index 60100b549..7a4f35a72 100644 --- a/js/src/jsversion.h +++ b/js/src/jsversion.h @@ -37,11 +37,4 @@ */ #define JS_OLD_GETTER_SETTER_METHODS 1 -#ifdef NIGHTLY_BUILD - -/* Support for ES7 Exponentiation proposal. */ -#define JS_HAS_EXPONENTIATION 1 - -#endif // NIGHTLY_BUILD - #endif /* jsversion_h */ diff --git a/js/src/tests/ecma_7/Math/Pow.js b/js/src/tests/ecma_7/Math/Pow.js index c7cba3391..7c1af8c88 100644 --- a/js/src/tests/ecma_7/Math/Pow.js +++ b/js/src/tests/ecma_7/Math/Pow.js @@ -9,8 +9,6 @@ var summary = "Implement the exponentiation operator"; print(BUGNUMBER + ": " + summary); -var test = ` - // Constant folding assertEq(2 ** 2 ** 3, 256); assertEq(1 ** 1 ** 4, 1); @@ -53,6 +51,28 @@ assertEq(a**/**b**/c/**/**/**d**/e, 16); // Two stars separated should not parse as exp operator assertThrows(function() { return Reflect.parse("2 * * 3"); }, SyntaxError); +// Left-hand side expression must not be a unary expression. +for (let unaryOp of ["delete", "typeof", "void", "+", "-", "!", "~"]) { + assertThrowsInstanceOf(() => eval(unaryOp + " a ** 2"), SyntaxError); + assertThrowsInstanceOf(() => eval(unaryOp + " " + unaryOp + " a ** 2"), SyntaxError); +} + +// Test the other |delete| operators (DELETENAME and DELETEEXPR are already tested above). +assertThrowsInstanceOf(() => eval("delete a.name ** 2"), SyntaxError); +assertThrowsInstanceOf(() => eval("delete a[0] ** 2"), SyntaxError); + +// Unary expression lhs is valid if parenthesized. +for (let unaryOp of ["delete", "void", "+", "-", "!", "~"]) { + let a = 0; + eval("(" + unaryOp + " a) ** 2"); + eval("(" + unaryOp + " " + unaryOp + " a) ** 2"); +} +{ + let a = {}; + (delete a.name) ** 2; + (delete a[0]) ** 2; +} + // Check if error propagation works var thrower = { get value() { @@ -93,26 +113,5 @@ assertEq(parseTree.body[0].expression.right.operator, "**"); assertEq(parseTree.body[0].expression.right.left.name, "b"); assertEq(parseTree.body[0].expression.right.right.name, "c"); - -function assertTrue(v) { - assertEq(v, true); -} - -function assertFalse(v) { - assertEq(v, false); -} -`; - -function exponentiationEnabled() { - try { - Function("1 ** 1"); - return true; - } catch (e if e instanceof SyntaxError) { } - return false; -} - -if (exponentiationEnabled()) - eval(test); - if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 0b3262408..fa796bcda 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 = 335; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 336; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 433, +static_assert(JSErr_Limit == 434, "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 "