2017-04-19 07:56:45 +00:00
|
|
|
/* -*- 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 "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 = JS_snprintf(number, sizeof(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, converting it to a stringified name that
|
|
|
|
* respresents where the function is being assigned to.
|
|
|
|
*/
|
|
|
|
bool nameExpression(ParseNode* n) {
|
|
|
|
switch (n->getKind()) {
|
|
|
|
case PNK_DOT:
|
|
|
|
return nameExpression(n->expr()) && appendPropertyReference(n->pn_atom);
|
|
|
|
|
|
|
|
case PNK_NAME:
|
|
|
|
return buf->append(n->pn_atom);
|
|
|
|
|
|
|
|
case PNK_THIS:
|
|
|
|
return buf->append("this");
|
|
|
|
|
|
|
|
case PNK_ELEM:
|
|
|
|
return nameExpression(n->pn_left) &&
|
|
|
|
buf->append('[') &&
|
|
|
|
nameExpression(n->pn_right) &&
|
|
|
|
buf->append(']');
|
|
|
|
|
|
|
|
case PNK_NUMBER:
|
|
|
|
return appendNumber(n->pn_dval);
|
|
|
|
|
|
|
|
default:
|
|
|
|
/*
|
|
|
|
* Technically this isn't an "abort" situation, we're just confused
|
|
|
|
* on what to call this function, but failures in naming aren't
|
|
|
|
* treated as fatal.
|
|
|
|
*/
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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--;
|
|
|
|
/* 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;
|
|
|
|
if (!nameExpression(assignment))
|
|
|
|
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->maybeExpr());
|
|
|
|
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_LETBLOCK:
|
|
|
|
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_OBJ));
|
|
|
|
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));
|
|
|
|
MOZ_ASSERT(!cur->pn_right->isAssigned());
|
|
|
|
if (!resolve(cur->pn_left, prefix))
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PNK_YIELD:
|
2019-08-18 21:26:54 +00:00
|
|
|
case PNK_AWAIT:
|
2017-04-19 07:56:45 +00:00
|
|
|
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->isAssigned()) ||
|
|
|
|
(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->maybeExpr());
|
|
|
|
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->maybeExpr());
|
|
|
|
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_ARGSBODY:
|
|
|
|
// 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_IMPORT_SPEC_LIST: {
|
|
|
|
case PNK_EXPORT_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->maybeExpr());
|
|
|
|
MOZ_ASSERT(item->pn_right->isKind(PNK_NAME));
|
|
|
|
MOZ_ASSERT(!item->pn_right->maybeExpr());
|
|
|
|
}
|
|
|
|
#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->expr()->isKind(PNK_CATCH));
|
|
|
|
MOZ_ASSERT(catchNode->expr()->isArity(PN_TERNARY));
|
|
|
|
if (!resolve(catchNode->expr(), 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_LEXICALSCOPE:
|
|
|
|
case PNK_NAME:
|
|
|
|
MOZ_ASSERT(cur->isArity(PN_NAME));
|
|
|
|
if (!resolve(cur->maybeExpr(), 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;
|
|
|
|
|
2015-12-18 21:18:19 +00:00
|
|
|
case PNK_ANNEXB_FUNCTION:
|
|
|
|
MOZ_ASSERT(cur->isArity(PN_BINARY));
|
|
|
|
if (!resolve(cur->pn_left, prefix))
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
|
2017-04-19 07:56:45 +00:00
|
|
|
// 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);
|
|
|
|
}
|