diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 7ddd8d306..c7a7e0843 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -878,6 +878,10 @@ class FullParseHandler return node->isKind(PNK_NAME); } + bool isArgumentsAnyParentheses(ParseNode* node, ExclusiveContext* cx) { + return node->isKind(PNK_NAME) && node->pn_atom == cx->names().arguments; + } + bool isEvalAnyParentheses(ParseNode* node, ExclusiveContext* cx) { return node->isKind(PNK_NAME) && node->pn_atom == cx->names().eval; } @@ -888,7 +892,7 @@ class FullParseHandler if (isEvalAnyParentheses(node, cx)) return js_eval_str; - if (node->pn_atom == cx->names().arguments) + if (isArgumentsAnyParentheses(node, cx)) return js_arguments_str; return nullptr; } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 3154e41d5..c1e165db5 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -689,6 +689,19 @@ ParserBase::extraWarning(unsigned errorNumber, ...) return result; } +bool +ParserBase::extraWarningAt(uint32_t offset, unsigned errorNumber, ...) +{ + va_list args; + va_start(args, errorNumber); + + bool result = + tokenStream.reportExtraWarningErrorNumberVA(nullptr, offset, errorNumber, args); + + va_end(args); + return result; +} + bool ParserBase::strictModeError(unsigned errorNumber, ...) { @@ -743,14 +756,6 @@ ParserBase::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumb return result; } -template <> -bool -Parser::abortIfSyntaxParser() -{ - handler.disableSyntaxParser(); - return true; -} - template <> bool Parser::abortIfSyntaxParser() @@ -4128,8 +4133,17 @@ Parser::PossibleError::error(ErrorKind kind) { if (kind == ErrorKind::Expression) return exprError_; - MOZ_ASSERT(kind == ErrorKind::Destructuring); - return destructuringError_; + if (kind == ErrorKind::Destructuring) + return destructuringError_; + MOZ_ASSERT(kind == ErrorKind::DestructuringWarning); + return destructuringWarning_; +} + +template +bool +Parser::PossibleError::hasPendingDestructuringError() +{ + return hasError(ErrorKind::Destructuring); } template @@ -4171,6 +4185,14 @@ Parser::PossibleError::setPendingDestructuringErrorAt(const TokenP setPending(ErrorKind::Destructuring, pos, errorNumber); } +template +void +Parser::PossibleError::setPendingDestructuringWarningAt(const TokenPos& pos, + unsigned errorNumber) +{ + setPending(ErrorKind::DestructuringWarning, pos, errorNumber); +} + template void Parser::PossibleError::setPendingExpressionErrorAt(const TokenPos& pos, @@ -4193,23 +4215,36 @@ Parser::PossibleError::checkForError(ErrorKind kind) template bool -Parser::PossibleError::checkForDestructuringError() +Parser::PossibleError::checkForWarning(ErrorKind kind) +{ + if (!hasError(kind)) + return true; + + Error& err = error(kind); + return parser_.extraWarningAt(err.offset_, err.errorNumber_); +} + +template +bool +Parser::PossibleError::checkForDestructuringErrorOrWarning() { // Clear pending expression error, because we're definitely not in an // expression context. setResolved(ErrorKind::Expression); - // Report any pending destructuring error. - return checkForError(ErrorKind::Destructuring); + // Report any pending destructuring error or warning. + return checkForError(ErrorKind::Destructuring) && + checkForWarning(ErrorKind::DestructuringWarning); } template bool Parser::PossibleError::checkForExpressionError() { - // Clear pending destructuring error, because we're definitely not in a - // destructuring context. + // Clear pending destructuring error or warning, because we're definitely + // not in a destructuring context. setResolved(ErrorKind::Destructuring); + setResolved(ErrorKind::DestructuringWarning); // Report any pending expression error. return checkForError(ErrorKind::Expression); @@ -4241,192 +4276,258 @@ Parser::PossibleError::transferErrorsTo(PossibleError* other) transferErrorTo(ErrorKind::Expression, other); } -template <> -bool -Parser::checkDestructuringName(ParseNode* expr, Maybe maybeDecl) +template +typename ParseHandler::Node +Parser::bindingInitializer(Node lhs, DeclarationKind kind, + YieldHandling yieldHandling) { - MOZ_ASSERT(!handler.isUnparenthesizedDestructuringPattern(expr)); + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_ASSIGN)); - // Parentheses are forbidden around destructuring *patterns* (but allowed - // around names). Use our nicer error message for parenthesized, nested - // patterns. - if (handler.isParenthesizedDestructuringPattern(expr)) { - errorAt(expr->pn_pos.begin, JSMSG_BAD_DESTRUCT_PARENS); - return false; + if (kind == DeclarationKind::FormalParameter) + pc->functionBox()->hasParameterExprs = true; + + Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!rhs) + return null(); + + handler.checkAndSetIsDirectRHSAnonFunction(rhs); + + Node assign = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP); + if (!assign) + return null(); + + if (foldConstants && !FoldConstants(context, &assign, this)) + return null(); + + return assign; +} + +template +typename ParseHandler::Node +Parser::bindingIdentifier(DeclarationKind kind, YieldHandling yieldHandling) +{ + RootedPropertyName name(context, bindingIdentifier(yieldHandling)); + if (!name) + return null(); + + Node binding = newName(name); + if (!binding || !noteDeclaredName(name, kind, pos())) + return null(); + + return binding; +} + +template +typename ParseHandler::Node +Parser::bindingIdentifierOrPattern(DeclarationKind kind, YieldHandling yieldHandling, + TokenKind tt) +{ + if (tt == TOK_LB) + return arrayBindingPattern(kind, yieldHandling); + + if (tt == TOK_LC) + return objectBindingPattern(kind, yieldHandling); + + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_NO_VARIABLE_NAME); + return null(); } - // This expression might be in a variable-binding pattern where only plain, - // unparenthesized names are permitted. - if (maybeDecl) { - // Destructuring patterns in declarations must only contain - // unparenthesized names. - if (!handler.isUnparenthesizedName(expr)) { - errorAt(expr->pn_pos.begin, JSMSG_NO_VARIABLE_NAME); - return false; + return bindingIdentifier(kind, yieldHandling); +} + +template +typename ParseHandler::Node +Parser::objectBindingPattern(DeclarationKind kind, YieldHandling yieldHandling) +{ + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); + + JS_CHECK_RECURSION(context, return null()); + + uint32_t begin = pos().begin; + Node literal = handler.newObjectLiteral(begin); + if (!literal) + return null(); + + Maybe declKind = Some(kind); + RootedAtom propAtom(context); + for (;;) { + TokenKind tt; + if (!tokenStream.getToken(&tt)) + return null(); + if (tt == TOK_RC) + break; + + TokenPos namePos = pos(); + + tokenStream.ungetToken(); + + PropertyType propType; + Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom); + if (!propName) + return null(); + + if (propType == PropertyType::Normal) { + // Handle e.g., |var {p: x} = o| and |var {p: x=0} = o|. + + if (!tokenStream.getToken(&tt, TokenStream::Operand)) + return null(); + + Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!binding) + return null(); + + bool hasInitializer; + if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) + return null(); + + Node bindingExpr = hasInitializer + ? bindingInitializer(binding, kind, yieldHandling) + : binding; + if (!bindingExpr) + return null(); + + if (!handler.addPropertyDefinition(literal, propName, bindingExpr)) + return null(); + } else if (propType == PropertyType::Shorthand) { + // Handle e.g., |var {x, y} = o| as destructuring shorthand + // for |var {x: x, y: y} = o|. + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt)); + + Node binding = bindingIdentifier(kind, yieldHandling); + if (!binding) + return null(); + + if (!handler.addShorthand(literal, propName, binding)) + return null(); + } else if (propType == PropertyType::CoverInitializedName) { + // Handle e.g., |var {x=1, y=2} = o| as destructuring shorthand + // with default values. + MOZ_ASSERT(TokenKindIsPossibleIdentifierName(tt)); + + Node binding = bindingIdentifier(kind, yieldHandling); + if (!binding) + return null(); + + tokenStream.consumeKnownToken(TOK_ASSIGN); + + Node bindingExpr = bindingInitializer(binding, kind, yieldHandling); + if (!bindingExpr) + return null(); + + if (!handler.addPropertyDefinition(literal, propName, bindingExpr)) + return null(); + } else { + errorAt(namePos.begin, JSMSG_NO_VARIABLE_NAME); + return null(); } - RootedPropertyName name(context, expr->name()); - // `yield` is already checked, so pass YieldIsName to skip that check. - if (!checkBindingIdentifier(name, expr->pn_pos.begin, YieldIsName)) - return false; - return noteDeclaredName(name, *maybeDecl, expr->pn_pos); - } - - // Otherwise this is an expression in destructuring outside a declaration. - if (handler.isNameAnyParentheses(expr)) { - if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(expr, context)) { - if (!strictModeErrorAt(expr->pn_pos.begin, JSMSG_BAD_STRICT_ASSIGN, chars)) - return false; + if (!tokenStream.getToken(&tt)) + return null(); + if (tt == TOK_RC) + break; + if (tt != TOK_COMMA) { + reportMissingClosing(JSMSG_CURLY_AFTER_LIST, JSMSG_CURLY_OPENED, begin); + return null(); } - - return true; } - if (handler.isPropertyAccess(expr)) - return true; + handler.setEndPosition(literal, pos().end); + return literal; +} - errorAt(expr->pn_pos.begin, JSMSG_BAD_DESTRUCT_TARGET); - return false; +template +typename ParseHandler::Node +Parser::arrayBindingPattern(DeclarationKind kind, YieldHandling yieldHandling) +{ + MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_LB)); + + JS_CHECK_RECURSION(context, return null()); + + uint32_t begin = pos().begin; + Node literal = handler.newArrayLiteral(begin); + if (!literal) + return null(); + + uint32_t index = 0; + TokenStream::Modifier modifier = TokenStream::Operand; + for (; ; index++) { + if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) { + error(JSMSG_ARRAY_INIT_TOO_BIG); + return null(); + } + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::Operand)) + return null(); + + if (tt == TOK_RB) { + tokenStream.ungetToken(); + break; + } + + if (tt == TOK_COMMA) { + if (!handler.addElision(literal, pos())) + return null(); + } else if (tt == TOK_TRIPLEDOT) { + uint32_t begin = pos().begin; + + TokenKind tt; + if (!tokenStream.getToken(&tt, TokenStream::Operand)) + return null(); + + Node inner = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!inner) + return null(); + + if (!handler.addSpreadElement(literal, begin, inner)) + return null(); + } else { + Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt); + if (!binding) + return null(); + + bool hasInitializer; + if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) + return null(); + + Node element = hasInitializer + ? bindingInitializer(binding, kind, yieldHandling) + : binding; + if (!element) + return null(); + + handler.addArrayElement(literal, element); + } + + if (tt != TOK_COMMA) { + // If we didn't already match TOK_COMMA in above case. + bool matched; + if (!tokenStream.matchToken(&matched, TOK_COMMA)) + return null(); + if (!matched) { + modifier = TokenStream::None; + break; + } + if (tt == TOK_TRIPLEDOT) { + error(JSMSG_REST_WITH_COMMA); + return null(); + } + } + } + + MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RB, modifier, + reportMissingClosing(JSMSG_BRACKET_AFTER_LIST, + JSMSG_BRACKET_OPENED, begin)); + + handler.setEndPosition(literal, pos().end); + return literal; } template <> bool -Parser::checkDestructuringPattern(ParseNode* pattern, - Maybe maybeDecl, - PossibleError* possibleError /* = nullptr */); - -template <> -bool -Parser::checkDestructuringObject(ParseNode* objectPattern, - Maybe maybeDecl) -{ - MOZ_ASSERT(objectPattern->isKind(PNK_OBJECT)); - - for (ParseNode* member = objectPattern->pn_head; member; member = member->pn_next) { - ParseNode* target; - if (member->isKind(PNK_MUTATEPROTO)) { - target = member->pn_kid; - } else { - MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); - MOZ_ASSERT_IF(member->isKind(PNK_SHORTHAND), - member->pn_left->isKind(PNK_OBJECT_PROPERTY_NAME) && - member->pn_right->isKind(PNK_NAME) && - member->pn_left->pn_atom == member->pn_right->pn_atom); - - target = member->pn_right; - } - if (handler.isUnparenthesizedAssignment(target)) - target = target->pn_left; - - if (handler.isUnparenthesizedDestructuringPattern(target)) { - if (!checkDestructuringPattern(target, maybeDecl)) - return false; - } else { - if (!checkDestructuringName(target, maybeDecl)) - return false; - } - } - - return true; -} - -template <> -bool -Parser::checkDestructuringArray(ParseNode* arrayPattern, - Maybe maybeDecl) -{ - MOZ_ASSERT(arrayPattern->isKind(PNK_ARRAY)); - - for (ParseNode* element = arrayPattern->pn_head; element; element = element->pn_next) { - if (element->isKind(PNK_ELISION)) - continue; - - ParseNode* target; - if (element->isKind(PNK_SPREAD)) { - if (element->pn_next) { - errorAt(element->pn_next->pn_pos.begin, JSMSG_PARAMETER_AFTER_REST); - return false; - } - target = element->pn_kid; - } else if (handler.isUnparenthesizedAssignment(element)) { - target = element->pn_left; - } else { - target = element; - } - - if (handler.isUnparenthesizedDestructuringPattern(target)) { - if (!checkDestructuringPattern(target, maybeDecl)) - return false; - } else { - if (!checkDestructuringName(target, maybeDecl)) - return false; - } - } - - return true; -} - -/* - * Destructuring patterns can appear in two kinds of contexts: - * - * - assignment-like: assignment expressions and |for| loop heads. In - * these cases, the patterns' property value positions can be - * arbitrary lvalue expressions; the destructuring is just a fancy - * assignment. - * - * - binding-like: |var| and |let| declarations, functions' formal - * parameter lists, |catch| clauses, and comprehension tails. In - * these cases, the patterns' property value positions must be - * simple names; the destructuring defines them as new variables. - * - * In both cases, other code parses the pattern as an arbitrary - * primaryExpr, and then, here in checkDestructuringPattern, verify - * that the tree is a valid AssignmentPattern or BindingPattern. - * - * In assignment-like contexts, we parse the pattern with - * pc->inDestructuringDecl clear, so the lvalue expressions in the - * pattern are parsed normally. primaryExpr links variable references - * into the appropriate use chains; creates placeholder definitions; - * and so on. checkDestructuringPattern won't bind any new names and - * we specialize lvalues as appropriate. - * - * In declaration-like contexts, the normal variable reference - * processing would just be an obstruction, because we're going to - * define the names that appear in the property value positions as new - * variables anyway. In this case, we parse the pattern with - * pc->inDestructuringDecl set, which directs primaryExpr to leave - * whatever name nodes it creates unconnected. Then, here in - * checkDestructuringPattern, we require the pattern's property value - * positions to be simple names, and define them as appropriate to the - * context. - */ -template <> -bool -Parser::checkDestructuringPattern(ParseNode* pattern, - Maybe maybeDecl, - PossibleError* possibleError /* = nullptr */) -{ - if (pattern->isKind(PNK_ARRAYCOMP)) { - errorAt(pattern->pn_pos.begin, JSMSG_ARRAY_COMP_LEFTSIDE); - return false; - } - - bool isDestructuring = pattern->isKind(PNK_ARRAY) - ? checkDestructuringArray(pattern, maybeDecl) - : checkDestructuringObject(pattern, maybeDecl); - - // Report any pending destructuring error. - if (isDestructuring && possibleError && !possibleError->checkForDestructuringError()) - return false; - - return isDestructuring; -} - -template <> -bool -Parser::checkDestructuringPattern(Node pattern, - Maybe maybeDecl, - PossibleError* possibleError /* = nullptr */) +Parser::checkDestructuringAssignmentPattern(Node pattern, + PossibleError* possibleError /* = nullptr */) { return abortIfSyntaxParser(); } @@ -4439,18 +4540,9 @@ Parser::destructuringDeclaration(DeclarationKind kind, YieldHandli MOZ_ASSERT(tokenStream.isCurrentTokenType(tt)); MOZ_ASSERT(tt == TOK_LB || tt == TOK_LC); - PossibleError possibleError(*this); - Node pattern; - { - pc->inDestructuringDecl = Some(kind); - pattern = primaryExpr(yieldHandling, TripledotProhibited, tt, &possibleError); - pc->inDestructuringDecl = Nothing(); - } - - if (!pattern || !checkDestructuringPattern(pattern, Some(kind), &possibleError)) - return null(); - - return pattern; + return tt == TOK_LB + ? arrayBindingPattern(kind, yieldHandling) + : objectBindingPattern(kind, yieldHandling); } template @@ -4564,6 +4656,12 @@ Parser::declarationPattern(Node decl, DeclarationKind declKind, To // binary operator (examined with modifier None) terminated |init|. // For all other declarations, through ASI's infinite majesty, a next // token on a new line would begin an expression. + // Similar to the case in initializerInNameDeclaration(), we need to + // peek at the next token when assignExpr() is a lazily parsed arrow + // function. + TokenKind ignored; + if (!tokenStream.peekToken(&ignored)) + return null(); tokenStream.addModifierException(TokenStream::OperandIsNone); } @@ -4946,14 +5044,6 @@ Parser::namedImportsOrNamespaceImport(TokenKind tt, Node impor return true; } -template<> -bool -Parser::namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet) -{ - MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); - return false; -} - template<> ParseNode* Parser::importDeclaration() @@ -5975,7 +6065,7 @@ Parser::forHeadStart(YieldHandling yieldHandling, // Verify the left-hand side expression doesn't have a forbidden form. if (handler.isUnparenthesizedDestructuringPattern(*forInitialPart)) { - if (!checkDestructuringPattern(*forInitialPart, Nothing(), &possibleError)) + if (!possibleError.checkForDestructuringErrorOrWarning()) return false; } else if (handler.isNameAnyParentheses(*forInitialPart)) { const char* chars = handler.nameIsArgumentsEvalAnyParentheses(*forInitialPart, context); @@ -6820,14 +6910,10 @@ Parser::tryStatement(YieldHandling yieldHandling) return null(); } - RootedPropertyName param(context, bindingIdentifier(yieldHandling)); - if (!param) - return null(); - catchName = newName(param); + catchName = bindingIdentifier(DeclarationKind::SimpleCatchParameter, + yieldHandling); if (!catchName) return null(); - if (!noteDeclaredName(param, DeclarationKind::SimpleCatchParameter, pos())) - return null(); break; } } @@ -7055,6 +7141,7 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!classMethods) return null(); + Maybe declKind = Nothing(); for (;;) { TokenKind tt; if (!tokenStream.getToken(&tt)) @@ -7089,7 +7176,7 @@ Parser::classDefinition(YieldHandling yieldHandling, return null(); PropertyType propType; - Node propName = propertyName(yieldHandling, classMethods, &propType, &propAtom); + Node propName = propertyName(yieldHandling, declKind, classMethods, &propType, &propAtom); if (!propName) return null(); @@ -7920,26 +8007,6 @@ Parser::condExpr1(InHandling inHandling, YieldHandling yieldHandli return handler.newConditional(condition, thenExpr, elseExpr); } -class AutoClearInDestructuringDecl -{ - ParseContext* pc_; - Maybe saved_; - - public: - explicit AutoClearInDestructuringDecl(ParseContext* pc) - : pc_(pc), - saved_(pc->inDestructuringDecl) - { - pc->inDestructuringDecl = Nothing(); - if (saved_ && *saved_ == DeclarationKind::FormalParameter) - pc->functionBox()->hasParameterExprs = true; - } - - ~AutoClearInDestructuringDecl() { - pc_->inDestructuringDecl = saved_; - } -}; - template typename ParseHandler::Node Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandling, @@ -7964,7 +8031,7 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl if (!tokenStream.getToken(&tt, TokenStream::Operand)) return null(); - uint32_t exprOffset = pos().begin; + TokenPos exprPos = pos(); bool endsExpr; @@ -8170,12 +8237,12 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl return null(); } - if (!checkDestructuringPattern(lhs, Nothing(), &possibleErrorInner)) + if (!possibleErrorInner.checkForDestructuringErrorOrWarning()) return null(); } else if (handler.isNameAnyParentheses(lhs)) { if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(lhs, context)) { // |chars| is "arguments" or "eval" here. - if (!strictModeErrorAt(exprOffset, JSMSG_BAD_STRICT_ASSIGN, chars)) + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_STRICT_ASSIGN, chars)) return null(); } @@ -8183,23 +8250,22 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl } else if (handler.isPropertyAccess(lhs)) { // Permitted: no additional testing/fixup needed. } else if (handler.isFunctionCall(lhs)) { - if (!strictModeErrorAt(exprOffset, JSMSG_BAD_LEFTSIDE_OF_ASS)) + if (!strictModeErrorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS)) return null(); + + if (possibleError) + possibleError->setPendingDestructuringErrorAt(exprPos, JSMSG_BAD_DESTRUCT_TARGET); } else { - errorAt(exprOffset, JSMSG_BAD_LEFTSIDE_OF_ASS); + errorAt(exprPos.begin, JSMSG_BAD_LEFTSIDE_OF_ASS); return null(); } if (!possibleErrorInner.checkForExpressionError()) return null(); - Node rhs; - { - AutoClearInDestructuringDecl autoClear(pc); - rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited); - if (!rhs) - return null(); - } + Node rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited); + if (!rhs) + return null(); if (kind == PNK_ASSIGN) handler.checkAndSetIsDirectRHSAnonFunction(rhs); @@ -9144,7 +9210,7 @@ Parser::identifierReference(Handle name) if (!pn) return null(); - if (!pc->inDestructuringDecl && !noteUsedName(name)) + if (!noteUsedName(name)) return null(); return pn; @@ -9210,6 +9276,74 @@ Parser::newRegExp() return handler.newRegExp(reobj, pos(), *this); } +template +void +Parser::checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos, + PossibleError* possibleError) +{ + // Return early if a pending destructuring error is already present. + if (possibleError->hasPendingDestructuringError()) + return; + + if (pc->sc()->needStrictChecks()) { + if (handler.isArgumentsAnyParentheses(expr, context)) { + if (pc->sc()->strict()) { + possibleError->setPendingDestructuringErrorAt(exprPos, + JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS); + } else { + possibleError->setPendingDestructuringWarningAt(exprPos, + JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS); + } + return; + } + + if (handler.isEvalAnyParentheses(expr, context)) { + if (pc->sc()->strict()) { + possibleError->setPendingDestructuringErrorAt(exprPos, + JSMSG_BAD_STRICT_ASSIGN_EVAL); + } else { + possibleError->setPendingDestructuringWarningAt(exprPos, + JSMSG_BAD_STRICT_ASSIGN_EVAL); + } + return; + } + } + + // The expression must be either a simple assignment target, i.e. a name + // or a property accessor, or a nested destructuring pattern. + if (!handler.isUnparenthesizedDestructuringPattern(expr) && + !handler.isNameAnyParentheses(expr) && + !handler.isPropertyAccess(expr)) + { + // Parentheses are forbidden around destructuring *patterns* (but + // allowed around names). Use our nicer error message for + // parenthesized, nested patterns. + if (handler.isParenthesizedDestructuringPattern(expr)) + possibleError->setPendingDestructuringErrorAt(exprPos, JSMSG_BAD_DESTRUCT_PARENS); + else + possibleError->setPendingDestructuringErrorAt(exprPos, JSMSG_BAD_DESTRUCT_TARGET); + } +} + +template +void +Parser::checkDestructuringAssignmentElement(Node expr, TokenPos exprPos, + PossibleError* possibleError) +{ + // ES2018 draft rev 0719f44aab93215ed9a626b2f45bd34f36916834 + // 12.15.5 Destructuring Assignment + // + // AssignmentElement[Yield, Await]: + // DestructuringAssignmentTarget[?Yield, ?Await] + // DestructuringAssignmentTarget[?Yield, ?Await] Initializer[+In, ?Yield, ?Await] + + // If |expr| is an assignment element with an initializer expression, its + // destructuring assignment target was already validated in assignExpr(). + // Otherwise we need to check that |expr| is a valid destructuring target. + if (!handler.isUnparenthesizedAssignment(expr)) + checkDestructuringAssignmentTarget(expr, exprPos, possibleError); +} + template typename ParseHandler::Node Parser::arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError) @@ -9259,17 +9393,29 @@ Parser::arrayInitializer(YieldHandling yieldHandling, PossibleErro } else if (tt == TOK_TRIPLEDOT) { tokenStream.consumeKnownToken(TOK_TRIPLEDOT, TokenStream::Operand); uint32_t begin = pos().begin; + + TokenPos innerPos; + if (!tokenStream.peekTokenPos(&innerPos, TokenStream::Operand)) + return null(); + Node inner = assignExpr(InAllowed, yieldHandling, TripledotProhibited, possibleError); if (!inner) return null(); + if (possibleError) + checkDestructuringAssignmentTarget(inner, innerPos, possibleError); if (!handler.addSpreadElement(literal, begin, inner)) return null(); } else { + TokenPos elementPos; + if (!tokenStream.peekTokenPos(&elementPos, TokenStream::Operand)) + return null(); Node element = assignExpr(InAllowed, yieldHandling, TripledotProhibited, possibleError); if (!element) return null(); + if (possibleError) + checkDestructuringAssignmentElement(element, elementPos, possibleError); if (foldConstants && !FoldConstants(context, &element, this)) return null(); handler.addArrayElement(literal, element); @@ -9307,7 +9453,8 @@ DoubleToAtom(ExclusiveContext* cx, double value) template typename ParseHandler::Node -Parser::propertyName(YieldHandling yieldHandling, Node propList, +Parser::propertyName(YieldHandling yieldHandling, + const Maybe& maybeDecl, Node propList, PropertyType* propType, MutableHandleAtom propAtom) { TokenKind ltok; @@ -9368,7 +9515,7 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, break; case TOK_LB: - propName = computedPropertyName(yieldHandling, propList); + propName = computedPropertyName(yieldHandling, maybeDecl, propList); if (!propName) return null(); break; @@ -9426,7 +9573,7 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, if (tt == TOK_LB) { tokenStream.consumeKnownToken(TOK_LB); - return computedPropertyName(yieldHandling, propList); + return computedPropertyName(yieldHandling, maybeDecl, propList); } // Not an accessor property after all. @@ -9496,28 +9643,25 @@ Parser::propertyName(YieldHandling yieldHandling, Node propList, template typename ParseHandler::Node -Parser::computedPropertyName(YieldHandling yieldHandling, Node literal) +Parser::computedPropertyName(YieldHandling yieldHandling, + const Maybe& maybeDecl, + Node literal) { uint32_t begin = pos().begin; - Node assignNode; - { - // Turn off the inDestructuringDecl flag when parsing computed property - // names. In short, when parsing 'let {[x + y]: z} = obj;', noteUsedName() - // should be called on x and y, but not on z. See the comment on - // Parser<>::checkDestructuringPattern() for details. - AutoClearInDestructuringDecl autoClear(pc); - assignNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited); - if (!assignNode) - return null(); + if (maybeDecl) { + if (*maybeDecl == DeclarationKind::FormalParameter) + pc->functionBox()->hasParameterExprs = true; + } else { + handler.setListFlag(literal, PNX_NONCONST); } - MUST_MATCH_TOKEN(TOK_RB, JSMSG_COMP_PROP_UNTERM_EXPR); - Node propname = handler.newComputedName(assignNode, begin, pos().end); - if (!propname) + Node assignNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!assignNode) return null(); - handler.setListFlag(literal, PNX_NONCONST); - return propname; + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_COMP_PROP_UNTERM_EXPR); + return handler.newComputedName(assignNode, begin, pos().end); } template @@ -9534,6 +9678,7 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* bool seenPrototypeMutation = false; bool seenCoverInitializedName = false; + Maybe declKind = Nothing(); RootedAtom propAtom(context); for (;;) { TokenKind tt; @@ -9547,11 +9692,14 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* tokenStream.ungetToken(); PropertyType propType; - Node propName = propertyName(yieldHandling, literal, &propType, &propAtom); + Node propName = propertyName(yieldHandling, declKind, literal, &propType, &propAtom); if (!propName) return null(); if (propType == PropertyType::Normal) { + TokenPos exprPos; + if (!tokenStream.peekTokenPos(&exprPos, TokenStream::Operand)) + return null(); Node propExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited, possibleError); if (!propExpr) @@ -9591,10 +9739,13 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!handler.addPropertyDefinition(literal, propName, propExpr)) return null(); } + + if (possibleError) + checkDestructuringAssignmentElement(propExpr, exprPos, possibleError); } else if (propType == PropertyType::Shorthand) { /* - * Support, e.g., |var {x, y} = o| as destructuring shorthand - * for |var {x: x, y: y} = o|, and |var o = {x, y}| as initializer + * Support, e.g., |({x, y} = o)| as destructuring shorthand + * for |({x: x, y: y} = o)|, and |var o = {x, y}| as initializer * shorthand for |var o = {x: x, y: y}|. */ Rooted name(context, identifierReference(yieldHandling)); @@ -9605,11 +9756,14 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!nameExpr) return null(); + if (possibleError) + checkDestructuringAssignmentTarget(nameExpr, namePos, possibleError); + if (!handler.addShorthand(literal, propName, nameExpr)) return null(); } else if (propType == PropertyType::CoverInitializedName) { /* - * Support, e.g., |var {x=1, y=2} = o| as destructuring shorthand + * Support, e.g., |({x=1, y=2} = o)| as destructuring shorthand * with default values, as per ES6 12.14.5 */ Rooted name(context, identifierReference(yieldHandling)); @@ -9641,16 +9795,16 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError->setPendingExpressionErrorAt(pos(), JSMSG_COLON_AFTER_ID); } - Node rhs; - { - // Clearing `inDestructuringDecl` allows name use to be noted - // in Parser::identifierReference. See bug 1255167. - AutoClearInDestructuringDecl autoClear(pc); - rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited); - if (!rhs) + if (const char* chars = handler.nameIsArgumentsEvalAnyParentheses(lhs, context)) { + // |chars| is "arguments" or "eval" here. + if (!strictModeErrorAt(namePos.begin, JSMSG_BAD_STRICT_ASSIGN, chars)) return null(); } + Node rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited); + if (!rhs) + return null(); + handler.checkAndSetIsDirectRHSAnonFunction(rhs); Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP); @@ -9659,9 +9813,6 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* if (!handler.addPropertyDefinition(literal, propName, propExpr)) return null(); - - if (!abortIfSyntaxParser()) - return null(); } else { RootedAtom funName(context); if (!tokenStream.isCurrentTokenType(TOK_RB)) { @@ -9683,6 +9834,9 @@ Parser::objectLiteral(YieldHandling yieldHandling, PossibleError* JSOp op = JSOpFromPropertyType(propType); if (!handler.addObjectMethodDefinition(literal, propName, fn, op)) return null(); + + if (possibleError) + possibleError->setPendingDestructuringErrorAt(namePos, JSMSG_BAD_DESTRUCT_TARGET); } if (!tokenStream.getToken(&tt)) diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index b1d3bdee0..88d2dad18 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -340,17 +340,6 @@ class ParseContext : public Nestable // pointer may be nullptr. Directives* newDirectives; - // Set when parsing a declaration-like destructuring pattern. This flag - // causes PrimaryExpr to create PN_NAME parse nodes for variable references - // which are not hooked into any definition's use chain, added to any tree - // context's AtomList, etc. etc. checkDestructuring will do that work - // later. - // - // The comments atop checkDestructuring explain the distinction between - // assignment-like and declaration-like destructuring patterns, and why - // they need to be treated differently. - mozilla::Maybe inDestructuringDecl; - // Set when parsing a function and it has 'return ;' bool funHasReturnExpr; @@ -888,6 +877,12 @@ class ParserBase : public StrictModeGetter */ MOZ_MUST_USE bool extraWarning(unsigned errorNumber, ...); + /* + * If extra warnings are enabled, report the given warning at the given + * offset. + */ + MOZ_MUST_USE bool extraWarningAt(uint32_t offset, unsigned errorNumber, ...); + bool isValidStrictBinding(PropertyName* name); bool warnOnceAboutExprClosure(); @@ -952,7 +947,7 @@ class Parser final : public ParserBase, private JS::AutoGCRooter class MOZ_STACK_CLASS PossibleError { private: - enum class ErrorKind { Expression, Destructuring }; + enum class ErrorKind { Expression, Destructuring, DestructuringWarning }; enum class ErrorState { None, Pending }; @@ -967,11 +962,12 @@ class Parser final : public ParserBase, private JS::AutoGCRooter Parser& parser_; Error exprError_; Error destructuringError_; + Error destructuringWarning_; // Returns the error report. Error& error(ErrorKind kind); - // Return true if an error is pending without reporting + // Return true if an error is pending without reporting. bool hasError(ErrorKind kind); // Resolve any pending error. @@ -983,7 +979,11 @@ class Parser final : public ParserBase, private JS::AutoGCRooter // If there is a pending error, report it and return false, otherwise // return true. - bool checkForError(ErrorKind kind); + MOZ_MUST_USE bool checkForError(ErrorKind kind); + + // If there is a pending warning, report it and return either false or + // true depending on the werror option, otherwise return true. + MOZ_MUST_USE bool checkForWarning(ErrorKind kind); // Transfer an existing error to another instance. void transferErrorTo(ErrorKind kind, PossibleError* other); @@ -991,23 +991,33 @@ class Parser final : public ParserBase, private JS::AutoGCRooter public: explicit PossibleError(Parser& parser); + // Return true if a pending destructuring error is present. + bool hasPendingDestructuringError(); + // Set a pending destructuring error. Only a single error may be set // per instance, i.e. subsequent calls to this method are ignored and // won't overwrite the existing pending error. void setPendingDestructuringErrorAt(const TokenPos& pos, unsigned errorNumber); + // Set a pending destructuring warning. Only a single warning may be + // set per instance, i.e. subsequent calls to this method are ignored + // and won't overwrite the existing pending warning. + void setPendingDestructuringWarningAt(const TokenPos& pos, unsigned errorNumber); + // Set a pending expression error. Only a single error may be set per // instance, i.e. subsequent calls to this method are ignored and won't // overwrite the existing pending error. void setPendingExpressionErrorAt(const TokenPos& pos, unsigned errorNumber); - // If there is a pending destructuring error, report it and return - // false, otherwise return true. Clears any pending expression error. - bool checkForDestructuringError(); + // If there is a pending destructuring error or warning, report it and + // return false, otherwise return true. Clears any pending expression + // error. + MOZ_MUST_USE bool checkForDestructuringErrorOrWarning(); // If there is a pending expression error, report it and return false, - // otherwise return true. Clears any pending destructuring error. - bool checkForExpressionError(); + // otherwise return true. Clears any pending destructuring error or + // warning. + MOZ_MUST_USE bool checkForExpressionError(); // Pass pending errors between possible error instances. This is useful // for extending the lifetime of a pending error beyond the scope of @@ -1490,25 +1500,27 @@ class Parser final : public ParserBase, private JS::AutoGCRooter mozilla::Maybe newLexicalScopeData(ParseContext::Scope& scope); Node finishLexicalScope(ParseContext::Scope& scope, Node body); - Node propertyName(YieldHandling yieldHandling, Node propList, + Node propertyName(YieldHandling yieldHandling, + const mozilla::Maybe& maybeDecl, Node propList, PropertyType* propType, MutableHandleAtom propAtom); - Node computedPropertyName(YieldHandling yieldHandling, Node literal); + Node computedPropertyName(YieldHandling yieldHandling, + const mozilla::Maybe& maybeDecl, Node literal); Node arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError); Node newRegExp(); Node objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError); - // Top-level entrypoint into destructuring pattern checking/name-analyzing. - bool checkDestructuringPattern(Node pattern, mozilla::Maybe maybeDecl, - PossibleError* possibleError = nullptr); + Node bindingInitializer(Node lhs, DeclarationKind kind, YieldHandling yieldHandling); + Node bindingIdentifier(DeclarationKind kind, YieldHandling yieldHandling); + Node bindingIdentifierOrPattern(DeclarationKind kind, YieldHandling yieldHandling, + TokenKind tt); + Node objectBindingPattern(DeclarationKind kind, YieldHandling yieldHandling); + Node arrayBindingPattern(DeclarationKind kind, YieldHandling yieldHandling); - // Recursive methods for checking/name-analyzing subcomponents of a - // destructuring pattern. The array/object methods *must* be passed arrays - // or objects. The name method may be passed anything but will report an - // error if not passed a name. - bool checkDestructuringArray(Node arrayPattern, mozilla::Maybe maybeDecl); - bool checkDestructuringObject(Node objectPattern, mozilla::Maybe maybeDecl); - bool checkDestructuringName(Node expr, mozilla::Maybe maybeDecl); + void checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos, + PossibleError* possibleError); + void checkDestructuringAssignmentElement(Node expr, TokenPos exprPos, + PossibleError* possibleError); Node newNumber(const Token& tok) { return handler.newNumber(tok.number(), tok.decimalPoint(), tok.pos); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 895ba6416..7de6a242e 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -561,6 +561,10 @@ class SyntaxParseHandler node == NodeParenthesizedName; } + bool isArgumentsAnyParentheses(Node node, ExclusiveContext* cx) { + return node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName; + } + bool isEvalAnyParentheses(Node node, ExclusiveContext* cx) { return node == NodeUnparenthesizedEvalName || node == NodeParenthesizedEvalName; } @@ -571,7 +575,7 @@ class SyntaxParseHandler if (isEvalAnyParentheses(node, cx)) return js_eval_str; - if (node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName) + if (isArgumentsAnyParentheses(node, cx)) return js_arguments_str; return nullptr; } diff --git a/js/src/js.msg b/js/src/js.msg index ec49d06c9..f57b36a90 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -211,6 +211,8 @@ MSG_DEF(JSMSG_BAD_POW_LEFTSIDE, 0, JSEXN_SYNTAXERR, "unparenthesized unar 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, "'{0}' can't be defined or assigned to in strict mode code") +MSG_DEF(JSMSG_BAD_STRICT_ASSIGN_ARGUMENTS, 0, JSEXN_SYNTAXERR, "'arguments' can't be defined or assigned to in strict mode code") +MSG_DEF(JSMSG_BAD_STRICT_ASSIGN_EVAL, 0, JSEXN_SYNTAXERR, "'eval' can't be defined or assigned to in strict mode code") MSG_DEF(JSMSG_BAD_SWITCH, 0, JSEXN_SYNTAXERR, "invalid switch statement") MSG_DEF(JSMSG_BAD_SUPER, 0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'") MSG_DEF(JSMSG_BAD_SUPERPROP, 1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods")