From b09c5594dee74fcb6477b13b6c5bf9bcf8c675f4 Mon Sep 17 00:00:00 2001 From: Fedor Date: Sun, 8 Mar 2020 00:50:51 +0200 Subject: [PATCH] Implement optional catch binding. --- js/src/builtin/ReflectParse.cpp | 14 ++- js/src/frontend/BytecodeEmitter.cpp | 53 ++++++----- js/src/frontend/FoldConstants.cpp | 7 +- js/src/frontend/NameFunctions.cpp | 6 +- js/src/frontend/ParseNode.cpp | 11 +-- js/src/frontend/Parser.cpp | 90 ++++++++++--------- .../tests/js1_8_5/reflect-parse/statements.js | 11 ++- 7 files changed, 117 insertions(+), 75 deletions(-) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 22c958d4c..a8065d6d1 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -1547,7 +1547,7 @@ NodeBuilder::catchClause(HandleValue var, HandleValue guard, HandleValue body, T { RootedValue cb(cx, callbacks[AST_CATCH]); if (!cb.isNull()) - return callback(cb, var, opt(guard), body, pos, dst); + return callback(cb, opt(var), opt(guard), body, pos, dst); return newNode(AST_CATCH, pos, "param", var, @@ -1803,6 +1803,14 @@ class ASTSerializer bool identifier(ParseNode* pn, MutableHandleValue dst); bool literal(ParseNode* pn, MutableHandleValue dst); + bool optPattern(ParseNode* pn, MutableHandleValue dst) { + if (!pn) { + dst.setMagic(JS_SERIALIZE_NO_NODE); + return true; + } + return pattern(pn, dst); + } + bool pattern(ParseNode* pn, MutableHandleValue dst); bool arrayPattern(ParseNode* pn, MutableHandleValue dst); bool objectPattern(ParseNode* pn, MutableHandleValue dst); @@ -2265,13 +2273,13 @@ ASTSerializer::switchStatement(ParseNode* pn, MutableHandleValue dst) bool ASTSerializer::catchClause(ParseNode* pn, bool* isGuarded, MutableHandleValue dst) { - MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos)); + MOZ_ASSERT_IF(pn->pn_kid1, pn->pn_pos.encloses(pn->pn_kid1->pn_pos)); MOZ_ASSERT_IF(pn->pn_kid2, pn->pn_pos.encloses(pn->pn_kid2->pn_pos)); MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid3->pn_pos)); RootedValue var(cx), guard(cx), body(cx); - if (!pattern(pn->pn_kid1, &var) || + if (!optPattern(pn->pn_kid1, &var) || !optExpression(pn->pn_kid2, &guard)) { return false; } diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 8cd06feac..18cc7d954 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -3459,10 +3459,12 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_CATCH: MOZ_ASSERT(pn->isArity(PN_TERNARY)); - if (!checkSideEffects(pn->pn_kid1, answer)) - return false; - if (*answer) - return true; + if (ParseNode* name = pn->pn_kid1) { + if (!checkSideEffects(name, answer)) + return false; + if (*answer) + return true; + } if (ParseNode* cond = pn->pn_kid2) { if (!checkSideEffects(cond, answer)) return false; @@ -6638,24 +6640,31 @@ BytecodeEmitter::emitCatch(ParseNode* pn) return false; ParseNode* pn2 = pn->pn_kid1; - switch (pn2->getKind()) { - case PNK_ARRAY: - case PNK_OBJECT: - if (!emitDestructuringOps(pn2, DestructuringDeclaration)) - return false; - if (!emit1(JSOP_POP)) - return false; - break; + if (!pn2) { + // See ES2019 13.15.7 Runtime Semantics: CatchClauseEvaluation + // Catch variable was omitted: discard the exception. + if (!emit1(JSOP_POP)) + return false; + } else { + switch (pn2->getKind()) { + case PNK_ARRAY: + case PNK_OBJECT: + if (!emitDestructuringOps(pn2, DestructuringDeclaration)) + return false; + if (!emit1(JSOP_POP)) + return false; + break; - case PNK_NAME: - if (!emitLexicalInitialization(pn2)) - return false; - if (!emit1(JSOP_POP)) - return false; - break; + case PNK_NAME: + if (!emitLexicalInitialization(pn2)) + return false; + if (!emit1(JSOP_POP)) + return false; + break; - default: - MOZ_ASSERT(0); + default: + MOZ_ASSERT(0); + } } // If there is a guard expression, emit it and arrange to jump to the next @@ -6899,7 +6908,9 @@ BytecodeEmitter::emitLexicalScope(ParseNode* pn) EmitterScope emitterScope(this); ScopeKind kind; if (body->isKind(PNK_CATCH)) - kind = body->pn_kid1->isKind(PNK_NAME) ? ScopeKind::SimpleCatch : ScopeKind::Catch; + kind = (!body->pn_kid1 || body->pn_kid1->isKind(PNK_NAME)) ? + ScopeKind::SimpleCatch : + ScopeKind::Catch; else kind = ScopeKind::Lexical; diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 16294c4a8..b460b92b6 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -1260,9 +1260,10 @@ FoldCatch(ExclusiveContext* cx, ParseNode* node, Parser& parse MOZ_ASSERT(node->isKind(PNK_CATCH)); MOZ_ASSERT(node->isArity(PN_TERNARY)); - ParseNode*& declPattern = node->pn_kid1; - if (!Fold(cx, &declPattern, parser, inGenexpLambda)) - return false; + if (ParseNode*& declPattern = node->pn_kid1) { + if (!Fold(cx, &declPattern, parser, inGenexpLambda)) + return false; + } if (ParseNode*& cond = node->pn_kid2) { if (!FoldCondition(cx, &cond, parser, inGenexpLambda)) diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index 376be7624..db70bb5b4 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -651,8 +651,10 @@ class NameResolver // contain arbitrary expressions. case PNK_CATCH: MOZ_ASSERT(cur->isArity(PN_TERNARY)); - if (!resolve(cur->pn_kid1, prefix)) - return false; + if (cur->pn_kid1) { + if (!resolve(cur->pn_kid1, prefix)) + return false; + } if (cur->pn_kid2) { if (!resolve(cur->pn_kid2, prefix)) return false; diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 42ae9451a..e01a41067 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -409,13 +409,14 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushResult::Recyclable; } - // A catch node has first kid as catch-variable pattern, the second kid - // as catch condition (which, if non-null, records the || in - // SpiderMonkey's |catch (e if )| extension), and third kid as the - // statements in the catch block. + // A catch node has an (optional) first kid as catch-variable pattern, + // the second kid as (optional) catch condition (which, records the + // || in SpiderMonkey's |catch (e if )| extension), and + // third kid as the statements in the catch block. case PNK_CATCH: { MOZ_ASSERT(pn->isArity(PN_TERNARY)); - stack->push(pn->pn_kid1); + if (pn->pn_kid1) + stack->push(pn->pn_kid1); if (pn->pn_kid2) stack->push(pn->pn_kid2); stack->push(pn->pn_kid3); diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index cf9f1e27c..810d589be 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -6901,57 +6901,67 @@ Parser::tryStatement(YieldHandling yieldHandling) /* * Legal catch forms are: * catch (lhs) - * catch (lhs if ) + * catch (lhs if ) ** non-standard ** + * catch ** ES2019 ** * where lhs is a name or a destructuring left-hand side. - * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD) + * The second is legal only #ifdef JS_HAS_CATCH_GUARD */ - MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH); - - if (!tokenStream.getToken(&tt)) + bool omittedBinding; + if (!tokenStream.matchToken(&omittedBinding, TOK_LC)) return null(); - Node catchName; - switch (tt) { - case TOK_LB: - case TOK_LC: - catchName = destructuringDeclaration(DeclarationKind::CatchParameter, - yieldHandling, tt); - if (!catchName) - return null(); - break; - default: { - if (!TokenKindIsPossibleIdentifierName(tt)) { - error(JSMSG_CATCH_IDENTIFIER); + Node catchName; + Node catchGuard = null(); + + if (omittedBinding) { + catchName = null(); + } else { + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH); + + if (!tokenStream.getToken(&tt)) return null(); + switch (tt) { + case TOK_LB: + case TOK_LC: + catchName = destructuringDeclaration(DeclarationKind::CatchParameter, + yieldHandling, tt); + if (!catchName) + return null(); + break; + + default: { + if (!TokenKindIsPossibleIdentifierName(tt)) { + error(JSMSG_CATCH_IDENTIFIER); + return null(); + } + + catchName = bindingIdentifier(DeclarationKind::SimpleCatchParameter, + yieldHandling); + if (!catchName) + return null(); + break; + } } - catchName = bindingIdentifier(DeclarationKind::SimpleCatchParameter, - yieldHandling); - if (!catchName) - return null(); - break; - } - } - - Node catchGuard = null(); #if JS_HAS_CATCH_GUARD - /* - * We use 'catch (x if x === 5)' (not 'catch (x : x === 5)') - * to avoid conflicting with the JS2/ECMAv4 type annotation - * catchguard syntax. - */ - bool matched; - if (!tokenStream.matchToken(&matched, TOK_IF)) - return null(); - if (matched) { - catchGuard = expr(InAllowed, yieldHandling, TripledotProhibited); - if (!catchGuard) + /* + * We use 'catch (x if x === 5)' (not 'catch (x : x === 5)') + * to avoid conflicting with the JS2/ECMAv4 type annotation + * catchguard syntax. + */ + bool matched; + if (!tokenStream.matchToken(&matched, TOK_IF)) return null(); - } + if (matched) { + catchGuard = expr(InAllowed, yieldHandling, TripledotProhibited); + if (!catchGuard) + return null(); + } #endif - MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); - MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); + } Node catchBody = catchBlockStatement(yieldHandling, scope); if (!catchBody) diff --git a/js/src/tests/js1_8_5/reflect-parse/statements.js b/js/src/tests/js1_8_5/reflect-parse/statements.js index 1df86508b..11c1b88b4 100644 --- a/js/src/tests/js1_8_5/reflect-parse/statements.js +++ b/js/src/tests/js1_8_5/reflect-parse/statements.js @@ -77,7 +77,16 @@ assertStmt("try { } catch (e if foo) { } catch (e if bar) { } catch (e) { } fina catchClause(ident("e"), ident("bar"), blockStmt([])) ], catchClause(ident("e"), null, blockStmt([])), blockStmt([]))); - +assertStmt("try { } catch { }", + tryStmt(blockStmt([]), + [], + catchClause(null, null, blockStmt([])), + null)); +assertStmt("try { } catch { } finally { }", + tryStmt(blockStmt([]), + [], + catchClause(null, null, blockStmt([])), + blockStmt([]))); // Bug 632028: yield outside of a function should throw (function() {