diff options
Diffstat (limited to 'js/src/frontend/NameFunctions.cpp')
-rw-r--r-- | js/src/frontend/NameFunctions.cpp | 838 |
1 files changed, 838 insertions, 0 deletions
diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp new file mode 100644 index 000000000..ce1318f0b --- /dev/null +++ b/js/src/frontend/NameFunctions.cpp @@ -0,0 +1,838 @@ +/* -*- 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/NameFunctions.h" + +#include "mozilla/Sprintf.h" + +#include "jsfun.h" +#include "jsprf.h" + +#include "frontend/BytecodeCompiler.h" +#include "frontend/ParseNode.h" +#include "frontend/SharedContext.h" +#include "vm/StringBuffer.h" + +using namespace js; +using namespace js::frontend; + +namespace { + +class NameResolver +{ + static const size_t MaxParents = 100; + + ExclusiveContext* cx; + size_t nparents; /* number of parents in the parents array */ + ParseNode* parents[MaxParents]; /* history of ParseNodes we've been looking at */ + StringBuffer* buf; /* when resolving, buffer to append to */ + + /* Test whether a ParseNode represents a function invocation */ + bool call(ParseNode* pn) { + return pn && pn->isKind(PNK_CALL); + } + + /* + * Append a reference to a property named |name| to |buf|. If |name| is + * a proper identifier name, then we append '.name'; otherwise, we + * append '["name"]'. + * + * Note that we need the IsIdentifier check for atoms from both + * PNK_NAME nodes and PNK_STRING nodes: given code like a["b c"], the + * front end will produce a PNK_DOT with a PNK_NAME child whose name + * contains spaces. + */ + bool appendPropertyReference(JSAtom* name) { + if (IsIdentifier(name)) + return buf->append('.') && buf->append(name); + + /* Quote the string as needed. */ + JSString* source = QuoteString(cx, name, '"'); + return source && buf->append('[') && buf->append(source) && buf->append(']'); + } + + /* Append a number to buf. */ + bool appendNumber(double n) { + char number[30]; + int digits = SprintfLiteral(number, "%g", n); + return buf->append(number, digits); + } + + /* Append "[<n>]" to buf, referencing a property named by a numeric literal. */ + bool appendNumericPropertyReference(double n) { + return buf->append("[") && appendNumber(n) && buf->append(']'); + } + + /* + * Walk over the given ParseNode, attempting to convert it to a stringified + * name that respresents where the function is being assigned to. + * + * |*foundName| is set to true if a name is found for the expression. + */ + bool nameExpression(ParseNode* n, bool* foundName) { + switch (n->getKind()) { + case PNK_DOT: + if (!nameExpression(n->expr(), foundName)) + return false; + if (!*foundName) + return true; + return appendPropertyReference(n->pn_atom); + + case PNK_NAME: + *foundName = true; + return buf->append(n->pn_atom); + + case PNK_THIS: + *foundName = true; + return buf->append("this"); + + case PNK_ELEM: + if (!nameExpression(n->pn_left, foundName)) + return false; + if (!*foundName) + return true; + if (!buf->append('[') || !nameExpression(n->pn_right, foundName)) + return false; + if (!*foundName) + return true; + return buf->append(']'); + + case PNK_NUMBER: + *foundName = true; + return appendNumber(n->pn_dval); + + default: + /* We're confused as to what to call this function. */ + *foundName = false; + return true; + } + } + + /* + * When naming an anonymous function, the process works loosely by walking + * up the AST and then translating that to a string. The stringification + * happens from some far-up assignment and then going back down the parse + * tree to the function definition point. + * + * This function will walk up the parse tree, gathering relevant nodes used + * for naming, and return the assignment node if there is one. The provided + * array and size will be filled in, and the returned node could be nullptr + * if no assignment is found. The first element of the array will be the + * innermost node relevant to naming, and the last element will be the + * outermost node. + */ + ParseNode* gatherNameable(ParseNode** nameable, size_t* size) { + *size = 0; + + for (int pos = nparents - 1; pos >= 0; pos--) { + ParseNode* cur = parents[pos]; + if (cur->isAssignment()) + return cur; + + switch (cur->getKind()) { + case PNK_NAME: return cur; /* found the initialized declaration */ + case PNK_THIS: return cur; /* Setting a property of 'this'. */ + case PNK_FUNCTION: return nullptr; /* won't find an assignment or declaration */ + + case PNK_RETURN: + /* + * Normally the relevant parent of a node is its direct parent, but + * sometimes with code like: + * + * var foo = (function() { return function() {}; })(); + * + * the outer function is just a helper to create a scope for the + * returned function. Hence the name of the returned function should + * actually be 'foo'. This loop sees if the current node is a + * PNK_RETURN, and if there is a direct function call we skip to + * that. + */ + for (int tmp = pos - 1; tmp > 0; tmp--) { + if (isDirectCall(tmp, cur)) { + pos = tmp; + break; + } else if (call(cur)) { + /* Don't skip too high in the tree */ + break; + } + cur = parents[tmp]; + } + break; + + case PNK_COLON: + case PNK_SHORTHAND: + /* + * Record the PNK_COLON/SHORTHAND but skip the PNK_OBJECT so we're not + * flagged as a contributor. + */ + pos--; + MOZ_FALLTHROUGH; + + default: + /* Save any other nodes we encounter on the way up. */ + MOZ_ASSERT(*size < MaxParents); + nameable[(*size)++] = cur; + break; + } + } + + return nullptr; + } + + /* + * Resolve the name of a function. If the function already has a name + * listed, then it is skipped. Otherwise an intelligent name is guessed to + * assign to the function's displayAtom field. + */ + bool resolveFun(ParseNode* pn, HandleAtom prefix, MutableHandleAtom retAtom) { + MOZ_ASSERT(pn != nullptr); + MOZ_ASSERT(pn->isKind(PNK_FUNCTION)); + MOZ_ASSERT(pn->isArity(PN_CODE)); + RootedFunction fun(cx, pn->pn_funbox->function()); + + StringBuffer buf(cx); + this->buf = &buf; + + retAtom.set(nullptr); + + /* If the function already has a name, use that */ + if (fun->displayAtom() != nullptr) { + if (prefix == nullptr) { + retAtom.set(fun->displayAtom()); + return true; + } + if (!buf.append(prefix) || + !buf.append('/') || + !buf.append(fun->displayAtom())) + return false; + retAtom.set(buf.finishAtom()); + return !!retAtom; + } + + /* If a prefix is specified, then it is a form of namespace */ + if (prefix != nullptr && (!buf.append(prefix) || !buf.append('/'))) + return false; + + /* Gather all nodes relevant to naming */ + ParseNode* toName[MaxParents]; + size_t size; + ParseNode* assignment = gatherNameable(toName, &size); + + /* If the function is assigned to something, then that is very relevant */ + if (assignment) { + if (assignment->isAssignment()) + assignment = assignment->pn_left; + bool foundName = false; + if (!nameExpression(assignment, &foundName)) + return false; + if (!foundName) + return true; + } + + /* + * Other than the actual assignment, other relevant nodes to naming are + * those in object initializers and then particular nodes marking a + * contribution. + */ + for (int pos = size - 1; pos >= 0; pos--) { + ParseNode* node = toName[pos]; + + if (node->isKind(PNK_COLON) || node->isKind(PNK_SHORTHAND)) { + ParseNode* left = node->pn_left; + if (left->isKind(PNK_OBJECT_PROPERTY_NAME) || left->isKind(PNK_STRING)) { + if (!appendPropertyReference(left->pn_atom)) + return false; + } else if (left->isKind(PNK_NUMBER)) { + if (!appendNumericPropertyReference(left->pn_dval)) + return false; + } else { + MOZ_ASSERT(left->isKind(PNK_COMPUTED_NAME)); + } + } else { + /* + * Don't have consecutive '<' characters, and also don't start + * with a '<' character. + */ + if (!buf.empty() && buf.getChar(buf.length() - 1) != '<' && !buf.append('<')) + return false; + } + } + + /* + * functions which are "genuinely anonymous" but are contained in some + * other namespace are rather considered as "contributing" to the outer + * function, so give them a contribution symbol here. + */ + if (!buf.empty() && buf.getChar(buf.length() - 1) == '/' && !buf.append('<')) + return false; + + if (buf.empty()) + return true; + + retAtom.set(buf.finishAtom()); + if (!retAtom) + return false; + fun->setGuessedAtom(retAtom); + return true; + } + + /* + * Tests whether parents[pos] is a function call whose callee is cur. + * This is the case for functions which do things like simply create a scope + * for new variables and then return an anonymous function using this scope. + */ + bool isDirectCall(int pos, ParseNode* cur) { + return pos >= 0 && call(parents[pos]) && parents[pos]->pn_head == cur; + } + + bool resolveTemplateLiteral(ParseNode* node, HandleAtom prefix) { + MOZ_ASSERT(node->isKind(PNK_TEMPLATE_STRING_LIST)); + ParseNode* element = node->pn_head; + while (true) { + MOZ_ASSERT(element->isKind(PNK_TEMPLATE_STRING)); + + element = element->pn_next; + if (!element) + return true; + + if (!resolve(element, prefix)) + return false; + + element = element->pn_next; + } + } + + bool resolveTaggedTemplate(ParseNode* node, HandleAtom prefix) { + MOZ_ASSERT(node->isKind(PNK_TAGGED_TEMPLATE)); + + ParseNode* element = node->pn_head; + + // The list head is a leading expression, e.g. |tag| in |tag`foo`|, + // that might contain functions. + if (!resolve(element, prefix)) + return false; + + // Next is the callsite object node. This node only contains + // internal strings and an array -- no user-controlled expressions. + element = element->pn_next; +#ifdef DEBUG + { + MOZ_ASSERT(element->isKind(PNK_CALLSITEOBJ)); + ParseNode* array = element->pn_head; + MOZ_ASSERT(array->isKind(PNK_ARRAY)); + for (ParseNode* kid = array->pn_head; kid; kid = kid->pn_next) + MOZ_ASSERT(kid->isKind(PNK_TEMPLATE_STRING)); + for (ParseNode* next = array->pn_next; next; next = next->pn_next) + MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING)); + } +#endif + + // Next come any interpolated expressions in the tagged template. + ParseNode* interpolated = element->pn_next; + for (; interpolated; interpolated = interpolated->pn_next) { + if (!resolve(interpolated, prefix)) + return false; + } + + return true; + } + + public: + explicit NameResolver(ExclusiveContext* cx) : cx(cx), nparents(0), buf(nullptr) {} + + /* + * Resolve all names for anonymous functions recursively within the + * ParseNode instance given. The prefix is for each subsequent name, and + * should initially be nullptr. + */ + bool resolve(ParseNode* cur, HandleAtom prefixArg = nullptr) { + RootedAtom prefix(cx, prefixArg); + if (cur == nullptr) + return true; + + MOZ_ASSERT((cur->isKind(PNK_FUNCTION) || cur->isKind(PNK_MODULE)) == cur->isArity(PN_CODE)); + if (cur->isKind(PNK_FUNCTION)) { + RootedAtom prefix2(cx); + if (!resolveFun(cur, prefix, &prefix2)) + return false; + + /* + * If a function looks like (function(){})() where the parent node + * of the definition of the function is a call, then it shouldn't + * contribute anything to the namespace, so don't bother updating + * the prefix to whatever was returned. + */ + if (!isDirectCall(nparents - 1, cur)) + prefix = prefix2; + } + if (nparents >= MaxParents) + return true; + parents[nparents++] = cur; + + switch (cur->getKind()) { + // Nodes with no children that might require name resolution need no + // further work. + case PNK_NOP: + case PNK_STRING: + case PNK_TEMPLATE_STRING: + case PNK_REGEXP: + case PNK_TRUE: + case PNK_FALSE: + case PNK_NULL: + case PNK_ELISION: + case PNK_GENERATOR: + case PNK_NUMBER: + case PNK_BREAK: + case PNK_CONTINUE: + case PNK_DEBUGGER: + case PNK_EXPORT_BATCH_SPEC: + case PNK_OBJECT_PROPERTY_NAME: + case PNK_POSHOLDER: + MOZ_ASSERT(cur->isArity(PN_NULLARY)); + break; + + case PNK_TYPEOFNAME: + case PNK_SUPERBASE: + MOZ_ASSERT(cur->isArity(PN_UNARY)); + MOZ_ASSERT(cur->pn_kid->isKind(PNK_NAME)); + MOZ_ASSERT(!cur->pn_kid->expr()); + break; + + case PNK_NEWTARGET: + MOZ_ASSERT(cur->isArity(PN_BINARY)); + MOZ_ASSERT(cur->pn_left->isKind(PNK_POSHOLDER)); + MOZ_ASSERT(cur->pn_right->isKind(PNK_POSHOLDER)); + break; + + // Nodes with a single non-null child requiring name resolution. + case PNK_TYPEOFEXPR: + case PNK_VOID: + case PNK_NOT: + case PNK_BITNOT: + case PNK_THROW: + case PNK_DELETENAME: + case PNK_DELETEPROP: + case PNK_DELETEELEM: + case PNK_DELETEEXPR: + case PNK_NEG: + case PNK_POS: + case PNK_PREINCREMENT: + case PNK_POSTINCREMENT: + case PNK_PREDECREMENT: + case PNK_POSTDECREMENT: + case PNK_COMPUTED_NAME: + case PNK_ARRAYPUSH: + case PNK_SPREAD: + case PNK_MUTATEPROTO: + case PNK_EXPORT: + MOZ_ASSERT(cur->isArity(PN_UNARY)); + if (!resolve(cur->pn_kid, prefix)) + return false; + break; + + // Nodes with a single nullable child. + case PNK_SEMI: + case PNK_THIS: + MOZ_ASSERT(cur->isArity(PN_UNARY)); + if (ParseNode* expr = cur->pn_kid) { + if (!resolve(expr, prefix)) + return false; + } + break; + + // Binary nodes with two non-null children. + 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_COLON: + case PNK_SHORTHAND: + case PNK_DOWHILE: + case PNK_WHILE: + case PNK_SWITCH: + case PNK_FOR: + case PNK_COMPREHENSIONFOR: + case PNK_CLASSMETHOD: + case PNK_SETTHIS: + MOZ_ASSERT(cur->isArity(PN_BINARY)); + if (!resolve(cur->pn_left, prefix)) + return false; + if (!resolve(cur->pn_right, prefix)) + return false; + break; + + case PNK_ELEM: + MOZ_ASSERT(cur->isArity(PN_BINARY)); + if (!cur->as<PropertyByValue>().isSuper() && !resolve(cur->pn_left, prefix)) + return false; + if (!resolve(cur->pn_right, prefix)) + return false; + break; + + case PNK_WITH: + MOZ_ASSERT(cur->isArity(PN_BINARY)); + if (!resolve(cur->pn_left, prefix)) + return false; + if (!resolve(cur->pn_right, prefix)) + return false; + break; + + case PNK_CASE: + MOZ_ASSERT(cur->isArity(PN_BINARY)); + if (ParseNode* caseExpr = cur->pn_left) { + if (!resolve(caseExpr, prefix)) + return false; + } + if (!resolve(cur->pn_right, prefix)) + return false; + break; + + case PNK_YIELD_STAR: + MOZ_ASSERT(cur->isArity(PN_BINARY)); + MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME)); + if (!resolve(cur->pn_left, prefix)) + return false; + break; + + case PNK_YIELD: + case PNK_AWAIT: + MOZ_ASSERT(cur->isArity(PN_BINARY)); + if (cur->pn_left) { + if (!resolve(cur->pn_left, prefix)) + return false; + } + MOZ_ASSERT(cur->pn_right->isKind(PNK_NAME) || + (cur->pn_right->isKind(PNK_ASSIGN) && + cur->pn_right->pn_left->isKind(PNK_NAME) && + cur->pn_right->pn_right->isKind(PNK_GENERATOR))); + break; + + case PNK_RETURN: + MOZ_ASSERT(cur->isArity(PN_UNARY)); + if (ParseNode* returnValue = cur->pn_kid) { + if (!resolve(returnValue, prefix)) + return false; + } + break; + + case PNK_IMPORT: + case PNK_EXPORT_FROM: + case PNK_EXPORT_DEFAULT: + MOZ_ASSERT(cur->isArity(PN_BINARY)); + // The left halves of these nodes don't contain any unconstrained + // expressions, but it's very hard to assert this to safely rely on + // it. So recur anyway. + if (!resolve(cur->pn_left, prefix)) + return false; + MOZ_ASSERT_IF(!cur->isKind(PNK_EXPORT_DEFAULT), + cur->pn_right->isKind(PNK_STRING)); + break; + + // Ternary nodes with three expression children. + case PNK_CONDITIONAL: + MOZ_ASSERT(cur->isArity(PN_TERNARY)); + if (!resolve(cur->pn_kid1, prefix)) + return false; + if (!resolve(cur->pn_kid2, prefix)) + return false; + if (!resolve(cur->pn_kid3, prefix)) + return false; + break; + + // The first part of a for-in/of is the declaration in the loop (or + // null if no declaration). The latter two parts are the location + // assigned each loop and the value being looped over; obviously, + // either might contain functions to name. Declarations may (through + // computed property names, and possibly through [deprecated!] + // initializers) also contain functions to name. + case PNK_FORIN: + case PNK_FOROF: + MOZ_ASSERT(cur->isArity(PN_TERNARY)); + if (ParseNode* decl = cur->pn_kid1) { + if (!resolve(decl, prefix)) + return false; + } + if (!resolve(cur->pn_kid2, prefix)) + return false; + if (!resolve(cur->pn_kid3, prefix)) + return false; + break; + + // Every part of a for(;;) head may contain a function needing name + // resolution. + case PNK_FORHEAD: + MOZ_ASSERT(cur->isArity(PN_TERNARY)); + if (ParseNode* init = cur->pn_kid1) { + if (!resolve(init, prefix)) + return false; + } + if (ParseNode* cond = cur->pn_kid2) { + if (!resolve(cond, prefix)) + return false; + } + if (ParseNode* step = cur->pn_kid3) { + if (!resolve(step, prefix)) + return false; + } + break; + + // The first child of a class is a pair of names referring to it, + // inside and outside the class. The second is the class's heritage, + // if any. The third is the class body. + case PNK_CLASS: + MOZ_ASSERT(cur->isArity(PN_TERNARY)); + MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->isKind(PNK_CLASSNAMES)); + MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->isArity(PN_BINARY)); + MOZ_ASSERT_IF(cur->pn_kid1 && cur->pn_kid1->pn_left, + cur->pn_kid1->pn_left->isKind(PNK_NAME)); + MOZ_ASSERT_IF(cur->pn_kid1 && cur->pn_kid1->pn_left, + !cur->pn_kid1->pn_left->expr()); + MOZ_ASSERT_IF(cur->pn_kid1, cur->pn_kid1->pn_right->isKind(PNK_NAME)); + MOZ_ASSERT_IF(cur->pn_kid1, !cur->pn_kid1->pn_right->expr()); + if (cur->pn_kid2) { + if (!resolve(cur->pn_kid2, prefix)) + return false; + } + if (!resolve(cur->pn_kid3, prefix)) + return false; + break; + + // The condition and consequent are non-optional, but the alternative + // might be omitted. + case PNK_IF: + MOZ_ASSERT(cur->isArity(PN_TERNARY)); + if (!resolve(cur->pn_kid1, prefix)) + return false; + if (!resolve(cur->pn_kid2, prefix)) + return false; + if (cur->pn_kid3) { + if (!resolve(cur->pn_kid3, prefix)) + return false; + } + break; + + // The statements in the try-block are mandatory. The catch-blocks + // and finally block are optional (but at least one or the other must + // be present). + case PNK_TRY: + MOZ_ASSERT(cur->isArity(PN_TERNARY)); + if (!resolve(cur->pn_kid1, prefix)) + return false; + MOZ_ASSERT(cur->pn_kid2 || cur->pn_kid3); + if (ParseNode* catchList = cur->pn_kid2) { + MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST)); + if (!resolve(catchList, prefix)) + return false; + } + if (ParseNode* finallyBlock = cur->pn_kid3) { + if (!resolve(finallyBlock, prefix)) + return false; + } + break; + + // The first child, the catch-pattern, may contain functions via + // computed property names. The optional catch-conditions may + // contain any expression. The catch statements, of course, may + // contain arbitrary expressions. + case PNK_CATCH: + MOZ_ASSERT(cur->isArity(PN_TERNARY)); + if (!resolve(cur->pn_kid1, prefix)) + return false; + if (cur->pn_kid2) { + if (!resolve(cur->pn_kid2, prefix)) + return false; + } + if (!resolve(cur->pn_kid3, prefix)) + return false; + break; + + // Nodes with arbitrary-expression children. + 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_COMMA: + case PNK_NEW: + case PNK_CALL: + case PNK_SUPERCALL: + case PNK_GENEXP: + case PNK_ARRAY: + case PNK_STATEMENTLIST: + case PNK_PARAMSBODY: + // Initializers for individual variables, and computed property names + // within destructuring patterns, may contain unnamed functions. + case PNK_VAR: + case PNK_CONST: + case PNK_LET: + MOZ_ASSERT(cur->isArity(PN_LIST)); + for (ParseNode* element = cur->pn_head; element; element = element->pn_next) { + if (!resolve(element, prefix)) + return false; + } + break; + + // Array comprehension nodes are lists with a single child: + // PNK_COMPREHENSIONFOR for comprehensions, PNK_LEXICALSCOPE for + // legacy comprehensions. Probably this should be a non-list + // eventually. + case PNK_ARRAYCOMP: + MOZ_ASSERT(cur->isArity(PN_LIST)); + MOZ_ASSERT(cur->pn_count == 1); + MOZ_ASSERT(cur->pn_head->isKind(PNK_LEXICALSCOPE) || + cur->pn_head->isKind(PNK_COMPREHENSIONFOR)); + if (!resolve(cur->pn_head, prefix)) + return false; + break; + + case PNK_OBJECT: + case PNK_CLASSMETHODLIST: + MOZ_ASSERT(cur->isArity(PN_LIST)); + for (ParseNode* element = cur->pn_head; element; element = element->pn_next) { + if (!resolve(element, prefix)) + return false; + } + break; + + // A template string list's contents alternate raw template string + // contents with expressions interpolated into the overall literal. + case PNK_TEMPLATE_STRING_LIST: + MOZ_ASSERT(cur->isArity(PN_LIST)); + if (!resolveTemplateLiteral(cur, prefix)) + return false; + break; + + case PNK_TAGGED_TEMPLATE: + MOZ_ASSERT(cur->isArity(PN_LIST)); + if (!resolveTaggedTemplate(cur, prefix)) + return false; + break; + + // Import/export spec lists contain import/export specs containing + // only pairs of names. Alternatively, an export spec lists may + // contain a single export batch specifier. + case PNK_EXPORT_SPEC_LIST: + case PNK_IMPORT_SPEC_LIST: { + MOZ_ASSERT(cur->isArity(PN_LIST)); +#ifdef DEBUG + bool isImport = cur->isKind(PNK_IMPORT_SPEC_LIST); + ParseNode* item = cur->pn_head; + if (!isImport && item && item->isKind(PNK_EXPORT_BATCH_SPEC)) { + MOZ_ASSERT(item->isArity(PN_NULLARY)); + break; + } + for (; item; item = item->pn_next) { + MOZ_ASSERT(item->isKind(isImport ? PNK_IMPORT_SPEC : PNK_EXPORT_SPEC)); + MOZ_ASSERT(item->isArity(PN_BINARY)); + MOZ_ASSERT(item->pn_left->isKind(PNK_NAME)); + MOZ_ASSERT(!item->pn_left->expr()); + MOZ_ASSERT(item->pn_right->isKind(PNK_NAME)); + MOZ_ASSERT(!item->pn_right->expr()); + } +#endif + break; + } + + case PNK_CATCHLIST: { + MOZ_ASSERT(cur->isArity(PN_LIST)); + for (ParseNode* catchNode = cur->pn_head; catchNode; catchNode = catchNode->pn_next) { + MOZ_ASSERT(catchNode->isKind(PNK_LEXICALSCOPE)); + MOZ_ASSERT(catchNode->scopeBody()->isKind(PNK_CATCH)); + MOZ_ASSERT(catchNode->scopeBody()->isArity(PN_TERNARY)); + if (!resolve(catchNode->scopeBody(), prefix)) + return false; + } + break; + } + + case PNK_DOT: + MOZ_ASSERT(cur->isArity(PN_NAME)); + + // Super prop nodes do not have a meaningful LHS + if (cur->as<PropertyAccess>().isSuper()) + break; + if (!resolve(cur->expr(), prefix)) + return false; + break; + + case PNK_LABEL: + MOZ_ASSERT(cur->isArity(PN_NAME)); + if (!resolve(cur->expr(), prefix)) + return false; + break; + + case PNK_NAME: + MOZ_ASSERT(cur->isArity(PN_NAME)); + if (!resolve(cur->expr(), prefix)) + return false; + break; + + case PNK_LEXICALSCOPE: + MOZ_ASSERT(cur->isArity(PN_SCOPE)); + if (!resolve(cur->scopeBody(), prefix)) + return false; + break; + + case PNK_FUNCTION: + case PNK_MODULE: + MOZ_ASSERT(cur->isArity(PN_CODE)); + if (!resolve(cur->pn_body, prefix)) + return false; + break; + + // Kinds that should be handled by parent node resolution. + + case PNK_IMPORT_SPEC: // by PNK_IMPORT_SPEC_LIST + case PNK_EXPORT_SPEC: // by PNK_EXPORT_SPEC_LIST + case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE + case PNK_CLASSNAMES: // by PNK_CLASS + MOZ_CRASH("should have been handled by a parent node"); + + case PNK_LIMIT: // invalid sentinel value + MOZ_CRASH("invalid node kind"); + } + + nparents--; + return true; + } +}; + +} /* anonymous namespace */ + +bool +frontend::NameFunctions(ExclusiveContext* cx, ParseNode* pn) +{ + NameResolver nr(cx); + return nr.resolve(pn); +} |