/* -*- 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 #include #include #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(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