/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "frontend/FoldConstants.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" // mozilla::Maybe #include "jslibmath.h" #include "jsmath.h" #include "frontend/FullParseHandler.h" #include "frontend/ParseNode.h" #include "frontend/ParseNodeVisitor.h" #include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex #include "js/Conversions.h" #include "js/Stack.h" // JS::NativeStackLimit #include "util/StringBuffer.h" // StringBuffer using namespace js; using namespace js::frontend; using JS::GenericNaN; using JS::ToInt32; using JS::ToUint32; using mozilla::IsNegative; using mozilla::NegativeInfinity; using mozilla::PositiveInfinity; struct FoldInfo { FrontendContext* fc; ParserAtomsTable& parserAtoms; FullParseHandler* handler; }; // Don't use ReplaceNode directly, because we want the constant folder to keep // the attributes isInParens and isDirectRHSAnonFunction of the old node being // replaced. [[nodiscard]] inline bool TryReplaceNode(ParseNode** pnp, ParseNode* pn) { // convenience check: can call TryReplaceNode(pnp, alloc_parsenode()) // directly, without having to worry about alloc returning null. if (!pn) { return false; } pn->setInParens((*pnp)->isInParens()); pn->setDirectRHSAnonFunction((*pnp)->isDirectRHSAnonFunction()); ReplaceNode(pnp, pn); return true; } static bool ContainsHoistedDeclaration(FoldInfo& info, ParseNode* node, bool* result); static bool ListContainsHoistedDeclaration(FoldInfo& info, ListNode* list, bool* result) { for (ParseNode* node : list->contents()) { if (!ContainsHoistedDeclaration(info, node, result)) { return false; } if (*result) { return true; } } *result = false; return true; } // Determines whether the given ParseNode contains any declarations whose // visibility will extend outside the node itself -- that is, whether the // ParseNode contains any var statements. // // THIS IS NOT A GENERAL-PURPOSE FUNCTION. It is only written to work in the // specific context of deciding that |node|, as one arm of a ParseNodeKind::If // controlled by a constant condition, contains a declaration that forbids // |node| being completely eliminated as dead. static bool ContainsHoistedDeclaration(FoldInfo& info, ParseNode* node, bool* result) { AutoCheckRecursionLimit recursion(info.fc); if (!recursion.check(info.fc)) { return false; } restart: // With a better-typed AST, we would have distinct parse node classes for // expressions and for statements and would characterize expressions with // ExpressionKind and statements with StatementKind. Perhaps someday. In // the meantime we must characterize every ParseNodeKind, even the // expression/sub-expression ones that, if we handle all statement kinds // correctly, we'll never see. switch (node->getKind()) { // Base case. case ParseNodeKind::VarStmt: *result = true; return true; // Non-global lexical declarations are block-scoped (ergo not hoistable). case ParseNodeKind::LetDecl: case ParseNodeKind::ConstDecl: MOZ_ASSERT(node->is()); *result = false; return true; // Similarly to the lexical declarations above, classes cannot add hoisted // declarations case ParseNodeKind::ClassDecl: MOZ_ASSERT(node->is()); *result = false; return true; // Function declarations *can* be hoisted declarations. But in the // magical world of the rewritten frontend, the declaration necessitated // by a nested function statement, not at body level, doesn't require // that we preserve an unreachable function declaration node against // dead-code removal. case ParseNodeKind::Function: *result = false; return true; case ParseNodeKind::Module: *result = false; return true; // Statements with no sub-components at all. case ParseNodeKind::EmptyStmt: MOZ_ASSERT(node->is()); *result = false; return true; case ParseNodeKind::DebuggerStmt: MOZ_ASSERT(node->is()); *result = false; return true; // Statements containing only an expression have no declarations. case ParseNodeKind::ExpressionStmt: case ParseNodeKind::ThrowStmt: case ParseNodeKind::ReturnStmt: MOZ_ASSERT(node->is()); *result = false; return true; // These two aren't statements in the spec, but we sometimes insert them // in statement lists anyway. case ParseNodeKind::InitialYield: case ParseNodeKind::YieldStarExpr: case ParseNodeKind::YieldExpr: MOZ_ASSERT(node->is()); *result = false; return true; // Other statements with no sub-statement components. case ParseNodeKind::BreakStmt: case ParseNodeKind::ContinueStmt: case ParseNodeKind::ImportDecl: case ParseNodeKind::ImportSpecList: case ParseNodeKind::ImportSpec: case ParseNodeKind::ImportNamespaceSpec: case ParseNodeKind::ExportFromStmt: case ParseNodeKind::ExportDefaultStmt: case ParseNodeKind::ExportSpecList: case ParseNodeKind::ExportSpec: case ParseNodeKind::ExportNamespaceSpec: case ParseNodeKind::ExportStmt: case ParseNodeKind::ExportBatchSpecStmt: case ParseNodeKind::CallImportExpr: case ParseNodeKind::CallImportSpec: case ParseNodeKind::ImportAssertionList: case ParseNodeKind::ImportAssertion: case ParseNodeKind::ImportModuleRequest: *result = false; return true; // Statements possibly containing hoistable declarations only in the left // half, in ParseNode terms -- the loop body in AST terms. case ParseNodeKind::DoWhileStmt: return ContainsHoistedDeclaration(info, node->as().left(), result); // Statements possibly containing hoistable declarations only in the // right half, in ParseNode terms -- the loop body or nested statement // (usually a block statement), in AST terms. case ParseNodeKind::WhileStmt: case ParseNodeKind::WithStmt: return ContainsHoistedDeclaration(info, node->as().right(), result); case ParseNodeKind::LabelStmt: return ContainsHoistedDeclaration( info, node->as().statement(), result); // Statements with more complicated structures. // if-statement nodes may have hoisted declarations in their consequent // and alternative components. case ParseNodeKind::IfStmt: { TernaryNode* ifNode = &node->as(); ParseNode* consequent = ifNode->kid2(); if (!ContainsHoistedDeclaration(info, consequent, result)) { return false; } if (*result) { return true; } if ((node = ifNode->kid3())) { goto restart; } *result = false; return true; } // try-statements have statements to execute, and one or both of a // catch-list and a finally-block. case ParseNodeKind::TryStmt: { TernaryNode* tryNode = &node->as(); MOZ_ASSERT(tryNode->kid2() || tryNode->kid3(), "must have either catch or finally"); ParseNode* tryBlock = tryNode->kid1(); if (!ContainsHoistedDeclaration(info, tryBlock, result)) { return false; } if (*result) { return true; } if (ParseNode* catchScope = tryNode->kid2()) { BinaryNode* catchNode = &catchScope->as().scopeBody()->as(); MOZ_ASSERT(catchNode->isKind(ParseNodeKind::Catch)); ParseNode* catchStatements = catchNode->right(); if (!ContainsHoistedDeclaration(info, catchStatements, result)) { return false; } if (*result) { return true; } } if (ParseNode* finallyBlock = tryNode->kid3()) { return ContainsHoistedDeclaration(info, finallyBlock, result); } *result = false; return true; } // A switch node's left half is an expression; only its right half (a // list of cases/defaults, or a block node) could contain hoisted // declarations. case ParseNodeKind::SwitchStmt: { SwitchStatement* switchNode = &node->as(); return ContainsHoistedDeclaration(info, &switchNode->lexicalForCaseList(), result); } case ParseNodeKind::Case: { CaseClause* caseClause = &node->as(); return ContainsHoistedDeclaration(info, caseClause->statementList(), result); } case ParseNodeKind::ForStmt: { ForNode* forNode = &node->as(); TernaryNode* loopHead = forNode->head(); MOZ_ASSERT(loopHead->isKind(ParseNodeKind::ForHead) || loopHead->isKind(ParseNodeKind::ForIn) || loopHead->isKind(ParseNodeKind::ForOf)); if (loopHead->isKind(ParseNodeKind::ForHead)) { // for (init?; cond?; update?), with only init possibly containing // a hoisted declaration. (Note: a lexical-declaration |init| is // (at present) hoisted in SpiderMonkey parlance -- but such // hoisting doesn't extend outside of this statement, so it is not // hoisting in the sense meant by ContainsHoistedDeclaration.) ParseNode* init = loopHead->kid1(); if (init && init->isKind(ParseNodeKind::VarStmt)) { *result = true; return true; } } else { MOZ_ASSERT(loopHead->isKind(ParseNodeKind::ForIn) || loopHead->isKind(ParseNodeKind::ForOf)); // for each? (target in ...), where only target may introduce // hoisted declarations. // // -- or -- // // for (target of ...), where only target may introduce hoisted // declarations. // // Either way, if |target| contains a declaration, it's |loopHead|'s // first kid. ParseNode* decl = loopHead->kid1(); if (decl && decl->isKind(ParseNodeKind::VarStmt)) { *result = true; return true; } } ParseNode* loopBody = forNode->body(); return ContainsHoistedDeclaration(info, loopBody, result); } case ParseNodeKind::LexicalScope: { LexicalScopeNode* scope = &node->as(); ParseNode* expr = scope->scopeBody(); if (expr->isKind(ParseNodeKind::ForStmt) || expr->is()) { return ContainsHoistedDeclaration(info, expr, result); } MOZ_ASSERT(expr->isKind(ParseNodeKind::StatementList)); return ListContainsHoistedDeclaration( info, &scope->scopeBody()->as(), result); } // List nodes with all non-null children. case ParseNodeKind::StatementList: return ListContainsHoistedDeclaration(info, &node->as(), result); // Grammar sub-components that should never be reached directly by this // method, because some parent component should have asserted itself. case ParseNodeKind::ObjectPropertyName: case ParseNodeKind::ComputedName: case ParseNodeKind::Spread: case ParseNodeKind::MutateProto: case ParseNodeKind::PropertyDefinition: case ParseNodeKind::Shorthand: case ParseNodeKind::ConditionalExpr: case ParseNodeKind::TypeOfNameExpr: case ParseNodeKind::TypeOfExpr: case ParseNodeKind::AwaitExpr: case ParseNodeKind::VoidExpr: case ParseNodeKind::NotExpr: case ParseNodeKind::BitNotExpr: case ParseNodeKind::DeleteNameExpr: case ParseNodeKind::DeletePropExpr: case ParseNodeKind::DeleteElemExpr: case ParseNodeKind::DeleteOptionalChainExpr: case ParseNodeKind::DeleteExpr: case ParseNodeKind::PosExpr: case ParseNodeKind::NegExpr: case ParseNodeKind::PreIncrementExpr: case ParseNodeKind::PostIncrementExpr: case ParseNodeKind::PreDecrementExpr: case ParseNodeKind::PostDecrementExpr: case ParseNodeKind::CoalesceExpr: case ParseNodeKind::OrExpr: case ParseNodeKind::AndExpr: case ParseNodeKind::BitOrExpr: case ParseNodeKind::BitXorExpr: case ParseNodeKind::BitAndExpr: case ParseNodeKind::StrictEqExpr: case ParseNodeKind::EqExpr: case ParseNodeKind::StrictNeExpr: case ParseNodeKind::NeExpr: case ParseNodeKind::LtExpr: case ParseNodeKind::LeExpr: case ParseNodeKind::GtExpr: case ParseNodeKind::GeExpr: case ParseNodeKind::InstanceOfExpr: case ParseNodeKind::InExpr: case ParseNodeKind::PrivateInExpr: case ParseNodeKind::LshExpr: case ParseNodeKind::RshExpr: case ParseNodeKind::UrshExpr: case ParseNodeKind::AddExpr: case ParseNodeKind::SubExpr: case ParseNodeKind::MulExpr: case ParseNodeKind::DivExpr: case ParseNodeKind::ModExpr: case ParseNodeKind::PowExpr: case ParseNodeKind::InitExpr: case ParseNodeKind::AssignExpr: case ParseNodeKind::AddAssignExpr: case ParseNodeKind::SubAssignExpr: case ParseNodeKind::CoalesceAssignExpr: case ParseNodeKind::OrAssignExpr: case ParseNodeKind::AndAssignExpr: case ParseNodeKind::BitOrAssignExpr: case ParseNodeKind::BitXorAssignExpr: case ParseNodeKind::BitAndAssignExpr: case ParseNodeKind::LshAssignExpr: case ParseNodeKind::RshAssignExpr: case ParseNodeKind::UrshAssignExpr: case ParseNodeKind::MulAssignExpr: case ParseNodeKind::DivAssignExpr: case ParseNodeKind::ModAssignExpr: case ParseNodeKind::PowAssignExpr: case ParseNodeKind::CommaExpr: case ParseNodeKind::ArrayExpr: case ParseNodeKind::ObjectExpr: case ParseNodeKind::PropertyNameExpr: case ParseNodeKind::DotExpr: case ParseNodeKind::ElemExpr: case ParseNodeKind::Arguments: case ParseNodeKind::CallExpr: case ParseNodeKind::PrivateMemberExpr: case ParseNodeKind::OptionalChain: case ParseNodeKind::OptionalDotExpr: case ParseNodeKind::OptionalElemExpr: case ParseNodeKind::OptionalCallExpr: case ParseNodeKind::OptionalPrivateMemberExpr: case ParseNodeKind::Name: case ParseNodeKind::PrivateName: case ParseNodeKind::TemplateStringExpr: case ParseNodeKind::TemplateStringListExpr: case ParseNodeKind::TaggedTemplateExpr: case ParseNodeKind::CallSiteObj: case ParseNodeKind::StringExpr: case ParseNodeKind::RegExpExpr: case ParseNodeKind::TrueExpr: case ParseNodeKind::FalseExpr: case ParseNodeKind::NullExpr: case ParseNodeKind::RawUndefinedExpr: case ParseNodeKind::ThisExpr: case ParseNodeKind::Elision: case ParseNodeKind::NumberExpr: case ParseNodeKind::BigIntExpr: case ParseNodeKind::NewExpr: case ParseNodeKind::Generator: case ParseNodeKind::ParamsBody: case ParseNodeKind::Catch: case ParseNodeKind::ForIn: case ParseNodeKind::ForOf: case ParseNodeKind::ForHead: case ParseNodeKind::DefaultConstructor: case ParseNodeKind::ClassBodyScope: case ParseNodeKind::ClassMethod: case ParseNodeKind::ClassField: case ParseNodeKind::StaticClassBlock: case ParseNodeKind::ClassMemberList: case ParseNodeKind::ClassNames: case ParseNodeKind::NewTargetExpr: case ParseNodeKind::ImportMetaExpr: case ParseNodeKind::PosHolder: case ParseNodeKind::SuperCallExpr: case ParseNodeKind::SuperBase: case ParseNodeKind::SetThis: #ifdef ENABLE_DECORATORS case ParseNodeKind::DecoratorList: #endif MOZ_CRASH( "ContainsHoistedDeclaration should have indicated false on " "some parent node without recurring to test this node"); case ParseNodeKind::LastUnused: case ParseNodeKind::Limit: MOZ_CRASH("unexpected sentinel ParseNodeKind in node"); #ifdef ENABLE_RECORD_TUPLE case ParseNodeKind::RecordExpr: case ParseNodeKind::TupleExpr: MOZ_CRASH("Record and Tuple are not supported yet"); #endif } MOZ_CRASH("invalid node kind"); } /* * Fold from one constant type to another. * XXX handles only strings and numbers for now */ static bool FoldType(FoldInfo info, ParseNode** pnp, ParseNodeKind kind) { const ParseNode* pn = *pnp; if (!pn->isKind(kind)) { switch (kind) { case ParseNodeKind::NumberExpr: if (pn->isKind(ParseNodeKind::StringExpr)) { auto atom = pn->as().atom(); double d = info.parserAtoms.toNumber(atom); if (!TryReplaceNode( pnp, info.handler->newNumber(d, NoDecimal, pn->pn_pos))) { return false; } } break; case ParseNodeKind::StringExpr: if (pn->isKind(ParseNodeKind::NumberExpr)) { TaggedParserAtomIndex atom = pn->as().toAtom(info.fc, info.parserAtoms); if (!atom) { return false; } if (!TryReplaceNode( pnp, info.handler->newStringLiteral(atom, pn->pn_pos))) { return false; } } break; default: MOZ_CRASH("Invalid type in constant folding FoldType"); } } return true; } static bool IsEffectless(ParseNode* node) { return node->isKind(ParseNodeKind::TrueExpr) || node->isKind(ParseNodeKind::FalseExpr) || node->isKind(ParseNodeKind::StringExpr) || node->isKind(ParseNodeKind::TemplateStringExpr) || node->isKind(ParseNodeKind::NumberExpr) || node->isKind(ParseNodeKind::BigIntExpr) || node->isKind(ParseNodeKind::NullExpr) || node->isKind(ParseNodeKind::RawUndefinedExpr) || node->isKind(ParseNodeKind::Function); } enum Truthiness { Truthy, Falsy, Unknown }; static Truthiness Boolish(ParseNode* pn) { switch (pn->getKind()) { case ParseNodeKind::NumberExpr: return (pn->as().value() != 0 && !std::isnan(pn->as().value())) ? Truthy : Falsy; case ParseNodeKind::BigIntExpr: return (pn->as().isZero()) ? Falsy : Truthy; case ParseNodeKind::StringExpr: case ParseNodeKind::TemplateStringExpr: return (pn->as().atom() == TaggedParserAtomIndex::WellKnown::empty()) ? Falsy : Truthy; case ParseNodeKind::TrueExpr: case ParseNodeKind::Function: return Truthy; case ParseNodeKind::FalseExpr: case ParseNodeKind::NullExpr: case ParseNodeKind::RawUndefinedExpr: return Falsy; case ParseNodeKind::VoidExpr: { // |void | evaluates to |undefined| which isn't truthy. But the // sense of this method requires that the expression be literally // replaceable with true/false: not the case if the nested expression // is effectful, might throw, &c. Walk past the |void| (and nested // |void| expressions, for good measure) and check that the nested // expression doesn't break this requirement before indicating falsity. do { pn = pn->as().kid(); } while (pn->isKind(ParseNodeKind::VoidExpr)); return IsEffectless(pn) ? Falsy : Unknown; } default: return Unknown; } } static bool SimplifyCondition(FoldInfo info, ParseNode** nodePtr) { // Conditions fold like any other expression, but then they sometimes can be // further folded to constants. *nodePtr should already have been // constant-folded. ParseNode* node = *nodePtr; if (Truthiness t = Boolish(node); t != Unknown) { // We can turn function nodes into constant nodes here, but mutating // function nodes is tricky --- in particular, mutating a function node // that appears on a method list corrupts the method list. However, // methods are M's in statements of the form 'this.foo = M;', which we // never fold, so we're okay. if (!TryReplaceNode(nodePtr, info.handler->newBooleanLiteral( t == Truthy, node->pn_pos))) { return false; } } return true; } static bool FoldTypeOfExpr(FoldInfo info, ParseNode** nodePtr) { UnaryNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::TypeOfExpr)); ParseNode* expr = node->kid(); // Constant-fold the entire |typeof| if given a constant with known type. TaggedParserAtomIndex result; if (expr->isKind(ParseNodeKind::StringExpr) || expr->isKind(ParseNodeKind::TemplateStringExpr)) { result = TaggedParserAtomIndex::WellKnown::string(); } else if (expr->isKind(ParseNodeKind::NumberExpr)) { result = TaggedParserAtomIndex::WellKnown::number(); } else if (expr->isKind(ParseNodeKind::BigIntExpr)) { result = TaggedParserAtomIndex::WellKnown::bigint(); } else if (expr->isKind(ParseNodeKind::NullExpr)) { result = TaggedParserAtomIndex::WellKnown::object(); } else if (expr->isKind(ParseNodeKind::TrueExpr) || expr->isKind(ParseNodeKind::FalseExpr)) { result = TaggedParserAtomIndex::WellKnown::boolean(); } else if (expr->is()) { result = TaggedParserAtomIndex::WellKnown::function(); } if (result) { if (!TryReplaceNode(nodePtr, info.handler->newStringLiteral(result, node->pn_pos))) { return false; } } return true; } static bool FoldDeleteExpr(FoldInfo info, ParseNode** nodePtr) { UnaryNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteExpr)); ParseNode* expr = node->kid(); // Expression deletion evaluates the expression, then evaluates to true. // For effectless expressions, eliminate the expression evaluation. if (IsEffectless(expr)) { if (!TryReplaceNode(nodePtr, info.handler->newBooleanLiteral(true, node->pn_pos))) { return false; } } return true; } static bool FoldDeleteElement(FoldInfo info, ParseNode** nodePtr) { UnaryNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteElemExpr)); ParseNode* expr = node->kid(); // If we're deleting an element, but constant-folding converted our // element reference into a dotted property access, we must *also* // morph the node's kind. // // In principle this also applies to |super["foo"] -> super.foo|, // but we don't constant-fold |super["foo"]| yet. MOZ_ASSERT(expr->isKind(ParseNodeKind::ElemExpr) || expr->isKind(ParseNodeKind::DotExpr)); if (expr->isKind(ParseNodeKind::DotExpr)) { // newDelete will detect and use DeletePropExpr if (!TryReplaceNode(nodePtr, info.handler->newDelete(node->pn_pos.begin, expr))) { return false; } MOZ_ASSERT((*nodePtr)->getKind() == ParseNodeKind::DeletePropExpr); } return true; } static bool FoldNot(FoldInfo info, ParseNode** nodePtr) { UnaryNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::NotExpr)); if (!SimplifyCondition(info, node->unsafeKidReference())) { return false; } ParseNode* expr = node->kid(); if (expr->isKind(ParseNodeKind::TrueExpr) || expr->isKind(ParseNodeKind::FalseExpr)) { bool newval = !expr->isKind(ParseNodeKind::TrueExpr); if (!TryReplaceNode( nodePtr, info.handler->newBooleanLiteral(newval, node->pn_pos))) { return false; } } return true; } static bool FoldUnaryArithmetic(FoldInfo info, ParseNode** nodePtr) { UnaryNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::BitNotExpr) || node->isKind(ParseNodeKind::PosExpr) || node->isKind(ParseNodeKind::NegExpr), "need a different method for this node kind"); ParseNode* expr = node->kid(); if (expr->isKind(ParseNodeKind::NumberExpr) || expr->isKind(ParseNodeKind::TrueExpr) || expr->isKind(ParseNodeKind::FalseExpr)) { double d = expr->isKind(ParseNodeKind::NumberExpr) ? expr->as().value() : double(expr->isKind(ParseNodeKind::TrueExpr)); if (node->isKind(ParseNodeKind::BitNotExpr)) { d = ~ToInt32(d); } else if (node->isKind(ParseNodeKind::NegExpr)) { d = -d; } else { MOZ_ASSERT(node->isKind(ParseNodeKind::PosExpr)); // nothing to do } if (!TryReplaceNode(nodePtr, info.handler->newNumber(d, NoDecimal, node->pn_pos))) { return false; } } return true; } static bool FoldAndOrCoalesce(FoldInfo info, ParseNode** nodePtr) { ListNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::AndExpr) || node->isKind(ParseNodeKind::CoalesceExpr) || node->isKind(ParseNodeKind::OrExpr)); bool isOrNode = node->isKind(ParseNodeKind::OrExpr); bool isAndNode = node->isKind(ParseNodeKind::AndExpr); bool isCoalesceNode = node->isKind(ParseNodeKind::CoalesceExpr); ParseNode** elem = node->unsafeHeadReference(); do { Truthiness t = Boolish(*elem); // If we don't know the constant-folded node's truthiness, we can't // reduce this node with its surroundings. Continue folding any // remaining nodes. if (t == Unknown) { elem = &(*elem)->pn_next; continue; } bool isTruthyCoalesceNode = isCoalesceNode && !((*elem)->isKind(ParseNodeKind::NullExpr) || (*elem)->isKind(ParseNodeKind::VoidExpr) || (*elem)->isKind(ParseNodeKind::RawUndefinedExpr)); bool canShortCircuit = (isOrNode && t == Truthy) || (isAndNode && t == Falsy) || isTruthyCoalesceNode; // If the constant-folded node's truthiness will terminate the // condition -- `a || true || expr` or `b && false && expr` or // `false ?? c ?? expr` -- then trailing nodes will never be // evaluated. Truncate the list after the known-truthiness node, // as it's the overall result. if (canShortCircuit) { for (ParseNode* next = (*elem)->pn_next; next; next = next->pn_next) { node->unsafeDecrementCount(); } // Terminate the original and/or list at the known-truthiness // node. (*elem)->pn_next = nullptr; elem = &(*elem)->pn_next; break; } // We've encountered a vacuous node that'll never short-circuit // evaluation. if ((*elem)->pn_next) { // This node is never the overall result when there are // subsequent nodes. Remove it. ParseNode* elt = *elem; *elem = elt->pn_next; node->unsafeDecrementCount(); } else { // Otherwise this node is the result of the overall expression, // so leave it alone. And we're done. elem = &(*elem)->pn_next; break; } } while (*elem); node->unsafeReplaceTail(elem); // If we removed nodes, we may have to replace a one-element list with // its element. if (node->count() == 1) { ParseNode* first = node->head(); if (!TryReplaceNode(nodePtr, first)) { ; return false; } } return true; } static bool Fold(FoldInfo info, ParseNode** pnp); static bool FoldConditional(FoldInfo info, ParseNode** nodePtr) { ParseNode** nextNode = nodePtr; do { // |nextNode| on entry points to the C?T:F expression to be folded. // Reset it to exit the loop in the common case where F isn't another // ?: expression. nodePtr = nextNode; nextNode = nullptr; TernaryNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::ConditionalExpr)); ParseNode** expr = node->unsafeKid1Reference(); if (!Fold(info, expr)) { return false; } if (!SimplifyCondition(info, expr)) { return false; } ParseNode** ifTruthy = node->unsafeKid2Reference(); if (!Fold(info, ifTruthy)) { return false; } ParseNode** ifFalsy = node->unsafeKid3Reference(); // If our C?T:F node has F as another ?: node, *iteratively* constant- // fold F *after* folding C and T (and possibly eliminating C and one // of T/F entirely); otherwise fold F normally. Making |nextNode| non- // null causes this loop to run again to fold F. // // Conceivably we could instead/also iteratively constant-fold T, if T // were more complex than F. Such an optimization is unimplemented. if ((*ifFalsy)->isKind(ParseNodeKind::ConditionalExpr)) { MOZ_ASSERT((*ifFalsy)->is()); nextNode = ifFalsy; } else { if (!Fold(info, ifFalsy)) { return false; } } // Try to constant-fold based on the condition expression. Truthiness t = Boolish(*expr); if (t == Unknown) { continue; } // Otherwise reduce 'C ? T : F' to T or F as directed by C. ParseNode* replacement = t == Truthy ? *ifTruthy : *ifFalsy; // Otherwise perform a replacement. This invalidates |nextNode|, so // reset it (if the replacement requires folding) or clear it (if // |ifFalsy| is dead code) as needed. if (nextNode) { nextNode = (*nextNode == replacement) ? nodePtr : nullptr; } ReplaceNode(nodePtr, replacement); } while (nextNode); return true; } static bool FoldIf(FoldInfo info, ParseNode** nodePtr) { ParseNode** nextNode = nodePtr; do { // |nextNode| on entry points to the initial |if| to be folded. Reset // it to exit the loop when the |else| arm isn't another |if|. nodePtr = nextNode; nextNode = nullptr; TernaryNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::IfStmt)); ParseNode** expr = node->unsafeKid1Reference(); if (!Fold(info, expr)) { return false; } if (!SimplifyCondition(info, expr)) { return false; } ParseNode** consequent = node->unsafeKid2Reference(); if (!Fold(info, consequent)) { return false; } ParseNode** alternative = node->unsafeKid3Reference(); if (*alternative) { // If in |if (C) T; else F;| we have |F| as another |if|, // *iteratively* constant-fold |F| *after* folding |C| and |T| (and // possibly completely replacing the whole thing with |T| or |F|); // otherwise fold F normally. Making |nextNode| non-null causes // this loop to run again to fold F. if ((*alternative)->isKind(ParseNodeKind::IfStmt)) { MOZ_ASSERT((*alternative)->is()); nextNode = alternative; } else { if (!Fold(info, alternative)) { return false; } } } // Eliminate the consequent or alternative if the condition has // constant truthiness. Truthiness t = Boolish(*expr); if (t == Unknown) { continue; } // Careful! Either of these can be null: |replacement| in |if (0) T;|, // and |discarded| in |if (true) T;|. ParseNode* replacement; ParseNode* discarded; if (t == Truthy) { replacement = *consequent; discarded = *alternative; } else { replacement = *alternative; discarded = *consequent; } bool performReplacement = true; if (discarded) { // A declaration that hoists outside the discarded arm prevents the // |if| from being folded away. bool containsHoistedDecls; if (!ContainsHoistedDeclaration(info, discarded, &containsHoistedDecls)) { return false; } performReplacement = !containsHoistedDecls; } if (!performReplacement) { continue; } if (!replacement) { // If there's no replacement node, we have a constantly-false |if| // with no |else|. Replace the entire thing with an empty // statement list. if (!TryReplaceNode(nodePtr, info.handler->newStatementList(node->pn_pos))) { return false; } } else { // Replacement invalidates |nextNode|, so reset it (if the // replacement requires folding) or clear it (if |alternative| // is dead code) as needed. if (nextNode) { nextNode = (*nextNode == replacement) ? nodePtr : nullptr; } ReplaceNode(nodePtr, replacement); } } while (nextNode); return true; } static double ComputeBinary(ParseNodeKind kind, double left, double right) { if (kind == ParseNodeKind::AddExpr) { return left + right; } if (kind == ParseNodeKind::SubExpr) { return left - right; } if (kind == ParseNodeKind::MulExpr) { return left * right; } if (kind == ParseNodeKind::ModExpr) { return NumberMod(left, right); } if (kind == ParseNodeKind::UrshExpr) { return ToUint32(left) >> (ToUint32(right) & 31); } if (kind == ParseNodeKind::DivExpr) { return NumberDiv(left, right); } MOZ_ASSERT(kind == ParseNodeKind::LshExpr || kind == ParseNodeKind::RshExpr); int32_t i = ToInt32(left); uint32_t j = ToUint32(right) & 31; return int32_t((kind == ParseNodeKind::LshExpr) ? uint32_t(i) << j : i >> j); } static bool FoldBinaryArithmetic(FoldInfo info, ParseNode** nodePtr) { ListNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::SubExpr) || node->isKind(ParseNodeKind::MulExpr) || node->isKind(ParseNodeKind::LshExpr) || node->isKind(ParseNodeKind::RshExpr) || node->isKind(ParseNodeKind::UrshExpr) || node->isKind(ParseNodeKind::DivExpr) || node->isKind(ParseNodeKind::ModExpr)); MOZ_ASSERT(node->count() >= 2); // Fold each operand to a number if possible. ParseNode** listp = node->unsafeHeadReference(); for (; *listp; listp = &(*listp)->pn_next) { if (!FoldType(info, listp, ParseNodeKind::NumberExpr)) { return false; } } node->unsafeReplaceTail(listp); // Now fold all leading numeric terms together into a single number. // (Trailing terms for the non-shift operations can't be folded together // due to floating point imprecision. For example, if |x === -2**53|, // |x - 1 - 1 === -2**53| but |x - 2 === -2**53 - 2|. Shifts could be // folded, but it doesn't seem worth the effort.) ParseNode** elem = node->unsafeHeadReference(); ParseNode** next = &(*elem)->pn_next; if ((*elem)->isKind(ParseNodeKind::NumberExpr)) { ParseNodeKind kind = node->getKind(); while (true) { if (!*next || !(*next)->isKind(ParseNodeKind::NumberExpr)) { break; } double d = ComputeBinary(kind, (*elem)->as().value(), (*next)->as().value()); TokenPos pos((*elem)->pn_pos.begin, (*next)->pn_pos.end); if (!TryReplaceNode(elem, info.handler->newNumber(d, NoDecimal, pos))) { return false; } (*elem)->pn_next = (*next)->pn_next; next = &(*elem)->pn_next; node->unsafeDecrementCount(); } if (node->count() == 1) { MOZ_ASSERT(node->head() == *elem); MOZ_ASSERT((*elem)->isKind(ParseNodeKind::NumberExpr)); if (!TryReplaceNode(nodePtr, *elem)) { return false; } } } return true; } static bool FoldExponentiation(FoldInfo info, ParseNode** nodePtr) { ListNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::PowExpr)); MOZ_ASSERT(node->count() >= 2); // Fold each operand, ideally into a number. ParseNode** listp = node->unsafeHeadReference(); for (; *listp; listp = &(*listp)->pn_next) { if (!FoldType(info, listp, ParseNodeKind::NumberExpr)) { return false; } } node->unsafeReplaceTail(listp); // Unlike all other binary arithmetic operators, ** is right-associative: // 2**3**5 is 2**(3**5), not (2**3)**5. As list nodes singly-link their // children, full constant-folding requires either linear space or dodgy // in-place linked list reversal. So we only fold one exponentiation: it's // easy and addresses common cases like |2**32|. if (node->count() > 2) { return true; } ParseNode* base = node->head(); ParseNode* exponent = base->pn_next; if (!base->isKind(ParseNodeKind::NumberExpr) || !exponent->isKind(ParseNodeKind::NumberExpr)) { return true; } double d1 = base->as().value(); double d2 = exponent->as().value(); return TryReplaceNode(nodePtr, info.handler->newNumber( ecmaPow(d1, d2), NoDecimal, node->pn_pos)); } static bool FoldElement(FoldInfo info, ParseNode** nodePtr) { PropertyByValue* elem = &(*nodePtr)->as(); ParseNode* expr = &elem->expression(); ParseNode* key = &elem->key(); TaggedParserAtomIndex name; if (key->isKind(ParseNodeKind::StringExpr)) { auto keyIndex = key->as().atom(); uint32_t index; if (info.parserAtoms.isIndex(keyIndex, &index)) { // Optimization 1: We have something like expr["100"]. This is // equivalent to expr[100] which is faster. if (!TryReplaceNode( elem->unsafeRightReference(), info.handler->newNumber(index, NoDecimal, key->pn_pos))) { return false; } key = &elem->key(); } else { name = keyIndex; } } else if (key->isKind(ParseNodeKind::NumberExpr)) { auto* numeric = &key->as(); double number = numeric->value(); if (number != ToUint32(number)) { // Optimization 2: We have something like expr[3.14]. The number // isn't an array index, so it converts to a string ("3.14"), // enabling optimization 3 below. name = numeric->toAtom(info.fc, info.parserAtoms); if (!name) { return false; } } } // If we don't have a name, we can't optimize to getprop. if (!name) { return true; } // Optimization 3: We have expr["foo"] where foo is not an index. Convert // to a property access (like expr.foo) that optimizes better downstream. NameNode* propertyNameExpr = info.handler->newPropertyName(name, key->pn_pos); if (!propertyNameExpr) { return false; } if (!TryReplaceNode( nodePtr, info.handler->newPropertyAccess(expr, propertyNameExpr))) { return false; } return true; } static bool FoldAdd(FoldInfo info, ParseNode** nodePtr) { ListNode* node = &(*nodePtr)->as(); MOZ_ASSERT(node->isKind(ParseNodeKind::AddExpr)); MOZ_ASSERT(node->count() >= 2); // Fold leading numeric operands together: // // (1 + 2 + x) becomes (3 + x) // // Don't go past the leading operands: additions after a string are // string concatenations, not additions: ("1" + 2 + 3 === "123"). ParseNode** current = node->unsafeHeadReference(); ParseNode** next = &(*current)->pn_next; if ((*current)->isKind(ParseNodeKind::NumberExpr)) { do { if (!(*next)->isKind(ParseNodeKind::NumberExpr)) { break; } double left = (*current)->as().value(); double right = (*next)->as().value(); TokenPos pos((*current)->pn_pos.begin, (*next)->pn_pos.end); if (!TryReplaceNode( current, info.handler->newNumber(left + right, NoDecimal, pos))) { return false; } (*current)->pn_next = (*next)->pn_next; next = &(*current)->pn_next; node->unsafeDecrementCount(); } while (*next); } // If any operands remain, attempt string concatenation folding. do { // If no operands remain, we're done. if (!*next) { break; } // (number + string) is string concatenation *only* at the start of // the list: (x + 1 + "2" !== x + "12") when x is a number. if ((*current)->isKind(ParseNodeKind::NumberExpr) && (*next)->isKind(ParseNodeKind::StringExpr)) { if (!FoldType(info, current, ParseNodeKind::StringExpr)) { return false; } next = &(*current)->pn_next; } // The first string forces all subsequent additions to be // string concatenations. do { if ((*current)->isKind(ParseNodeKind::StringExpr)) { break; } current = next; next = &(*current)->pn_next; } while (*next); // If there's nothing left to fold, we're done. if (!*next) { break; } do { // Concat all strings. MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr)); // To avoid unnecessarily copy when there's no strings after the // first item, lazily construct StringBuffer and append the first item. mozilla::Maybe accum; TaggedParserAtomIndex firstAtom; firstAtom = (*current)->as().atom(); do { // Try folding the next operand to a string. if (!FoldType(info, next, ParseNodeKind::StringExpr)) { return false; } // Stop glomming once folding doesn't produce a string. if (!(*next)->isKind(ParseNodeKind::StringExpr)) { break; } if (!accum) { accum.emplace(info.fc); if (!accum->append(info.parserAtoms, firstAtom)) { return false; } } // Append this string and remove the node. if (!accum->append(info.parserAtoms, (*next)->as().atom())) { return false; } (*current)->pn_next = (*next)->pn_next; next = &(*current)->pn_next; node->unsafeDecrementCount(); } while (*next); // Replace with concatenation if we multiple nodes. if (accum) { auto combination = accum->finishParserAtom(info.parserAtoms, info.fc); if (!combination) { return false; } // Replace |current|'s string with the entire combination. MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr)); (*current)->as().setAtom(combination); } // If we're out of nodes, we're done. if (!*next) { break; } current = next; next = &(*current)->pn_next; // If we're out of nodes *after* the non-foldable-to-string // node, we're done. if (!*next) { break; } // Otherwise find the next node foldable to a string, and loop. do { current = next; if (!FoldType(info, current, ParseNodeKind::StringExpr)) { return false; } next = &(*current)->pn_next; } while (!(*current)->isKind(ParseNodeKind::StringExpr) && *next); } while (*next); } while (false); MOZ_ASSERT(!*next, "must have considered all nodes here"); MOZ_ASSERT(!(*current)->pn_next, "current node must be the last node"); node->unsafeReplaceTail(&(*current)->pn_next); if (node->count() == 1) { // We reduced the list to a constant. Replace the ParseNodeKind::Add node // with that constant. ReplaceNode(nodePtr, *current); } return true; } class FoldVisitor : public RewritingParseNodeVisitor { using Base = RewritingParseNodeVisitor; ParserAtomsTable& parserAtoms; FullParseHandler* handler; FoldInfo info() const { return FoldInfo{fc_, parserAtoms, handler}; } public: FoldVisitor(FrontendContext* fc, ParserAtomsTable& parserAtoms, FullParseHandler* handler) : RewritingParseNodeVisitor(fc), parserAtoms(parserAtoms), handler(handler) {} bool visitElemExpr(ParseNode*& pn) { return Base::visitElemExpr(pn) && FoldElement(info(), &pn); } bool visitTypeOfExpr(ParseNode*& pn) { return Base::visitTypeOfExpr(pn) && FoldTypeOfExpr(info(), &pn); } bool visitDeleteExpr(ParseNode*& pn) { return Base::visitDeleteExpr(pn) && FoldDeleteExpr(info(), &pn); } bool visitDeleteElemExpr(ParseNode*& pn) { return Base::visitDeleteElemExpr(pn) && FoldDeleteElement(info(), &pn); } bool visitNotExpr(ParseNode*& pn) { return Base::visitNotExpr(pn) && FoldNot(info(), &pn); } bool visitBitNotExpr(ParseNode*& pn) { return Base::visitBitNotExpr(pn) && FoldUnaryArithmetic(info(), &pn); } bool visitPosExpr(ParseNode*& pn) { return Base::visitPosExpr(pn) && FoldUnaryArithmetic(info(), &pn); } bool visitNegExpr(ParseNode*& pn) { return Base::visitNegExpr(pn) && FoldUnaryArithmetic(info(), &pn); } bool visitPowExpr(ParseNode*& pn) { return Base::visitPowExpr(pn) && FoldExponentiation(info(), &pn); } bool visitMulExpr(ParseNode*& pn) { return Base::visitMulExpr(pn) && FoldBinaryArithmetic(info(), &pn); } bool visitDivExpr(ParseNode*& pn) { return Base::visitDivExpr(pn) && FoldBinaryArithmetic(info(), &pn); } bool visitModExpr(ParseNode*& pn) { return Base::visitModExpr(pn) && FoldBinaryArithmetic(info(), &pn); } bool visitAddExpr(ParseNode*& pn) { return Base::visitAddExpr(pn) && FoldAdd(info(), &pn); } bool visitSubExpr(ParseNode*& pn) { return Base::visitSubExpr(pn) && FoldBinaryArithmetic(info(), &pn); } bool visitLshExpr(ParseNode*& pn) { return Base::visitLshExpr(pn) && FoldBinaryArithmetic(info(), &pn); } bool visitRshExpr(ParseNode*& pn) { return Base::visitRshExpr(pn) && FoldBinaryArithmetic(info(), &pn); } bool visitUrshExpr(ParseNode*& pn) { return Base::visitUrshExpr(pn) && FoldBinaryArithmetic(info(), &pn); } bool visitAndExpr(ParseNode*& pn) { // Note that this does result in the unfortunate fact that dead arms of this // node get constant folded. The same goes for visitOr and visitCoalesce. return Base::visitAndExpr(pn) && FoldAndOrCoalesce(info(), &pn); } bool visitOrExpr(ParseNode*& pn) { return Base::visitOrExpr(pn) && FoldAndOrCoalesce(info(), &pn); } bool visitCoalesceExpr(ParseNode*& pn) { return Base::visitCoalesceExpr(pn) && FoldAndOrCoalesce(info(), &pn); } bool visitConditionalExpr(ParseNode*& pn) { // Don't call base-class visitConditional because FoldConditional processes // pn's child nodes specially to save stack space. return FoldConditional(info(), &pn); } private: bool internalVisitCall(BinaryNode* node) { MOZ_ASSERT(node->isKind(ParseNodeKind::CallExpr) || node->isKind(ParseNodeKind::OptionalCallExpr) || node->isKind(ParseNodeKind::SuperCallExpr) || node->isKind(ParseNodeKind::NewExpr) || node->isKind(ParseNodeKind::TaggedTemplateExpr)); // Don't fold a parenthesized callable component in an invocation, as this // might cause a different |this| value to be used, changing semantics: // // var prop = "global"; // var obj = { prop: "obj", f: function() { return this.prop; } }; // assertEq((true ? obj.f : null)(), "global"); // assertEq(obj.f(), "obj"); // assertEq((true ? obj.f : null)``, "global"); // assertEq(obj.f``, "obj"); // // As an exception to this, we do allow folding the function in // `(function() { ... })()` (the module pattern), because that lets us // constant fold code inside that function. // // See bug 537673 and bug 1182373. ParseNode* callee = node->left(); if (node->isKind(ParseNodeKind::NewExpr) || !callee->isInParens() || callee->is()) { if (!visit(*node->unsafeLeftReference())) { return false; } } if (!visit(*node->unsafeRightReference())) { return false; } return true; } public: bool visitCallExpr(ParseNode*& pn) { return internalVisitCall(&pn->as()); } bool visitOptionalCallExpr(ParseNode*& pn) { return internalVisitCall(&pn->as()); } bool visitNewExpr(ParseNode*& pn) { return internalVisitCall(&pn->as()); } bool visitSuperCallExpr(ParseNode*& pn) { return internalVisitCall(&pn->as()); } bool visitTaggedTemplateExpr(ParseNode*& pn) { return internalVisitCall(&pn->as()); } bool visitIfStmt(ParseNode*& pn) { // Don't call base-class visitIf because FoldIf processes pn's child nodes // specially to save stack space. return FoldIf(info(), &pn); } bool visitForStmt(ParseNode*& pn) { if (!Base::visitForStmt(pn)) { return false; } ForNode& stmt = pn->as(); if (stmt.left()->isKind(ParseNodeKind::ForHead)) { TernaryNode& head = stmt.left()->as(); ParseNode** test = head.unsafeKid2Reference(); if (*test) { if (!SimplifyCondition(info(), test)) { return false; } if ((*test)->isKind(ParseNodeKind::TrueExpr)) { *test = nullptr; } } } return true; } bool visitWhileStmt(ParseNode*& pn) { BinaryNode& node = pn->as(); return Base::visitWhileStmt(pn) && SimplifyCondition(info(), node.unsafeLeftReference()); } bool visitDoWhileStmt(ParseNode*& pn) { BinaryNode& node = pn->as(); return Base::visitDoWhileStmt(pn) && SimplifyCondition(info(), node.unsafeRightReference()); } bool visitFunction(ParseNode*& pn) { FunctionNode& node = pn->as(); // Don't constant-fold inside "use asm" code, as this could create a parse // tree that doesn't type-check as asm.js. if (node.funbox()->useAsmOrInsideUseAsm()) { return true; } return Base::visitFunction(pn); } bool visitArrayExpr(ParseNode*& pn) { if (!Base::visitArrayExpr(pn)) { return false; } ListNode* list = &pn->as(); // Empty arrays are non-constant, since we cannot easily determine their // type. if (list->hasNonConstInitializer() && list->count() > 0) { for (ParseNode* node : list->contents()) { if (!node->isConstant()) { return true; } } list->unsetHasNonConstInitializer(); } return true; } bool visitObjectExpr(ParseNode*& pn) { if (!Base::visitObjectExpr(pn)) { return false; } ListNode* list = &pn->as(); if (list->hasNonConstInitializer()) { for (ParseNode* node : list->contents()) { if (node->getKind() != ParseNodeKind::PropertyDefinition) { return true; } BinaryNode* binary = &node->as(); if (binary->left()->isKind(ParseNodeKind::ComputedName)) { return true; } if (!binary->right()->isConstant()) { return true; } } list->unsetHasNonConstInitializer(); } return true; } }; static bool Fold(FrontendContext* fc, ParserAtomsTable& parserAtoms, FullParseHandler* handler, ParseNode** pnp) { FoldVisitor visitor(fc, parserAtoms, handler); return visitor.visit(*pnp); } static bool Fold(FoldInfo info, ParseNode** pnp) { return Fold(info.fc, info.parserAtoms, info.handler, pnp); } bool frontend::FoldConstants(FrontendContext* fc, ParserAtomsTable& parserAtoms, ParseNode** pnp, FullParseHandler* handler) { return Fold(fc, parserAtoms, handler, pnp); }