mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1130811 - Examine nodes kind-wise when deciding whether a node contains a hoisted declaration. r=shu
This commit is contained in:
parent
5651d15e52
commit
4648bc5a38
@ -29,67 +29,384 @@ 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;
|
||||
}
|
||||
if (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST)) {
|
||||
*resultp = pn;
|
||||
|
||||
// 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;
|
||||
}
|
||||
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)
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PN_TERNARY:
|
||||
if (!ContainsVarOrConst(cx, pn->pn_kid1, resultp))
|
||||
return false;
|
||||
if (*resultp)
|
||||
return true;
|
||||
if (!ContainsVarOrConst(cx, pn->pn_kid2, resultp))
|
||||
return false;
|
||||
if (*resultp)
|
||||
return true;
|
||||
return ContainsVarOrConst(cx, pn->pn_kid3, resultp);
|
||||
ParseNode *loopBody = node->pn_right;
|
||||
return ContainsHoistedDeclaration(cx, loopBody, 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;
|
||||
}
|
||||
if (!ContainsVarOrConst(cx, pn->pn_left, resultp))
|
||||
return false;
|
||||
if (*resultp)
|
||||
return true;
|
||||
return ContainsVarOrConst(cx, pn->pn_right, resultp);
|
||||
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 PN_UNARY:
|
||||
if (!pn->isOp(JSOP_NOP)) {
|
||||
*resultp = nullptr;
|
||||
return true;
|
||||
}
|
||||
return ContainsVarOrConst(cx, pn->pn_kid, resultp);
|
||||
case PNK_LEXICALSCOPE: {
|
||||
ParseNode *expr = node->pn_expr;
|
||||
|
||||
case PN_NAME:
|
||||
return ContainsVarOrConst(cx, pn->maybeExpr(), resultp);
|
||||
if (expr->isKind(PNK_FOR))
|
||||
return ContainsHoistedDeclaration(cx, expr, result);
|
||||
|
||||
default:;
|
||||
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_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 PNK_GLOBALCONST:
|
||||
MOZ_CRASH("ContainsHoistedDeclaration is only called on nested nodes where "
|
||||
"globalconst nodes should never have been generated");
|
||||
|
||||
case PNK_LIMIT: // invalid sentinel value
|
||||
MOZ_CRASH("unexpected PNK_LIMIT in node");
|
||||
}
|
||||
*resultp = nullptr;
|
||||
return true;
|
||||
|
||||
MOZ_CRASH("invalid node kind");
|
||||
}
|
||||
|
||||
/*
|
||||
@ -424,7 +741,7 @@ Fold(ExclusiveContext *cx, ParseNode **pnp,
|
||||
// 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;
|
||||
@ -432,15 +749,19 @@ Fold(ExclusiveContext *cx, ParseNode **pnp,
|
||||
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 */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user