/* -*- 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 Parser.h). * After tree construction, it rewrites trees to fold constants and evaluate * compile-time expressions. Finally, it calls js::frontend::EmitTree (see * BytecodeEmitter.h) to generate bytecode. * * This parser attempts no error recovery. */ #include "frontend/Parser.h" #include #include #include "jstypes.h" #include "jsutil.h" #include "jsapi.h" #include "jsarray.h" #include "jsatom.h" #include "jscntxt.h" #include "jsversion.h" #include "jsfun.h" #include "jsgc.h" #include "jsgcmark.h" #include "jsinterp.h" #include "jsiter.h" #include "jslock.h" #include "jsnum.h" #include "jsobj.h" #include "jsopcode.h" #include "jsscope.h" #include "jsscript.h" #include "jsstr.h" #include "frontend/BytecodeCompiler.h" #include "frontend/BytecodeEmitter.h" #include "frontend/FoldConstants.h" #include "frontend/ParseMaps.h" #include "frontend/TokenStream.h" #if JS_HAS_XML_SUPPORT #include "jsxml.h" #endif #if JS_HAS_DESTRUCTURING #include "jsdhash.h" #endif #include "jsatominlines.h" #include "jsscriptinlines.h" #include "frontend/BytecodeEmitter-inl.h" #include "frontend/ParseMaps-inl.h" #include "frontend/ParseNode-inl.h" #include "vm/RegExpObject-inl.h" using namespace js; using namespace js::gc; using namespace js::frontend; /* * 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) Parser::Parser(JSContext *cx, JSPrincipals *prin, JSPrincipals *originPrin, StackFrame *cfp, bool foldConstants) : AutoGCRooter(cx, PARSER), context(cx), tokenStream(cx, prin, originPrin), principals(NULL), originPrincipals(NULL), callerFrame(cfp), callerVarObj(cfp ? &cfp->varObj() : NULL), allocator(cx), functionCount(0), traceListHead(NULL), tc(NULL), keepAtoms(cx->runtime), foldConstants(foldConstants) { cx->activeCompilations++; PodArrayZero(tempFreeList); setPrincipals(prin, originPrin); JS_ASSERT_IF(cfp, cfp->isScriptFrame()); } bool Parser::init(const jschar *base, size_t length, const char *filename, unsigned lineno, JSVersion version) { JSContext *cx = context; if (!cx->ensureParseMapPool()) return false; tempPoolMark = cx->tempLifoAlloc().mark(); if (!tokenStream.init(base, length, filename, lineno, version)) { cx->tempLifoAlloc().release(tempPoolMark); return false; } return true; } Parser::~Parser() { JSContext *cx = context; if (principals) JS_DropPrincipals(cx->runtime, principals); if (originPrincipals) JS_DropPrincipals(cx->runtime, originPrincipals); cx->tempLifoAlloc().release(tempPoolMark); cx->activeCompilations--; } void Parser::setPrincipals(JSPrincipals *prin, JSPrincipals *originPrin) { JS_ASSERT(!principals && !originPrincipals); principals = prin; if (principals) JS_HoldPrincipals(principals); originPrincipals = originPrin; if (originPrincipals) JS_HoldPrincipals(originPrincipals); } ObjectBox * Parser::newObjectBox(JSObject *obj) { /* * We use JSContext.tempLifoAlloc to allocate parsed objects and place them * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc * 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. */ ObjectBox *objbox = context->tempLifoAlloc().new_(); if (!objbox) { js_ReportOutOfMemory(context); return NULL; } objbox->traceLink = traceListHead; traceListHead = objbox; objbox->emitLink = NULL; objbox->object = obj; objbox->isFunctionBox = false; return objbox; } FunctionBox * Parser::newFunctionBox(JSObject *obj, ParseNode *fn, TreeContext *tc) { JS_ASSERT(obj); JS_ASSERT(obj->isFunction()); /* * We use JSContext.tempLifoAlloc to allocate parsed objects and place them * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc * 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. */ FunctionBox *funbox = context->tempLifoAlloc().newPod(); if (!funbox) { js_ReportOutOfMemory(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 (StmtInfo *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; if (!tc->inFunction()) { JSObject *scope = tc->scopeChain(); while (scope) { if (scope->isWith()) funbox->tcflags |= TCF_IN_WITH; scope = scope->enclosingScope(); } } return funbox; } void Parser::trace(JSTracer *trc) { ObjectBox *objbox = traceListHead; while (objbox) { MarkObjectRoot(trc, &objbox->object, "parser.object"); if (objbox->isFunctionBox) static_cast(objbox)->bindings.trace(trc); objbox = objbox->traceLink; } for (TreeContext *tc = this->tc; tc; tc = tc->parent) tc->trace(trc); } static bool GenerateBlockIdForStmtNode(ParseNode *pn, TreeContext *tc) { JS_ASSERT(tc->topStmt); JS_ASSERT(STMT_MAYBE_SCOPE(tc->topStmt)); JS_ASSERT(pn->isKind(PNK_STATEMENTLIST) || pn->isKind(PNK_LEXICALSCOPE)); if (!GenerateBlockId(tc, tc->topStmt->blockid)) return false; pn->pn_blockid = tc->topStmt->blockid; return true; } /* * Parse a top-level JS script. */ ParseNode * 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. */ TreeContext globaltc(this); if (!globaltc.init(context)) return NULL; globaltc.setScopeChain(chain); if (!GenerateBlockId(&globaltc, globaltc.bodyid)) return NULL; ParseNode *pn = statements(); if (pn) { if (!tokenStream.matchToken(TOK_EOF)) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); pn = NULL; } else if (foldConstants) { if (!FoldConstants(context, pn, &globaltc)) pn = NULL; } } return pn; } JS_STATIC_ASSERT(UpvarCookie::FREE_LEVEL == JS_BITMASK(JSFB_LEVEL_BITS)); /* * 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(ParseNode *pn) { ParseNode *pn2, *pn3; unsigned rv, rv2, hasDefault; switch (pn->getKind()) { case PNK_STATEMENTLIST: if (!pn->pn_head) return ENDS_IN_OTHER; return HasFinalReturn(pn->last()); case PNK_IF: if (!pn->pn_kid3) return ENDS_IN_OTHER; return HasFinalReturn(pn->pn_kid2) & HasFinalReturn(pn->pn_kid3); case PNK_WHILE: pn2 = pn->pn_left; if (pn2->isKind(PNK_TRUE)) return ENDS_IN_RETURN; if (pn2->isKind(PNK_NUMBER) && pn2->pn_dval) return ENDS_IN_RETURN; return ENDS_IN_OTHER; case PNK_DOWHILE: pn2 = pn->pn_right; if (pn2->isKind(PNK_FALSE)) return HasFinalReturn(pn->pn_left); if (pn2->isKind(PNK_TRUE)) return ENDS_IN_RETURN; if (pn2->isKind(PNK_NUMBER)) { if (pn2->pn_dval == 0) return HasFinalReturn(pn->pn_left); return ENDS_IN_RETURN; } return ENDS_IN_OTHER; case PNK_FOR: pn2 = pn->pn_left; if (pn2->isArity(PN_TERNARY) && !pn2->pn_kid2) return ENDS_IN_RETURN; return ENDS_IN_OTHER; case PNK_SWITCH: rv = ENDS_IN_RETURN; hasDefault = ENDS_IN_OTHER; pn2 = pn->pn_right; if (pn2->isKind(PNK_LEXICALSCOPE)) pn2 = pn2->expr(); for (pn2 = pn2->pn_head; rv && pn2; pn2 = pn2->pn_next) { if (pn2->isKind(PNK_DEFAULT)) hasDefault = ENDS_IN_RETURN; pn3 = pn2->pn_right; JS_ASSERT(pn3->isKind(PNK_STATEMENTLIST)); 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 PNK_BREAK: return ENDS_IN_BREAK; case PNK_WITH: return HasFinalReturn(pn->pn_right); case PNK_RETURN: return ENDS_IN_RETURN; case PNK_COLON: case PNK_LEXICALSCOPE: return HasFinalReturn(pn->expr()); case PNK_THROW: return ENDS_IN_RETURN; case PNK_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->isArity(PN_LIST)); for (pn2 = pn->pn_kid2->pn_head; pn2; pn2 = pn2->pn_next) rv &= HasFinalReturn(pn2); } return rv; case PNK_CATCH: /* Check this catch block's body. */ return HasFinalReturn(pn->pn_kid3); case PNK_LET: /* Non-binary let statements are let declarations. */ if (!pn->isArity(PN_BINARY)) return ENDS_IN_OTHER; return HasFinalReturn(pn->pn_right); default: return ENDS_IN_OTHER; } } static JSBool ReportBadReturn(JSContext *cx, TreeContext *tc, ParseNode *pn, unsigned flags, unsigned errnum, unsigned 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), pn, flags, errnum, name.ptr()); } static JSBool CheckFinalReturn(JSContext *cx, TreeContext *tc, ParseNode *pn) { JS_ASSERT(tc->inFunction()); return HasFinalReturn(pn) == ENDS_IN_RETURN || ReportBadReturn(cx, tc, pn, 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'. */ static bool CheckStrictAssignment(JSContext *cx, TreeContext *tc, ParseNode *lhs) { if (tc->needStrictChecks() && lhs->isKind(PNK_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, TreeContext *tc, PropertyName *name, ParseNode *pn) { if (!tc->needStrictChecks()) return true; JSAtomState *atomState = &cx->runtime->atomState; if (name == atomState->evalAtom || name == atomState->argumentsAtom || FindKeyword(name->charsZ(), name->length())) { JSAutoByteString bytes; if (!js_AtomToPrintableString(cx, name, &bytes)) return false; return ReportStrictModeError(cx, TS(tc->parser), tc, pn, JSMSG_BAD_BINDING, bytes.ptr()); } return true; } static bool ReportBadParameter(JSContext *cx, TreeContext *tc, JSAtom *name, unsigned errorNumber) { Definition *dn = tc->decls.lookupFirst(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. */ bool js::CheckStrictParameters(JSContext *cx, TreeContext *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 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().propid(); 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::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; } ParseNode * Parser::functionBody(FunctionBodyType type) { JS_ASSERT(tc->inFunction()); StmtInfo stmtInfo; PushStatement(tc, &stmtInfo, STMT_BLOCK, -1); stmtInfo.flags = SIF_BODY_BLOCK; unsigned oldflags = tc->flags; tc->flags &= ~(TCF_RETURN_EXPR | TCF_RETURN_VOID); ParseNode *pn; if (type == StatementListBody) { pn = statements(); } else { JS_ASSERT(type == ExpressionBody); JS_ASSERT(JS_HAS_EXPR_CLOSURES); pn = UnaryNode::create(PNK_RETURN, tc); if (pn) { pn->pn_kid = assignExpr(); if (!pn->pn_kid) { pn = NULL; } else { if (tc->flags & TCF_FUN_IS_GENERATOR) { ReportBadReturn(context, tc, pn, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_RETURN, JSMSG_BAD_ANON_GENERATOR_RETURN); pn = NULL; } else { pn->setOp(JSOP_RETURN); pn->pn_pos.end = pn->pn_kid->pn_pos.end; } } } } if (pn) { JS_ASSERT(!(tc->topStmt->flags & SIF_SCOPE)); PopStatementTC(tc); /* 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; } /* Create a placeholder Definition node for |atom|. */ static Definition * MakePlaceholder(ParseNode *pn, TreeContext *tc) { Definition *dn = (Definition *) NameNode::create(PNK_NAME, pn->pn_atom, tc); if (!dn) return NULL; dn->setOp(JSOP_NOP); dn->setDefn(true); dn->pn_dflags |= PND_PLACEHOLDER; return dn; } static bool Define(ParseNode *pn, JSAtom *atom, TreeContext *tc, bool let = false) { JS_ASSERT(!pn->isUsed()); JS_ASSERT_IF(pn->isDefn(), pn->isPlaceholder()); bool foundLexdep = false; Definition *dn = NULL; if (let) dn = tc->decls.lookupFirst(atom); if (!dn) { dn = tc->lexdeps.lookupDefn(atom); foundLexdep = !!dn; } if (dn && dn != pn) { ParseNode **pnup = &dn->dn_uses; ParseNode *pnu; unsigned start = let ? pn->pn_blockid : tc->bodyid; while ((pnu = *pnup) != NULL && pnu->pn_blockid >= start) { JS_ASSERT(pnu->isUsed()); pnu->pn_lexdef = (Definition *) 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) && foundLexdep) tc->lexdeps->remove(atom); } pn->pn_dflags |= dn->pn_dflags & PND_CLOSED; } Definition *toAdd = (Definition *) pn; bool ok = let ? tc->decls.addShadow(atom, toAdd) : tc->decls.addUnique(atom, toAdd); if (!ok) return false; pn->setDefn(true); pn->pn_dflags &= ~PND_PLACEHOLDER; if (!tc->parent) pn->pn_dflags |= PND_TOPLEVEL; return true; } static void ForgetUse(ParseNode *pn) { if (!pn->isUsed()) { JS_ASSERT(!pn->isDefn()); return; } ParseNode **pnup = &pn->lexdef()->dn_uses; ParseNode *pnu; while ((pnu = *pnup) != pn) pnup = &pnu->pn_link; *pnup = pn->pn_link; pn->setUsed(false); } static ParseNode * MakeAssignment(ParseNode *pn, ParseNode *rhs, TreeContext *tc) { ParseNode *lhs = tc->parser->cloneNode(*pn); if (!lhs) return NULL; if (pn->isUsed()) { Definition *dn = pn->pn_lexdef; ParseNode **pnup = &dn->dn_uses; while (*pnup != pn) pnup = &(*pnup)->pn_link; *pnup = lhs; lhs->pn_link = pn->pn_link; pn->pn_link = NULL; } pn->setKind(PNK_ASSIGN); pn->setOp(JSOP_NOP); pn->setArity(PN_BINARY); pn->setInParens(false); pn->setUsed(false); pn->setDefn(false); pn->pn_left = lhs; pn->pn_right = rhs; return lhs; } static ParseNode * MakeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom, TreeContext *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()) { ParseNode *rhs = dn->expr(); if (rhs) { ParseNode *lhs = MakeAssignment(dn, rhs, tc); if (!lhs) return NULL; //pn->dn_uses = lhs; dn = (Definition *) lhs; } dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETNAME : JSOP_NAME); } else if (dn->kind() == Definition::FUNCTION) { JS_ASSERT(dn->isOp(JSOP_NOP)); tc->parser->prepareNodeForMutation(dn); dn->setKind(PNK_NAME); dn->setArity(PN_NAME); dn->pn_atom = atom; } /* Now make dn no longer a definition, rather a use of pn. */ JS_ASSERT(dn->isKind(PNK_NAME)); JS_ASSERT(dn->isArity(PN_NAME)); JS_ASSERT(dn->pn_atom == atom); for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { JS_ASSERT(pnu->isUsed()); JS_ASSERT(!pnu->isDefn()); pnu->pn_lexdef = (Definition *) pn; pn->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS; } pn->pn_dflags |= dn->pn_dflags & PND_USE2DEF_FLAGS; pn->dn_uses = dn; dn->setDefn(false); dn->setUsed(true); dn->pn_lexdef = (Definition *) pn; dn->pn_cookie.makeFree(); dn->pn_dflags &= ~PND_BOUND; return dn; } bool js::DefineArg(ParseNode *pn, JSAtom *atom, unsigned i, TreeContext *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; /* * Make an argument definition node, distinguished by being in tc->decls * but having PNK_NAME kind and JSOP_NOP op. Insert it in a PNK_ARGSBODY * list node returned via pn->pn_body. */ ParseNode *argpn = NameNode::create(PNK_NAME, atom, tc); if (!argpn) return false; JS_ASSERT(argpn->isKind(PNK_NAME) && argpn->isOp(JSOP_NOP)); /* Arguments are initialized by definition. */ argpn->pn_dflags |= PND_INITIALIZED; if (!Define(argpn, atom, tc)) return false; ParseNode *argsbody = pn->pn_body; if (!argsbody) { argsbody = ListNode::create(PNK_ARGSBODY, tc); if (!argsbody) return false; argsbody->setOp(JSOP_NOP); argsbody->makeEmpty(); pn->pn_body = argsbody; } argsbody->append(argpn); argpn->setOp(JSOP_GETARG); argpn->pn_cookie.set(tc->staticLevel, i); argpn->pn_dflags |= PND_BOUND; return true; } /* * 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, TreeContext *tc); static JSBool BindLet(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc); static JSBool BindVarOrConst(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc); struct BindData { BindData() : fresh(true) {} ParseNode *pn; /* name node for definition processing and error source coordinates */ JSOp op; /* prolog bytecode or nop */ Binder binder; /* binder, discriminates u */ union { struct { VarContext varContext; StaticBlockObject *blockObj; unsigned overflow; } let; }; bool fresh; void initLet(VarContext varContext, StaticBlockObject &blockObj, unsigned overflow) { this->pn = NULL; this->op = JSOP_NOP; this->binder = BindLet; this->let.varContext = varContext; this->let.blockObj = &blockObj; this->let.overflow = overflow; } void initVarOrConst(JSOp op) { this->op = op; this->binder = BindVarOrConst; } }; static bool BindLocalVariable(JSContext *cx, TreeContext *tc, ParseNode *pn, BindingKind kind) { JS_ASSERT(kind == VARIABLE || kind == CONSTANT); /* 'arguments' can be bound as a local only via a destructuring formal parameter. */ JS_ASSERT_IF(pn->pn_atom == cx->runtime->atomState.argumentsAtom, kind == VARIABLE); unsigned index = tc->bindings.countVars(); if (!tc->bindings.add(cx, pn->pn_atom, kind)) return false; pn->pn_cookie.set(tc->staticLevel, index); pn->pn_dflags |= PND_BOUND; return true; } #if JS_HAS_DESTRUCTURING static JSBool BindDestructuringArg(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *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.lookupFirst(atom)) { ReportCompileErrorNumber(cx, TS(tc->parser), NULL, JSREPORT_ERROR, JSMSG_DESTRUCT_DUP_ARG); return JS_FALSE; } ParseNode *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->setOp(JSOP_SETLOCAL); pn->pn_dflags |= PND_BOUND; return Define(pn, atom, tc); } #endif /* JS_HAS_DESTRUCTURING */ JSFunction * Parser::newFunction(TreeContext *tc, JSAtom *atom, FunctionSyntaxKind kind) { JS_ASSERT_IF(kind == Statement, atom != NULL); /* * 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; RootedVarObject parent(context); parent = tc->inFunction() ? NULL : tc->scopeChain(); JSFunction *fun = js_NewFunction(context, NULL, NULL, 0, JSFUN_INTERPRETED | (kind == Expression ? JSFUN_LAMBDA : 0), parent, atom); if (fun && !tc->compileAndGo()) { if (!fun->clearParent(context)) return NULL; if (!fun->clearType(context)) return NULL; fun->setEnvironment(NULL); } 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; } static FunctionBox * EnterFunction(ParseNode *fn, TreeContext *funtc, JSAtom *funAtom = NULL, FunctionSyntaxKind kind = Expression) { TreeContext *tc = funtc->parent; JSFunction *fun = tc->parser->newFunction(tc, funAtom, kind); if (!fun) return NULL; /* Create box for fun->object early to protect against last-ditch GC. */ FunctionBox *funbox = tc->parser->newFunctionBox(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 DeoptimizeUsesWithin(Definition *dn, const TokenPos &pos) { unsigned ndeoptimized = 0; for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { JS_ASSERT(pnu->isUsed()); JS_ASSERT(!pnu->isDefn()); if (pnu->pn_pos.begin >= pos.begin && pnu->pn_pos.end <= pos.end) { pnu->pn_dflags |= PND_DEOPTIMIZED; ++ndeoptimized; } } return ndeoptimized != 0; } static bool LeaveFunction(ParseNode *fn, TreeContext *funtc, PropertyName *funName = NULL, FunctionSyntaxKind kind = Expression) { TreeContext *tc = funtc->parent; tc->blockidGen = funtc->blockidGen; FunctionBox *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()) { int foundCallee = 0; for (AtomDefnRange r = funtc->lexdeps->all(); !r.empty(); r.popFront()) { JSAtom *atom = r.front().key(); Definition *dn = r.front().value(); JS_ASSERT(dn->isPlaceholder()); if (atom == funName && kind == Expression) { dn->setOp(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 (ParseNode *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; } } } Definition *outer_dn = tc->decls.lookupFirst(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_dn && tc->innermostWith && outer_dn->pn_pos < tc->innermostWith->pn_pos)) { DeoptimizeUsesWithin(dn, fn->pn_pos); } if (!outer_dn) { AtomDefnAddPtr p = tc->lexdeps->lookupForAdd(atom); if (p) { outer_dn = p.value(); } else { /* * 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_dn = MakePlaceholder(dn, tc); if (!outer_dn || !tc->lexdeps->add(p, atom, outer_dn)) return false; } } /* * 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) { ParseNode **pnup = &dn->dn_uses; ParseNode *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 Definition::resolve. */ *pnup = outer_dn->dn_uses; outer_dn->dn_uses = dn; outer_dn->pn_dflags |= dn->pn_dflags & ~PND_PLACEHOLDER; dn->setDefn(false); dn->setUsed(true); dn->pn_lexdef = outer_dn; } /* Mark the outer dn as escaping. */ outer_dn->pn_dflags |= PND_CLOSED; } if (funtc->lexdeps->count() - foundCallee != 0) { ParseNode *body = fn->pn_body; fn->pn_body = NameSetNode::create(PNK_UPVARS, tc); if (!fn->pn_body) return false; fn->pn_body->pn_pos = body->pn_pos; if (foundCallee) funtc->lexdeps->remove(funName); /* Transfer ownership of the lexdep map to the parse node. */ fn->pn_body->pn_names = funtc->lexdeps; funtc->lexdeps.clearMap(); fn->pn_body->pn_tree = body; } else { funtc->lexdeps.releaseMap(funtc->parser->context); } } /* * 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->toFunction()->nargs > 0) { AtomDeclsIter iter(&funtc->decls); Definition *dn; while ((dn = iter()) != NULL) { if (dn->kind() == Definition::ARG && dn->isAssigned()) { funbox->tcflags |= TCF_FUN_MUTATES_PARAMETER; break; } } } funbox->bindings.transfer(funtc->parser->context, &funtc->bindings); return true; } static bool DefineGlobal(ParseNode *pn, BytecodeEmitter *bce, PropertyName *name); /* * 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(TreeContext &funtc, FunctionBox *funbox, ParseNode **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; ParseNode *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; ParseNode *lhs = destructuringExpr(&data, tt); if (!lhs) return false; /* * Adjust fun->nargs to count the single anonymous positional * parameter that is to be destructured. */ uint16_t 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. */ ParseNode *rhs = NameNode::create(PNK_NAME, context->runtime->atomState.emptyAtom, &funtc); if (!rhs) return false; rhs->setOp(JSOP_GETARG); rhs->pn_cookie.set(funtc.staticLevel, slot); rhs->pn_dflags |= PND_BOUND; ParseNode *item = new_(PNK_ASSIGN, JSOP_NOP, lhs->pn_pos, lhs, rhs); if (!item) return false; if (!list) { list = ListNode::create(PNK_VAR, &funtc); if (!list) return false; list->makeEmpty(); *listp = list; } list->append(item); break; } #endif /* JS_HAS_DESTRUCTURING */ case TOK_NAME: { PropertyName *name = tokenStream.currentToken().name(); #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.lookupFirst(name)) { funtc.bindings.noteDup(); duplicatedArg = name; if (destructuringArg) goto report_dup_and_destructuring; } #endif uint16_t slot; if (!funtc.bindings.addArgument(context, name, &slot)) return false; if (!DefineArg(funbox->node, name, 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: Definition *dn = funtc.decls.lookupFirst(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; } ParseNode * Parser::functionDef(PropertyName *funName, FunctionType type, FunctionSyntaxKind kind) { JS_ASSERT_IF(kind == Statement, funName); /* Make a TOK_FUNCTION node. */ ParseNode *pn = FunctionNode::create(PNK_FUNCTION, tc); if (!pn) return NULL; pn->pn_body = NULL; pn->pn_cookie.makeFree(); /* * If this is a function expression, 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 (those not at body level of a function or * program) as escaping funargs, since we can't statically analyze their * definitions and uses. */ bool bodyLevel = tc->atBodyLevel(); pn->pn_dflags = (kind == Expression || !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 (kind == Statement) { if (Definition *dn = tc->decls.lookupFirst(funName)) { Definition::Kind dn_kind = dn->kind(); JS_ASSERT(!dn->isUsed()); JS_ASSERT(dn->isDefn()); if (context->hasStrictOption() || dn_kind == Definition::CONST) { JSAutoByteString name; if (!js_AtomToPrintableString(context, funName, &name) || !reportErrorNumber(NULL, (dn_kind != Definition::CONST) ? JSREPORT_WARNING | JSREPORT_STRICT : JSREPORT_ERROR, JSMSG_REDECLARED_VAR, Definition::kindString(dn_kind), name.ptr())) { return NULL; } } if (bodyLevel) { tc->decls.updateFirst(funName, (Definition *) pn); pn->setDefn(true); pn->dn_uses = dn; /* dn->dn_uses is now pn_link */ if (!MakeDefIntoUse(dn, pn, funName, 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. */ if (Definition *fn = tc->lexdeps.lookupDefn(funName)) { JS_ASSERT(fn->isDefn()); fn->setKind(PNK_FUNCTION); fn->setArity(PN_FUNC); fn->pn_pos.begin = pn->pn_pos.begin; /* * Set fn->pn_pos.end too, in case of error before we parse the * closing brace. See bug 640075. */ fn->pn_pos.end = pn->pn_pos.end; fn->pn_body = NULL; fn->pn_cookie.makeFree(); tc->lexdeps->remove(funName); freeTree(pn); pn = fn; } if (!Define(pn, funName, 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 BytecodeEmitter.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. */ unsigned index; switch (tc->bindings.lookup(context, funName, &index)) { case NONE: case ARGUMENT: index = tc->bindings.countVars(); if (!tc->bindings.addVariable(context, funName)) return NULL; /* FALL THROUGH */ case VARIABLE: pn->pn_cookie.set(tc->staticLevel, index); pn->pn_dflags |= PND_BOUND; break; default:; } } } TreeContext *outertc = tc; /* Initialize early for possible flags mutation via destructuringExpr. */ TreeContext funtc(tc->parser); if (!funtc.init(context)) return NULL; FunctionBox *funbox = EnterFunction(pn, &funtc, funName, kind); if (!funbox) return NULL; JSFunction *fun = funbox->function(); /* Now parse formal argument list and compute fun->nargs. */ ParseNode *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) { AtomDeclsIter iter(&funtc.decls); while (Definition *apn = iter()) { /* Filter based on pn_op -- see BindDestructuringArg, above. */ if (!apn->isOp(JSOP_SETLOCAL)) continue; if (!BindLocalVariable(context, &funtc, apn, VARIABLE)) return NULL; } } #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; } FunctionBodyType bodyType = StatementListBody; #if JS_HAS_EXPR_CLOSURES if (tokenStream.getToken(TSF_OPERAND) != TOK_LC) { tokenStream.ungetToken(); fun->flags |= JSFUN_EXPR_CLOSURE; bodyType = ExpressionBody; } #else MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_BODY); #endif ParseNode *body = functionBody(bodyType); if (!body) return NULL; if (funName && !CheckStrictBinding(context, &funtc, funName, pn)) return NULL; if (!CheckStrictParameters(context, &funtc)) return NULL; #if JS_HAS_EXPR_CLOSURES if (bodyType == StatementListBody) MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY); else if (kind == Statement && !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 PNK_SEQ node, to prepend the destructuring * code without bracing the decompilation of the function body. */ if (prelude) { if (!body->isArity(PN_LIST)) { ParseNode *block; block = ListNode::create(PNK_SEQ, outertc); if (!block) return NULL; block->pn_pos = body->pn_pos; block->initList(body); body = block; } ParseNode *item = UnaryNode::create(PNK_SEMI, outertc); if (!item) return NULL; 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 && kind == Statement) outertc->flags |= TCF_FUN_HEAVYWEIGHT; } JSOp op = JSOP_NOP; if (kind == Expression) { op = JSOP_LAMBDA; } else { if (!bodyLevel) { /* * Extension: in non-strict mode code, a function statement not at * the top level of a function body or whole program, 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. */ JS_ASSERT(!outertc->inStrictMode()); op = JSOP_DEFFUN; outertc->noteMightAliasLocals(); } } funbox->kids = funtc.functionList; pn->pn_funbox = funbox; pn->setOp(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 && kind == Statement && outertc->compiling()) { JS_ASSERT(pn->pn_cookie.isFree()); if (!DefineGlobal(pn, outertc->asBytecodeEmitter(), funName)) return NULL; } pn->pn_blockid = outertc->blockid(); if (!LeaveFunction(pn, &funtc, funName, kind)) return NULL; /* If the surrounding function is not strict code, reset the lexer. */ if (!outertc->inStrictMode()) tokenStream.setStrictMode(false); return pn; } ParseNode * Parser::functionStmt() { PropertyName *name = NULL; if (tokenStream.getToken(TSF_KEYWORD_IS_NAME) == TOK_NAME) { name = tokenStream.currentToken().name(); } else { /* Unnamed function expressions are forbidden in statement context. */ reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_UNNAMED_FUNCTION_STMT); return NULL; } /* 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, Normal, Statement); } ParseNode * Parser::functionExpr() { PropertyName *name = NULL; if (tokenStream.getToken(TSF_KEYWORD_IS_NAME) == TOK_NAME) name = tokenStream.currentToken().name(); else tokenStream.ungetToken(); return functionDef(name, Normal, Expression); } /* * 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(ParseNode *pn, bool *isDirectivePrologueMember) { *isDirectivePrologueMember = pn->isStringExprStatement(); if (!*isDirectivePrologueMember) return true; ParseNode *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. */ ParseNode * Parser::statements() { JS_CHECK_RECURSION(context, return NULL); ParseNode *pn = ListNode::create(PNK_STATEMENTLIST, tc); if (!pn) return NULL; pn->makeEmpty(); pn->pn_blockid = tc->blockid(); ParseNode *saveBlock = tc->blockNode; tc->blockNode = pn; bool inDirectivePrologue = tc->atBodyLevel(); tokenStream.setOctalCharacterEscape(false); for (;;) { TokenKind tt = tokenStream.peekToken(TSF_OPERAND); if (tt <= TOK_EOF || tt == TOK_RC) { if (tt == TOK_ERROR) { if (tokenStream.isEOF()) tokenStream.setUnexpectedEOF(); return NULL; } break; } ParseNode *next = statement(); if (!next) { if (tokenStream.isEOF()) tokenStream.setUnexpectedEOF(); return NULL; } if (inDirectivePrologue && !recognizeDirectivePrologue(next, &inDirectivePrologue)) return NULL; if (next->isKind(PNK_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; /* Function statements extend the Call object at runtime. */ tc->noteHasExtensibleScope(); } } pn->append(next); } /* * 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; JS_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end); return pn; } ParseNode * Parser::condition() { MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); ParseNode *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. */ JS_ASSERT_IF(pn->isKind(PNK_ASSIGN), pn->isOp(JSOP_NOP)); if (pn->isKind(PNK_ASSIGN) && !pn->isInParens() && !reportErrorNumber(NULL, JSREPORT_WARNING | JSREPORT_STRICT, JSMSG_EQUAL_AS_ASSIGN)) { return NULL; } return pn; } static bool MatchLabel(JSContext *cx, TokenStream *ts, PropertyName **label) { TokenKind tt = ts->peekTokenSameLine(TSF_OPERAND); if (tt == TOK_ERROR) return false; if (tt == TOK_NAME) { (void) ts->getToken(); *label = ts->currentToken().name(); } else { *label = NULL; } return true; } static bool ReportRedeclaration(JSContext *cx, TreeContext *tc, ParseNode *pn, bool isConst, JSAtom *atom) { JSAutoByteString name; if (js_AtomToPrintableString(cx, atom, &name)) { ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, JSMSG_REDECLARED_VAR, isConst ? "const" : "variable", name.ptr()); } return false; } /* * Define a let-variable in a block, let-expression, or comprehension scope. tc * must already be in such a scope. * * Throw a SyntaxError if 'atom' is an invalid name. Otherwise create a * property for the new variable on the block object, tc->blockChain; * populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to * data->pn in a slot of the block object. */ static JSBool BindLet(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc) { ParseNode *pn = data->pn; if (!CheckStrictBinding(cx, tc, atom->asPropertyName(), pn)) return false; StaticBlockObject &blockObj = *data->let.blockObj; unsigned blockCount = blockObj.slotCount(); if (blockCount == JS_BIT(16)) { ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, data->let.overflow); return false; } /* * For bindings that are hoisted to the beginning of the block/function, * Define() right now. For the rest, delay Define() until PushLetScope. */ if (data->let.varContext == HoistVars) { JS_ASSERT(!tc->atBodyLevel()); Definition *dn = tc->decls.lookupFirst(atom); if (dn && dn->pn_blockid == tc->blockid()) return ReportRedeclaration(cx, tc, pn, dn->isConst(), atom); 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, js::frontend::CompileScript will adjust the slot * again to include script->nfixed. */ pn->setOp(JSOP_GETLOCAL); pn->pn_cookie.set(tc->staticLevel, uint16_t(blockCount)); pn->pn_dflags |= PND_LET | PND_BOUND; /* * Define the let binding's property before storing pn in the the binding's * slot indexed by blockCount off the class-reserved slot base. */ bool redeclared; jsid id = ATOM_TO_JSID(atom); const Shape *shape = blockObj.addVar(cx, id, blockCount, &redeclared); if (!shape) { if (redeclared) ReportRedeclaration(cx, tc, pn, false, atom); return false; } /* Store pn in the static block object. */ blockObj.setDefinitionParseNode(blockCount, reinterpret_cast(pn)); return true; } template static inline bool ForEachLetDef(TreeContext *tc, StaticBlockObject &blockObj, Op op) { for (Shape::Range r = blockObj.lastProperty()->all(); !r.empty(); r.popFront()) { const Shape &shape = r.front(); /* Beware the destructuring dummy slots. */ if (JSID_IS_INT(shape.propid())) continue; if (!op(tc, blockObj, shape, JSID_TO_ATOM(shape.propid()))) return false; } return true; } struct RemoveDecl { bool operator()(TreeContext *tc, StaticBlockObject &, const Shape &, JSAtom *atom) { tc->decls.remove(atom); return true; } }; static void PopStatement(TreeContext *tc) { if (tc->topStmt->flags & SIF_SCOPE) { StaticBlockObject &blockObj = *tc->topStmt->blockObj; JS_ASSERT(!blockObj.inDictionaryMode()); ForEachLetDef(tc, blockObj, RemoveDecl()); } PopStatementTC(tc); } static inline bool OuterLet(TreeContext *tc, StmtInfo *stmt, JSAtom *atom) { while (stmt->downScope) { stmt = 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(ParseNode *pn, BytecodeEmitter *bce, PropertyName *name) { GlobalScope *globalScope = bce->globalScope; JSObject *globalObj = globalScope->globalObj; if (!bce->compileAndGo() || !globalObj || bce->compilingForEval()) return true; AtomIndexAddPtr p = globalScope->names.lookupForAdd(name); if (!p) { JSContext *cx = bce->parser->context; JSObject *holder; JSProperty *prop; if (!globalObj->lookupProperty(cx, name, &holder, &prop)) return false; FunctionBox *funbox = pn->isKind(PNK_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(name, funbox); } if (!globalScope->defs.append(def)) return false; jsatomid index = globalScope->names.count(); if (!globalScope->names.add(p, name, index)) return false; JS_ASSERT(index == 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->isKind(PNK_FUNCTION)) { JS_ASSERT(pn->isArity(PN_FUNC)); jsatomid index = p.value(); globalScope->defs[index].funbox = pn->pn_funbox; } } pn->pn_dflags |= PND_GVAR; return true; } static bool BindTopLevelVar(JSContext *cx, BindData *data, ParseNode *pn, TreeContext *tc) { JS_ASSERT(pn->isOp(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->asBytecodeEmitter(), pn->pn_atom->asPropertyName()); } static bool BindFunctionLocal(JSContext *cx, BindData *data, MultiDeclRange &mdl, TreeContext *tc) { JS_ASSERT(tc->inFunction()); ParseNode *pn = data->pn; JSAtom *name = pn->pn_atom; /* * Don't create a local variable with the name 'arguments', per ECMA-262. * Instead, 'var arguments' always restates that predefined binding of the * lexical environment for function activations. Assignments to arguments * must be handled specially -- see NoteLValue. */ if (name == cx->runtime->atomState.argumentsAtom) { pn->setOp(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; if (!BindLocalVariable(cx, tc, pn, kind)) return false; pn->setOp(JSOP_GETLOCAL); return true; } if (kind == ARGUMENT) { JS_ASSERT(tc->inFunction()); JS_ASSERT(!mdl.empty() && mdl.front()->kind() == Definition::ARG); } else { JS_ASSERT(kind == VARIABLE || kind == CONSTANT); } return true; } static JSBool BindVarOrConst(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc) { ParseNode *pn = data->pn; /* Default best op for pn is JSOP_NAME; we'll try to improve below. */ pn->setOp(JSOP_NAME); if (!CheckStrictBinding(cx, tc, atom->asPropertyName(), pn)) return false; StmtInfo *stmt = LexicalLookup(tc, atom, NULL); if (stmt && stmt->type == STMT_WITH) { data->fresh = false; pn->pn_dflags |= PND_DEOPTIMIZED; tc->noteMightAliasLocals(); return true; } MultiDeclRange mdl = tc->decls.lookupMulti(atom); JSOp op = data->op; if (stmt || !mdl.empty()) { Definition *dn = mdl.empty() ? NULL : mdl.front(); Definition::Kind dn_kind = dn ? dn->kind() : Definition::VAR; if (dn_kind == Definition::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 == Definition::CONST || (dn_kind == Definition::LET && (stmt->type != STMT_CATCH || OuterLet(tc, stmt, atom)))); if (cx->hasStrictOption() ? op != JSOP_DEFVAR || dn_kind != Definition::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, Definition::kindString(dn_kind), name.ptr())) { return JS_FALSE; } } } } if (mdl.empty()) { 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 |mdl.defn()|, and if we have an initializer for this var or * const (typically a const would ;-), then pn must be rewritten into a * PNK_ASSIGN node. See js::Parser::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. */ Definition *dn = mdl.front(); data->fresh = false; if (!pn->isUsed()) { /* Make pnu be a fresh name node that uses dn. */ ParseNode *pnu = pn; if (pn->isDefn()) { pnu = NameNode::create(PNK_NAME, atom, tc); if (!pnu) return JS_FALSE; } LinkUseToDef(pnu, dn, tc); pnu->setOp(JSOP_NAME); } /* Find the first non-let binding of this atom. */ while (dn->kind() == Definition::LET) { mdl.popFront(); if (mdl.empty()) break; dn = mdl.front(); } if (dn) { JS_ASSERT_IF(data->op == JSOP_DEFCONST, dn->kind() == Definition::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->isDefn()) { if (tc->lexdeps->lookup(atom)) { tc->lexdeps->remove(atom); } else { ParseNode *pn2 = NameNode::create(PNK_NAME, atom, tc); if (!pn2) return JS_FALSE; /* The token stream may be past the location for pn. */ pn2->pn_pos = pn->pn_pos; pn = pn2; } pn->setOp(JSOP_NAME); } if (!tc->decls.addHoist(atom, (Definition *) pn)) return JS_FALSE; pn->setDefn(true); pn->pn_dflags &= ~PND_PLACEHOLDER; } if (data->op == JSOP_DEFCONST) pn->pn_dflags |= PND_CONST; if (tc->inFunction()) return BindFunctionLocal(cx, data, mdl, tc); return BindTopLevelVar(cx, data, pn, tc); } static bool MakeSetCall(JSContext *cx, ParseNode *pn, TreeContext *tc, unsigned msg) { JS_ASSERT(pn->isArity(PN_LIST)); JS_ASSERT(pn->isOp(JSOP_CALL) || pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY)); if (!ReportStrictModeError(cx, TS(tc->parser), tc, pn, msg)) return false; ParseNode *pn2 = pn->pn_head; if (pn2->isKind(PNK_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, ParseNode *pn, TreeContext *tc, unsigned dflag = PND_ASSIGNED) { if (pn->isUsed()) { Definition *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->flags |= (TCF_FUN_HEAVYWEIGHT | TCF_FUN_LOCAL_ARGUMENTS); tc->countArgumentsUse(pn); } else if (tc->inFunction() && lname == tc->fun()->atom) { tc->flags |= TCF_FUN_HEAVYWEIGHT; } } #if JS_HAS_DESTRUCTURING static JSBool BindDestructuringVar(JSContext *cx, BindData *data, ParseNode *pn, TreeContext *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->isKind(PNK_NAME)); atom = pn->pn_atom; if (atom == cx->runtime->atomState.argumentsAtom) tc->flags |= (TCF_FUN_HEAVYWEIGHT | TCF_FUN_LOCAL_ARGUMENTS); 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->setOp(pn->isOp(JSOP_ARGUMENTS) ? JSOP_SETNAME : JSOP_SETLOCAL); } else { pn->setOp((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, ParseNode *pn, TreeContext *tc) { switch (pn->getKind()) { case PNK_NAME: NoteLValue(cx, pn, tc); /* FALL THROUGH */ case PNK_DOT: case PNK_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->getOp()].format & JOF_SET)) pn->setOp(JSOP_SETNAME); break; case PNK_LP: if (!MakeSetCall(cx, pn, tc, JSMSG_BAD_LEFTSIDE_OF_ASS)) return JS_FALSE; break; #if JS_HAS_XML_SUPPORT case PNK_XMLUNARY: JS_ASSERT(pn->isOp(JSOP_XMLNAME)); pn->setOp(JSOP_BINDXMLNAME); break; #endif default: ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, JSMSG_BAD_LEFTSIDE_OF_ASS); return JS_FALSE; } return JS_TRUE; } /* * 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. * * 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. * * The 'toplevel' is a private detail of the recursive strategy used by * CheckDestructuring and callers should use the default value. */ static bool CheckDestructuring(JSContext *cx, BindData *data, ParseNode *left, TreeContext *tc, bool toplevel = true) { bool ok; if (left->isKind(PNK_ARRAYCOMP)) { ReportCompileErrorNumber(cx, TS(tc->parser), left, JSREPORT_ERROR, JSMSG_ARRAY_COMP_LEFTSIDE); return false; } StaticBlockObject *blockObj = data && data->binder == BindLet ? data->let.blockObj : NULL; uint32_t blockCountBefore = blockObj ? blockObj->slotCount() : 0; if (left->isKind(PNK_RB)) { for (ParseNode *pn = left->pn_head; pn; pn = pn->pn_next) { /* Nullary comma is an elision; binary comma is an expression.*/ if (!pn->isArrayHole()) { if (pn->isKind(PNK_RB) || pn->isKind(PNK_RC)) { ok = CheckDestructuring(cx, data, pn, tc, false); } else { if (data) { if (!pn->isKind(PNK_NAME)) { ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, JSMSG_NO_VARIABLE_NAME); return false; } ok = BindDestructuringVar(cx, data, pn, tc); } else { ok = BindDestructuringLHS(cx, pn, tc); } } if (!ok) return false; } } } else { JS_ASSERT(left->isKind(PNK_RC)); for (ParseNode *pair = left->pn_head; pair; pair = pair->pn_next) { JS_ASSERT(pair->isKind(PNK_COLON)); ParseNode *pn = pair->pn_right; if (pn->isKind(PNK_RB) || pn->isKind(PNK_RC)) { ok = CheckDestructuring(cx, data, pn, tc, false); } else if (data) { if (!pn->isKind(PNK_NAME)) { ReportCompileErrorNumber(cx, TS(tc->parser), pn, JSREPORT_ERROR, JSMSG_NO_VARIABLE_NAME); return false; } ok = BindDestructuringVar(cx, data, pn, tc); } else { ok = BindDestructuringLHS(cx, pn, tc); } if (!ok) return false; } } /* * 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. * * Furthermore, the decompiler needs an abstract stack location to store * the decompilation of each let block/expr initializer. E.g., given: * * let (x = 1, [[]] = b, y = 3, {a:[]} = c) { ... } * * four slots are needed. * * To satisfy both constraints, we push a dummy slot (and add a * corresponding dummy property to the block object) for each initializer * that doesn't introduce at least one binding. */ if (toplevel && blockObj && blockCountBefore == blockObj->slotCount()) { bool redeclared; if (!blockObj->addVar(cx, INT_TO_JSID(blockCountBefore), blockCountBefore, &redeclared)) return false; JS_ASSERT(!redeclared); JS_ASSERT(blockObj->slotCount() == blockCountBefore + 1); } return true; } /* * Extend the pn_pos.end source coordinate of each name in a destructuring * binding such as * * var [x, y] = [function () y, 42]; * * to cover the entire initializer, so that the initialized bindings do not * appear to dominate any closures in the 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 void UndominateInitializers(ParseNode *left, const TokenPtr &end, TreeContext *tc) { if (left->isKind(PNK_RB)) { for (ParseNode *pn = left->pn_head; pn; pn = pn->pn_next) { /* Nullary comma is an elision; binary comma is an expression.*/ if (!pn->isKind(PNK_COMMA) || !pn->isArity(PN_NULLARY)) { if (pn->isKind(PNK_RB) || pn->isKind(PNK_RC)) UndominateInitializers(pn, end, tc); else pn->pn_pos.end = end; } } } else { JS_ASSERT(left->isKind(PNK_RC)); for (ParseNode *pair = left->pn_head; pair; pair = pair->pn_next) { JS_ASSERT(pair->isKind(PNK_COLON)); ParseNode *pn = pair->pn_right; if (pn->isKind(PNK_RB) || pn->isKind(PNK_RC)) UndominateInitializers(pn, end, tc); else pn->pn_pos.end = end; } } } ParseNode * Parser::destructuringExpr(BindData *data, TokenKind tt) { JS_ASSERT(tokenStream.isCurrentTokenType(tt)); tc->flags |= TCF_DECL_DESTRUCTURING; ParseNode *pn = primaryExpr(tt, JS_FALSE); tc->flags &= ~TCF_DECL_DESTRUCTURING; if (!pn) return NULL; if (!CheckDestructuring(context, data, pn, tc)) return NULL; return pn; } #endif /* JS_HAS_DESTRUCTURING */ ParseNode * Parser::returnOrYield(bool useAssignExpr) { TokenKind tt = tokenStream.currentToken().type; if (!tc->inFunction()) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_RETURN_OR_YIELD, (tt == TOK_RETURN) ? js_return_str : js_yield_str); return NULL; } ParseNode *pn = UnaryNode::create((tt == TOK_RETURN) ? PNK_RETURN : PNK_YIELD, tc); if (!pn) return NULL; #if JS_HAS_GENERATORS if (tt == TOK_YIELD) { /* * If we're within parens, we won't know if this is a generator expression until we see * a |for| token, so we have to delay flagging the current function. */ if (tc->parenDepth == 0) { tc->flags |= TCF_FUN_IS_GENERATOR; } else { tc->yieldCount++; tc->yieldNode = pn; } } #endif /* This is ugly, but we don't want to require a semicolon. */ TokenKind 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 ) { ParseNode *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, pn, 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, pn, JSREPORT_WARNING | JSREPORT_STRICT, JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE)) { return NULL; } return pn; } static ParseNode * PushLexicalScope(JSContext *cx, TreeContext *tc, StaticBlockObject &obj, StmtInfo *stmt) { ParseNode *pn = LexicalScopeNode::create(PNK_LEXICALSCOPE, tc); if (!pn) return NULL; ObjectBox *blockbox = tc->parser->newObjectBox(&obj); if (!blockbox) return NULL; PushBlockScope(tc, stmt, obj, -1); pn->setOp(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; } static ParseNode * PushLexicalScope(JSContext *cx, TreeContext *tc, StmtInfo *stmt) { StaticBlockObject *blockObj = StaticBlockObject::create(cx); if (!blockObj) return NULL; return PushLexicalScope(cx, tc, *blockObj, stmt); } #if JS_HAS_BLOCK_SCOPE struct AddDecl { uint32_t blockid; AddDecl(uint32_t blockid) : blockid(blockid) {} bool operator()(TreeContext *tc, StaticBlockObject &blockObj, const Shape &shape, JSAtom *atom) { ParseNode *def = (ParseNode *) blockObj.getSlot(shape.slot()).toPrivate(); def->pn_blockid = blockid; return Define(def, atom, tc, true); } }; static ParseNode * PushLetScope(JSContext *cx, TreeContext *tc, StaticBlockObject &blockObj, StmtInfo *stmt) { ParseNode *pn = PushLexicalScope(cx, tc, blockObj, stmt); if (!pn) return NULL; /* Tell codegen to emit JSOP_ENTERLETx (not JSOP_ENTERBLOCK). */ pn->pn_dflags |= PND_LET; /* Populate the new scope with decls found in the head with updated blockid. */ if (!ForEachLetDef(tc, blockObj, AddDecl(stmt->blockid))) return NULL; return pn; } /* * Parse a let block statement or let expression (determined by 'letContext'). * In both cases, bindings are not hoisted to the top of the enclosing block * and thus must be carefully injected between variables() and the let body. */ ParseNode * Parser::letBlock(LetContext letContext) { JS_ASSERT(tokenStream.currentToken().type == TOK_LET); ParseNode *pnlet = BinaryNode::create(PNK_LET, tc); if (!pnlet) return NULL; StaticBlockObject *blockObj = StaticBlockObject::create(context); if (!blockObj) return NULL; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_LET); ParseNode *vars = variables(PNK_LET, blockObj, DontHoistVars); if (!vars) return NULL; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_LET); StmtInfo stmtInfo; ParseNode *block = PushLetScope(context, tc, *blockObj, &stmtInfo); if (!block) return NULL; pnlet->pn_left = vars; pnlet->pn_right = block; ParseNode *ret; if (letContext == LetStatement && !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. */ ParseNode *semi = UnaryNode::create(PNK_SEMI, tc); if (!semi) return NULL; semi->pn_kid = pnlet; letContext = LetExpresion; ret = semi; } else { ret = pnlet; } if (letContext == LetStatement) { JS_ASSERT(block->getOp() == JSOP_LEAVEBLOCK); block->pn_expr = statements(); if (!block->pn_expr) return NULL; MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET); } else { JS_ASSERT(letContext == LetExpresion); block->setOp(JSOP_LEAVEBLOCKEXPR); block->pn_expr = assignExpr(); if (!block->pn_expr) return NULL; } PopStatement(tc); return ret; } #endif /* JS_HAS_BLOCK_SCOPE */ static bool PushBlocklikeStatement(StmtInfo *stmt, StmtType type, TreeContext *tc) { PushStatement(tc, stmt, type, -1); return GenerateBlockId(tc, stmt->blockid); } static ParseNode * NewBindingNode(JSAtom *atom, TreeContext *tc, StaticBlockObject *blockObj = NULL, VarContext varContext = HoistVars) { /* * If this name is being injected into an existing block/function, see if * it has already been declared or if it resolves an outstanding lexdep. * Otherwise, this is a let block/expr that introduces a new scope and thus * shadows existing decls and doesn't resolve existing lexdeps. Duplicate * names are caught by BindLet. */ if (!blockObj || varContext == HoistVars) { ParseNode *pn = tc->decls.lookupFirst(atom); AtomDefnPtr removal; if (pn) { JS_ASSERT(!pn->isPlaceholder()); } else { removal = tc->lexdeps->lookup(atom); pn = removal ? removal.value() : NULL; JS_ASSERT_IF(pn, pn->isPlaceholder()); } if (pn) { JS_ASSERT(pn->isDefn()); /* * 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(blockObj && pn->pn_blockid == tc->blockid(), pn->pn_blockid != tc->bodyid); if (pn->isPlaceholder() && pn->pn_blockid >= tc->blockid()) { pn->pn_blockid = tc->blockid(); tc->lexdeps->remove(removal); return pn; } } } /* Make a new node for this declarator name (or destructuring pattern). */ JS_ASSERT(tc->parser->tokenStream.currentToken().type == TOK_NAME); ParseNode *pn = NameNode::create(PNK_NAME, atom, tc); if (!pn) return NULL; if (atom == tc->parser->context->runtime->atomState.argumentsAtom) tc->countArgumentsUse(pn); return pn; } ParseNode * Parser::switchStatement() { JS_ASSERT(tc->parser->tokenStream.currentToken().type == TOK_SWITCH); ParseNode *pn = BinaryNode::create(PNK_SWITCH, tc); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); /* pn1 points to the switch's discriminant. */ ParseNode *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. */ StmtInfo stmtInfo; PushStatement(tc, &stmtInfo, STMT_SWITCH, -1); /* pn2 is a list of case nodes. The default case has pn_left == NULL */ ParseNode *pn2 = ListNode::create(PNK_STATEMENTLIST, tc); if (!pn2) return NULL; pn2->makeEmpty(); if (!GenerateBlockIdForStmtNode(pn2, tc)) return NULL; ParseNode *saveBlock = tc->blockNode; tc->blockNode = pn2; bool seenDefault = false; TokenKind tt; while ((tt = tokenStream.getToken()) != TOK_RC) { ParseNode *pn3; switch (tt) { case TOK_DEFAULT: if (seenDefault) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_DEFAULTS); return NULL; } seenDefault = true; pn3 = BinaryNode::create(PNK_DEFAULT, tc); if (!pn3) return NULL; break; case TOK_CASE: { pn3 = BinaryNode::create(PNK_CASE, tc); if (!pn3) return NULL; pn3->pn_left = expr(); if (!pn3->pn_left) return NULL; break; } case TOK_ERROR: return NULL; default: reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_SWITCH); return NULL; } pn2->append(pn3); if (pn2->pn_count == JS_BIT(16)) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_TOO_MANY_CASES); return NULL; } MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE); ParseNode *pn4 = ListNode::create(PNK_STATEMENTLIST, tc); if (!pn4) return NULL; pn4->makeEmpty(); while ((tt = tokenStream.peekToken(TSF_OPERAND)) != TOK_RC && tt != TOK_CASE && tt != TOK_DEFAULT) { if (tt == TOK_ERROR) return NULL; ParseNode *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; } bool Parser::matchInOrOf(bool *isForOfp) { if (tokenStream.matchToken(TOK_IN)) { *isForOfp = false; return true; } if (tokenStream.matchToken(TOK_NAME)) { if (tokenStream.currentToken().name() == context->runtime->atomState.ofAtom) { *isForOfp = true; return true; } tokenStream.ungetToken(); } return false; } ParseNode * Parser::forStatement() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); /* A FOR node is binary, left is loop control and right is the body. */ ParseNode *pn = BinaryNode::create(PNK_FOR, tc); if (!pn) return NULL; StmtInfo forStmt; PushStatement(tc, &forStmt, STMT_FOR_LOOP, -1); pn->setOp(JSOP_ITER); pn->pn_iflags = 0; if (tokenStream.matchToken(TOK_NAME)) { if (tokenStream.currentToken().name() == context->runtime->atomState.eachAtom) pn->pn_iflags = JSITER_FOREACH; else tokenStream.ungetToken(); } MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); /* * True if we have 'for (var/let/const ...)', except in the oddball case * where 'let' begins a let-expression in 'for (let (...) ...)'. */ bool forDecl = false; /* Non-null when forDecl is true for a 'for (let ...)' statement. */ StaticBlockObject *blockObj = NULL; /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ ParseNode *pn1; { TokenKind tt = tokenStream.peekToken(TSF_OPERAND); if (tt == TOK_SEMI) { if (pn->pn_iflags & JSITER_FOREACH) { reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP); return 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 || tt == TOK_CONST) { forDecl = true; tokenStream.consumeKnownToken(tt); pn1 = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST); } #if JS_HAS_BLOCK_SCOPE else if (tt == TOK_LET) { (void) tokenStream.getToken(); if (tokenStream.peekToken() == TOK_LP) { pn1 = letBlock(LetExpresion); } else { forDecl = true; blockObj = StaticBlockObject::create(context); if (!blockObj) return NULL; pn1 = variables(PNK_LET, blockObj, DontHoistVars); } } #endif else { pn1 = expr(); } tc->flags &= ~TCF_IN_FOR_INIT; if (!pn1) return NULL; } } JS_ASSERT_IF(forDecl, pn1->isArity(PN_LIST)); JS_ASSERT(!!blockObj == (forDecl && pn1->isOp(JSOP_NOP))); const TokenPos pos = tokenStream.currentToken().pos; /* If non-null, the parent that should be returned instead of forHead. */ ParseNode *forParent = 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 TreeContext. */ ParseNode *forHead; /* initialized by both branches. */ StmtInfo letStmt; /* used if blockObj != NULL. */ ParseNode *pn2, *pn3; /* forHead->pn_kid1 and pn_kid2. */ bool forOf; if (pn1 && matchInOrOf(&forOf)) { /* * Parse the rest of the for/in or for/of head. * * Here pn1 is everything to the left of 'in' or 'of'. At the end of * this block, pn1 is a decl or NULL, pn2 is the assignment target that * receives the enumeration value each iteration, and pn3 is the rhs of * 'in'. */ forStmt.type = STMT_FOR_IN_LOOP; /* Set pn_iflags and rule out invalid combinations. */ if (forOf && pn->pn_iflags != 0) { JS_ASSERT(pn->pn_iflags == JSITER_FOREACH); reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP); return NULL; } pn->pn_iflags |= (forOf ? JSITER_FOR_OF : JSITER_ENUMERATE); /* Check that the left side of the 'in' or 'of' is valid. */ if (forDecl ? (pn1->pn_count > 1 || pn1->isOp(JSOP_DEFCONST) #if JS_HAS_DESTRUCTURING || (versionNumber() == JSVERSION_1_7 && pn->isOp(JSOP_ITER) && !(pn->pn_iflags & JSITER_FOREACH) && (pn1->pn_head->isKind(PNK_RC) || (pn1->pn_head->isKind(PNK_RB) && pn1->pn_head->pn_count != 2) || (pn1->pn_head->isKind(PNK_ASSIGN) && (!pn1->pn_head->pn_left->isKind(PNK_RB) || pn1->pn_head->pn_left->pn_count != 2)))) #endif ) : (!pn1->isKind(PNK_NAME) && !pn1->isKind(PNK_DOT) && #if JS_HAS_DESTRUCTURING ((versionNumber() == JSVERSION_1_7 && pn->isOp(JSOP_ITER) && !(pn->pn_iflags & JSITER_FOREACH)) ? (!pn1->isKind(PNK_RB) || pn1->pn_count != 2) : (!pn1->isKind(PNK_RB) && !pn1->isKind(PNK_RC))) && #endif !pn1->isKind(PNK_LP) && #if JS_HAS_XML_SUPPORT !pn1->isKind(PNK_XMLUNARY) && #endif !pn1->isKind(PNK_LB))) { reportErrorNumber(pn1, JSREPORT_ERROR, JSMSG_BAD_FOR_LEFTSIDE); return NULL; } /* * After the following if-else, pn2 will point to the name or * destructuring pattern on in's left. pn1 will point to the decl, if * any, else NULL. Note that the "declaration with initializer" case * rewrites the loop-head, moving the decl and setting pn1 to NULL. */ pn2 = NULL; unsigned dflag = PND_ASSIGNED; if (forDecl) { /* Tell EmitVariables that pn1 is part of a for/in. */ pn1->pn_xflags |= PNX_FORINVAR; pn2 = pn1->pn_head; if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr()) #if JS_HAS_DESTRUCTURING || pn2->isKind(PNK_ASSIGN) #endif ) { /* * Declaration with initializer. * * Rewrite 'for ( x = i in o)' where is 'var' or * 'const' to hoist the initializer or the entire decl out of * the loop head. */ #if JS_HAS_BLOCK_SCOPE if (blockObj) { reportErrorNumber(pn2, JSREPORT_ERROR, JSMSG_INVALID_FOR_IN_INIT); return NULL; } #endif /* JS_HAS_BLOCK_SCOPE */ ParseNode *pnseq = ListNode::create(PNK_SEQ, tc); if (!pnseq) return NULL; 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); pn1 = NULL; #if JS_HAS_DESTRUCTURING if (pn2->isKind(PNK_ASSIGN)) { pn2 = pn2->pn_left; JS_ASSERT(pn2->isKind(PNK_RB) || pn2->isKind(PNK_RC) || pn2->isKind(PNK_NAME)); } #endif pnseq->append(pn); forParent = pnseq; } } else { /* Not a declaration. */ JS_ASSERT(!blockObj); pn2 = pn1; pn1 = NULL; if (!setAssignmentLhsOps(pn2, JSOP_NOP)) return NULL; } pn3 = expr(); if (!pn3) return NULL; if (blockObj) { /* * Now that the pn3 has been parsed, push the let scope. To hold * the blockObj for the emitter, wrap the TOK_LEXICALSCOPE node * created by PushLetScope around the for's initializer. This also * serves to indicate the let-decl to the emitter. */ ParseNode *block = PushLetScope(context, tc, *blockObj, &letStmt); if (!block) return NULL; letStmt.flags |= SIF_FOR_BLOCK; block->pn_expr = pn1; pn1 = block; } if (forDecl) { /* * pn2 is part of a declaration. Make a copy that can be passed to * EmitAssignment. Take care to do this after PushLetScope has * Define's the new binding since this pn2->isDefn() which tells * CloneLeftHandSide to make the new pn2 a use. */ pn2 = CloneLeftHandSide(pn2, tc); if (!pn2) return NULL; } switch (pn2->getKind()) { case PNK_NAME: /* Beware 'for (arguments in ...)' with or without a 'var'. */ NoteLValue(context, pn2, tc, dflag); break; #if JS_HAS_DESTRUCTURING case PNK_ASSIGN: JS_NOT_REACHED("forStatement TOK_ASSIGN"); break; case PNK_RB: case PNK_RC: if (versionNumber() == JSVERSION_1_7) { /* * Destructuring for-in requires [key, value] enumeration * in JS1.7. */ JS_ASSERT(pn->isOp(JSOP_ITER)); if (!(pn->pn_iflags & JSITER_FOREACH)) pn->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE; } break; #endif default:; } forHead = TernaryNode::create(PNK_FORIN, tc); if (!forHead) return NULL; } else { if (blockObj) { /* * Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }' * to induce the correct scoping for A. */ ParseNode *block = PushLetScope(context, tc, *blockObj, &letStmt); if (!block) return NULL; letStmt.flags |= SIF_FOR_BLOCK; ParseNode *let = new_(PNK_LET, JSOP_NOP, pos, pn1, block); if (!let) return NULL; pn1 = NULL; block->pn_expr = pn; forParent = let; } if (pn->pn_iflags & JSITER_FOREACH) { reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP); return NULL; } pn->setOp(JSOP_NOP); /* Parse the loop condition or null into pn2. */ MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); if (tokenStream.peekToken(TSF_OPERAND) == 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); if (tokenStream.peekToken(TSF_OPERAND) == TOK_RP) { pn3 = NULL; } else { pn3 = expr(); if (!pn3) return NULL; } forHead = TernaryNode::create(PNK_FORHEAD, tc); if (!forHead) return NULL; } forHead->pn_pos = pos; forHead->setOp(JSOP_NOP); forHead->pn_kid1 = pn1; forHead->pn_kid2 = pn2; forHead->pn_kid3 = pn3; pn->pn_left = forHead; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); /* Parse the loop body. */ ParseNode *body = statement(); if (!body) return NULL; /* Record the absolute line number for source note emission. */ pn->pn_pos.end = body->pn_pos.end; pn->pn_right = body; if (forParent) { forParent->pn_pos.begin = pn->pn_pos.begin; forParent->pn_pos.end = pn->pn_pos.end; } #if JS_HAS_BLOCK_SCOPE if (blockObj) PopStatement(tc); #endif PopStatement(tc); return forParent ? forParent : pn; } ParseNode * Parser::tryStatement() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_TRY)); /* * 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. */ ParseNode *pn = TernaryNode::create(PNK_TRY, tc); if (!pn) return NULL; pn->setOp(JSOP_NOP); MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY); StmtInfo 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); ParseNode *lastCatch; ParseNode *catchList = NULL; TokenKind tt = tokenStream.getToken(); if (tt == TOK_CATCH) { catchList = ListNode::create(PNK_CATCHLIST, tc); if (!catchList) return NULL; catchList->makeEmpty(); lastCatch = NULL; do { ParseNode *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, tc, &stmtInfo); if (!pnblock) return NULL; stmtInfo.type = STMT_CATCH; /* * Legal catch forms are: * catch (lhs) * catch (lhs if ) * where lhs is a name or a destructuring left-hand side. * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD) */ ParseNode *pn2 = TernaryNode::create(PNK_CATCH, 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.initLet(HoistVars, *tc->blockChain, JSMSG_TOO_MANY_CATCH_VARS); JS_ASSERT(data.let.blockObj && data.let.blockObj == pnblock->pn_objbox->object); tt = tokenStream.getToken(); ParseNode *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().name(); pn3 = NewBindingNode(label, tc); 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; } ParseNode * Parser::withStatement() { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_WITH)); /* * 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; } ParseNode *pn = BinaryNode::create(PNK_WITH, tc); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); ParseNode *pn2 = parenExpr(); if (!pn2) return NULL; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); pn->pn_left = pn2; ParseNode *oldWith = tc->innermostWith; tc->innermostWith = pn; StmtInfo stmtInfo; 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). */ for (AtomDefnRange r = tc->lexdeps->all(); !r.empty(); r.popFront()) { Definition *defn = r.front().value(); Definition *lexdep = defn->resolve(); DeoptimizeUsesWithin(lexdep, pn->pn_pos); } return pn; } #if JS_HAS_BLOCK_SCOPE ParseNode * Parser::letStatement() { ParseNode *pn; do { /* Check for a let statement or let expression. */ if (tokenStream.peekToken() == TOK_LP) { pn = letBlock(LetStatement); if (!pn) return NULL; JS_ASSERT(pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); if (pn->isKind(PNK_LET) && pn->pn_expr->getOp() == JSOP_LEAVEBLOCK) return pn; /* Let expressions require automatic semicolon insertion. */ JS_ASSERT(pn->isKind(PNK_SEMI) || pn->isOp(JSOP_NOP)); 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 * StmtInfo 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 StmtInfo isn't yet a scope statement) then * we also need to set tc->blockNode to be our TOK_LEXICALSCOPE. */ StmtInfo *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->blockChain == stmt->blockObj); } 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. */ pn = variables(PNK_VAR); 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. */ StaticBlockObject *blockObj = StaticBlockObject::create(tc->parser->context); if (!blockObj) return NULL; ObjectBox *blockbox = tc->parser->newObjectBox(blockObj); 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; blockObj->setEnclosingBlock(tc->blockChain); tc->blockChain = blockObj; stmt->blockObj = blockObj; #ifdef DEBUG ParseNode *tmp = tc->blockNode; JS_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE)); #endif /* Create a new lexical scope node for these statements. */ ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, tc); if (!pn1) return NULL; pn1->setOp(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(PNK_LET, tc->blockChain, HoistVars); if (!pn) return NULL; pn->pn_xflags = PNX_POPVAR; } while (0); /* Check termination of this primitive statement. */ return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; } #endif ParseNode * Parser::expressionStatement() { tokenStream.ungetToken(); ParseNode *pn2 = expr(); if (!pn2) return NULL; if (tokenStream.peekToken() == TOK_COLON) { if (!pn2->isKind(PNK_NAME)) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_LABEL); return NULL; } JSAtom *label = pn2->pn_atom; for (StmtInfo *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. */ StmtInfo stmtInfo; PushStatement(tc, &stmtInfo, STMT_LABEL, -1); stmtInfo.label = label; ParseNode *pn = statement(); if (!pn) return NULL; /* Normalize empty statement to empty block for the decompiler. */ if (pn->isKind(PNK_SEMI) && !pn->pn_kid) { pn->setKind(PNK_STATEMENTLIST); pn->setArity(PN_LIST); pn->makeEmpty(); } /* Pop the label, set pn_expr, and return early. */ PopStatement(tc); pn2->setKind(PNK_COLON); pn2->pn_pos.end = pn->pn_pos.end; pn2->pn_expr = pn; return pn2; } ParseNode *pn = UnaryNode::create(PNK_SEMI, tc); if (!pn) return NULL; pn->pn_pos = pn2->pn_pos; pn->pn_kid = pn2; switch (pn2->getKind()) { case PNK_LP: /* * Flag lambdas immediately applied as statements as instances of * the JS "module pattern". See CheckForImmediatelyAppliedLambda. */ if (pn2->pn_head->isKind(PNK_FUNCTION) && !pn2->pn_head->pn_funbox->node->isFunArg()) { pn2->pn_head->pn_funbox->tcflags |= TCF_FUN_MODULE_PATTERN; } break; case PNK_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. */ JS_ASSERT(pn2->isOp(JSOP_NOP)); if (tc->funbox && pn2->pn_left->isOp(JSOP_SETPROP) && pn2->pn_left->pn_expr->isKind(PNK_THIS) && pn2->pn_right->isOp(JSOP_LAMBDA)) { JS_ASSERT(!pn2->isDefn()); JS_ASSERT(!pn2->isUsed()); 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; } ParseNode * Parser::statement() { ParseNode *pn; JS_CHECK_RECURSION(context, return NULL); switch (tokenStream.getToken(TSF_OPERAND)) { case TOK_FUNCTION: { #if JS_HAS_XML_SUPPORT if (!tc->inStrictMode()) { TokenKind tt = tokenStream.peekToken(TSF_KEYWORD_IS_NAME); if (tt == TOK_DBLCOLON) return expressionStatement(); } #endif return functionStmt(); } case TOK_IF: { /* An IF node has three kids: condition, then, and optional else. */ pn = TernaryNode::create(PNK_IF, tc); if (!pn) return NULL; ParseNode *pn1 = condition(); if (!pn1) return NULL; StmtInfo stmtInfo; PushStatement(tc, &stmtInfo, STMT_IF, -1); ParseNode *pn2 = statement(); if (!pn2) return NULL; if (pn2->isKind(PNK_SEMI) && !pn2->pn_kid && !reportErrorNumber(NULL, JSREPORT_WARNING | JSREPORT_STRICT, JSMSG_EMPTY_CONSEQUENT)) { return NULL; } ParseNode *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(PNK_WHILE, tc); if (!pn) return NULL; StmtInfo stmtInfo; PushStatement(tc, &stmtInfo, STMT_WHILE_LOOP, -1); ParseNode *pn2 = condition(); if (!pn2) return NULL; pn->pn_left = pn2; ParseNode *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(PNK_DOWHILE, tc); if (!pn) return NULL; StmtInfo stmtInfo; PushStatement(tc, &stmtInfo, STMT_DO_LOOP, -1); ParseNode *pn2 = statement(); if (!pn2) return NULL; pn->pn_left = pn2; MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO); ParseNode *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(PNK_THROW, 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; } ParseNode *pn2 = expr(); if (!pn2) return NULL; pn->pn_pos.end = pn2->pn_pos.end; pn->setOp(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: { TokenPtr begin = tokenStream.currentToken().pos.begin; PropertyName *label; if (!MatchLabel(context, &tokenStream, &label)) return NULL; TokenPtr end = tokenStream.currentToken().pos.end; pn = new_(label, begin, end); if (!pn) return NULL; StmtInfo *stmt = tc->topStmt; 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; } } break; } case TOK_CONTINUE: { TokenPtr begin = tokenStream.currentToken().pos.begin; PropertyName *label; if (!MatchLabel(context, &tokenStream, &label)) return NULL; TokenPtr end = tokenStream.currentToken().pos.begin; pn = new_(label, begin, end); if (!pn) return NULL; StmtInfo *stmt = tc->topStmt; if (label) { for (StmtInfo *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; } } break; } case TOK_WITH: return withStatement(); case TOK_VAR: pn = variables(PNK_VAR); if (!pn) return NULL; /* Tell js_EmitTree to generate a final POP. */ pn->pn_xflags |= PNX_POPVAR; break; case TOK_CONST: pn = variables(PNK_CONST); 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: { unsigned oldflags; oldflags = tc->flags; tc->flags = oldflags & ~TCF_HAS_FUNCTION_STMT; StmtInfo 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_SEMI: pn = UnaryNode::create(PNK_SEMI, tc); if (!pn) return NULL; return pn; case TOK_DEBUGGER: pn = tc->parser->new_(tokenStream.currentToken().pos); if (!pn) return NULL; tc->flags |= TCF_FUN_HEAVYWEIGHT; break; #if JS_HAS_XML_SUPPORT case TOK_DEFAULT: { if (tc->inStrictMode()) return expressionStatement(); pn = UnaryNode::create(PNK_DEFXMLNS, tc); if (!pn) return NULL; if (!tokenStream.matchToken(TOK_NAME) || tokenStream.currentToken().name() != context->runtime->atomState.xmlAtom || !tokenStream.matchToken(TOK_NAME) || tokenStream.currentToken().name() != context->runtime->atomState.namespaceAtom || !tokenStream.matchToken(TOK_ASSIGN)) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_DEFAULT_XML_NAMESPACE); return NULL; } JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NOP); /* Is this an E4X dagger I see before me? */ tc->flags |= TCF_FUN_HEAVYWEIGHT; ParseNode *pn2 = expr(); if (!pn2) return NULL; pn->setOp(JSOP_DEFXMLNS); pn->pn_pos.end = pn2->pn_pos.end; pn->pn_kid = pn2; break; } #endif case TOK_ERROR: return NULL; default: return expressionStatement(); } /* Check termination of this primitive statement. */ return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL; } /* * The 'blockObj' parameter is non-null when parsing the 'vars' in a let * expression, block statement, non-top-level let declaration in statement * context, and the let-initializer of a for-statement. */ ParseNode * Parser::variables(ParseNodeKind kind, StaticBlockObject *blockObj, VarContext varContext) { /* * The four options here are: * - PNK_VAR: We're parsing var declarations. * - PNK_CONST: We're parsing const declarations. * - PNK_LET: We are parsing a let declaration. * - PNK_LP: We are parsing the head of a let block. */ JS_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || kind == PNK_LP); ParseNode *pn = ListNode::create(kind, tc); if (!pn) return NULL; pn->setOp(blockObj ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST); 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. */ BindData data; if (blockObj) data.initLet(varContext, *blockObj, JSMSG_TOO_MANY_LOCALS); else data.initVarOrConst(pn->getOp()); ParseNode *pn2; do { TokenKind 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, tc)) return NULL; bool ignored; if ((tc->flags & TCF_IN_FOR_INIT) && matchInOrOf(&ignored)) { tokenStream.ungetToken(); pn->append(pn2); continue; } MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL); JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NOP); ParseNode *init = assignExpr(); if (!init) return NULL; UndominateInitializers(pn2, init->pn_pos.end, tc); pn2 = ParseNode::newBinaryOrAppend(PNK_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; } PropertyName *name = tokenStream.currentToken().name(); pn2 = NewBindingNode(name, tc, blockObj, varContext); if (!pn2) return NULL; if (data.op == JSOP_DEFCONST) pn2->pn_dflags |= PND_CONST; data.pn = pn2; if (!data.binder(context, &data, name, tc)) return NULL; pn->append(pn2); if (tokenStream.matchToken(TOK_ASSIGN)) { JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NOP); ParseNode *init = assignExpr(); if (!init) return NULL; if (pn2->isUsed()) { 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->setOp(pn2->isOp(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() && name == context->runtime->atomState.argumentsAtom) { tc->noteArgumentsNameUse(pn2); if (!blockObj) tc->flags |= (TCF_FUN_HEAVYWEIGHT | TCF_FUN_LOCAL_ARGUMENTS); } } } while (tokenStream.matchToken(TOK_COMMA)); pn->pn_pos.end = pn->last()->pn_pos.end; return pn; } ParseNode * Parser::expr() { ParseNode *pn = assignExpr(); if (pn && tokenStream.matchToken(TOK_COMMA)) { ParseNode *pn2 = ListNode::create(PNK_COMMA, 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->isKind(PNK_YIELD) && !pn2->isInParens()) { 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; } /* * For a number of the expression parsers we define an always-inlined version * and a never-inlined version (which just calls the always-inlined version). * Using the always-inlined version in the hot call-sites givs a ~5% parsing * speedup. These macros help avoid some boilerplate code. */ #define BEGIN_EXPR_PARSER(name) \ JS_ALWAYS_INLINE ParseNode * \ Parser::name##i() #define END_EXPR_PARSER(name) \ JS_NEVER_INLINE ParseNode * \ Parser::name##n() { \ return name##i(); \ } BEGIN_EXPR_PARSER(mulExpr1) { ParseNode *pn = unaryExpr(); /* * Note: unlike addExpr1() et al, we use getToken() here instead of * isCurrentTokenType() because unaryExpr() doesn't leave the TokenStream * state one past the end of the unary expression. */ TokenKind tt; while (pn && ((tt = tokenStream.getToken()) == TOK_STAR || tt == TOK_DIV || tt == TOK_MOD)) { ParseNodeKind kind = (tt == TOK_STAR) ? PNK_STAR : (tt == TOK_DIV) ? PNK_DIV : PNK_MOD; JSOp op = tokenStream.currentToken().t_op; pn = ParseNode::newBinaryOrAppend(kind, op, pn, unaryExpr(), tc); } return pn; } END_EXPR_PARSER(mulExpr1) BEGIN_EXPR_PARSER(addExpr1) { ParseNode *pn = mulExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_PLUS, TOK_MINUS)) { TokenKind tt = tokenStream.currentToken().type; JSOp op = (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB; ParseNodeKind kind = (tt == TOK_PLUS) ? PNK_ADD : PNK_SUB; pn = ParseNode::newBinaryOrAppend(kind, op, pn, mulExpr1n(), tc); } return pn; } END_EXPR_PARSER(addExpr1) inline ParseNodeKind ShiftTokenToParseNodeKind(const Token &token) { switch (token.type) { case TOK_LSH: return PNK_LSH; case TOK_RSH: return PNK_RSH; default: JS_ASSERT(token.type == TOK_URSH); return PNK_URSH; } } BEGIN_EXPR_PARSER(shiftExpr1) { ParseNode *left = addExpr1i(); while (left && tokenStream.isCurrentTokenShift()) { ParseNodeKind kind = ShiftTokenToParseNodeKind(tokenStream.currentToken()); JSOp op = tokenStream.currentToken().t_op; ParseNode *right = addExpr1n(); if (!right) return NULL; left = tc->parser->new_(kind, op, left, right); } return left; } END_EXPR_PARSER(shiftExpr1) inline ParseNodeKind RelationalTokenToParseNodeKind(const Token &token) { switch (token.type) { case TOK_IN: return PNK_IN; case TOK_INSTANCEOF: return PNK_INSTANCEOF; case TOK_LT: return PNK_LT; case TOK_LE: return PNK_LE; case TOK_GT: return PNK_GT; default: JS_ASSERT(token.type == TOK_GE); return PNK_GE; } } BEGIN_EXPR_PARSER(relExpr1) { unsigned 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; ParseNode *pn = shiftExpr1i(); while (pn && (tokenStream.isCurrentTokenRelational() || /* * Recognize the 'in' token as an operator only if we're not * currently in the init expr of a for loop. */ (inForInitFlag == 0 && tokenStream.isCurrentTokenType(TOK_IN)) || tokenStream.isCurrentTokenType(TOK_INSTANCEOF))) { ParseNodeKind kind = RelationalTokenToParseNodeKind(tokenStream.currentToken()); JSOp op = tokenStream.currentToken().t_op; pn = ParseNode::newBinaryOrAppend(kind, op, pn, shiftExpr1n(), tc); } /* Restore previous state of inForInit flag. */ tc->flags |= inForInitFlag; return pn; } END_EXPR_PARSER(relExpr1) inline ParseNodeKind EqualityTokenToParseNodeKind(const Token &token) { switch (token.type) { case TOK_STRICTEQ: return PNK_STRICTEQ; case TOK_EQ: return PNK_EQ; case TOK_STRICTNE: return PNK_STRICTNE; default: JS_ASSERT(token.type == TOK_NE); return PNK_NE; } } BEGIN_EXPR_PARSER(eqExpr1) { ParseNode *left = relExpr1i(); while (left && tokenStream.isCurrentTokenEquality()) { ParseNodeKind kind = EqualityTokenToParseNodeKind(tokenStream.currentToken()); JSOp op = tokenStream.currentToken().t_op; ParseNode *right = relExpr1n(); if (!right) return NULL; left = tc->parser->new_(kind, op, left, right); } return left; } END_EXPR_PARSER(eqExpr1) BEGIN_EXPR_PARSER(bitAndExpr1) { ParseNode *pn = eqExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_BITAND)) pn = ParseNode::newBinaryOrAppend(PNK_BITAND, JSOP_BITAND, pn, eqExpr1n(), tc); return pn; } END_EXPR_PARSER(bitAndExpr1) BEGIN_EXPR_PARSER(bitXorExpr1) { ParseNode *pn = bitAndExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_BITXOR)) pn = ParseNode::newBinaryOrAppend(PNK_BITXOR, JSOP_BITXOR, pn, bitAndExpr1n(), tc); return pn; } END_EXPR_PARSER(bitXorExpr1) BEGIN_EXPR_PARSER(bitOrExpr1) { ParseNode *pn = bitXorExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_BITOR)) pn = ParseNode::newBinaryOrAppend(PNK_BITOR, JSOP_BITOR, pn, bitXorExpr1n(), tc); return pn; } END_EXPR_PARSER(bitOrExpr1) BEGIN_EXPR_PARSER(andExpr1) { ParseNode *pn = bitOrExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_AND)) pn = ParseNode::newBinaryOrAppend(PNK_AND, JSOP_AND, pn, bitOrExpr1n(), tc); return pn; } END_EXPR_PARSER(andExpr1) JS_ALWAYS_INLINE ParseNode * Parser::orExpr1() { ParseNode *pn = andExpr1i(); while (pn && tokenStream.isCurrentTokenType(TOK_OR)) pn = ParseNode::newBinaryOrAppend(PNK_OR, JSOP_OR, pn, andExpr1n(), tc); return pn; } JS_ALWAYS_INLINE ParseNode * Parser::condExpr1() { ParseNode *condition = orExpr1(); if (!condition || !tokenStream.isCurrentTokenType(TOK_HOOK)) return condition; /* * 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. */ unsigned oldflags = tc->flags; tc->flags &= ~TCF_IN_FOR_INIT; ParseNode *thenExpr = assignExpr(); tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); if (!thenExpr) return NULL; MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND); ParseNode *elseExpr = assignExpr(); if (!elseExpr) return NULL; tokenStream.getToken(); /* read one token past the end */ return new_(condition, thenExpr, elseExpr); } bool Parser::setAssignmentLhsOps(ParseNode *pn, JSOp op) { switch (pn->getKind()) { case PNK_NAME: if (!CheckStrictAssignment(context, tc, pn)) return false; pn->setOp(pn->isOp(JSOP_GETLOCAL) ? JSOP_SETLOCAL : JSOP_SETNAME); NoteLValue(context, pn, tc); break; case PNK_DOT: pn->setOp(JSOP_SETPROP); break; case PNK_LB: pn->setOp(JSOP_SETELEM); break; #if JS_HAS_DESTRUCTURING case PNK_RB: case PNK_RC: if (op != JSOP_NOP) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_DESTRUCT_ASS); return false; } if (!CheckDestructuring(context, NULL, pn, tc)) return false; break; #endif case PNK_LP: if (!MakeSetCall(context, pn, tc, JSMSG_BAD_LEFTSIDE_OF_ASS)) return false; break; #if JS_HAS_XML_SUPPORT case PNK_XMLUNARY: JS_ASSERT(pn->isOp(JSOP_XMLNAME)); pn->setOp(JSOP_SETXMLNAME); break; #endif default: reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_LEFTSIDE_OF_ASS); return false; } return true; } ParseNode * Parser::assignExpr() { JS_CHECK_RECURSION(context, return NULL); #if JS_HAS_GENERATORS if (tokenStream.matchToken(TOK_YIELD, TSF_OPERAND)) return returnOrYield(true); #endif ParseNode *lhs = condExpr1(); if (!lhs) return NULL; ParseNodeKind kind; switch (tokenStream.currentToken().type) { case TOK_ASSIGN: kind = PNK_ASSIGN; break; case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; break; case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; break; case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; break; case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; break; case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; break; case TOK_LSHASSIGN: kind = PNK_LSHASSIGN; break; case TOK_RSHASSIGN: kind = PNK_RSHASSIGN; break; case TOK_URSHASSIGN: kind = PNK_URSHASSIGN; break; case TOK_MULASSIGN: kind = PNK_MULASSIGN; break; case TOK_DIVASSIGN: kind = PNK_DIVASSIGN; break; case TOK_MODASSIGN: kind = PNK_MODASSIGN; break; default: JS_ASSERT(!tokenStream.isCurrentTokenAssignment()); tokenStream.ungetToken(); return lhs; } JSOp op = tokenStream.currentToken().t_op; if (!setAssignmentLhsOps(lhs, op)) return NULL; ParseNode *rhs = assignExpr(); if (!rhs) return NULL; if (lhs->isKind(PNK_NAME) && lhs->isUsed()) { Definition *dn = lhs->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 ParseNode::newBinaryOrAppend(kind, op, lhs, rhs, tc); } static bool SetLvalKid(JSContext *cx, TokenStream *ts, TreeContext *tc, ParseNode *pn, ParseNode *kid, const char *name) { if (!kid->isKind(PNK_NAME) && !kid->isKind(PNK_DOT) && (!kid->isKind(PNK_LP) || (!kid->isOp(JSOP_CALL) && !kid->isOp(JSOP_EVAL) && !kid->isOp(JSOP_FUNCALL) && !kid->isOp(JSOP_FUNAPPLY))) && #if JS_HAS_XML_SUPPORT !kid->isKind(PNK_XMLUNARY) && #endif !kid->isKind(PNK_LB)) { ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, JSMSG_BAD_OPERAND, name); return false; } if (!CheckStrictAssignment(cx, tc, kid)) return false; pn->pn_kid = kid; return true; } static const char incop_name_str[][10] = {"increment", "decrement"}; static JSBool SetIncOpKid(JSContext *cx, TokenStream *ts, TreeContext *tc, ParseNode *pn, ParseNode *kid, TokenKind tt, bool preorder) { JSOp op; if (!SetLvalKid(cx, ts, tc, pn, kid, incop_name_str[tt == TOK_DEC])) return false; switch (kid->getKind()) { case PNK_NAME: op = (tt == TOK_INC) ? (preorder ? JSOP_INCNAME : JSOP_NAMEINC) : (preorder ? JSOP_DECNAME : JSOP_NAMEDEC); NoteLValue(cx, kid, tc); break; case PNK_DOT: op = (tt == TOK_INC) ? (preorder ? JSOP_INCPROP : JSOP_PROPINC) : (preorder ? JSOP_DECPROP : JSOP_PROPDEC); break; case PNK_LP: if (!MakeSetCall(cx, kid, tc, JSMSG_BAD_INCOP_OPERAND)) return JS_FALSE; /* FALL THROUGH */ #if JS_HAS_XML_SUPPORT case PNK_XMLUNARY: if (kid->isOp(JSOP_XMLNAME)) kid->setOp(JSOP_SETXMLNAME); /* FALL THROUGH */ #endif case PNK_LB: op = (tt == TOK_INC) ? (preorder ? JSOP_INCELEM : JSOP_ELEMINC) : (preorder ? JSOP_DECELEM : JSOP_ELEMDEC); break; default: JS_ASSERT(0); op = JSOP_NOP; } pn->setOp(op); return JS_TRUE; } ParseNode * Parser::unaryOpExpr(ParseNodeKind kind, JSOp op) { TokenPtr begin = tokenStream.currentToken().pos.begin; ParseNode *kid = unaryExpr(); if (!kid) return NULL; return new_(kind, op, TokenPos::make(begin, kid->pn_pos.end), kid); } ParseNode * Parser::unaryExpr() { ParseNode *pn, *pn2; JS_CHECK_RECURSION(context, return NULL); switch (TokenKind tt = tokenStream.getToken(TSF_OPERAND)) { case TOK_TYPEOF: return unaryOpExpr(PNK_TYPEOF, JSOP_TYPEOF); case TOK_VOID: return unaryOpExpr(PNK_VOID, JSOP_VOID); case TOK_NOT: return unaryOpExpr(PNK_NOT, JSOP_NOT); case TOK_BITNOT: return unaryOpExpr(PNK_BITNOT, JSOP_BITNOT); case TOK_PLUS: return unaryOpExpr(PNK_POS, JSOP_POS); case TOK_MINUS: return unaryOpExpr(PNK_NEG, JSOP_NEG); case TOK_INC: case TOK_DEC: pn = UnaryNode::create((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT, tc); if (!pn) return NULL; pn2 = memberExpr(JS_TRUE); if (!pn2) return NULL; if (!SetIncOpKid(context, &tokenStream, tc, pn, pn2, tt, true)) return NULL; pn->pn_pos.end = pn2->pn_pos.end; break; case TOK_DELETE: { pn = UnaryNode::create(PNK_DELETE, 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 (foldConstants && !FoldConstants(context, pn2, tc)) return NULL; switch (pn2->getKind()) { case PNK_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 PNK_NAME: if (!ReportStrictModeError(context, &tokenStream, tc, pn, JSMSG_DEPRECATED_DELETE_OPERAND)) { return NULL; } pn2->setOp(JSOP_DELNAME); if (pn2->pn_atom == context->runtime->atomState.argumentsAtom) { tc->flags |= TCF_FUN_HEAVYWEIGHT; tc->countArgumentsUse(pn2); } 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) { tokenStream.consumeKnownToken(tt); pn2 = UnaryNode::create((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT, tc); if (!pn2) return NULL; if (!SetIncOpKid(context, &tokenStream, tc, pn2, pn, tt, 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 { ParseNode *root; TreeContext *tc; bool genexp; unsigned adjust; unsigned funcLevel; public: CompExprTransplanter(ParseNode *pn, TreeContext *tc, bool ge, unsigned adj) : root(pn), tc(tc), genexp(ge), adjust(adj), funcLevel(0) { } bool transplant(ParseNode *pn); }; /* * A helper for lazily checking for the presence of illegal |yield| or |arguments| * tokens inside of generator expressions. This must be done lazily since we don't * know whether we're in a generator expression until we see the "for" token after * we've already parsed the body expression. * * Use in any context which may turn out to be inside a generator expression. This * includes parenthesized expressions and argument lists, and it includes the tail * of generator expressions. * * The guard will keep track of any |yield| or |arguments| tokens that occur while * parsing the body. As soon as the parser reaches the end of the body expression, * call endBody() to reset the context's state, and then immediately call: * * - checkValidBody() if this *did* turn out to be a generator expression * - maybeNoteGenerator() if this *did not* turn out to be a generator expression */ class GenexpGuard { TreeContext *tc; uint32_t startYieldCount; uint32_t startArgumentsCount; public: explicit GenexpGuard(TreeContext *tc) : tc(tc) { if (tc->parenDepth == 0) { tc->yieldCount = tc->argumentsCount = 0; tc->yieldNode = tc->argumentsNode = NULL; } startYieldCount = tc->yieldCount; startArgumentsCount = tc->argumentsCount; tc->parenDepth++; } void endBody(); bool checkValidBody(ParseNode *pn); bool maybeNoteGenerator(ParseNode *pn); }; void GenexpGuard::endBody() { tc->parenDepth--; } /* * Check whether a |yield| or |arguments| token has been encountered in the * body expression, and if so, report an error. * * Call this after endBody() when determining that the body *was* in a * generator expression. */ bool GenexpGuard::checkValidBody(ParseNode *pn) { if (tc->yieldCount > startYieldCount) { ParseNode *errorNode = tc->yieldNode; if (!errorNode) errorNode = pn; tc->parser->reportErrorNumber(errorNode, JSREPORT_ERROR, JSMSG_BAD_GENEXP_BODY, js_yield_str); return false; } if (tc->argumentsCount > startArgumentsCount) { ParseNode *errorNode = tc->argumentsNode; if (!errorNode) errorNode = pn; tc->parser->reportErrorNumber(errorNode, JSREPORT_ERROR, JSMSG_BAD_GENEXP_BODY, js_arguments_str); return false; } return true; } /* * Check whether a |yield| token has been encountered in the body expression, * and if so, note that the current function is a generator function. * * Call this after endBody() when determining that the body *was not* in a * generator expression. */ bool GenexpGuard::maybeNoteGenerator(ParseNode *pn) { if (tc->yieldCount > 0) { tc->flags |= TCF_FUN_IS_GENERATOR; if (!tc->inFunction()) { tc->parser->reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_RETURN_OR_YIELD, js_yield_str); return false; } if (tc->flags & TCF_RETURN_EXPR) { /* At the time we saw the yield, we might not have set TCF_FUN_IS_GENERATOR yet. */ ReportBadReturn(tc->parser->context, tc, pn, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_RETURN, JSMSG_BAD_ANON_GENERATOR_RETURN); return false; } } return true; } /* * 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(ParseNode *pn, TreeContext *tc) { if (!pn->pn_cookie.isFree()) { unsigned 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(ParseNode *pn, unsigned adjust, TreeContext *tc) { JS_ASSERT(pn->isArity(PN_LIST) || pn->isArity(PN_FUNC) || pn->isArity(PN_NAME)); pn->pn_blockid += adjust; if (pn->pn_blockid >= tc->blockidGen) tc->blockidGen = pn->pn_blockid + 1; } bool CompExprTransplanter::transplant(ParseNode *pn) { if (!pn) return true; switch (pn->getArity()) { case PN_LIST: for (ParseNode *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. */ FunctionBox *funbox = pn->pn_funbox; funbox->level = tc->staticLevel + funcLevel; if (++funcLevel == 1 && genexp) { FunctionBox *parent = tc->funbox; FunctionBox **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->isArity(PN_FUNC)) --funcLevel; if (pn->isDefn()) { if (genexp && !BumpStaticLevel(pn, tc)) return false; } else if (pn->isUsed()) { JS_ASSERT(!pn->isOp(JSOP_NOP)); JS_ASSERT(pn->pn_cookie.isFree()); Definition *dn = pn->pn_lexdef; JS_ASSERT(dn->isDefn()); /* * 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 StmtInfo *stmt = LexicalLookup(tc, atom, NULL); JS_ASSERT(!stmt || stmt != tc->topStmt); #endif if (genexp && !dn->isOp(JSOP_CALLEE)) { JS_ASSERT(!tc->decls.lookupFirst(atom)); if (dn->pn_pos < root->pn_pos) { /* * The variable originally appeared to be a use of a * definition or placeholder outside the generator, but now * we know it is scoped within the comprehension tail's * clauses. Make it (along with any other uses within the * generator) a use of a new placeholder in the generator's * lexdeps. */ Definition *dn2 = MakePlaceholder(pn, tc); if (!dn2) return false; dn2->pn_pos = root->pn_pos; /* * Change all uses of |dn| that lie within the generator's * |yield| expression into uses of dn2. */ ParseNode **pnup = &dn->dn_uses; ParseNode *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; if (!tc->lexdeps->put(atom, dn2)) return false; } else if (dn->isPlaceholder()) { /* * The variable first occurs free in the 'yield' expression; * move the existing placeholder node (and all its uses) * from the parent's lexdeps into the generator's lexdeps. */ tc->parent->lexdeps->remove(atom); if (!tc->lexdeps->put(atom, dn)) return false; } } } if (pn->pn_pos >= root->pn_pos) AdjustBlockId(pn, adjust, tc); break; case PN_NAMESET: if (!transplant(pn->pn_tree)) return false; break; case PN_NULLARY: /* Nothing. */ 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 |kind, op, kid|. */ ParseNode * Parser::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp, ParseNodeKind kind, JSOp op) { unsigned adjust; ParseNode *pn, *pn2, *pn3, **pnp; StmtInfo stmtInfo; BindData data; TokenKind tt; JS_ASSERT(tokenStream.currentToken().type == TOK_FOR); if (kind == PNK_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, tc, &stmtInfo); if (!pn) return NULL; adjust = pn->pn_blockid - blockid; } else { JS_ASSERT(kind == PNK_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 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, 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, kind == PNK_SEMI, adjust); transplanter.transplant(kid); JS_ASSERT(tc->blockChain && tc->blockChain == pn->pn_objbox->object); data.initLet(HoistVars, *tc->blockChain, 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/of. */ pn2 = BinaryNode::create(PNK_FOR, tc); if (!pn2) return NULL; pn2->setOp(JSOP_ITER); pn2->pn_iflags = JSITER_ENUMERATE; if (tokenStream.matchToken(TOK_NAME)) { if (tokenStream.currentToken().name() == context->runtime->atomState.eachAtom) pn2->pn_iflags |= JSITER_FOREACH; else tokenStream.ungetToken(); } MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); GenexpGuard guard(tc); PropertyName *name = 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: name = tokenStream.currentToken().name(); /* * 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(name, tc); if (!pn3) return NULL; break; default: reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NO_VARIABLE_NAME); case TOK_ERROR: return NULL; } bool forOf; if (!matchInOrOf(&forOf)) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_IN_AFTER_FOR_NAME); return NULL; } if (forOf) { if (pn2->pn_iflags != JSITER_ENUMERATE) { JS_ASSERT(pn2->pn_iflags == (JSITER_FOREACH | JSITER_ENUMERATE)); reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP); return NULL; } pn2->pn_iflags = JSITER_FOR_OF; } ParseNode *pn4 = expr(); if (!pn4) return NULL; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); guard.endBody(); if (isGenexp) { if (!guard.checkValidBody(pn2)) return NULL; } else { if (!guard.maybeNoteGenerator(pn2)) return NULL; } switch (tt) { #if JS_HAS_DESTRUCTURING case TOK_LB: case TOK_LC: if (!CheckDestructuring(context, &data, pn3, tc)) return NULL; if (versionNumber() == JSVERSION_1_7) { /* Destructuring requires [key, value] enumeration in JS1.7. */ if (!pn3->isKind(PNK_RB) || pn3->pn_count != 2) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_FOR_LEFTSIDE); return NULL; } JS_ASSERT(pn2->isOp(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, name, tc)) return NULL; break; default:; } /* * Synthesize a declaration. Every definition must appear in the parse * tree in order for ComprehensionTranslator to work. */ ParseNode *vars = ListNode::create(PNK_VAR, tc); if (!vars) return NULL; vars->setOp(JSOP_NOP); vars->pn_pos = pn3->pn_pos; vars->makeEmpty(); vars->append(pn3); vars->pn_xflags |= PNX_FORINVAR; /* Definitions can't be passed directly to EmitAssignment as lhs. */ pn3 = CloneLeftHandSide(pn3, tc); if (!pn3) return NULL; pn2->pn_left = new_(PNK_FORIN, JSOP_NOP, vars, pn3, pn4); 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(PNK_IF, tc); if (!pn2) return NULL; pn2->pn_kid1 = condition(); if (!pn2->pn_kid1) return NULL; *pnp = pn2; pnp = &pn2->pn_kid2; } pn2 = UnaryNode::create(kind, tc); if (!pn2) return NULL; pn2->setOp(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. */ ParseNode * Parser::generatorExpr(ParseNode *kid) { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); /* Create a |yield| node for |kid|. */ ParseNode *pn = UnaryNode::create(PNK_YIELD, tc); if (!pn) return NULL; pn->setOp(JSOP_YIELD); pn->setInParens(true); pn->pn_pos = kid->pn_pos; pn->pn_kid = kid; pn->pn_hidden = true; /* Make a new node for the desugared generator function. */ ParseNode *genfn = FunctionNode::create(PNK_FUNCTION, tc); if (!genfn) return NULL; genfn->setOp(JSOP_LAMBDA); JS_ASSERT(!genfn->pn_body); genfn->pn_dflags = PND_FUNARG; { TreeContext *outertc = tc; TreeContext gentc(tc->parser); if (!gentc.init(context)) return NULL; FunctionBox *funbox = EnterFunction(genfn, &gentc); if (!funbox) 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 | (outertc->flags & (TCF_FUN_FLAGS & ~TCF_FUN_PARAM_ARGUMENTS)); funbox->tcflags |= gentc.flags; genfn->pn_funbox = funbox; genfn->pn_blockid = gentc.bodyid; ParseNode *body = comprehensionTail(pn, outertc->blockid(), true); 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. */ ParseNode *result = ListNode::create(PNK_LP, tc); if (!result) return NULL; result->setOp(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(ParseNode *listNode) { if (tokenStream.matchToken(TOK_RP, TSF_OPERAND)) return JS_TRUE; GenexpGuard guard(tc); bool arg0 = true; do { ParseNode *argNode = assignExpr(); if (!argNode) return JS_FALSE; if (arg0) guard.endBody(); #if JS_HAS_GENERATORS if (argNode->isKind(PNK_YIELD) && !argNode->isInParens() && 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)) { if (!guard.checkValidBody(argNode)) return JS_FALSE; 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; } } else #endif if (arg0 && !guard.maybeNoteGenerator(argNode)) return JS_FALSE; arg0 = false; 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 ParseNode * CheckForImmediatelyAppliedLambda(ParseNode *pn) { if (pn->isKind(PNK_FUNCTION)) { JS_ASSERT(pn->isArity(PN_FUNC)); FunctionBox *funbox = pn->pn_funbox; JS_ASSERT((funbox->function())->flags & JSFUN_LAMBDA); if (!(funbox->tcflags & (TCF_FUN_USES_ARGUMENTS | TCF_FUN_USES_OWN_NAME))) pn->pn_dflags &= ~PND_FUNARG; } return pn; } ParseNode * Parser::memberExpr(JSBool allowCallSyntax) { ParseNode *lhs; JS_CHECK_RECURSION(context, return NULL); /* Check for new expression first. */ TokenKind tt = tokenStream.getToken(TSF_OPERAND); if (tt == TOK_NEW) { lhs = ListNode::create(PNK_NEW, tc); if (!lhs) return NULL; ParseNode *ctorExpr = memberExpr(JS_FALSE); if (!ctorExpr) return NULL; ctorExpr = CheckForImmediatelyAppliedLambda(ctorExpr); lhs->setOp(JSOP_NEW); lhs->initList(ctorExpr); lhs->pn_pos.begin = ctorExpr->pn_pos.begin; if (tokenStream.matchToken(TOK_LP) && !argumentList(lhs)) return NULL; if (lhs->pn_count > ARGC_LIMIT) { JS_ReportErrorNumber(context, js_GetErrorMessage, NULL, JSMSG_TOO_MANY_CON_ARGS); return NULL; } lhs->pn_pos.end = lhs->last()->pn_pos.end; } else { lhs = primaryExpr(tt, JS_FALSE); if (!lhs) return NULL; if (lhs->isXMLNameOp()) { lhs = new_(PNK_XMLUNARY, JSOP_XMLNAME, lhs->pn_pos, lhs); if (!lhs) return NULL; } } while ((tt = tokenStream.getToken()) > TOK_EOF) { ParseNode *nextMember; if (tt == TOK_DOT) { tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); if (tt == TOK_ERROR) return NULL; if (tt == TOK_NAME) { #if JS_HAS_XML_SUPPORT if (!tc->inStrictMode() && tokenStream.peekToken() == TOK_DBLCOLON) { ParseNode *propertyId = propertyQualifiedIdentifier(); if (!propertyId) return NULL; nextMember = new_(lhs, propertyId, lhs->pn_pos.begin, tokenStream.currentToken().pos.end); if (!nextMember) return NULL; } else #endif { PropertyName *field = tokenStream.currentToken().name(); nextMember = new_(lhs, field, lhs->pn_pos.begin, tokenStream.currentToken().pos.end); if (!nextMember) return NULL; } } #if JS_HAS_XML_SUPPORT else if (!tc->inStrictMode()) { TokenPtr begin = lhs->pn_pos.begin; if (tt == TOK_LP) { /* Filters are effectively 'with', so deoptimize names. */ tc->flags |= TCF_FUN_HEAVYWEIGHT; StmtInfo stmtInfo; ParseNode *oldWith = tc->innermostWith; tc->innermostWith = lhs; PushStatement(tc, &stmtInfo, STMT_WITH, -1); ParseNode *filter = bracketedExpr(); if (!filter) return NULL; filter->setInParens(true); MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); tc->innermostWith = oldWith; PopStatement(tc); nextMember = new_(lhs, filter, begin, tokenStream.currentToken().pos.end); if (!nextMember) return NULL; } else if (tt == TOK_AT || tt == TOK_STAR) { ParseNode *propertyId = starOrAtPropertyIdentifier(tt); if (!propertyId) return NULL; nextMember = new_(lhs, propertyId, begin, tokenStream.currentToken().pos.end); if (!nextMember) return NULL; } else { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NAME_AFTER_DOT); return NULL; } } #endif else { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NAME_AFTER_DOT); return NULL; } } #if JS_HAS_XML_SUPPORT else if (tt == TOK_DBLDOT) { if (tc->inStrictMode()) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NAME_AFTER_DOT); return NULL; } nextMember = BinaryNode::create(PNK_DBLDOT, tc); if (!nextMember) return NULL; tt = tokenStream.getToken(TSF_OPERAND | TSF_KEYWORD_IS_NAME); ParseNode *pn3 = primaryExpr(tt, JS_TRUE); if (!pn3) return NULL; if (pn3->isKind(PNK_NAME) && !pn3->isInParens()) { pn3->setKind(PNK_STRING); pn3->setArity(PN_NULLARY); pn3->setOp(JSOP_QNAMEPART); } else if (!pn3->isXMLPropertyIdentifier()) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NAME_AFTER_DOT); return NULL; } nextMember->setOp(JSOP_DESCENDANTS); nextMember->pn_left = lhs; nextMember->pn_right = pn3; nextMember->pn_pos.begin = lhs->pn_pos.begin; nextMember->pn_pos.end = tokenStream.currentToken().pos.end; } #endif else if (tt == TOK_LB) { ParseNode *propExpr = expr(); if (!propExpr) return NULL; MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); TokenPtr begin = lhs->pn_pos.begin, end = tokenStream.currentToken().pos.end; /* * Optimize property name lookups. If the name is a PropertyName, * then make a name-based node so the emitter will use a name-based * bytecode. Otherwise make a node using the property expression * by value. If the node is a string containing an index, convert * it to a number to save work later. */ uint32_t index; PropertyName *name = NULL; if (propExpr->isKind(PNK_STRING)) { JSAtom *atom = propExpr->pn_atom; if (atom->isIndex(&index)) { propExpr->setKind(PNK_NUMBER); propExpr->setOp(JSOP_DOUBLE); propExpr->pn_dval = index; } else { name = atom->asPropertyName(); } } else if (propExpr->isKind(PNK_NUMBER)) { JSAtom *atom; if (!js_ValueToAtom(context, NumberValue(propExpr->pn_dval), &atom)) return NULL; if (!atom->isIndex(&index)) name = atom->asPropertyName(); } if (name) nextMember = new_(lhs, name, begin, end); else nextMember = new_(lhs, propExpr, begin, end); if (!nextMember) return NULL; } else if (allowCallSyntax && tt == TOK_LP) { nextMember = ListNode::create(PNK_LP, tc); if (!nextMember) return NULL; nextMember->setOp(JSOP_CALL); lhs = CheckForImmediatelyAppliedLambda(lhs); if (lhs->isOp(JSOP_NAME)) { if (lhs->pn_atom == context->runtime->atomState.evalAtom) { /* Select JSOP_EVAL and flag tc as heavyweight. */ nextMember->setOp(JSOP_EVAL); tc->noteCallsEval(); tc->flags |= TCF_FUN_HEAVYWEIGHT; /* * In non-strict mode code, direct calls to eval can add * variables to the call object. */ if (!tc->inStrictMode()) tc->noteHasExtensibleScope(); } } else if (lhs->isOp(JSOP_GETPROP)) { /* Select JSOP_FUNAPPLY given foo.apply(...). */ if (lhs->pn_atom == context->runtime->atomState.applyAtom) nextMember->setOp(JSOP_FUNAPPLY); else if (lhs->pn_atom == context->runtime->atomState.callAtom) nextMember->setOp(JSOP_FUNCALL); } nextMember->initList(lhs); nextMember->pn_pos.begin = lhs->pn_pos.begin; if (!argumentList(nextMember)) return NULL; if (nextMember->pn_count > ARGC_LIMIT) { JS_ReportErrorNumber(context, js_GetErrorMessage, NULL, JSMSG_TOO_MANY_FUN_ARGS); return NULL; } nextMember->pn_pos.end = tokenStream.currentToken().pos.end; } else { tokenStream.ungetToken(); return lhs; } lhs = nextMember; } if (tt == TOK_ERROR) return NULL; return lhs; } ParseNode * Parser::bracketedExpr() { unsigned oldflags; ParseNode *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 ParseNode * Parser::endBracketedExpr() { JS_ASSERT(!tc->inStrictMode()); ParseNode *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. */ ParseNode * Parser::propertySelector() { JS_ASSERT(!tc->inStrictMode()); ParseNode *selector; if (tokenStream.isCurrentTokenType(TOK_STAR)) { selector = NullaryNode::create(PNK_ANYNAME, tc); if (!selector) return NULL; selector->setOp(JSOP_ANYNAME); selector->pn_atom = context->runtime->atomState.starAtom; } else { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME)); selector = NullaryNode::create(PNK_NAME, tc); if (!selector) return NULL; selector->setOp(JSOP_QNAMEPART); selector->setArity(PN_NAME); selector->pn_atom = tokenStream.currentToken().name(); selector->pn_cookie.makeFree(); } return selector; } ParseNode * Parser::qualifiedSuffix(ParseNode *pn) { JS_ASSERT(!tc->inStrictMode()); JS_ASSERT(tokenStream.currentToken().type == TOK_DBLCOLON); ParseNode *pn2 = NameNode::create(PNK_DBLCOLON, NULL, tc); if (!pn2) return NULL; /* This qualifiedSuffice may refer to 'arguments'. */ tc->flags |= (TCF_FUN_HEAVYWEIGHT | TCF_FUN_LOCAL_ARGUMENTS); /* Left operand of :: must be evaluated if it is an identifier. */ if (pn->isOp(JSOP_QNAMEPART)) pn->setOp(JSOP_NAME); TokenKind tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); if (tt == TOK_STAR || tt == TOK_NAME) { /* Inline and specialize propertySelector for JSOP_QNAMECONST. */ pn2->setOp(JSOP_QNAMECONST); pn2->pn_pos.begin = pn->pn_pos.begin; pn2->pn_atom = (tt == TOK_STAR) ? context->runtime->atomState.starAtom : tokenStream.currentToken().name(); pn2->pn_expr = pn; pn2->pn_cookie.makeFree(); return pn2; } if (tt != TOK_LB) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); return NULL; } ParseNode *pn3 = endBracketedExpr(); if (!pn3) return NULL; pn2->setOp(JSOP_QNAME); pn2->setArity(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; } ParseNode * Parser::qualifiedIdentifier() { JS_ASSERT(!tc->inStrictMode()); ParseNode *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 | TCF_FUN_LOCAL_ARGUMENTS); pn = qualifiedSuffix(pn); } return pn; } ParseNode * Parser::attributeIdentifier() { JS_ASSERT(!tc->inStrictMode()); JS_ASSERT(tokenStream.currentToken().type == TOK_AT); ParseNode *pn = UnaryNode::create(PNK_AT, tc); if (!pn) return NULL; pn->setOp(JSOP_TOATTRNAME); ParseNode *pn2; TokenKind 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. */ ParseNode * Parser::xmlExpr(JSBool inTag) { JS_ASSERT(!tc->inStrictMode()); JS_ASSERT(tokenStream.currentToken().type == TOK_LC); ParseNode *pn = UnaryNode::create(PNK_XMLCURLYEXPR, 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); ParseNode *pn2 = expr(); if (!pn2) return NULL; MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_XML_EXPR); tokenStream.setXMLTagMode(oldflag); pn->pn_kid = pn2; pn->setOp(inTag ? JSOP_XMLTAGEXPR : JSOP_XMLELTEXPR); return pn; } ParseNode * Parser::atomNode(ParseNodeKind kind, JSOp op) { ParseNode *node = NullaryNode::create(kind, tc); if (!node) return NULL; node->setOp(op); const Token &tok = tokenStream.currentToken(); node->pn_atom = tok.atom(); return node; } /* * 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, getKind() will be PNK_XMLNAME. Otherwise if * PN_UNARY, getKind() will be PNK_XMLCURLYEXPR. */ ParseNode * Parser::xmlNameExpr() { JS_ASSERT(!tc->inStrictMode()); ParseNode *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); JS_ASSERT(tokenStream.currentToken().t_op == JSOP_STRING); pn2 = atomNode(PNK_XMLNAME, JSOP_STRING); if (!pn2) return NULL; } if (!pn) { pn = pn2; } else { if (!list) { list = ListNode::create(PNK_XMLNAME, tc); if (!list) return NULL; 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)->isArity(PN_LIST) \ ? ((pn)->pn_xflags & PNX_CANTFOLD) == 0 \ : !(pn)->isKind(PNK_XMLCURLYEXPR)) /* * 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, getKind() will be PNK_XMLNAME for the case where * XMLTagContent: XMLNameExpr. If getKind() is not PNK_XMLNAME but getArity() * is PN_LIST, getKind() will be tagkind. If PN_UNARY, getKind() will be * PNK_XMLCURLYEXPR and we parsed exactly one expression. */ ParseNode * Parser::xmlTagContent(ParseNodeKind tagkind, JSAtom **namep) { JS_ASSERT(!tc->inStrictMode()); ParseNode *pn, *pn2, *list; TokenKind tt; pn = xmlNameExpr(); if (!pn) return NULL; *namep = (pn->isArity(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(tagkind, tc); if (!list) return NULL; 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) { JS_ASSERT(tokenStream.currentToken().t_op == JSOP_STRING); pn2 = atomNode(PNK_XMLATTR, JSOP_STRING); } 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 (inStrictMode()); 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().atom(); if (textAtom) { /* Non-zero-length XML text scanned. */ JS_ASSERT(tokenStream.currentToken().t_op == JSOP_STRING); ParseNode *pn2 = atomNode(tt == TOK_XMLSPACE ? PNK_XMLSPACE : PNK_XMLTEXT, JSOP_STRING); if (!pn2) return 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; ParseNode *pn2; if (tt == TOK_LC) { pn2 = xmlExpr(JS_FALSE); if (!pn2) return false; pn->pn_xflags |= PNX_CANTFOLD; } else if (tt == TOK_XMLSTAGO) { pn2 = xmlElementOrList(JS_FALSE); if (!pn2) return false; pn2->pn_xflags &= ~PNX_XMLROOT; pn->pn_xflags |= pn2->pn_xflags; } else if (tt == TOK_XMLPI) { const Token &tok = tokenStream.currentToken(); pn2 = new_(tok.xmlPITarget(), tok.xmlPIData(), tok.pos); if (!pn2) return false; } else { JS_ASSERT(tt == TOK_XMLCDATA || tt == TOK_XMLCOMMENT); pn2 = atomNode(tt == TOK_XMLCDATA ? PNK_XMLCDATA : PNK_XMLCOMMENT, tokenStream.currentToken().t_op); if (!pn2) return 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. */ ParseNode * Parser::xmlElementOrList(JSBool allowList) { JS_ASSERT(!tc->inStrictMode()); ParseNode *pn, *pn2, *list; TokenKind tt; JSAtom *startAtom, *endAtom; JS_CHECK_RECURSION(context, return NULL); JS_ASSERT(tokenStream.currentToken().type == TOK_XMLSTAGO); pn = ListNode::create(PNK_XMLSTAGO, 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(PNK_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->isKind(PNK_XMLSTAGO)) { pn->makeEmpty(); freeTree(pn); pn = pn2; } else { JS_ASSERT(pn2->isKind(PNK_XMLNAME) || pn2->isKind(PNK_XMLCURLYEXPR)); pn->initList(pn2); if (!XML_FOLDABLE(pn2)) pn->pn_xflags |= PNX_CANTFOLD; } pn->setKind(PNK_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->isKind(PNK_XMLSTAGO)) { pn->initList(pn2); if (!XML_FOLDABLE(pn2)) pn->pn_xflags |= PNX_CANTFOLD; pn2 = pn; pn = ListNode::create(PNK_XMLTAGC, tc); if (!pn) return NULL; } /* Now make pn a nominal-root TOK_XMLELEM list containing pn2. */ pn->setKind(PNK_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(PNK_XMLETAGO, &endAtom); if (!pn2) return NULL; if (pn2->isKind(PNK_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->isKind(PNK_XMLNAME) || pn2->isKind(PNK_XMLCURLYEXPR)); list = ListNode::create(PNK_XMLETAGO, tc); if (!list) return NULL; 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->setOp(JSOP_TOXML); } else if (allowList && tt == TOK_XMLTAGC) { /* XMLList Initialiser. */ pn->setKind(PNK_XMLLIST); pn->setOp(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; } ParseNode * Parser::xmlElementOrListRoot(JSBool allowList) { JS_ASSERT(!tc->inStrictMode()); /* * Force XML support to be enabled so that comments and CDATA literals * are recognized, instead of ). */ bool hadXML = tokenStream.hasXML(); tokenStream.setXML(true); ParseNode *pn = xmlElementOrList(allowList); tokenStream.setXML(hadXML); return pn; } ParseNode * 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. */ TreeContext xmltc(this); if (!xmltc.init(context)) return NULL; JS_ASSERT(!xmltc.inStrictMode()); xmltc.setScopeChain(chain); /* Set XML-only mode to turn off special treatment of {expr} in XML. */ tokenStream.setXMLOnlyMode(); TokenKind tt = tokenStream.getToken(TSF_OPERAND); ParseNode *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 */ bool Parser::checkForFunctionNode(PropertyName *name, ParseNode *node) { /* * In |a.ns::name|, |ns| refers to an in-scope variable, so |ns| can't be a * keyword. (Exception: |function::name| is the actual name property, not * what E4X would expose.) We parsed |ns| accepting a keyword as a name, * so we must implement the keyword restriction manually in this case. */ if (const KeywordInfo *ki = FindKeyword(name->charsZ(), name->length())) { if (ki->tokentype != TOK_FUNCTION) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_KEYWORD_NOT_NS); return false; } node->setArity(PN_NULLARY); node->setKind(PNK_FUNCTION); } return true; } #if JS_HAS_XML_SUPPORT ParseNode * Parser::propertyQualifiedIdentifier() { JS_ASSERT(!tc->inStrictMode()); JS_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME)); JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NAME); JS_ASSERT(tokenStream.peekToken() == TOK_DBLCOLON); /* Deoptimize QualifiedIdentifier properties to avoid tricky analysis. */ tc->flags |= (TCF_FUN_HEAVYWEIGHT | TCF_FUN_LOCAL_ARGUMENTS); PropertyName *name = tokenStream.currentToken().name(); ParseNode *node = NameNode::create(PNK_NAME, name, tc); if (!node) return NULL; node->setOp(JSOP_NAME); node->pn_dflags |= PND_DEOPTIMIZED; if (!checkForFunctionNode(name, node)) return NULL; tokenStream.consumeKnownToken(TOK_DBLCOLON); return qualifiedSuffix(node); } #endif ParseNode * Parser::identifierName(bool afterDoubleDot) { JS_ASSERT(tokenStream.isCurrentTokenType(TOK_NAME)); PropertyName *name = tokenStream.currentToken().name(); ParseNode *node = NameNode::create(PNK_NAME, name, tc); if (!node) return NULL; JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NAME); node->setOp(JSOP_NAME); if ((tc->flags & (TCF_IN_FUNCTION | TCF_FUN_PARAM_ARGUMENTS)) == TCF_IN_FUNCTION && name == context->runtime->atomState.argumentsAtom) { /* * Bind early to JSOP_ARGUMENTS to relieve later code from having * to do this work (new rule for the emitter to count on). */ if (!afterDoubleDot) { /* * Note use of |arguments| to ensure we can properly create the * |arguments| object for this function. */ tc->noteArgumentsNameUse(node); if (!(tc->flags & TCF_DECL_DESTRUCTURING) && !tc->inStatement(STMT_WITH)) { node->setOp(JSOP_ARGUMENTS); node->pn_dflags |= PND_BOUND; } } } else if ((!afterDoubleDot #if JS_HAS_XML_SUPPORT || (!tc->inStrictMode() && tokenStream.peekToken() == TOK_DBLCOLON) #endif ) && !(tc->flags & TCF_DECL_DESTRUCTURING)) { /* In case this is a generator expression outside of any function. */ if (!tc->inFunction() && name == context->runtime->atomState.argumentsAtom) tc->countArgumentsUse(node); StmtInfo *stmt = LexicalLookup(tc, name, NULL); MultiDeclRange mdl = tc->decls.lookupMulti(name); Definition *dn; if (!mdl.empty()) { dn = mdl.front(); } else { if (AtomDefnAddPtr p = tc->lexdeps->lookupForAdd(name)) { dn = p.value(); } else { /* * No definition before this use in any lexical scope. * Create a placeholder definition node to either: * - Be adopted when we parse the real defining * declaration, or * - Be left as a free variable definition if we never * see the real definition. */ dn = MakePlaceholder(node, tc); if (!dn || !tc->lexdeps->add(p, name, dn)) return NULL; /* * 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->isDefn()); LinkUseToDef(node, dn, tc); /* Here we handle the backward function reference case. */ if (tokenStream.peekToken() != TOK_LP) dn->pn_dflags |= PND_FUNARG; node->pn_dflags |= (dn->pn_dflags & PND_FUNARG); if (stmt && stmt->type == STMT_WITH) node->pn_dflags |= PND_DEOPTIMIZED; } #if JS_HAS_XML_SUPPORT if (!tc->inStrictMode() && tokenStream.matchToken(TOK_DBLCOLON)) { if (afterDoubleDot) { if (!checkForFunctionNode(name, node)) return NULL; } node = qualifiedSuffix(node); if (!node) return NULL; } #endif return node; } #if JS_HAS_XML_SUPPORT ParseNode * Parser::starOrAtPropertyIdentifier(TokenKind tt) { JS_ASSERT(tt == TOK_AT || tt == TOK_STAR); if (tc->inStrictMode()) { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); return NULL; } return (tt == TOK_AT) ? attributeIdentifier() : qualifiedIdentifier(); } #endif ParseNode * Parser::primaryExpr(TokenKind tt, bool afterDoubleDot) { JS_ASSERT(tokenStream.isCurrentTokenType(tt)); ParseNode *pn, *pn2, *pn3; JSOp op; JS_CHECK_RECURSION(context, return NULL); switch (tt) { case TOK_FUNCTION: #if JS_HAS_XML_SUPPORT if (!tc->inStrictMode() && tokenStream.matchToken(TOK_DBLCOLON, TSF_KEYWORD_IS_NAME)) { pn2 = NullaryNode::create(PNK_FUNCTION, tc); if (!pn2) return NULL; pn = qualifiedSuffix(pn2); if (!pn) return NULL; break; } #endif pn = functionExpr(); if (!pn) return NULL; break; case TOK_LB: { JSBool matched; unsigned index; pn = ListNode::create(PNK_RB, tc); if (!pn) return NULL; pn->setOp(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 == StackSpace::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(PNK_COMMA, 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 ... JSOP_LEAVEBLOCK * * where is a literal object representing the block scope, * with properties, naming each var declared in the block. * * Each var declaration in a let-block binds a name in 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. These ops 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 ; JSOP_ARRAYCOMP , * where is the index of array's stack slot. */ if (index == 0 && pn->pn_count != 0 && tokenStream.matchToken(TOK_FOR)) { ParseNode *pnexp, *pntop; /* Relabel pn as an array comprehension node. */ pn->setKind(PNK_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, false, PNK_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: { ParseNode *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. */ AtomIndexMap seen(context); enum AssignmentType { GET = 0x1, SET = 0x2, VALUE = 0x4 | GET | SET }; pn = ListNode::create(PNK_RC, tc); if (!pn) return NULL; pn->setOp(JSOP_NEWINIT); pn->makeEmpty(); for (;;) { JSAtom *atom; TokenKind ltok = tokenStream.getToken(TSF_KEYWORD_IS_NAME); TokenPtr begin = tokenStream.currentToken().pos.begin; switch (ltok) { case TOK_NUMBER: pn3 = NullaryNode::create(PNK_NUMBER, tc); if (!pn3) return NULL; pn3->pn_dval = tokenStream.currentToken().number(); if (!js_ValueToAtom(context, DoubleValue(pn3->pn_dval), &atom)) return NULL; break; case TOK_NAME: { atom = tokenStream.currentToken().name(); if (atom == context->runtime->atomState.getAtom) { op = JSOP_GETTER; } else if (atom == context->runtime->atomState.setAtom) { op = JSOP_SETTER; } else { pn3 = NullaryNode::create(PNK_NAME, tc); if (!pn3) return NULL; pn3->pn_atom = atom; break; } tt = tokenStream.getToken(TSF_KEYWORD_IS_NAME); if (tt == TOK_NAME) { atom = tokenStream.currentToken().name(); pn3 = NameNode::create(PNK_NAME, atom, tc); if (!pn3) return NULL; } else if (tt == TOK_STRING) { atom = tokenStream.currentToken().atom(); uint32_t index; if (atom->isIndex(&index)) { pn3 = NullaryNode::create(PNK_NUMBER, tc); if (!pn3) return NULL; pn3->pn_dval = index; if (!js_ValueToAtom(context, DoubleValue(pn3->pn_dval), &atom)) return NULL; } else { pn3 = NameNode::create(PNK_STRING, atom, tc); if (!pn3) return NULL; } } else if (tt == TOK_NUMBER) { pn3 = NullaryNode::create(PNK_NUMBER, tc); if (!pn3) return NULL; pn3->pn_dval = tokenStream.currentToken().number(); if (!js_ValueToAtom(context, DoubleValue(pn3->pn_dval), &atom)) return NULL; } else { tokenStream.ungetToken(); pn3 = NullaryNode::create(PNK_NAME, tc); if (!pn3) return NULL; pn3->pn_atom = atom; break; } pn->pn_xflags |= PNX_NONCONST; /* NB: Getter function in { get x(){} } is unnamed. */ pn2 = functionDef(NULL, op == JSOP_GETTER ? Getter : Setter, Expression); if (!pn2) return NULL; TokenPos pos = {begin, pn2->pn_pos.end}; pn2 = new_(PNK_COLON, op, pos, pn3, pn2); goto skip; } case TOK_STRING: { atom = tokenStream.currentToken().atom(); uint32_t index; if (atom->isIndex(&index)) { pn3 = NullaryNode::create(PNK_NUMBER, tc); if (!pn3) return NULL; pn3->pn_dval = index; } else { pn3 = NullaryNode::create(PNK_STRING, 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) return NULL; /* * Treat initializers which mutate __proto__ as non-constant, * so that we can later assume singleton objects delegate to * the default Object.prototype. */ if (!pnval->isConstant() || atom == context->runtime->atomState.protoAtom) pn->pn_xflags |= PNX_NONCONST; } #if JS_HAS_DESTRUCTURING_SHORTHAND else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) { /* * 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(); if (!tokenStream.checkForKeyword(atom->charsZ(), atom->length(), NULL, NULL)) return NULL; pn->pn_xflags |= PNX_DESTRUCT | PNX_NONCONST; pnval = pn3; JS_ASSERT(pnval->isKind(PNK_NAME)); pnval->setArity(PN_NAME); ((NameNode *)pnval)->initCommon(tc); } #endif else { reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_COLON_AFTER_ID); return NULL; } { TokenPos pos = {begin, pnval->pn_pos.end}; pn2 = new_(PNK_COLON, op, pos, pn3, pnval); } 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 */ } AtomIndexAddPtr p = seen.lookupForAdd(atom); if (p) { jsatomid index = p.value(); AssignmentType oldAssignType = AssignmentType(index); if ((oldAssignType & assignType) && (oldAssignType != VALUE || assignType != VALUE || tc->needStrictChecks())) { JSAutoByteString name; if (!js_AtomToPrintableString(context, atom, &name)) return NULL; unsigned flags = (oldAssignType == VALUE && assignType == VALUE && !tc->inStrictMode()) ? JSREPORT_WARNING : JSREPORT_ERROR; if (!ReportCompileErrorNumber(context, &tokenStream, NULL, flags, JSMSG_DUPLICATE_PROPERTY, name.ptr())) { return NULL; } } p.value() = assignType | oldAssignType; } else { if (!seen.add(p, atom, assignType)) return NULL; } 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; } } end_obj_init: pn->pn_pos.end = tokenStream.currentToken().pos.end; return pn; } #if JS_HAS_BLOCK_SCOPE case TOK_LET: pn = letBlock(LetExpresion); if (!pn) return NULL; break; #endif case TOK_LP: { JSBool genexp; pn = parenExpr(&genexp); if (!pn) return NULL; pn->setInParens(true); if (!genexp) MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); break; } case TOK_STRING: pn = atomNode(PNK_STRING, JSOP_STRING); if (!pn) return NULL; break; #if JS_HAS_XML_SUPPORT case TOK_AT: case TOK_STAR: pn = starOrAtPropertyIdentifier(tt); break; case TOK_XMLSTAGO: pn = xmlElementOrListRoot(JS_TRUE); if (!pn) return NULL; break; case TOK_XMLCDATA: JS_ASSERT(!tc->inStrictMode()); pn = atomNode(PNK_XMLCDATA, JSOP_XMLCDATA); if (!pn) return NULL; break; case TOK_XMLCOMMENT: JS_ASSERT(!tc->inStrictMode()); pn = atomNode(PNK_XMLCOMMENT, JSOP_XMLCOMMENT); if (!pn) return NULL; break; case TOK_XMLPI: { JS_ASSERT(!tc->inStrictMode()); const Token &tok = tokenStream.currentToken(); pn = new_(tok.xmlPITarget(), tok.xmlPIData(), tok.pos); if (!pn) return NULL; break; } #endif case TOK_NAME: pn = identifierName(afterDoubleDot); break; case TOK_REGEXP: { pn = NullaryNode::create(PNK_REGEXP, tc); if (!pn) return NULL; const jschar *chars = tokenStream.getTokenbuf().begin(); size_t length = tokenStream.getTokenbuf().length(); RegExpFlag flags = tokenStream.currentToken().regExpFlags(); RegExpStatics *res = context->regExpStatics(); RegExpObject *reobj; if (context->hasfp()) reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream); else reobj = RegExpObject::createNoStatics(context, chars, length, flags, &tokenStream); if (!reobj) return NULL; if (!tc->compileAndGo()) { if (!reobj->clearParent(context)) return NULL; if (!reobj->clearType(context)) return NULL; } pn->pn_objbox = tc->parser->newObjectBox(reobj); if (!pn->pn_objbox) return NULL; pn->setOp(JSOP_REGEXP); break; } case TOK_NUMBER: pn = NullaryNode::create(PNK_NUMBER, tc); if (!pn) return NULL; pn->setOp(JSOP_DOUBLE); pn->pn_dval = tokenStream.currentToken().number(); break; case TOK_TRUE: return new_(true, tokenStream.currentToken().pos); case TOK_FALSE: return new_(false, tokenStream.currentToken().pos); case TOK_THIS: return new_(tokenStream.currentToken().pos); case TOK_NULL: return new_(tokenStream.currentToken().pos); 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; } ParseNode * Parser::parenExpr(JSBool *genexp) { TokenPtr begin; ParseNode *pn; JS_ASSERT(tokenStream.currentToken().type == TOK_LP); begin = tokenStream.currentToken().pos.begin; if (genexp) *genexp = JS_FALSE; GenexpGuard guard(tc); pn = bracketedExpr(); if (!pn) return NULL; guard.endBody(); #if JS_HAS_GENERATOR_EXPRS if (tokenStream.matchToken(TOK_FOR)) { if (!guard.checkValidBody(pn)) return NULL; JS_ASSERT(!pn->isKind(PNK_YIELD)); if (pn->isKind(PNK_COMMA) && !pn->isInParens()) { 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; } } else #endif /* JS_HAS_GENERATOR_EXPRS */ if (!guard.maybeNoteGenerator(pn)) return NULL; return pn; }