diff options
Diffstat (limited to 'js/src/jsparse.cpp')
-rw-r--r-- | js/src/jsparse.cpp | 9864 |
1 files changed, 9864 insertions, 0 deletions
diff --git a/js/src/jsparse.cpp b/js/src/jsparse.cpp new file mode 100644 index 0000000..c735970 --- /dev/null +++ b/js/src/jsparse.cpp @@ -0,0 +1,9864 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * JS parser. + * + * This is a recursive-descent parser for the JavaScript language specified by + * "The JavaScript 1.5 Language Specification". It uses lexical and semantic + * feedback to disambiguate non-LL(1) structures. It generates trees of nodes + * induced by the recursive parsing (not precise syntax trees, see jsparse.h). + * After tree construction, it rewrites trees to fold constants and evaluate + * compile-time expressions. Finally, it calls js_EmitTree (see jsemit.h) to + * generate bytecode. + * + * This parser attempts no error recovery. + */ +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "jstypes.h" +#include "jsstdint.h" +#include "jsarena.h" +#include "jsutil.h" +#include "jsapi.h" +#include "jsarray.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsversion.h" +#include "jsemit.h" +#include "jsfun.h" +#include "jsinterp.h" +#include "jsiter.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsparse.h" +#include "jsscan.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" +#include "jsstaticcheck.h" +#include "jslibmath.h" +#include "jsvector.h" + +#if JS_HAS_XML_SUPPORT +#include "jsxml.h" +#endif + +#if JS_HAS_DESTRUCTURING +#include "jsdhash.h" +#endif + +#include "jsatominlines.h" +#include "jsinterpinlines.h" +#include "jsobjinlines.h" +#include "jsregexpinlines.h" +#include "jsscriptinlines.h" + +// Grr, windows.h or something under it #defines CONST... +#ifdef CONST +#undef CONST +#endif + +using namespace js; +using namespace js::gc; + +/* + * Asserts to verify assumptions behind pn_ macros. + */ +#define pn_offsetof(m) offsetof(JSParseNode, m) + +JS_STATIC_ASSERT(pn_offsetof(pn_link) == pn_offsetof(dn_uses)); +JS_STATIC_ASSERT(pn_offsetof(pn_u.name.atom) == pn_offsetof(pn_u.apair.atom)); + +#undef pn_offsetof + +/* + * Insist that the next token be of type tt, or report errno and return null. + * NB: this macro uses cx and ts from its lexical environment. + */ +#define MUST_MATCH_TOKEN_WITH_FLAGS(tt, errno, __flags) \ + JS_BEGIN_MACRO \ + if (tokenStream.getToken((__flags)) != tt) { \ + reportErrorNumber(NULL, JSREPORT_ERROR, errno); \ + return NULL; \ + } \ + JS_END_MACRO +#define MUST_MATCH_TOKEN(tt, errno) MUST_MATCH_TOKEN_WITH_FLAGS(tt, errno, 0) + +#ifdef METER_PARSENODES +static uint32 parsenodes = 0; +static uint32 maxparsenodes = 0; +static uint32 recyclednodes = 0; +#endif + +void +JSParseNode::become(JSParseNode *pn2) +{ + JS_ASSERT(!pn_defn); + JS_ASSERT(!pn2->pn_defn); + + JS_ASSERT(!pn_used); + if (pn2->pn_used) { + JSParseNode **pnup = &pn2->pn_lexdef->dn_uses; + while (*pnup != pn2) + pnup = &(*pnup)->pn_link; + *pnup = this; + pn_link = pn2->pn_link; + pn_used = true; + pn2->pn_link = NULL; + pn2->pn_used = false; + } + + pn_type = pn2->pn_type; + pn_op = pn2->pn_op; + pn_arity = pn2->pn_arity; + pn_parens = pn2->pn_parens; + pn_u = pn2->pn_u; + + /* + * If any pointers are pointing to pn2, change them to point to this + * instead, since pn2 will be cleared and probably recycled. + */ + if (PN_TYPE(this) == TOK_FUNCTION && pn_arity == PN_FUNC) { + /* Function node: fix up the pn_funbox->node back-pointer. */ + JS_ASSERT(pn_funbox->node == pn2); + pn_funbox->node = this; + } else if (pn_arity == PN_LIST && !pn_head) { + /* Empty list: fix up the pn_tail pointer. */ + JS_ASSERT(pn_count == 0); + JS_ASSERT(pn_tail == &pn2->pn_head); + pn_tail = &pn_head; + } + + pn2->clear(); +} + +void +JSParseNode::clear() +{ + pn_type = TOK_EOF; + pn_op = JSOP_NOP; + pn_used = pn_defn = false; + pn_arity = PN_NULLARY; + pn_parens = false; +} + +Parser::Parser(JSContext *cx, JSPrincipals *prin, JSStackFrame *cfp) + : js::AutoGCRooter(cx, PARSER), + context(cx), + aleFreeList(NULL), + tokenStream(cx), + principals(NULL), + callerFrame(cfp), + callerVarObj(cfp ? &cfp->varobj(cx->containingSegment(cfp)) : NULL), + nodeList(NULL), + functionCount(0), + traceListHead(NULL), + tc(NULL), + keepAtoms(cx->runtime) +{ + js::PodArrayZero(tempFreeList); + setPrincipals(prin); + JS_ASSERT_IF(cfp, cfp->isScriptFrame()); +} + +bool +Parser::init(const jschar *base, size_t length, const char *filename, uintN lineno, + JSVersion version) +{ + JSContext *cx = context; + tempPoolMark = JS_ARENA_MARK(&cx->tempPool); + if (!tokenStream.init(base, length, filename, lineno, version)) { + JS_ARENA_RELEASE(&cx->tempPool, tempPoolMark); + return false; + } + return true; +} + +Parser::~Parser() +{ + JSContext *cx = context; + + if (principals) + JSPRINCIPALS_DROP(cx, principals); + tokenStream.close(); + JS_ARENA_RELEASE(&cx->tempPool, tempPoolMark); +} + +void +Parser::setPrincipals(JSPrincipals *prin) +{ + JS_ASSERT(!principals); + if (prin) + JSPRINCIPALS_HOLD(context, prin); + principals = prin; +} + +JSObjectBox * +Parser::newObjectBox(JSObject *obj) +{ + JS_ASSERT(obj); + + /* + * We use JSContext.tempPool to allocate parsed objects and place them on + * a list in this Parser to ensure GC safety. Thus the tempPool arenas + * containing the entries must be alive until we are done with scanning, + * parsing and code generation for the whole script or top-level function. + */ + JSObjectBox *objbox; + JS_ARENA_ALLOCATE_TYPE(objbox, JSObjectBox, &context->tempPool); + if (!objbox) { + js_ReportOutOfScriptQuota(context); + return NULL; + } + objbox->traceLink = traceListHead; + traceListHead = objbox; + objbox->emitLink = NULL; + objbox->object = obj; + objbox->isFunctionBox = false; + return objbox; +} + +JSFunctionBox * +Parser::newFunctionBox(JSObject *obj, JSParseNode *fn, JSTreeContext *tc) +{ + JS_ASSERT(obj); + JS_ASSERT(obj->isFunction()); + + /* + * We use JSContext.tempPool to allocate parsed objects and place them on + * a list in this Parser to ensure GC safety. Thus the tempPool arenas + * containing the entries must be alive until we are done with scanning, + * parsing and code generation for the whole script or top-level function. + */ + JSFunctionBox *funbox; + JS_ARENA_ALLOCATE_TYPE(funbox, JSFunctionBox, &context->tempPool); + if (!funbox) { + js_ReportOutOfScriptQuota(context); + return NULL; + } + funbox->traceLink = traceListHead; + traceListHead = funbox; + funbox->emitLink = NULL; + funbox->object = obj; + funbox->isFunctionBox = true; + funbox->node = fn; + funbox->siblings = tc->functionList; + tc->functionList = funbox; + ++tc->parser->functionCount; + funbox->kids = NULL; + funbox->parent = tc->funbox; + funbox->methods = NULL; + new (&funbox->bindings) Bindings(context); + funbox->queued = false; + funbox->inLoop = false; + for (JSStmtInfo *stmt = tc->topStmt; stmt; stmt = stmt->down) { + if (STMT_IS_LOOP(stmt)) { + funbox->inLoop = true; + break; + } + } + funbox->level = tc->staticLevel; + funbox->tcflags = (TCF_IN_FUNCTION | (tc->flags & (TCF_COMPILE_N_GO | TCF_STRICT_MODE_CODE))); + if (tc->innermostWith) + funbox->tcflags |= TCF_IN_WITH; + return funbox; +} + +bool +JSFunctionBox::joinable() const +{ + return FUN_NULL_CLOSURE((JSFunction *) object) && + !(tcflags & (TCF_FUN_USES_ARGUMENTS | TCF_FUN_USES_OWN_NAME)); +} + +bool +JSFunctionBox::inAnyDynamicScope() const +{ + for (const JSFunctionBox *funbox = this; funbox; funbox = funbox->parent) { + if (funbox->tcflags & (TCF_IN_WITH | TCF_FUN_CALLS_EVAL)) + return true; + } + return false; +} + +bool +JSFunctionBox::shouldUnbrand(uintN methods, uintN slowMethods) const +{ + if (slowMethods != 0) { + for (const JSFunctionBox *funbox = this; funbox; funbox = funbox->parent) { + if (!(funbox->tcflags & TCF_FUN_MODULE_PATTERN)) + return true; + if (funbox->inLoop) + return true; + } + } + return false; +} + +void +Parser::trace(JSTracer *trc) +{ + JSObjectBox *objbox = traceListHead; + while (objbox) { + MarkObject(trc, *objbox->object, "parser.object"); + if (objbox->isFunctionBox) + static_cast<JSFunctionBox *>(objbox)->bindings.trace(trc); + objbox = objbox->traceLink; + } + + for (JSTreeContext *tc = this->tc; tc; tc = tc->parent) + tc->trace(trc); +} + +/* Add |node| to |parser|'s free node list. */ +static inline void +AddNodeToFreeList(JSParseNode *pn, js::Parser *parser) +{ + /* Catch back-to-back dup recycles. */ + JS_ASSERT(pn != parser->nodeList); + + /* + * It's too hard to clear these nodes from the JSAtomLists, 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. + */ + JS_ASSERT(!pn->pn_used); + JS_ASSERT(!pn->pn_defn); + +#ifdef DEBUG + /* Poison the node, to catch attempts to use it without initializing it. */ + memset(pn, 0xab, sizeof(*pn)); +#endif + + pn->pn_next = parser->nodeList; + parser->nodeList = pn; + +#ifdef METER_PARSENODES + recyclednodes++; +#endif +} + +/* Add |node| to |tc|'s parser's free node list. */ +static inline void +AddNodeToFreeList(JSParseNode *pn, JSTreeContext *tc) +{ + AddNodeToFreeList(pn, tc->parser); +} + +/* + * Walk the function box list at |*funboxHead|, removing boxes for deleted + * functions and cleaning up method lists. We do this once, before + * performing function analysis, to avoid traversing possibly long function + * lists repeatedly when recycling nodes. + * + * There are actually three possible states for function boxes and their + * nodes: + * + * - Live: funbox->node points to the node, and funbox->node->pn_funbox + * points back to the funbox. + * + * - Recycled: funbox->node points to the node, but funbox->node->pn_funbox + * is NULL. When a function node is part of a tree that gets recycled, we + * must avoid corrupting any method list the node is on, so we leave the + * function node unrecycled until we call cleanFunctionList. At recycle + * time, we clear such nodes' pn_funbox pointers to indicate that they + * are deleted and should be recycled once we get here. + * + * - Mutated: funbox->node is NULL; the contents of the node itself could + * be anything. When we mutate a function node into some other kind of + * node, we lose all indication that the node was ever part of the + * function box tree; it could later be recycled, reallocated, and turned + * into anything at all. (Fortunately, method list members never get + * mutated, so we don't have to worry about that case.) + * PrepareNodeForMutation clears the node's function box's node pointer, + * disconnecting it entirely from the function box tree, and marking the + * function box to be trimmed out. + */ +void +Parser::cleanFunctionList(JSFunctionBox **funboxHead) +{ + JSFunctionBox **link = funboxHead; + while (JSFunctionBox *box = *link) { + if (!box->node) { + /* + * This funbox's parse node was mutated into something else. Drop the box, + * and stay at the same link. + */ + *link = box->siblings; + } else if (!box->node->pn_funbox) { + /* + * This funbox's parse node is ready to be recycled. Drop the box, recycle + * the node, and stay at the same link. + */ + *link = box->siblings; + AddNodeToFreeList(box->node, this); + } else { + /* The function is still live. */ + + /* First, remove nodes for deleted functions from our methods list. */ + { + JSParseNode **methodLink = &box->methods; + while (JSParseNode *method = *methodLink) { + /* Method nodes are never rewritten in place to be other kinds of nodes. */ + JS_ASSERT(method->pn_arity == PN_FUNC); + if (!method->pn_funbox) { + /* Deleted: drop the node, and stay on this link. */ + *methodLink = method->pn_link; + } else { + /* Live: keep the node, and move to the next link. */ + methodLink = &method->pn_link; + } + } + } + + /* Second, remove boxes for deleted functions from our kids list. */ + cleanFunctionList(&box->kids); + + /* Keep the box on the list, and move to the next link. */ + link = &box->siblings; + } + } +} + +namespace js { + +/* + * A work pool of JSParseNodes. 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(NULL) { } + bool empty() { return top == NULL; } + void push(JSParseNode *pn) { + pn->pn_next = top; + top = pn; + } + void pushUnlessNull(JSParseNode *pn) { if (pn) push(pn); } + /* Push the children of the PN_LIST node |pn| on the stack. */ + void pushList(JSParseNode *pn) { + /* This clobbers pn->pn_head if the list is empty; should be okay. */ + *pn->pn_tail = top; + top = pn->pn_head; + } + JSParseNode *pop() { + JS_ASSERT(!empty()); + JSParseNode *hold = top; /* my kingdom for a prog1 */ + top = top->pn_next; + return hold; + } + private: + JSParseNode *top; +}; + +} /* namespace js */ + +/* + * 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 + * js::Parser::cleanFunctionList). Some callers want to free |pn|; others + * (PrepareNodeForMutation) don't care about |pn|, and just need to take care of + * its children. + */ +static bool +PushNodeChildren(JSParseNode *pn, NodeStack *stack) +{ + switch (pn->pn_arity) { + case PN_FUNC: + /* + * 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 + * js::Parser::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; js::Parser::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. + */ + pn->pn_funbox = NULL; + stack->pushUnlessNull(pn->pn_body); + pn->pn_body = NULL; + return false; + + case PN_NAME: + /* + * Because used/defn nodes appear in JSAtomLists 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 (!pn->pn_used) { + stack->pushUnlessNull(pn->pn_expr); + pn->pn_expr = NULL; + } + return !pn->pn_used && !pn->pn_defn; + + case PN_LIST: + stack->pushList(pn); + break; + case PN_TERNARY: + stack->pushUnlessNull(pn->pn_kid1); + stack->pushUnlessNull(pn->pn_kid2); + stack->pushUnlessNull(pn->pn_kid3); + break; + case PN_BINARY: + if (pn->pn_left != pn->pn_right) + stack->pushUnlessNull(pn->pn_left); + stack->pushUnlessNull(pn->pn_right); + break; + case PN_UNARY: + stack->pushUnlessNull(pn->pn_kid); + break; + case PN_NULLARY: + /* + * E4X function namespace nodes are PN_NULLARY, but can appear on use + * lists. + */ + return !pn->pn_used && !pn->pn_defn; + } + + return true; +} + +/* + * 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). + */ +static void +PrepareNodeForMutation(JSParseNode *pn, JSTreeContext *tc) +{ + if (pn->pn_arity != PN_NULLARY) { + if (pn->pn_arity == PN_FUNC) { + /* + * Since this node could be turned into anything, we can't + * ensure it won't be subsequently recycled, so we must + * disconnect it from the funbox tree entirely. + * + * Note that pn_funbox may legitimately be NULL. functionDef + * applies MakeDefIntoUse to definition nodes, which can come + * from prior iterations of the big loop in compileScript. In + * such cases, the defn nodes have been visited by the recycler + * (but not actually recycled!), and their funbox pointers + * cleared. But it's fine to mutate them into uses of some new + * definition. + */ + if (pn->pn_funbox) + pn->pn_funbox->node = NULL; + } + + /* 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)) + AddNodeToFreeList(pn, tc); + } + } +} + +/* + * Return the nodes in the subtree |pn| to the parser's free node list, for + * reallocation. + * + * Note that all functions in |pn| that are not enclosed by other functions + * in |pn| must be direct children of |tc|, because we only clean up |tc|'s + * function and method lists. You must not reach into a function and + * recycle some part of it (unless you've updated |tc|->functionList, the + * way js_FoldConstants does). + */ +static JSParseNode * +RecycleTree(JSParseNode *pn, JSTreeContext *tc) +{ + if (!pn) + return NULL; + + JSParseNode *savedNext = pn->pn_next; + + NodeStack stack; + for (;;) { + if (PushNodeChildren(pn, &stack)) + AddNodeToFreeList(pn, tc); + if (stack.empty()) + break; + pn = stack.pop(); + } + + return savedNext; +} + +/* + * Allocate a JSParseNode from tc's node freelist or, failing that, from + * cx's temporary arena. + */ +static JSParseNode * +NewOrRecycledNode(JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = tc->parser->nodeList; + if (!pn) { + JSContext *cx = tc->parser->context; + + JS_ARENA_ALLOCATE_TYPE(pn, JSParseNode, &cx->tempPool); + if (!pn) + js_ReportOutOfScriptQuota(cx); + } else { + tc->parser->nodeList = pn->pn_next; + } + + if (pn) { +#ifdef METER_PARSENODES + parsenodes++; + if (parsenodes - recyclednodes > maxparsenodes) + maxparsenodes = parsenodes - recyclednodes; +#endif + pn->pn_used = pn->pn_defn = false; + memset(&pn->pn_u, 0, sizeof pn->pn_u); + pn->pn_next = NULL; + } + return pn; +} + +/* used only by static create methods of subclasses */ + +JSParseNode * +JSParseNode::create(JSParseNodeArity arity, JSTreeContext *tc) +{ + JSParseNode *pn = NewOrRecycledNode(tc); + if (!pn) + return NULL; + const Token &tok = tc->parser->tokenStream.currentToken(); + pn->init(tok.type, JSOP_NOP, arity); + pn->pn_pos = tok.pos; + return pn; +} + +JSParseNode * +JSParseNode::newBinaryOrAppend(TokenKind tt, JSOp op, JSParseNode *left, JSParseNode *right, + JSTreeContext *tc) +{ + JSParseNode *pn, *pn1, *pn2; + + if (!left || !right) + return NULL; + + /* + * Flatten a left-associative (left-heavy) tree of a given operator into + * a list, to reduce js_FoldConstants and js_EmitTree recursion. + */ + if (PN_TYPE(left) == tt && + PN_OP(left) == op && + (js_CodeSpec[op].format & JOF_LEFTASSOC)) { + if (left->pn_arity != PN_LIST) { + pn1 = left->pn_left, pn2 = left->pn_right; + left->pn_arity = PN_LIST; + left->pn_parens = false; + left->initList(pn1); + left->append(pn2); + if (tt == TOK_PLUS) { + if (pn1->pn_type == TOK_STRING) + left->pn_xflags |= PNX_STRCAT; + else if (pn1->pn_type != TOK_NUMBER) + left->pn_xflags |= PNX_CANTFOLD; + if (pn2->pn_type == TOK_STRING) + left->pn_xflags |= PNX_STRCAT; + else if (pn2->pn_type != TOK_NUMBER) + left->pn_xflags |= PNX_CANTFOLD; + } + } + left->append(right); + left->pn_pos.end = right->pn_pos.end; + if (tt == TOK_PLUS) { + if (right->pn_type == TOK_STRING) + left->pn_xflags |= PNX_STRCAT; + else if (right->pn_type != TOK_NUMBER) + left->pn_xflags |= PNX_CANTFOLD; + } + return left; + } + + /* + * Fold constant addition immediately, to conserve node space and, what's + * more, so js_FoldConstants never sees mixed addition and concatenation + * operations with more than one leading non-string operand in a PN_LIST + * generated for expressions such as 1 + 2 + "pt" (which should evaluate + * to "3pt", not "12pt"). + */ + if (tt == TOK_PLUS && + left->pn_type == TOK_NUMBER && + right->pn_type == TOK_NUMBER) { + left->pn_dval += right->pn_dval; + left->pn_pos.end = right->pn_pos.end; + RecycleTree(right, tc); + return left; + } + + pn = NewOrRecycledNode(tc); + if (!pn) + return NULL; + pn->init(tt, op, PN_BINARY); + pn->pn_pos.begin = left->pn_pos.begin; + pn->pn_pos.end = right->pn_pos.end; + pn->pn_left = left; + pn->pn_right = right; + return (BinaryNode *)pn; +} + +namespace js { + +inline void +NameNode::initCommon(JSTreeContext *tc) +{ + pn_expr = NULL; + pn_cookie.makeFree(); + pn_dflags = (!tc->topStmt || tc->topStmt->type == STMT_BLOCK) + ? PND_BLOCKCHILD + : 0; + pn_blockid = tc->blockid(); +} + +NameNode * +NameNode::create(JSAtom *atom, JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = JSParseNode::create(PN_NAME, tc); + if (pn) { + pn->pn_atom = atom; + ((NameNode *)pn)->initCommon(tc); + } + return (NameNode *)pn; +} + +} /* namespace js */ + +static bool +GenerateBlockId(JSTreeContext *tc, uint32& blockid) +{ + if (tc->blockidGen == JS_BIT(20)) { + JS_ReportErrorNumber(tc->parser->context, js_GetErrorMessage, NULL, + JSMSG_NEED_DIET, "program"); + return false; + } + blockid = tc->blockidGen++; + return true; +} + +static bool +GenerateBlockIdForStmtNode(JSParseNode *pn, JSTreeContext *tc) +{ + JS_ASSERT(tc->topStmt); + JS_ASSERT(STMT_MAYBE_SCOPE(tc->topStmt)); + JS_ASSERT(pn->pn_type == TOK_LC || pn->pn_type == TOK_LEXICALSCOPE); + if (!GenerateBlockId(tc, tc->topStmt->blockid)) + return false; + pn->pn_blockid = tc->topStmt->blockid; + return true; +} + +/* + * Parse a top-level JS script. + */ +JSParseNode * +Parser::parse(JSObject *chain) +{ + /* + * Protect atoms from being collected by a GC activation, which might + * - nest on this thread due to out of memory (the so-called "last ditch" + * GC attempted within js_NewGCThing), or + * - run for any reason on another thread if this thread is suspended on + * an object lock before it finishes generating bytecode into a script + * protected from the GC by a root or a stack frame reference. + */ + JSTreeContext globaltc(this); + globaltc.setScopeChain(chain); + if (!GenerateBlockId(&globaltc, globaltc.bodyid)) + return NULL; + + JSParseNode *pn = statements(); + if (pn) { + if (!tokenStream.matchToken(TOK_EOF)) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); + pn = NULL; + } else { + if (!js_FoldConstants(context, pn, &globaltc)) + pn = NULL; + } + } + return pn; +} + +JS_STATIC_ASSERT(UpvarCookie::FREE_LEVEL == JS_BITMASK(JSFB_LEVEL_BITS)); + +static inline bool +SetStaticLevel(JSTreeContext *tc, uintN staticLevel) +{ + /* + * This is a lot simpler than error-checking every UpvarCookie::set, and + * practically speaking it leaves more than enough room for upvars. + */ + if (UpvarCookie::isLevelReserved(staticLevel)) { + JS_ReportErrorNumber(tc->parser->context, js_GetErrorMessage, NULL, + JSMSG_TOO_DEEP, js_function_str); + return false; + } + tc->staticLevel = staticLevel; + return true; +} + +/* + * Compile a top-level script. + */ +Compiler::Compiler(JSContext *cx, JSPrincipals *prin, JSStackFrame *cfp) + : parser(cx, prin, cfp) +{ +} + +JSScript * +Compiler::compileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, + JSPrincipals *principals, uint32 tcflags, + const jschar *chars, size_t length, + const char *filename, uintN lineno, JSVersion version, + JSString *source /* = NULL */, + uintN staticLevel /* = 0 */) +{ + JSArenaPool codePool, notePool; + TokenKind tt; + JSParseNode *pn; + JSScript *script; + bool inDirectivePrologue; +#ifdef METER_PARSENODES + void *sbrk(ptrdiff_t), *before = sbrk(0); +#endif + + JS_ASSERT(!(tcflags & ~(TCF_COMPILE_N_GO | TCF_NO_SCRIPT_RVAL | TCF_NEED_MUTABLE_SCRIPT | + TCF_COMPILE_FOR_EVAL))); + + /* + * The scripted callerFrame can only be given for compile-and-go scripts + * and non-zero static level requires callerFrame. + */ + JS_ASSERT_IF(callerFrame, tcflags & TCF_COMPILE_N_GO); + JS_ASSERT_IF(staticLevel != 0, callerFrame); + + Compiler compiler(cx, principals, callerFrame); + if (!compiler.init(chars, length, filename, lineno, version)) + return NULL; + + JS_InitArenaPool(&codePool, "code", 1024, sizeof(jsbytecode), + &cx->scriptStackQuota); + JS_InitArenaPool(¬ePool, "note", 1024, sizeof(jssrcnote), + &cx->scriptStackQuota); + + Parser &parser = compiler.parser; + TokenStream &tokenStream = parser.tokenStream; + + JSCodeGenerator cg(&parser, &codePool, ¬ePool, tokenStream.getLineno()); + if (!cg.init()) + return NULL; + + MUST_FLOW_THROUGH("out"); + + // We can specialize a bit for the given scope chain if that scope chain is the global object. + JSObject *globalObj = scopeChain && scopeChain == scopeChain->getGlobal() + ? scopeChain->getGlobal() + : NULL; + js::GlobalScope globalScope(cx, globalObj, &cg); + if (globalObj) { + JS_ASSERT(globalObj->isNative()); + JS_ASSERT((globalObj->getClass()->flags & JSCLASS_GLOBAL_FLAGS) == JSCLASS_GLOBAL_FLAGS); + } + + /* Null script early in case of error, to reduce our code footprint. */ + script = NULL; + + globalScope.cg = &cg; + cg.flags |= tcflags; + cg.setScopeChain(scopeChain); + compiler.globalScope = &globalScope; + if (!SetStaticLevel(&cg, staticLevel)) + goto out; + + /* If this is a direct call to eval, inherit the caller's strictness. */ + if (callerFrame && + callerFrame->isScriptFrame() && + callerFrame->script()->strictModeCode) { + cg.flags |= TCF_STRICT_MODE_CODE; + tokenStream.setStrictMode(); + } + + /* + * If funbox is non-null after we create the new script, callerFrame->fun + * was saved in the 0th object table entry. + */ + JSObjectBox *funbox; + funbox = NULL; + + if (tcflags & TCF_COMPILE_N_GO) { + if (source) { + /* + * Save eval program source in script->atomMap.vector[0] for the + * eval cache (see EvalCacheLookup in jsobj.cpp). + */ + JSAtom *atom = js_AtomizeString(cx, source, 0); + if (!atom || !cg.atomList.add(&parser, atom)) + goto out; + } + + if (callerFrame && callerFrame->isFunctionFrame()) { + /* + * An eval script in a caller frame needs to have its enclosing + * function captured in case it refers to an upvar, and someone + * wishes to decompile it while it's running. + */ + funbox = parser.newObjectBox(FUN_OBJECT(callerFrame->fun())); + if (!funbox) + goto out; + funbox->emitLink = cg.objectList.lastbox; + cg.objectList.lastbox = funbox; + cg.objectList.length++; + } + } + + /* + * Inline this->statements to emit as we go to save AST space. We must + * generate our script-body blockid since we aren't calling Statements. + */ + uint32 bodyid; + if (!GenerateBlockId(&cg, bodyid)) + goto out; + cg.bodyid = bodyid; + +#if JS_HAS_XML_SUPPORT + pn = NULL; + bool onlyXML; + onlyXML = true; +#endif + + inDirectivePrologue = true; + tokenStream.setOctalCharacterEscape(false); + for (;;) { + tt = tokenStream.peekToken(TSF_OPERAND); + if (tt <= TOK_EOF) { + if (tt == TOK_EOF) + break; + JS_ASSERT(tt == TOK_ERROR); + goto out; + } + + pn = parser.statement(); + if (!pn) + goto out; + JS_ASSERT(!cg.blockNode); + + if (inDirectivePrologue && !parser.recognizeDirectivePrologue(pn, &inDirectivePrologue)) + goto out; + + if (!js_FoldConstants(cx, pn, &cg)) + goto out; + + if (!parser.analyzeFunctions(&cg)) + goto out; + cg.functionList = NULL; + + if (!js_EmitTree(cx, &cg, pn)) + goto out; + +#if JS_HAS_XML_SUPPORT + if (PN_TYPE(pn) != TOK_SEMI || + !pn->pn_kid || + !TreeTypeIsXML(PN_TYPE(pn->pn_kid))) { + onlyXML = false; + } +#endif + RecycleTree(pn, &cg); + } + +#if JS_HAS_XML_SUPPORT + /* + * Prevent XML data theft via <script src="http://victim.com/foo.xml">. + * For background, see: + * + * https://bugzilla.mozilla.org/show_bug.cgi?id=336551 + */ + if (pn && onlyXML && !callerFrame) { + parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_XML_WHOLE_PROGRAM); + goto out; + } +#endif + + /* + * Global variables (gvars) share the atom index space with locals. Due to + * incremental code generation we need to patch the bytecode to adjust the + * local references to skip the globals. + */ + if (cg.hasSharps()) { + jsbytecode *code, *end; + JSOp op; + const JSCodeSpec *cs; + uintN len, slot; + + code = CG_BASE(&cg); + for (end = code + CG_OFFSET(&cg); code != end; code += len) { + JS_ASSERT(code < end); + op = (JSOp) *code; + cs = &js_CodeSpec[op]; + len = (cs->length > 0) + ? (uintN) cs->length + : js_GetVariableBytecodeLength(code); + if ((cs->format & JOF_SHARPSLOT) || + JOF_TYPE(cs->format) == JOF_LOCAL || + (JOF_TYPE(cs->format) == JOF_SLOTATOM)) { + /* + * JSOP_GETARGPROP also has JOF_SLOTATOM type, but it may be + * emitted only for a function. + */ + JS_ASSERT_IF(!(cs->format & JOF_SHARPSLOT), + (JOF_TYPE(cs->format) == JOF_SLOTATOM) == + (op == JSOP_GETLOCALPROP)); + slot = GET_SLOTNO(code); + if (!(cs->format & JOF_SHARPSLOT)) + slot += cg.sharpSlots(); + if (slot >= SLOTNO_LIMIT) + goto too_many_slots; + SET_SLOTNO(code, slot); + } + } + } + +#ifdef METER_PARSENODES + printf("Parser growth: %d (%u nodes, %u max, %u unrecycled)\n", + (char *)sbrk(0) - (char *)before, + parsenodes, + maxparsenodes, + parsenodes - recyclednodes); + before = sbrk(0); +#endif + + /* + * Nowadays the threaded interpreter needs a stop instruction, so we + * do have to emit that here. + */ + if (js_Emit1(cx, &cg, JSOP_STOP) < 0) + goto out; + +#ifdef METER_PARSENODES + printf("Code-gen growth: %d (%u bytecodes, %u srcnotes)\n", + (char *)sbrk(0) - (char *)before, CG_OFFSET(&cg), cg.noteCount); +#endif + +#ifdef JS_ARENAMETER + JS_DumpArenaStats(stdout); +#endif + + JS_ASSERT(cg.version() == version); + + script = JSScript::NewScriptFromCG(cx, &cg); + if (script && funbox) + script->savedCallerFun = true; + +#ifdef JS_SCOPE_DEPTH_METER + if (script) { + JSObject *obj = scopeChain; + uintN depth = 1; + while ((obj = obj->getParent()) != NULL) + ++depth; + JS_BASIC_STATS_ACCUM(&cx->runtime->hostenvScopeDepthStats, depth); + } +#endif + + if (!defineGlobals(cx, globalScope, script)) + goto late_error; + + out: + JS_FinishArenaPool(&codePool); + JS_FinishArenaPool(¬ePool); + return script; + + too_many_slots: + parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_LOCALS); + /* Fall through. */ + + late_error: + if (script) { + js_DestroyScript(cx, script); + script = NULL; + } + goto out; +} + +bool +Compiler::defineGlobals(JSContext *cx, GlobalScope &globalScope, JSScript *script) +{ + if (!globalScope.defs.length()) + return true; + + JSObject *globalObj = globalScope.globalObj; + + /* Define and update global properties. */ + for (size_t i = 0; i < globalScope.defs.length(); i++) { + GlobalScope::GlobalDef &def = globalScope.defs[i]; + + /* Names that could be resolved ahead of time can be skipped. */ + if (!def.atom) + continue; + + jsid id = ATOM_TO_JSID(def.atom); + Value rval; + + if (def.funbox) { + JSFunction *fun = (JSFunction *)def.funbox->object; + + /* + * No need to check for redeclarations or anything, global + * optimizations only take place if the property is not defined. + */ + rval.setObject(*fun); + } else { + rval.setUndefined(); + } + + JSProperty *prop; + + if (!js_DefineNativeProperty(cx, globalObj, id, rval, PropertyStub, StrictPropertyStub, + JSPROP_ENUMERATE | JSPROP_PERMANENT, 0, 0, &prop)) { + return false; + } + + JS_ASSERT(prop); + const Shape *shape = (const Shape *)prop; + def.knownSlot = shape->slot; + } + + js::Vector<JSScript *, 16, ContextAllocPolicy> worklist(cx); + if (!worklist.append(script)) + return false; + + /* + * Recursively walk through all scripts we just compiled. For each script, + * go through all global uses. Each global use indexes into globalScope->defs. + * Use this information to repoint each use to the correct slot in the global + * object. + */ + while (worklist.length()) { + JSScript *inner = worklist.back(); + worklist.popBack(); + + if (JSScript::isValidOffset(inner->objectsOffset)) { + JSObjectArray *arr = inner->objects(); + for (size_t i = 0; i < arr->length; i++) { + JSObject *obj = arr->vector[i]; + if (!obj->isFunction()) + continue; + JSFunction *fun = obj->getFunctionPrivate(); + JS_ASSERT(fun->isInterpreted()); + JSScript *inner = fun->u.i.script; + if (!JSScript::isValidOffset(inner->globalsOffset) && + !JSScript::isValidOffset(inner->objectsOffset)) { + continue; + } + if (!worklist.append(inner)) + return false; + } + } + + if (!JSScript::isValidOffset(inner->globalsOffset)) + continue; + + GlobalSlotArray *globalUses = inner->globals(); + uint32 nGlobalUses = globalUses->length; + for (uint32 i = 0; i < nGlobalUses; i++) { + uint32 index = globalUses->vector[i].slot; + JS_ASSERT(index < globalScope.defs.length()); + globalUses->vector[i].slot = globalScope.defs[index].knownSlot; + } + } + + return true; +} + +/* + * Insist on a final return before control flows out of pn. Try to be a bit + * smart about loops: do {...; return e2;} while(0) at the end of a function + * that contains an early return e1 will get a strict warning. Similarly for + * iloops: while (true){...} is treated as though ... returns. + */ +#define ENDS_IN_OTHER 0 +#define ENDS_IN_RETURN 1 +#define ENDS_IN_BREAK 2 + +static int +HasFinalReturn(JSParseNode *pn) +{ + JSParseNode *pn2, *pn3; + uintN rv, rv2, hasDefault; + + switch (pn->pn_type) { + case TOK_LC: + if (!pn->pn_head) + return ENDS_IN_OTHER; + return HasFinalReturn(pn->last()); + + case TOK_IF: + if (!pn->pn_kid3) + return ENDS_IN_OTHER; + return HasFinalReturn(pn->pn_kid2) & HasFinalReturn(pn->pn_kid3); + + case TOK_WHILE: + pn2 = pn->pn_left; + if (pn2->pn_type == TOK_PRIMARY && pn2->pn_op == JSOP_TRUE) + return ENDS_IN_RETURN; + if (pn2->pn_type == TOK_NUMBER && pn2->pn_dval) + return ENDS_IN_RETURN; + return ENDS_IN_OTHER; + + case TOK_DO: + pn2 = pn->pn_right; + if (pn2->pn_type == TOK_PRIMARY) { + if (pn2->pn_op == JSOP_FALSE) + return HasFinalReturn(pn->pn_left); + if (pn2->pn_op == JSOP_TRUE) + return ENDS_IN_RETURN; + } + if (pn2->pn_type == TOK_NUMBER) { + if (pn2->pn_dval == 0) + return HasFinalReturn(pn->pn_left); + return ENDS_IN_RETURN; + } + return ENDS_IN_OTHER; + + case TOK_FOR: + pn2 = pn->pn_left; + if (pn2->pn_arity == PN_TERNARY && !pn2->pn_kid2) + return ENDS_IN_RETURN; + return ENDS_IN_OTHER; + + case TOK_SWITCH: + rv = ENDS_IN_RETURN; + hasDefault = ENDS_IN_OTHER; + pn2 = pn->pn_right; + if (pn2->pn_type == TOK_LEXICALSCOPE) + pn2 = pn2->expr(); + for (pn2 = pn2->pn_head; rv && pn2; pn2 = pn2->pn_next) { + if (pn2->pn_type == TOK_DEFAULT) + hasDefault = ENDS_IN_RETURN; + pn3 = pn2->pn_right; + JS_ASSERT(pn3->pn_type == TOK_LC); + if (pn3->pn_head) { + rv2 = HasFinalReturn(pn3->last()); + if (rv2 == ENDS_IN_OTHER && pn2->pn_next) + /* Falling through to next case or default. */; + else + rv &= rv2; + } + } + /* If a final switch has no default case, we judge it harshly. */ + rv &= hasDefault; + return rv; + + case TOK_BREAK: + return ENDS_IN_BREAK; + + case TOK_WITH: + return HasFinalReturn(pn->pn_right); + + case TOK_RETURN: + return ENDS_IN_RETURN; + + case TOK_COLON: + case TOK_LEXICALSCOPE: + return HasFinalReturn(pn->expr()); + + case TOK_THROW: + return ENDS_IN_RETURN; + + case TOK_TRY: + /* If we have a finally block that returns, we are done. */ + if (pn->pn_kid3) { + rv = HasFinalReturn(pn->pn_kid3); + if (rv == ENDS_IN_RETURN) + return rv; + } + + /* Else check the try block and any and all catch statements. */ + rv = HasFinalReturn(pn->pn_kid1); + if (pn->pn_kid2) { + JS_ASSERT(pn->pn_kid2->pn_arity == PN_LIST); + for (pn2 = pn->pn_kid2->pn_head; pn2; pn2 = pn2->pn_next) + rv &= HasFinalReturn(pn2); + } + return rv; + + case TOK_CATCH: + /* Check this catch block's body. */ + return HasFinalReturn(pn->pn_kid3); + + case TOK_LET: + /* Non-binary let statements are let declarations. */ + if (pn->pn_arity != PN_BINARY) + return ENDS_IN_OTHER; + return HasFinalReturn(pn->pn_right); + + default: + return ENDS_IN_OTHER; + } +} + +static JSBool +ReportBadReturn(JSContext *cx, JSTreeContext *tc, uintN flags, uintN errnum, + uintN anonerrnum) +{ + JSAutoByteString name; + if (tc->fun()->atom) { + if (!js_AtomToPrintableString(cx, tc->fun()->atom, &name)) + return false; + } else { + errnum = anonerrnum; + } + return ReportCompileErrorNumber(cx, TS(tc->parser), NULL, flags, errnum, name.ptr()); +} + +static JSBool +CheckFinalReturn(JSContext *cx, JSTreeContext *tc, JSParseNode *pn) +{ + JS_ASSERT(tc->inFunction()); + return HasFinalReturn(pn) == ENDS_IN_RETURN || + ReportBadReturn(cx, tc, JSREPORT_WARNING | JSREPORT_STRICT, + JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE); +} + +/* + * Check that it is permitted to assign to lhs. Strict mode code may not + * assign to 'eval' or 'arguments'. + */ +bool +CheckStrictAssignment(JSContext *cx, JSTreeContext *tc, JSParseNode *lhs) +{ + if (tc->needStrictChecks() && lhs->pn_type == TOK_NAME) { + JSAtom *atom = lhs->pn_atom; + JSAtomState *atomState = &cx->runtime->atomState; + if (atom == atomState->evalAtom || atom == atomState->argumentsAtom) { + JSAutoByteString name; + if (!js_AtomToPrintableString(cx, atom, &name) || + !ReportStrictModeError(cx, TS(tc->parser), tc, lhs, JSMSG_DEPRECATED_ASSIGN, + name.ptr())) { + return false; + } + } + } + return true; +} + +/* + * Check that it is permitted to introduce a binding for atom. Strict mode + * forbids introducing new definitions for 'eval', 'arguments', or for any + * strict mode reserved keyword. Use pn for reporting error locations, or use + * tc's token stream if pn is NULL. + */ +bool +CheckStrictBinding(JSContext *cx, JSTreeContext *tc, JSAtom *atom, JSParseNode *pn) +{ + if (!tc->needStrictChecks()) + return true; + + JSAtomState *atomState = &cx->runtime->atomState; + if (atom == atomState->evalAtom || + atom == atomState->argumentsAtom || + FindKeyword(atom->charsZ(), atom->length())) + { + JSAutoByteString name; + if (!js_AtomToPrintableString(cx, atom, &name)) + return false; + return ReportStrictModeError(cx, TS(tc->parser), tc, pn, JSMSG_BAD_BINDING, name.ptr()); + } + + return true; +} + +static bool +ReportBadParameter(JSContext *cx, JSTreeContext *tc, JSAtom *name, uintN errorNumber) +{ + JSDefinition *dn = ALE_DEFN(tc->decls.lookup(name)); + JSAutoByteString bytes; + return js_AtomToPrintableString(cx, name, &bytes) && + ReportStrictModeError(cx, TS(tc->parser), tc, dn, errorNumber, bytes.ptr()); +} + +/* + * In strict mode code, all parameter names must be distinct, must not be + * strict mode reserved keywords, and must not be 'eval' or 'arguments'. We + * must perform these checks here, and not eagerly during parsing, because a + * function's body may turn on strict mode for the function head. + */ +static bool +CheckStrictParameters(JSContext *cx, JSTreeContext *tc) +{ + JS_ASSERT(tc->inFunction()); + + if (!tc->needStrictChecks() || tc->bindings.countArgs() == 0) + return true; + + JSAtom *argumentsAtom = cx->runtime->atomState.argumentsAtom; + JSAtom *evalAtom = cx->runtime->atomState.evalAtom; + + /* name => whether we've warned about the name already */ + HashMap<JSAtom *, bool> parameters(cx); + if (!parameters.init(tc->bindings.countArgs())) + return false; + + /* Start with lastVariable(), not lastArgument(), for destructuring. */ + for (Shape::Range r = tc->bindings.lastVariable(); !r.empty(); r.popFront()) { + jsid id = r.front().id; + if (!JSID_IS_ATOM(id)) + continue; + + JSAtom *name = JSID_TO_ATOM(id); + + if (name == argumentsAtom || name == evalAtom) { + if (!ReportBadParameter(cx, tc, name, JSMSG_BAD_BINDING)) + return false; + } + + if (tc->inStrictMode() && FindKeyword(name->charsZ(), name->length())) { + /* + * JSOPTION_STRICT is supposed to warn about future keywords, too, + * but we took care of that in the scanner. + */ + JS_ALWAYS_TRUE(!ReportBadParameter(cx, tc, name, JSMSG_RESERVED_ID)); + return false; + } + + /* + * Check for a duplicate parameter: warn or report an error exactly + * once for each duplicated parameter. + */ + if (HashMap<JSAtom *, bool>::AddPtr p = parameters.lookupForAdd(name)) { + if (!p->value && !ReportBadParameter(cx, tc, name, JSMSG_DUPLICATE_FORMAL)) + return false; + p->value = true; + } else { + if (!parameters.add(p, name, false)) + return false; + } + } + + return true; +} + +JSParseNode * +Parser::functionBody() +{ + JSStmtInfo stmtInfo; + uintN oldflags, firstLine; + JSParseNode *pn; + + JS_ASSERT(tc->inFunction()); + js_PushStatement(tc, &stmtInfo, STMT_BLOCK, -1); + stmtInfo.flags = SIF_BODY_BLOCK; + + oldflags = tc->flags; + tc->flags &= ~(TCF_RETURN_EXPR | TCF_RETURN_VOID); + + /* + * Save the body's first line, and store it in pn->pn_pos.begin.lineno + * later, because we may have not peeked in tokenStream yet, so statements + * won't acquire a valid pn->pn_pos.begin from the current token. + */ + firstLine = tokenStream.getLineno(); +#if JS_HAS_EXPR_CLOSURES + if (tokenStream.currentToken().type == TOK_LC) { + pn = statements(); + } else { + pn = UnaryNode::create(tc); + if (pn) { + pn->pn_kid = assignExpr(); + if (!pn->pn_kid) { + pn = NULL; + } else { + if (tc->flags & TCF_FUN_IS_GENERATOR) { + ReportBadReturn(context, tc, JSREPORT_ERROR, + JSMSG_BAD_GENERATOR_RETURN, + JSMSG_BAD_ANON_GENERATOR_RETURN); + pn = NULL; + } else { + pn->pn_type = TOK_RETURN; + pn->pn_op = JSOP_RETURN; + pn->pn_pos.end = pn->pn_kid->pn_pos.end; + } + } + } + } +#else + pn = statements(); +#endif + + if (pn) { + JS_ASSERT(!(tc->topStmt->flags & SIF_SCOPE)); + js_PopStatement(tc); + pn->pn_pos.begin.lineno = firstLine; + + /* Check for falling off the end of a function that returns a value. */ + if (context->hasStrictOption() && (tc->flags & TCF_RETURN_EXPR) && + !CheckFinalReturn(context, tc, pn)) { + pn = NULL; + } + } + + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); + return pn; +} + +static JSAtomListElement * +MakePlaceholder(JSParseNode *pn, JSTreeContext *tc) +{ + JSAtomListElement *ale = tc->lexdeps.add(tc->parser, pn->pn_atom); + if (!ale) + return NULL; + + JSDefinition *dn = (JSDefinition *)NameNode::create(pn->pn_atom, tc); + if (!dn) + return NULL; + + ALE_SET_DEFN(ale, dn); + dn->pn_type = TOK_NAME; + dn->pn_op = JSOP_NOP; + dn->pn_defn = true; + dn->pn_dflags |= PND_PLACEHOLDER; + return ale; +} + +static bool +Define(JSParseNode *pn, JSAtom *atom, JSTreeContext *tc, bool let = false) +{ + JS_ASSERT(!pn->pn_used); + JS_ASSERT_IF(pn->pn_defn, pn->isPlaceholder()); + + JSHashEntry **hep; + JSAtomListElement *ale = NULL; + JSAtomList *list = NULL; + + if (let) + ale = (list = &tc->decls)->rawLookup(atom, hep); + if (!ale) + ale = (list = &tc->lexdeps)->rawLookup(atom, hep); + + if (ale) { + JSDefinition *dn = ALE_DEFN(ale); + if (dn != pn) { + JSParseNode **pnup = &dn->dn_uses; + JSParseNode *pnu; + uintN start = let ? pn->pn_blockid : tc->bodyid; + + while ((pnu = *pnup) != NULL && pnu->pn_blockid >= start) { + JS_ASSERT(pnu->pn_used); + pnu->pn_lexdef = (JSDefinition *) pn; + pn->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS; + pnup = &pnu->pn_link; + } + + if (pnu != dn->dn_uses) { + *pnup = pn->dn_uses; + pn->dn_uses = dn->dn_uses; + dn->dn_uses = pnu; + + if ((!pnu || pnu->pn_blockid < tc->bodyid) && list != &tc->decls) + list->rawRemove(tc->parser, ale, hep); + } + } + } + + ale = tc->decls.add(tc->parser, atom, let ? JSAtomList::SHADOW : JSAtomList::UNIQUE); + if (!ale) + return false; + ALE_SET_DEFN(ale, pn); + pn->pn_defn = true; + pn->pn_dflags &= ~PND_PLACEHOLDER; + if (!tc->parent) + pn->pn_dflags |= PND_TOPLEVEL; + return true; +} + +static void +LinkUseToDef(JSParseNode *pn, JSDefinition *dn, JSTreeContext *tc) +{ + JS_ASSERT(!pn->pn_used); + JS_ASSERT(!pn->pn_defn); + JS_ASSERT(pn != dn->dn_uses); + pn->pn_link = dn->dn_uses; + dn->dn_uses = pn; + dn->pn_dflags |= pn->pn_dflags & PND_USE2DEF_FLAGS; + pn->pn_used = true; + pn->pn_lexdef = dn; +} + +static void +ForgetUse(JSParseNode *pn) +{ + if (!pn->pn_used) { + JS_ASSERT(!pn->pn_defn); + return; + } + + JSParseNode **pnup = &pn->lexdef()->dn_uses; + JSParseNode *pnu; + while ((pnu = *pnup) != pn) + pnup = &pnu->pn_link; + *pnup = pn->pn_link; + pn->pn_used = false; +} + +static JSParseNode * +MakeAssignment(JSParseNode *pn, JSParseNode *rhs, JSTreeContext *tc) +{ + JSParseNode *lhs = NewOrRecycledNode(tc); + if (!lhs) + return NULL; + *lhs = *pn; + + if (pn->pn_used) { + JSDefinition *dn = pn->pn_lexdef; + JSParseNode **pnup = &dn->dn_uses; + + while (*pnup != pn) + pnup = &(*pnup)->pn_link; + *pnup = lhs; + lhs->pn_link = pn->pn_link; + pn->pn_link = NULL; + } + + pn->pn_type = TOK_ASSIGN; + pn->pn_op = JSOP_NOP; + pn->pn_arity = PN_BINARY; + pn->pn_parens = false; + pn->pn_used = pn->pn_defn = false; + pn->pn_left = lhs; + pn->pn_right = rhs; + return lhs; +} + +static JSParseNode * +MakeDefIntoUse(JSDefinition *dn, JSParseNode *pn, JSAtom *atom, JSTreeContext *tc) +{ + /* + * If dn is arg, or in [var, const, let] and has an initializer, then we + * must rewrite it to be an assignment node, whose freshly allocated + * left-hand side becomes a use of pn. + */ + if (dn->isBindingForm()) { + JSParseNode *rhs = dn->expr(); + if (rhs) { + JSParseNode *lhs = MakeAssignment(dn, rhs, tc); + if (!lhs) + return NULL; + //pn->dn_uses = lhs; + dn = (JSDefinition *) lhs; + } + + dn->pn_op = (js_CodeSpec[dn->pn_op].format & JOF_SET) ? JSOP_SETNAME : JSOP_NAME; + } else if (dn->kind() == JSDefinition::FUNCTION) { + JS_ASSERT(dn->pn_op == JSOP_NOP); + PrepareNodeForMutation(dn, tc); + dn->pn_type = TOK_NAME; + dn->pn_arity = PN_NAME; + dn->pn_atom = atom; + } + + /* Now make dn no longer a definition, rather a use of pn. */ + JS_ASSERT(dn->pn_type == TOK_NAME); + JS_ASSERT(dn->pn_arity == PN_NAME); + JS_ASSERT(dn->pn_atom == atom); + + for (JSParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { + JS_ASSERT(pnu->pn_used); + JS_ASSERT(!pnu->pn_defn); + pnu->pn_lexdef = (JSDefinition *) pn; + pn->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS; + } + pn->pn_dflags |= dn->pn_dflags & PND_USE2DEF_FLAGS; + pn->dn_uses = dn; + + dn->pn_defn = false; + dn->pn_used = true; + dn->pn_lexdef = (JSDefinition *) pn; + dn->pn_cookie.makeFree(); + dn->pn_dflags &= ~PND_BOUND; + return dn; +} + +static bool +DefineArg(JSParseNode *pn, JSAtom *atom, uintN i, JSTreeContext *tc) +{ + JSParseNode *argpn, *argsbody; + + /* Flag tc so we don't have to lookup arguments on every use. */ + if (atom == tc->parser->context->runtime->atomState.argumentsAtom) + tc->flags |= TCF_FUN_PARAM_ARGUMENTS; + + /* + * Make an argument definition node, distinguished by being in tc->decls + * but having TOK_NAME type and JSOP_NOP op. Insert it in a TOK_ARGSBODY + * list node returned via pn->pn_body. + */ + argpn = NameNode::create(atom, tc); + if (!argpn) + return false; + JS_ASSERT(PN_TYPE(argpn) == TOK_NAME && PN_OP(argpn) == JSOP_NOP); + + /* Arguments are initialized by definition. */ + argpn->pn_dflags |= PND_INITIALIZED; + if (!Define(argpn, atom, tc)) + return false; + + argsbody = pn->pn_body; + if (!argsbody) { + argsbody = ListNode::create(tc); + if (!argsbody) + return false; + argsbody->pn_type = TOK_ARGSBODY; + argsbody->pn_op = JSOP_NOP; + argsbody->makeEmpty(); + pn->pn_body = argsbody; + } + argsbody->append(argpn); + + argpn->pn_op = JSOP_GETARG; + argpn->pn_cookie.set(tc->staticLevel, i); + argpn->pn_dflags |= PND_BOUND; + return true; +} + +/* + * Compile a JS function body, which might appear as the value of an event + * handler attribute in an HTML <INPUT> tag. + */ +bool +Compiler::compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals, + Bindings *bindings, const jschar *chars, size_t length, + const char *filename, uintN lineno, JSVersion version) +{ + Compiler compiler(cx, principals); + + if (!compiler.init(chars, length, filename, lineno, version)) + return false; + + /* No early return from after here until the js_FinishArenaPool calls. */ + JSArenaPool codePool, notePool; + JS_InitArenaPool(&codePool, "code", 1024, sizeof(jsbytecode), + &cx->scriptStackQuota); + JS_InitArenaPool(¬ePool, "note", 1024, sizeof(jssrcnote), + &cx->scriptStackQuota); + + Parser &parser = compiler.parser; + TokenStream &tokenStream = parser.tokenStream; + + JSCodeGenerator funcg(&parser, &codePool, ¬ePool, tokenStream.getLineno()); + if (!funcg.init()) + return NULL; + + funcg.flags |= TCF_IN_FUNCTION; + funcg.setFunction(fun); + funcg.bindings.transfer(cx, bindings); + fun->setArgCount(funcg.bindings.countArgs()); + if (!GenerateBlockId(&funcg, funcg.bodyid)) + return NULL; + + /* FIXME: make Function format the source for a function definition. */ + tokenStream.mungeCurrentToken(TOK_NAME); + JSParseNode *fn = FunctionNode::create(&funcg); + if (fn) { + fn->pn_body = NULL; + fn->pn_cookie.makeFree(); + + uintN nargs = fun->nargs; + if (nargs) { + /* + * NB: do not use AutoLocalNameArray because it will release space + * allocated from cx->tempPool by DefineArg. + */ + jsuword *names = funcg.bindings.getLocalNameArray(cx, &cx->tempPool); + if (!names) { + fn = NULL; + } else { + for (uintN i = 0; i < nargs; i++) { + JSAtom *name = JS_LOCAL_NAME_TO_ATOM(names[i]); + if (!DefineArg(fn, name, i, &funcg)) { + fn = NULL; + break; + } + } + } + } + } + + /* + * Farble the body so that it looks like a block statement to js_EmitTree, + * which is called from js_EmitFunctionBody (see jsemit.cpp). After we're + * done parsing, we must fold constants, analyze any nested functions, and + * generate code for this function, including a stop opcode at the end. + */ + tokenStream.mungeCurrentToken(TOK_LC); + JSParseNode *pn = fn ? parser.functionBody() : NULL; + if (pn) { + if (!CheckStrictParameters(cx, &funcg)) { + pn = NULL; + } else if (!tokenStream.matchToken(TOK_EOF)) { + parser.reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); + pn = NULL; + } else if (!js_FoldConstants(cx, pn, &funcg)) { + /* js_FoldConstants reported the error already. */ + pn = NULL; + } else if (!parser.analyzeFunctions(&funcg)) { + pn = NULL; + } else { + if (fn->pn_body) { + JS_ASSERT(PN_TYPE(fn->pn_body) == TOK_ARGSBODY); + fn->pn_body->append(pn); + fn->pn_body->pn_pos = pn->pn_pos; + pn = fn->pn_body; + } + + if (!js_EmitFunctionScript(cx, &funcg, pn)) + pn = NULL; + } + } + + /* Restore saved state and release code generation arenas. */ + JS_FinishArenaPool(&codePool); + JS_FinishArenaPool(¬ePool); + return pn != NULL; +} + +/* + * Parameter block types for the several Binder functions. We use a common + * helper function signature in order to share code among destructuring and + * simple variable declaration parsers. In the destructuring case, the binder + * function is called indirectly from the variable declaration parser by way + * of CheckDestructuring and its friends. + */ +typedef JSBool +(*Binder)(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc); + +struct BindData { + BindData() : fresh(true) {} + + JSParseNode *pn; /* name node for definition processing and + error source coordinates */ + JSOp op; /* prolog bytecode or nop */ + Binder binder; /* binder, discriminates u */ + union { + struct { + uintN overflow; + } let; + }; + bool fresh; +}; + +static bool +BindLocalVariable(JSContext *cx, JSTreeContext *tc, JSAtom *atom, BindingKind kind, bool isArg) +{ + JS_ASSERT(kind == VARIABLE || kind == CONSTANT); + + /* + * Don't bind a variable with the hidden name 'arguments', per ECMA-262. + * Instead 'var arguments' always restates the predefined property of the + * activation objects whose name is 'arguments'. Assignment to such a + * variable must be handled specially. + * + * Special case: an argument named 'arguments' *does* shadow the predefined + * arguments property. + */ + if (atom == cx->runtime->atomState.argumentsAtom && !isArg) + return true; + + return tc->bindings.add(cx, atom, kind); +} + +#if JS_HAS_DESTRUCTURING +static JSBool +BindDestructuringArg(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc) +{ + /* Flag tc so we don't have to lookup arguments on every use. */ + if (atom == tc->parser->context->runtime->atomState.argumentsAtom) + tc->flags |= TCF_FUN_PARAM_ARGUMENTS; + + JS_ASSERT(tc->inFunction()); + + /* + * NB: Check tc->decls rather than tc->bindings, because destructuring + * bindings aren't added to tc->bindings until after all arguments have + * been parsed. + */ + if (tc->decls.lookup(atom)) { + ReportCompileErrorNumber(cx, TS(tc->parser), NULL, JSREPORT_ERROR, + JSMSG_DESTRUCT_DUP_ARG); + return JS_FALSE; + } + + JSParseNode *pn = data->pn; + + /* + * Distinguish destructured-to binding nodes as vars, not args, by setting + * pn_op to JSOP_SETLOCAL. Parser::functionDef checks for this pn_op value + * when processing the destructuring-assignment AST prelude induced by such + * destructuring args in Parser::functionArguments. + * + * We must set the PND_BOUND flag too to prevent pn_op from being reset to + * JSOP_SETNAME by BindDestructuringVar. The only field not initialized is + * pn_cookie; it gets set in functionDef in the first "if (prelude)" block. + * We have to wait to set the cookie until we can call JSFunction::addLocal + * with kind = JSLOCAL_VAR, after all JSLOCAL_ARG locals have been added. + * + * Thus a destructuring formal parameter binds an ARG (as in arguments[i] + * element) with a null atom name for the object or array passed in to be + * destructured, and zero or more VARs (as in named local variables) for + * the destructured-to identifiers in the property value positions within + * the object or array destructuring pattern, and all ARGs for the formal + * parameter list bound as locals before any VAR for a destructured name. + */ + pn->pn_op = JSOP_SETLOCAL; + pn->pn_dflags |= PND_BOUND; + + return Define(pn, atom, tc); +} +#endif /* JS_HAS_DESTRUCTURING */ + +JSFunction * +Parser::newFunction(JSTreeContext *tc, JSAtom *atom, uintN lambda) +{ + JSObject *parent; + JSFunction *fun; + + JS_ASSERT((lambda & ~JSFUN_LAMBDA) == 0); + + /* + * Find the global compilation context in order to pre-set the newborn + * function's parent slot to tc->scopeChain. If the global context is a + * compile-and-go one, we leave the pre-set parent intact; otherwise we + * clear parent and proto. + */ + while (tc->parent) + tc = tc->parent; + parent = tc->inFunction() ? NULL : tc->scopeChain(); + + fun = js_NewFunction(context, NULL, NULL, 0, JSFUN_INTERPRETED | lambda, parent, atom); + if (fun && !tc->compileAndGo()) { + FUN_OBJECT(fun)->clearParent(); + FUN_OBJECT(fun)->clearProto(); + } + return fun; +} + +static JSBool +MatchOrInsertSemicolon(JSContext *cx, TokenStream *ts) +{ + TokenKind tt = ts->peekTokenSameLine(TSF_OPERAND); + if (tt == TOK_ERROR) + return JS_FALSE; + if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { + /* Advance the scanner for proper error location reporting. */ + ts->getToken(TSF_OPERAND); + ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, JSMSG_SEMI_BEFORE_STMNT); + return JS_FALSE; + } + (void) ts->matchToken(TOK_SEMI); + return JS_TRUE; +} + +bool +Parser::analyzeFunctions(JSTreeContext *tc) +{ + cleanFunctionList(&tc->functionList); + if (!tc->functionList) + return true; + if (!markFunArgs(tc->functionList)) + return false; + setFunctionKinds(tc->functionList, &tc->flags); + return true; +} + +/* + * Mark as funargs any functions that reach up to one or more upvars across an + * already-known funarg. The parser will flag the o_m lambda as a funarg in: + * + * function f(o, p) { + * o.m = function o_m(a) { + * function g() { return p; } + * function h() { return a; } + * return g() + h(); + * } + * } + * + * but without this extra marking phase, function g will not be marked as a + * funarg since it is called from within its parent scope. But g reaches up to + * f's parameter p, so if o_m escapes f's activation scope, g does too and + * cannot assume that p's stack slot is still alive. In contast function h + * neither escapes nor uses an upvar "above" o_m's level. + * + * If function g itself contained lambdas that contained non-lambdas that reach + * up above its level, then those non-lambdas would have to be marked too. This + * process is potentially exponential in the number of functions, but generally + * not so complex. But it can't be done during a single recursive traversal of + * the funbox tree, so we must use a work queue. + * + * Return the minimal "skipmin" for funbox and its siblings. This is the delta + * between the static level of the bodies of funbox and its peers (which must + * be funbox->level + 1), and the static level of the nearest upvar among all + * the upvars contained by funbox and its peers. If there are no upvars, return + * FREE_STATIC_LEVEL. Thus this function never returns 0. + */ +static uintN +FindFunArgs(JSFunctionBox *funbox, int level, JSFunctionBoxQueue *queue) +{ + uintN allskipmin = UpvarCookie::FREE_LEVEL; + + do { + JSParseNode *fn = funbox->node; + JS_ASSERT(fn->pn_arity == PN_FUNC); + JSFunction *fun = (JSFunction *) funbox->object; + int fnlevel = level; + + /* + * An eval can leak funbox, functions along its ancestor line, and its + * immediate kids. Since FindFunArgs uses DFS and the parser propagates + * TCF_FUN_HEAVYWEIGHT bottom up, funbox's ancestor function nodes have + * already been marked as funargs by this point. Therefore we have to + * flag only funbox->node and funbox->kids' nodes here. + * + * Generators need to be treated in the same way. Even if the value + * of a generator function doesn't escape, anything defined or referred + * to inside the generator can escape through a call to the generator. + * We could imagine doing static analysis to track the calls and see + * if any iterators or values returned by iterators escape, but that + * would be hard, so instead we just assume everything might escape. + */ + if (funbox->tcflags & (TCF_FUN_HEAVYWEIGHT | TCF_FUN_IS_GENERATOR)) { + fn->setFunArg(); + for (JSFunctionBox *kid = funbox->kids; kid; kid = kid->siblings) + kid->node->setFunArg(); + } + + /* + * Compute in skipmin the least distance from fun's static level up to + * an upvar, whether used directly by fun, or indirectly by a function + * nested in fun. + */ + uintN skipmin = UpvarCookie::FREE_LEVEL; + JSParseNode *pn = fn->pn_body; + + if (pn->pn_type == TOK_UPVARS) { + JSAtomList upvars(pn->pn_names); + JS_ASSERT(upvars.count != 0); + + JSAtomListIterator iter(&upvars); + JSAtomListElement *ale; + + while ((ale = iter()) != NULL) { + JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); + + if (!lexdep->isFreeVar()) { + uintN upvarLevel = lexdep->frameLevel(); + + if (int(upvarLevel) <= fnlevel) + fn->setFunArg(); + + uintN skip = (funbox->level + 1) - upvarLevel; + if (skip < skipmin) + skipmin = skip; + } + } + } + + /* + * If this function escapes, whether directly (the parser detects such + * escapes) or indirectly (because this non-escaping function uses an + * upvar that reaches across an outer function boundary where the outer + * function escapes), enqueue it for further analysis, and bump fnlevel + * to trap any non-escaping children. + */ + if (fn->isFunArg()) { + queue->push(funbox); + fnlevel = int(funbox->level); + } + + /* + * Now process the current function's children, and recalibrate their + * cumulative skipmin to be relative to the current static level. + */ + if (funbox->kids) { + uintN kidskipmin = FindFunArgs(funbox->kids, fnlevel, queue); + + JS_ASSERT(kidskipmin != 0); + if (kidskipmin != UpvarCookie::FREE_LEVEL) { + --kidskipmin; + if (kidskipmin != 0 && kidskipmin < skipmin) + skipmin = kidskipmin; + } + } + + /* + * Finally, after we've traversed all of the current function's kids, + * minimize fun's skipmin against our accumulated skipmin. Do likewise + * with allskipmin, but minimize across funbox and all of its siblings, + * to compute our return value. + */ + if (skipmin != UpvarCookie::FREE_LEVEL) { + fun->u.i.skipmin = skipmin; + if (skipmin < allskipmin) + allskipmin = skipmin; + } + } while ((funbox = funbox->siblings) != NULL); + + return allskipmin; +} + +bool +Parser::markFunArgs(JSFunctionBox *funbox) +{ + JSFunctionBoxQueue queue; + if (!queue.init(functionCount)) + return false; + + FindFunArgs(funbox, -1, &queue); + while ((funbox = queue.pull()) != NULL) { + JSParseNode *fn = funbox->node; + JS_ASSERT(fn->isFunArg()); + + JSParseNode *pn = fn->pn_body; + if (pn->pn_type == TOK_UPVARS) { + JSAtomList upvars(pn->pn_names); + JS_ASSERT(upvars.count != 0); + + JSAtomListIterator iter(&upvars); + JSAtomListElement *ale; + + while ((ale = iter()) != NULL) { + JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); + + if (!lexdep->isFreeVar() && + !lexdep->isFunArg() && + (lexdep->kind() == JSDefinition::FUNCTION || + PN_OP(lexdep) == JSOP_CALLEE)) { + /* + * Mark this formerly-Algol-like function as an escaping + * function (i.e., as a funarg), because it is used from + * another funarg. + * + * Progress is guaranteed because we set the funarg flag + * here, which suppresses revisiting this function (thanks + * to the !lexdep->isFunArg() test just above). + */ + lexdep->setFunArg(); + + JSFunctionBox *afunbox; + if (PN_OP(lexdep) == JSOP_CALLEE) { + /* + * A named function expression will not appear to be a + * funarg if it is immediately applied. However, if its + * name is used in an escaping function nested within + * it, then it must become flagged as a funarg again. + * See bug 545980. + */ + afunbox = funbox; + uintN calleeLevel = lexdep->pn_cookie.level(); + uintN staticLevel = afunbox->level + 1U; + while (staticLevel != calleeLevel) { + afunbox = afunbox->parent; + --staticLevel; + } + JS_ASSERT(afunbox->level + 1U == calleeLevel); + afunbox->node->setFunArg(); + } else { + afunbox = lexdep->pn_funbox; + } + queue.push(afunbox); + + /* + * Walk over nested functions again, now that we have + * changed the level across which it is unsafe to access + * upvars using the runtime dynamic link (frame chain). + */ + if (afunbox->kids) + FindFunArgs(afunbox->kids, afunbox->level, &queue); + } + } + } + } + return true; +} + +static uint32 +MinBlockId(JSParseNode *fn, uint32 id) +{ + if (fn->pn_blockid < id) + return false; + if (fn->pn_defn) { + for (JSParseNode *pn = fn->dn_uses; pn; pn = pn->pn_link) { + if (pn->pn_blockid < id) + return false; + } + } + return true; +} + +static inline bool +CanFlattenUpvar(JSDefinition *dn, JSFunctionBox *funbox, uint32 tcflags) +{ + /* + * Consider the current function (the lambda, innermost below) using a var + * x defined two static levels up: + * + * function f() { + * // z = g(); + * var x = 42; + * function g() { + * return function () { return x; }; + * } + * return g(); + * } + * + * So long as (1) the initialization in 'var x = 42' dominates all uses of + * g and (2) x is not reassigned, it is safe to optimize the lambda to a + * flat closure. Uncommenting the early call to g makes this optimization + * unsafe (z could name a global setter that calls its argument). + */ + JSFunctionBox *afunbox = funbox; + uintN dnLevel = dn->frameLevel(); + + JS_ASSERT(dnLevel <= funbox->level); + while (afunbox->level != dnLevel) { + afunbox = afunbox->parent; + + /* + * NB: afunbox can't be null because we are sure to find a function box + * whose level == dnLevel before we would try to walk above the root of + * the funbox tree. See bug 493260 comments 16-18. + * + * Assert but check anyway, to protect future changes that bind eval + * upvars in the parser. + */ + JS_ASSERT(afunbox); + + /* + * If this function is reaching up across an enclosing funarg, then we + * cannot copy dn's value into a flat closure slot (the display stops + * working once the funarg escapes). + */ + if (!afunbox || afunbox->node->isFunArg()) + return false; + + /* + * Reaching up for dn across a generator also means we can't flatten, + * since the generator iterator does not run until later, in general. + * See bug 563034. + */ + if (afunbox->tcflags & TCF_FUN_IS_GENERATOR) + return false; + } + + /* + * If afunbox's function (which is at the same level as dn) is in a loop, + * pessimistically assume the variable initializer may be in the same loop. + * A flat closure would then be unsafe, as the captured variable could be + * assigned after the closure is created. See bug 493232. + */ + if (afunbox->inLoop) + return false; + + /* + * |with| and eval used as an operator defeat lexical scoping: they can be + * used to assign to any in-scope variable. Therefore they must disable + * flat closures that use such upvars. The parser detects these as special + * forms and marks the function heavyweight. + */ + if ((afunbox->parent ? afunbox->parent->tcflags : tcflags) & TCF_FUN_HEAVYWEIGHT) + return false; + + /* + * If afunbox's function is not a lambda, it will be hoisted, so it could + * capture the undefined value that by default initializes var, let, and + * const bindings. And if dn is a function that comes at (meaning a + * function refers to its own name) or strictly after afunbox, we also + * defeat the flat closure optimization for this dn. + */ + JSFunction *afun = (JSFunction *) afunbox->object; + if (!(afun->flags & JSFUN_LAMBDA)) { + if (dn->isBindingForm() || dn->pn_pos >= afunbox->node->pn_pos) + return false; + } + + if (!dn->isInitialized()) + return false; + + JSDefinition::Kind dnKind = dn->kind(); + if (dnKind != JSDefinition::CONST) { + if (dn->isAssigned()) + return false; + + /* + * Any formal could be mutated behind our back via the arguments + * object, so deoptimize if the outer function uses arguments. + * + * In a Function constructor call where the final argument -- the body + * source for the function to create -- contains a nested function + * definition or expression, afunbox->parent will be null. The body + * source might use |arguments| outside of any nested functions it may + * contain, so we have to check the tcflags parameter that was passed + * in from Compiler::compileFunctionBody. + */ + if (dnKind == JSDefinition::ARG && + ((afunbox->parent ? afunbox->parent->tcflags : tcflags) & TCF_FUN_USES_ARGUMENTS)) { + return false; + } + } + + /* + * Check quick-and-dirty dominance relation. Function definitions dominate + * their uses thanks to hoisting. Other binding forms hoist as undefined, + * of course, so check forward-reference and blockid relations. + */ + if (dnKind != JSDefinition::FUNCTION) { + /* + * Watch out for code such as + * + * (function () { + * ... + * var jQuery = ... = function (...) { + * return new jQuery.foo.bar(baz); + * } + * ... + * })(); + * + * where the jQuery variable is not reassigned, but of course is not + * initialized at the time that the would-be-flat closure containing + * the jQuery upvar is formed. + */ + if (dn->pn_pos.end >= afunbox->node->pn_pos.end) + return false; + if (!MinBlockId(afunbox->node, dn->pn_blockid)) + return false; + } + return true; +} + +static void +FlagHeavyweights(JSDefinition *dn, JSFunctionBox *funbox, uint32 *tcflags) +{ + uintN dnLevel = dn->frameLevel(); + + while ((funbox = funbox->parent) != NULL) { + /* + * Notice that funbox->level is the static level of the definition or + * expression of the function parsed into funbox, not the static level + * of its body. Therefore we must add 1 to match dn's level to find the + * funbox whose body contains the dn definition. + */ + if (funbox->level + 1U == dnLevel || (dnLevel == 0 && dn->isLet())) { + funbox->tcflags |= TCF_FUN_HEAVYWEIGHT; + break; + } + funbox->tcflags |= TCF_FUN_ENTRAINS_SCOPES; + } + + if (!funbox && (*tcflags & TCF_IN_FUNCTION)) + *tcflags |= TCF_FUN_HEAVYWEIGHT; +} + +static bool +DeoptimizeUsesWithin(JSDefinition *dn, const TokenPos &pos) +{ + uintN ndeoptimized = 0; + + for (JSParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { + JS_ASSERT(pnu->pn_used); + JS_ASSERT(!pnu->pn_defn); + if (pnu->pn_pos.begin >= pos.begin && pnu->pn_pos.end <= pos.end) { + pnu->pn_dflags |= PND_DEOPTIMIZED; + ++ndeoptimized; + } + } + + return ndeoptimized != 0; +} + +void +Parser::setFunctionKinds(JSFunctionBox *funbox, uint32 *tcflags) +{ +#define FUN_METER(x) JS_FUNCTION_METER(context, x) + + for (;;) { + JSParseNode *fn = funbox->node; + JSParseNode *pn = fn->pn_body; + + if (funbox->kids) { + setFunctionKinds(funbox->kids, tcflags); + + /* + * We've unwound from recursively setting our kids' kinds, which + * also classifies enclosing functions holding upvars referenced in + * those descendants' bodies. So now we can check our "methods". + * + * Despecialize from branded method-identity-based shape to shape- + * or slot-based shape if this function smells like a constructor + * and too many of its methods are *not* joinable null closures + * (i.e., they have one or more upvars fetched via the display). + */ + JSParseNode *pn2 = pn; + if (PN_TYPE(pn2) == TOK_UPVARS) + pn2 = pn2->pn_tree; + if (PN_TYPE(pn2) == TOK_ARGSBODY) + pn2 = pn2->last(); + +#if JS_HAS_EXPR_CLOSURES + if (PN_TYPE(pn2) == TOK_LC) +#endif + if (!(funbox->tcflags & TCF_RETURN_EXPR)) { + uintN methodSets = 0, slowMethodSets = 0; + + for (JSParseNode *method = funbox->methods; method; method = method->pn_link) { + JS_ASSERT(PN_OP(method) == JSOP_LAMBDA || PN_OP(method) == JSOP_LAMBDA_FC); + ++methodSets; + if (!method->pn_funbox->joinable()) + ++slowMethodSets; + } + + if (funbox->shouldUnbrand(methodSets, slowMethodSets)) + funbox->tcflags |= TCF_FUN_UNBRAND_THIS; + } + } + + JSFunction *fun = (JSFunction *) funbox->object; + + JS_ASSERT(FUN_KIND(fun) == JSFUN_INTERPRETED); + + FUN_METER(allfun); + if (funbox->tcflags & TCF_FUN_HEAVYWEIGHT) { + FUN_METER(heavy); + } else if (funbox->inAnyDynamicScope()) { + JS_ASSERT(!FUN_NULL_CLOSURE(fun)); + FUN_METER(indynamicscope); + } else if (pn->pn_type != TOK_UPVARS) { + /* + * No lexical dependencies => null closure, for best performance. + * A null closure needs no scope chain, but alas we've coupled + * principals-finding to scope (for good fundamental reasons, but + * the implementation overloads the parent slot and we should fix + * that). See, e.g., the JSOP_LAMBDA case in jsinterp.cpp. + * + * In more detail: the ES3 spec allows the implementation to create + * "joined function objects", or not, at its discretion. But real- + * world implementations always create unique function objects for + * closures, and this can be detected via mutation. Open question: + * do popular implementations create unique function objects for + * null closures? + * + * FIXME: bug 476950. + */ + FUN_METER(nofreeupvar); + FUN_SET_KIND(fun, JSFUN_NULL_CLOSURE); + } else { + JSAtomList upvars(pn->pn_names); + JS_ASSERT(upvars.count != 0); + + JSAtomListIterator iter(&upvars); + JSAtomListElement *ale; + + if (!fn->isFunArg()) { + /* + * This function is Algol-like, it never escapes. + * + * Check that at least one outer lexical binding was assigned + * to (global variables don't count). This is conservative: we + * could limit assignments to those in the current function, + * but that's too much work. As with flat closures (handled + * below), we optimize for the case where outer bindings are + * not reassigned anywhere. + */ + while ((ale = iter()) != NULL) { + JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); + + if (!lexdep->isFreeVar()) { + JS_ASSERT(lexdep->frameLevel() <= funbox->level); + break; + } + } + + if (!ale) { + FUN_METER(onlyfreevar); + FUN_SET_KIND(fun, JSFUN_NULL_CLOSURE); + } + } else { + uintN nupvars = 0, nflattened = 0; + + /* + * For each lexical dependency from this closure to an outer + * binding, analyze whether it is safe to copy the binding's + * value into a flat closure slot when the closure is formed. + */ + while ((ale = iter()) != NULL) { + JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); + + if (!lexdep->isFreeVar()) { + ++nupvars; + if (CanFlattenUpvar(lexdep, funbox, *tcflags)) { + ++nflattened; + continue; + } + + /* + * FIXME bug 545759: to test nflattened != 0 instead of + * nflattened == nupvars below, we'll need to avoid n^2 + * bugs such as 617430 in uncommenting the following: + * + * if (DeoptimizeUsesWithin(lexdep, funbox->node->pn_body->pn_pos)) + * FlagHeavyweights(lexdep, funbox, tcflags); + * + * For now it's best to avoid this tedious, use-wise + * deoptimization and let fun remain an unoptimized + * closure. This is safe because we leave fun's kind + * set to interpreted, so all functions holding its + * upvars will be flagged as heavyweight. + */ + } + } + + if (nupvars == 0) { + FUN_METER(onlyfreevar); + FUN_SET_KIND(fun, JSFUN_NULL_CLOSURE); + } else if (nflattened == nupvars) { + /* + * We made it all the way through the upvar loop, so it's + * safe to optimize to a flat closure. + */ + FUN_METER(flat); + FUN_SET_KIND(fun, JSFUN_FLAT_CLOSURE); + switch (PN_OP(fn)) { + case JSOP_DEFFUN: + fn->pn_op = JSOP_DEFFUN_FC; + break; + case JSOP_DEFLOCALFUN: + fn->pn_op = JSOP_DEFLOCALFUN_FC; + break; + case JSOP_LAMBDA: + fn->pn_op = JSOP_LAMBDA_FC; + break; + default: + /* js_EmitTree's case TOK_FUNCTION: will select op. */ + JS_ASSERT(PN_OP(fn) == JSOP_NOP); + } + } else { + FUN_METER(badfunarg); + } + } + } + + if (FUN_KIND(fun) == JSFUN_INTERPRETED && pn->pn_type == TOK_UPVARS) { + /* + * One or more upvars cannot be safely snapshot into a flat + * closure's non-reserved slot (see JSOP_GETFCSLOT), so we loop + * again over all upvars, and for each non-free upvar, ensure that + * its containing function has been flagged as heavyweight. + * + * The emitter must see TCF_FUN_HEAVYWEIGHT accurately before + * generating any code for a tree of nested functions. + */ + JSAtomList upvars(pn->pn_names); + JS_ASSERT(upvars.count != 0); + + JSAtomListIterator iter(&upvars); + JSAtomListElement *ale; + + while ((ale = iter()) != NULL) { + JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); + if (!lexdep->isFreeVar()) + FlagHeavyweights(lexdep, funbox, tcflags); + } + } + + if (funbox->joinable()) + fun->setJoinable(); + + funbox = funbox->siblings; + if (!funbox) + break; + } + +#undef FUN_METER +} + +const char js_argument_str[] = "argument"; +const char js_variable_str[] = "variable"; +const char js_unknown_str[] = "unknown"; + +const char * +JSDefinition::kindString(Kind kind) +{ + static const char *table[] = { + js_var_str, js_const_str, js_let_str, + js_function_str, js_argument_str, js_unknown_str + }; + + JS_ASSERT(unsigned(kind) <= unsigned(ARG)); + return table[kind]; +} + +static JSFunctionBox * +EnterFunction(JSParseNode *fn, JSTreeContext *funtc, JSAtom *funAtom = NULL, + uintN lambda = JSFUN_LAMBDA) +{ + JSTreeContext *tc = funtc->parent; + JSFunction *fun = tc->parser->newFunction(tc, funAtom, lambda); + if (!fun) + return NULL; + + /* Create box for fun->object early to protect against last-ditch GC. */ + JSFunctionBox *funbox = tc->parser->newFunctionBox(FUN_OBJECT(fun), fn, tc); + if (!funbox) + return NULL; + + /* Initialize non-default members of funtc. */ + funtc->flags |= funbox->tcflags; + funtc->blockidGen = tc->blockidGen; + if (!GenerateBlockId(funtc, funtc->bodyid)) + return NULL; + funtc->setFunction(fun); + funtc->funbox = funbox; + if (!SetStaticLevel(funtc, tc->staticLevel + 1)) + return NULL; + + return funbox; +} + +static bool +LeaveFunction(JSParseNode *fn, JSTreeContext *funtc, JSAtom *funAtom = NULL, + uintN lambda = JSFUN_LAMBDA) +{ + JSTreeContext *tc = funtc->parent; + tc->blockidGen = funtc->blockidGen; + + JSFunctionBox *funbox = fn->pn_funbox; + funbox->tcflags |= funtc->flags & (TCF_FUN_FLAGS | TCF_COMPILE_N_GO | TCF_RETURN_EXPR); + + fn->pn_dflags |= PND_INITIALIZED; + if (!tc->topStmt || tc->topStmt->type == STMT_BLOCK) + fn->pn_dflags |= PND_BLOCKCHILD; + + /* + * Propagate unresolved lexical names up to tc->lexdeps, and save a copy + * of funtc->lexdeps in a TOK_UPVARS node wrapping the function's formal + * params and body. We do this only if there are lexical dependencies not + * satisfied by the function's declarations, to avoid penalizing functions + * that use only their arguments and other local bindings. + */ + if (funtc->lexdeps.count != 0) { + JSAtomListIterator iter(&funtc->lexdeps); + JSAtomListElement *ale; + int foundCallee = 0; + + while ((ale = iter()) != NULL) { + JSAtom *atom = ALE_ATOM(ale); + JSDefinition *dn = ALE_DEFN(ale); + JS_ASSERT(dn->isPlaceholder()); + + if (atom == funAtom && lambda != 0) { + dn->pn_op = JSOP_CALLEE; + dn->pn_cookie.set(funtc->staticLevel, UpvarCookie::CALLEE_SLOT); + dn->pn_dflags |= PND_BOUND; + + /* + * If this named function expression uses its own name other + * than to call itself, flag this function specially. + */ + if (dn->isFunArg()) + funbox->tcflags |= TCF_FUN_USES_OWN_NAME; + foundCallee = 1; + continue; + } + + if (!(funbox->tcflags & TCF_FUN_SETS_OUTER_NAME) && + dn->isAssigned()) { + /* + * Make sure we do not fail to set TCF_FUN_SETS_OUTER_NAME if + * any use of dn in funtc assigns. See NoteLValue for the easy + * backward-reference case; this is the hard forward-reference + * case where we pay a higher price. + */ + for (JSParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { + if (pnu->isAssigned() && pnu->pn_blockid >= funtc->bodyid) { + funbox->tcflags |= TCF_FUN_SETS_OUTER_NAME; + break; + } + } + } + + JSAtomListElement *outer_ale = tc->decls.lookup(atom); + + /* + * Make sure to deoptimize lexical dependencies that are polluted + * by eval or with, to safely bind globals (see bug 561923). + */ + if (funtc->callsEval() || + (outer_ale && tc->innermostWith && + ALE_DEFN(outer_ale)->pn_pos < tc->innermostWith->pn_pos)) { + DeoptimizeUsesWithin(dn, fn->pn_pos); + } + + if (!outer_ale) + outer_ale = tc->lexdeps.lookup(atom); + if (!outer_ale) { + /* + * Create a new placeholder for our outer lexdep. We could simply re-use + * the inner placeholder, but that introduces subtleties in the case where + * we find a later definition that captures an existing lexdep. For + * example: + * + * function f() { function g() { x; } let x; } + * + * Here, g's TOK_UPVARS node lists the placeholder for x, which must be + * captured by the 'let' declaration later, since 'let's are hoisted. + * Taking g's placeholder as our own would work fine. But consider: + * + * function f() { x; { function g() { x; } let x; } } + * + * Here, the 'let' must not capture all the uses of f's lexdep entry for + * x, but it must capture the x node referred to from g's TOK_UPVARS node. + * Always turning inherited lexdeps into uses of a new outer definition + * allows us to handle both these cases in a natural way. + */ + outer_ale = MakePlaceholder(dn, tc); + } + + JSDefinition *outer_dn = ALE_DEFN(outer_ale); + + /* + * Insert dn's uses list at the front of outer_dn's list. + * + * Without loss of generality or correctness, we allow a dn to + * be in inner and outer lexdeps, since the purpose of lexdeps + * is one-pass coordination of name use and definition across + * functions, and if different dn's are used we'll merge lists + * when leaving the inner function. + * + * The dn == outer_dn case arises with generator expressions + * (see CompExprTransplanter::transplant, the PN_FUNC/PN_NAME + * case), and nowhere else, currently. + */ + if (dn != outer_dn) { + JSParseNode **pnup = &dn->dn_uses; + JSParseNode *pnu; + + while ((pnu = *pnup) != NULL) { + pnu->pn_lexdef = outer_dn; + pnup = &pnu->pn_link; + } + + /* + * Make dn be a use that redirects to outer_dn, because we + * can't replace dn with outer_dn in all the pn_namesets in + * the AST where it may be. Instead we make it forward to + * outer_dn. See JSDefinition::resolve. + */ + *pnup = outer_dn->dn_uses; + outer_dn->dn_uses = dn; + outer_dn->pn_dflags |= dn->pn_dflags & ~PND_PLACEHOLDER; + dn->pn_defn = false; + dn->pn_used = true; + dn->pn_lexdef = outer_dn; + } + + /* Mark the outer dn as escaping. */ + outer_dn->pn_dflags |= PND_CLOSED; + } + + if (funtc->lexdeps.count - foundCallee != 0) { + JSParseNode *body = fn->pn_body; + + fn->pn_body = NameSetNode::create(tc); + if (!fn->pn_body) + return false; + + fn->pn_body->pn_type = TOK_UPVARS; + fn->pn_body->pn_pos = body->pn_pos; + if (foundCallee) + funtc->lexdeps.remove(tc->parser, funAtom); + fn->pn_body->pn_names = funtc->lexdeps; + fn->pn_body->pn_tree = body; + } + + funtc->lexdeps.clear(); + } + + /* + * Check whether any parameters have been assigned within this function. + * In strict mode parameters do not alias arguments[i], and to make the + * arguments object reflect initial parameter values prior to any mutation + * we create it eagerly whenever parameters are (or might, in the case of + * calls to eval) be assigned. + */ + if (funtc->inStrictMode() && funbox->object->getFunctionPrivate()->nargs > 0) { + JSAtomListIterator iter(&funtc->decls); + JSAtomListElement *ale; + + while ((ale = iter()) != NULL) { + JSDefinition *dn = ALE_DEFN(ale); + if (dn->kind() == JSDefinition::ARG && dn->isAssigned()) { + funbox->tcflags |= TCF_FUN_MUTATES_PARAMETER; + break; + } + } + } + + funbox->bindings.transfer(funtc->parser->context, &funtc->bindings); + + return true; +} + +static bool +DefineGlobal(JSParseNode *pn, JSCodeGenerator *cg, JSAtom *atom); + +/* + * FIXME? this Parser method was factored from Parser::functionDef with minimal + * change, hence the funtc ref param and funbox. It probably should match + * functionBody, etc., and use tc and tc->funbox instead of taking explicit + * parameters. + */ +bool +Parser::functionArguments(JSTreeContext &funtc, JSFunctionBox *funbox, JSParseNode **listp) +{ + if (tokenStream.getToken() != TOK_LP) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_BEFORE_FORMAL); + return false; + } + + if (!tokenStream.matchToken(TOK_RP)) { +#if JS_HAS_DESTRUCTURING + JSAtom *duplicatedArg = NULL; + bool destructuringArg = false; + JSParseNode *list = NULL; +#endif + + do { + switch (TokenKind tt = tokenStream.getToken()) { +#if JS_HAS_DESTRUCTURING + case TOK_LB: + case TOK_LC: + { + /* See comment below in the TOK_NAME case. */ + if (duplicatedArg) + goto report_dup_and_destructuring; + destructuringArg = true; + + /* + * A destructuring formal parameter turns into one or more + * local variables initialized from properties of a single + * anonymous positional parameter, so here we must tweak our + * binder and its data. + */ + BindData data; + data.pn = NULL; + data.op = JSOP_DEFVAR; + data.binder = BindDestructuringArg; + JSParseNode *lhs = destructuringExpr(&data, tt); + if (!lhs) + return false; + + /* + * Adjust fun->nargs to count the single anonymous positional + * parameter that is to be destructured. + */ + uint16 slot; + if (!funtc.bindings.addDestructuring(context, &slot)) + return false; + + /* + * Synthesize a destructuring assignment from the single + * anonymous positional parameter into the destructuring + * left-hand-side expression and accumulate it in list. + */ + JSParseNode *rhs = NameNode::create(context->runtime->atomState.emptyAtom, &funtc); + if (!rhs) + return false; + rhs->pn_type = TOK_NAME; + rhs->pn_op = JSOP_GETARG; + rhs->pn_cookie.set(funtc.staticLevel, slot); + rhs->pn_dflags |= PND_BOUND; + + JSParseNode *item = + JSParseNode::newBinaryOrAppend(TOK_ASSIGN, JSOP_NOP, lhs, rhs, &funtc); + if (!item) + return false; + if (!list) { + list = ListNode::create(&funtc); + if (!list) + return false; + list->pn_type = TOK_VAR; + list->makeEmpty(); + *listp = list; + } + list->append(item); + break; + } +#endif /* JS_HAS_DESTRUCTURING */ + + case TOK_NAME: + { + JSAtom *atom = tokenStream.currentToken().t_atom; + +#ifdef JS_HAS_DESTRUCTURING + /* + * ECMA-262 requires us to support duplicate parameter names, + * but if the parameter list includes destructuring, we + * consider the code to have "opted in" to higher standards and + * forbid duplicates. We may see a destructuring parameter + * later, so always note duplicates now. + * + * Duplicates are warned about (strict option) or cause errors + * (strict mode code), but we do those tests in one place + * below, after having parsed the body in case it begins with a + * "use strict"; directive. + * + * NB: Check funtc.decls rather than funtc.bindings, because + * destructuring bindings aren't added to funtc.bindings + * until after all arguments have been parsed. + */ + if (funtc.decls.lookup(atom)) { + duplicatedArg = atom; + if (destructuringArg) + goto report_dup_and_destructuring; + } +#endif + + uint16 slot; + if (!funtc.bindings.addArgument(context, atom, &slot)) + return false; + if (!DefineArg(funbox->node, atom, slot, &funtc)) + return false; + break; + } + + default: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_MISSING_FORMAL); + /* FALL THROUGH */ + case TOK_ERROR: + return false; + +#if JS_HAS_DESTRUCTURING + report_dup_and_destructuring: + JSDefinition *dn = ALE_DEFN(funtc.decls.lookup(duplicatedArg)); + reportErrorNumber(dn, JSREPORT_ERROR, JSMSG_DESTRUCT_DUP_ARG); + return false; +#endif + } + } while (tokenStream.matchToken(TOK_COMMA)); + + if (tokenStream.getToken() != TOK_RP) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_AFTER_FORMAL); + return false; + } + } + + return true; +} + +JSParseNode * +Parser::functionDef(JSAtom *funAtom, FunctionType type, uintN lambda) +{ + /* Make a TOK_FUNCTION node. */ + tokenStream.mungeCurrentToken(TOK_FUNCTION, JSOP_NOP); + JSParseNode *pn = FunctionNode::create(tc); + if (!pn) + return NULL; + pn->pn_body = NULL; + pn->pn_cookie.makeFree(); + + /* + * If a lambda, mark this function as escaping (as a "funarg") unless it is + * immediately applied (we clear PND_FUNARG if so -- see memberExpr). + * + * Treat function sub-statements (non-lambda, non-body-level functions) as + * escaping funargs, since we can't statically analyze their definitions + * and uses. + */ + bool bodyLevel = tc->atBodyLevel(); + pn->pn_dflags = (lambda || !bodyLevel) ? PND_FUNARG : 0; + + /* + * Record names for function statements in tc->decls so we know when to + * avoid optimizing variable references that might name a function. + */ + if (lambda == 0 && funAtom) { + if (JSAtomListElement *ale = tc->decls.lookup(funAtom)) { + JSDefinition *dn = ALE_DEFN(ale); + JSDefinition::Kind dn_kind = dn->kind(); + + JS_ASSERT(!dn->pn_used); + JS_ASSERT(dn->pn_defn); + + if (context->hasStrictOption() || dn_kind == JSDefinition::CONST) { + JSAutoByteString name; + if (!js_AtomToPrintableString(context, funAtom, &name) || + !reportErrorNumber(NULL, + (dn_kind != JSDefinition::CONST) + ? JSREPORT_WARNING | JSREPORT_STRICT + : JSREPORT_ERROR, + JSMSG_REDECLARED_VAR, + JSDefinition::kindString(dn_kind), + name.ptr())) { + return NULL; + } + } + + if (bodyLevel) { + ALE_SET_DEFN(ale, pn); + pn->pn_defn = true; + pn->dn_uses = dn; /* dn->dn_uses is now pn_link */ + + if (!MakeDefIntoUse(dn, pn, funAtom, tc)) + return NULL; + } + } else if (bodyLevel) { + /* + * If this function was used before it was defined, claim the + * pre-created definition node for this function that primaryExpr + * put in tc->lexdeps on first forward reference, and recycle pn. + */ + JSHashEntry **hep; + + ale = tc->lexdeps.rawLookup(funAtom, hep); + if (ale) { + JSDefinition *fn = ALE_DEFN(ale); + + JS_ASSERT(fn->pn_defn); + fn->pn_type = TOK_FUNCTION; + fn->pn_arity = PN_FUNC; + fn->pn_pos.begin = pn->pn_pos.begin; + fn->pn_body = NULL; + fn->pn_cookie.makeFree(); + + tc->lexdeps.rawRemove(tc->parser, ale, hep); + RecycleTree(pn, tc); + pn = fn; + } + + if (!Define(pn, funAtom, tc)) + return NULL; + } + + /* + * A function directly inside another's body needs only a local + * variable to bind its name to its value, and not an activation object + * property (it might also need the activation property, if the outer + * function contains with statements, e.g., but the stack slot wins + * when jsemit.cpp's BindNameToSlot can optimize a JSOP_NAME into a + * JSOP_GETLOCAL bytecode). + */ + if (bodyLevel && tc->inFunction()) { + /* + * Define a local in the outer function so that BindNameToSlot + * can properly optimize accesses. Note that we need a local + * variable, not an argument, for the function statement. Thus + * we add a variable even if a parameter with the given name + * already exists. + */ + uintN index; + switch (tc->bindings.lookup(context, funAtom, &index)) { + case NONE: + case ARGUMENT: + index = tc->bindings.countVars(); + if (!tc->bindings.addVariable(context, funAtom)) + return NULL; + /* FALL THROUGH */ + + case VARIABLE: + pn->pn_cookie.set(tc->staticLevel, index); + pn->pn_dflags |= PND_BOUND; + break; + + default:; + } + } + } + + JSTreeContext *outertc = tc; + + /* Initialize early for possible flags mutation via destructuringExpr. */ + JSTreeContext funtc(tc->parser); + + JSFunctionBox *funbox = EnterFunction(pn, &funtc, funAtom, lambda); + if (!funbox) + return NULL; + + JSFunction *fun = (JSFunction *) funbox->object; + + /* Now parse formal argument list and compute fun->nargs. */ + JSParseNode *prelude = NULL; + if (!functionArguments(funtc, funbox, &prelude)) + return NULL; + + fun->setArgCount(funtc.bindings.countArgs()); + +#if JS_HAS_DESTRUCTURING + /* + * If there were destructuring formal parameters, bind the destructured-to + * local variables now that we've parsed all the regular and destructuring + * formal parameters. Because js::Bindings::add must be called first for + * all ARGUMENTs, then all VARIABLEs and CONSTANTs, and finally all UPVARs, + * we can't bind vars induced by formal parameter destructuring until after + * Parser::functionArguments has returned. + */ + if (prelude) { + JSAtomListIterator iter(&funtc.decls); + + while (JSAtomListElement *ale = iter()) { + JSParseNode *apn = ALE_DEFN(ale); + + /* Filter based on pn_op -- see BindDestructuringArg, above. */ + if (apn->pn_op != JSOP_SETLOCAL) + continue; + + uint16 index = funtc.bindings.countVars(); + if (!BindLocalVariable(context, &funtc, apn->pn_atom, VARIABLE, true)) + return NULL; + apn->pn_cookie.set(funtc.staticLevel, index); + } + } +#endif + + if (type == GETTER && fun->nargs > 0) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ACCESSOR_WRONG_ARGS, + "getter", "no", "s"); + return NULL; + } + if (type == SETTER && fun->nargs != 1) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ACCESSOR_WRONG_ARGS, + "setter", "one", ""); + return NULL; + } + +#if JS_HAS_EXPR_CLOSURES + TokenKind tt = tokenStream.getToken(TSF_OPERAND); + if (tt != TOK_LC) { + tokenStream.ungetToken(); + fun->flags |= JSFUN_EXPR_CLOSURE; + } +#else + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_BODY); +#endif + + JSParseNode *body = functionBody(); + if (!body) + return NULL; + + if (funAtom && !CheckStrictBinding(context, &funtc, funAtom, pn)) + return NULL; + + if (!CheckStrictParameters(context, &funtc)) + return NULL; + +#if JS_HAS_EXPR_CLOSURES + if (tt == TOK_LC) + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY); + else if (lambda == 0 && !MatchOrInsertSemicolon(context, &tokenStream)) + return NULL; +#else + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY); +#endif + pn->pn_pos.end = tokenStream.currentToken().pos.end; + + /* + * Fruit of the poisonous tree: if a closure calls eval, we consider the + * parent to call eval. We need this for two reasons: (1) the Jaegermonkey + * optimizations really need to know if eval is called transitively, and + * (2) in strict mode, eval called transitively requires eager argument + * creation in strict mode parent functions. + * + * For the latter, we really only need to propagate callsEval if both + * functions are strict mode, but we don't lose much by always propagating. + * The only optimization we lose this way is in the case where a function + * is strict, does not mutate arguments, does not call eval directly, but + * calls eval transitively. + */ + if (funtc.callsEval()) + outertc->noteCallsEval(); + +#if JS_HAS_DESTRUCTURING + /* + * If there were destructuring formal parameters, prepend the initializing + * comma expression that we synthesized to body. If the body is a return + * node, we must make a special TOK_SEQ node, to prepend the destructuring + * code without bracing the decompilation of the function body. + */ + if (prelude) { + if (body->pn_arity != PN_LIST) { + JSParseNode *block; + + block = ListNode::create(outertc); + if (!block) + return NULL; + block->pn_type = TOK_SEQ; + block->pn_pos = body->pn_pos; + block->initList(body); + + body = block; + } + + JSParseNode *item = UnaryNode::create(outertc); + if (!item) + return NULL; + + item->pn_type = TOK_SEMI; + item->pn_pos.begin = item->pn_pos.end = body->pn_pos.begin; + item->pn_kid = prelude; + item->pn_next = body->pn_head; + body->pn_head = item; + if (body->pn_tail == &body->pn_head) + body->pn_tail = &item->pn_next; + ++body->pn_count; + body->pn_xflags |= PNX_DESTRUCT; + } +#endif + + /* + * If we collected flags that indicate nested heavyweight functions, or + * this function contains heavyweight-making statements (with statement, + * visible eval call, or assignment to 'arguments'), flag the function as + * heavyweight (requiring a call object per invocation). + */ + if (funtc.flags & TCF_FUN_HEAVYWEIGHT) { + fun->flags |= JSFUN_HEAVYWEIGHT; + outertc->flags |= TCF_FUN_HEAVYWEIGHT; + } else { + /* + * If this function is not at body level of a program or function (i.e. + * it is a function statement that is not a direct child of a program + * or function), then our enclosing function, if any, must be + * heavyweight. + */ + if (!bodyLevel && lambda == 0 && funAtom) + outertc->flags |= TCF_FUN_HEAVYWEIGHT; + } + + JSParseNode *result = pn; + JSOp op = JSOP_NOP; + if (lambda != 0) { + /* + * ECMA ed. 3 standard: function expression, possibly anonymous. + */ + op = JSOP_LAMBDA; + } else if (!funAtom) { + /* + * If this anonymous function definition is *not* embedded within a + * larger expression, we treat it as an expression statement, not as + * a function declaration -- and not as a syntax error (as ECMA-262 + * Edition 3 would have it). Backward compatibility must trump all, + * unless JSOPTION_ANONFUNFIX is set. + */ + result = UnaryNode::create(outertc); + if (!result) + return NULL; + result->pn_type = TOK_SEMI; + result->pn_pos = pn->pn_pos; + result->pn_kid = pn; + op = JSOP_LAMBDA; + } else if (!bodyLevel) { + /* + * ECMA ed. 3 extension: a function expression statement not at the + * top level, e.g., in a compound statement such as the "then" part + * of an "if" statement, binds a closure only if control reaches that + * sub-statement. + */ + op = JSOP_DEFFUN; + outertc->noteMightAliasLocals(); + } + + funbox->kids = funtc.functionList; + + pn->pn_funbox = funbox; + pn->pn_op = op; + if (pn->pn_body) { + pn->pn_body->append(body); + pn->pn_body->pn_pos = body->pn_pos; + } else { + pn->pn_body = body; + } + + if (!outertc->inFunction() && bodyLevel && funAtom && !lambda && outertc->compiling()) { + JS_ASSERT(pn->pn_cookie.isFree()); + if (!DefineGlobal(pn, outertc->asCodeGenerator(), funAtom)) + return false; + } + + pn->pn_blockid = outertc->blockid(); + + if (!LeaveFunction(pn, &funtc, funAtom, lambda)) + return NULL; + + /* If the surrounding function is not strict code, reset the lexer. */ + if (!outertc->inStrictMode()) + tokenStream.setStrictMode(false); + + return result; +} + +JSParseNode * +Parser::functionStmt() +{ + JSAtom *name = NULL; + if (tokenStream.getToken(TSF_KEYWORD_IS_NAME) == TOK_NAME) { + name = tokenStream.currentToken().t_atom; + } else { + if (hasAnonFunFix()) { + /* Extension: accept unnamed function expressions as statements. */ + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); + return NULL; + } + tokenStream.ungetToken(); + } + + /* We forbid function statements in strict mode code. */ + if (!tc->atBodyLevel() && tc->inStrictMode()) { + reportErrorNumber(NULL, JSREPORT_STRICT_MODE_ERROR, JSMSG_STRICT_FUNCTION_STATEMENT); + return NULL; + } + + return functionDef(name, GENERAL, 0); +} + +JSParseNode * +Parser::functionExpr() +{ + JSAtom *name = NULL; + if (tokenStream.getToken(TSF_KEYWORD_IS_NAME) == TOK_NAME) + name = tokenStream.currentToken().t_atom; + else + tokenStream.ungetToken(); + return functionDef(name, GENERAL, JSFUN_LAMBDA); +} + +/* + * Recognize Directive Prologue members and directives. Assuming |pn| is a + * candidate for membership in a directive prologue, recognize directives and + * set |tc|'s flags accordingly. If |pn| is indeed part of a prologue, set its + * |pn_prologue| flag. + * + * Note that the following is a strict mode function: + * + * function foo() { + * "blah" // inserted semi colon + * "blurgh" + * "use\x20loose" + * "use strict" + * } + * + * That is, even though "use\x20loose" can never be a directive, now or in the + * future (because of the hex escape), the Directive Prologue extends through it + * to the "use strict" statement, which is indeed a directive. + */ +bool +Parser::recognizeDirectivePrologue(JSParseNode *pn, bool *isDirectivePrologueMember) +{ + *isDirectivePrologueMember = pn->isStringExprStatement(); + if (!*isDirectivePrologueMember) + return true; + + JSParseNode *kid = pn->pn_kid; + if (kid->isEscapeFreeStringLiteral()) { + /* + * Mark this statement as being a possibly legitimate part of a + * directive prologue, so the byte code emitter won't warn about it + * being useless code. (We mustn't just omit the statement entirely yet, + * as it could be producing the value of an eval or JSScript execution.) + * + * Note that even if the string isn't one we recognize as a directive, + * the emitter still shouldn't flag it as useless, as it could become a + * directive in the future. We don't want to interfere with people + * taking advantage of directive-prologue-enabled features that appear + * in other browsers first. + */ + pn->pn_prologue = true; + + JSAtom *directive = kid->pn_atom; + if (directive == context->runtime->atomState.useStrictAtom) { + /* + * Unfortunately, Directive Prologue members in general may contain + * escapes, even while "use strict" directives may not. Therefore + * we must check whether an octal character escape has been seen in + * any previous directives whenever we encounter a "use strict" + * directive, so that the octal escape is properly treated as a + * syntax error. An example of this case: + * + * function error() + * { + * "\145"; // octal escape + * "use strict"; // retroactively makes "\145" a syntax error + * } + */ + if (tokenStream.hasOctalCharacterEscape()) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_DEPRECATED_OCTAL); + return false; + } + + tc->flags |= TCF_STRICT_MODE_CODE; + tokenStream.setStrictMode(); + } + } + return true; +} + +/* + * Parse the statements in a block, creating a TOK_LC node that lists the + * statements' trees. If called from block-parsing code, the caller must + * match { before and } after. + */ +JSParseNode * +Parser::statements() +{ + JSParseNode *pn, *pn2, *saveBlock; + TokenKind tt; + + JS_CHECK_RECURSION(context, return NULL); + + pn = ListNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_LC; + pn->makeEmpty(); + pn->pn_blockid = tc->blockid(); + saveBlock = tc->blockNode; + tc->blockNode = pn; + + bool inDirectivePrologue = tc->atBodyLevel(); + tokenStream.setOctalCharacterEscape(false); + for (;;) { + tt = tokenStream.peekToken(TSF_OPERAND); + if (tt <= TOK_EOF || tt == TOK_RC) { + if (tt == TOK_ERROR) { + if (tokenStream.isEOF()) + tokenStream.setUnexpectedEOF(); + return NULL; + } + break; + } + pn2 = statement(); + if (!pn2) { + if (tokenStream.isEOF()) + tokenStream.setUnexpectedEOF(); + return NULL; + } + + if (inDirectivePrologue && !recognizeDirectivePrologue(pn2, &inDirectivePrologue)) + return NULL; + + if (pn2->pn_type == TOK_FUNCTION) { + /* + * PNX_FUNCDEFS notifies the emitter that the block contains body- + * level function definitions that should be processed before the + * rest of nodes. + * + * TCF_HAS_FUNCTION_STMT is for the TOK_LC case in Statement. It + * is relevant only for function definitions not at body-level, + * which we call function statements. + */ + if (tc->atBodyLevel()) + pn->pn_xflags |= PNX_FUNCDEFS; + else + tc->flags |= TCF_HAS_FUNCTION_STMT; + } + pn->append(pn2); + } + + /* + * Handle the case where there was a let declaration under this block. If + * it replaced tc->blockNode with a new block node then we must refresh pn + * and then restore tc->blockNode. + */ + if (tc->blockNode != pn) + pn = tc->blockNode; + tc->blockNode = saveBlock; + + pn->pn_pos.end = tokenStream.currentToken().pos.end; + return pn; +} + +JSParseNode * +Parser::condition() +{ + JSParseNode *pn; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); + pn = parenExpr(); + if (!pn) + return NULL; + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); + + /* Check for (a = b) and warn about possible (a == b) mistype. */ + if (pn->pn_type == TOK_ASSIGN && + pn->pn_op == JSOP_NOP && + !pn->pn_parens && + !reportErrorNumber(NULL, JSREPORT_WARNING | JSREPORT_STRICT, JSMSG_EQUAL_AS_ASSIGN, "")) { + return NULL; + } + return pn; +} + +static JSBool +MatchLabel(JSContext *cx, TokenStream *ts, JSParseNode *pn) +{ + JSAtom *label; + TokenKind tt; + + tt = ts->peekTokenSameLine(); + if (tt == TOK_ERROR) + return JS_FALSE; + if (tt == TOK_NAME) { + (void) ts->getToken(); + label = ts->currentToken().t_atom; + } else { + label = NULL; + } + pn->pn_atom = label; + return JS_TRUE; +} + +static JSBool +BindLet(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc) +{ + JSParseNode *pn; + JSObject *blockObj; + JSAtomListElement *ale; + jsint n; + + /* + * Body-level 'let' is the same as 'var' currently -- this may change in a + * successor standard to ES5 that specifies 'let'. + */ + JS_ASSERT(!tc->atBodyLevel()); + + pn = data->pn; + if (!CheckStrictBinding(cx, tc, atom, pn)) + return false; + + blockObj = tc->blockChain(); + ale = tc->decls.lookup(atom); + if (ale && ALE_DEFN(ale)->pn_blockid == tc->blockid()) { + JSAutoByteString name; + if (js_AtomToPrintableString(cx, atom, &name)) { + ReportCompileErrorNumber(cx, TS(tc->parser), pn, + JSREPORT_ERROR, JSMSG_REDECLARED_VAR, + (ale && ALE_DEFN(ale)->isConst()) + ? js_const_str + : js_variable_str, + name.ptr()); + } + return false; + } + + n = OBJ_BLOCK_COUNT(cx, blockObj); + if (n == JS_BIT(16)) { + ReportCompileErrorNumber(cx, TS(tc->parser), pn, + JSREPORT_ERROR, data->let.overflow); + return false; + } + + /* + * Pass push = true to Define so it pushes an ale ahead of any outer scope. + * This is balanced by PopStatement, defined immediately below. + */ + if (!Define(pn, atom, tc, true)) + return false; + + /* + * Assign block-local index to pn->pn_cookie right away, encoding it as an + * upvar cookie whose skip tells the current static level. The emitter will + * adjust the node's slot based on its stack depth model -- and, for global + * and eval code, Compiler::compileScript will adjust the slot again to + * include script->nfixed. + */ + pn->pn_op = JSOP_GETLOCAL; + pn->pn_cookie.set(tc->staticLevel, uint16(n)); + pn->pn_dflags |= PND_LET | PND_BOUND; + + /* + * Define the let binding's property before storing pn in the the binding's + * slot indexed by n off the class-reserved slot base. + */ + const Shape *shape = blockObj->defineBlockVariable(cx, ATOM_TO_JSID(atom), n); + if (!shape) + return false; + + /* + * Store pn temporarily in what would be shape-mapped slots in a cloned + * block object (once the prototype's final population is known, after all + * 'let' bindings for this block have been parsed). We free these slots in + * jsemit.cpp:EmitEnterBlock so they don't tie up unused space in the so- + * called "static" prototype Block. + */ + blockObj->setSlot(shape->slot, PrivateValue(pn)); + return true; +} + +static void +PopStatement(JSTreeContext *tc) +{ + JSStmtInfo *stmt = tc->topStmt; + + if (stmt->flags & SIF_SCOPE) { + JSObject *obj = stmt->blockBox->object; + JS_ASSERT(!obj->isClonedBlock()); + + for (Shape::Range r = obj->lastProperty()->all(); !r.empty(); r.popFront()) { + JSAtom *atom = JSID_TO_ATOM(r.front().id); + + /* Beware the empty destructuring dummy. */ + if (atom == tc->parser->context->runtime->atomState.emptyAtom) + continue; + tc->decls.remove(tc->parser, atom); + } + } + js_PopStatement(tc); +} + +static inline bool +OuterLet(JSTreeContext *tc, JSStmtInfo *stmt, JSAtom *atom) +{ + while (stmt->downScope) { + stmt = js_LexicalLookup(tc, atom, NULL, stmt->downScope); + if (!stmt) + return false; + if (stmt->type == STMT_BLOCK) + return true; + } + return false; +} + +/* + * If we are generating global or eval-called-from-global code, bind a "gvar" + * here, as soon as possible. The JSOP_GETGVAR, etc., ops speed up interpreted + * global variable access by memoizing name-to-slot mappings during execution + * of the script prolog (via JSOP_DEFVAR/JSOP_DEFCONST). If the memoization + * can't be done due to a pre-existing property of the same name as the var or + * const but incompatible attributes/getter/setter/etc, these ops devolve to + * JSOP_NAME, etc. + * + * For now, don't try to lookup eval frame variables at compile time. This is + * sub-optimal: we could handle eval-called-from-global-code gvars since eval + * gets its own script and frame. The eval-from-function-code case is harder, + * since functions do not atomize gvars and then reserve their atom indexes as + * stack frame slots. + */ +static bool +DefineGlobal(JSParseNode *pn, JSCodeGenerator *cg, JSAtom *atom) +{ + GlobalScope *globalScope = cg->compiler()->globalScope; + JSObject *globalObj = globalScope->globalObj; + + if (!cg->compileAndGo() || !globalObj || cg->compilingForEval()) + return true; + + JSAtomListElement *ale = globalScope->names.lookup(atom); + if (!ale) { + JSContext *cx = cg->parser->context; + + JSObject *holder; + JSProperty *prop; + if (!globalObj->lookupProperty(cx, ATOM_TO_JSID(atom), &holder, &prop)) + return false; + + JSFunctionBox *funbox = (pn->pn_type == TOK_FUNCTION) ? pn->pn_funbox : NULL; + + GlobalScope::GlobalDef def; + if (prop) { + /* + * A few cases where we don't bother aggressively caching: + * 1) Function value changes. + * 2) Configurable properties. + * 3) Properties without slots, or with getters/setters. + */ + const Shape *shape = (const Shape *)prop; + if (funbox || + globalObj != holder || + shape->configurable() || + !shape->hasSlot() || + !shape->hasDefaultGetterOrIsMethod() || + !shape->hasDefaultSetter()) { + return true; + } + + def = GlobalScope::GlobalDef(shape->slot); + } else { + def = GlobalScope::GlobalDef(atom, funbox); + } + + if (!globalScope->defs.append(def)) + return false; + + ale = globalScope->names.add(cg->parser, atom); + if (!ale) + return false; + + JS_ASSERT(ALE_INDEX(ale) == globalScope->defs.length() - 1); + } else { + /* + * Functions can be redeclared, and the last one takes effect. Check + * for this and make sure to rewrite the definition. + * + * Note: This could overwrite an existing variable declaration, for + * example: + * var c = [] + * function c() { } + * + * This rewrite is allowed because the function will be statically + * hoisted to the top of the script, and the |c = []| will just + * overwrite it at runtime. + */ + if (pn->pn_type == TOK_FUNCTION) { + JS_ASSERT(pn->pn_arity = PN_FUNC); + uint32 index = ALE_INDEX(ale); + globalScope->defs[index].funbox = pn->pn_funbox; + } + } + + pn->pn_dflags |= PND_GVAR; + + return true; +} + +static bool +BindTopLevelVar(JSContext *cx, BindData *data, JSAtomListElement *ale, JSParseNode *pn, + JSAtom *varname, JSTreeContext *tc) +{ + JS_ASSERT(pn->pn_op == JSOP_NAME); + JS_ASSERT(!tc->inFunction()); + + /* There's no need to optimize bindings if we're not compiling code. */ + if (!tc->compiling()) + return true; + + /* + * Bindings at top level in eval code aren't like bindings at top level in + * regular code, and we must handle them specially. + */ + if (tc->parser->callerFrame) { + /* + * If the eval code is not strict mode code, such bindings are created + * from scratch in the the caller's environment (if the eval is direct) + * or in the global environment (if the eval is indirect) -- and they + * can be deleted. Therefore we can't bind early. + */ + if (!tc->inStrictMode()) + return true; + + /* + * But if the eval code is strict mode code, bindings are added to a + * new environment specifically for that eval code's compilation, and + * they can't be deleted. Thus strict mode eval code does not affect + * the caller's environment, and we can bind such names early. (But + * note: strict mode eval code can still affect the global environment + * by performing an indirect eval of non-strict mode code.) + * + * However, optimizing such bindings requires either precarious + * type-punning or, ideally, a new kind of Call object specifically for + * strict mode eval frames. Further, strict mode eval is not (yet) + * common. So for now (until we rewrite the scope chain to not use + * objects?) just disable optimizations for top-level names in eval + * code. + */ + return true; + } + + if (pn->pn_dflags & PND_CONST) + return true; + + /* + * If this is a global variable, we're compile-and-go, and a global object + * is present, try to bake in either an already available slot or a + * predicted slot that will be defined after compiling is completed. + */ + return DefineGlobal(pn, tc->asCodeGenerator(), pn->pn_atom); +} + +static bool +BindFunctionLocal(JSContext *cx, BindData *data, JSAtomListElement *ale, JSParseNode *pn, + JSAtom *name, JSTreeContext *tc) +{ + JS_ASSERT(tc->inFunction()); + + if (name == cx->runtime->atomState.argumentsAtom) { + pn->pn_op = JSOP_ARGUMENTS; + pn->pn_dflags |= PND_BOUND; + return true; + } + + BindingKind kind = tc->bindings.lookup(cx, name, NULL); + if (kind == NONE) { + /* + * Property not found in current variable scope: we have not seen this + * variable before, so bind a new local variable for it. Any locals + * declared in a with statement body are handled at runtime, by script + * prolog JSOP_DEFVAR opcodes generated for global and heavyweight- + * function-local vars. + */ + kind = (data->op == JSOP_DEFCONST) ? CONSTANT : VARIABLE; + + uintN index = tc->bindings.countVars(); + if (!BindLocalVariable(cx, tc, name, kind, false)) + return false; + pn->pn_op = JSOP_GETLOCAL; + pn->pn_cookie.set(tc->staticLevel, index); + pn->pn_dflags |= PND_BOUND; + return true; + } + + if (kind == ARGUMENT) { + JS_ASSERT(tc->inFunction()); + JS_ASSERT(ale && ALE_DEFN(ale)->kind() == JSDefinition::ARG); + } else { + JS_ASSERT(kind == VARIABLE || kind == CONSTANT); + } + + return true; +} + +static JSBool +BindVarOrConst(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc) +{ + JSParseNode *pn = data->pn; + + /* Default best op for pn is JSOP_NAME; we'll try to improve below. */ + pn->pn_op = JSOP_NAME; + + if (!CheckStrictBinding(cx, tc, atom, pn)) + return false; + + JSStmtInfo *stmt = js_LexicalLookup(tc, atom, NULL); + + if (stmt && stmt->type == STMT_WITH) { + data->fresh = false; + pn->pn_dflags |= PND_DEOPTIMIZED; + tc->noteMightAliasLocals(); + return true; + } + + JSAtomListElement *ale = tc->decls.lookup(atom); + JSOp op = data->op; + + if (stmt || ale) { + JSDefinition *dn = ale ? ALE_DEFN(ale) : NULL; + JSDefinition::Kind dn_kind = dn ? dn->kind() : JSDefinition::VAR; + + if (dn_kind == JSDefinition::ARG) { + JSAutoByteString name; + if (!js_AtomToPrintableString(cx, atom, &name)) + return JS_FALSE; + + if (op == JSOP_DEFCONST) { + ReportCompileErrorNumber(cx, TS(tc->parser), pn, + JSREPORT_ERROR, JSMSG_REDECLARED_PARAM, + name.ptr()); + return JS_FALSE; + } + if (!ReportCompileErrorNumber(cx, TS(tc->parser), pn, + JSREPORT_WARNING | JSREPORT_STRICT, + JSMSG_VAR_HIDES_ARG, name.ptr())) { + return JS_FALSE; + } + } else { + bool error = (op == JSOP_DEFCONST || + dn_kind == JSDefinition::CONST || + (dn_kind == JSDefinition::LET && + (stmt->type != STMT_CATCH || OuterLet(tc, stmt, atom)))); + + if (cx->hasStrictOption() + ? op != JSOP_DEFVAR || dn_kind != JSDefinition::VAR + : error) { + JSAutoByteString name; + if (!js_AtomToPrintableString(cx, atom, &name) || + !ReportCompileErrorNumber(cx, TS(tc->parser), pn, + !error + ? JSREPORT_WARNING | JSREPORT_STRICT + : JSREPORT_ERROR, + JSMSG_REDECLARED_VAR, + JSDefinition::kindString(dn_kind), + name.ptr())) { + return JS_FALSE; + } + } + } + } + + if (!ale) { + if (!Define(pn, atom, tc)) + return JS_FALSE; + } else { + /* + * A var declaration never recreates an existing binding, it restates + * it and possibly reinitializes its value. Beware that if pn becomes a + * use of ALE_DEFN(ale), and if we have an initializer for this var or + * const (typically a const would ;-), then pn must be rewritten into a + * TOK_ASSIGN node. See Variables, further below. + * + * A case such as let (x = 1) { var x = 2; print(x); } is even harder. + * There the x definition is hoisted but the x = 2 assignment mutates + * the block-local binding of x. + */ + JSDefinition *dn = ALE_DEFN(ale); + + data->fresh = false; + + if (!pn->pn_used) { + /* Make pnu be a fresh name node that uses dn. */ + JSParseNode *pnu = pn; + + if (pn->pn_defn) { + pnu = NameNode::create(atom, tc); + if (!pnu) + return JS_FALSE; + } + + LinkUseToDef(pnu, dn, tc); + pnu->pn_op = JSOP_NAME; + } + + while (dn->kind() == JSDefinition::LET) { + do { + ale = ALE_NEXT(ale); + } while (ale && ALE_ATOM(ale) != atom); + if (!ale) + break; + dn = ALE_DEFN(ale); + } + + if (ale) { + JS_ASSERT_IF(data->op == JSOP_DEFCONST, + dn->kind() == JSDefinition::CONST); + return JS_TRUE; + } + + /* + * A var or const that is shadowed by one or more let bindings of the + * same name, but that has not been declared until this point, must be + * hoisted above the let bindings. + */ + if (!pn->pn_defn) { + JSHashEntry **hep; + + ale = tc->lexdeps.rawLookup(atom, hep); + if (ale) { + pn = ALE_DEFN(ale); + tc->lexdeps.rawRemove(tc->parser, ale, hep); + } else { + JSParseNode *pn2 = NameNode::create(atom, tc); + if (!pn2) + return JS_FALSE; + + /* The token stream may be past the location for pn. */ + pn2->pn_type = TOK_NAME; + pn2->pn_pos = pn->pn_pos; + pn = pn2; + } + pn->pn_op = JSOP_NAME; + } + + ale = tc->decls.add(tc->parser, atom, JSAtomList::HOIST); + if (!ale) + return JS_FALSE; + ALE_SET_DEFN(ale, pn); + pn->pn_defn = true; + pn->pn_dflags &= ~PND_PLACEHOLDER; + } + + if (data->op == JSOP_DEFCONST) + pn->pn_dflags |= PND_CONST; + + if (tc->inFunction()) + return BindFunctionLocal(cx, data, ale, pn, atom, tc); + + return BindTopLevelVar(cx, data, ale, pn, atom, tc); +} + +static bool +MakeSetCall(JSContext *cx, JSParseNode *pn, JSTreeContext *tc, uintN msg) +{ + JS_ASSERT(pn->pn_arity == PN_LIST); + JS_ASSERT(pn->pn_op == JSOP_CALL || pn->pn_op == JSOP_EVAL || + pn->pn_op == JSOP_FUNCALL || pn->pn_op == JSOP_FUNAPPLY); + if (!ReportStrictModeError(cx, TS(tc->parser), tc, pn, msg)) + return false; + + JSParseNode *pn2 = pn->pn_head; + if (pn2->pn_type == TOK_FUNCTION && (pn2->pn_funbox->tcflags & TCF_GENEXP_LAMBDA)) { + ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, msg); + return false; + } + pn->pn_xflags |= PNX_SETCALL; + return true; +} + +static void +NoteLValue(JSContext *cx, JSParseNode *pn, JSTreeContext *tc, uintN dflag = PND_ASSIGNED) +{ + if (pn->pn_used) { + JSDefinition *dn = pn->pn_lexdef; + + /* + * Save the win of PND_INITIALIZED if we can prove 'var x;' and 'x = y' + * occur as direct kids of the same block with no forward refs to x. + */ + if (!(dn->pn_dflags & (PND_INITIALIZED | PND_CONST | PND_PLACEHOLDER)) && + dn->isBlockChild() && + pn->isBlockChild() && + dn->pn_blockid == pn->pn_blockid && + dn->pn_pos.end <= pn->pn_pos.begin && + dn->dn_uses == pn) { + dflag = PND_INITIALIZED; + } + + dn->pn_dflags |= dflag; + + if (dn->pn_cookie.isFree() || dn->frameLevel() < tc->staticLevel) + tc->flags |= TCF_FUN_SETS_OUTER_NAME; + } + + pn->pn_dflags |= dflag; + + /* + * Both arguments and the enclosing function's name are immutable bindings + * in ES5, so assignments to them must do nothing or throw a TypeError + * depending on code strictness. Assignment to arguments is a syntax error + * in strict mode and thus cannot happen. Outside strict mode, we optimize + * away assignment to the function name. For assignment to function name + * to fail in strict mode, we must have a binding for it in the scope + * chain; we ensure this happens by making such functions heavyweight. + */ + JSAtom *lname = pn->pn_atom; + if (lname == cx->runtime->atomState.argumentsAtom || + (tc->inFunction() && lname == tc->fun()->atom)) { + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } +} + +#if JS_HAS_DESTRUCTURING + +static JSBool +BindDestructuringVar(JSContext *cx, BindData *data, JSParseNode *pn, + JSTreeContext *tc) +{ + JSAtom *atom; + + /* + * Destructuring is a form of assignment, so just as for an initialized + * simple variable, we must check for assignment to 'arguments' and flag + * the enclosing function (if any) as heavyweight. + */ + JS_ASSERT(pn->pn_type == TOK_NAME); + atom = pn->pn_atom; + if (atom == cx->runtime->atomState.argumentsAtom) + tc->flags |= TCF_FUN_HEAVYWEIGHT; + + data->pn = pn; + if (!data->binder(cx, data, atom, tc)) + return JS_FALSE; + + /* + * Select the appropriate name-setting opcode, respecting eager selection + * done by the data->binder function. + */ + if (pn->pn_dflags & PND_BOUND) { + JS_ASSERT(!(pn->pn_dflags & PND_GVAR)); + pn->pn_op = (pn->pn_op == JSOP_ARGUMENTS) + ? JSOP_SETNAME + : JSOP_SETLOCAL; + } else { + pn->pn_op = (data->op == JSOP_DEFCONST) + ? JSOP_SETCONST + : JSOP_SETNAME; + } + + if (data->op == JSOP_DEFCONST) + pn->pn_dflags |= PND_CONST; + + NoteLValue(cx, pn, tc, PND_INITIALIZED); + return JS_TRUE; +} + +/* + * Here, we are destructuring {... P: Q, ...} = R, where P is any id, Q is any + * LHS expression except a destructuring initialiser, and R is on the stack. + * Because R is already evaluated, the usual LHS-specialized bytecodes won't + * work. After pushing R[P] we need to evaluate Q's "reference base" QB and + * then push its property name QN. At this point the stack looks like + * + * [... R, R[P], QB, QN] + * + * We need to set QB[QN] = R[P]. This is a job for JSOP_ENUMELEM, which takes + * its operands with left-hand side above right-hand side: + * + * [rval, lval, xval] + * + * and pops all three values, setting lval[xval] = rval. But we cannot select + * JSOP_ENUMELEM yet, because the LHS may turn out to be an arg or local var, + * which can be optimized further. So we select JSOP_SETNAME. + */ +static JSBool +BindDestructuringLHS(JSContext *cx, JSParseNode *pn, JSTreeContext *tc) +{ + switch (pn->pn_type) { + case TOK_NAME: + NoteLValue(cx, pn, tc); + /* FALL THROUGH */ + + case TOK_DOT: + case TOK_LB: + /* + * We may be called on a name node that has already been specialized, + * in the very weird and ECMA-262-required "for (var [x] = i in o) ..." + * case. See bug 558633. + */ + if (!(js_CodeSpec[pn->pn_op].format & JOF_SET)) + pn->pn_op = JSOP_SETNAME; + break; + + case TOK_LP: + if (!MakeSetCall(cx, pn, tc, JSMSG_BAD_LEFTSIDE_OF_ASS)) + return JS_FALSE; + break; + +#if JS_HAS_XML_SUPPORT + case TOK_UNARYOP: + if (pn->pn_op == JSOP_XMLNAME) { + pn->pn_op = JSOP_BINDXMLNAME; + break; + } + /* FALL THROUGH */ +#endif + + default: + ReportCompileErrorNumber(cx, TS(tc->parser), pn, + JSREPORT_ERROR, JSMSG_BAD_LEFTSIDE_OF_ASS); + return JS_FALSE; + } + + return JS_TRUE; +} + +typedef struct FindPropValData { + uint32 numvars; /* # of destructuring vars in left side */ + uint32 maxstep; /* max # of steps searching right side */ + JSDHashTable table; /* hash table for O(1) right side search */ +} FindPropValData; + +typedef struct FindPropValEntry { + JSDHashEntryHdr hdr; + JSParseNode *pnkey; + JSParseNode *pnval; +} FindPropValEntry; + +#define ASSERT_VALID_PROPERTY_KEY(pnkey) \ + JS_ASSERT(((pnkey)->pn_arity == PN_NULLARY && \ + ((pnkey)->pn_type == TOK_NUMBER || \ + (pnkey)->pn_type == TOK_STRING || \ + (pnkey)->pn_type == TOK_NAME)) || \ + ((pnkey)->pn_arity == PN_NAME && (pnkey)->pn_type == TOK_NAME)) + +static JSDHashNumber +HashFindPropValKey(JSDHashTable *table, const void *key) +{ + const JSParseNode *pnkey = (const JSParseNode *)key; + + ASSERT_VALID_PROPERTY_KEY(pnkey); + return (pnkey->pn_type == TOK_NUMBER) + ? (JSDHashNumber) JS_HASH_DOUBLE(pnkey->pn_dval) + : ATOM_HASH(pnkey->pn_atom); +} + +static JSBool +MatchFindPropValEntry(JSDHashTable *table, + const JSDHashEntryHdr *entry, + const void *key) +{ + const FindPropValEntry *fpve = (const FindPropValEntry *)entry; + const JSParseNode *pnkey = (const JSParseNode *)key; + + ASSERT_VALID_PROPERTY_KEY(pnkey); + return pnkey->pn_type == fpve->pnkey->pn_type && + ((pnkey->pn_type == TOK_NUMBER) + ? pnkey->pn_dval == fpve->pnkey->pn_dval + : pnkey->pn_atom == fpve->pnkey->pn_atom); +} + +static const JSDHashTableOps FindPropValOps = { + JS_DHashAllocTable, + JS_DHashFreeTable, + HashFindPropValKey, + MatchFindPropValEntry, + JS_DHashMoveEntryStub, + JS_DHashClearEntryStub, + JS_DHashFinalizeStub, + NULL +}; + +#define STEP_HASH_THRESHOLD 10 +#define BIG_DESTRUCTURING 5 +#define BIG_OBJECT_INIT 20 + +static JSParseNode * +FindPropertyValue(JSParseNode *pn, JSParseNode *pnid, FindPropValData *data) +{ + FindPropValEntry *entry; + JSParseNode *pnhit, *pnhead, *pnprop, *pnkey; + uint32 step; + + /* If we have a hash table, use it as the sole source of truth. */ + if (data->table.ops) { + entry = (FindPropValEntry *) + JS_DHashTableOperate(&data->table, pnid, JS_DHASH_LOOKUP); + return JS_DHASH_ENTRY_IS_BUSY(&entry->hdr) ? entry->pnval : NULL; + } + + /* If pn is not an object initialiser node, we can't do anything here. */ + if (pn->pn_type != TOK_RC) + return NULL; + + /* + * We must search all the way through pn's list, to handle the case of an + * id duplicated for two or more property initialisers. + */ + pnhit = NULL; + step = 0; + ASSERT_VALID_PROPERTY_KEY(pnid); + pnhead = pn->pn_head; + if (pnid->pn_type == TOK_NUMBER) { + for (pnprop = pnhead; pnprop; pnprop = pnprop->pn_next) { + JS_ASSERT(pnprop->pn_type == TOK_COLON); + if (pnprop->pn_op == JSOP_NOP) { + pnkey = pnprop->pn_left; + ASSERT_VALID_PROPERTY_KEY(pnkey); + if (pnkey->pn_type == TOK_NUMBER && + pnkey->pn_dval == pnid->pn_dval) { + pnhit = pnprop; + } + ++step; + } + } + } else { + for (pnprop = pnhead; pnprop; pnprop = pnprop->pn_next) { + JS_ASSERT(pnprop->pn_type == TOK_COLON); + if (pnprop->pn_op == JSOP_NOP) { + pnkey = pnprop->pn_left; + ASSERT_VALID_PROPERTY_KEY(pnkey); + if (pnkey->pn_type == pnid->pn_type && + pnkey->pn_atom == pnid->pn_atom) { + pnhit = pnprop; + } + ++step; + } + } + } + if (!pnhit) + return NULL; + + /* Hit via full search -- see whether it's time to create the hash table. */ + JS_ASSERT(!data->table.ops); + if (step > data->maxstep) { + data->maxstep = step; + if (step >= STEP_HASH_THRESHOLD && + data->numvars >= BIG_DESTRUCTURING && + pn->pn_count >= BIG_OBJECT_INIT && + JS_DHashTableInit(&data->table, &FindPropValOps, pn, + sizeof(FindPropValEntry), + JS_DHASH_DEFAULT_CAPACITY(pn->pn_count))) + { + for (pn = pnhead; pn; pn = pn->pn_next) { + JS_ASSERT(pnprop->pn_type == TOK_COLON); + ASSERT_VALID_PROPERTY_KEY(pn->pn_left); + entry = (FindPropValEntry *) + JS_DHashTableOperate(&data->table, pn->pn_left, + JS_DHASH_ADD); + entry->pnval = pn->pn_right; + } + } + } + return pnhit->pn_right; +} + +/* + * Destructuring patterns can appear in two kinds of contexts: + * + * - assignment-like: assignment expressions and |for| loop heads. In + * these cases, the patterns' property value positions can be + * arbitrary lvalue expressions; the destructuring is just a fancy + * assignment. + * + * - declaration-like: |var| and |let| declarations, functions' formal + * parameter lists, |catch| clauses, and comprehension tails. In + * these cases, the patterns' property value positions must be + * simple names; the destructuring defines them as new variables. + * + * In both cases, other code parses the pattern as an arbitrary + * primaryExpr, and then, here in CheckDestructuring, verify that the + * tree is a valid destructuring expression. + * + * In assignment-like contexts, we parse the pattern with the + * TCF_DECL_DESTRUCTURING flag clear, so the lvalue expressions in the + * pattern are parsed normally. primaryExpr links variable references + * into the appropriate use chains; creates placeholder definitions; + * and so on. CheckDestructuring is called with |data| NULL (since we + * won't be binding any new names), and we specialize lvalues as + * appropriate. If right is NULL, we just check for well-formed lvalues. + * + * In declaration-like contexts, the normal variable reference + * processing would just be an obstruction, because we're going to + * define the names that appear in the property value positions as new + * variables anyway. In this case, we parse the pattern with + * TCF_DECL_DESTRUCTURING set, which directs primaryExpr to leave + * whatever name nodes it creates unconnected. Then, here in + * CheckDestructuring, we require the pattern's property value + * positions to be simple names, and define them as appropriate to the + * context. For these calls, |data| points to the right sort of + * BindData. + * + * See also UndominateInitializers, immediately below. If you change + * either of these functions, you might have to change the other to + * match. + */ +static JSBool +CheckDestructuring(JSContext *cx, BindData *data, + JSParseNode *left, JSParseNode *right, + JSTreeContext *tc) +{ + JSBool ok; + FindPropValData fpvd; + JSParseNode *lhs, *rhs, *pn, *pn2; + + if (left->pn_type == TOK_ARRAYCOMP) { + ReportCompileErrorNumber(cx, TS(tc->parser), left, JSREPORT_ERROR, + JSMSG_ARRAY_COMP_LEFTSIDE); + return JS_FALSE; + } + +#if JS_HAS_DESTRUCTURING_SHORTHAND + if (right && right->pn_arity == PN_LIST && (right->pn_xflags & PNX_DESTRUCT)) { + ReportCompileErrorNumber(cx, TS(tc->parser), right, JSREPORT_ERROR, + JSMSG_BAD_OBJECT_INIT); + return JS_FALSE; + } +#endif + + fpvd.table.ops = NULL; + lhs = left->pn_head; + if (left->pn_type == TOK_RB) { + rhs = (right && right->pn_type == left->pn_type) + ? right->pn_head + : NULL; + + while (lhs) { + pn = lhs, pn2 = rhs; + + /* Nullary comma is an elision; binary comma is an expression.*/ + if (pn->pn_type != TOK_COMMA || pn->pn_arity != PN_NULLARY) { + if (pn->pn_type == TOK_RB || pn->pn_type == TOK_RC) { + ok = CheckDestructuring(cx, data, pn, pn2, tc); + } else { + if (data) { + if (pn->pn_type != TOK_NAME) + goto no_var_name; + + ok = BindDestructuringVar(cx, data, pn, tc); + } else { + ok = BindDestructuringLHS(cx, pn, tc); + } + } + if (!ok) + goto out; + } + + lhs = lhs->pn_next; + if (rhs) + rhs = rhs->pn_next; + } + } else { + JS_ASSERT(left->pn_type == TOK_RC); + fpvd.numvars = left->pn_count; + fpvd.maxstep = 0; + rhs = NULL; + + while (lhs) { + JS_ASSERT(lhs->pn_type == TOK_COLON); + pn = lhs->pn_right; + + if (pn->pn_type == TOK_RB || pn->pn_type == TOK_RC) { + if (right) + rhs = FindPropertyValue(right, lhs->pn_left, &fpvd); + ok = CheckDestructuring(cx, data, pn, rhs, tc); + } else if (data) { + if (pn->pn_type != TOK_NAME) + goto no_var_name; + + ok = BindDestructuringVar(cx, data, pn, tc); + } else { + ok = BindDestructuringLHS(cx, pn, tc); + } + if (!ok) + goto out; + + lhs = lhs->pn_next; + } + } + + /* + * The catch/finally handler implementation in the interpreter assumes + * that any operation that introduces a new scope (like a "let" or "with" + * block) increases the stack depth. This way, it is possible to restore + * the scope chain based on stack depth of the handler alone. "let" with + * an empty destructuring pattern like in + * + * let [] = 1; + * + * would violate this assumption as the there would be no let locals to + * store on the stack. To satisfy it we add an empty property to such + * blocks so that OBJ_BLOCK_COUNT(cx, blockObj), which gives the number of + * slots, would be always positive. + * + * Note that we add such a property even if the block has locals due to + * later let declarations in it. We optimize for code simplicity here, + * not the fastest runtime performance with empty [] or {}. + */ + if (data && + data->binder == BindLet && + OBJ_BLOCK_COUNT(cx, tc->blockChain()) == 0) { + ok = !!js_DefineNativeProperty(cx, tc->blockChain(), + ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), + UndefinedValue(), NULL, NULL, + JSPROP_ENUMERATE | JSPROP_PERMANENT, + Shape::HAS_SHORTID, 0, NULL); + if (!ok) + goto out; + } + + ok = JS_TRUE; + + out: + if (fpvd.table.ops) + JS_DHashTableFinish(&fpvd.table); + return ok; + + no_var_name: + ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, + JSMSG_NO_VARIABLE_NAME); + ok = JS_FALSE; + goto out; +} + +/* + * This is a greatly pared down version of CheckDestructuring that extends the + * pn_pos.end source coordinate of each name in a destructuring binding such as + * + * var [x, y] = [function () y, 42]; + * + * to cover its corresponding initializer, so that the initialized binding does + * not appear to dominate any closures in its initializer. See bug 496134. + * + * The quick-and-dirty dominance computation in Parser::setFunctionKinds is not + * very precise. With one-pass SSA construction from structured source code + * (see "Single-Pass Generation of Static Single Assignment Form for Structured + * Languages", Brandis and Mössenböck), we could do much better. + * + * See CheckDestructuring, immediately above. If you change either of these + * functions, you might have to change the other to match. + */ +static JSBool +UndominateInitializers(JSParseNode *left, JSParseNode *right, JSTreeContext *tc) +{ + FindPropValData fpvd; + JSParseNode *lhs, *rhs; + + JS_ASSERT(left->pn_type != TOK_ARRAYCOMP); + JS_ASSERT(right); + +#if JS_HAS_DESTRUCTURING_SHORTHAND + if (right->pn_arity == PN_LIST && (right->pn_xflags & PNX_DESTRUCT)) { + ReportCompileErrorNumber(tc->parser->context, TS(tc->parser), right, JSREPORT_ERROR, + JSMSG_BAD_OBJECT_INIT); + return JS_FALSE; + } +#endif + + if (right->pn_type != left->pn_type) + return JS_TRUE; + + fpvd.table.ops = NULL; + lhs = left->pn_head; + if (left->pn_type == TOK_RB) { + rhs = right->pn_head; + + while (lhs && rhs) { + /* Nullary comma is an elision; binary comma is an expression.*/ + if (lhs->pn_type != TOK_COMMA || lhs->pn_arity != PN_NULLARY) { + if (lhs->pn_type == TOK_RB || lhs->pn_type == TOK_RC) { + if (!UndominateInitializers(lhs, rhs, tc)) + return JS_FALSE; + } else { + lhs->pn_pos.end = rhs->pn_pos.end; + } + } + + lhs = lhs->pn_next; + rhs = rhs->pn_next; + } + } else { + JS_ASSERT(left->pn_type == TOK_RC); + fpvd.numvars = left->pn_count; + fpvd.maxstep = 0; + + while (lhs) { + JS_ASSERT(lhs->pn_type == TOK_COLON); + JSParseNode *pn = lhs->pn_right; + + rhs = FindPropertyValue(right, lhs->pn_left, &fpvd); + if (pn->pn_type == TOK_RB || pn->pn_type == TOK_RC) { + if (rhs && !UndominateInitializers(pn, rhs, tc)) + return JS_FALSE; + } else { + if (rhs) + pn->pn_pos.end = rhs->pn_pos.end; + } + + lhs = lhs->pn_next; + } + } + return JS_TRUE; +} + +JSParseNode * +Parser::destructuringExpr(BindData *data, TokenKind tt) +{ + JSParseNode *pn; + + tc->flags |= TCF_DECL_DESTRUCTURING; + pn = primaryExpr(tt, JS_FALSE); + tc->flags &= ~TCF_DECL_DESTRUCTURING; + if (!pn) + return NULL; + if (!CheckDestructuring(context, data, pn, NULL, tc)) + return NULL; + return pn; +} + +/* + * Currently used only #if JS_HAS_DESTRUCTURING, in Statement's TOK_FOR case. + * This function assumes the cloned tree is for use in the same statement and + * binding context as the original tree. + */ +static JSParseNode * +CloneParseTree(JSParseNode *opn, JSTreeContext *tc) +{ + JSParseNode *pn, *pn2, *opn2; + + pn = NewOrRecycledNode(tc); + if (!pn) + return NULL; + pn->pn_type = opn->pn_type; + pn->pn_pos = opn->pn_pos; + pn->pn_op = opn->pn_op; + pn->pn_used = opn->pn_used; + pn->pn_defn = opn->pn_defn; + pn->pn_arity = opn->pn_arity; + pn->pn_parens = opn->pn_parens; + + switch (pn->pn_arity) { +#define NULLCHECK(e) JS_BEGIN_MACRO if (!(e)) return NULL; JS_END_MACRO + + case PN_FUNC: + NULLCHECK(pn->pn_funbox = + tc->parser->newFunctionBox(opn->pn_funbox->object, pn, tc)); + NULLCHECK(pn->pn_body = CloneParseTree(opn->pn_body, tc)); + pn->pn_cookie = opn->pn_cookie; + pn->pn_dflags = opn->pn_dflags; + pn->pn_blockid = opn->pn_blockid; + break; + + case PN_LIST: + pn->makeEmpty(); + for (opn2 = opn->pn_head; opn2; opn2 = opn2->pn_next) { + NULLCHECK(pn2 = CloneParseTree(opn2, tc)); + pn->append(pn2); + } + pn->pn_xflags = opn->pn_xflags; + break; + + case PN_TERNARY: + NULLCHECK(pn->pn_kid1 = CloneParseTree(opn->pn_kid1, tc)); + NULLCHECK(pn->pn_kid2 = CloneParseTree(opn->pn_kid2, tc)); + NULLCHECK(pn->pn_kid3 = CloneParseTree(opn->pn_kid3, tc)); + break; + + case PN_BINARY: + NULLCHECK(pn->pn_left = CloneParseTree(opn->pn_left, tc)); + if (opn->pn_right != opn->pn_left) + NULLCHECK(pn->pn_right = CloneParseTree(opn->pn_right, tc)); + else + pn->pn_right = pn->pn_left; + pn->pn_pval = opn->pn_pval; + pn->pn_iflags = opn->pn_iflags; + break; + + case PN_UNARY: + NULLCHECK(pn->pn_kid = CloneParseTree(opn->pn_kid, tc)); + pn->pn_num = opn->pn_num; + pn->pn_hidden = opn->pn_hidden; + 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->pn_used) { + /* + * The old name is a use of its pn_lexdef. Make the clone also be a + * use of that definition. + */ + JSDefinition *dn = pn->pn_lexdef; + + pn->pn_link = dn->dn_uses; + dn->dn_uses = pn; + } else if (opn->pn_expr) { + NULLCHECK(pn->pn_expr = CloneParseTree(opn->pn_expr, tc)); + + /* + * 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->pn_defn) { + opn->pn_defn = false; + LinkUseToDef(opn, (JSDefinition *) pn, tc); + } + } + break; + + case PN_NAMESET: + pn->pn_names = opn->pn_names; + NULLCHECK(pn->pn_tree = CloneParseTree(opn->pn_tree, tc)); + break; + + case PN_NULLARY: + // Even PN_NULLARY may have data (apair for E4X -- what a botch). + pn->pn_u = opn->pn_u; + break; + +#undef NULLCHECK + } + return pn; +} + +#endif /* JS_HAS_DESTRUCTURING */ + +extern const char js_with_statement_str[]; + +static JSParseNode * +ContainsStmt(JSParseNode *pn, TokenKind tt) +{ + JSParseNode *pn2, *pnt; + + if (!pn) + return NULL; + if (PN_TYPE(pn) == tt) + return pn; + switch (pn->pn_arity) { + case PN_LIST: + for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + pnt = ContainsStmt(pn2, tt); + if (pnt) + return pnt; + } + break; + case PN_TERNARY: + pnt = ContainsStmt(pn->pn_kid1, tt); + if (pnt) + return pnt; + pnt = ContainsStmt(pn->pn_kid2, tt); + if (pnt) + return pnt; + return ContainsStmt(pn->pn_kid3, tt); + case PN_BINARY: + /* + * Limit recursion if pn is a binary expression, which can't contain a + * var statement. + */ + if (pn->pn_op != JSOP_NOP) + return NULL; + pnt = ContainsStmt(pn->pn_left, tt); + if (pnt) + return pnt; + return ContainsStmt(pn->pn_right, tt); + case PN_UNARY: + if (pn->pn_op != JSOP_NOP) + return NULL; + return ContainsStmt(pn->pn_kid, tt); + case PN_NAME: + return ContainsStmt(pn->maybeExpr(), tt); + case PN_NAMESET: + return ContainsStmt(pn->pn_tree, tt); + default:; + } + return NULL; +} + +JSParseNode * +Parser::returnOrYield(bool useAssignExpr) +{ + TokenKind tt, tt2; + JSParseNode *pn, *pn2; + + tt = tokenStream.currentToken().type; + if (tt == TOK_RETURN && !tc->inFunction()) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_RETURN_OR_YIELD, js_return_str); + return NULL; + } + + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + +#if JS_HAS_GENERATORS + if (tt == TOK_YIELD) + tc->flags |= TCF_FUN_IS_GENERATOR; +#endif + + /* This is ugly, but we don't want to require a semicolon. */ + tt2 = tokenStream.peekTokenSameLine(TSF_OPERAND); + if (tt2 == TOK_ERROR) + return NULL; + + if (tt2 != TOK_EOF && tt2 != TOK_EOL && tt2 != TOK_SEMI && tt2 != TOK_RC +#if JS_HAS_GENERATORS + && (tt != TOK_YIELD || + (tt2 != tt && tt2 != TOK_RB && tt2 != TOK_RP && + tt2 != TOK_COLON && tt2 != TOK_COMMA)) +#endif + ) { + pn2 = useAssignExpr ? assignExpr() : expr(); + if (!pn2) + return NULL; +#if JS_HAS_GENERATORS + if (tt == TOK_RETURN) +#endif + tc->flags |= TCF_RETURN_EXPR; + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_kid = pn2; + } else { +#if JS_HAS_GENERATORS + if (tt == TOK_RETURN) +#endif + tc->flags |= TCF_RETURN_VOID; + } + + if ((~tc->flags & (TCF_RETURN_EXPR | TCF_FUN_IS_GENERATOR)) == 0) { + /* As in Python (see PEP-255), disallow return v; in generators. */ + ReportBadReturn(context, tc, JSREPORT_ERROR, + JSMSG_BAD_GENERATOR_RETURN, + JSMSG_BAD_ANON_GENERATOR_RETURN); + return NULL; + } + + if (context->hasStrictOption() && + (~tc->flags & (TCF_RETURN_EXPR | TCF_RETURN_VOID)) == 0 && + !ReportBadReturn(context, tc, JSREPORT_WARNING | JSREPORT_STRICT, + JSMSG_NO_RETURN_VALUE, + JSMSG_ANON_NO_RETURN_VALUE)) { + return NULL; + } + + return pn; +} + +static JSParseNode * +PushLexicalScope(JSContext *cx, TokenStream *ts, JSTreeContext *tc, + JSStmtInfo *stmt) +{ + JSParseNode *pn; + JSObject *obj; + JSObjectBox *blockbox; + + pn = LexicalScopeNode::create(tc); + if (!pn) + return NULL; + + obj = js_NewBlockObject(cx); + if (!obj) + return NULL; + + blockbox = tc->parser->newObjectBox(obj); + if (!blockbox) + return NULL; + + js_PushBlockScope(tc, stmt, blockbox, -1); + pn->pn_type = TOK_LEXICALSCOPE; + pn->pn_op = JSOP_LEAVEBLOCK; + pn->pn_objbox = blockbox; + pn->pn_cookie.makeFree(); + pn->pn_dflags = 0; + if (!GenerateBlockId(tc, stmt->blockid)) + return NULL; + pn->pn_blockid = stmt->blockid; + return pn; +} + +#if JS_HAS_BLOCK_SCOPE + +JSParseNode * +Parser::letBlock(JSBool statement) +{ + JSParseNode *pn, *pnblock, *pnlet; + JSStmtInfo stmtInfo; + + JS_ASSERT(tokenStream.currentToken().type == TOK_LET); + + /* Create the let binary node. */ + pnlet = BinaryNode::create(tc); + if (!pnlet) + return NULL; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_LET); + + /* This is a let block or expression of the form: let (a, b, c) .... */ + pnblock = PushLexicalScope(context, &tokenStream, tc, &stmtInfo); + if (!pnblock) + return NULL; + pn = pnblock; + pn->pn_expr = pnlet; + + pnlet->pn_left = variables(true); + if (!pnlet->pn_left) + return NULL; + pnlet->pn_left->pn_xflags = PNX_POPVAR; + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_LET); + + if (statement && !tokenStream.matchToken(TOK_LC, TSF_OPERAND)) { + /* + * Strict mode eliminates a grammar ambiguity with unparenthesized + * LetExpressions in an ExpressionStatement. If followed immediately + * by an arguments list, it's ambiguous whether the let expression + * is the callee or the call is inside the let expression body. + * + * See bug 569464. + */ + if (!ReportStrictModeError(context, &tokenStream, tc, pnlet, + JSMSG_STRICT_CODE_LET_EXPR_STMT)) { + return NULL; + } + + /* + * If this is really an expression in let statement guise, then we + * need to wrap the TOK_LET node in a TOK_SEMI node so that we pop + * the return value of the expression. + */ + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_SEMI; + pn->pn_num = -1; + pn->pn_kid = pnblock; + + statement = JS_FALSE; + } + + if (statement) { + pnlet->pn_right = statements(); + if (!pnlet->pn_right) + return NULL; + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET); + } else { + /* + * Change pnblock's opcode to the variant that propagates the last + * result down after popping the block, and clear statement. + */ + pnblock->pn_op = JSOP_LEAVEBLOCKEXPR; + pnlet->pn_right = assignExpr(); + if (!pnlet->pn_right) + return NULL; + } + + PopStatement(tc); + return pn; +} + +#endif /* JS_HAS_BLOCK_SCOPE */ + +static bool +PushBlocklikeStatement(JSStmtInfo *stmt, JSStmtType type, JSTreeContext *tc) +{ + js_PushStatement(tc, stmt, type, -1); + return GenerateBlockId(tc, stmt->blockid); +} + +static JSParseNode * +NewBindingNode(JSAtom *atom, JSTreeContext *tc, bool let = false) +{ + JSParseNode *pn = NULL; + + JSAtomListElement *ale = tc->decls.lookup(atom); + if (ale) { + pn = ALE_DEFN(ale); + JS_ASSERT(!pn->isPlaceholder()); + } else { + ale = tc->lexdeps.lookup(atom); + if (ale) { + pn = ALE_DEFN(ale); + JS_ASSERT(pn->isPlaceholder()); + } + } + + if (pn) { + JS_ASSERT(pn->pn_defn); + + /* + * A let binding at top level becomes a var before we get here, so if + * pn and tc have the same blockid then that id must not be the bodyid. + * If pn is a forward placeholder definition from the same or a higher + * block then we claim it. + */ + JS_ASSERT_IF(let && pn->pn_blockid == tc->blockid(), + pn->pn_blockid != tc->bodyid); + + if (pn->isPlaceholder() && pn->pn_blockid >= (let ? tc->blockid() : tc->bodyid)) { + if (let) + pn->pn_blockid = tc->blockid(); + + tc->lexdeps.remove(tc->parser, atom); + return pn; + } + } + + /* Make a new node for this declarator name (or destructuring pattern). */ + pn = NameNode::create(atom, tc); + if (!pn) + return NULL; + return pn; +} + +#if JS_HAS_BLOCK_SCOPE +static bool +RebindLets(JSParseNode *pn, JSTreeContext *tc) +{ + if (!pn) + return true; + + switch (pn->pn_arity) { + case PN_LIST: + for (JSParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) + RebindLets(pn2, tc); + break; + + case PN_TERNARY: + RebindLets(pn->pn_kid1, tc); + RebindLets(pn->pn_kid2, tc); + RebindLets(pn->pn_kid3, tc); + break; + + case PN_BINARY: + RebindLets(pn->pn_left, tc); + RebindLets(pn->pn_right, tc); + break; + + case PN_UNARY: + RebindLets(pn->pn_kid, tc); + break; + + case PN_FUNC: + RebindLets(pn->pn_body, tc); + break; + + case PN_NAME: + RebindLets(pn->maybeExpr(), tc); + + if (pn->pn_defn) { + JS_ASSERT(pn->pn_blockid > tc->topStmt->blockid); + } else if (pn->pn_used) { + if (pn->pn_lexdef->pn_blockid == tc->topStmt->blockid) { + ForgetUse(pn); + + JSAtomListElement *ale = tc->decls.lookup(pn->pn_atom); + if (ale) { + while ((ale = ALE_NEXT(ale)) != NULL) { + if (ALE_ATOM(ale) == pn->pn_atom) { + LinkUseToDef(pn, ALE_DEFN(ale), tc); + return true; + } + } + } + + ale = tc->lexdeps.lookup(pn->pn_atom); + if (!ale) { + ale = MakePlaceholder(pn, tc); + if (!ale) + return NULL; + } + LinkUseToDef(pn, ALE_DEFN(ale), tc); + } + } + break; + + case PN_NAMESET: + RebindLets(pn->pn_tree, tc); + break; + } + + return true; +} +#endif /* JS_HAS_BLOCK_SCOPE */ + +JSParseNode * +Parser::switchStatement() +{ + JSParseNode *pn5, *saveBlock; + JSBool seenDefault = JS_FALSE; + + JSParseNode *pn = BinaryNode::create(tc); + if (!pn) + return NULL; + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); + + /* pn1 points to the switch's discriminant. */ + JSParseNode *pn1 = parenExpr(); + if (!pn1) + return NULL; + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH); + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH); + + /* + * NB: we must push stmtInfo before calling GenerateBlockIdForStmtNode + * because that function states tc->topStmt->blockid. + */ + JSStmtInfo stmtInfo; + js_PushStatement(tc, &stmtInfo, STMT_SWITCH, -1); + + /* pn2 is a list of case nodes. The default case has pn_left == NULL */ + JSParseNode *pn2 = ListNode::create(tc); + if (!pn2) + return NULL; + pn2->makeEmpty(); + if (!GenerateBlockIdForStmtNode(pn2, tc)) + return NULL; + saveBlock = tc->blockNode; + tc->blockNode = pn2; + + TokenKind tt; + while ((tt = tokenStream.getToken()) != TOK_RC) { + JSParseNode *pn3; + switch (tt) { + case TOK_DEFAULT: + if (seenDefault) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_DEFAULTS); + return NULL; + } + seenDefault = JS_TRUE; + /* FALL THROUGH */ + + case TOK_CASE: + { + pn3 = BinaryNode::create(tc); + if (!pn3) + return NULL; + if (tt == TOK_CASE) { + pn3->pn_left = expr(); + if (!pn3->pn_left) + return NULL; + } + pn2->append(pn3); + if (pn2->pn_count == JS_BIT(16)) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_CASES); + return NULL; + } + break; + } + + case TOK_ERROR: + return NULL; + + default: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_SWITCH); + return NULL; + } + MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE); + + JSParseNode *pn4 = ListNode::create(tc); + if (!pn4) + return NULL; + pn4->pn_type = TOK_LC; + pn4->makeEmpty(); + while ((tt = tokenStream.peekToken(TSF_OPERAND)) != TOK_RC && + tt != TOK_CASE && tt != TOK_DEFAULT) { + if (tt == TOK_ERROR) + return NULL; + pn5 = statement(); + if (!pn5) + return NULL; + pn4->pn_pos.end = pn5->pn_pos.end; + pn4->append(pn5); + } + + /* Fix the PN_LIST so it doesn't begin at the TOK_COLON. */ + if (pn4->pn_head) + pn4->pn_pos.begin = pn4->pn_head->pn_pos.begin; + pn3->pn_pos.end = pn4->pn_pos.end; + pn3->pn_right = pn4; + } + + /* + * Handle the case where there was a let declaration in any case in + * the switch body, but not within an inner block. If it replaced + * tc->blockNode with a new block node then we must refresh pn2 and + * then restore tc->blockNode. + */ + if (tc->blockNode != pn2) + pn2 = tc->blockNode; + tc->blockNode = saveBlock; + PopStatement(tc); + + pn->pn_pos.end = pn2->pn_pos.end = tokenStream.currentToken().pos.end; + pn->pn_left = pn1; + pn->pn_right = pn2; + return pn; +} + +JSParseNode * +Parser::forStatement() +{ + JSParseNode *pnseq = NULL; +#if JS_HAS_BLOCK_SCOPE + JSParseNode *pnlet = NULL; + JSStmtInfo blockInfo; +#endif + + /* A FOR node is binary, left is loop control and right is the body. */ + JSParseNode *pn = BinaryNode::create(tc); + if (!pn) + return NULL; + JSStmtInfo stmtInfo; + js_PushStatement(tc, &stmtInfo, STMT_FOR_LOOP, -1); + + pn->pn_op = JSOP_ITER; + pn->pn_iflags = 0; + if (tokenStream.matchToken(TOK_NAME)) { + if (tokenStream.currentToken().t_atom == context->runtime->atomState.eachAtom) + pn->pn_iflags = JSITER_FOREACH; + else + tokenStream.ungetToken(); + } + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + TokenKind tt = tokenStream.peekToken(TSF_OPERAND); + +#if JS_HAS_BLOCK_SCOPE + bool let = false; +#endif + + JSParseNode *pn1; + if (tt == TOK_SEMI) { + if (pn->pn_iflags & JSITER_FOREACH) + goto bad_for_each; + + /* No initializer -- set first kid of left sub-node to null. */ + pn1 = NULL; + } else { + /* + * Set pn1 to a var list or an initializing expression. + * + * Set the TCF_IN_FOR_INIT flag during parsing of the first clause + * of the for statement. This flag will be used by the RelExpr + * production; if it is set, then the 'in' keyword will not be + * recognized as an operator, leaving it available to be parsed as + * part of a for/in loop. + * + * A side effect of this restriction is that (unparenthesized) + * expressions involving an 'in' operator are illegal in the init + * clause of an ordinary for loop. + */ + tc->flags |= TCF_IN_FOR_INIT; + if (tt == TOK_VAR) { + (void) tokenStream.getToken(); + pn1 = variables(false); +#if JS_HAS_BLOCK_SCOPE + } else if (tt == TOK_LET) { + let = true; + (void) tokenStream.getToken(); + if (tokenStream.peekToken() == TOK_LP) { + pn1 = letBlock(JS_FALSE); + tt = TOK_LEXICALSCOPE; + } else { + pnlet = PushLexicalScope(context, &tokenStream, tc, &blockInfo); + if (!pnlet) + return NULL; + blockInfo.flags |= SIF_FOR_BLOCK; + pn1 = variables(false); + } +#endif + } else { + pn1 = expr(); + } + tc->flags &= ~TCF_IN_FOR_INIT; + if (!pn1) + return NULL; + } + + /* + * We can be sure that it's a for/in loop if there's still an 'in' + * keyword here, even if JavaScript recognizes 'in' as an operator, + * as we've excluded 'in' from being parsed in RelExpr by setting + * the TCF_IN_FOR_INIT flag in our JSTreeContext. + */ + if (pn1 && tokenStream.matchToken(TOK_IN)) { + pn->pn_iflags |= JSITER_ENUMERATE; + stmtInfo.type = STMT_FOR_IN_LOOP; + + /* Check that the left side of the 'in' is valid. */ + JS_ASSERT(!TokenKindIsDecl(tt) || PN_TYPE(pn1) == tt); + if (TokenKindIsDecl(tt) + ? (pn1->pn_count > 1 || pn1->pn_op == JSOP_DEFCONST +#if JS_HAS_DESTRUCTURING + || (versionNumber() == JSVERSION_1_7 && + pn->pn_op == JSOP_ITER && + !(pn->pn_iflags & JSITER_FOREACH) && + (pn1->pn_head->pn_type == TOK_RC || + (pn1->pn_head->pn_type == TOK_RB && + pn1->pn_head->pn_count != 2) || + (pn1->pn_head->pn_type == TOK_ASSIGN && + (pn1->pn_head->pn_left->pn_type != TOK_RB || + pn1->pn_head->pn_left->pn_count != 2)))) +#endif + ) + : (pn1->pn_type != TOK_NAME && + pn1->pn_type != TOK_DOT && +#if JS_HAS_DESTRUCTURING + ((versionNumber() == JSVERSION_1_7 && + pn->pn_op == JSOP_ITER && + !(pn->pn_iflags & JSITER_FOREACH)) + ? (pn1->pn_type != TOK_RB || pn1->pn_count != 2) + : (pn1->pn_type != TOK_RB && pn1->pn_type != TOK_RC)) && +#endif + pn1->pn_type != TOK_LP && +#if JS_HAS_XML_SUPPORT + (pn1->pn_type != TOK_UNARYOP || + pn1->pn_op != JSOP_XMLNAME) && +#endif + pn1->pn_type != TOK_LB)) { + reportErrorNumber(pn1, JSREPORT_ERROR, JSMSG_BAD_FOR_LEFTSIDE); + return NULL; + } + + /* pn2 points to the name or destructuring pattern on in's left. */ + JSParseNode *pn2 = NULL; + uintN dflag = PND_ASSIGNED; + + if (TokenKindIsDecl(tt)) { + /* Tell js_EmitTree(TOK_VAR) that pn1 is part of a for/in. */ + pn1->pn_xflags |= PNX_FORINVAR; + + /* + * Rewrite 'for (<decl> x = i in o)' where <decl> is 'var' or + * 'const' to hoist the initializer or the entire decl out of + * the loop head. TOK_VAR is the type for both 'var' and 'const'. + */ + pn2 = pn1->pn_head; + if ((pn2->pn_type == TOK_NAME && pn2->maybeExpr()) +#if JS_HAS_DESTRUCTURING + || pn2->pn_type == TOK_ASSIGN +#endif + ) { +#if JS_HAS_BLOCK_SCOPE + if (tt == TOK_LET) { + reportErrorNumber(pn2, JSREPORT_ERROR, JSMSG_INVALID_FOR_IN_INIT); + return NULL; + } +#endif /* JS_HAS_BLOCK_SCOPE */ + + pnseq = ListNode::create(tc); + if (!pnseq) + return NULL; + pnseq->pn_type = TOK_SEQ; + pnseq->pn_pos.begin = pn->pn_pos.begin; + + dflag = PND_INITIALIZED; + + /* + * All of 'var x = i' is hoisted above 'for (x in o)', + * so clear PNX_FORINVAR. + * + * Request JSOP_POP here since the var is for a simple + * name (it is not a destructuring binding's left-hand + * side) and it has an initializer. + */ + pn1->pn_xflags &= ~PNX_FORINVAR; + pn1->pn_xflags |= PNX_POPVAR; + pnseq->initList(pn1); + +#if JS_HAS_DESTRUCTURING + if (pn2->pn_type == TOK_ASSIGN) { + pn1 = CloneParseTree(pn2->pn_left, tc); + if (!pn1) + return NULL; + } else +#endif + { + JS_ASSERT(pn2->pn_type == TOK_NAME); + pn1 = NameNode::create(pn2->pn_atom, tc); + if (!pn1) + return NULL; + pn1->pn_type = TOK_NAME; + pn1->pn_op = JSOP_NAME; + pn1->pn_pos = pn2->pn_pos; + if (pn2->pn_defn) + LinkUseToDef(pn1, (JSDefinition *) pn2, tc); + } + pn2 = pn1; + } + } + + if (!pn2) { + pn2 = pn1; + if (pn2->pn_type == TOK_LP && + !MakeSetCall(context, pn2, tc, JSMSG_BAD_LEFTSIDE_OF_ASS)) { + return NULL; + } +#if JS_HAS_XML_SUPPORT + if (pn2->pn_type == TOK_UNARYOP) + pn2->pn_op = JSOP_BINDXMLNAME; +#endif + } + + switch (pn2->pn_type) { + case TOK_NAME: + /* Beware 'for (arguments in ...)' with or without a 'var'. */ + NoteLValue(context, pn2, tc, dflag); + break; + +#if JS_HAS_DESTRUCTURING + case TOK_ASSIGN: + pn2 = pn2->pn_left; + JS_ASSERT(pn2->pn_type == TOK_RB || pn2->pn_type == TOK_RC); + /* FALL THROUGH */ + case TOK_RB: + case TOK_RC: + /* Check for valid lvalues in var-less destructuring for-in. */ + if (pn1 == pn2 && !CheckDestructuring(context, NULL, pn2, NULL, tc)) + return NULL; + + if (versionNumber() == JSVERSION_1_7) { + /* + * Destructuring for-in requires [key, value] enumeration + * in JS1.7. + */ + JS_ASSERT(pn->pn_op == JSOP_ITER); + if (!(pn->pn_iflags & JSITER_FOREACH)) + pn->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE; + } + break; +#endif + + default:; + } + + /* + * Parse the object expression as the right operand of 'in', first + * removing the top statement from the statement-stack if this is a + * 'for (let x in y)' loop. + */ +#if JS_HAS_BLOCK_SCOPE + JSStmtInfo *save = tc->topStmt; + if (let) + tc->topStmt = save->down; +#endif + pn2 = expr(); +#if JS_HAS_BLOCK_SCOPE + if (let) + tc->topStmt = save; +#endif + + pn2 = JSParseNode::newBinaryOrAppend(TOK_IN, JSOP_NOP, pn1, pn2, tc); + if (!pn2) + return NULL; + pn->pn_left = pn2; + } else { + if (pn->pn_iflags & JSITER_FOREACH) + goto bad_for_each; + pn->pn_op = JSOP_NOP; + + /* Parse the loop condition or null into pn2. */ + MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); + tt = tokenStream.peekToken(TSF_OPERAND); + JSParseNode *pn2; + if (tt == TOK_SEMI) { + pn2 = NULL; + } else { + pn2 = expr(); + if (!pn2) + return NULL; + } + + /* Parse the update expression or null into pn3. */ + MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND); + tt = tokenStream.peekToken(TSF_OPERAND); + JSParseNode *pn3; + if (tt == TOK_RP) { + pn3 = NULL; + } else { + pn3 = expr(); + if (!pn3) + return NULL; + } + + /* Build the FORHEAD node to use as the left kid of pn. */ + JSParseNode *pn4 = TernaryNode::create(tc); + if (!pn4) + return NULL; + pn4->pn_type = TOK_FORHEAD; + pn4->pn_op = JSOP_NOP; + pn4->pn_kid1 = pn1; + pn4->pn_kid2 = pn2; + pn4->pn_kid3 = pn3; + pn->pn_left = pn4; + } + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); + + /* Parse the loop body into pn->pn_right. */ + JSParseNode *pn2; + pn2 = statement(); + if (!pn2) + return NULL; + pn->pn_right = pn2; + + /* Record the absolute line number for source note emission. */ + pn->pn_pos.end = pn2->pn_pos.end; + +#if JS_HAS_BLOCK_SCOPE + if (pnlet) { + PopStatement(tc); + pnlet->pn_expr = pn; + pn = pnlet; + } +#endif + if (pnseq) { + pnseq->pn_pos.end = pn->pn_pos.end; + pnseq->append(pn); + pn = pnseq; + } + PopStatement(tc); + return pn; + + bad_for_each: + reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP); + return NULL; +} + +JSParseNode * +Parser::tryStatement() +{ + JSParseNode *catchList, *lastCatch; + + /* + * try nodes are ternary. + * kid1 is the try statement + * kid2 is the catch node list or null + * kid3 is the finally statement + * + * catch nodes are ternary. + * kid1 is the lvalue (TOK_NAME, TOK_LB, or TOK_LC) + * kid2 is the catch guard or null if no guard + * kid3 is the catch block + * + * catch lvalue nodes are either: + * TOK_NAME for a single identifier + * TOK_RB or TOK_RC for a destructuring left-hand side + * + * finally nodes are TOK_LC statement lists. + */ + JSParseNode *pn = TernaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_op = JSOP_NOP; + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY); + JSStmtInfo stmtInfo; + if (!PushBlocklikeStatement(&stmtInfo, STMT_TRY, tc)) + return NULL; + pn->pn_kid1 = statements(); + if (!pn->pn_kid1) + return NULL; + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_TRY); + PopStatement(tc); + + catchList = NULL; + TokenKind tt = tokenStream.getToken(); + if (tt == TOK_CATCH) { + catchList = ListNode::create(tc); + if (!catchList) + return NULL; + catchList->pn_type = TOK_RESERVED; + catchList->makeEmpty(); + lastCatch = NULL; + + do { + JSParseNode *pnblock; + BindData data; + + /* Check for another catch after unconditional catch. */ + if (lastCatch && !lastCatch->pn_kid2) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_CATCH_AFTER_GENERAL); + return NULL; + } + + /* + * Create a lexical scope node around the whole catch clause, + * including the head. + */ + pnblock = PushLexicalScope(context, &tokenStream, tc, &stmtInfo); + if (!pnblock) + return NULL; + stmtInfo.type = STMT_CATCH; + + /* + * Legal catch forms are: + * catch (lhs) + * catch (lhs if <boolean_expression>) + * where lhs is a name or a destructuring left-hand side. + * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD) + */ + JSParseNode *pn2 = TernaryNode::create(tc); + if (!pn2) + return NULL; + pnblock->pn_expr = pn2; + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH); + + /* + * Contrary to ECMA Ed. 3, the catch variable is lexically + * scoped, not a property of a new Object instance. This is + * an intentional change that anticipates ECMA Ed. 4. + */ + data.pn = NULL; + data.op = JSOP_NOP; + data.binder = BindLet; + data.let.overflow = JSMSG_TOO_MANY_CATCH_VARS; + + tt = tokenStream.getToken(); + JSParseNode *pn3; + switch (tt) { +#if JS_HAS_DESTRUCTURING + case TOK_LB: + case TOK_LC: + pn3 = destructuringExpr(&data, tt); + if (!pn3) + return NULL; + break; +#endif + + case TOK_NAME: + { + JSAtom *label = tokenStream.currentToken().t_atom; + pn3 = NewBindingNode(label, tc, true); + if (!pn3) + return NULL; + data.pn = pn3; + if (!data.binder(context, &data, label, tc)) + return NULL; + break; + } + + default: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_CATCH_IDENTIFIER); + return NULL; + } + + pn2->pn_kid1 = pn3; +#if JS_HAS_CATCH_GUARD + /* + * We use 'catch (x if x === 5)' (not 'catch (x : x === 5)') + * to avoid conflicting with the JS2/ECMAv4 type annotation + * catchguard syntax. + */ + if (tokenStream.matchToken(TOK_IF)) { + pn2->pn_kid2 = expr(); + if (!pn2->pn_kid2) + return NULL; + } +#endif + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); + pn2->pn_kid3 = statements(); + if (!pn2->pn_kid3) + return NULL; + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH); + PopStatement(tc); + + catchList->append(pnblock); + lastCatch = pn2; + tt = tokenStream.getToken(TSF_OPERAND); + } while (tt == TOK_CATCH); + } + pn->pn_kid2 = catchList; + + if (tt == TOK_FINALLY) { + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY); + if (!PushBlocklikeStatement(&stmtInfo, STMT_FINALLY, tc)) + return NULL; + pn->pn_kid3 = statements(); + if (!pn->pn_kid3) + return NULL; + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_FINALLY); + PopStatement(tc); + } else { + tokenStream.ungetToken(); + } + if (!catchList && !pn->pn_kid3) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_CATCH_OR_FINALLY); + return NULL; + } + return pn; +} + +JSParseNode * +Parser::withStatement() +{ + /* + * In most cases, we want the constructs forbidden in strict mode + * code to be a subset of those that JSOPTION_STRICT warns about, and + * we should use ReportStrictModeError. However, 'with' is the sole + * instance of a construct that is forbidden in strict mode code, but + * doesn't even merit a warning under JSOPTION_STRICT. See + * https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1. + */ + if (tc->flags & TCF_STRICT_MODE_CODE) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_STRICT_CODE_WITH); + return NULL; + } + + JSParseNode *pn = BinaryNode::create(tc); + if (!pn) + return NULL; + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); + JSParseNode *pn2 = parenExpr(); + if (!pn2) + return NULL; + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); + pn->pn_left = pn2; + + JSParseNode *oldWith = tc->innermostWith; + tc->innermostWith = pn; + + JSStmtInfo stmtInfo; + js_PushStatement(tc, &stmtInfo, STMT_WITH, -1); + pn2 = statement(); + if (!pn2) + return NULL; + PopStatement(tc); + + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_right = pn2; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + tc->innermostWith = oldWith; + + /* + * Make sure to deoptimize lexical dependencies inside the |with| + * to safely optimize binding globals (see bug 561923). + */ + JSAtomListIterator iter(&tc->lexdeps); + while (JSAtomListElement *ale = iter()) { + JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); + DeoptimizeUsesWithin(lexdep, pn->pn_pos); + } + + return pn; +} + +#if JS_HAS_BLOCK_SCOPE +JSParseNode * +Parser::letStatement() +{ + JSObjectBox *blockbox; + + JSParseNode *pn; + do { + /* Check for a let statement or let expression. */ + if (tokenStream.peekToken() == TOK_LP) { + pn = letBlock(JS_TRUE); + if (!pn || pn->pn_op == JSOP_LEAVEBLOCK) + return pn; + + /* Let expressions require automatic semicolon insertion. */ + JS_ASSERT(pn->pn_type == TOK_SEMI || + pn->pn_op == JSOP_LEAVEBLOCKEXPR); + break; + } + + /* + * This is a let declaration. We must be directly under a block per + * the proposed ES4 specs, but not an implicit block created due to + * 'for (let ...)'. If we pass this error test, make the enclosing + * JSStmtInfo be our scope. Further let declarations in this block + * will find this scope statement and use the same block object. + * + * If we are the first let declaration in this block (i.e., when the + * enclosing maybe-scope JSStmtInfo isn't yet a scope statement) then + * we also need to set tc->blockNode to be our TOK_LEXICALSCOPE. + */ + JSStmtInfo *stmt = tc->topStmt; + if (stmt && + (!STMT_MAYBE_SCOPE(stmt) || (stmt->flags & SIF_FOR_BLOCK))) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_LET_DECL_NOT_IN_BLOCK); + return NULL; + } + + if (stmt && (stmt->flags & SIF_SCOPE)) { + JS_ASSERT(tc->blockChainBox == stmt->blockBox); + } else { + if (!stmt || (stmt->flags & SIF_BODY_BLOCK)) { + /* + * ES4 specifies that let at top level and at body-block scope + * does not shadow var, so convert back to var. + */ + tokenStream.mungeCurrentToken(TOK_VAR, JSOP_DEFVAR); + + pn = variables(false); + if (!pn) + return NULL; + pn->pn_xflags |= PNX_POPVAR; + break; + } + + /* + * Some obvious assertions here, but they may help clarify the + * situation. This stmt is not yet a scope, so it must not be a + * catch block (catch is a lexical scope by definition). + */ + JS_ASSERT(!(stmt->flags & SIF_SCOPE)); + JS_ASSERT(stmt != tc->topScopeStmt); + JS_ASSERT(stmt->type == STMT_BLOCK || + stmt->type == STMT_SWITCH || + stmt->type == STMT_TRY || + stmt->type == STMT_FINALLY); + JS_ASSERT(!stmt->downScope); + + /* Convert the block statement into a scope statement. */ + JSObject *obj = js_NewBlockObject(tc->parser->context); + if (!obj) + return NULL; + + blockbox = tc->parser->newObjectBox(obj); + if (!blockbox) + return NULL; + + /* + * Insert stmt on the tc->topScopeStmt/stmtInfo.downScope linked + * list stack, if it isn't already there. If it is there, but it + * lacks the SIF_SCOPE flag, it must be a try, catch, or finally + * block. + */ + stmt->flags |= SIF_SCOPE; + stmt->downScope = tc->topScopeStmt; + tc->topScopeStmt = stmt; + JS_SCOPE_DEPTH_METERING(++tc->scopeDepth > tc->maxScopeDepth && + (tc->maxScopeDepth = tc->scopeDepth)); + + obj->setParent(tc->blockChain()); + blockbox->parent = tc->blockChainBox; + tc->blockChainBox = blockbox; + stmt->blockBox = blockbox; + +#ifdef DEBUG + JSParseNode *tmp = tc->blockNode; + JS_ASSERT(!tmp || tmp->pn_type != TOK_LEXICALSCOPE); +#endif + + /* Create a new lexical scope node for these statements. */ + JSParseNode *pn1 = LexicalScopeNode::create(tc); + if (!pn1) + return NULL; + + pn1->pn_type = TOK_LEXICALSCOPE; + pn1->pn_op = JSOP_LEAVEBLOCK; + pn1->pn_pos = tc->blockNode->pn_pos; + pn1->pn_objbox = blockbox; + pn1->pn_expr = tc->blockNode; + pn1->pn_blockid = tc->blockNode->pn_blockid; + tc->blockNode = pn1; + } + + pn = variables(false); + if (!pn) + return NULL; + pn->pn_xflags = PNX_POPVAR; + } while (0); + + /* Check termination of this primitive statement. */ + return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; +} +#endif + +JSParseNode * +Parser::expressionStatement() +{ + tokenStream.ungetToken(); + JSParseNode *pn2 = expr(); + if (!pn2) + return NULL; + + if (tokenStream.peekToken() == TOK_COLON) { + if (pn2->pn_type != TOK_NAME) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_LABEL); + return NULL; + } + JSAtom *label = pn2->pn_atom; + for (JSStmtInfo *stmt = tc->topStmt; stmt; stmt = stmt->down) { + if (stmt->type == STMT_LABEL && stmt->label == label) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_DUPLICATE_LABEL); + return NULL; + } + } + ForgetUse(pn2); + + (void) tokenStream.getToken(); + + /* Push a label struct and parse the statement. */ + JSStmtInfo stmtInfo; + js_PushStatement(tc, &stmtInfo, STMT_LABEL, -1); + stmtInfo.label = label; + JSParseNode *pn = statement(); + if (!pn) + return NULL; + + /* Normalize empty statement to empty block for the decompiler. */ + if (pn->pn_type == TOK_SEMI && !pn->pn_kid) { + pn->pn_type = TOK_LC; + pn->pn_arity = PN_LIST; + pn->makeEmpty(); + } + + /* Pop the label, set pn_expr, and return early. */ + PopStatement(tc); + pn2->pn_type = TOK_COLON; + pn2->pn_pos.end = pn->pn_pos.end; + pn2->pn_expr = pn; + return pn2; + } + + JSParseNode *pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_SEMI; + pn->pn_pos = pn2->pn_pos; + pn->pn_kid = pn2; + + switch (PN_TYPE(pn2)) { + case TOK_LP: + /* + * Flag lambdas immediately applied as statements as instances of + * the JS "module pattern". See CheckForImmediatelyAppliedLambda. + */ + if (PN_TYPE(pn2->pn_head) == TOK_FUNCTION && + !pn2->pn_head->pn_funbox->node->isFunArg()) { + pn2->pn_head->pn_funbox->tcflags |= TCF_FUN_MODULE_PATTERN; + } + break; + case TOK_ASSIGN: + /* + * Keep track of all apparent methods created by assignments such + * as this.foo = function (...) {...} in a function that could end + * up a constructor function. See Parser::setFunctionKinds. + */ + if (tc->funbox && + PN_OP(pn2) == JSOP_NOP && + PN_OP(pn2->pn_left) == JSOP_SETPROP && + PN_OP(pn2->pn_left->pn_expr) == JSOP_THIS && + PN_OP(pn2->pn_right) == JSOP_LAMBDA) { + JS_ASSERT(!pn2->pn_defn); + JS_ASSERT(!pn2->pn_used); + pn2->pn_right->pn_link = tc->funbox->methods; + tc->funbox->methods = pn2->pn_right; + } + break; + default:; + } + + /* Check termination of this primitive statement. */ + return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; +} + +JSParseNode * +Parser::statement() +{ + JSParseNode *pn; + + JS_CHECK_RECURSION(context, return NULL); + + switch (tokenStream.getToken(TSF_OPERAND)) { + case TOK_FUNCTION: + { +#if JS_HAS_XML_SUPPORT + TokenKind tt = tokenStream.peekToken(TSF_KEYWORD_IS_NAME); + if (tt == TOK_DBLCOLON) + goto expression; +#endif + return functionStmt(); + } + + case TOK_IF: + { + /* An IF node has three kids: condition, then, and optional else. */ + pn = TernaryNode::create(tc); + if (!pn) + return NULL; + JSParseNode *pn1 = condition(); + if (!pn1) + return NULL; + JSStmtInfo stmtInfo; + js_PushStatement(tc, &stmtInfo, STMT_IF, -1); + JSParseNode *pn2 = statement(); + if (!pn2) + return NULL; + JSParseNode *pn3; + if (tokenStream.matchToken(TOK_ELSE, TSF_OPERAND)) { + stmtInfo.type = STMT_ELSE; + pn3 = statement(); + if (!pn3) + return NULL; + pn->pn_pos.end = pn3->pn_pos.end; + } else { + pn3 = NULL; + pn->pn_pos.end = pn2->pn_pos.end; + } + PopStatement(tc); + pn->pn_kid1 = pn1; + pn->pn_kid2 = pn2; + pn->pn_kid3 = pn3; + return pn; + } + + case TOK_SWITCH: + return switchStatement(); + + case TOK_WHILE: + { + pn = BinaryNode::create(tc); + if (!pn) + return NULL; + JSStmtInfo stmtInfo; + js_PushStatement(tc, &stmtInfo, STMT_WHILE_LOOP, -1); + JSParseNode *pn2 = condition(); + if (!pn2) + return NULL; + pn->pn_left = pn2; + JSParseNode *pn3 = statement(); + if (!pn3) + return NULL; + PopStatement(tc); + pn->pn_pos.end = pn3->pn_pos.end; + pn->pn_right = pn3; + return pn; + } + + case TOK_DO: + { + pn = BinaryNode::create(tc); + if (!pn) + return NULL; + JSStmtInfo stmtInfo; + js_PushStatement(tc, &stmtInfo, STMT_DO_LOOP, -1); + JSParseNode *pn2 = statement(); + if (!pn2) + return NULL; + pn->pn_left = pn2; + MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO); + JSParseNode *pn3 = condition(); + if (!pn3) + return NULL; + PopStatement(tc); + pn->pn_pos.end = pn3->pn_pos.end; + pn->pn_right = pn3; + if (versionNumber() != JSVERSION_ECMA_3) { + /* + * All legacy and extended versions must do automatic semicolon + * insertion after do-while. See the testcase and discussion in + * http://bugzilla.mozilla.org/show_bug.cgi?id=238945. + */ + (void) tokenStream.matchToken(TOK_SEMI); + return pn; + } + break; + } + + case TOK_FOR: + return forStatement(); + + case TOK_TRY: + return tryStatement(); + + case TOK_THROW: + { + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + + /* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */ + TokenKind tt = tokenStream.peekTokenSameLine(TSF_OPERAND); + if (tt == TOK_ERROR) + return NULL; + if (tt == TOK_EOF || tt == TOK_EOL || tt == TOK_SEMI || tt == TOK_RC) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); + return NULL; + } + + JSParseNode *pn2 = expr(); + if (!pn2) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_op = JSOP_THROW; + pn->pn_kid = pn2; + break; + } + + /* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */ + case TOK_CATCH: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_CATCH_WITHOUT_TRY); + return NULL; + + case TOK_FINALLY: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_FINALLY_WITHOUT_TRY); + return NULL; + + case TOK_BREAK: + { + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + if (!MatchLabel(context, &tokenStream, pn)) + return NULL; + JSStmtInfo *stmt = tc->topStmt; + JSAtom *label = pn->pn_atom; + if (label) { + for (; ; stmt = stmt->down) { + if (!stmt) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_LABEL_NOT_FOUND); + return NULL; + } + if (stmt->type == STMT_LABEL && stmt->label == label) + break; + } + } else { + for (; ; stmt = stmt->down) { + if (!stmt) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOUGH_BREAK); + return NULL; + } + if (STMT_IS_LOOP(stmt) || stmt->type == STMT_SWITCH) + break; + } + } + if (label) + pn->pn_pos.end = tokenStream.currentToken().pos.end; + break; + } + + case TOK_CONTINUE: + { + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + if (!MatchLabel(context, &tokenStream, pn)) + return NULL; + JSStmtInfo *stmt = tc->topStmt; + JSAtom *label = pn->pn_atom; + if (label) { + for (JSStmtInfo *stmt2 = NULL; ; stmt = stmt->down) { + if (!stmt) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_LABEL_NOT_FOUND); + return NULL; + } + if (stmt->type == STMT_LABEL) { + if (stmt->label == label) { + if (!stmt2 || !STMT_IS_LOOP(stmt2)) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_CONTINUE); + return NULL; + } + break; + } + } else { + stmt2 = stmt; + } + } + } else { + for (; ; stmt = stmt->down) { + if (!stmt) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_CONTINUE); + return NULL; + } + if (STMT_IS_LOOP(stmt)) + break; + } + } + if (label) + pn->pn_pos.end = tokenStream.currentToken().pos.end; + break; + } + + case TOK_WITH: + return withStatement(); + + case TOK_VAR: + pn = variables(false); + if (!pn) + return NULL; + + /* Tell js_EmitTree to generate a final POP. */ + pn->pn_xflags |= PNX_POPVAR; + break; + +#if JS_HAS_BLOCK_SCOPE + case TOK_LET: + return letStatement(); +#endif /* JS_HAS_BLOCK_SCOPE */ + + case TOK_RETURN: + pn = returnOrYield(false); + if (!pn) + return NULL; + break; + + case TOK_LC: + { + uintN oldflags; + + oldflags = tc->flags; + tc->flags = oldflags & ~TCF_HAS_FUNCTION_STMT; + JSStmtInfo stmtInfo; + if (!PushBlocklikeStatement(&stmtInfo, STMT_BLOCK, tc)) + return NULL; + pn = statements(); + if (!pn) + return NULL; + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_COMPOUND); + PopStatement(tc); + + /* + * If we contain a function statement and our container is top-level + * or another block, flag pn to preserve braces when decompiling. + */ + if ((tc->flags & TCF_HAS_FUNCTION_STMT) && + (!tc->topStmt || tc->topStmt->type == STMT_BLOCK)) { + pn->pn_xflags |= PNX_NEEDBRACES; + } + tc->flags = oldflags | (tc->flags & (TCF_FUN_FLAGS | TCF_RETURN_FLAGS)); + return pn; + } + + case TOK_EOL: + case TOK_SEMI: + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_SEMI; + return pn; + + case TOK_DEBUGGER: + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_DEBUGGER; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + break; + +#if JS_HAS_XML_SUPPORT + case TOK_DEFAULT: + { + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + if (!tokenStream.matchToken(TOK_NAME) || + tokenStream.currentToken().t_atom != context->runtime->atomState.xmlAtom || + !tokenStream.matchToken(TOK_NAME) || + tokenStream.currentToken().t_atom != context->runtime->atomState.namespaceAtom || + !tokenStream.matchToken(TOK_ASSIGN) || + tokenStream.currentToken().t_op != JSOP_NOP) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_DEFAULT_XML_NAMESPACE); + return NULL; + } + + /* Is this an E4X dagger I see before me? */ + tc->flags |= TCF_FUN_HEAVYWEIGHT; + JSParseNode *pn2 = expr(); + if (!pn2) + return NULL; + pn->pn_op = JSOP_DEFXMLNS; + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_kid = pn2; + break; + } +#endif + + case TOK_ERROR: + return NULL; + + default: +#if JS_HAS_XML_SUPPORT + expression: +#endif + return expressionStatement(); + } + + /* Check termination of this primitive statement. */ + return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; +} + +JSParseNode * +Parser::variables(bool inLetHead) +{ + TokenKind tt; + bool let; + JSStmtInfo *scopeStmt; + BindData data; + JSParseNode *pn, *pn2; + JSAtom *atom; + + /* + * The three options here are: + * - TOK_LET: We are parsing a let declaration. + * - TOK_LP: We are parsing the head of a let block. + * - Otherwise, we're parsing var declarations. + */ + tt = tokenStream.currentToken().type; + let = (tt == TOK_LET || tt == TOK_LP); + JS_ASSERT(let || tt == TOK_VAR); + +#if JS_HAS_BLOCK_SCOPE + bool popScope = (inLetHead || (let && (tc->flags & TCF_IN_FOR_INIT))); + JSStmtInfo *save = tc->topStmt, *saveScope = tc->topScopeStmt; +#endif + + /* Make sure that statement set up the tree context correctly. */ + scopeStmt = tc->topScopeStmt; + if (let) { + while (scopeStmt && !(scopeStmt->flags & SIF_SCOPE)) { + JS_ASSERT(!STMT_MAYBE_SCOPE(scopeStmt)); + scopeStmt = scopeStmt->downScope; + } + JS_ASSERT(scopeStmt); + } + + data.op = let ? JSOP_NOP : tokenStream.currentToken().t_op; + pn = ListNode::create(tc); + if (!pn) + return NULL; + pn->pn_op = data.op; + pn->makeEmpty(); + + /* + * SpiderMonkey const is really "write once per initialization evaluation" + * var, whereas let is block scoped. ES-Harmony wants block-scoped const so + * this code will change soon. + */ + if (let) { + JS_ASSERT(tc->blockChainBox == scopeStmt->blockBox); + data.binder = BindLet; + data.let.overflow = JSMSG_TOO_MANY_LOCALS; + } else { + data.binder = BindVarOrConst; + } + + do { + tt = tokenStream.getToken(); +#if JS_HAS_DESTRUCTURING + if (tt == TOK_LB || tt == TOK_LC) { + tc->flags |= TCF_DECL_DESTRUCTURING; + pn2 = primaryExpr(tt, JS_FALSE); + tc->flags &= ~TCF_DECL_DESTRUCTURING; + if (!pn2) + return NULL; + + if (!CheckDestructuring(context, &data, pn2, NULL, tc)) + return NULL; + if ((tc->flags & TCF_IN_FOR_INIT) && + tokenStream.peekToken() == TOK_IN) { + pn->append(pn2); + continue; + } + + MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL); + if (tokenStream.currentToken().t_op != JSOP_NOP) + goto bad_var_init; + +#if JS_HAS_BLOCK_SCOPE + if (popScope) { + tc->topStmt = save->down; + tc->topScopeStmt = saveScope->downScope; + } +#endif + JSParseNode *init = assignExpr(); +#if JS_HAS_BLOCK_SCOPE + if (popScope) { + tc->topStmt = save; + tc->topScopeStmt = saveScope; + } +#endif + + if (!init || !UndominateInitializers(pn2, init, tc)) + return NULL; + + pn2 = JSParseNode::newBinaryOrAppend(TOK_ASSIGN, JSOP_NOP, pn2, init, tc); + if (!pn2) + return NULL; + pn->append(pn2); + continue; + } +#endif /* JS_HAS_DESTRUCTURING */ + + if (tt != TOK_NAME) { + if (tt != TOK_ERROR) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NO_VARIABLE_NAME); + } + return NULL; + } + + atom = tokenStream.currentToken().t_atom; + pn2 = NewBindingNode(atom, tc, let); + if (!pn2) + return NULL; + if (data.op == JSOP_DEFCONST) + pn2->pn_dflags |= PND_CONST; + data.pn = pn2; + if (!data.binder(context, &data, atom, tc)) + return NULL; + pn->append(pn2); + + if (tokenStream.matchToken(TOK_ASSIGN)) { + if (tokenStream.currentToken().t_op != JSOP_NOP) + goto bad_var_init; + +#if JS_HAS_BLOCK_SCOPE + if (popScope) { + tc->topStmt = save->down; + tc->topScopeStmt = saveScope->downScope; + } +#endif + JSParseNode *init = assignExpr(); +#if JS_HAS_BLOCK_SCOPE + if (popScope) { + tc->topStmt = save; + tc->topScopeStmt = saveScope; + } +#endif + if (!init) + return NULL; + + if (pn2->pn_used) { + pn2 = MakeAssignment(pn2, init, tc); + if (!pn2) + return NULL; + } else { + pn2->pn_expr = init; + } + + JS_ASSERT_IF(pn2->pn_dflags & PND_GVAR, !(pn2->pn_dflags & PND_BOUND)); + + pn2->pn_op = (PN_OP(pn2) == JSOP_ARGUMENTS) + ? JSOP_SETNAME + : (pn2->pn_dflags & PND_BOUND) + ? JSOP_SETLOCAL + : (data.op == JSOP_DEFCONST) + ? JSOP_SETCONST + : JSOP_SETNAME; + + NoteLValue(context, pn2, tc, data.fresh ? PND_INITIALIZED : PND_ASSIGNED); + + /* The declarator's position must include the initializer. */ + pn2->pn_pos.end = init->pn_pos.end; + + if (tc->inFunction() && + atom == context->runtime->atomState.argumentsAtom) { + tc->noteArgumentsUse(); + if (!let) + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + } + } while (tokenStream.matchToken(TOK_COMMA)); + + pn->pn_pos.end = pn->last()->pn_pos.end; + return pn; + +bad_var_init: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_VAR_INIT); + return NULL; +} + +JSParseNode * +Parser::expr() +{ + JSParseNode *pn = assignExpr(); + if (pn && tokenStream.matchToken(TOK_COMMA)) { + JSParseNode *pn2 = ListNode::create(tc); + if (!pn2) + return NULL; + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->initList(pn); + pn = pn2; + do { +#if JS_HAS_GENERATORS + pn2 = pn->last(); + if (pn2->pn_type == TOK_YIELD && !pn2->pn_parens) { + reportErrorNumber(pn2, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); + return NULL; + } +#endif + pn2 = assignExpr(); + if (!pn2) + return NULL; + pn->append(pn2); + } while (tokenStream.matchToken(TOK_COMMA)); + pn->pn_pos.end = pn->last()->pn_pos.end; + } + return pn; +} + +JSParseNode * +Parser::assignExpr() +{ + JS_CHECK_RECURSION(context, return NULL); + +#if JS_HAS_GENERATORS + if (tokenStream.matchToken(TOK_YIELD, TSF_OPERAND)) + return returnOrYield(true); +#endif + + JSParseNode *pn = condExpr(); + if (!pn) + return NULL; + + TokenKind tt = tokenStream.getToken(); + if (tt != TOK_ASSIGN) { + tokenStream.ungetToken(); + return pn; + } + + JSOp op = tokenStream.currentToken().t_op; + switch (pn->pn_type) { + case TOK_NAME: + if (!CheckStrictAssignment(context, tc, pn)) + return NULL; + pn->pn_op = JSOP_SETNAME; + NoteLValue(context, pn, tc); + break; + case TOK_DOT: + pn->pn_op = JSOP_SETPROP; + break; + case TOK_LB: + pn->pn_op = JSOP_SETELEM; + break; +#if JS_HAS_DESTRUCTURING + case TOK_RB: + case TOK_RC: + { + if (op != JSOP_NOP) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_DESTRUCT_ASS); + return NULL; + } + JSParseNode *rhs = assignExpr(); + if (!rhs || !CheckDestructuring(context, NULL, pn, rhs, tc)) + return NULL; + return JSParseNode::newBinaryOrAppend(TOK_ASSIGN, op, pn, rhs, tc); + } +#endif + case TOK_LP: + if (!MakeSetCall(context, pn, tc, JSMSG_BAD_LEFTSIDE_OF_ASS)) + return NULL; + break; +#if JS_HAS_XML_SUPPORT + case TOK_UNARYOP: + if (pn->pn_op == JSOP_XMLNAME) { + pn->pn_op = JSOP_SETXMLNAME; + break; + } + /* FALL THROUGH */ +#endif + default: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_LEFTSIDE_OF_ASS); + return NULL; + } + + JSParseNode *rhs = assignExpr(); + if (rhs && PN_TYPE(pn) == TOK_NAME && pn->pn_used) { + JSDefinition *dn = pn->pn_lexdef; + + /* + * If the definition is not flagged as assigned, we must have imputed + * the initialized flag to it, to optimize for flat closures. But that + * optimization uses source coordinates to check dominance relations, + * so we must extend the end of the definition to cover the right-hand + * side of this assignment, i.e., the initializer. + */ + if (!dn->isAssigned()) { + JS_ASSERT(dn->isInitialized()); + dn->pn_pos.end = rhs->pn_pos.end; + } + } + + return JSParseNode::newBinaryOrAppend(TOK_ASSIGN, op, pn, rhs, tc); +} + +JSParseNode * +Parser::condExpr() +{ + JSParseNode *pn = orExpr(); + if (pn && tokenStream.matchToken(TOK_HOOK)) { + JSParseNode *pn1 = pn; + pn = TernaryNode::create(tc); + if (!pn) + return NULL; + + /* + * Always accept the 'in' operator in the middle clause of a ternary, + * where it's unambiguous, even if we might be parsing the init of a + * for statement. + */ + uintN oldflags = tc->flags; + tc->flags &= ~TCF_IN_FOR_INIT; + JSParseNode *pn2 = assignExpr(); + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); + + if (!pn2) + return NULL; + MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND); + JSParseNode *pn3 = assignExpr(); + if (!pn3) + return NULL; + pn->pn_pos.begin = pn1->pn_pos.begin; + pn->pn_pos.end = pn3->pn_pos.end; + pn->pn_kid1 = pn1; + pn->pn_kid2 = pn2; + pn->pn_kid3 = pn3; + } + return pn; +} + +JSParseNode * +Parser::orExpr() +{ + JSParseNode *pn = andExpr(); + while (pn && tokenStream.matchToken(TOK_OR)) + pn = JSParseNode::newBinaryOrAppend(TOK_OR, JSOP_OR, pn, andExpr(), tc); + return pn; +} + +JSParseNode * +Parser::andExpr() +{ + JSParseNode *pn = bitOrExpr(); + while (pn && tokenStream.matchToken(TOK_AND)) + pn = JSParseNode::newBinaryOrAppend(TOK_AND, JSOP_AND, pn, bitOrExpr(), tc); + return pn; +} + +JSParseNode * +Parser::bitOrExpr() +{ + JSParseNode *pn = bitXorExpr(); + while (pn && tokenStream.matchToken(TOK_BITOR)) + pn = JSParseNode::newBinaryOrAppend(TOK_BITOR, JSOP_BITOR, pn, bitXorExpr(), tc); + return pn; +} + +JSParseNode * +Parser::bitXorExpr() +{ + JSParseNode *pn = bitAndExpr(); + while (pn && tokenStream.matchToken(TOK_BITXOR)) + pn = JSParseNode::newBinaryOrAppend(TOK_BITXOR, JSOP_BITXOR, pn, bitAndExpr(), tc); + return pn; +} + +JSParseNode * +Parser::bitAndExpr() +{ + JSParseNode *pn = eqExpr(); + while (pn && tokenStream.matchToken(TOK_BITAND)) + pn = JSParseNode::newBinaryOrAppend(TOK_BITAND, JSOP_BITAND, pn, eqExpr(), tc); + return pn; +} + +JSParseNode * +Parser::eqExpr() +{ + JSParseNode *pn = relExpr(); + while (pn && tokenStream.matchToken(TOK_EQOP)) { + JSOp op = tokenStream.currentToken().t_op; + pn = JSParseNode::newBinaryOrAppend(TOK_EQOP, op, pn, relExpr(), tc); + } + return pn; +} + +JSParseNode * +Parser::relExpr() +{ + uintN inForInitFlag = tc->flags & TCF_IN_FOR_INIT; + + /* + * Uses of the in operator in shiftExprs are always unambiguous, + * so unset the flag that prohibits recognizing it. + */ + tc->flags &= ~TCF_IN_FOR_INIT; + + JSParseNode *pn = shiftExpr(); + while (pn && + (tokenStream.matchToken(TOK_RELOP) || + /* + * Recognize the 'in' token as an operator only if we're not + * currently in the init expr of a for loop. + */ + (inForInitFlag == 0 && tokenStream.matchToken(TOK_IN)) || + tokenStream.matchToken(TOK_INSTANCEOF))) { + TokenKind tt = tokenStream.currentToken().type; + JSOp op = tokenStream.currentToken().t_op; + pn = JSParseNode::newBinaryOrAppend(tt, op, pn, shiftExpr(), tc); + } + /* Restore previous state of inForInit flag. */ + tc->flags |= inForInitFlag; + + return pn; +} + +JSParseNode * +Parser::shiftExpr() +{ + JSParseNode *pn = addExpr(); + while (pn && tokenStream.matchToken(TOK_SHOP)) { + JSOp op = tokenStream.currentToken().t_op; + pn = JSParseNode::newBinaryOrAppend(TOK_SHOP, op, pn, addExpr(), tc); + } + return pn; +} + +JSParseNode * +Parser::addExpr() +{ + JSParseNode *pn = mulExpr(); + while (pn && + (tokenStream.matchToken(TOK_PLUS) || + tokenStream.matchToken(TOK_MINUS))) { + TokenKind tt = tokenStream.currentToken().type; + JSOp op = (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB; + pn = JSParseNode::newBinaryOrAppend(tt, op, pn, mulExpr(), tc); + } + return pn; +} + +JSParseNode * +Parser::mulExpr() +{ + JSParseNode *pn = unaryExpr(); + while (pn && (tokenStream.matchToken(TOK_STAR) || tokenStream.matchToken(TOK_DIVOP))) { + TokenKind tt = tokenStream.currentToken().type; + JSOp op = tokenStream.currentToken().t_op; + pn = JSParseNode::newBinaryOrAppend(tt, op, pn, unaryExpr(), tc); + } + return pn; +} + +static JSParseNode * +SetLvalKid(JSContext *cx, TokenStream *ts, JSTreeContext *tc, + JSParseNode *pn, JSParseNode *kid, const char *name) +{ + if (kid->pn_type != TOK_NAME && + kid->pn_type != TOK_DOT && + (kid->pn_type != TOK_LP || + (kid->pn_op != JSOP_CALL && kid->pn_op != JSOP_EVAL && + kid->pn_op != JSOP_FUNCALL && kid->pn_op != JSOP_FUNAPPLY)) && +#if JS_HAS_XML_SUPPORT + (kid->pn_type != TOK_UNARYOP || kid->pn_op != JSOP_XMLNAME) && +#endif + kid->pn_type != TOK_LB) { + ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, JSMSG_BAD_OPERAND, name); + return NULL; + } + if (!CheckStrictAssignment(cx, tc, kid)) + return NULL; + pn->pn_kid = kid; + return kid; +} + +static const char incop_name_str[][10] = {"increment", "decrement"}; + +static JSBool +SetIncOpKid(JSContext *cx, TokenStream *ts, JSTreeContext *tc, + JSParseNode *pn, JSParseNode *kid, + TokenKind tt, JSBool preorder) +{ + JSOp op; + + kid = SetLvalKid(cx, ts, tc, pn, kid, incop_name_str[tt == TOK_DEC]); + if (!kid) + return JS_FALSE; + switch (kid->pn_type) { + case TOK_NAME: + op = (tt == TOK_INC) + ? (preorder ? JSOP_INCNAME : JSOP_NAMEINC) + : (preorder ? JSOP_DECNAME : JSOP_NAMEDEC); + NoteLValue(cx, kid, tc); + break; + + case TOK_DOT: + op = (tt == TOK_INC) + ? (preorder ? JSOP_INCPROP : JSOP_PROPINC) + : (preorder ? JSOP_DECPROP : JSOP_PROPDEC); + break; + + case TOK_LP: + if (!MakeSetCall(cx, kid, tc, JSMSG_BAD_INCOP_OPERAND)) + return JS_FALSE; + /* FALL THROUGH */ +#if JS_HAS_XML_SUPPORT + case TOK_UNARYOP: + if (kid->pn_op == JSOP_XMLNAME) + kid->pn_op = JSOP_SETXMLNAME; + /* FALL THROUGH */ +#endif + case TOK_LB: + op = (tt == TOK_INC) + ? (preorder ? JSOP_INCELEM : JSOP_ELEMINC) + : (preorder ? JSOP_DECELEM : JSOP_ELEMDEC); + break; + + default: + JS_ASSERT(0); + op = JSOP_NOP; + } + pn->pn_op = op; + return JS_TRUE; +} + +JSParseNode * +Parser::unaryExpr() +{ + JSParseNode *pn, *pn2; + + JS_CHECK_RECURSION(context, return NULL); + + TokenKind tt = tokenStream.getToken(TSF_OPERAND); + switch (tt) { + case TOK_UNARYOP: + case TOK_PLUS: + case TOK_MINUS: + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_UNARYOP; /* PLUS and MINUS are binary */ + pn->pn_op = tokenStream.currentToken().t_op; + pn2 = unaryExpr(); + if (!pn2) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_kid = pn2; + break; + + case TOK_INC: + case TOK_DEC: + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn2 = memberExpr(JS_TRUE); + if (!pn2) + return NULL; + if (!SetIncOpKid(context, &tokenStream, tc, pn, pn2, tt, JS_TRUE)) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + break; + + case TOK_DELETE: + { + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn2 = unaryExpr(); + if (!pn2) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + + /* + * Under ECMA3, deleting any unary expression is valid -- it simply + * returns true. Here we fold constants before checking for a call + * expression, in order to rule out delete of a generator expression. + */ + if (!js_FoldConstants(context, pn2, tc)) + return NULL; + switch (pn2->pn_type) { + case TOK_LP: + if (!(pn2->pn_xflags & PNX_SETCALL)) { + /* + * Call MakeSetCall to check for errors, but clear PNX_SETCALL + * because the optimizer will eliminate the useless delete. + */ + if (!MakeSetCall(context, pn2, tc, JSMSG_BAD_DELETE_OPERAND)) + return NULL; + pn2->pn_xflags &= ~PNX_SETCALL; + } + break; + case TOK_NAME: + if (!ReportStrictModeError(context, &tokenStream, tc, pn, + JSMSG_DEPRECATED_DELETE_OPERAND)) { + return NULL; + } + pn2->pn_op = JSOP_DELNAME; + if (pn2->pn_atom == context->runtime->atomState.argumentsAtom) + tc->flags |= TCF_FUN_HEAVYWEIGHT; + break; + default:; + } + pn->pn_kid = pn2; + break; + } + case TOK_ERROR: + return NULL; + + default: + tokenStream.ungetToken(); + pn = memberExpr(JS_TRUE); + if (!pn) + return NULL; + + /* Don't look across a newline boundary for a postfix incop. */ + if (tokenStream.onCurrentLine(pn->pn_pos)) { + tt = tokenStream.peekTokenSameLine(TSF_OPERAND); + if (tt == TOK_INC || tt == TOK_DEC) { + (void) tokenStream.getToken(); + pn2 = UnaryNode::create(tc); + if (!pn2) + return NULL; + if (!SetIncOpKid(context, &tokenStream, tc, pn2, pn, tt, JS_FALSE)) + return NULL; + pn2->pn_pos.begin = pn->pn_pos.begin; + pn = pn2; + } + } + break; + } + return pn; +} + +#if JS_HAS_GENERATORS + +/* + * A dedicated helper for transplanting the comprehension expression E in + * + * [E for (V in I)] // array comprehension + * (E for (V in I)) // generator expression + * + * from its initial location in the AST, on the left of the 'for', to its final + * position on the right. To avoid a separate pass we do this by adjusting the + * blockids and name binding links that were established when E was parsed. + * + * A generator expression desugars like so: + * + * (E for (V in I)) => (function () { for (var V in I) yield E; })() + * + * so the transplanter must adjust static level as well as blockid. E's source + * coordinates in root->pn_pos are critical to deciding which binding links to + * preserve and which to cut. + * + * NB: This is not a general tree transplanter -- it knows in particular that + * the one or more bindings induced by V have not yet been created. + */ +class CompExprTransplanter { + JSParseNode *root; + JSTreeContext *tc; + bool genexp; + uintN adjust; + uintN funcLevel; + + public: + CompExprTransplanter(JSParseNode *pn, JSTreeContext *tc, bool ge, uintN adj) + : root(pn), tc(tc), genexp(ge), adjust(adj), funcLevel(0) + { + } + + bool transplant(JSParseNode *pn); +}; + +/* + * Any definitions nested within the comprehension expression of a generator + * expression must move "down" one static level, which of course increases the + * upvar-frame-skip count. + */ +static bool +BumpStaticLevel(JSParseNode *pn, JSTreeContext *tc) +{ + if (!pn->pn_cookie.isFree()) { + uintN level = pn->pn_cookie.level() + 1; + + JS_ASSERT(level >= tc->staticLevel); + if (level >= UpvarCookie::FREE_LEVEL) { + JS_ReportErrorNumber(tc->parser->context, js_GetErrorMessage, NULL, + JSMSG_TOO_DEEP, js_function_str); + return false; + } + + pn->pn_cookie.set(level, pn->pn_cookie.slot()); + } + return true; +} + +static void +AdjustBlockId(JSParseNode *pn, uintN adjust, JSTreeContext *tc) +{ + JS_ASSERT(pn->pn_arity == PN_LIST || pn->pn_arity == PN_FUNC || pn->pn_arity == PN_NAME); + pn->pn_blockid += adjust; + if (pn->pn_blockid >= tc->blockidGen) + tc->blockidGen = pn->pn_blockid + 1; +} + +bool +CompExprTransplanter::transplant(JSParseNode *pn) +{ + if (!pn) + return true; + + switch (pn->pn_arity) { + case PN_LIST: + for (JSParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + if (!transplant(pn2)) + return false; + } + if (pn->pn_pos >= root->pn_pos) + AdjustBlockId(pn, adjust, tc); + break; + + case PN_TERNARY: + if (!transplant(pn->pn_kid1) || + !transplant(pn->pn_kid2) || + !transplant(pn->pn_kid3)) + return false; + break; + + case PN_BINARY: + if (!transplant(pn->pn_left)) + return false; + + /* Binary TOK_COLON nodes can have left == right. See bug 492714. */ + if (pn->pn_right != pn->pn_left) { + if (!transplant(pn->pn_right)) + return false; + } + break; + + case PN_UNARY: + if (!transplant(pn->pn_kid)) + return false; + break; + + case PN_FUNC: + { + /* + * Only the first level of transplant recursion through functions needs + * to reparent the funbox, since all descendant functions are correctly + * linked under the top-most funbox. But every visit to this case needs + * to update funbox->level. + * + * Recall that funbox->level is the static level of the code containing + * the definition or expression of the function and not the static level + * of the function's body. + */ + JSFunctionBox *funbox = pn->pn_funbox; + + funbox->level = tc->staticLevel + funcLevel; + if (++funcLevel == 1 && genexp) { + JSFunctionBox *parent = tc->funbox; + + JSFunctionBox **funboxp = &tc->parent->functionList; + while (*funboxp != funbox) + funboxp = &(*funboxp)->siblings; + *funboxp = funbox->siblings; + + funbox->parent = parent; + funbox->siblings = parent->kids; + parent->kids = funbox; + funbox->level = tc->staticLevel; + } + /* FALL THROUGH */ + } + + case PN_NAME: + if (!transplant(pn->maybeExpr())) + return false; + if (pn->pn_arity == PN_FUNC) + --funcLevel; + + if (pn->pn_defn) { + if (genexp && !BumpStaticLevel(pn, tc)) + return false; + } else if (pn->pn_used) { + JS_ASSERT(pn->pn_op != JSOP_NOP); + JS_ASSERT(pn->pn_cookie.isFree()); + + JSDefinition *dn = pn->pn_lexdef; + JS_ASSERT(dn->pn_defn); + + /* + * Adjust the definition's block id only if it is a placeholder not + * to the left of the root node, and if pn is the last use visited + * in the comprehension expression (to avoid adjusting the blockid + * multiple times). + * + * Non-placeholder definitions within the comprehension expression + * will be visited further below. + */ + if (dn->isPlaceholder() && dn->pn_pos >= root->pn_pos && dn->dn_uses == pn) { + if (genexp && !BumpStaticLevel(dn, tc)) + return false; + AdjustBlockId(dn, adjust, tc); + } + + JSAtom *atom = pn->pn_atom; +#ifdef DEBUG + JSStmtInfo *stmt = js_LexicalLookup(tc, atom, NULL); + JS_ASSERT(!stmt || stmt != tc->topStmt); +#endif + if (genexp && PN_OP(dn) != JSOP_CALLEE) { + JS_ASSERT(!tc->decls.lookup(atom)); + + if (dn->pn_pos < root->pn_pos || dn->isPlaceholder()) { + JSAtomListElement *ale = tc->lexdeps.add(tc->parser, atom); + if (!ale) + return false; + + if (dn->pn_pos >= root->pn_pos) { + tc->parent->lexdeps.remove(tc->parser, atom); + } else { + JSDefinition *dn2 = (JSDefinition *)NameNode::create(atom, tc); + if (!dn2) + return false; + + dn2->pn_type = TOK_NAME; + dn2->pn_op = JSOP_NOP; + dn2->pn_defn = true; + dn2->pn_dflags |= PND_PLACEHOLDER; + dn2->pn_pos = root->pn_pos; + + JSParseNode **pnup = &dn->dn_uses; + JSParseNode *pnu; + while ((pnu = *pnup) != NULL && pnu->pn_pos >= root->pn_pos) { + pnu->pn_lexdef = dn2; + dn2->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS; + pnup = &pnu->pn_link; + } + dn2->dn_uses = dn->dn_uses; + dn->dn_uses = *pnup; + *pnup = NULL; + + dn = dn2; + } + + ALE_SET_DEFN(ale, dn); + } + } + } + + if (pn->pn_pos >= root->pn_pos) + AdjustBlockId(pn, adjust, tc); + break; + + case PN_NAMESET: + if (!transplant(pn->pn_tree)) + return false; + break; + } + return true; +} + +/* + * Starting from a |for| keyword after the first array initialiser element or + * an expression in an open parenthesis, parse the tail of the comprehension + * or generator expression signified by this |for| keyword in context. + * + * Return null on failure, else return the top-most parse node for the array + * comprehension or generator expression, with a unary node as the body of the + * (possibly nested) for-loop, initialized by |type, op, kid|. + */ +JSParseNode * +Parser::comprehensionTail(JSParseNode *kid, uintN blockid, + TokenKind type, JSOp op) +{ + uintN adjust; + JSParseNode *pn, *pn2, *pn3, **pnp; + JSStmtInfo stmtInfo; + BindData data; + TokenKind tt; + JSAtom *atom; + + JS_ASSERT(tokenStream.currentToken().type == TOK_FOR); + + if (type == TOK_SEMI) { + /* + * Generator expression desugars to an immediately applied lambda that + * yields the next value from a for-in loop (possibly nested, and with + * optional if guard). Make pn be the TOK_LC body node. + */ + pn = PushLexicalScope(context, &tokenStream, tc, &stmtInfo); + if (!pn) + return NULL; + adjust = pn->pn_blockid - blockid; + } else { + JS_ASSERT(type == TOK_ARRAYPUSH); + + /* + * Make a parse-node and literal object representing the block scope of + * this array comprehension. Our caller in primaryExpr, the TOK_LB case + * aka the array initialiser case, has passed the blockid to claim for + * the comprehension's block scope. We allocate that id or one above it + * here, by calling js_PushLexicalScope. + * + * In the case of a comprehension expression that has nested blocks + * (e.g., let expressions), we will allocate a higher blockid but then + * slide all blocks "to the right" to make room for the comprehension's + * block scope. + */ + adjust = tc->blockid(); + pn = PushLexicalScope(context, &tokenStream, tc, &stmtInfo); + if (!pn) + return NULL; + + JS_ASSERT(blockid <= pn->pn_blockid); + JS_ASSERT(blockid < tc->blockidGen); + JS_ASSERT(tc->bodyid < blockid); + pn->pn_blockid = stmtInfo.blockid = blockid; + JS_ASSERT(adjust < blockid); + adjust = blockid - adjust; + } + + pnp = &pn->pn_expr; + + CompExprTransplanter transplanter(kid, tc, type == TOK_SEMI, adjust); + transplanter.transplant(kid); + + data.pn = NULL; + data.op = JSOP_NOP; + data.binder = BindLet; + data.let.overflow = JSMSG_ARRAY_INIT_TOO_BIG; + + do { + /* + * FOR node is binary, left is loop control and right is body. Use + * index to count each block-local let-variable on the left-hand side + * of the IN. + */ + pn2 = BinaryNode::create(tc); + if (!pn2) + return NULL; + + pn2->pn_op = JSOP_ITER; + pn2->pn_iflags = JSITER_ENUMERATE; + if (tokenStream.matchToken(TOK_NAME)) { + if (tokenStream.currentToken().t_atom == context->runtime->atomState.eachAtom) + pn2->pn_iflags |= JSITER_FOREACH; + else + tokenStream.ungetToken(); + } + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + + atom = NULL; + tt = tokenStream.getToken(); + switch (tt) { +#if JS_HAS_DESTRUCTURING + case TOK_LB: + case TOK_LC: + tc->flags |= TCF_DECL_DESTRUCTURING; + pn3 = primaryExpr(tt, JS_FALSE); + tc->flags &= ~TCF_DECL_DESTRUCTURING; + if (!pn3) + return NULL; + break; +#endif + + case TOK_NAME: + atom = tokenStream.currentToken().t_atom; + + /* + * Create a name node with pn_op JSOP_NAME. We can't set pn_op to + * JSOP_GETLOCAL here, because we don't yet know the block's depth + * in the operand stack frame. The code generator computes that, + * and it tries to bind all names to slots, so we must let it do + * the deed. + */ + pn3 = NewBindingNode(atom, tc, true); + if (!pn3) + return NULL; + break; + + default: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NO_VARIABLE_NAME); + + case TOK_ERROR: + return NULL; + } + + MUST_MATCH_TOKEN(TOK_IN, JSMSG_IN_AFTER_FOR_NAME); + JSParseNode *pn4 = expr(); + if (!pn4) + return NULL; + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); + + switch (tt) { +#if JS_HAS_DESTRUCTURING + case TOK_LB: + case TOK_LC: + if (!CheckDestructuring(context, &data, pn3, NULL, tc)) + return NULL; + + if (versionNumber() == JSVERSION_1_7) { + /* Destructuring requires [key, value] enumeration in JS1.7. */ + if (pn3->pn_type != TOK_RB || pn3->pn_count != 2) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_FOR_LEFTSIDE); + return NULL; + } + + JS_ASSERT(pn2->pn_op == JSOP_ITER); + JS_ASSERT(pn2->pn_iflags & JSITER_ENUMERATE); + if (!(pn2->pn_iflags & JSITER_FOREACH)) + pn2->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE; + } + break; +#endif + + case TOK_NAME: + data.pn = pn3; + if (!data.binder(context, &data, atom, tc)) + return NULL; + break; + + default:; + } + + pn2->pn_left = JSParseNode::newBinaryOrAppend(TOK_IN, JSOP_NOP, pn3, pn4, tc); + if (!pn2->pn_left) + return NULL; + *pnp = pn2; + pnp = &pn2->pn_right; + } while (tokenStream.matchToken(TOK_FOR)); + + if (tokenStream.matchToken(TOK_IF)) { + pn2 = TernaryNode::create(tc); + if (!pn2) + return NULL; + pn2->pn_kid1 = condition(); + if (!pn2->pn_kid1) + return NULL; + *pnp = pn2; + pnp = &pn2->pn_kid2; + } + + pn2 = UnaryNode::create(tc); + if (!pn2) + return NULL; + pn2->pn_type = type; + pn2->pn_op = op; + pn2->pn_kid = kid; + *pnp = pn2; + + PopStatement(tc); + return pn; +} + +#if JS_HAS_GENERATOR_EXPRS + +/* + * Starting from a |for| keyword after an expression, parse the comprehension + * tail completing this generator expression. Wrap the expression at kid in a + * generator function that is immediately called to evaluate to the generator + * iterator that is the value of this generator expression. + * + * |kid| must be the expression before the |for| keyword; we return an + * application of a generator function that includes the |for| loops and + * |if| guards, with |kid| as the operand of a |yield| expression as the + * innermost loop body. + * + * Note how unlike Python, we do not evaluate the expression to the right of + * the first |in| in the chain of |for| heads. Instead, a generator expression + * is merely sugar for a generator function expression and its application. + */ +JSParseNode * +Parser::generatorExpr(JSParseNode *kid) +{ + /* Create a |yield| node for |kid|. */ + JSParseNode *pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_YIELD; + pn->pn_op = JSOP_YIELD; + pn->pn_parens = true; + pn->pn_pos = kid->pn_pos; + pn->pn_kid = kid; + pn->pn_hidden = true; + + /* Make a new node for the desugared generator function. */ + JSParseNode *genfn = FunctionNode::create(tc); + if (!genfn) + return NULL; + genfn->pn_type = TOK_FUNCTION; + genfn->pn_op = JSOP_LAMBDA; + JS_ASSERT(!genfn->pn_body); + genfn->pn_dflags = PND_FUNARG; + + { + JSTreeContext *outertc = tc; + JSTreeContext gentc(tc->parser); + + JSFunctionBox *funbox = EnterFunction(genfn, &gentc); + if (!funbox) + return NULL; + + /* + * We have to dance around a bit to propagate sharp variables from + * outertc to gentc before setting TCF_HAS_SHARPS implicitly by + * propagating all of outertc's TCF_FUN_FLAGS flags. As below, we have + * to be conservative by leaving TCF_HAS_SHARPS set in outertc if we + * do propagate to gentc. + */ + if (outertc->flags & TCF_HAS_SHARPS) { + gentc.flags |= TCF_IN_FUNCTION; + if (!gentc.ensureSharpSlots()) + return NULL; + } + + /* + * We assume conservatively that any deoptimization flag in tc->flags + * besides TCF_FUN_PARAM_ARGUMENTS can come from the kid. So we + * propagate these flags into genfn. For code simplicity we also do + * not detect if the flags were only set in the kid and could be + * removed from tc->flags. + */ + gentc.flags |= TCF_FUN_IS_GENERATOR | TCF_GENEXP_LAMBDA | + (tc->flags & (TCF_FUN_FLAGS & ~TCF_FUN_PARAM_ARGUMENTS)); + funbox->tcflags |= gentc.flags; + genfn->pn_funbox = funbox; + genfn->pn_blockid = gentc.bodyid; + + JSParseNode *body = comprehensionTail(pn, outertc->blockid()); + if (!body) + return NULL; + JS_ASSERT(!genfn->pn_body); + genfn->pn_body = body; + genfn->pn_pos.begin = body->pn_pos.begin = kid->pn_pos.begin; + genfn->pn_pos.end = body->pn_pos.end = tokenStream.currentToken().pos.end; + + if (!LeaveFunction(genfn, &gentc)) + return NULL; + } + + /* + * Our result is a call expression that invokes the anonymous generator + * function object. + */ + JSParseNode *result = ListNode::create(tc); + if (!result) + return NULL; + result->pn_type = TOK_LP; + result->pn_op = JSOP_CALL; + result->pn_pos.begin = genfn->pn_pos.begin; + result->initList(genfn); + return result; +} + +static const char js_generator_str[] = "generator"; + +#endif /* JS_HAS_GENERATOR_EXPRS */ +#endif /* JS_HAS_GENERATORS */ + +JSBool +Parser::argumentList(JSParseNode *listNode) +{ + if (tokenStream.matchToken(TOK_RP, TSF_OPERAND)) + return JS_TRUE; + + do { + JSParseNode *argNode = assignExpr(); + if (!argNode) + return JS_FALSE; +#if JS_HAS_GENERATORS + if (argNode->pn_type == TOK_YIELD && + !argNode->pn_parens && + tokenStream.peekToken() == TOK_COMMA) { + reportErrorNumber(argNode, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); + return JS_FALSE; + } +#endif +#if JS_HAS_GENERATOR_EXPRS + if (tokenStream.matchToken(TOK_FOR)) { + argNode = generatorExpr(argNode); + if (!argNode) + return JS_FALSE; + if (listNode->pn_count > 1 || + tokenStream.peekToken() == TOK_COMMA) { + reportErrorNumber(argNode, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, + js_generator_str); + return JS_FALSE; + } + } +#endif + listNode->append(argNode); + } while (tokenStream.matchToken(TOK_COMMA)); + + if (tokenStream.getToken() != TOK_RP) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_PAREN_AFTER_ARGS); + return JS_FALSE; + } + return JS_TRUE; +} + +/* Check for an immediately-applied (new'ed) lambda and clear PND_FUNARG. */ +static JSParseNode * +CheckForImmediatelyAppliedLambda(JSParseNode *pn) +{ + if (pn->pn_type == TOK_FUNCTION) { + JS_ASSERT(pn->pn_arity == PN_FUNC); + + JSFunctionBox *funbox = pn->pn_funbox; + JS_ASSERT(((JSFunction *) funbox->object)->flags & JSFUN_LAMBDA); + if (!(funbox->tcflags & (TCF_FUN_USES_ARGUMENTS | TCF_FUN_USES_OWN_NAME))) + pn->pn_dflags &= ~PND_FUNARG; + } + return pn; +} + +JSParseNode * +Parser::memberExpr(JSBool allowCallSyntax) +{ + JSParseNode *pn, *pn2, *pn3; + + JS_CHECK_RECURSION(context, return NULL); + + /* Check for new expression first. */ + TokenKind tt = tokenStream.getToken(TSF_OPERAND); + if (tt == TOK_NEW) { + pn = ListNode::create(tc); + if (!pn) + return NULL; + pn2 = memberExpr(JS_FALSE); + if (!pn2) + return NULL; + pn2 = CheckForImmediatelyAppliedLambda(pn2); + pn->pn_op = JSOP_NEW; + pn->initList(pn2); + pn->pn_pos.begin = pn2->pn_pos.begin; + + if (tokenStream.matchToken(TOK_LP) && !argumentList(pn)) + return NULL; + if (pn->pn_count > ARGC_LIMIT) { + JS_ReportErrorNumber(context, js_GetErrorMessage, NULL, + JSMSG_TOO_MANY_CON_ARGS); + return NULL; + } + pn->pn_pos.end = pn->last()->pn_pos.end; + } else { + pn = primaryExpr(tt, JS_FALSE); + if (!pn) + return NULL; + + if (pn->pn_type == TOK_ANYNAME || + pn->pn_type == TOK_AT || + pn->pn_type == TOK_DBLCOLON) { + pn2 = NewOrRecycledNode(tc); + if (!pn2) + return NULL; + pn2->pn_type = TOK_UNARYOP; + pn2->pn_pos = pn->pn_pos; + pn2->pn_op = JSOP_XMLNAME; + pn2->pn_arity = PN_UNARY; + pn2->pn_parens = false; + pn2->pn_kid = pn; + pn = pn2; + } + } + + while ((tt = tokenStream.getToken()) > TOK_EOF) { + if (tt == TOK_DOT) { + pn2 = NameNode::create(NULL, tc); + if (!pn2) + return NULL; +#if JS_HAS_XML_SUPPORT + tt = tokenStream.getToken(TSF_OPERAND | TSF_KEYWORD_IS_NAME); + pn3 = primaryExpr(tt, JS_TRUE); + if (!pn3) + return NULL; + + /* Check both tt and pn_type, to distinguish |x.(y)| and |x.y::z| from |x.y|. */ + if (tt == TOK_NAME && pn3->pn_type == TOK_NAME) { + pn2->pn_op = JSOP_GETPROP; + pn2->pn_expr = pn; + pn2->pn_atom = pn3->pn_atom; + RecycleTree(pn3, tc); + } else { + if (tt == TOK_LP) { + pn2->pn_type = TOK_FILTER; + pn2->pn_op = JSOP_FILTER; + + /* A filtering predicate is like a with statement. */ + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } else if (TokenKindIsXML(PN_TYPE(pn3))) { + pn2->pn_type = TOK_LB; + pn2->pn_op = JSOP_GETELEM; + } else { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NAME_AFTER_DOT); + return NULL; + } + pn2->pn_arity = PN_BINARY; + pn2->pn_left = pn; + pn2->pn_right = pn3; + } +#else + MUST_MATCH_TOKEN_WITH_FLAGS(TOK_NAME, JSMSG_NAME_AFTER_DOT, TSF_KEYWORD_IS_NAME); + pn2->pn_op = JSOP_GETPROP; + pn2->pn_expr = pn; + pn2->pn_atom = tokenStream.currentToken().t_atom; +#endif + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = tokenStream.currentToken().pos.end; +#if JS_HAS_XML_SUPPORT + } else if (tt == TOK_DBLDOT) { + pn2 = BinaryNode::create(tc); + if (!pn2) + return NULL; + tt = tokenStream.getToken(TSF_OPERAND | TSF_KEYWORD_IS_NAME); + pn3 = primaryExpr(tt, JS_TRUE); + if (!pn3) + return NULL; + tt = PN_TYPE(pn3); + if (tt == TOK_NAME && !pn3->pn_parens) { + pn3->pn_type = TOK_STRING; + pn3->pn_arity = PN_NULLARY; + pn3->pn_op = JSOP_QNAMEPART; + } else if (!TokenKindIsXML(tt)) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NAME_AFTER_DOT); + return NULL; + } + pn2->pn_op = JSOP_DESCENDANTS; + pn2->pn_left = pn; + pn2->pn_right = pn3; + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = tokenStream.currentToken().pos.end; +#endif + } else if (tt == TOK_LB) { + pn2 = BinaryNode::create(tc); + if (!pn2) + return NULL; + pn3 = expr(); + if (!pn3) + return NULL; + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = tokenStream.currentToken().pos.end; + + /* + * Optimize o['p'] to o.p by rewriting pn2, but avoid rewriting + * o['0'] to use JSOP_GETPROP, to keep fast indexing disjoint in + * the interpreter from fast property access. However, if the + * bracketed string is a uint32, we rewrite pn3 to be a number + * instead of a string. + */ + do { + if (pn3->pn_type == TOK_STRING) { + jsuint index; + + if (!js_IdIsIndex(ATOM_TO_JSID(pn3->pn_atom), &index)) { + pn2->pn_type = TOK_DOT; + pn2->pn_op = JSOP_GETPROP; + pn2->pn_arity = PN_NAME; + pn2->pn_expr = pn; + pn2->pn_atom = pn3->pn_atom; + break; + } + pn3->pn_type = TOK_NUMBER; + pn3->pn_op = JSOP_DOUBLE; + pn3->pn_dval = index; + } + pn2->pn_op = JSOP_GETELEM; + pn2->pn_left = pn; + pn2->pn_right = pn3; + } while (0); + } else if (allowCallSyntax && tt == TOK_LP) { + pn2 = ListNode::create(tc); + if (!pn2) + return NULL; + pn2->pn_op = JSOP_CALL; + + pn = CheckForImmediatelyAppliedLambda(pn); + if (pn->pn_op == JSOP_NAME) { + if (pn->pn_atom == context->runtime->atomState.evalAtom) { + /* Select JSOP_EVAL and flag tc as heavyweight. */ + pn2->pn_op = JSOP_EVAL; + tc->noteCallsEval(); + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + } else if (pn->pn_op == JSOP_GETPROP) { + /* Select JSOP_FUNAPPLY given foo.apply(...). */ + if (pn->pn_atom == context->runtime->atomState.applyAtom) + pn2->pn_op = JSOP_FUNAPPLY; + else if (pn->pn_atom == context->runtime->atomState.callAtom) + pn2->pn_op = JSOP_FUNCALL; + } + + pn2->initList(pn); + pn2->pn_pos.begin = pn->pn_pos.begin; + + if (!argumentList(pn2)) + return NULL; + if (pn2->pn_count > ARGC_LIMIT) { + JS_ReportErrorNumber(context, js_GetErrorMessage, NULL, + JSMSG_TOO_MANY_FUN_ARGS); + return NULL; + } + pn2->pn_pos.end = tokenStream.currentToken().pos.end; + } else { + tokenStream.ungetToken(); + return pn; + } + + pn = pn2; + } + if (tt == TOK_ERROR) + return NULL; + return pn; +} + +JSParseNode * +Parser::bracketedExpr() +{ + uintN oldflags; + JSParseNode *pn; + + /* + * Always accept the 'in' operator in a parenthesized expression, + * where it's unambiguous, even if we might be parsing the init of a + * for statement. + */ + oldflags = tc->flags; + tc->flags &= ~TCF_IN_FOR_INIT; + pn = expr(); + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); + return pn; +} + +#if JS_HAS_XML_SUPPORT + +JSParseNode * +Parser::endBracketedExpr() +{ + JSParseNode *pn; + + pn = bracketedExpr(); + if (!pn) + return NULL; + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ATTR_EXPR); + return pn; +} + +/* + * From the ECMA-357 grammar in 11.1.1 and 11.1.2: + * + * AttributeIdentifier: + * @ PropertySelector + * @ QualifiedIdentifier + * @ [ Expression ] + * + * PropertySelector: + * Identifier + * * + * + * QualifiedIdentifier: + * PropertySelector :: PropertySelector + * PropertySelector :: [ Expression ] + * + * We adapt AttributeIdentifier and QualifiedIdentier to be LL(1), like so: + * + * AttributeIdentifier: + * @ QualifiedIdentifier + * @ [ Expression ] + * + * PropertySelector: + * Identifier + * * + * + * QualifiedIdentifier: + * PropertySelector :: PropertySelector + * PropertySelector :: [ Expression ] + * PropertySelector + * + * As PrimaryExpression: Identifier is in ECMA-262 and we want the semantics + * for that rule to result in a name node, but ECMA-357 extends the grammar + * to include PrimaryExpression: QualifiedIdentifier, we must factor further: + * + * QualifiedIdentifier: + * PropertySelector QualifiedSuffix + * + * QualifiedSuffix: + * :: PropertySelector + * :: [ Expression ] + * /nothing/ + * + * And use this production instead of PrimaryExpression: QualifiedIdentifier: + * + * PrimaryExpression: + * Identifier QualifiedSuffix + * + * We hoist the :: match into callers of QualifiedSuffix, in order to tweak + * PropertySelector vs. Identifier pn_arity, pn_op, and other members. + */ +JSParseNode * +Parser::propertySelector() +{ + JSParseNode *pn; + + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + if (pn->pn_type == TOK_STAR) { + pn->pn_type = TOK_ANYNAME; + pn->pn_op = JSOP_ANYNAME; + pn->pn_atom = context->runtime->atomState.starAtom; + } else { + JS_ASSERT(pn->pn_type == TOK_NAME); + pn->pn_op = JSOP_QNAMEPART; + pn->pn_arity = PN_NAME; + pn->pn_atom = tokenStream.currentToken().t_atom; + pn->pn_cookie.makeFree(); + } + return pn; +} + +JSParseNode * +Parser::qualifiedSuffix(JSParseNode *pn) +{ + JSParseNode *pn2, *pn3; + TokenKind tt; + + JS_ASSERT(tokenStream.currentToken().type == TOK_DBLCOLON); + pn2 = NameNode::create(NULL, tc); + if (!pn2) + return NULL; + + /* Left operand of :: must be evaluated if it is an identifier. */ + if (pn->pn_op == JSOP_QNAMEPART) + pn->pn_op = JSOP_NAME; + + tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); + if (tt == TOK_STAR || tt == TOK_NAME) { + /* Inline and specialize propertySelector for JSOP_QNAMECONST. */ + pn2->pn_op = JSOP_QNAMECONST; + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_atom = (tt == TOK_STAR) + ? context->runtime->atomState.starAtom + : tokenStream.currentToken().t_atom; + pn2->pn_expr = pn; + pn2->pn_cookie.makeFree(); + return pn2; + } + + if (tt != TOK_LB) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); + return NULL; + } + pn3 = endBracketedExpr(); + if (!pn3) + return NULL; + + pn2->pn_op = JSOP_QNAME; + pn2->pn_arity = PN_BINARY; + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = pn3->pn_pos.end; + pn2->pn_left = pn; + pn2->pn_right = pn3; + return pn2; +} + +JSParseNode * +Parser::qualifiedIdentifier() +{ + JSParseNode *pn; + + pn = propertySelector(); + if (!pn) + return NULL; + if (tokenStream.matchToken(TOK_DBLCOLON)) { + /* Hack for bug 496316. Slowing down E4X won't make it go away, alas. */ + tc->flags |= TCF_FUN_HEAVYWEIGHT; + pn = qualifiedSuffix(pn); + } + return pn; +} + +JSParseNode * +Parser::attributeIdentifier() +{ + JSParseNode *pn, *pn2; + TokenKind tt; + + JS_ASSERT(tokenStream.currentToken().type == TOK_AT); + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_op = JSOP_TOATTRNAME; + tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); + if (tt == TOK_STAR || tt == TOK_NAME) { + pn2 = qualifiedIdentifier(); + } else if (tt == TOK_LB) { + pn2 = endBracketedExpr(); + } else { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); + return NULL; + } + if (!pn2) + return NULL; + pn->pn_kid = pn2; + return pn; +} + +/* + * Make a TOK_LC unary node whose pn_kid is an expression. + */ +JSParseNode * +Parser::xmlExpr(JSBool inTag) +{ + JSParseNode *pn, *pn2; + + JS_ASSERT(tokenStream.currentToken().type == TOK_LC); + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + + /* + * Turn off XML tag mode. We save the old value of the flag because it may + * already be off: XMLExpr is called both from within a tag, and from + * within text contained in an element, but outside of any start, end, or + * point tag. + */ + bool oldflag = tokenStream.isXMLTagMode(); + tokenStream.setXMLTagMode(false); + pn2 = expr(); + if (!pn2) + return NULL; + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_XML_EXPR); + tokenStream.setXMLTagMode(oldflag); + pn->pn_kid = pn2; + pn->pn_op = inTag ? JSOP_XMLTAGEXPR : JSOP_XMLELTEXPR; + return pn; +} + +/* + * Make a terminal node for one of TOK_XMLNAME, TOK_XMLATTR, TOK_XMLSPACE, + * TOK_XMLTEXT, TOK_XMLCDATA, TOK_XMLCOMMENT, or TOK_XMLPI. When converting + * parse tree to XML, we preserve a TOK_XMLSPACE node only if it's the sole + * child of a container tag. + */ +JSParseNode * +Parser::xmlAtomNode() +{ + JSParseNode *pn = NullaryNode::create(tc); + if (!pn) + return NULL; + const Token &tok = tokenStream.currentToken(); + pn->pn_op = tok.t_op; + pn->pn_atom = tok.t_atom; + if (tok.type == TOK_XMLPI) + pn->pn_atom2 = tok.t_atom2; + return pn; +} + +/* + * Parse the productions: + * + * XMLNameExpr: + * XMLName XMLNameExpr? + * { Expr } XMLNameExpr? + * + * Return a PN_LIST, PN_UNARY, or PN_NULLARY according as XMLNameExpr produces + * a list of names and/or expressions, a single expression, or a single name. + * If PN_LIST or PN_NULLARY, pn_type will be TOK_XMLNAME; if PN_UNARY, pn_type + * will be TOK_LC. + */ +JSParseNode * +Parser::xmlNameExpr() +{ + JSParseNode *pn, *pn2, *list; + TokenKind tt; + + pn = list = NULL; + do { + tt = tokenStream.currentToken().type; + if (tt == TOK_LC) { + pn2 = xmlExpr(JS_TRUE); + if (!pn2) + return NULL; + } else { + JS_ASSERT(tt == TOK_XMLNAME); + pn2 = xmlAtomNode(); + if (!pn2) + return NULL; + } + + if (!pn) { + pn = pn2; + } else { + if (!list) { + list = ListNode::create(tc); + if (!list) + return NULL; + list->pn_type = TOK_XMLNAME; + list->pn_pos.begin = pn->pn_pos.begin; + list->initList(pn); + list->pn_xflags = PNX_CANTFOLD; + pn = list; + } + pn->pn_pos.end = pn2->pn_pos.end; + pn->append(pn2); + } + } while ((tt = tokenStream.getToken()) == TOK_XMLNAME || tt == TOK_LC); + + tokenStream.ungetToken(); + return pn; +} + +/* + * Macro to test whether an XMLNameExpr or XMLTagContent node can be folded + * at compile time into a JSXML tree. + */ +#define XML_FOLDABLE(pn) ((pn)->pn_arity == PN_LIST \ + ? ((pn)->pn_xflags & PNX_CANTFOLD) == 0 \ + : (pn)->pn_type != TOK_LC) + +/* + * Parse the productions: + * + * XMLTagContent: + * XMLNameExpr + * XMLTagContent S XMLNameExpr S? = S? XMLAttr + * XMLTagContent S XMLNameExpr S? = S? { Expr } + * + * Return a PN_LIST, PN_UNARY, or PN_NULLARY according to how XMLTagContent + * produces a list of name and attribute values and/or braced expressions, a + * single expression, or a single name. + * + * If PN_LIST or PN_NULLARY, pn_type will be TOK_XMLNAME for the case where + * XMLTagContent: XMLNameExpr. If pn_type is not TOK_XMLNAME but pn_arity is + * PN_LIST, pn_type will be tagtype. If PN_UNARY, pn_type will be TOK_LC and + * we parsed exactly one expression. + */ +JSParseNode * +Parser::xmlTagContent(TokenKind tagtype, JSAtom **namep) +{ + JSParseNode *pn, *pn2, *list; + TokenKind tt; + + pn = xmlNameExpr(); + if (!pn) + return NULL; + *namep = (pn->pn_arity == PN_NULLARY) ? pn->pn_atom : NULL; + list = NULL; + + while (tokenStream.matchToken(TOK_XMLSPACE)) { + tt = tokenStream.getToken(); + if (tt != TOK_XMLNAME && tt != TOK_LC) { + tokenStream.ungetToken(); + break; + } + + pn2 = xmlNameExpr(); + if (!pn2) + return NULL; + if (!list) { + list = ListNode::create(tc); + if (!list) + return NULL; + list->pn_type = tagtype; + list->pn_pos.begin = pn->pn_pos.begin; + list->initList(pn); + pn = list; + } + pn->append(pn2); + if (!XML_FOLDABLE(pn2)) + pn->pn_xflags |= PNX_CANTFOLD; + + tokenStream.matchToken(TOK_XMLSPACE); + MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_NO_ASSIGN_IN_XML_ATTR); + tokenStream.matchToken(TOK_XMLSPACE); + + tt = tokenStream.getToken(); + if (tt == TOK_XMLATTR) { + pn2 = xmlAtomNode(); + } else if (tt == TOK_LC) { + pn2 = xmlExpr(JS_TRUE); + pn->pn_xflags |= PNX_CANTFOLD; + } else { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_XML_ATTR_VALUE); + return NULL; + } + if (!pn2) + return NULL; + pn->pn_pos.end = pn2->pn_pos.end; + pn->append(pn2); + } + + return pn; +} + +#define XML_CHECK_FOR_ERROR_AND_EOF(tt,result) \ + JS_BEGIN_MACRO \ + if ((tt) <= TOK_EOF) { \ + if ((tt) == TOK_EOF) { \ + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_END_OF_XML_SOURCE); \ + } \ + return result; \ + } \ + JS_END_MACRO + +/* + * Consume XML element tag content, including the TOK_XMLETAGO (</) sequence + * that opens the end tag for the container. + */ +JSBool +Parser::xmlElementContent(JSParseNode *pn) +{ + tokenStream.setXMLTagMode(false); + for (;;) { + TokenKind tt = tokenStream.getToken(TSF_XMLTEXTMODE); + XML_CHECK_FOR_ERROR_AND_EOF(tt, JS_FALSE); + + JS_ASSERT(tt == TOK_XMLSPACE || tt == TOK_XMLTEXT); + JSAtom *textAtom = tokenStream.currentToken().t_atom; + if (textAtom) { + /* Non-zero-length XML text scanned. */ + JSParseNode *pn2 = xmlAtomNode(); + if (!pn2) + return JS_FALSE; + pn->pn_pos.end = pn2->pn_pos.end; + pn->append(pn2); + } + + tt = tokenStream.getToken(TSF_OPERAND); + XML_CHECK_FOR_ERROR_AND_EOF(tt, JS_FALSE); + if (tt == TOK_XMLETAGO) + break; + + JSParseNode *pn2; + if (tt == TOK_LC) { + pn2 = xmlExpr(JS_FALSE); + pn->pn_xflags |= PNX_CANTFOLD; + } else if (tt == TOK_XMLSTAGO) { + pn2 = xmlElementOrList(JS_FALSE); + if (pn2) { + pn2->pn_xflags &= ~PNX_XMLROOT; + pn->pn_xflags |= pn2->pn_xflags; + } + } else { + JS_ASSERT(tt == TOK_XMLCDATA || tt == TOK_XMLCOMMENT || + tt == TOK_XMLPI); + pn2 = xmlAtomNode(); + } + if (!pn2) + return JS_FALSE; + pn->pn_pos.end = pn2->pn_pos.end; + pn->append(pn2); + } + tokenStream.setXMLTagMode(true); + + JS_ASSERT(tokenStream.currentToken().type == TOK_XMLETAGO); + return JS_TRUE; +} + +/* + * Return a PN_LIST node containing an XML or XMLList Initialiser. + */ +JSParseNode * +Parser::xmlElementOrList(JSBool allowList) +{ + JSParseNode *pn, *pn2, *list; + TokenKind tt; + JSAtom *startAtom, *endAtom; + + JS_CHECK_RECURSION(context, return NULL); + + JS_ASSERT(tokenStream.currentToken().type == TOK_XMLSTAGO); + pn = ListNode::create(tc); + if (!pn) + return NULL; + + tokenStream.setXMLTagMode(true); + tt = tokenStream.getToken(); + if (tt == TOK_ERROR) + return NULL; + + if (tt == TOK_XMLNAME || tt == TOK_LC) { + /* + * XMLElement. Append the tag and its contents, if any, to pn. + */ + pn2 = xmlTagContent(TOK_XMLSTAGO, &startAtom); + if (!pn2) + return NULL; + tokenStream.matchToken(TOK_XMLSPACE); + + tt = tokenStream.getToken(); + if (tt == TOK_XMLPTAGC) { + /* Point tag (/>): recycle pn if pn2 is a list of tag contents. */ + if (pn2->pn_type == TOK_XMLSTAGO) { + pn->makeEmpty(); + RecycleTree(pn, tc); + pn = pn2; + } else { + JS_ASSERT(pn2->pn_type == TOK_XMLNAME || + pn2->pn_type == TOK_LC); + pn->initList(pn2); + if (!XML_FOLDABLE(pn2)) + pn->pn_xflags |= PNX_CANTFOLD; + } + pn->pn_type = TOK_XMLPTAGC; + pn->pn_xflags |= PNX_XMLROOT; + } else { + /* We had better have a tag-close (>) at this point. */ + if (tt != TOK_XMLTAGC) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_XML_TAG_SYNTAX); + return NULL; + } + pn2->pn_pos.end = tokenStream.currentToken().pos.end; + + /* Make sure pn2 is a TOK_XMLSTAGO list containing tag contents. */ + if (pn2->pn_type != TOK_XMLSTAGO) { + pn->initList(pn2); + if (!XML_FOLDABLE(pn2)) + pn->pn_xflags |= PNX_CANTFOLD; + pn2 = pn; + pn = ListNode::create(tc); + if (!pn) + return NULL; + } + + /* Now make pn a nominal-root TOK_XMLELEM list containing pn2. */ + pn->pn_type = TOK_XMLELEM; + pn->pn_pos.begin = pn2->pn_pos.begin; + pn->initList(pn2); + if (!XML_FOLDABLE(pn2)) + pn->pn_xflags |= PNX_CANTFOLD; + pn->pn_xflags |= PNX_XMLROOT; + + /* Get element contents and delimiting end-tag-open sequence. */ + if (!xmlElementContent(pn)) + return NULL; + + tt = tokenStream.getToken(); + XML_CHECK_FOR_ERROR_AND_EOF(tt, NULL); + if (tt != TOK_XMLNAME && tt != TOK_LC) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_XML_TAG_SYNTAX); + return NULL; + } + + /* Parse end tag; check mismatch at compile-time if we can. */ + pn2 = xmlTagContent(TOK_XMLETAGO, &endAtom); + if (!pn2) + return NULL; + if (pn2->pn_type == TOK_XMLETAGO) { + /* Oops, end tag has attributes! */ + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_XML_TAG_SYNTAX); + return NULL; + } + if (endAtom && startAtom && endAtom != startAtom) { + /* End vs. start tag name mismatch: point to the tag name. */ + reportErrorNumber(pn2, JSREPORT_UC | JSREPORT_ERROR, JSMSG_XML_TAG_NAME_MISMATCH, + startAtom->chars()); + return NULL; + } + + /* Make a TOK_XMLETAGO list with pn2 as its single child. */ + JS_ASSERT(pn2->pn_type == TOK_XMLNAME || pn2->pn_type == TOK_LC); + list = ListNode::create(tc); + if (!list) + return NULL; + list->pn_type = TOK_XMLETAGO; + list->initList(pn2); + pn->append(list); + if (!XML_FOLDABLE(pn2)) { + list->pn_xflags |= PNX_CANTFOLD; + pn->pn_xflags |= PNX_CANTFOLD; + } + + tokenStream.matchToken(TOK_XMLSPACE); + MUST_MATCH_TOKEN(TOK_XMLTAGC, JSMSG_BAD_XML_TAG_SYNTAX); + } + + /* Set pn_op now that pn has been updated to its final value. */ + pn->pn_op = JSOP_TOXML; + } else if (allowList && tt == TOK_XMLTAGC) { + /* XMLList Initialiser. */ + pn->pn_type = TOK_XMLLIST; + pn->pn_op = JSOP_TOXMLLIST; + pn->makeEmpty(); + pn->pn_xflags |= PNX_XMLROOT; + if (!xmlElementContent(pn)) + return NULL; + + MUST_MATCH_TOKEN(TOK_XMLTAGC, JSMSG_BAD_XML_LIST_SYNTAX); + } else { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_XML_NAME_SYNTAX); + return NULL; + } + tokenStream.setXMLTagMode(false); + + pn->pn_pos.end = tokenStream.currentToken().pos.end; + return pn; +} + +JSParseNode * +Parser::xmlElementOrListRoot(JSBool allowList) +{ + /* + * Force XML support to be enabled so that comments and CDATA literals + * are recognized, instead of <! followed by -- starting an HTML comment + * to end of line (used in script tags to hide content from old browsers + * that don't recognize <script>). + */ + bool hadXML = tokenStream.hasXML(); + tokenStream.setXML(true); + JSParseNode *pn = xmlElementOrList(allowList); + tokenStream.setXML(hadXML); + return pn; +} + +JSParseNode * +Parser::parseXMLText(JSObject *chain, bool allowList) +{ + /* + * Push a compiler frame if we have no frames, or if the top frame is a + * lightweight function activation, or if its scope chain doesn't match + * the one passed to us. + */ + JSTreeContext xmltc(this); + xmltc.setScopeChain(chain); + + /* Set XML-only mode to turn off special treatment of {expr} in XML. */ + tokenStream.setXMLOnlyMode(); + TokenKind tt = tokenStream.getToken(TSF_OPERAND); + + JSParseNode *pn; + if (tt != TOK_XMLSTAGO) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_XML_MARKUP); + pn = NULL; + } else { + pn = xmlElementOrListRoot(allowList); + } + tokenStream.setXMLOnlyMode(false); + + return pn; +} + +#endif /* JS_HAS_XMLSUPPORT */ + +#if JS_HAS_BLOCK_SCOPE +/* + * Check whether blockid is an active scoping statement in tc. This code is + * necessary to qualify tc->decls.lookup() hits in primaryExpr's TOK_NAME case + * (below) where the hits come from Scheme-ish let bindings in for loop heads + * and let blocks and expressions (not let declarations). + * + * Unlike let declarations ("let as the new var"), which is a kind of letrec + * due to hoisting, let in a for loop head, let block, or let expression acts + * like Scheme's let: initializers are evaluated without the new let bindings + * being in scope. + * + * Name binding analysis is eager with fixups, rather than multi-pass, and let + * bindings push on the front of the tc->decls JSAtomList (either the singular + * list or on a hash chain -- see JSAtomList::AddHow) in order to shadow outer + * scope bindings of the same name. + * + * This simplifies binding lookup code at the price of a linear search here, + * but only if code uses let (var predominates), and even then this function's + * loop iterates more than once only in crazy cases. + */ +static inline bool +BlockIdInScope(uintN blockid, JSTreeContext *tc) +{ + if (blockid > tc->blockid()) + return false; + for (JSStmtInfo *stmt = tc->topScopeStmt; stmt; stmt = stmt->downScope) { + if (stmt->blockid == blockid) + return true; + } + return false; +} +#endif + +bool +JSParseNode::isConstant() +{ + switch (pn_type) { + case TOK_NUMBER: + case TOK_STRING: + return true; + case TOK_PRIMARY: + switch (pn_op) { + case JSOP_NULL: + case JSOP_FALSE: + case JSOP_TRUE: + return true; + default: + return false; + } + case TOK_RB: + case TOK_RC: + return (pn_op == JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST); + default: + return false; + } +} + +JSParseNode * +Parser::primaryExpr(TokenKind tt, JSBool afterDot) +{ + JSParseNode *pn, *pn2, *pn3; + JSOp op; + + JS_CHECK_RECURSION(context, return NULL); + + switch (tt) { + case TOK_FUNCTION: +#if JS_HAS_XML_SUPPORT + if (tokenStream.matchToken(TOK_DBLCOLON, TSF_KEYWORD_IS_NAME)) { + pn2 = NullaryNode::create(tc); + if (!pn2) + return NULL; + pn2->pn_type = TOK_FUNCTION; + pn = qualifiedSuffix(pn2); + if (!pn) + return NULL; + break; + } +#endif + pn = functionExpr(); + if (!pn) + return NULL; + break; + + case TOK_LB: + { + JSBool matched; + jsuint index; + + pn = ListNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_RB; + pn->pn_op = JSOP_NEWINIT; + pn->makeEmpty(); + +#if JS_HAS_GENERATORS + pn->pn_blockid = tc->blockidGen; +#endif + + matched = tokenStream.matchToken(TOK_RB, TSF_OPERAND); + if (!matched) { + for (index = 0; ; index++) { + if (index == JS_ARGS_LENGTH_MAX) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ARRAY_INIT_TOO_BIG); + return NULL; + } + + tt = tokenStream.peekToken(TSF_OPERAND); + if (tt == TOK_RB) { + pn->pn_xflags |= PNX_ENDCOMMA; + break; + } + + if (tt == TOK_COMMA) { + /* So CURRENT_TOKEN gets TOK_COMMA and not TOK_LB. */ + tokenStream.matchToken(TOK_COMMA); + pn2 = NullaryNode::create(tc); + pn->pn_xflags |= PNX_HOLEY | PNX_NONCONST; + } else { + pn2 = assignExpr(); + if (pn2 && !pn2->isConstant()) + pn->pn_xflags |= PNX_NONCONST; + } + if (!pn2) + return NULL; + pn->append(pn2); + + if (tt != TOK_COMMA) { + /* If we didn't already match TOK_COMMA in above case. */ + if (!tokenStream.matchToken(TOK_COMMA)) + break; + } + } + +#if JS_HAS_GENERATORS + /* + * At this point, (index == 0 && pn->pn_count != 0) implies one + * element initialiser was parsed. + * + * An array comprehension of the form: + * + * [i * j for (i in o) for (j in p) if (i != j)] + * + * translates to roughly the following let expression: + * + * let (array = new Array, i, j) { + * for (i in o) let { + * for (j in p) + * if (i != j) + * array.push(i * j) + * } + * array + * } + * + * where array is a nameless block-local variable. The "roughly" + * means that an implementation may optimize away the array.push. + * An array comprehension opens exactly one block scope, no matter + * how many for heads it contains. + * + * Each let () {...} or for (let ...) ... compiles to: + * + * JSOP_ENTERBLOCK <o> ... JSOP_LEAVEBLOCK <n> + * + * where <o> is a literal object representing the block scope, + * with <n> properties, naming each var declared in the block. + * + * Each var declaration in a let-block binds a name in <o> at + * compile time, and allocates a slot on the operand stack at + * runtime via JSOP_ENTERBLOCK. A block-local var is accessed + * by the JSOP_GETLOCAL and JSOP_SETLOCAL ops, and iterated with + * JSOP_FORLOCAL. These ops all have an immediate operand, the + * local slot's stack index from fp->spbase. + * + * The array comprehension iteration step, array.push(i * j) in + * the example above, is done by <i * j>; JSOP_ARRAYCOMP <array>, + * where <array> is the index of array's stack slot. + */ + if (index == 0 && pn->pn_count != 0 && tokenStream.matchToken(TOK_FOR)) { + JSParseNode *pnexp, *pntop; + + /* Relabel pn as an array comprehension node. */ + pn->pn_type = TOK_ARRAYCOMP; + + /* + * Remove the comprehension expression from pn's linked list + * and save it via pnexp. We'll re-install it underneath the + * ARRAYPUSH node after we parse the rest of the comprehension. + */ + pnexp = pn->last(); + JS_ASSERT(pn->pn_count == 1); + pn->pn_count = 0; + pn->pn_tail = &pn->pn_head; + *pn->pn_tail = NULL; + + pntop = comprehensionTail(pnexp, pn->pn_blockid, + TOK_ARRAYPUSH, JSOP_ARRAYPUSH); + if (!pntop) + return NULL; + pn->append(pntop); + } +#endif /* JS_HAS_GENERATORS */ + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_LIST); + } + pn->pn_pos.end = tokenStream.currentToken().pos.end; + return pn; + } + + case TOK_LC: + { + JSBool afterComma; + JSParseNode *pnval; + + /* + * A map from property names we've seen thus far to a mask of property + * assignment types, stored and retrieved with ALE_SET_INDEX/ALE_INDEX. + */ + JSAutoAtomList seen(tc->parser); + enum AssignmentType { + GET = 0x1, + SET = 0x2, + VALUE = 0x4 | GET | SET + }; + + pn = ListNode::create(tc); + if (!pn) + return NULL; + pn->pn_type = TOK_RC; + pn->pn_op = JSOP_NEWINIT; + pn->makeEmpty(); + + afterComma = JS_FALSE; + for (;;) { + JSAtom *atom; + tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); + switch (tt) { + case TOK_NUMBER: + pn3 = NullaryNode::create(tc); + if (!pn3) + return NULL; + pn3->pn_dval = tokenStream.currentToken().t_dval; + if (!js_ValueToAtom(context, DoubleValue(pn3->pn_dval), &atom)) + return NULL; + break; + case TOK_NAME: + { + atom = tokenStream.currentToken().t_atom; + if (atom == context->runtime->atomState.getAtom) + op = JSOP_GETTER; + else if (atom == context->runtime->atomState.setAtom) + op = JSOP_SETTER; + else + goto property_name; + + tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); + if (tt == TOK_NAME || tt == TOK_STRING) { + atom = tokenStream.currentToken().t_atom; + pn3 = NameNode::create(atom, tc); + if (!pn3) + return NULL; + } else if (tt == TOK_NUMBER) { + pn3 = NullaryNode::create(tc); + if (!pn3) + return NULL; + pn3->pn_dval = tokenStream.currentToken().t_dval; + if (!js_ValueToAtom(context, DoubleValue(pn3->pn_dval), &atom)) + return NULL; + } else { + tokenStream.ungetToken(); + goto property_name; + } + + pn->pn_xflags |= PNX_NONCONST; + + /* NB: Getter function in { get x(){} } is unnamed. */ + pn2 = functionDef(NULL, op == JSOP_SETTER ? SETTER : GETTER, JSFUN_LAMBDA); + pn2 = JSParseNode::newBinaryOrAppend(TOK_COLON, op, pn3, pn2, tc); + goto skip; + } + property_name: + case TOK_STRING: + atom = tokenStream.currentToken().t_atom; + pn3 = NullaryNode::create(tc); + if (!pn3) + return NULL; + pn3->pn_atom = atom; + break; + case TOK_RC: + goto end_obj_init; + default: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_PROP_ID); + return NULL; + } + + op = JSOP_INITPROP; + tt = tokenStream.getToken(); + if (tt == TOK_COLON) { + pnval = assignExpr(); + if (pnval && !pnval->isConstant()) + pn->pn_xflags |= PNX_NONCONST; + } else { +#if JS_HAS_DESTRUCTURING_SHORTHAND + if (tt != TOK_COMMA && tt != TOK_RC) { +#endif + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_COLON_AFTER_ID); + return NULL; +#if JS_HAS_DESTRUCTURING_SHORTHAND + } + + /* + * Support, e.g., |var {x, y} = o| as destructuring shorthand + * for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8. + */ + tokenStream.ungetToken(); + pn->pn_xflags |= PNX_DESTRUCT | PNX_NONCONST; + pnval = pn3; + if (pnval->pn_type == TOK_NAME) { + pnval->pn_arity = PN_NAME; + ((NameNode *)pnval)->initCommon(tc); + } +#endif + } + + pn2 = JSParseNode::newBinaryOrAppend(TOK_COLON, op, pn3, pnval, tc); + skip: + if (!pn2) + return NULL; + pn->append(pn2); + + /* + * Check for duplicate property names. Duplicate data properties + * only conflict in strict mode. Duplicate getter or duplicate + * setter halves always conflict. A data property conflicts with + * any part of an accessor property. + */ + AssignmentType assignType; + if (op == JSOP_INITPROP) { + assignType = VALUE; + } else if (op == JSOP_GETTER) { + assignType = GET; + } else if (op == JSOP_SETTER) { + assignType = SET; + } else { + JS_NOT_REACHED("bad opcode in object initializer"); + assignType = VALUE; /* try to error early */ + } + + if (JSAtomListElement *ale = seen.lookup(atom)) { + AssignmentType oldAssignType = AssignmentType(ALE_INDEX(ale)); + if ((oldAssignType & assignType) && + (oldAssignType != VALUE || assignType != VALUE || tc->needStrictChecks())) + { + JSAutoByteString name; + if (!js_AtomToPrintableString(context, atom, &name)) + return NULL; + + uintN flags = (oldAssignType == VALUE && + assignType == VALUE && + !tc->inStrictMode()) + ? JSREPORT_WARNING + : JSREPORT_ERROR; + if (!ReportCompileErrorNumber(context, &tokenStream, NULL, flags, + JSMSG_DUPLICATE_PROPERTY, name.ptr())) + { + return NULL; + } + } + ALE_SET_INDEX(ale, assignType | oldAssignType); + } else { + ale = seen.add(tc->parser, atom); + if (!ale) + return NULL; + ALE_SET_INDEX(ale, assignType); + } + + tt = tokenStream.getToken(); + if (tt == TOK_RC) + goto end_obj_init; + if (tt != TOK_COMMA) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_CURLY_AFTER_LIST); + return NULL; + } + afterComma = JS_TRUE; + } + + end_obj_init: + pn->pn_pos.end = tokenStream.currentToken().pos.end; + return pn; + } + +#if JS_HAS_BLOCK_SCOPE + case TOK_LET: + pn = letBlock(JS_FALSE); + if (!pn) + return NULL; + break; +#endif + +#if JS_HAS_SHARP_VARS + case TOK_DEFSHARP: + pn = UnaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_num = (jsint) tokenStream.currentToken().t_dval; + tt = tokenStream.getToken(TSF_OPERAND); + pn->pn_kid = primaryExpr(tt, JS_FALSE); + if (!pn->pn_kid) + return NULL; + if (PN_TYPE(pn->pn_kid) == TOK_USESHARP || + PN_TYPE(pn->pn_kid) == TOK_DEFSHARP || + PN_TYPE(pn->pn_kid) == TOK_STRING || + PN_TYPE(pn->pn_kid) == TOK_NUMBER || + PN_TYPE(pn->pn_kid) == TOK_PRIMARY) { + reportErrorNumber(pn->pn_kid, JSREPORT_ERROR, JSMSG_BAD_SHARP_VAR_DEF); + return NULL; + } + if (!tc->ensureSharpSlots()) + return NULL; + break; + + case TOK_USESHARP: + /* Check for forward/dangling references at runtime, to allow eval. */ + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + if (!tc->ensureSharpSlots()) + return NULL; + pn->pn_num = (jsint) tokenStream.currentToken().t_dval; + break; +#endif /* JS_HAS_SHARP_VARS */ + + case TOK_LP: + { + JSBool genexp; + + pn = parenExpr(&genexp); + if (!pn) + return NULL; + pn->pn_parens = true; + if (!genexp) + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); + break; + } + +#if JS_HAS_XML_SUPPORT + case TOK_STAR: + pn = qualifiedIdentifier(); + if (!pn) + return NULL; + break; + + case TOK_AT: + pn = attributeIdentifier(); + if (!pn) + return NULL; + break; + + case TOK_XMLSTAGO: + pn = xmlElementOrListRoot(JS_TRUE); + if (!pn) + return NULL; + break; +#endif /* JS_HAS_XML_SUPPORT */ + + case TOK_STRING: +#if JS_HAS_SHARP_VARS + /* FALL THROUGH */ +#endif + +#if JS_HAS_XML_SUPPORT + case TOK_XMLCDATA: + case TOK_XMLCOMMENT: + case TOK_XMLPI: +#endif + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_atom = tokenStream.currentToken().t_atom; +#if JS_HAS_XML_SUPPORT + if (tt == TOK_XMLPI) + pn->pn_atom2 = tokenStream.currentToken().t_atom2; + else +#endif + pn->pn_op = tokenStream.currentToken().t_op; + break; + + case TOK_NAME: + pn = NameNode::create(tokenStream.currentToken().t_atom, tc); + if (!pn) + return NULL; + JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NAME); + pn->pn_op = JSOP_NAME; + + if ((tc->flags & (TCF_IN_FUNCTION | TCF_FUN_PARAM_ARGUMENTS)) == TCF_IN_FUNCTION && + pn->pn_atom == context->runtime->atomState.argumentsAtom) { + /* + * Flag arguments usage so we can avoid unsafe optimizations such + * as formal parameter assignment analysis (because of the hated + * feature whereby arguments alias formals). We do this even for + * a reference of the form foo.arguments, which ancient code may + * still use instead of arguments (more hate). + */ + tc->noteArgumentsUse(); + + /* + * Bind early to JSOP_ARGUMENTS to relieve later code from having + * to do this work (new rule for the emitter to count on). + */ + if (!afterDot && !(tc->flags & TCF_DECL_DESTRUCTURING) && !tc->inStatement(STMT_WITH)) { + pn->pn_op = JSOP_ARGUMENTS; + pn->pn_dflags |= PND_BOUND; + } + } else if ((!afterDot +#if JS_HAS_XML_SUPPORT + || tokenStream.peekToken() == TOK_DBLCOLON +#endif + ) && !(tc->flags & TCF_DECL_DESTRUCTURING)) { + JSStmtInfo *stmt = js_LexicalLookup(tc, pn->pn_atom, NULL); + + JSDefinition *dn; + + JSAtomListElement *ale = tc->decls.lookup(pn->pn_atom); + if (ale) { + dn = ALE_DEFN(ale); +#if JS_HAS_BLOCK_SCOPE + /* + * Skip out-of-scope let bindings along an ALE list or hash + * chain. These can happen due to |let (x = x) x| block and + * expression bindings, where the x on the right of = comes + * from an outer scope. See bug 496532. + */ + while (dn->isLet() && !BlockIdInScope(dn->pn_blockid, tc)) { + do { + ale = ALE_NEXT(ale); + } while (ale && ALE_ATOM(ale) != pn->pn_atom); + if (!ale) + break; + dn = ALE_DEFN(ale); + } +#endif + } + + if (ale) { + dn = ALE_DEFN(ale); + } else { + ale = tc->lexdeps.lookup(pn->pn_atom); + if (ale) { + dn = ALE_DEFN(ale); + } else { + /* + * No definition before this use in any lexical scope. + * Add a mapping in tc->lexdeps from pn->pn_atom to a + * new node for the forward-referenced definition. This + * placeholder definition node will be adopted when we + * parse the real defining declaration form, or left as + * a free variable definition if we never see the real + * definition. + */ + ale = MakePlaceholder(pn, tc); + if (!ale) + return NULL; + dn = ALE_DEFN(ale); + + /* + * In case this is a forward reference to a function, + * we pessimistically set PND_FUNARG if the next token + * is not a left parenthesis. + * + * If the definition eventually parsed into dn is not a + * function, this flag won't hurt, and if we do parse a + * function with pn's name, then the PND_FUNARG flag is + * necessary for safe context->display-based optimiza- + * tion of the closure's static link. + */ + if (tokenStream.peekToken() != TOK_LP) + dn->pn_dflags |= PND_FUNARG; + } + } + + JS_ASSERT(dn->pn_defn); + LinkUseToDef(pn, dn, tc); + + /* Here we handle the backward function reference case. */ + if (tokenStream.peekToken() != TOK_LP) + dn->pn_dflags |= PND_FUNARG; + + pn->pn_dflags |= (dn->pn_dflags & PND_FUNARG); + if (stmt && stmt->type == STMT_WITH) + pn->pn_dflags |= PND_DEOPTIMIZED; + } + +#if JS_HAS_XML_SUPPORT + if (tokenStream.matchToken(TOK_DBLCOLON)) { + if (afterDot) { + /* + * Here primaryExpr is called after . or .. followed by a name + * followed by ::. This is the only case where a keyword after + * . or .. is not treated as a property name. + */ + const KeywordInfo *ki = FindKeyword(pn->pn_atom->charsZ(), pn->pn_atom->length()); + if (ki) { + if (ki->tokentype != TOK_FUNCTION) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_KEYWORD_NOT_NS); + return NULL; + } + + pn->pn_arity = PN_NULLARY; + pn->pn_type = TOK_FUNCTION; + } + } + pn = qualifiedSuffix(pn); + if (!pn) + return NULL; + } +#endif + break; + + case TOK_REGEXP: + { + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + + JSObject *obj; + if (context->hasfp()) { + obj = RegExp::createObject(context, context->regExpStatics(), + tokenStream.getTokenbuf().begin(), + tokenStream.getTokenbuf().length(), + tokenStream.currentToken().t_reflags); + } else { + obj = RegExp::createObjectNoStatics(context, + tokenStream.getTokenbuf().begin(), + tokenStream.getTokenbuf().length(), + tokenStream.currentToken().t_reflags); + } + + if (!obj) + return NULL; + if (!tc->compileAndGo()) { + obj->clearParent(); + obj->clearProto(); + } + + pn->pn_objbox = tc->parser->newObjectBox(obj); + if (!pn->pn_objbox) + return NULL; + + pn->pn_op = JSOP_REGEXP; + break; + } + + case TOK_NUMBER: + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_op = JSOP_DOUBLE; + pn->pn_dval = tokenStream.currentToken().t_dval; + break; + + case TOK_PRIMARY: + pn = NullaryNode::create(tc); + if (!pn) + return NULL; + pn->pn_op = tokenStream.currentToken().t_op; + break; + + case TOK_ERROR: + /* The scanner or one of its subroutines reported the error. */ + return NULL; + + default: + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); + return NULL; + } + return pn; +} + +JSParseNode * +Parser::parenExpr(JSBool *genexp) +{ + TokenPtr begin; + JSParseNode *pn; + + JS_ASSERT(tokenStream.currentToken().type == TOK_LP); + begin = tokenStream.currentToken().pos.begin; + + if (genexp) + *genexp = JS_FALSE; + pn = bracketedExpr(); + if (!pn) + return NULL; + +#if JS_HAS_GENERATOR_EXPRS + if (tokenStream.matchToken(TOK_FOR)) { + if (pn->pn_type == TOK_YIELD && !pn->pn_parens) { + reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); + return NULL; + } + if (pn->pn_type == TOK_COMMA && !pn->pn_parens) { + reportErrorNumber(pn->last(), JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, + js_generator_str); + return NULL; + } + pn = generatorExpr(pn); + if (!pn) + return NULL; + pn->pn_pos.begin = begin; + if (genexp) { + if (tokenStream.getToken() != TOK_RP) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, + js_generator_str); + return NULL; + } + pn->pn_pos.end = tokenStream.currentToken().pos.end; + *genexp = JS_TRUE; + } + } +#endif /* JS_HAS_GENERATOR_EXPRS */ + + return pn; +} + +/* + * Fold from one constant type to another. + * XXX handles only strings and numbers for now + */ +static JSBool +FoldType(JSContext *cx, JSParseNode *pn, TokenKind type) +{ + if (PN_TYPE(pn) != type) { + switch (type) { + case TOK_NUMBER: + if (pn->pn_type == TOK_STRING) { + jsdouble d; + if (!ValueToNumber(cx, StringValue(ATOM_TO_STRING(pn->pn_atom)), &d)) + return JS_FALSE; + pn->pn_dval = d; + pn->pn_type = TOK_NUMBER; + pn->pn_op = JSOP_DOUBLE; + } + break; + + case TOK_STRING: + if (pn->pn_type == TOK_NUMBER) { + JSString *str = js_NumberToString(cx, pn->pn_dval); + if (!str) + return JS_FALSE; + pn->pn_atom = js_AtomizeString(cx, str, 0); + if (!pn->pn_atom) + return JS_FALSE; + pn->pn_type = TOK_STRING; + pn->pn_op = JSOP_STRING; + } + break; + + default:; + } + } + return JS_TRUE; +} + +/* + * Fold two numeric constants. Beware that pn1 and pn2 are recycled, unless + * one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after + * a successful call to this function. + */ +static JSBool +FoldBinaryNumeric(JSContext *cx, JSOp op, JSParseNode *pn1, JSParseNode *pn2, + JSParseNode *pn, JSTreeContext *tc) +{ + jsdouble d, d2; + int32 i, j; + + JS_ASSERT(pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER); + d = pn1->pn_dval; + d2 = pn2->pn_dval; + switch (op) { + case JSOP_LSH: + case JSOP_RSH: + i = js_DoubleToECMAInt32(d); + j = js_DoubleToECMAInt32(d2); + j &= 31; + d = (op == JSOP_LSH) ? i << j : i >> j; + break; + + case JSOP_URSH: + j = js_DoubleToECMAInt32(d2); + j &= 31; + d = js_DoubleToECMAUint32(d) >> j; + break; + + case JSOP_ADD: + d += d2; + break; + + case JSOP_SUB: + d -= d2; + break; + + case JSOP_MUL: + d *= d2; + break; + + case JSOP_DIV: + if (d2 == 0) { +#if defined(XP_WIN) + /* XXX MSVC miscompiles such that (NaN == 0) */ + if (JSDOUBLE_IS_NaN(d2)) + d = js_NaN; + else +#endif + if (d == 0 || JSDOUBLE_IS_NaN(d)) + d = js_NaN; + else if (JSDOUBLE_IS_NEG(d) != JSDOUBLE_IS_NEG(d2)) + d = js_NegativeInfinity; + else + d = js_PositiveInfinity; + } else { + d /= d2; + } + break; + + case JSOP_MOD: + if (d2 == 0) { + d = js_NaN; + } else { + d = js_fmod(d, d2); + } + break; + + default:; + } + + /* Take care to allow pn1 or pn2 to alias pn. */ + if (pn1 != pn) + RecycleTree(pn1, tc); + if (pn2 != pn) + RecycleTree(pn2, tc); + pn->pn_type = TOK_NUMBER; + pn->pn_op = JSOP_DOUBLE; + pn->pn_arity = PN_NULLARY; + pn->pn_dval = d; + return JS_TRUE; +} + +#if JS_HAS_XML_SUPPORT + +static JSBool +FoldXMLConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc) +{ + TokenKind tt; + JSParseNode **pnp, *pn1, *pn2; + JSString *accum, *str; + uint32 i, j; + + JS_ASSERT(pn->pn_arity == PN_LIST); + tt = PN_TYPE(pn); + pnp = &pn->pn_head; + pn1 = *pnp; + accum = NULL; + str = NULL; + if ((pn->pn_xflags & PNX_CANTFOLD) == 0) { + if (tt == TOK_XMLETAGO) + accum = ATOM_TO_STRING(cx->runtime->atomState.etagoAtom); + else if (tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC) + accum = ATOM_TO_STRING(cx->runtime->atomState.stagoAtom); + } + + /* + * GC Rooting here is tricky: for most of the loop, |accum| is safe via + * the newborn string root. However, when |pn2->pn_type| is TOK_XMLCDATA, + * TOK_XMLCOMMENT, or TOK_XMLPI it is knocked out of the newborn root. + * Therefore, we have to add additonal protection from GC nesting under + * js_ConcatStrings. + */ + for (pn2 = pn1, i = j = 0; pn2; pn2 = pn2->pn_next, i++) { + /* The parser already rejected end-tags with attributes. */ + JS_ASSERT(tt != TOK_XMLETAGO || i == 0); + switch (pn2->pn_type) { + case TOK_XMLATTR: + if (!accum) + goto cantfold; + /* FALL THROUGH */ + case TOK_XMLNAME: + case TOK_XMLSPACE: + case TOK_XMLTEXT: + case TOK_STRING: + if (pn2->pn_arity == PN_LIST) + goto cantfold; + str = ATOM_TO_STRING(pn2->pn_atom); + break; + + case TOK_XMLCDATA: + str = js_MakeXMLCDATAString(cx, ATOM_TO_STRING(pn2->pn_atom)); + if (!str) + return JS_FALSE; + break; + + case TOK_XMLCOMMENT: + str = js_MakeXMLCommentString(cx, ATOM_TO_STRING(pn2->pn_atom)); + if (!str) + return JS_FALSE; + break; + + case TOK_XMLPI: + str = js_MakeXMLPIString(cx, ATOM_TO_STRING(pn2->pn_atom), + ATOM_TO_STRING(pn2->pn_atom2)); + if (!str) + return JS_FALSE; + break; + + cantfold: + default: + JS_ASSERT(*pnp == pn1); + if ((tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC) && + (i & 1) ^ (j & 1)) { +#ifdef DEBUG_brendanXXX + printf("1: %d, %d => ", i, j); + if (accum) + FileEscapedString(stdout, accum, 0); + else + fputs("NULL", stdout); + fputc('\n', stdout); +#endif + } else if (accum && pn1 != pn2) { + while (pn1->pn_next != pn2) { + pn1 = RecycleTree(pn1, tc); + --pn->pn_count; + } + pn1->pn_type = TOK_XMLTEXT; + pn1->pn_op = JSOP_STRING; + pn1->pn_arity = PN_NULLARY; + pn1->pn_atom = js_AtomizeString(cx, accum, 0); + if (!pn1->pn_atom) + return JS_FALSE; + JS_ASSERT(pnp != &pn1->pn_next); + *pnp = pn1; + } + pnp = &pn2->pn_next; + pn1 = *pnp; + accum = NULL; + continue; + } + + if (accum) { + { + AutoStringRooter tvr(cx, accum); + str = ((tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC) && i != 0) + ? js_AddAttributePart(cx, i & 1, accum, str) + : js_ConcatStrings(cx, accum, str); + } + if (!str) + return JS_FALSE; +#ifdef DEBUG_brendanXXX + printf("2: %d, %d => ", i, j); + FileEscapedString(stdout, str, 0); + printf(" (%u)\n", str->length()); +#endif + ++j; + } + accum = str; + } + + if (accum) { + str = NULL; + if ((pn->pn_xflags & PNX_CANTFOLD) == 0) { + if (tt == TOK_XMLPTAGC) + str = ATOM_TO_STRING(cx->runtime->atomState.ptagcAtom); + else if (tt == TOK_XMLSTAGO || tt == TOK_XMLETAGO) + str = ATOM_TO_STRING(cx->runtime->atomState.tagcAtom); + } + if (str) { + accum = js_ConcatStrings(cx, accum, str); + if (!accum) + return JS_FALSE; + } + + JS_ASSERT(*pnp == pn1); + while (pn1->pn_next) { + pn1 = RecycleTree(pn1, tc); + --pn->pn_count; + } + pn1->pn_type = TOK_XMLTEXT; + pn1->pn_op = JSOP_STRING; + pn1->pn_arity = PN_NULLARY; + pn1->pn_atom = js_AtomizeString(cx, accum, 0); + if (!pn1->pn_atom) + return JS_FALSE; + JS_ASSERT(pnp != &pn1->pn_next); + *pnp = pn1; + } + + if (pn1 && pn->pn_count == 1) { + /* + * Only one node under pn, and it has been folded: move pn1 onto pn + * unless pn is an XML root (in which case we need it to tell the code + * generator to emit a JSOP_TOXML or JSOP_TOXMLLIST op). If pn is an + * XML root *and* it's a point-tag, rewrite it to TOK_XMLELEM to avoid + * extra "<" and "/>" bracketing at runtime. + */ + if (!(pn->pn_xflags & PNX_XMLROOT)) { + pn->become(pn1); + } else if (tt == TOK_XMLPTAGC) { + pn->pn_type = TOK_XMLELEM; + pn->pn_op = JSOP_TOXML; + } + } + return JS_TRUE; +} + +#endif /* JS_HAS_XML_SUPPORT */ + +static int +Boolish(JSParseNode *pn) +{ + switch (pn->pn_op) { + case JSOP_DOUBLE: + return pn->pn_dval != 0 && !JSDOUBLE_IS_NaN(pn->pn_dval); + + case JSOP_STRING: + return ATOM_TO_STRING(pn->pn_atom)->length() != 0; + +#if JS_HAS_GENERATOR_EXPRS + case JSOP_CALL: + { + /* + * A generator expression as an if or loop condition has no effects, it + * simply results in a truthy object reference. This condition folding + * is needed for the decompiler. See bug 442342 and bug 443074. + */ + if (pn->pn_count != 1) + break; + JSParseNode *pn2 = pn->pn_head; + if (pn2->pn_type != TOK_FUNCTION) + break; + if (!(pn2->pn_funbox->tcflags & TCF_GENEXP_LAMBDA)) + break; + /* FALL THROUGH */ + } +#endif + + case JSOP_DEFFUN: + case JSOP_LAMBDA: + case JSOP_TRUE: + return 1; + + case JSOP_NULL: + case JSOP_FALSE: + return 0; + + default:; + } + return -1; +} + +JSBool +js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc, bool inCond) +{ + JSParseNode *pn1 = NULL, *pn2 = NULL, *pn3 = NULL; + + JS_CHECK_RECURSION(cx, return JS_FALSE); + + switch (pn->pn_arity) { + case PN_FUNC: + { + uint32 oldflags = tc->flags; + JSFunctionBox *oldlist = tc->functionList; + + tc->flags = pn->pn_funbox->tcflags; + tc->functionList = pn->pn_funbox->kids; + if (!js_FoldConstants(cx, pn->pn_body, tc)) + return JS_FALSE; + pn->pn_funbox->kids = tc->functionList; + tc->flags = oldflags; + tc->functionList = oldlist; + break; + } + + case PN_LIST: + { + /* Propagate inCond through logical connectives. */ + bool cond = inCond && (pn->pn_type == TOK_OR || pn->pn_type == TOK_AND); + + /* Don't fold a parenthesized call expression. See bug 537673. */ + pn1 = pn2 = pn->pn_head; + if ((pn->pn_type == TOK_LP || pn->pn_type == TOK_NEW) && pn2->pn_parens) + pn2 = pn2->pn_next; + + /* Save the list head in pn1 for later use. */ + for (; pn2; pn2 = pn2->pn_next) { + if (!js_FoldConstants(cx, pn2, tc, cond)) + return JS_FALSE; + } + break; + } + + case PN_TERNARY: + /* Any kid may be null (e.g. for (;;)). */ + pn1 = pn->pn_kid1; + pn2 = pn->pn_kid2; + pn3 = pn->pn_kid3; + if (pn1 && !js_FoldConstants(cx, pn1, tc, pn->pn_type == TOK_IF)) + return JS_FALSE; + if (pn2) { + if (!js_FoldConstants(cx, pn2, tc, pn->pn_type == TOK_FORHEAD)) + return JS_FALSE; + if (pn->pn_type == TOK_FORHEAD && pn2->pn_op == JSOP_TRUE) { + RecycleTree(pn2, tc); + pn->pn_kid2 = NULL; + } + } + if (pn3 && !js_FoldConstants(cx, pn3, tc)) + return JS_FALSE; + break; + + case PN_BINARY: + pn1 = pn->pn_left; + pn2 = pn->pn_right; + + /* Propagate inCond through logical connectives. */ + if (pn->pn_type == TOK_OR || pn->pn_type == TOK_AND) { + if (!js_FoldConstants(cx, pn1, tc, inCond)) + return JS_FALSE; + if (!js_FoldConstants(cx, pn2, tc, inCond)) + return JS_FALSE; + break; + } + + /* First kid may be null (for default case in switch). */ + if (pn1 && !js_FoldConstants(cx, pn1, tc, pn->pn_type == TOK_WHILE)) + return JS_FALSE; + if (!js_FoldConstants(cx, pn2, tc, pn->pn_type == TOK_DO)) + return JS_FALSE; + break; + + case PN_UNARY: + pn1 = pn->pn_kid; + + /* + * Kludge to deal with typeof expressions: because constant folding + * can turn an expression into a name node, we have to check here, + * before folding, to see if we should throw undefined name errors. + * + * NB: We know that if pn->pn_op is JSOP_TYPEOF, pn1 will not be + * null. This assumption does not hold true for other unary + * expressions. + */ + if (pn->pn_op == JSOP_TYPEOF && pn1->pn_type != TOK_NAME) + pn->pn_op = JSOP_TYPEOFEXPR; + + if (pn1 && !js_FoldConstants(cx, pn1, tc, pn->pn_op == JSOP_NOT)) + return JS_FALSE; + break; + + case PN_NAME: + /* + * Skip pn1 down along a chain of dotted member expressions to avoid + * excessive recursion. Our only goal here is to fold constants (if + * any) in the primary expression operand to the left of the first + * dot in the chain. + */ + if (!pn->pn_used) { + pn1 = pn->pn_expr; + while (pn1 && pn1->pn_arity == PN_NAME && !pn1->pn_used) + pn1 = pn1->pn_expr; + if (pn1 && !js_FoldConstants(cx, pn1, tc)) + return JS_FALSE; + } + break; + + case PN_NAMESET: + pn1 = pn->pn_tree; + if (!js_FoldConstants(cx, pn1, tc)) + return JS_FALSE; + break; + + case PN_NULLARY: + break; + } + + switch (pn->pn_type) { + case TOK_IF: + if (ContainsStmt(pn2, TOK_VAR) || ContainsStmt(pn3, TOK_VAR)) + break; + /* FALL THROUGH */ + + case TOK_HOOK: + /* Reduce 'if (C) T; else E' into T for true C, E for false. */ + switch (pn1->pn_type) { + case TOK_NUMBER: + if (pn1->pn_dval == 0 || JSDOUBLE_IS_NaN(pn1->pn_dval)) + pn2 = pn3; + break; + case TOK_STRING: + if (ATOM_TO_STRING(pn1->pn_atom)->length() == 0) + pn2 = pn3; + break; + case TOK_PRIMARY: + if (pn1->pn_op == JSOP_TRUE) + break; + if (pn1->pn_op == JSOP_FALSE || pn1->pn_op == JSOP_NULL) { + pn2 = pn3; + break; + } + /* FALL THROUGH */ + default: + /* Early return to dodge common code that copies pn2 to pn. */ + return JS_TRUE; + } + +#if JS_HAS_GENERATOR_EXPRS + /* Don't fold a trailing |if (0)| in a generator expression. */ + if (!pn2 && (tc->flags & TCF_GENEXP_LAMBDA)) + break; +#endif + + if (pn2 && !pn2->pn_defn) + pn->become(pn2); + if (!pn2 || (pn->pn_type == TOK_SEMI && !pn->pn_kid)) { + /* + * False condition and no else, or an empty then-statement was + * moved up over pn. Either way, make pn an empty block (not an + * empty statement, which does not decompile, even when labeled). + * NB: pn must be a TOK_IF as TOK_HOOK can never have a null kid + * or an empty statement for a child. + */ + pn->pn_type = TOK_LC; + pn->pn_arity = PN_LIST; + pn->makeEmpty(); + } + RecycleTree(pn2, tc); + if (pn3 && pn3 != pn2) + RecycleTree(pn3, tc); + break; + + case TOK_OR: + case TOK_AND: + if (inCond) { + if (pn->pn_arity == PN_LIST) { + JSParseNode **pnp = &pn->pn_head; + JS_ASSERT(*pnp == pn1); + do { + int cond = Boolish(pn1); + if (cond == (pn->pn_type == TOK_OR)) { + for (pn2 = pn1->pn_next; pn2; pn2 = pn3) { + pn3 = pn2->pn_next; + RecycleTree(pn2, tc); + --pn->pn_count; + } + pn1->pn_next = NULL; + break; + } + if (cond != -1) { + JS_ASSERT(cond == (pn->pn_type == TOK_AND)); + if (pn->pn_count == 1) + break; + *pnp = pn1->pn_next; + RecycleTree(pn1, tc); + --pn->pn_count; + } else { + pnp = &pn1->pn_next; + } + } while ((pn1 = *pnp) != NULL); + + // We may have to change arity from LIST to BINARY. + pn1 = pn->pn_head; + if (pn->pn_count == 2) { + pn2 = pn1->pn_next; + pn1->pn_next = NULL; + JS_ASSERT(!pn2->pn_next); + pn->pn_arity = PN_BINARY; + pn->pn_left = pn1; + pn->pn_right = pn2; + } else if (pn->pn_count == 1) { + pn->become(pn1); + RecycleTree(pn1, tc); + } + } else { + int cond = Boolish(pn1); + if (cond == (pn->pn_type == TOK_OR)) { + RecycleTree(pn2, tc); + pn->become(pn1); + } else if (cond != -1) { + JS_ASSERT(cond == (pn->pn_type == TOK_AND)); + RecycleTree(pn1, tc); + pn->become(pn2); + } + } + } + break; + + case TOK_ASSIGN: + /* + * Compound operators such as *= should be subject to folding, in case + * the left-hand side is constant, and so that the decompiler produces + * the same string that you get from decompiling a script or function + * compiled from that same string. As with +, += is special. + */ + if (pn->pn_op == JSOP_NOP) + break; + if (pn->pn_op != JSOP_ADD) + goto do_binary_op; + /* FALL THROUGH */ + + case TOK_PLUS: + if (pn->pn_arity == PN_LIST) { + size_t length, length2; + jschar *chars; + JSString *str, *str2; + + /* + * Any string literal term with all others number or string means + * this is a concatenation. If any term is not a string or number + * literal, we can't fold. + */ + JS_ASSERT(pn->pn_count > 2); + if (pn->pn_xflags & PNX_CANTFOLD) + return JS_TRUE; + if (pn->pn_xflags != PNX_STRCAT) + goto do_binary_op; + + /* Ok, we're concatenating: convert non-string constant operands. */ + length = 0; + for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { + if (!FoldType(cx, pn2, TOK_STRING)) + return JS_FALSE; + /* XXX fold only if all operands convert to string */ + if (pn2->pn_type != TOK_STRING) + return JS_TRUE; + length += ATOM_TO_STRING(pn2->pn_atom)->flatLength(); + } + + /* Allocate a new buffer and string descriptor for the result. */ + chars = (jschar *) cx->malloc((length + 1) * sizeof(jschar)); + if (!chars) + return JS_FALSE; + chars[length] = 0; + str = js_NewString(cx, chars, length); + if (!str) { + cx->free(chars); + return JS_FALSE; + } + + /* Fill the buffer, advancing chars and recycling kids as we go. */ + for (pn2 = pn1; pn2; pn2 = RecycleTree(pn2, tc)) { + str2 = ATOM_TO_STRING(pn2->pn_atom); + length2 = str2->flatLength(); + js_strncpy(chars, str2->flatChars(), length2); + chars += length2; + } + JS_ASSERT(*chars == 0); + + /* Atomize the result string and mutate pn to refer to it. */ + pn->pn_atom = js_AtomizeString(cx, str, 0); + if (!pn->pn_atom) + return JS_FALSE; + pn->pn_type = TOK_STRING; + pn->pn_op = JSOP_STRING; + pn->pn_arity = PN_NULLARY; + break; + } + + /* Handle a binary string concatenation. */ + JS_ASSERT(pn->pn_arity == PN_BINARY); + if (pn1->pn_type == TOK_STRING || pn2->pn_type == TOK_STRING) { + JSString *left, *right, *str; + + if (!FoldType(cx, (pn1->pn_type != TOK_STRING) ? pn1 : pn2, + TOK_STRING)) { + return JS_FALSE; + } + if (pn1->pn_type != TOK_STRING || pn2->pn_type != TOK_STRING) + return JS_TRUE; + left = ATOM_TO_STRING(pn1->pn_atom); + right = ATOM_TO_STRING(pn2->pn_atom); + str = js_ConcatStrings(cx, left, right); + if (!str) + return JS_FALSE; + pn->pn_atom = js_AtomizeString(cx, str, 0); + if (!pn->pn_atom) + return JS_FALSE; + pn->pn_type = TOK_STRING; + pn->pn_op = JSOP_STRING; + pn->pn_arity = PN_NULLARY; + RecycleTree(pn1, tc); + RecycleTree(pn2, tc); + break; + } + + /* Can't concatenate string literals, let's try numbers. */ + goto do_binary_op; + + case TOK_STAR: + case TOK_SHOP: + case TOK_MINUS: + case TOK_DIVOP: + do_binary_op: + if (pn->pn_arity == PN_LIST) { + JS_ASSERT(pn->pn_count > 2); + for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { + if (!FoldType(cx, pn2, TOK_NUMBER)) + return JS_FALSE; + } + for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { + /* XXX fold only if all operands convert to number */ + if (pn2->pn_type != TOK_NUMBER) + break; + } + if (!pn2) { + JSOp op = PN_OP(pn); + + pn2 = pn1->pn_next; + pn3 = pn2->pn_next; + if (!FoldBinaryNumeric(cx, op, pn1, pn2, pn, tc)) + return JS_FALSE; + while ((pn2 = pn3) != NULL) { + pn3 = pn2->pn_next; + if (!FoldBinaryNumeric(cx, op, pn, pn2, pn, tc)) + return JS_FALSE; + } + } + } else { + JS_ASSERT(pn->pn_arity == PN_BINARY); + if (!FoldType(cx, pn1, TOK_NUMBER) || + !FoldType(cx, pn2, TOK_NUMBER)) { + return JS_FALSE; + } + if (pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER) { + if (!FoldBinaryNumeric(cx, PN_OP(pn), pn1, pn2, pn, tc)) + return JS_FALSE; + } + } + break; + + case TOK_UNARYOP: + if (pn1->pn_type == TOK_NUMBER) { + jsdouble d; + + /* Operate on one numeric constant. */ + d = pn1->pn_dval; + switch (pn->pn_op) { + case JSOP_BITNOT: + d = ~js_DoubleToECMAInt32(d); + break; + + case JSOP_NEG: + d = -d; + break; + + case JSOP_POS: + break; + + case JSOP_NOT: + pn->pn_type = TOK_PRIMARY; + pn->pn_op = (d == 0 || JSDOUBLE_IS_NaN(d)) ? JSOP_TRUE : JSOP_FALSE; + pn->pn_arity = PN_NULLARY; + /* FALL THROUGH */ + + default: + /* Return early to dodge the common TOK_NUMBER code. */ + return JS_TRUE; + } + pn->pn_type = TOK_NUMBER; + pn->pn_op = JSOP_DOUBLE; + pn->pn_arity = PN_NULLARY; + pn->pn_dval = d; + RecycleTree(pn1, tc); + } else if (pn1->pn_type == TOK_PRIMARY) { + if (pn->pn_op == JSOP_NOT && + (pn1->pn_op == JSOP_TRUE || + pn1->pn_op == JSOP_FALSE)) { + pn->become(pn1); + pn->pn_op = (pn->pn_op == JSOP_TRUE) ? JSOP_FALSE : JSOP_TRUE; + RecycleTree(pn1, tc); + } + } + break; + +#if JS_HAS_XML_SUPPORT + case TOK_XMLELEM: + case TOK_XMLLIST: + case TOK_XMLPTAGC: + case TOK_XMLSTAGO: + case TOK_XMLETAGO: + case TOK_XMLNAME: + if (pn->pn_arity == PN_LIST) { + JS_ASSERT(pn->pn_type == TOK_XMLLIST || pn->pn_count != 0); + if (!FoldXMLConstants(cx, pn, tc)) + return JS_FALSE; + } + break; + + case TOK_AT: + if (pn1->pn_type == TOK_XMLNAME) { + JSObjectBox *xmlbox; + + Value v = StringValue(ATOM_TO_STRING(pn1->pn_atom)); + if (!js_ToAttributeName(cx, &v)) + return JS_FALSE; + JS_ASSERT(v.isObject()); + + xmlbox = tc->parser->newObjectBox(&v.toObject()); + if (!xmlbox) + return JS_FALSE; + + pn->pn_type = TOK_XMLNAME; + pn->pn_op = JSOP_OBJECT; + pn->pn_arity = PN_NULLARY; + pn->pn_objbox = xmlbox; + RecycleTree(pn1, tc); + } + break; +#endif /* JS_HAS_XML_SUPPORT */ + + default:; + } + + if (inCond) { + int cond = Boolish(pn); + if (cond >= 0) { + /* + * We can turn function nodes into constant nodes here, but mutating function + * nodes is tricky --- in particular, mutating a function node that appears on + * a method list corrupts the method list. However, methods are M's in + * statements of the form 'this.foo = M;', which we never fold, so we're okay. + */ + PrepareNodeForMutation(pn, tc); + pn->pn_type = TOK_PRIMARY; + pn->pn_op = cond ? JSOP_TRUE : JSOP_FALSE; + pn->pn_arity = PN_NULLARY; + } + } + + return JS_TRUE; +} |