diff options
Diffstat (limited to 'js/src/frontend/FoldConstants.cpp')
-rw-r--r-- | js/src/frontend/FoldConstants.cpp | 1928 |
1 files changed, 1928 insertions, 0 deletions
diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp new file mode 100644 index 000000000..6f62ffac6 --- /dev/null +++ b/js/src/frontend/FoldConstants.cpp @@ -0,0 +1,1928 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jslibmath.h" + +#include "frontend/ParseNode.h" +#include "frontend/Parser.h" +#include "js/Conversions.h" + +#include "jscntxtinlines.h" +#include "jsobjinlines.h" + +using namespace js; +using namespace js::frontend; + +using mozilla::IsNaN; +using mozilla::IsNegative; +using mozilla::NegativeInfinity; +using mozilla::PositiveInfinity; +using JS::GenericNaN; +using JS::ToInt32; +using JS::ToUint32; + +static bool +ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result); + +static bool +ListContainsHoistedDeclaration(ExclusiveContext* cx, ListNode* list, bool* result) +{ + for (ParseNode* node = list->pn_head; node; node = node->pn_next) { + if (!ContainsHoistedDeclaration(cx, 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 PNK_IF controlled +// by a constant condition, contains a declaration that forbids |node| being +// completely eliminated as dead. +static bool +ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) +{ + JS_CHECK_RECURSION(cx, 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 PNK_VAR: + *result = true; + return true; + + // Non-global lexical declarations are block-scoped (ergo not hoistable). + case PNK_LET: + case PNK_CONST: + MOZ_ASSERT(node->isArity(PN_LIST)); + *result = false; + return true; + + // Similarly to the lexical declarations above, classes cannot add hoisted + // declarations + case PNK_CLASS: + MOZ_ASSERT(node->isArity(PN_TERNARY)); + *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 PNK_FUNCTION: + MOZ_ASSERT(node->isArity(PN_CODE)); + *result = false; + return true; + + case PNK_MODULE: + *result = false; + return true; + + // Statements with no sub-components at all. + case PNK_NOP: // induced by function f() {} function f() {} + case PNK_DEBUGGER: + MOZ_ASSERT(node->isArity(PN_NULLARY)); + *result = false; + return true; + + // Statements containing only an expression have no declarations. + case PNK_SEMI: + case PNK_THROW: + case PNK_RETURN: + MOZ_ASSERT(node->isArity(PN_UNARY)); + *result = false; + return true; + + // These two aren't statements in the spec, but we sometimes insert them + // in statement lists anyway. + case PNK_YIELD_STAR: + case PNK_YIELD: + MOZ_ASSERT(node->isArity(PN_BINARY)); + *result = false; + return true; + + // Other statements with no sub-statement components. + case PNK_BREAK: + case PNK_CONTINUE: + case PNK_IMPORT: + case PNK_IMPORT_SPEC_LIST: + case PNK_IMPORT_SPEC: + case PNK_EXPORT_FROM: + case PNK_EXPORT_DEFAULT: + case PNK_EXPORT_SPEC_LIST: + case PNK_EXPORT_SPEC: + case PNK_EXPORT: + case PNK_EXPORT_BATCH_SPEC: + *result = false; + return true; + + // Statements possibly containing hoistable declarations only in the left + // half, in ParseNode terms -- the loop body in AST terms. + case PNK_DOWHILE: + return ContainsHoistedDeclaration(cx, node->pn_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 PNK_WHILE: + case PNK_WITH: + return ContainsHoistedDeclaration(cx, node->pn_right, result); + + case PNK_LABEL: + return ContainsHoistedDeclaration(cx, node->pn_expr, result); + + // Statements with more complicated structures. + + // if-statement nodes may have hoisted declarations in their consequent + // and alternative components. + case PNK_IF: { + MOZ_ASSERT(node->isArity(PN_TERNARY)); + + ParseNode* consequent = node->pn_kid2; + if (!ContainsHoistedDeclaration(cx, consequent, result)) + return false; + if (*result) + return true; + + if ((node = node->pn_kid3)) + goto restart; + + *result = false; + return true; + } + + // Legacy array and generator comprehensions use PNK_IF to represent + // conditions specified in the comprehension tail: for example, + // [x for (x in obj) if (x)]. The consequent of such PNK_IF nodes is + // either PNK_YIELD in a PNK_SEMI statement (generator comprehensions) or + // PNK_ARRAYPUSH (array comprehensions) . The first case is consistent + // with normal if-statement structure with consequent/alternative as + // statements. The second case is abnormal and requires that we not + // banish PNK_ARRAYPUSH to the unreachable list, handling it explicitly. + // + // We could require that this one weird PNK_ARRAYPUSH case be packaged in + // a PNK_SEMI, for consistency. That requires careful bytecode emitter + // adjustment that seems unwarranted for a deprecated feature. + case PNK_ARRAYPUSH: + *result = false; + return true; + + // try-statements have statements to execute, and one or both of a + // catch-list and a finally-block. + case PNK_TRY: { + MOZ_ASSERT(node->isArity(PN_TERNARY)); + MOZ_ASSERT(node->pn_kid2 || node->pn_kid3, + "must have either catch(es) or finally"); + + ParseNode* tryBlock = node->pn_kid1; + if (!ContainsHoistedDeclaration(cx, tryBlock, result)) + return false; + if (*result) + return true; + + if (ParseNode* catchList = node->pn_kid2) { + for (ParseNode* lexicalScope = catchList->pn_head; + lexicalScope; + lexicalScope = lexicalScope->pn_next) + { + MOZ_ASSERT(lexicalScope->isKind(PNK_LEXICALSCOPE)); + + ParseNode* catchNode = lexicalScope->pn_expr; + MOZ_ASSERT(catchNode->isKind(PNK_CATCH)); + + ParseNode* catchStatements = catchNode->pn_kid3; + if (!ContainsHoistedDeclaration(cx, catchStatements, result)) + return false; + if (*result) + return true; + } + } + + if (ParseNode* finallyBlock = node->pn_kid3) + return ContainsHoistedDeclaration(cx, 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 PNK_SWITCH: + MOZ_ASSERT(node->isArity(PN_BINARY)); + return ContainsHoistedDeclaration(cx, node->pn_right, result); + + case PNK_CASE: + return ContainsHoistedDeclaration(cx, node->as<CaseClause>().statementList(), result); + + case PNK_FOR: + case PNK_COMPREHENSIONFOR: { + MOZ_ASSERT(node->isArity(PN_BINARY)); + + ParseNode* loopHead = node->pn_left; + MOZ_ASSERT(loopHead->isKind(PNK_FORHEAD) || + loopHead->isKind(PNK_FORIN) || + loopHead->isKind(PNK_FOROF)); + + if (loopHead->isKind(PNK_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.) + MOZ_ASSERT(loopHead->isArity(PN_TERNARY)); + + ParseNode* init = loopHead->pn_kid1; + if (init && init->isKind(PNK_VAR)) { + *result = true; + return true; + } + } else { + MOZ_ASSERT(loopHead->isKind(PNK_FORIN) || loopHead->isKind(PNK_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. + MOZ_ASSERT(loopHead->isArity(PN_TERNARY)); + + ParseNode* decl = loopHead->pn_kid1; + if (decl && decl->isKind(PNK_VAR)) { + *result = true; + return true; + } + } + + ParseNode* loopBody = node->pn_right; + return ContainsHoistedDeclaration(cx, loopBody, result); + } + + case PNK_LEXICALSCOPE: { + MOZ_ASSERT(node->isArity(PN_SCOPE)); + ParseNode* expr = node->pn_expr; + + if (expr->isKind(PNK_FOR) || expr->isKind(PNK_FUNCTION)) + return ContainsHoistedDeclaration(cx, expr, result); + + MOZ_ASSERT(expr->isKind(PNK_STATEMENTLIST)); + return ListContainsHoistedDeclaration(cx, &node->pn_expr->as<ListNode>(), result); + } + + // List nodes with all non-null children. + case PNK_STATEMENTLIST: + return ListContainsHoistedDeclaration(cx, &node->as<ListNode>(), result); + + // Grammar sub-components that should never be reached directly by this + // method, because some parent component should have asserted itself. + case PNK_OBJECT_PROPERTY_NAME: + case PNK_COMPUTED_NAME: + case PNK_SPREAD: + case PNK_MUTATEPROTO: + case PNK_COLON: + case PNK_SHORTHAND: + case PNK_CONDITIONAL: + case PNK_TYPEOFNAME: + case PNK_TYPEOFEXPR: + case PNK_AWAIT: + case PNK_VOID: + case PNK_NOT: + case PNK_BITNOT: + case PNK_DELETENAME: + case PNK_DELETEPROP: + case PNK_DELETEELEM: + case PNK_DELETEEXPR: + case PNK_POS: + case PNK_NEG: + case PNK_PREINCREMENT: + case PNK_POSTINCREMENT: + case PNK_PREDECREMENT: + case PNK_POSTDECREMENT: + case PNK_OR: + case PNK_AND: + case PNK_BITOR: + case PNK_BITXOR: + case PNK_BITAND: + case PNK_STRICTEQ: + case PNK_EQ: + case PNK_STRICTNE: + case PNK_NE: + case PNK_LT: + case PNK_LE: + case PNK_GT: + case PNK_GE: + case PNK_INSTANCEOF: + case PNK_IN: + case PNK_LSH: + case PNK_RSH: + case PNK_URSH: + case PNK_ADD: + case PNK_SUB: + case PNK_STAR: + case PNK_DIV: + case PNK_MOD: + case PNK_POW: + case PNK_ASSIGN: + case PNK_ADDASSIGN: + case PNK_SUBASSIGN: + case PNK_BITORASSIGN: + case PNK_BITXORASSIGN: + case PNK_BITANDASSIGN: + case PNK_LSHASSIGN: + case PNK_RSHASSIGN: + case PNK_URSHASSIGN: + case PNK_MULASSIGN: + case PNK_DIVASSIGN: + case PNK_MODASSIGN: + case PNK_POWASSIGN: + case PNK_COMMA: + case PNK_ARRAY: + case PNK_OBJECT: + case PNK_DOT: + case PNK_ELEM: + case PNK_CALL: + case PNK_NAME: + case PNK_TEMPLATE_STRING: + case PNK_TEMPLATE_STRING_LIST: + case PNK_TAGGED_TEMPLATE: + case PNK_CALLSITEOBJ: + case PNK_STRING: + case PNK_REGEXP: + case PNK_TRUE: + case PNK_FALSE: + case PNK_NULL: + case PNK_THIS: + case PNK_ELISION: + case PNK_NUMBER: + case PNK_NEW: + case PNK_GENERATOR: + case PNK_GENEXP: + case PNK_ARRAYCOMP: + case PNK_PARAMSBODY: + case PNK_CATCHLIST: + case PNK_CATCH: + case PNK_FORIN: + case PNK_FOROF: + case PNK_FORHEAD: + case PNK_CLASSMETHOD: + case PNK_CLASSMETHODLIST: + case PNK_CLASSNAMES: + case PNK_NEWTARGET: + case PNK_POSHOLDER: + case PNK_SUPERCALL: + case PNK_SUPERBASE: + case PNK_SETTHIS: + MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on " + "some parent node without recurring to test this node"); + + case PNK_LIMIT: // invalid sentinel value + MOZ_CRASH("unexpected PNK_LIMIT in node"); + } + + MOZ_CRASH("invalid node kind"); +} + +/* + * Fold from one constant type to another. + * XXX handles only strings and numbers for now + */ +static bool +FoldType(ExclusiveContext* cx, ParseNode* pn, ParseNodeKind kind) +{ + if (!pn->isKind(kind)) { + switch (kind) { + case PNK_NUMBER: + if (pn->isKind(PNK_STRING)) { + double d; + if (!StringToNumber(cx, pn->pn_atom, &d)) + return false; + pn->pn_dval = d; + pn->setKind(PNK_NUMBER); + pn->setOp(JSOP_DOUBLE); + } + break; + + case PNK_STRING: + if (pn->isKind(PNK_NUMBER)) { + pn->pn_atom = NumberToAtom(cx, pn->pn_dval); + if (!pn->pn_atom) + return false; + pn->setKind(PNK_STRING); + pn->setOp(JSOP_STRING); + } + break; + + default:; + } + } + return true; +} + +// Remove a ParseNode, **pnp, from a parse tree, putting another ParseNode, +// *pn, in its place. +// +// pnp points to a ParseNode pointer. This must be the only pointer that points +// to the parse node being replaced. The replacement, *pn, is unchanged except +// for its pn_next pointer; updating that is necessary if *pn's new parent is a +// list node. +static void +ReplaceNode(ParseNode** pnp, ParseNode* pn) +{ + pn->pn_next = (*pnp)->pn_next; + *pnp = pn; +} + +static bool +IsEffectless(ParseNode* node) +{ + return node->isKind(PNK_TRUE) || + node->isKind(PNK_FALSE) || + node->isKind(PNK_STRING) || + node->isKind(PNK_TEMPLATE_STRING) || + node->isKind(PNK_NUMBER) || + node->isKind(PNK_NULL) || + node->isKind(PNK_FUNCTION) || + node->isKind(PNK_GENEXP); +} + +enum Truthiness { Truthy, Falsy, Unknown }; + +static Truthiness +Boolish(ParseNode* pn) +{ + switch (pn->getKind()) { + case PNK_NUMBER: + return (pn->pn_dval != 0 && !IsNaN(pn->pn_dval)) ? Truthy : Falsy; + + case PNK_STRING: + case PNK_TEMPLATE_STRING: + return (pn->pn_atom->length() > 0) ? Truthy : Falsy; + + case PNK_TRUE: + case PNK_FUNCTION: + case PNK_GENEXP: + return Truthy; + + case PNK_FALSE: + case PNK_NULL: + return Falsy; + + case PNK_VOID: { + // |void <foo>| 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->pn_kid; + } while (pn->isKind(PNK_VOID)); + + return IsEffectless(pn) ? Falsy : Unknown; + } + + default: + return Unknown; + } +} + +static bool +Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda); + +static bool +FoldCondition(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + // Conditions fold like any other expression... + if (!Fold(cx, nodePtr, parser, inGenexpLambda)) + return false; + + // ...but then they sometimes can be further folded to constants. + ParseNode* node = *nodePtr; + Truthiness t = Boolish(node); + if (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. + parser.prepareNodeForMutation(node); + if (t == Truthy) { + node->setKind(PNK_TRUE); + node->setOp(JSOP_TRUE); + } else { + node->setKind(PNK_FALSE); + node->setOp(JSOP_FALSE); + } + node->setArity(PN_NULLARY); + } + + return true; +} + +static bool +FoldTypeOfExpr(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_TYPEOFEXPR)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode*& expr = node->pn_kid; + if (!Fold(cx, &expr, parser, inGenexpLambda)) + return false; + + // Constant-fold the entire |typeof| if given a constant with known type. + RootedPropertyName result(cx); + if (expr->isKind(PNK_STRING) || expr->isKind(PNK_TEMPLATE_STRING)) + result = cx->names().string; + else if (expr->isKind(PNK_NUMBER)) + result = cx->names().number; + else if (expr->isKind(PNK_NULL)) + result = cx->names().object; + else if (expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) + result = cx->names().boolean; + else if (expr->isKind(PNK_FUNCTION)) + result = cx->names().function; + + if (result) { + parser.prepareNodeForMutation(node); + + node->setKind(PNK_STRING); + node->setArity(PN_NULLARY); + node->setOp(JSOP_NOP); + node->pn_atom = result; + } + + return true; +} + +static bool +FoldDeleteExpr(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_DELETEEXPR)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode*& expr = node->pn_kid; + if (!Fold(cx, &expr, parser, inGenexpLambda)) + return false; + + // Expression deletion evaluates the expression, then evaluates to true. + // For effectless expressions, eliminate the expression evaluation. + if (IsEffectless(expr)) { + parser.prepareNodeForMutation(node); + node->setKind(PNK_TRUE); + node->setArity(PN_NULLARY); + node->setOp(JSOP_TRUE); + } + + return true; +} + +static bool +FoldDeleteElement(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_DELETEELEM)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + MOZ_ASSERT(node->pn_kid->isKind(PNK_ELEM)); + + ParseNode*& expr = node->pn_kid; + if (!Fold(cx, &expr, parser, inGenexpLambda)) + return false; + + // 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(PNK_ELEM) || expr->isKind(PNK_DOT)); + if (expr->isKind(PNK_DOT)) + node->setKind(PNK_DELETEPROP); + + return true; +} + +static bool +FoldDeleteProperty(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_DELETEPROP)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + MOZ_ASSERT(node->pn_kid->isKind(PNK_DOT)); + + ParseNode*& expr = node->pn_kid; +#ifdef DEBUG + ParseNodeKind oldKind = expr->getKind(); +#endif + + if (!Fold(cx, &expr, parser, inGenexpLambda)) + return false; + + MOZ_ASSERT(expr->isKind(oldKind), + "kind should have remained invariant under folding"); + + return true; +} + +static bool +FoldNot(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_NOT)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode*& expr = node->pn_kid; + if (!FoldCondition(cx, &expr, parser, inGenexpLambda)) + return false; + + if (expr->isKind(PNK_NUMBER)) { + double d = expr->pn_dval; + + parser.prepareNodeForMutation(node); + if (d == 0 || IsNaN(d)) { + node->setKind(PNK_TRUE); + node->setOp(JSOP_TRUE); + } else { + node->setKind(PNK_FALSE); + node->setOp(JSOP_FALSE); + } + node->setArity(PN_NULLARY); + } else if (expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) { + bool newval = !expr->isKind(PNK_TRUE); + + parser.prepareNodeForMutation(node); + node->setKind(newval ? PNK_TRUE : PNK_FALSE); + node->setArity(PN_NULLARY); + node->setOp(newval ? JSOP_TRUE : JSOP_FALSE); + } + + return true; +} + +static bool +FoldUnaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_BITNOT) || node->isKind(PNK_POS) || node->isKind(PNK_NEG), + "need a different method for this node kind"); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode*& expr = node->pn_kid; + if (!Fold(cx, &expr, parser, inGenexpLambda)) + return false; + + if (expr->isKind(PNK_NUMBER) || expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) { + double d = expr->isKind(PNK_NUMBER) + ? expr->pn_dval + : double(expr->isKind(PNK_TRUE)); + + if (node->isKind(PNK_BITNOT)) + d = ~ToInt32(d); + else if (node->isKind(PNK_NEG)) + d = -d; + else + MOZ_ASSERT(node->isKind(PNK_POS)); // nothing to do + + parser.prepareNodeForMutation(node); + node->setKind(PNK_NUMBER); + node->setOp(JSOP_DOUBLE); + node->setArity(PN_NULLARY); + node->pn_dval = d; + } + + return true; +} + +static bool +FoldIncrementDecrement(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_PREINCREMENT) || + node->isKind(PNK_POSTINCREMENT) || + node->isKind(PNK_PREDECREMENT) || + node->isKind(PNK_POSTDECREMENT)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + ParseNode*& target = node->pn_kid; + MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser<FullParseHandler>::PermitAssignmentToFunctionCalls)); + + if (!Fold(cx, &target, parser, inGenexpLambda)) + return false; + + MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser<FullParseHandler>::PermitAssignmentToFunctionCalls)); + + return true; +} + +static bool +FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + ParseNode* node = *nodePtr; + + MOZ_ASSERT(node->isKind(PNK_AND) || node->isKind(PNK_OR)); + MOZ_ASSERT(node->isArity(PN_LIST)); + + bool isOrNode = node->isKind(PNK_OR); + ParseNode** elem = &node->pn_head; + do { + if (!Fold(cx, elem, parser, inGenexpLambda)) + return false; + + 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; + } + + // If the constant-folded node's truthiness will terminate the + // condition -- `a || true || expr` or |b && false && expr| -- then + // trailing nodes will never be evaluated. Truncate the list after + // the known-truthiness node, as it's the overall result. + if ((t == Truthy) == isOrNode) { + ParseNode* afterNext; + for (ParseNode* next = (*elem)->pn_next; next; next = afterNext) { + afterNext = next->pn_next; + parser.handler.freeTree(next); + --node->pn_count; + } + + // Terminate the original and/or list at the known-truthiness + // node. + (*elem)->pn_next = nullptr; + elem = &(*elem)->pn_next; + break; + } + + MOZ_ASSERT((t == Truthy) == !isOrNode); + + // 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; + parser.handler.freeTree(elt); + --node->pn_count; + } 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); + + // If the last node in the list was replaced, we need to update the + // tail pointer in the original and/or node. + node->pn_tail = elem; + + node->checkListConsistency(); + + // If we removed nodes, we may have to replace a one-element list with + // its element. + if (node->pn_count == 1) { + ParseNode* first = node->pn_head; + ReplaceNode(nodePtr, first); + + node->setKind(PNK_NULL); + node->setArity(PN_NULLARY); + parser.freeTree(node); + } + + return true; +} + +static bool +FoldConditional(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + 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; + + ParseNode* node = *nodePtr; + MOZ_ASSERT(node->isKind(PNK_CONDITIONAL)); + MOZ_ASSERT(node->isArity(PN_TERNARY)); + + ParseNode*& expr = node->pn_kid1; + if (!FoldCondition(cx, &expr, parser, inGenexpLambda)) + return false; + + ParseNode*& ifTruthy = node->pn_kid2; + if (!Fold(cx, &ifTruthy, parser, inGenexpLambda)) + return false; + + ParseNode*& ifFalsy = node->pn_kid3; + + // 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(PNK_CONDITIONAL)) { + nextNode = &ifFalsy; + } else { + if (!Fold(cx, &ifFalsy, parser, inGenexpLambda)) + 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; + ParseNode* discarded; + if (t == Truthy) { + replacement = ifTruthy; + discarded = ifFalsy; + } else { + replacement = ifFalsy; + discarded = ifTruthy; + } + + // 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); + + parser.freeTree(discarded); + } while (nextNode); + + return true; +} + +static bool +FoldIf(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + 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; + + ParseNode* node = *nodePtr; + MOZ_ASSERT(node->isKind(PNK_IF)); + MOZ_ASSERT(node->isArity(PN_TERNARY)); + + ParseNode*& expr = node->pn_kid1; + if (!FoldCondition(cx, &expr, parser, inGenexpLambda)) + return false; + + ParseNode*& consequent = node->pn_kid2; + if (!Fold(cx, &consequent, parser, inGenexpLambda)) + return false; + + ParseNode*& alternative = node->pn_kid3; + 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(PNK_IF)) { + nextNode = &alternative; + } else { + if (!Fold(cx, &alternative, parser, inGenexpLambda)) + return false; + } + } + + // Eliminate the consequent or alternative if the condition has + // constant truthiness. Don't eliminate if we have an |if (0)| in + // trailing position in a generator expression, as this is a special + // form we can't fold away. + Truthiness t = Boolish(expr); + if (t == Unknown || inGenexpLambda) + 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(cx, 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. + parser.prepareNodeForMutation(node); + node->setKind(PNK_STATEMENTLIST); + node->setArity(PN_LIST); + node->makeEmpty(); + } 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); + + // Morph the original node into a discardable node, then + // aggressively free it and the discarded arm (if any) to suss out + // any bugs in the preceding logic. + node->setKind(PNK_STATEMENTLIST); + node->setArity(PN_LIST); + node->makeEmpty(); + if (discarded) + node->append(discarded); + parser.freeTree(node); + } + } while (nextNode); + + return true; +} + +static bool +FoldFunction(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_FUNCTION)); + MOZ_ASSERT(node->isArity(PN_CODE)); + + // 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->pn_funbox->useAsmOrInsideUseAsm()) + return true; + + // Note: pn_body is null for lazily-parsed functions. + if (ParseNode*& functionBody = node->pn_body) { + if (!Fold(cx, &functionBody, parser, node->pn_funbox->isGenexpLambda)) + return false; + } + + return true; +} + +static double +ComputeBinary(ParseNodeKind kind, double left, double right) +{ + if (kind == PNK_ADD) + return left + right; + + if (kind == PNK_SUB) + return left - right; + + if (kind == PNK_STAR) + return left * right; + + if (kind == PNK_MOD) + return right == 0 ? GenericNaN() : js_fmod(left, right); + + if (kind == PNK_URSH) + return ToUint32(left) >> (ToUint32(right) & 31); + + if (kind == PNK_DIV) { + if (right == 0) { +#if defined(XP_WIN) + /* XXX MSVC miscompiles such that (NaN == 0) */ + if (IsNaN(right)) + return GenericNaN(); +#endif + if (left == 0 || IsNaN(left)) + return GenericNaN(); + if (IsNegative(left) != IsNegative(right)) + return NegativeInfinity<double>(); + return PositiveInfinity<double>(); + } + + return left / right; + } + + MOZ_ASSERT(kind == PNK_LSH || kind == PNK_RSH); + + int32_t i = ToInt32(left); + uint32_t j = ToUint32(right) & 31; + return int32_t((kind == PNK_LSH) ? uint32_t(i) << j : i >> j); +} + +static bool +FoldModule(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser) +{ + MOZ_ASSERT(node->isKind(PNK_MODULE)); + MOZ_ASSERT(node->isArity(PN_CODE)); + + ParseNode*& moduleBody = node->pn_body; + MOZ_ASSERT(moduleBody); + return Fold(cx, &moduleBody, parser, false); +} + +static bool +FoldBinaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_SUB) || + node->isKind(PNK_STAR) || + node->isKind(PNK_LSH) || + node->isKind(PNK_RSH) || + node->isKind(PNK_URSH) || + node->isKind(PNK_DIV) || + node->isKind(PNK_MOD)); + MOZ_ASSERT(node->isArity(PN_LIST)); + MOZ_ASSERT(node->pn_count >= 2); + + // Fold each operand, ideally into a number. + ParseNode** listp = &node->pn_head; + for (; *listp; listp = &(*listp)->pn_next) { + if (!Fold(cx, listp, parser, inGenexpLambda)) + return false; + + if (!FoldType(cx, *listp, PNK_NUMBER)) + return false; + } + + // Repoint the list's tail pointer. + node->pn_tail = 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->pn_head; + ParseNode* next = elem->pn_next; + if (elem->isKind(PNK_NUMBER)) { + ParseNodeKind kind = node->getKind(); + while (true) { + if (!next || !next->isKind(PNK_NUMBER)) + break; + + double d = ComputeBinary(kind, elem->pn_dval, next->pn_dval); + + ParseNode* afterNext = next->pn_next; + parser.freeTree(next); + next = afterNext; + elem->pn_next = next; + + elem->setKind(PNK_NUMBER); + elem->setOp(JSOP_DOUBLE); + elem->setArity(PN_NULLARY); + elem->pn_dval = d; + + node->pn_count--; + } + + if (node->pn_count == 1) { + MOZ_ASSERT(node->pn_head == elem); + MOZ_ASSERT(elem->isKind(PNK_NUMBER)); + + double d = elem->pn_dval; + node->setKind(PNK_NUMBER); + node->setArity(PN_NULLARY); + node->setOp(JSOP_DOUBLE); + node->pn_dval = d; + + parser.freeTree(elem); + } + } + + return true; +} + +static bool +FoldExponentiation(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_POW)); + MOZ_ASSERT(node->isArity(PN_LIST)); + MOZ_ASSERT(node->pn_count >= 2); + + // Fold each operand, ideally into a number. + ParseNode** listp = &node->pn_head; + for (; *listp; listp = &(*listp)->pn_next) { + if (!Fold(cx, listp, parser, inGenexpLambda)) + return false; + + if (!FoldType(cx, *listp, PNK_NUMBER)) + return false; + } + + // Repoint the list's tail pointer. + node->pn_tail = 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->pn_count > 2) + return true; + + ParseNode* base = node->pn_head; + ParseNode* exponent = base->pn_next; + if (!base->isKind(PNK_NUMBER) || !exponent->isKind(PNK_NUMBER)) + return true; + + double d1 = base->pn_dval, d2 = exponent->pn_dval; + + parser.prepareNodeForMutation(node); + node->setKind(PNK_NUMBER); + node->setArity(PN_NULLARY); + node->setOp(JSOP_DOUBLE); + node->pn_dval = ecmaPow(d1, d2); + return true; +} + +static bool +FoldList(ExclusiveContext* cx, ParseNode* list, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(list->isArity(PN_LIST)); + + ParseNode** elem = &list->pn_head; + for (; *elem; elem = &(*elem)->pn_next) { + if (!Fold(cx, elem, parser, inGenexpLambda)) + return false; + } + + // Repoint the list's tail pointer if the final element was replaced. + list->pn_tail = elem; + + list->checkListConsistency(); + + return true; +} + +static bool +FoldReturn(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_RETURN)); + MOZ_ASSERT(node->isArity(PN_UNARY)); + + if (ParseNode*& expr = node->pn_kid) { + if (!Fold(cx, &expr, parser, inGenexpLambda)) + return false; + } + + return true; +} + +static bool +FoldTry(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_TRY)); + MOZ_ASSERT(node->isArity(PN_TERNARY)); + + ParseNode*& statements = node->pn_kid1; + if (!Fold(cx, &statements, parser, inGenexpLambda)) + return false; + + if (ParseNode*& catchList = node->pn_kid2) { + if (!Fold(cx, &catchList, parser, inGenexpLambda)) + return false; + } + + if (ParseNode*& finally = node->pn_kid3) { + if (!Fold(cx, &finally, parser, inGenexpLambda)) + return false; + } + + return true; +} + +static bool +FoldCatch(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + 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*& cond = node->pn_kid2) { + if (!FoldCondition(cx, &cond, parser, inGenexpLambda)) + return false; + } + + if (ParseNode*& statements = node->pn_kid3) { + if (!Fold(cx, &statements, parser, inGenexpLambda)) + return false; + } + + return true; +} + +static bool +FoldClass(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_CLASS)); + MOZ_ASSERT(node->isArity(PN_TERNARY)); + + if (ParseNode*& classNames = node->pn_kid1) { + if (!Fold(cx, &classNames, parser, inGenexpLambda)) + return false; + } + + if (ParseNode*& heritage = node->pn_kid2) { + if (!Fold(cx, &heritage, parser, inGenexpLambda)) + return false; + } + + ParseNode*& body = node->pn_kid3; + return Fold(cx, &body, parser, inGenexpLambda); +} + +static bool +FoldElement(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + ParseNode* node = *nodePtr; + + MOZ_ASSERT(node->isKind(PNK_ELEM)); + MOZ_ASSERT(node->isArity(PN_BINARY)); + + ParseNode*& expr = node->pn_left; + if (!Fold(cx, &expr, parser, inGenexpLambda)) + return false; + + ParseNode*& key = node->pn_right; + if (!Fold(cx, &key, parser, inGenexpLambda)) + return false; + + PropertyName* name = nullptr; + if (key->isKind(PNK_STRING)) { + JSAtom* atom = key->pn_atom; + uint32_t index; + + if (atom->isIndex(&index)) { + // Optimization 1: We have something like expr["100"]. This is + // equivalent to expr[100] which is faster. + key->setKind(PNK_NUMBER); + key->setOp(JSOP_DOUBLE); + key->pn_dval = index; + } else { + name = atom->asPropertyName(); + } + } else if (key->isKind(PNK_NUMBER)) { + double number = key->pn_dval; + 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. + JSAtom* atom = ToAtom<NoGC>(cx, DoubleValue(number)); + if (!atom) + return false; + name = atom->asPropertyName(); + } + } + + // If we don't have a name, we can't optimize to getprop. + if (!name) + return true; + + // Also don't optimize if the name doesn't map directly to its id for TI's + // purposes. + if (NameToId(name) != IdToTypeId(NameToId(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. + // Don't bother with this for names that TI considers to be indexes, to + // simplify downstream analysis. + ParseNode* dottedAccess = parser.handler.newPropertyAccess(expr, name, node->pn_pos.end); + if (!dottedAccess) + return false; + dottedAccess->setInParens(node->isInParens()); + ReplaceNode(nodePtr, dottedAccess); + + // If we've replaced |expr["prop"]| with |expr.prop|, we can now free the + // |"prop"| and |expr["prop"]| nodes -- but not the |expr| node that we're + // now using as a sub-node of |dottedAccess|. Munge |expr["prop"]| into a + // node with |"prop"| as its only child, that'll pass AST sanity-checking + // assertions during freeing, then free it. + node->setKind(PNK_TYPEOFEXPR); + node->setArity(PN_UNARY); + node->pn_kid = key; + parser.freeTree(node); + + return true; +} + +static bool +FoldAdd(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + ParseNode* node = *nodePtr; + + MOZ_ASSERT(node->isKind(PNK_ADD)); + MOZ_ASSERT(node->isArity(PN_LIST)); + MOZ_ASSERT(node->pn_count >= 2); + + // Generically fold all operands first. + if (!FoldList(cx, node, parser, inGenexpLambda)) + return false; + + // 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->pn_head; + ParseNode* next = current->pn_next; + if (current->isKind(PNK_NUMBER)) { + do { + if (!next->isKind(PNK_NUMBER)) + break; + + current->pn_dval += next->pn_dval; + current->pn_next = next->pn_next; + parser.freeTree(next); + next = current->pn_next; + + MOZ_ASSERT(node->pn_count > 1); + node->pn_count--; + } 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(PNK_NUMBER) && next->isKind(PNK_STRING)) { + if (!FoldType(cx, current, PNK_STRING)) + return false; + next = current->pn_next; + } + + // The first string forces all subsequent additions to be + // string concatenations. + do { + if (current->isKind(PNK_STRING)) + break; + + current = next; + next = next->pn_next; + } while (next); + + // If there's nothing left to fold, we're done. + if (!next) + break; + + RootedString combination(cx); + RootedString tmp(cx); + do { + // Create a rope of the current string and all succeeding + // constants that we can convert to strings, then atomize it + // and replace them all with that fresh string. + MOZ_ASSERT(current->isKind(PNK_STRING)); + + combination = current->pn_atom; + + do { + // Try folding the next operand to a string. + if (!FoldType(cx, next, PNK_STRING)) + return false; + + // Stop glomming once folding doesn't produce a string. + if (!next->isKind(PNK_STRING)) + break; + + // Add this string to the combination and remove the node. + tmp = next->pn_atom; + combination = ConcatStrings<CanGC>(cx, combination, tmp); + if (!combination) + return false; + + current->pn_next = next->pn_next; + parser.freeTree(next); + next = current->pn_next; + + MOZ_ASSERT(node->pn_count > 1); + node->pn_count--; + } while (next); + + // Replace |current|'s string with the entire combination. + MOZ_ASSERT(current->isKind(PNK_STRING)); + combination = AtomizeString(cx, combination); + if (!combination) + return false; + current->pn_atom = &combination->asAtom(); + + + // 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; + next = current->pn_next; + + if (!FoldType(cx, current, PNK_STRING)) + return false; + next = current->pn_next; + } while (!current->isKind(PNK_STRING) && 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->pn_tail = ¤t->pn_next; + node->checkListConsistency(); + + if (node->pn_count == 1) { + // We reduced the list to a constant. Replace the PNK_ADD node + // with that constant. + ReplaceNode(nodePtr, current); + + // Free the old node to aggressively verify nothing uses it. + node->setKind(PNK_TRUE); + node->setArity(PN_NULLARY); + node->setOp(JSOP_TRUE); + parser.freeTree(node); + } + + return true; +} + +static bool +FoldCall(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_SUPERCALL) || + node->isKind(PNK_TAGGED_TEMPLATE)); + MOZ_ASSERT(node->isArity(PN_LIST)); + + // 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"); + // + // See bug 537673 and bug 1182373. + ParseNode** listp = &node->pn_head; + if ((*listp)->isInParens()) + listp = &(*listp)->pn_next; + + for (; *listp; listp = &(*listp)->pn_next) { + if (!Fold(cx, listp, parser, inGenexpLambda)) + return false; + } + + // If the last node in the list was replaced, pn_tail points into the wrong node. + node->pn_tail = listp; + + node->checkListConsistency(); + return true; +} + +static bool +FoldForInOrOf(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_FORIN) || node->isKind(PNK_FOROF)); + MOZ_ASSERT(node->isArity(PN_TERNARY)); + MOZ_ASSERT(!node->pn_kid2); + + return Fold(cx, &node->pn_kid1, parser, inGenexpLambda) && + Fold(cx, &node->pn_kid3, parser, inGenexpLambda); +} + +static bool +FoldForHead(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_FORHEAD)); + MOZ_ASSERT(node->isArity(PN_TERNARY)); + + if (ParseNode*& init = node->pn_kid1) { + if (!Fold(cx, &init, parser, inGenexpLambda)) + return false; + } + + if (ParseNode*& test = node->pn_kid2) { + if (!FoldCondition(cx, &test, parser, inGenexpLambda)) + return false; + + if (test->isKind(PNK_TRUE)) { + parser.freeTree(test); + test = nullptr; + } + } + + if (ParseNode*& update = node->pn_kid3) { + if (!Fold(cx, &update, parser, inGenexpLambda)) + return false; + } + + return true; +} + +static bool +FoldDottedProperty(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_DOT)); + MOZ_ASSERT(node->isArity(PN_NAME)); + + // Iterate through a long chain of dotted property accesses to find the + // most-nested non-dotted property node, then fold that. + ParseNode** nested = &node->pn_expr; + while ((*nested)->isKind(PNK_DOT)) { + MOZ_ASSERT((*nested)->isArity(PN_NAME)); + nested = &(*nested)->pn_expr; + } + + return Fold(cx, nested, parser, inGenexpLambda); +} + +static bool +FoldName(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser, + bool inGenexpLambda) +{ + MOZ_ASSERT(node->isKind(PNK_NAME)); + MOZ_ASSERT(node->isArity(PN_NAME)); + + if (!node->pn_expr) + return true; + + return Fold(cx, &node->pn_expr, parser, inGenexpLambda); +} + +bool +Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda) +{ + JS_CHECK_RECURSION(cx, return false); + + ParseNode* pn = *pnp; + + switch (pn->getKind()) { + case PNK_NOP: + case PNK_REGEXP: + case PNK_STRING: + case PNK_TRUE: + case PNK_FALSE: + case PNK_NULL: + case PNK_ELISION: + case PNK_NUMBER: + case PNK_DEBUGGER: + case PNK_BREAK: + case PNK_CONTINUE: + case PNK_TEMPLATE_STRING: + case PNK_GENERATOR: + case PNK_EXPORT_BATCH_SPEC: + case PNK_OBJECT_PROPERTY_NAME: + case PNK_POSHOLDER: + MOZ_ASSERT(pn->isArity(PN_NULLARY)); + return true; + + case PNK_SUPERBASE: + case PNK_TYPEOFNAME: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME)); + MOZ_ASSERT(!pn->pn_kid->expr()); + return true; + + case PNK_TYPEOFEXPR: + return FoldTypeOfExpr(cx, pn, parser, inGenexpLambda); + + case PNK_DELETENAME: { + MOZ_ASSERT(pn->isArity(PN_UNARY)); + MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME)); + return true; + } + + case PNK_DELETEEXPR: + return FoldDeleteExpr(cx, pn, parser, inGenexpLambda); + + case PNK_DELETEELEM: + return FoldDeleteElement(cx, pn, parser, inGenexpLambda); + + case PNK_DELETEPROP: + return FoldDeleteProperty(cx, pn, parser, inGenexpLambda); + + case PNK_CONDITIONAL: + return FoldConditional(cx, pnp, parser, inGenexpLambda); + + case PNK_IF: + return FoldIf(cx, pnp, parser, inGenexpLambda); + + case PNK_NOT: + return FoldNot(cx, pn, parser, inGenexpLambda); + + case PNK_BITNOT: + case PNK_POS: + case PNK_NEG: + return FoldUnaryArithmetic(cx, pn, parser, inGenexpLambda); + + case PNK_PREINCREMENT: + case PNK_POSTINCREMENT: + case PNK_PREDECREMENT: + case PNK_POSTDECREMENT: + return FoldIncrementDecrement(cx, pn, parser, inGenexpLambda); + + case PNK_THROW: + case PNK_ARRAYPUSH: + case PNK_MUTATEPROTO: + case PNK_COMPUTED_NAME: + case PNK_SPREAD: + case PNK_EXPORT: + case PNK_VOID: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + return Fold(cx, &pn->pn_kid, parser, inGenexpLambda); + + case PNK_EXPORT_DEFAULT: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda); + + case PNK_SEMI: + case PNK_THIS: + MOZ_ASSERT(pn->isArity(PN_UNARY)); + if (ParseNode*& expr = pn->pn_kid) + return Fold(cx, &expr, parser, inGenexpLambda); + return true; + + case PNK_AND: + case PNK_OR: + return FoldAndOr(cx, pnp, parser, inGenexpLambda); + + case PNK_FUNCTION: + return FoldFunction(cx, pn, parser, inGenexpLambda); + + case PNK_MODULE: + return FoldModule(cx, pn, parser); + + case PNK_SUB: + case PNK_STAR: + case PNK_LSH: + case PNK_RSH: + case PNK_URSH: + case PNK_DIV: + case PNK_MOD: + return FoldBinaryArithmetic(cx, pn, parser, inGenexpLambda); + + case PNK_POW: + return FoldExponentiation(cx, pn, parser, inGenexpLambda); + + // Various list nodes not requiring care to minimally fold. Some of + // these could be further folded/optimized, but we don't make the effort. + case PNK_BITOR: + case PNK_BITXOR: + case PNK_BITAND: + case PNK_STRICTEQ: + case PNK_EQ: + case PNK_STRICTNE: + case PNK_NE: + case PNK_LT: + case PNK_LE: + case PNK_GT: + case PNK_GE: + case PNK_INSTANCEOF: + case PNK_IN: + case PNK_COMMA: + case PNK_NEW: + case PNK_ARRAY: + case PNK_OBJECT: + case PNK_ARRAYCOMP: + case PNK_STATEMENTLIST: + case PNK_CLASSMETHODLIST: + case PNK_CATCHLIST: + case PNK_TEMPLATE_STRING_LIST: + case PNK_VAR: + case PNK_CONST: + case PNK_LET: + case PNK_PARAMSBODY: + case PNK_CALLSITEOBJ: + case PNK_EXPORT_SPEC_LIST: + case PNK_IMPORT_SPEC_LIST: + case PNK_GENEXP: + return FoldList(cx, pn, parser, inGenexpLambda); + + case PNK_YIELD_STAR: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME)); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda); + + case PNK_YIELD: + case PNK_AWAIT: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + MOZ_ASSERT(pn->pn_right->isKind(PNK_NAME) || + (pn->pn_right->isKind(PNK_ASSIGN) && + pn->pn_right->pn_left->isKind(PNK_NAME) && + pn->pn_right->pn_right->isKind(PNK_GENERATOR))); + if (!pn->pn_left) + return true; + return Fold(cx, &pn->pn_left, parser, inGenexpLambda); + + case PNK_RETURN: + return FoldReturn(cx, pn, parser, inGenexpLambda); + + case PNK_TRY: + return FoldTry(cx, pn, parser, inGenexpLambda); + + case PNK_CATCH: + return FoldCatch(cx, pn, parser, inGenexpLambda); + + case PNK_CLASS: + return FoldClass(cx, pn, parser, inGenexpLambda); + + case PNK_ELEM: + return FoldElement(cx, pnp, parser, inGenexpLambda); + + case PNK_ADD: + return FoldAdd(cx, pnp, parser, inGenexpLambda); + + case PNK_CALL: + case PNK_SUPERCALL: + case PNK_TAGGED_TEMPLATE: + return FoldCall(cx, pn, parser, inGenexpLambda); + + case PNK_SWITCH: + case PNK_COLON: + case PNK_ASSIGN: + case PNK_ADDASSIGN: + case PNK_SUBASSIGN: + case PNK_BITORASSIGN: + case PNK_BITANDASSIGN: + case PNK_BITXORASSIGN: + case PNK_LSHASSIGN: + case PNK_RSHASSIGN: + case PNK_URSHASSIGN: + case PNK_DIVASSIGN: + case PNK_MODASSIGN: + case PNK_MULASSIGN: + case PNK_POWASSIGN: + case PNK_IMPORT: + case PNK_EXPORT_FROM: + case PNK_SHORTHAND: + case PNK_FOR: + case PNK_COMPREHENSIONFOR: + case PNK_CLASSMETHOD: + case PNK_IMPORT_SPEC: + case PNK_EXPORT_SPEC: + case PNK_SETTHIS: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda) && + Fold(cx, &pn->pn_right, parser, inGenexpLambda); + + case PNK_NEWTARGET: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + MOZ_ASSERT(pn->pn_left->isKind(PNK_POSHOLDER)); + MOZ_ASSERT(pn->pn_right->isKind(PNK_POSHOLDER)); + return true; + + case PNK_CLASSNAMES: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + if (ParseNode*& outerBinding = pn->pn_left) { + if (!Fold(cx, &outerBinding, parser, inGenexpLambda)) + return false; + } + return Fold(cx, &pn->pn_right, parser, inGenexpLambda); + + case PNK_DOWHILE: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda) && + FoldCondition(cx, &pn->pn_right, parser, inGenexpLambda); + + case PNK_WHILE: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + return FoldCondition(cx, &pn->pn_left, parser, inGenexpLambda) && + Fold(cx, &pn->pn_right, parser, inGenexpLambda); + + case PNK_CASE: { + MOZ_ASSERT(pn->isArity(PN_BINARY)); + + // pn_left is null for DefaultClauses. + if (pn->pn_left) { + if (!Fold(cx, &pn->pn_left, parser, inGenexpLambda)) + return false; + } + return Fold(cx, &pn->pn_right, parser, inGenexpLambda); + } + + case PNK_WITH: + MOZ_ASSERT(pn->isArity(PN_BINARY)); + return Fold(cx, &pn->pn_left, parser, inGenexpLambda) && + Fold(cx, &pn->pn_right, parser, inGenexpLambda); + + case PNK_FORIN: + case PNK_FOROF: + return FoldForInOrOf(cx, pn, parser, inGenexpLambda); + + case PNK_FORHEAD: + return FoldForHead(cx, pn, parser, inGenexpLambda); + + case PNK_LABEL: + MOZ_ASSERT(pn->isArity(PN_NAME)); + return Fold(cx, &pn->pn_expr, parser, inGenexpLambda); + + case PNK_DOT: + return FoldDottedProperty(cx, pn, parser, inGenexpLambda); + + case PNK_LEXICALSCOPE: + MOZ_ASSERT(pn->isArity(PN_SCOPE)); + if (!pn->scopeBody()) + return true; + return Fold(cx, &pn->pn_u.scope.body, parser, inGenexpLambda); + + case PNK_NAME: + return FoldName(cx, pn, parser, inGenexpLambda); + + case PNK_LIMIT: // invalid sentinel value + MOZ_CRASH("invalid node kind"); + } + + MOZ_CRASH("shouldn't reach here"); + return false; +} + +bool +frontend::FoldConstants(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>* parser) +{ + // Don't constant-fold inside "use asm" code, as this could create a parse + // tree that doesn't type-check as asm.js. + if (parser->pc->useAsmOrInsideUseAsm()) + return true; + + return Fold(cx, pnp, *parser, false); +} |