From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/frontend/FoldConstants.cpp | 1587 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1587 insertions(+) create mode 100644 js/src/frontend/FoldConstants.cpp (limited to 'js/src/frontend/FoldConstants.cpp') diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp new file mode 100644 index 0000000000..8a434418b1 --- /dev/null +++ b/js/src/frontend/FoldConstants.cpp @@ -0,0 +1,1587 @@ +/* -*- 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 "mozilla/Try.h" // MOZ_TRY* + +#include "jslibmath.h" +#include "jsmath.h" + +#include "frontend/FullParseHandler.h" +#include "frontend/ParseNode.h" +#include "frontend/ParseNodeVisitor.h" +#include "frontend/Parser-macros.h" // MOZ_TRY_VAR_OR_RETURN +#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, + ParseNodeResult result) { + // convenience check: can call TryReplaceNode(pnp, alloc_parsenode()) + // directly, without having to worry about alloc returning null. + if (result.isErr()) { + return false; + } + auto* pn = result.unwrap(); + 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::ImportAttributeList: + case ParseNodeKind::ImportAttribute: + 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; + } + if (!TryReplaceNode(nodePtr, replacement)) { + return false; + } + } 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; + MOZ_TRY_VAR_OR_RETURN(propertyNameExpr, + info.handler->newPropertyName(name, key->pn_pos), + 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); +} -- cgit v1.2.3