--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -24,77 +24,394 @@ using mozilla::IsNaN;
using mozilla::IsNegative;
using mozilla::NegativeInfinity;
using mozilla::PositiveInfinity;
using JS::GenericNaN;
using JS::ToInt32;
using JS::ToUint32;
static bool
-ContainsVarOrConst(ExclusiveContext *cx, ParseNode *pn, ParseNode **resultp)
+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);
- if (!pn) {
- *resultp = nullptr;
+ // 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).
+ // (Global lexical declarations, in addition to being irrelevant here as
+ // ContainsHoistedDeclaration is only used on the arms of an |if|
+ // statement, are handled by PNK_GLOBALCONST and PNK_VAR.)
+ case PNK_LET:
+ case PNK_CONST:
+ *result = false;
+ return true;
+
+ // ContainsHoistedDeclaration is only called on nested nodes, so any
+ // instance of this can't be function statements at body level. In
+ // SpiderMonkey, a binding induced by a function statement is added when
+ // the function statement is evaluated. Thus any declaration introduced
+ // by a function statement, as observed by this function, isn't a hoisted
+ // declaration.
+ case PNK_FUNCTION:
+ *result = false;
return true;
- }
- if (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST)) {
- *resultp = pn;
+
+ // Statements with no sub-components at all.
+ case PNK_NOP: // induced by function f() {} function f() {}
+ case PNK_DEBUGGER:
+ *result = false;
+ return true;
+
+ // Statements containing only an expression have no declarations.
+ case PNK_SEMI:
+ case PNK_RETURN:
+ case PNK_THROW:
+ // These two aren't statements in the spec, but we sometimes insert them
+ // in statement lists anyway.
+ case PNK_YIELD_STAR:
+ case PNK_YIELD:
+ *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_SPEC_LIST:
+ case PNK_EXPORT_SPEC:
+ case PNK_EXPORT:
+ case PNK_EXPORT_BATCH_SPEC:
+ *result = false;
return true;
- }
- switch (pn->getArity()) {
- case PN_LIST:
- for (ParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
- if (!ContainsVarOrConst(cx, pn2, resultp))
- return false;
- if (*resultp)
- return true;
- }
- break;
+
+ // 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 (ParseNode *alternative = node->pn_kid3)
+ return ContainsHoistedDeclaration(cx, alternative, result);
+
+ *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;
- case PN_TERNARY:
- if (!ContainsVarOrConst(cx, pn->pn_kid1, resultp))
+ // 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 (*resultp)
+ if (*result)
return true;
- if (!ContainsVarOrConst(cx, pn->pn_kid2, resultp))
- return false;
- if (*resultp)
- return true;
- return ContainsVarOrConst(cx, pn->pn_kid3, resultp);
+
+ 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);
+
+ // A case/default node's right half is its statements. A default node's
+ // left half is null; a case node's left half is its expression.
+ case PNK_DEFAULT:
+ MOZ_ASSERT(!node->pn_left);
+ case PNK_CASE:
+ MOZ_ASSERT(node->isArity(PN_BINARY));
+ return ContainsHoistedDeclaration(cx, node->pn_right, result);
- case PN_BINARY:
- case PN_BINARY_OBJ:
- // Limit recursion if pn is a binary expression, which can't contain a
- // var statement.
- if (!pn->isOp(JSOP_NOP)) {
- *resultp = nullptr;
- return true;
+ // PNK_SEQ has two purposes.
+ //
+ // The first is to prepend destructuring operations to the body of a
+ // deprecated function expression closure: irrelevant here, as this
+ // function doesn't recur into PNK_FUNCTION, and this method's sole
+ // caller acts upon statements nested in if-statements not found in
+ // destructuring operations.
+ //
+ // The second is to provide a place for a hoisted declaration to go, in
+ // the bizarre for-in/of loops that have as target a declaration with an
+ // assignment, e.g. |for (var i = 0 in expr)|. This case is sadly still
+ // relevant, so we can't banish this ParseNodeKind to the unreachable
+ // list and must check every list member.
+ case PNK_SEQ:
+ return ListContainsHoistedDeclaration(cx, &node->as<ListNode>(), result);
+
+ case PNK_FOR: {
+ 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 either
+ // |loopHead|'s first kid, *or* that declaration was hoisted to
+ // become a child of an ancestral PNK_SEQ node. The former case we
+ // detect here. The latter case is handled by this method
+ // recurring on PNK_SEQ, above.
+ MOZ_ASSERT(loopHead->isArity(PN_TERNARY));
+
+ ParseNode *decl = loopHead->pn_kid1;
+ if (decl && decl->isKind(PNK_VAR)) {
+ *result = true;
+ return true;
+ }
}
- if (!ContainsVarOrConst(cx, pn->pn_left, resultp))
- return false;
- if (*resultp)
- return true;
- return ContainsVarOrConst(cx, pn->pn_right, resultp);
+
+ ParseNode *loopBody = node->pn_right;
+ return ContainsHoistedDeclaration(cx, loopBody, result);
+ }
+
+ case PNK_LETBLOCK: {
+ MOZ_ASSERT(node->isArity(PN_BINARY));
+ MOZ_ASSERT(node->pn_left->isKind(PNK_LET));
+ MOZ_ASSERT(node->pn_right->isKind(PNK_LEXICALSCOPE));
+ return ContainsHoistedDeclaration(cx, node->pn_right, result);
+ }
+
+ case PNK_LEXICALSCOPE: {
+ ParseNode *expr = node->pn_expr;
+
+ if (expr->isKind(PNK_FOR))
+ 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);
- case PN_UNARY:
- if (!pn->isOp(JSOP_NOP)) {
- *resultp = nullptr;
- return true;
- }
- return ContainsVarOrConst(cx, pn->pn_kid, resultp);
+ // Grammar sub-components that should never be reached directly by this
+ // method, because some parent component should have asserted itself.
+ case PNK_COMPUTED_NAME:
+ case PNK_SPREAD:
+ case PNK_MUTATEPROTO:
+ case PNK_COLON:
+ case PNK_SHORTHAND:
+ case PNK_CONDITIONAL:
+ case PNK_TYPEOF:
+ case PNK_VOID:
+ case PNK_NOT:
+ case PNK_BITNOT:
+ case PNK_DELETE:
+ 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_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_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_LETEXPR:
+ case PNK_ELISION:
+ case PNK_NUMBER:
+ case PNK_NEW:
+ case PNK_GENERATOR:
+ case PNK_GENEXP:
+ case PNK_ARRAYCOMP:
+ case PNK_ARGSBODY:
+ case PNK_CATCHLIST:
+ case PNK_CATCH:
+ case PNK_FORIN:
+ case PNK_FOROF:
+ case PNK_FORHEAD:
+ MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
+ "some parent node without recurring to test this node");
- case PN_NAME:
- return ContainsVarOrConst(cx, pn->maybeExpr(), resultp);
+ case PNK_GLOBALCONST:
+ MOZ_CRASH("ContainsHoistedDeclaration is only called on nested nodes where "
+ "globalconst nodes should never have been generated");
- default:;
+ case PNK_LIMIT: // invalid sentinel value
+ MOZ_CRASH("unexpected PNK_LIMIT in node");
}
- *resultp = nullptr;
- return true;
+
+ 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)
@@ -419,33 +736,37 @@ Fold(ExclusiveContext *cx, ParseNode **p
case PN_NULLARY:
break;
}
// The immediate child of a PNK_DELETE node should not be replaced
// with node indicating a different syntactic form; |delete x| is not
// the same as |delete (true && x)|. See bug 888002.
//
- // pn is the immediate child in question. Its descendents were already
+ // pn is the immediate child in question. Its descendants were already
// constant-folded above, so we're done.
if (sc == SyntacticContext::Delete)
return true;
switch (pn->getKind()) {
case PNK_IF:
{
- ParseNode *decl;
- if (!ContainsVarOrConst(cx, pn2, &decl))
- return false;
- if (decl)
- break;
- if (!ContainsVarOrConst(cx, pn3, &decl))
- return false;
- if (decl)
- break;
+ bool result;
+ if (ParseNode *consequent = pn2) {
+ if (!ContainsHoistedDeclaration(cx, consequent, &result))
+ return false;
+ if (result)
+ break;
+ }
+ if (ParseNode *alternative = pn3) {
+ if (!ContainsHoistedDeclaration(cx, alternative, &result))
+ return false;
+ if (result)
+ break;
+ }
}
/* FALL THROUGH */
case PNK_CONDITIONAL:
/* Reduce 'if (C) T; else E' into T for true C, E for false. */
switch (pn1->getKind()) {
case PNK_NUMBER:
if (pn1->pn_dval == 0 || IsNaN(pn1->pn_dval))