blob: 3a87e2db7fd956979db7ec3bd3f6155913e94a82 [file] [log] [blame]
/* -*- 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/ParseNode-inl.h"
#include "frontend/Parser.h"
#include "jscntxtinlines.h"
using namespace js;
using namespace js::frontend;
using mozilla::ArrayLength;
using mozilla::IsFinite;
#ifdef DEBUG
void
ParseNode::checkListConsistency()
{
MOZ_ASSERT(isArity(PN_LIST));
ParseNode** tail;
uint32_t count = 0;
if (pn_head) {
ParseNode* last = pn_head;
ParseNode* pn = last;
while (pn) {
last = pn;
pn = pn->pn_next;
count++;
}
tail = &last->pn_next;
} else {
tail = &pn_head;
}
MOZ_ASSERT(pn_tail == tail);
MOZ_ASSERT(pn_count == count);
}
#endif
/* Add |node| to |parser|'s free node list. */
void
ParseNodeAllocator::freeNode(ParseNode* pn)
{
/* Catch back-to-back dup recycles. */
MOZ_ASSERT(pn != freelist);
/*
* It's too hard to clear these nodes from the AtomDefnMaps, etc. that
* hold references to them, so we never free them. It's our caller's job to
* recognize and process these, since their children do need to be dealt
* with.
*/
MOZ_ASSERT(!pn->isUsed());
MOZ_ASSERT(!pn->isDefn());
#ifdef DEBUG
/* Poison the node, to catch attempts to use it without initializing it. */
memset(pn, 0xab, sizeof(*pn));
#endif
pn->pn_next = freelist;
freelist = pn;
}
namespace {
/*
* A work pool of ParseNodes. The work pool is a stack, chained together
* by nodes' pn_next fields. We use this to avoid creating deep C++ stacks
* when recycling deep parse trees.
*
* Since parse nodes are probably allocated in something close to the order
* they appear in a depth-first traversal of the tree, making the work pool
* a stack should give us pretty good locality.
*/
class NodeStack {
public:
NodeStack() : top(nullptr) { }
bool empty() { return top == nullptr; }
void push(ParseNode* pn) {
pn->pn_next = top;
top = pn;
}
/* Push the children of the PN_LIST node |pn| on the stack. */
void pushList(ParseNode* pn) {
/* This clobbers pn->pn_head if the list is empty; should be okay. */
*pn->pn_tail = top;
top = pn->pn_head;
}
ParseNode* pop() {
MOZ_ASSERT(!empty());
ParseNode* hold = top; /* my kingdom for a prog1 */
top = top->pn_next;
return hold;
}
private:
ParseNode* top;
};
} /* anonymous namespace */
enum class PushResult { Recyclable, CleanUpLater };
static PushResult
PushCodeNodeChildren(ParseNode* node, NodeStack* stack)
{
MOZ_ASSERT(node->isArity(PN_CODE));
/*
* Function nodes are linked into the function box tree, and may appear
* on method lists. Both of those lists are singly-linked, so trying to
* update them now could result in quadratic behavior when recycling
* trees containing many functions; and the lists can be very long. So
* we put off cleaning the lists up until just before function
* analysis, when we call CleanFunctionList.
*
* In fact, we can't recycle the parse node yet, either: it may appear
* on a method list, and reusing the node would corrupt that. Instead,
* we clear its pn_funbox pointer to mark it as deleted;
* CleanFunctionList recycles it as well.
*
* We do recycle the nodes around it, though, so we must clear pointers
* to them to avoid leaving dangling references where someone can find
* them.
*/
node->pn_funbox = nullptr;
if (node->pn_body)
stack->push(node->pn_body);
node->pn_body = nullptr;
return PushResult::CleanUpLater;
}
static PushResult
PushNameNodeChildren(ParseNode* node, NodeStack* stack)
{
MOZ_ASSERT(node->isArity(PN_NAME));
/*
* Because used/defn nodes appear in AtomDefnMaps and elsewhere, we
* don't recycle them. (We'll recover their storage when we free the
* temporary arena.) However, we do recycle the nodes around them, so
* clean up the pointers to avoid dangling references. The top-level
* decls table carries references to them that later iterations through
* the compileScript loop may find, so they need to be neat.
*
* pn_expr and pn_lexdef share storage; the latter isn't an owning
* reference.
*/
if (!node->isUsed()) {
if (node->pn_expr)
stack->push(node->pn_expr);
node->pn_expr = nullptr;
}
if (!node->isUsed() && !node->isDefn())
return PushResult::Recyclable;
return PushResult::CleanUpLater;
}
static PushResult
PushListNodeChildren(ParseNode* node, NodeStack* stack)
{
MOZ_ASSERT(node->isArity(PN_LIST));
node->checkListConsistency();
stack->pushList(node);
return PushResult::Recyclable;
}
static PushResult
PushUnaryNodeChild(ParseNode* node, NodeStack* stack)
{
MOZ_ASSERT(node->isArity(PN_UNARY));
stack->push(node->pn_kid);
return PushResult::Recyclable;
}
/*
* Push the children of |pn| on |stack|. Return true if |pn| itself could be
* safely recycled, or false if it must be cleaned later (pn_used and pn_defn
* nodes, and all function nodes; see comments for CleanFunctionList in
* SemanticAnalysis.cpp). Some callers want to free |pn|; others
* (js::ParseNodeAllocator::prepareNodeForMutation) don't care about |pn|, and
* just need to take care of its children.
*/
static PushResult
PushNodeChildren(ParseNode* pn, NodeStack* stack)
{
switch (pn->getKind()) {
// Trivial nodes that refer to no nodes, are referred to by nothing
// but their parents, are never used, and are never a definition.
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(pn->isArity(PN_NULLARY));
MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately");
MOZ_ASSERT(!pn->isDefn(), "handle non-trivial cases separately");
return PushResult::Recyclable;
// Nodes with a single non-null child.
case PNK_TYPEOFNAME:
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_POS:
case PNK_NEG:
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:
case PNK_SUPERBASE:
return PushUnaryNodeChild(pn, stack);
// Nodes with a single nullable child.
case PNK_THIS:
case PNK_SEMI: {
MOZ_ASSERT(pn->isArity(PN_UNARY));
if (pn->pn_kid)
stack->push(pn->pn_kid);
return PushResult::Recyclable;
}
// Binary nodes with two non-null children.
// All assignment and compound assignment nodes qualify.
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:
// ...and a few others.
case PNK_ELEM:
case PNK_IMPORT_SPEC:
case PNK_EXPORT_SPEC:
case PNK_COLON:
case PNK_SHORTHAND:
case PNK_DOWHILE:
case PNK_WHILE:
case PNK_SWITCH:
case PNK_LETBLOCK:
case PNK_CLASSMETHOD:
case PNK_NEWTARGET:
case PNK_SETTHIS:
case PNK_FOR:
case PNK_COMPREHENSIONFOR: {
MOZ_ASSERT(pn->isArity(PN_BINARY));
stack->push(pn->pn_left);
stack->push(pn->pn_right);
return PushResult::Recyclable;
}
// Default clauses are PNK_CASE but do not have case expressions.
// Named class expressions do not have outer binding nodes.
// So both are binary nodes with a possibly-null pn_left.
case PNK_CASE:
case PNK_CLASSNAMES: {
MOZ_ASSERT(pn->isArity(PN_BINARY));
if (pn->pn_left)
stack->push(pn->pn_left);
stack->push(pn->pn_right);
return PushResult::Recyclable;
}
// PNK_WITH is PN_BINARY_OBJ -- that is, PN_BINARY with (irrelevant for
// this method's purposes) the addition of the StaticWithObject as
// pn_binary_obj. Both left (expression) and right (statement) are
// non-null.
case PNK_WITH: {
MOZ_ASSERT(pn->isArity(PN_BINARY_OBJ));
stack->push(pn->pn_left);
stack->push(pn->pn_right);
return PushResult::Recyclable;
}
// The left half is the expression being yielded. The right half is
// internal goop: a name reference to the invisible '.generator' local
// variable, or an assignment of a PNK_GENERATOR node to the '.generator'
// local, for a synthesized, prepended initial yield. Yum!
case PNK_YIELD_STAR:
case PNK_YIELD: {
MOZ_ASSERT(pn->isArity(PN_BINARY));
MOZ_ASSERT(pn->pn_right);
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)
stack->push(pn->pn_left);
stack->push(pn->pn_right);
return PushResult::Recyclable;
}
// A return node's child is what you'd expect: the return expression,
// if any.
case PNK_RETURN: {
MOZ_ASSERT(pn->isArity(PN_UNARY));
if (pn->pn_kid)
stack->push(pn->pn_kid);
return PushResult::Recyclable;
}
// Import and export-from nodes have a list of specifiers on the left
// and a module string on the right.
case PNK_IMPORT:
case PNK_EXPORT_FROM: {
MOZ_ASSERT(pn->isArity(PN_BINARY));
MOZ_ASSERT_IF(pn->isKind(PNK_IMPORT), pn->pn_left->isKind(PNK_IMPORT_SPEC_LIST));
MOZ_ASSERT_IF(pn->isKind(PNK_EXPORT_FROM), pn->pn_left->isKind(PNK_EXPORT_SPEC_LIST));
MOZ_ASSERT(pn->pn_left->isArity(PN_LIST));
MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
stack->pushList(pn->pn_left);
stack->push(pn->pn_right);
return PushResult::Recyclable;
}
case PNK_EXPORT_DEFAULT: {
MOZ_ASSERT(pn->isArity(PN_BINARY));
MOZ_ASSERT_IF(pn->pn_right, pn->pn_right->isKind(PNK_NAME));
stack->push(pn->pn_left);
if (pn->pn_right)
stack->push(pn->pn_right);
return PushResult::Recyclable;
}
// Ternary nodes with all children non-null.
case PNK_CONDITIONAL: {
MOZ_ASSERT(pn->isArity(PN_TERNARY));
stack->push(pn->pn_kid1);
stack->push(pn->pn_kid2);
stack->push(pn->pn_kid3);
return PushResult::Recyclable;
}
// For for-in and for-of, the first child is any declaration present in
// the for-loop (and null if not). The second child is the expression or
// pattern assigned every loop, and the third child is the expression
// looped over. For example, in |for (var p in obj)|, the first child is
// |var p|, the second child is |p| (a node distinct from the one in
// |var p|), and the third child is |obj|.
case PNK_FORIN:
case PNK_FOROF: {
MOZ_ASSERT(pn->isArity(PN_TERNARY));
if (pn->pn_kid1)
stack->push(pn->pn_kid1);
stack->push(pn->pn_kid2);
stack->push(pn->pn_kid3);
return PushResult::Recyclable;
}
// for (;;) nodes have one child per optional component of the loop head.
case PNK_FORHEAD: {
MOZ_ASSERT(pn->isArity(PN_TERNARY));
if (pn->pn_kid1)
stack->push(pn->pn_kid1);
if (pn->pn_kid2)
stack->push(pn->pn_kid2);
if (pn->pn_kid3)
stack->push(pn->pn_kid3);
return PushResult::Recyclable;
}
// classes might have an optional node for the heritage, as well as the names
case PNK_CLASS: {
MOZ_ASSERT(pn->isArity(PN_TERNARY));
if (pn->pn_kid1)
stack->push(pn->pn_kid1);
if (pn->pn_kid2)
stack->push(pn->pn_kid2);
stack->push(pn->pn_kid3);
return PushResult::Recyclable;
}
// if-statement nodes have condition and consequent children and a
// possibly-null alternative.
case PNK_IF: {
MOZ_ASSERT(pn->isArity(PN_TERNARY));
stack->push(pn->pn_kid1);
stack->push(pn->pn_kid2);
if (pn->pn_kid3)
stack->push(pn->pn_kid3);
return PushResult::Recyclable;
}
// try-statements have statements to execute, and one or both of a
// catch-list and a finally-block.
case PNK_TRY: {
MOZ_ASSERT(pn->isArity(PN_TERNARY));
MOZ_ASSERT(pn->pn_kid2 || pn->pn_kid3);
stack->push(pn->pn_kid1);
if (pn->pn_kid2)
stack->push(pn->pn_kid2);
if (pn->pn_kid3)
stack->push(pn->pn_kid3);
return PushResult::Recyclable;
}
// A catch node has first kid as catch-variable pattern, the second kid
// as catch condition (which, if non-null, records the |<cond>| in
// SpiderMonkey's |catch (e if <cond>)| extension), and third kid as the
// statements in the catch block.
case PNK_CATCH: {
MOZ_ASSERT(pn->isArity(PN_TERNARY));
stack->push(pn->pn_kid1);
if (pn->pn_kid2)
stack->push(pn->pn_kid2);
stack->push(pn->pn_kid3);
return PushResult::Recyclable;
}
// List nodes with all non-null 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_OBJECT:
case PNK_TEMPLATE_STRING_LIST:
case PNK_TAGGED_TEMPLATE:
case PNK_CALLSITEOBJ:
case PNK_VAR:
case PNK_CONST:
case PNK_LET:
case PNK_CATCHLIST:
case PNK_STATEMENTLIST:
case PNK_IMPORT_SPEC_LIST:
case PNK_EXPORT_SPEC_LIST:
case PNK_ARGSBODY:
case PNK_CLASSMETHODLIST:
return PushListNodeChildren(pn, stack);
// 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: {
#ifdef DEBUG
MOZ_ASSERT(pn->isKind(PNK_ARRAYCOMP));
MOZ_ASSERT(pn->isArity(PN_LIST));
MOZ_ASSERT(pn->pn_count == 1);
MOZ_ASSERT(pn->pn_head->isKind(PNK_LEXICALSCOPE) ||
pn->pn_head->isKind(PNK_COMPREHENSIONFOR));
#endif
return PushListNodeChildren(pn, stack);
}
case PNK_LABEL:
case PNK_DOT:
case PNK_LEXICALSCOPE:
case PNK_NAME:
return PushNameNodeChildren(pn, stack);
case PNK_FUNCTION:
case PNK_MODULE:
return PushCodeNodeChildren(pn, stack);
case PNK_LIMIT: // invalid sentinel value
MOZ_CRASH("invalid node kind");
}
MOZ_CRASH("bad ParseNodeKind");
return PushResult::CleanUpLater;
}
/*
* Prepare |pn| to be mutated in place into a new kind of node. Recycle all
* |pn|'s recyclable children (but not |pn| itself!), and disconnect it from
* metadata structures (the function box tree).
*/
void
ParseNodeAllocator::prepareNodeForMutation(ParseNode* pn)
{
// Nothing to do for nullary nodes.
if (pn->isArity(PN_NULLARY))
return;
// Put |pn|'s children (but not |pn| itself) on a work stack.
NodeStack stack;
PushNodeChildren(pn, &stack);
// For each node on the work stack, push its children on the work stack,
// and free the node if we can.
while (!stack.empty()) {
pn = stack.pop();
if (PushNodeChildren(pn, &stack) == PushResult::Recyclable)
freeNode(pn);
}
}
/*
* Return the nodes in the subtree |pn| to the parser's free node list, for
* reallocation.
*/
ParseNode*
ParseNodeAllocator::freeTree(ParseNode* pn)
{
if (!pn)
return nullptr;
ParseNode* savedNext = pn->pn_next;
NodeStack stack;
for (;;) {
if (PushNodeChildren(pn, &stack) == PushResult::Recyclable)
freeNode(pn);
if (stack.empty())
break;
pn = stack.pop();
}
return savedNext;
}
/*
* Allocate a ParseNode from parser's node freelist or, failing that, from
* cx's temporary arena.
*/
void*
ParseNodeAllocator::allocNode()
{
if (ParseNode* pn = freelist) {
freelist = pn->pn_next;
return pn;
}
void* p = alloc.alloc(sizeof (ParseNode));
if (!p)
ReportOutOfMemory(cx);
return p;
}
ParseNode*
ParseNode::appendOrCreateList(ParseNodeKind kind, JSOp op, ParseNode* left, ParseNode* right,
FullParseHandler* handler, ParseContext<FullParseHandler>* pc)
{
// The asm.js specification is written in ECMAScript grammar terms that
// specify *only* a binary tree. It's a royal pain to implement the asm.js
// spec to act upon n-ary lists as created below. So for asm.js, form a
// binary tree of lists exactly as ECMAScript would by skipping the
// following optimization.
if (!pc->useAsmOrInsideUseAsm()) {
// Left-associative trees of a given operator (e.g. |a + b + c|) are
// binary trees in the spec: (+ (+ a b) c) in Lisp terms. Recursively
// processing such a tree, exactly implemented that way, would blow the
// the stack. We use a list node that uses O(1) stack to represent
// such operations: (+ a b c).
//
// (**) is right-associative; per spec |a ** b ** c| parses as
// (** a (** b c)). But we treat this the same way, creating a list
// node: (** a b c). All consumers must understand that this must be
// processed with a right fold, whereas the list (+ a b c) must be
// processed with a left fold because (+) is left-associative.
//
if (left->isKind(kind) &&
left->isOp(op) &&
(CodeSpec[op].format & JOF_LEFTASSOC ||
(kind == PNK_POW && !left->pn_parens)))
{
ListNode* list = &left->as<ListNode>();
list->append(right);
list->pn_pos.end = right->pn_pos.end;
return list;
}
}
ParseNode* list = handler->new_<ListNode>(kind, op, left);
if (!list)
return nullptr;
list->append(right);
return list;
}
const char*
Definition::kindString(Kind kind)
{
static const char* const table[] = {
"",
js_var_str,
js_const_str,
js_let_str,
"argument",
js_function_str,
"unknown",
js_import_str
};
MOZ_ASSERT(size_t(kind) < ArrayLength(table));
return table[kind];
}
namespace js {
namespace frontend {
/*
* This function assumes the cloned tree is for use in the same statement and
* binding context as the original tree.
*/
template <>
ParseNode*
Parser<FullParseHandler>::cloneParseTree(ParseNode* opn)
{
JS_CHECK_RECURSION(context, return nullptr);
if (opn->isKind(PNK_COMPUTED_NAME)) {
report(ParseError, false, opn, JSMSG_COMPUTED_NAME_IN_PATTERN);
return null();
}
ParseNode* pn = handler.new_<ParseNode>(opn->getKind(), opn->getOp(), opn->getArity(),
opn->pn_pos);
if (!pn)
return nullptr;
pn->setInParens(opn->isInParens());
pn->setDefn(opn->isDefn());
pn->setUsed(opn->isUsed());
switch (pn->getArity()) {
#define NULLCHECK(e) JS_BEGIN_MACRO if (!(e)) return nullptr; JS_END_MACRO
case PN_CODE: {
RootedFunction fun(context, opn->pn_funbox->function());
NULLCHECK(pn->pn_funbox = newFunctionBox(pn, fun, pc,
Directives(/* strict = */ opn->pn_funbox->strict()),
opn->pn_funbox->generatorKind()));
NULLCHECK(pn->pn_body = cloneParseTree(opn->pn_body));
pn->pn_scopecoord = opn->pn_scopecoord;
pn->pn_dflags = opn->pn_dflags;
pn->pn_blockid = opn->pn_blockid;
break;
}
case PN_LIST:
pn->makeEmpty();
for (ParseNode* opn2 = opn->pn_head; opn2; opn2 = opn2->pn_next) {
ParseNode* pn2;
NULLCHECK(pn2 = cloneParseTree(opn2));
pn->append(pn2);
}
pn->pn_xflags = opn->pn_xflags;
break;
case PN_TERNARY:
if (opn->pn_kid1)
NULLCHECK(pn->pn_kid1 = cloneParseTree(opn->pn_kid1));
if (opn->pn_kid2)
NULLCHECK(pn->pn_kid2 = cloneParseTree(opn->pn_kid2));
if (opn->pn_kid3)
NULLCHECK(pn->pn_kid3 = cloneParseTree(opn->pn_kid3));
break;
case PN_BINARY:
case PN_BINARY_OBJ:
if (opn->pn_left)
NULLCHECK(pn->pn_left = cloneParseTree(opn->pn_left));
if (opn->pn_right) {
if (opn->pn_right != opn->pn_left)
NULLCHECK(pn->pn_right = cloneParseTree(opn->pn_right));
else
pn->pn_right = pn->pn_left;
}
if (opn->isArity(PN_BINARY)) {
pn->pn_iflags = opn->pn_iflags;
} else {
MOZ_ASSERT(opn->isArity(PN_BINARY_OBJ));
pn->pn_binary_obj = opn->pn_binary_obj;
}
break;
case PN_UNARY:
if (opn->pn_kid)
NULLCHECK(pn->pn_kid = cloneParseTree(opn->pn_kid));
break;
case PN_NAME:
// PN_NAME could mean several arms in pn_u, so copy the whole thing.
pn->pn_u = opn->pn_u;
if (opn->isUsed()) {
/*
* The old name is a use of its pn_lexdef. Make the clone also be a
* use of that definition.
*/
Definition* dn = pn->pn_lexdef;
pn->pn_link = dn->dn_uses;
pn->pn_dflags = opn->pn_dflags;
dn->dn_uses = pn;
} else if (opn->pn_expr) {
NULLCHECK(pn->pn_expr = cloneParseTree(opn->pn_expr));
/*
* If the old name is a definition, the new one has pn_defn set.
* Make the old name a use of the new node.
*/
if (opn->isDefn()) {
opn->setDefn(false);
handler.linkUseToDef(opn, (Definition*) pn);
}
}
break;
case PN_NULLARY:
pn->pn_u = opn->pn_u;
break;
#undef NULLCHECK
}
return pn;
}
template <>
ParseNode*
Parser<FullParseHandler>::cloneLeftHandSide(ParseNode* opn);
/*
* Used by Parser::cloneLeftHandSide to clone a default expression
* in the form of
* [a = default] or {a: b = default}
*/
template <>
ParseNode*
Parser<FullParseHandler>::cloneDestructuringDefault(ParseNode* opn)
{
MOZ_ASSERT(opn->isKind(PNK_ASSIGN));
report(ParseError, false, opn, JSMSG_DEFAULT_IN_PATTERN);
return null();
}
/*
* Used by Parser::forStatement and comprehensionTail to clone the TARGET in
* for (var/const/let TARGET in EXPR)
*
* opn must be the pn_head of a node produced by Parser::variables, so its form
* is known to be LHS = NAME | [LHS] | {id:LHS}.
*
* The cloned tree is for use only in the same statement and binding context as
* the original tree.
*/
template <>
ParseNode*
Parser<FullParseHandler>::cloneLeftHandSide(ParseNode* opn)
{
ParseNode* pn = handler.new_<ParseNode>(opn->getKind(), opn->getOp(), opn->getArity(),
opn->pn_pos);
if (!pn)
return nullptr;
pn->setInParens(opn->isInParens());
pn->setDefn(opn->isDefn());
pn->setUsed(opn->isUsed());
if (opn->isArity(PN_LIST)) {
MOZ_ASSERT(opn->isKind(PNK_ARRAY) || opn->isKind(PNK_OBJECT));
pn->makeEmpty();
for (ParseNode* opn2 = opn->pn_head; opn2; opn2 = opn2->pn_next) {
ParseNode* pn2;
if (opn->isKind(PNK_OBJECT)) {
if (opn2->isKind(PNK_MUTATEPROTO)) {
ParseNode* target = opn2->pn_kid->isKind(PNK_ASSIGN)
? cloneDestructuringDefault(opn2->pn_kid)
: cloneLeftHandSide(opn2->pn_kid);
if (!target)
return nullptr;
pn2 = handler.new_<UnaryNode>(PNK_MUTATEPROTO, JSOP_NOP, opn2->pn_pos, target);
} else {
MOZ_ASSERT(opn2->isArity(PN_BINARY));
MOZ_ASSERT(opn2->isKind(PNK_COLON) || opn2->isKind(PNK_SHORTHAND));
ParseNode* tag = cloneParseTree(opn2->pn_left);
if (!tag)
return nullptr;
ParseNode* target = opn2->pn_right->isKind(PNK_ASSIGN)
? cloneDestructuringDefault(opn2->pn_right)
: cloneLeftHandSide(opn2->pn_right);
if (!target)
return nullptr;
pn2 = handler.new_<BinaryNode>(opn2->getKind(), JSOP_INITPROP, opn2->pn_pos, tag, target);
}
} else if (opn2->isArity(PN_NULLARY)) {
MOZ_ASSERT(opn2->isKind(PNK_ELISION));
pn2 = cloneParseTree(opn2);
} else if (opn2->isKind(PNK_SPREAD)) {
ParseNode* target = cloneLeftHandSide(opn2->pn_kid);
if (!target)
return nullptr;
pn2 = handler.new_<UnaryNode>(PNK_SPREAD, JSOP_NOP, opn2->pn_pos, target);
} else if (opn2->isKind(PNK_ASSIGN)) {
pn2 = cloneDestructuringDefault(opn2);
} else {
pn2 = cloneLeftHandSide(opn2);
}
if (!pn2)
return nullptr;
pn->append(pn2);
}
pn->pn_xflags = opn->pn_xflags;
return pn;
}
MOZ_ASSERT(opn->isArity(PN_NAME));
MOZ_ASSERT(opn->isKind(PNK_NAME));
/* If opn is a definition or use, make pn a use. */
pn->pn_u.name = opn->pn_u.name;
pn->setOp(JSOP_SETNAME);
if (opn->isUsed()) {
Definition* dn = pn->pn_lexdef;
pn->pn_link = dn->dn_uses;
dn->dn_uses = pn;
} else {
pn->pn_expr = nullptr;
if (opn->isDefn()) {
/* We copied some definition-specific state into pn. Clear it out. */
pn->pn_scopecoord.makeFree();
pn->pn_dflags &= ~(PND_LEXICAL | PND_BOUND);
pn->setDefn(false);
handler.linkUseToDef(pn, (Definition*) opn);
}
}
return pn;
}
} /* namespace frontend */
} /* namespace js */
#ifdef DEBUG
static const char * const parseNodeNames[] = {
#define STRINGIFY(name) #name,
FOR_EACH_PARSE_NODE_KIND(STRINGIFY)
#undef STRINGIFY
};
void
frontend::DumpParseTree(ParseNode* pn, int indent)
{
if (pn == nullptr)
fprintf(stderr, "#NULL");
else
pn->dump(indent);
}
static void
IndentNewLine(int indent)
{
fputc('\n', stderr);
for (int i = 0; i < indent; ++i)
fputc(' ', stderr);
}
void
ParseNode::dump()
{
dump(0);
fputc('\n', stderr);
}
void
ParseNode::dump(int indent)
{
switch (pn_arity) {
case PN_NULLARY:
((NullaryNode*) this)->dump();
break;
case PN_UNARY:
((UnaryNode*) this)->dump(indent);
break;
case PN_BINARY:
((BinaryNode*) this)->dump(indent);
break;
case PN_BINARY_OBJ:
((BinaryObjNode*) this)->dump(indent);
break;
case PN_TERNARY:
((TernaryNode*) this)->dump(indent);
break;
case PN_CODE:
((CodeNode*) this)->dump(indent);
break;
case PN_LIST:
((ListNode*) this)->dump(indent);
break;
case PN_NAME:
((NameNode*) this)->dump(indent);
break;
default:
fprintf(stderr, "#<BAD NODE %p, kind=%u, arity=%u>",
(void*) this, unsigned(getKind()), unsigned(pn_arity));
break;
}
}
void
NullaryNode::dump()
{
switch (getKind()) {
case PNK_TRUE: fprintf(stderr, "#true"); break;
case PNK_FALSE: fprintf(stderr, "#false"); break;
case PNK_NULL: fprintf(stderr, "#null"); break;
case PNK_NUMBER: {
ToCStringBuf cbuf;
const char* cstr = NumberToCString(nullptr, &cbuf, pn_dval);
if (!IsFinite(pn_dval))
fputc('#', stderr);
if (cstr)
fprintf(stderr, "%s", cstr);
else
fprintf(stderr, "%g", pn_dval);
break;
}
case PNK_STRING:
pn_atom->dumpCharsNoNewline();
break;
default:
fprintf(stderr, "(%s)", parseNodeNames[getKind()]);
}
}
void
UnaryNode::dump(int indent)
{
const char* name = parseNodeNames[getKind()];
fprintf(stderr, "(%s ", name);
indent += strlen(name) + 2;
DumpParseTree(pn_kid, indent);
fprintf(stderr, ")");
}
void
BinaryNode::dump(int indent)
{
const char* name = parseNodeNames[getKind()];
fprintf(stderr, "(%s ", name);
indent += strlen(name) + 2;
DumpParseTree(pn_left, indent);
IndentNewLine(indent);
DumpParseTree(pn_right, indent);
fprintf(stderr, ")");
}
void
BinaryObjNode::dump(int indent)
{
const char* name = parseNodeNames[getKind()];
fprintf(stderr, "(%s ", name);
indent += strlen(name) + 2;
DumpParseTree(pn_left, indent);
IndentNewLine(indent);
DumpParseTree(pn_right, indent);
fprintf(stderr, ")");
}
void
TernaryNode::dump(int indent)
{
const char* name = parseNodeNames[getKind()];
fprintf(stderr, "(%s ", name);
indent += strlen(name) + 2;
DumpParseTree(pn_kid1, indent);
IndentNewLine(indent);
DumpParseTree(pn_kid2, indent);
IndentNewLine(indent);
DumpParseTree(pn_kid3, indent);
fprintf(stderr, ")");
}
void
CodeNode::dump(int indent)
{
const char* name = parseNodeNames[getKind()];
fprintf(stderr, "(%s ", name);
indent += strlen(name) + 2;
DumpParseTree(pn_body, indent);
fprintf(stderr, ")");
}
void
ListNode::dump(int indent)
{
const char* name = parseNodeNames[getKind()];
fprintf(stderr, "(%s [", name);
if (pn_head != nullptr) {
indent += strlen(name) + 3;
DumpParseTree(pn_head, indent);
ParseNode* pn = pn_head->pn_next;
while (pn != nullptr) {
IndentNewLine(indent);
DumpParseTree(pn, indent);
pn = pn->pn_next;
}
}
fprintf(stderr, "])");
}
template <typename CharT>
static void
DumpName(const CharT* s, size_t len)
{
if (len == 0)
fprintf(stderr, "#<zero-length name>");
for (size_t i = 0; i < len; i++) {
char16_t c = s[i];
if (c > 32 && c < 127)
fputc(c, stderr);
else if (c <= 255)
fprintf(stderr, "\\x%02x", unsigned(c));
else
fprintf(stderr, "\\u%04x", unsigned(c));
}
}
void
NameNode::dump(int indent)
{
if (isKind(PNK_NAME) || isKind(PNK_DOT)) {
if (isKind(PNK_DOT))
fprintf(stderr, "(.");
if (!pn_atom) {
fprintf(stderr, "#<null name>");
} else if (getOp() == JSOP_GETARG && pn_atom->length() == 0) {
// Dump destructuring parameter.
fprintf(stderr, "(#<zero-length name> ");
DumpParseTree(expr(), indent + 21);
fputc(')', stderr);
} else {
JS::AutoCheckCannotGC nogc;
if (pn_atom->hasLatin1Chars())
DumpName(pn_atom->latin1Chars(nogc), pn_atom->length());
else
DumpName(pn_atom->twoByteChars(nogc), pn_atom->length());
}
if (isKind(PNK_DOT)) {
fputc(' ', stderr);
if (as<PropertyAccess>().isSuper())
fprintf(stderr, "super");
else
DumpParseTree(expr(), indent + 2);
fputc(')', stderr);
}
return;
}
MOZ_ASSERT(!isUsed());
const char* name = parseNodeNames[getKind()];
if (isUsed())
fprintf(stderr, "(%s)", name);
else {
fprintf(stderr, "(%s ", name);
indent += strlen(name) + 2;
DumpParseTree(expr(), indent);
fprintf(stderr, ")");
}
}
#endif
ObjectBox::ObjectBox(JSObject* object, ObjectBox* traceLink)
: object(object),
traceLink(traceLink),
emitLink(nullptr)
{
MOZ_ASSERT(!object->is<JSFunction>());
MOZ_ASSERT(object->isTenured());
}
ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink)
: object(function),
traceLink(traceLink),
emitLink(nullptr)
{
MOZ_ASSERT(object->is<JSFunction>());
MOZ_ASSERT(asFunctionBox()->function() == function);
MOZ_ASSERT(object->isTenured());
}
FunctionBox*
ObjectBox::asFunctionBox()
{
MOZ_ASSERT(isFunctionBox());
return static_cast<FunctionBox*>(this);
}
ModuleBox*
ObjectBox::asModuleBox()
{
MOZ_ASSERT(isModuleBox());
return static_cast<ModuleBox*>(this);
}
/* static */ void
ObjectBox::TraceList(JSTracer* trc, ObjectBox* listHead)
{
for (ObjectBox* box = listHead; box; box = box->traceLink)
box->trace(trc);
}
void
ObjectBox::trace(JSTracer* trc)
{
TraceRoot(trc, &object, "parser.object");
}
void
FunctionBox::trace(JSTracer* trc)
{
ObjectBox::trace(trc);
bindings.trace(trc);
if (enclosingStaticScope_)
TraceRoot(trc, &enclosingStaticScope_, "funbox-enclosingStaticScope");
}
void
ModuleBox::trace(JSTracer* trc)
{
ObjectBox::trace(trc);
bindings.trace(trc);
exportNames.trace(trc);
}