diff -r 000000000000 -r 6474c204b198 js/src/frontend/Parser.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/frontend/Parser.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,7499 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * 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. + * + * This parser attempts no error recovery. + */ + +#include "frontend/Parser-inl.h" + +#include "jsapi.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsfun.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsscript.h" +#include "jstypes.h" + +#include "frontend/BytecodeCompiler.h" +#include "frontend/FoldConstants.h" +#include "frontend/ParseMaps.h" +#include "frontend/TokenStream.h" +#include "jit/AsmJS.h" +#include "vm/Shape.h" + +#include "jsatominlines.h" +#include "jsscriptinlines.h" + +#include "frontend/ParseNode-inl.h" + +using namespace js; +using namespace js::gc; +using mozilla::Maybe; + +namespace js { +namespace frontend { + +typedef Rooted RootedStaticBlockObject; +typedef Handle HandleStaticBlockObject; +typedef Rooted RootedNestedScopeObject; +typedef Handle HandleNestedScopeObject; + + +/* + * 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(tt, errno) \ + JS_BEGIN_MACRO \ + if (tokenStream.getToken() != tt) { \ + report(ParseError, false, null(), errno); \ + return null(); \ + } \ + JS_END_MACRO + +static const unsigned BlockIdLimit = 1 << ParseNode::NumBlockIdBits; + +template +bool +GenerateBlockId(TokenStream &ts, ParseContext *pc, uint32_t &blockid) +{ + if (pc->blockidGen == BlockIdLimit) { + ts.reportError(JSMSG_NEED_DIET, "program"); + return false; + } + JS_ASSERT(pc->blockidGen < BlockIdLimit); + blockid = pc->blockidGen++; + return true; +} + +template bool +GenerateBlockId(TokenStream &ts, ParseContext *pc, uint32_t &blockid); + +template bool +GenerateBlockId(TokenStream &ts, ParseContext *pc, uint32_t &blockid); + +template +static void +PushStatementPC(ParseContext *pc, StmtInfoPC *stmt, StmtType type) +{ + stmt->blockid = pc->blockid(); + PushStatement(pc, stmt, type); +} + +// See comment on member function declaration. +template <> +bool +ParseContext::define(TokenStream &ts, + HandlePropertyName name, ParseNode *pn, Definition::Kind kind) +{ + JS_ASSERT(!pn->isUsed()); + JS_ASSERT_IF(pn->isDefn(), pn->isPlaceholder()); + + Definition *prevDef = nullptr; + if (kind == Definition::LET) + prevDef = decls_.lookupFirst(name); + else + JS_ASSERT(!decls_.lookupFirst(name)); + + if (!prevDef) + prevDef = lexdeps.lookupDefn(name); + + if (prevDef) { + ParseNode **pnup = &prevDef->dn_uses; + ParseNode *pnu; + unsigned start = (kind == Definition::LET) ? pn->pn_blockid : bodyid; + + while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) { + JS_ASSERT(pnu->pn_blockid >= bodyid); + JS_ASSERT(pnu->isUsed()); + pnu->pn_lexdef = (Definition *) pn; + pn->pn_dflags |= pnu->pn_dflags & PND_USE2DEF_FLAGS; + pnup = &pnu->pn_link; + } + + if (!pnu || pnu != prevDef->dn_uses) { + *pnup = pn->dn_uses; + pn->dn_uses = prevDef->dn_uses; + prevDef->dn_uses = pnu; + + if (!pnu && prevDef->isPlaceholder()) + lexdeps->remove(name); + } + + pn->pn_dflags |= prevDef->pn_dflags & PND_CLOSED; + } + + JS_ASSERT_IF(kind != Definition::LET, !lexdeps->lookup(name)); + pn->setDefn(true); + pn->pn_dflags &= ~PND_PLACEHOLDER; + if (kind == Definition::CONST) + pn->pn_dflags |= PND_CONST; + + Definition *dn = (Definition *)pn; + switch (kind) { + case Definition::ARG: + JS_ASSERT(sc->isFunctionBox()); + dn->setOp(JSOP_GETARG); + dn->pn_dflags |= PND_BOUND; + if (!dn->pn_cookie.set(ts, staticLevel, args_.length())) + return false; + if (!args_.append(dn)) + return false; + if (args_.length() >= ARGNO_LIMIT) { + ts.reportError(JSMSG_TOO_MANY_FUN_ARGS); + return false; + } + if (name == ts.names().empty) + break; + if (!decls_.addUnique(name, dn)) + return false; + break; + + case Definition::CONST: + case Definition::VAR: + if (sc->isFunctionBox()) { + dn->setOp(JSOP_GETLOCAL); + dn->pn_dflags |= PND_BOUND; + if (!dn->pn_cookie.set(ts, staticLevel, vars_.length())) + return false; + if (!vars_.append(dn)) + return false; + if (vars_.length() >= LOCALNO_LIMIT) { + ts.reportError(JSMSG_TOO_MANY_LOCALS); + return false; + } + } + if (!decls_.addUnique(name, dn)) + return false; + break; + + case Definition::LET: + dn->setOp(JSOP_GETLOCAL); + dn->pn_dflags |= (PND_LET | PND_BOUND); + JS_ASSERT(dn->pn_cookie.level() == staticLevel); /* see bindLet */ + if (!decls_.addShadow(name, dn)) + return false; + break; + + default: + MOZ_ASSUME_UNREACHABLE("unexpected kind"); + } + + return true; +} + +template <> +bool +ParseContext::define(TokenStream &ts, HandlePropertyName name, Node pn, + Definition::Kind kind) +{ + JS_ASSERT(!decls_.lookupFirst(name)); + + if (lexdeps.lookupDefn(name)) + lexdeps->remove(name); + + // Keep track of the number of arguments in args_, for fun->nargs. + if (kind == Definition::ARG) { + if (!args_.append((Definition *) nullptr)) + return false; + if (args_.length() >= ARGNO_LIMIT) { + ts.reportError(JSMSG_TOO_MANY_FUN_ARGS); + return false; + } + } + + return decls_.addUnique(name, kind); +} + +template +void +ParseContext::prepareToAddDuplicateArg(HandlePropertyName name, DefinitionNode prevDecl) +{ + JS_ASSERT(decls_.lookupFirst(name) == prevDecl); + decls_.remove(name); +} + +template +void +ParseContext::updateDecl(JSAtom *atom, Node pn) +{ + Definition *oldDecl = decls_.lookupFirst(atom); + + pn->setDefn(true); + Definition *newDecl = (Definition *)pn; + decls_.updateFirst(atom, newDecl); + + if (!sc->isFunctionBox()) { + JS_ASSERT(newDecl->isFreeVar()); + return; + } + + JS_ASSERT(oldDecl->isBound()); + JS_ASSERT(!oldDecl->pn_cookie.isFree()); + newDecl->pn_cookie = oldDecl->pn_cookie; + newDecl->pn_dflags |= PND_BOUND; + if (IsArgOp(oldDecl->getOp())) { + newDecl->setOp(JSOP_GETARG); + JS_ASSERT(args_[oldDecl->pn_cookie.slot()] == oldDecl); + args_[oldDecl->pn_cookie.slot()] = newDecl; + } else { + JS_ASSERT(IsLocalOp(oldDecl->getOp())); + newDecl->setOp(JSOP_GETLOCAL); + JS_ASSERT(vars_[oldDecl->pn_cookie.slot()] == oldDecl); + vars_[oldDecl->pn_cookie.slot()] = newDecl; + } +} + +template +void +ParseContext::popLetDecl(JSAtom *atom) +{ + JS_ASSERT(ParseHandler::getDefinitionKind(decls_.lookupFirst(atom)) == Definition::LET); + decls_.remove(atom); +} + +template +static void +AppendPackedBindings(const ParseContext *pc, const DeclVector &vec, Binding *dst) +{ + for (size_t i = 0; i < vec.length(); ++i, ++dst) { + Definition *dn = vec[i]; + PropertyName *name = dn->name(); + + Binding::Kind kind; + switch (dn->kind()) { + case Definition::VAR: + kind = Binding::VARIABLE; + break; + case Definition::CONST: + kind = Binding::CONSTANT; + break; + case Definition::ARG: + kind = Binding::ARGUMENT; + break; + default: + MOZ_ASSUME_UNREACHABLE("unexpected dn->kind"); + } + + /* + * Bindings::init does not check for duplicates so we must ensure that + * only one binding with a given name is marked aliased. pc->decls + * maintains the canonical definition for each name, so use that. + */ + JS_ASSERT_IF(dn->isClosed(), pc->decls().lookupFirst(name) == dn); + bool aliased = dn->isClosed() || + (pc->sc->allLocalsAliased() && + pc->decls().lookupFirst(name) == dn); + + *dst = Binding(name, kind, aliased); + } +} + +template +bool +ParseContext::generateFunctionBindings(ExclusiveContext *cx, TokenStream &ts, + LifoAlloc &alloc, + InternalHandle bindings) const +{ + JS_ASSERT(sc->isFunctionBox()); + JS_ASSERT(args_.length() < ARGNO_LIMIT); + JS_ASSERT(vars_.length() < LOCALNO_LIMIT); + + /* + * Avoid pathological edge cases by explicitly limiting the total number of + * bindings to what will fit in a uint32_t. + */ + if (UINT32_MAX - args_.length() <= vars_.length()) + return ts.reportError(JSMSG_TOO_MANY_LOCALS); + + uint32_t count = args_.length() + vars_.length(); + Binding *packedBindings = alloc.newArrayUninitialized(count); + if (!packedBindings) { + js_ReportOutOfMemory(cx); + return false; + } + + AppendPackedBindings(this, args_, packedBindings); + AppendPackedBindings(this, vars_, packedBindings + args_.length()); + + return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(), + packedBindings, blockScopeDepth); +} + +template +bool +Parser::reportHelper(ParseReportKind kind, bool strict, uint32_t offset, + unsigned errorNumber, va_list args) +{ + bool result = false; + switch (kind) { + case ParseError: + result = tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_ERROR, errorNumber, args); + break; + case ParseWarning: + result = + tokenStream.reportCompileErrorNumberVA(offset, JSREPORT_WARNING, errorNumber, args); + break; + case ParseExtraWarning: + result = tokenStream.reportStrictWarningErrorNumberVA(offset, errorNumber, args); + break; + case ParseStrictError: + result = tokenStream.reportStrictModeErrorNumberVA(offset, strict, errorNumber, args); + break; + } + return result; +} + +template +bool +Parser::report(ParseReportKind kind, bool strict, Node pn, unsigned errorNumber, ...) +{ + uint32_t offset = (pn ? handler.getPosition(pn) : pos()).begin; + + va_list args; + va_start(args, errorNumber); + bool result = reportHelper(kind, strict, offset, errorNumber, args); + va_end(args); + return result; +} + +template +bool +Parser::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...) +{ + va_list args; + va_start(args, errorNumber); + bool result = reportHelper(kind, strict, TokenStream::NoOffset, errorNumber, args); + va_end(args); + return result; +} + +template +bool +Parser::reportWithOffset(ParseReportKind kind, bool strict, uint32_t offset, + unsigned errorNumber, ...) +{ + va_list args; + va_start(args, errorNumber); + bool result = reportHelper(kind, strict, offset, errorNumber, args); + va_end(args); + return result; +} + +template <> +bool +Parser::abortIfSyntaxParser() +{ + handler.disableSyntaxParser(); + return true; +} + +template <> +bool +Parser::abortIfSyntaxParser() +{ + abortedSyntaxParse = true; + return false; +} + +template +Parser::Parser(ExclusiveContext *cx, LifoAlloc *alloc, + const ReadOnlyCompileOptions &options, + const jschar *chars, size_t length, bool foldConstants, + Parser *syntaxParser, + LazyScript *lazyOuterFunction) + : AutoGCRooter(cx, PARSER), + context(cx), + alloc(*alloc), + tokenStream(cx, options, chars, length, thisForCtor()), + traceListHead(nullptr), + pc(nullptr), + sct(nullptr), + ss(nullptr), + keepAtoms(cx->perThreadData), + foldConstants(foldConstants), + abortedSyntaxParse(false), + isUnexpectedEOF_(false), + handler(cx, *alloc, tokenStream, foldConstants, syntaxParser, lazyOuterFunction) +{ + { + AutoLockForExclusiveAccess lock(cx); + cx->perThreadData->addActiveCompilation(); + } + + // The Mozilla specific JSOPTION_EXTRA_WARNINGS option adds extra warnings + // which are not generated if functions are parsed lazily. Note that the + // standard "use strict" does not inhibit lazy parsing. + if (options.extraWarningsOption) + handler.disableSyntaxParser(); + + tempPoolMark = alloc->mark(); +} + +template +Parser::~Parser() +{ + alloc.release(tempPoolMark); + + /* + * The parser can allocate enormous amounts of memory for large functions. + * Eagerly free the memory now (which otherwise won't be freed until the + * next GC) to avoid unnecessary OOMs. + */ + alloc.freeAllIfHugeAndUnused(); + + { + AutoLockForExclusiveAccess lock(context); + context->perThreadData->removeActiveCompilation(); + } +} + +template +ObjectBox * +Parser::newObjectBox(JSObject *obj) +{ + JS_ASSERT(obj && !IsPoisonedPtr(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 = alloc.new_(obj, traceListHead); + if (!objbox) { + js_ReportOutOfMemory(context); + return nullptr; + } + + traceListHead = objbox; + + return objbox; +} + +template +FunctionBox::FunctionBox(ExclusiveContext *cx, ObjectBox* traceListHead, JSFunction *fun, + ParseContext *outerpc, Directives directives, + bool extraWarnings, GeneratorKind generatorKind) + : ObjectBox(fun, traceListHead), + SharedContext(cx, directives, extraWarnings), + bindings(), + bufStart(0), + bufEnd(0), + length(0), + generatorKindBits_(GeneratorKindAsBits(generatorKind)), + inWith(false), // initialized below + inGenexpLambda(false), + hasDestructuringArgs(false), + useAsm(directives.asmJS()), + insideUseAsm(outerpc && outerpc->useAsmOrInsideUseAsm()), + usesArguments(false), + usesApply(false), + funCxFlags() +{ + // Functions created at parse time may be set singleton after parsing and + // baked into JIT code, so they must be allocated tenured. They are held by + // the JSScript so cannot be collected during a minor GC anyway. + JS_ASSERT(fun->isTenured()); + + if (!outerpc) { + inWith = false; + + } else if (outerpc->parsingWith) { + // This covers cases that don't involve eval(). For example: + // + // with (o) { (function() { g(); })(); } + // + // In this case, |outerpc| corresponds to global code, and + // outerpc->parsingWith is true. + inWith = true; + + } else if (outerpc->sc->isGlobalSharedContext()) { + // This covers the case where a function is nested within an eval() + // within a |with| statement. + // + // with (o) { eval("(function() { g(); })();"); } + // + // In this case, |outerpc| corresponds to the eval(), + // outerpc->parsingWith is false because the eval() breaks the + // ParseContext chain, and |parent| is nullptr (again because of the + // eval(), so we have to look at |outerpc|'s scopeChain. + // + JSObject *scope = outerpc->sc->asGlobalSharedContext()->scopeChain(); + while (scope) { + if (scope->is()) + inWith = true; + scope = scope->enclosingScope(); + } + } else if (outerpc->sc->isFunctionBox()) { + // This is like the above case, but for more deeply nested functions. + // For example: + // + // with (o) { eval("(function() { (function() { g(); })(); })();"); } } + // + // In this case, the inner anonymous function needs to inherit the + // setting of |inWith| from the outer one. + FunctionBox *parent = outerpc->sc->asFunctionBox(); + if (parent && parent->inWith) + inWith = true; + } +} + +template +FunctionBox * +Parser::newFunctionBox(Node fn, JSFunction *fun, ParseContext *outerpc, + Directives inheritedDirectives, GeneratorKind generatorKind) +{ + JS_ASSERT(fun && !IsPoisonedPtr(fun)); + + /* + * 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 = + alloc.new_(context, traceListHead, fun, outerpc, + inheritedDirectives, options().extraWarningsOption, + generatorKind); + if (!funbox) { + js_ReportOutOfMemory(context); + return nullptr; + } + + traceListHead = funbox; + if (fn) + handler.setFunctionBox(fn, funbox); + + return funbox; +} + +template +void +Parser::trace(JSTracer *trc) +{ + traceListHead->trace(trc); +} + +void +MarkParser(JSTracer *trc, AutoGCRooter *parser) +{ + static_cast *>(parser)->trace(trc); +} + +/* + * Parse a top-level JS script. + */ +template +typename ParseHandler::Node +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. + */ + Directives directives(options().strictOption); + GlobalSharedContext globalsc(context, chain, directives, options().extraWarningsOption); + ParseContext globalpc(this, /* parent = */ nullptr, ParseHandler::null(), + &globalsc, /* newDirectives = */ nullptr, + /* staticLevel = */ 0, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); + if (!globalpc.init(tokenStream)) + return null(); + + Node pn = statements(); + if (pn) { + if (!tokenStream.matchToken(TOK_EOF)) { + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); + } + if (foldConstants) { + if (!FoldConstants(context, &pn, this)) + return null(); + } + } + return pn; +} + +/* + * 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. + */ +enum { + ENDS_IN_OTHER = 0, + ENDS_IN_RETURN = 1, + 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 int +HasFinalReturn(SyntaxParseHandler::Node pn) +{ + return ENDS_IN_RETURN; +} + +template +bool +Parser::reportBadReturn(Node pn, ParseReportKind kind, + unsigned errnum, unsigned anonerrnum) +{ + JSAutoByteString name; + JSAtom *atom = pc->sc->asFunctionBox()->function()->atom(); + if (atom) { + if (!AtomToPrintableString(context, atom, &name)) + return false; + } else { + errnum = anonerrnum; + } + return report(kind, pc->sc->strict, pn, errnum, name.ptr()); +} + +template +bool +Parser::checkFinalReturn(Node pn) +{ + JS_ASSERT(pc->sc->isFunctionBox()); + return HasFinalReturn(pn) == ENDS_IN_RETURN || + reportBadReturn(pn, ParseExtraWarning, + JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE); +} + +/* + * Check that assigning to lhs is permitted. Assigning to 'eval' or + * 'arguments' is banned in strict mode and in destructuring assignment. + */ +template +bool +Parser::checkStrictAssignment(Node lhs, AssignmentFlavor flavor) +{ + if (!pc->sc->needStrictChecks() && flavor != KeyedDestructuringAssignment) + return true; + + JSAtom *atom = handler.isName(lhs); + if (!atom) + return true; + + if (atom == context->names().eval || atom == context->names().arguments) { + JSAutoByteString name; + if (!AtomToPrintableString(context, atom, &name)) + return false; + + ParseReportKind kind; + unsigned errnum; + if (pc->sc->strict || flavor != KeyedDestructuringAssignment) { + kind = ParseStrictError; + errnum = JSMSG_BAD_STRICT_ASSIGN; + } else { + kind = ParseError; + errnum = JSMSG_BAD_DESTRUCT_ASSIGN; + } + if (!report(kind, pc->sc->strict, lhs, errnum, 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 + * pc's token stream if pn is nullptr. + */ +template +bool +Parser::checkStrictBinding(PropertyName *name, Node pn) +{ + if (!pc->sc->needStrictChecks()) + return true; + + if (name == context->names().eval || name == context->names().arguments || IsKeyword(name)) { + JSAutoByteString bytes; + if (!AtomToPrintableString(context, name, &bytes)) + return false; + return report(ParseStrictError, pc->sc->strict, pn, + JSMSG_BAD_BINDING, bytes.ptr()); + } + + return true; +} + +template <> +ParseNode * +Parser::standaloneFunctionBody(HandleFunction fun, const AutoNameVector &formals, + GeneratorKind generatorKind, + Directives inheritedDirectives, + Directives *newDirectives) +{ + Node fn = handler.newFunctionDefinition(); + if (!fn) + return null(); + + ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &handler); + if (!argsbody) + return null(); + argsbody->setOp(JSOP_NOP); + argsbody->makeEmpty(); + fn->pn_body = argsbody; + + FunctionBox *funbox = newFunctionBox(fn, fun, /* outerpc = */ nullptr, inheritedDirectives, + generatorKind); + if (!funbox) + return null(); + funbox->length = fun->nargs() - fun->hasRest(); + handler.setFunctionBox(fn, funbox); + + ParseContext funpc(this, pc, fn, funbox, newDirectives, + /* staticLevel = */ 0, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); + if (!funpc.init(tokenStream)) + return null(); + + for (unsigned i = 0; i < formals.length(); i++) { + if (!defineArg(fn, formals[i])) + return null(); + } + + ParseNode *pn = functionBody(Statement, StatementListBody); + if (!pn) + return null(); + + if (!tokenStream.matchToken(TOK_EOF)) { + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); + } + + if (!FoldConstants(context, &pn, this)) + return null(); + + InternalHandle funboxBindings = + InternalHandle::fromMarkedLocation(&funbox->bindings); + if (!funpc.generateFunctionBindings(context, tokenStream, alloc, funboxBindings)) + return null(); + + JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY)); + fn->pn_body->append(pn); + fn->pn_body->pn_pos = pn->pn_pos; + return fn; +} + +template <> +bool +Parser::checkFunctionArguments() +{ + /* + * Non-top-level functions use JSOP_DEFFUN which is a dynamic scope + * operation which means it aliases any bindings with the same name. + */ + if (FuncStmtSet *set = pc->funcStmts) { + for (FuncStmtSet::Range r = set->all(); !r.empty(); r.popFront()) { + PropertyName *name = r.front()->asPropertyName(); + if (Definition *dn = pc->decls().lookupFirst(name)) + dn->pn_dflags |= PND_CLOSED; + } + } + + /* Time to implement the odd semantics of 'arguments'. */ + HandlePropertyName arguments = context->names().arguments; + + /* + * As explained by the ContextFlags::funArgumentsHasLocalBinding comment, + * create a declaration for 'arguments' if there are any unbound uses in + * the function body. + */ + for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) { + if (r.front().key() == arguments) { + Definition *dn = r.front().value().get(); + pc->lexdeps->remove(arguments); + dn->pn_dflags |= PND_IMPLICITARGUMENTS; + if (!pc->define(tokenStream, arguments, dn, Definition::VAR)) + return false; + pc->sc->asFunctionBox()->usesArguments = true; + break; + } + } + + /* + * Report error if both rest parameters and 'arguments' are used. Do this + * check before adding artificial 'arguments' below. + */ + Definition *maybeArgDef = pc->decls().lookupFirst(arguments); + bool argumentsHasBinding = !!maybeArgDef; + bool argumentsHasLocalBinding = maybeArgDef && maybeArgDef->kind() != Definition::ARG; + bool hasRest = pc->sc->asFunctionBox()->function()->hasRest(); + if (hasRest && argumentsHasLocalBinding) { + report(ParseError, false, nullptr, JSMSG_ARGUMENTS_AND_REST); + return false; + } + + /* + * Even if 'arguments' isn't explicitly mentioned, dynamic name lookup + * forces an 'arguments' binding. The exception is that functions with rest + * parameters are free from 'arguments'. + */ + if (!argumentsHasBinding && pc->sc->bindingsAccessedDynamically() && !hasRest) { + ParseNode *pn = newName(arguments); + if (!pn) + return false; + if (!pc->define(tokenStream, arguments, pn, Definition::VAR)) + return false; + argumentsHasBinding = true; + argumentsHasLocalBinding = true; + } + + /* + * Now that all possible 'arguments' bindings have been added, note whether + * 'arguments' has a local binding and whether it unconditionally needs an + * arguments object. (Also see the flags' comments in ContextFlags.) + */ + if (argumentsHasLocalBinding) { + FunctionBox *funbox = pc->sc->asFunctionBox(); + funbox->setArgumentsHasLocalBinding(); + + /* + * If a script has both explicit mentions of 'arguments' and dynamic + * name lookups which could access the arguments, an arguments object + * must be created eagerly. The SSA analysis used for lazy arguments + * cannot cope with dynamic name accesses, so any 'arguments' accessed + * via a NAME opcode must force construction of the arguments object. + */ + if (pc->sc->bindingsAccessedDynamically() && maybeArgDef) + funbox->setDefinitelyNeedsArgsObj(); + + /* + * If a script contains the debugger statement either directly or + * within an inner function, the arguments object must be created + * eagerly. The debugger can walk the scope chain and observe any + * values along it. + */ + if (pc->sc->hasDebuggerStatement()) + funbox->setDefinitelyNeedsArgsObj(); + + /* + * 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 (pc->sc->needStrictChecks()) { + for (AtomDefnListMap::Range r = pc->decls().all(); !r.empty(); r.popFront()) { + DefinitionList &dlist = r.front().value(); + for (DefinitionList::Range dr = dlist.all(); !dr.empty(); dr.popFront()) { + Definition *dn = dr.front(); + if (dn->kind() == Definition::ARG && dn->isAssigned()) + funbox->setDefinitelyNeedsArgsObj(); + } + } + /* Watch for mutation of arguments through e.g. eval(). */ + if (pc->sc->bindingsAccessedDynamically()) + funbox->setDefinitelyNeedsArgsObj(); + } + } + + return true; +} + +template <> +bool +Parser::checkFunctionArguments() +{ + bool hasRest = pc->sc->asFunctionBox()->function()->hasRest(); + + if (pc->lexdeps->lookup(context->names().arguments)) { + pc->sc->asFunctionBox()->usesArguments = true; + if (hasRest) { + report(ParseError, false, null(), JSMSG_ARGUMENTS_AND_REST); + return false; + } + } else if (hasRest) { + DefinitionNode maybeArgDef = pc->decls().lookupFirst(context->names().arguments); + if (maybeArgDef && handler.getDefinitionKind(maybeArgDef) != Definition::ARG) { + report(ParseError, false, null(), JSMSG_ARGUMENTS_AND_REST); + return false; + } + } + + return true; +} + +template +typename ParseHandler::Node +Parser::functionBody(FunctionSyntaxKind kind, FunctionBodyType type) +{ + JS_ASSERT(pc->sc->isFunctionBox()); + JS_ASSERT(!pc->funHasReturnExpr && !pc->funHasReturnVoid); + +#ifdef DEBUG + uint32_t startYieldOffset = pc->lastYieldOffset; +#endif + + Node pn; + if (type == StatementListBody) { + pn = statements(); + if (!pn) + return null(); + } else { + JS_ASSERT(type == ExpressionBody); + JS_ASSERT(JS_HAS_EXPR_CLOSURES); + + Node kid = assignExpr(); + if (!kid) + return null(); + + pn = handler.newReturnStatement(kid, handler.getPosition(kid)); + if (!pn) + return null(); + } + + switch (pc->generatorKind()) { + case NotGenerator: + JS_ASSERT(pc->lastYieldOffset == startYieldOffset); + break; + + case LegacyGenerator: + // FIXME: Catch these errors eagerly, in yieldExpression(). + JS_ASSERT(pc->lastYieldOffset != startYieldOffset); + if (kind == Arrow) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_YIELD_IN_ARROW, js_yield_str); + return null(); + } + if (type == ExpressionBody) { + reportBadReturn(pn, ParseError, + JSMSG_BAD_GENERATOR_RETURN, + JSMSG_BAD_ANON_GENERATOR_RETURN); + return null(); + } + break; + + case StarGenerator: + JS_ASSERT(kind != Arrow); + JS_ASSERT(type == StatementListBody); + break; + } + + /* Check for falling off the end of a function that returns a value. */ + if (options().extraWarningsOption && pc->funHasReturnExpr && !checkFinalReturn(pn)) + return null(); + + /* Define the 'arguments' binding if necessary. */ + if (!checkFunctionArguments()) + return null(); + + return pn; +} + +/* See comment for use in Parser::functionDef. */ +template <> +bool +Parser::makeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom) +{ + /* Turn pn into a definition. */ + pc->updateDecl(atom, pn); + + /* Change all uses of dn to be uses of pn. */ + 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; + + /* + * A PNK_FUNCTION node must be a definition, so convert shadowed function + * statements into nops. This is valid since all body-level function + * statement initialization happens at the beginning of the function + * (thus, only the last statement's effect is visible). E.g., in + * + * function outer() { + * function g() { return 1 } + * assertEq(g(), 2); + * function g() { return 2 } + * assertEq(g(), 2); + * } + * + * both asserts are valid. + */ + if (dn->getKind() == PNK_FUNCTION) { + JS_ASSERT(dn->functionIsHoisted()); + pn->dn_uses = dn->pn_link; + handler.prepareNodeForMutation(dn); + dn->setKind(PNK_NOP); + dn->setArity(PN_NULLARY); + return true; + } + + /* + * 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->canHaveInitializer()) { + if (ParseNode *rhs = dn->expr()) { + ParseNode *lhs = handler.makeAssignment(dn, rhs); + if (!lhs) + return false; + pn->dn_uses = lhs; + dn->pn_link = nullptr; + dn = (Definition *) lhs; + } + } + + /* Turn dn into a use of pn. */ + JS_ASSERT(dn->isKind(PNK_NAME)); + JS_ASSERT(dn->isArity(PN_NAME)); + JS_ASSERT(dn->pn_atom == atom); + dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETNAME : JSOP_NAME); + dn->setDefn(false); + dn->setUsed(true); + dn->pn_lexdef = (Definition *) pn; + dn->pn_cookie.makeFree(); + dn->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. + */ + +template +struct BindData +{ + BindData(ExclusiveContext *cx) : let(cx) {} + + typedef bool + (*Binder)(BindData *data, HandlePropertyName name, Parser *parser); + + /* name node for definition processing and error source coordinates */ + typename ParseHandler::Node pn; + + JSOp op; /* prolog bytecode or nop */ + Binder binder; /* binder, discriminates u */ + + struct LetData { + LetData(ExclusiveContext *cx) : blockObj(cx) {} + VarContext varContext; + RootedStaticBlockObject blockObj; + unsigned overflow; + } let; + + void initLet(VarContext varContext, StaticBlockObject &blockObj, unsigned overflow) { + this->pn = ParseHandler::null(); + this->op = JSOP_NOP; + this->binder = Parser::bindLet; + this->let.varContext = varContext; + this->let.blockObj = &blockObj; + this->let.overflow = overflow; + } + + void initVarOrConst(JSOp op) { + this->op = op; + this->binder = Parser::bindVarOrConst; + } +}; + +template +JSFunction * +Parser::newFunction(GenericParseContext *pc, HandleAtom atom, + FunctionSyntaxKind kind, JSObject *proto) +{ + JS_ASSERT_IF(kind == Statement, atom != nullptr); + + /* + * Find the global compilation context in order to pre-set the newborn + * function's parent slot to pc->sc->as()->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 (pc->parent) + pc = pc->parent; + + RootedFunction fun(context); + JSFunction::Flags flags = (kind == Expression) + ? JSFunction::INTERPRETED_LAMBDA + : (kind == Arrow) + ? JSFunction::INTERPRETED_LAMBDA_ARROW + : JSFunction::INTERPRETED; + gc::AllocKind allocKind = JSFunction::FinalizeKind; + if (kind == Arrow) + allocKind = JSFunction::ExtendedFinalizeKind; + fun = NewFunctionWithProto(context, NullPtr(), nullptr, 0, flags, NullPtr(), atom, proto, + allocKind, MaybeSingletonObject); + if (!fun) + return nullptr; + if (options().selfHostingMode) + fun->setIsSelfHostedBuiltin(); + return fun; +} + +static bool +MatchOrInsertSemicolon(TokenStream &ts) +{ + TokenKind tt = ts.peekTokenSameLine(TokenStream::Operand); + if (tt == TOK_ERROR) + return false; + if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { + /* Advance the scanner for proper error location reporting. */ + ts.getToken(TokenStream::Operand); + ts.reportError(JSMSG_SEMI_BEFORE_STMNT); + return false; + } + (void) ts.matchToken(TOK_SEMI); + return true; +} + +template +typename ParseHandler::DefinitionNode +Parser::getOrCreateLexicalDependency(ParseContext *pc, JSAtom *atom) +{ + AtomDefnAddPtr p = pc->lexdeps->lookupForAdd(atom); + if (p) + return p.value().get(); + + DefinitionNode dn = handler.newPlaceholder(atom, pc->blockid(), pos()); + if (!dn) + return ParseHandler::nullDefinition(); + DefinitionSingle def = DefinitionSingle::new_(dn); + if (!pc->lexdeps->add(p, atom, def)) + return ParseHandler::nullDefinition(); + return dn; +} + +static bool +ConvertDefinitionToNamedLambdaUse(TokenStream &ts, ParseContext *pc, + FunctionBox *funbox, Definition *dn) +{ + dn->setOp(JSOP_CALLEE); + if (!dn->pn_cookie.set(ts, pc->staticLevel, 0)) + return false; + dn->pn_dflags |= PND_BOUND; + JS_ASSERT(dn->kind() == Definition::NAMED_LAMBDA); + + /* + * Since 'dn' is a placeholder, it has not been defined in the + * ParseContext and hence we must manually flag a closed-over + * callee name as needing a dynamic scope (this is done for all + * definitions in the ParseContext by generateFunctionBindings). + * + * If 'dn' has been assigned to, then we also flag the function + * scope has needing a dynamic scope so that dynamic scope + * setter can either ignore the set (in non-strict mode) or + * produce an error (in strict mode). + */ + if (dn->isClosed() || dn->isAssigned()) + funbox->setNeedsDeclEnvObject(); + return true; +} + +/* + * Beware: this function is called for functions nested in other functions or + * global scripts but not for functions compiled through the Function + * constructor or JSAPI. To always execute code when a function has finished + * parsing, use Parser::functionBody. + */ +template <> +bool +Parser::leaveFunction(ParseNode *fn, ParseContext *outerpc, + FunctionSyntaxKind kind) +{ + outerpc->blockidGen = pc->blockidGen; + + FunctionBox *funbox = fn->pn_funbox; + JS_ASSERT(funbox == pc->sc->asFunctionBox()); + + /* Propagate unresolved lexical names up to outerpc->lexdeps. */ + if (pc->lexdeps->count()) { + for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) { + JSAtom *atom = r.front().key(); + Definition *dn = r.front().value().get(); + JS_ASSERT(dn->isPlaceholder()); + + if (atom == funbox->function()->name() && kind == Expression) { + if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn)) + return false; + continue; + } + + Definition *outer_dn = outerpc->decls().lookupFirst(atom); + + /* + * Make sure to deoptimize lexical dependencies that are polluted + * by eval and function statements (which both flag the function as + * having an extensible scope) or any enclosing 'with'. + */ + if (funbox->hasExtensibleScope() || outerpc->parsingWith) + handler.deoptimizeUsesWithin(dn, fn->pn_pos); + + if (!outer_dn) { + /* + * 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 = getOrCreateLexicalDependency(outerpc, atom); + if (!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 LegacyCompExprTransplanter::transplant, the PN_CODE/PN_NAME + * case), and nowhere else, currently. + */ + if (dn != outer_dn) { + if (ParseNode *pnu = dn->dn_uses) { + while (true) { + pnu->pn_lexdef = outer_dn; + if (!pnu->pn_link) + break; + pnu = pnu->pn_link; + } + pnu->pn_link = outer_dn->dn_uses; + outer_dn->dn_uses = dn->dn_uses; + dn->dn_uses = nullptr; + } + + outer_dn->pn_dflags |= dn->pn_dflags & ~PND_PLACEHOLDER; + } + + /* Mark the outer dn as escaping. */ + outer_dn->pn_dflags |= PND_CLOSED; + } + } + + InternalHandle bindings = + InternalHandle::fromMarkedLocation(&funbox->bindings); + return pc->generateFunctionBindings(context, tokenStream, alloc, bindings); +} + +template <> +bool +Parser::leaveFunction(Node fn, ParseContext *outerpc, + FunctionSyntaxKind kind) +{ + outerpc->blockidGen = pc->blockidGen; + + FunctionBox *funbox = pc->sc->asFunctionBox(); + return addFreeVariablesFromLazyFunction(funbox->function(), outerpc); +} + +/* + * defineArg is called for both the arguments of a regular function definition + * and the arguments specified by the Function constructor. + * + * The 'disallowDuplicateArgs' bool indicates whether the use of another + * feature (destructuring or default arguments) disables duplicate arguments. + * (ECMA-262 requires us to support duplicate parameter names, but, for newer + * features, we consider the code to have "opted in" to higher standards and + * forbid duplicates.) + * + * If 'duplicatedArg' is non-null, then DefineArg assigns to it any previous + * argument with the same name. The caller may use this to report an error when + * one of the abovementioned features occurs after a duplicate. + */ +template +bool +Parser::defineArg(Node funcpn, HandlePropertyName name, + bool disallowDuplicateArgs, Node *duplicatedArg) +{ + SharedContext *sc = pc->sc; + + /* Handle duplicate argument names. */ + if (DefinitionNode prevDecl = pc->decls().lookupFirst(name)) { + Node pn = handler.getDefinitionNode(prevDecl); + + /* + * Strict-mode disallows duplicate args. We may not know whether we are + * in strict mode or not (since the function body hasn't been parsed). + * In such cases, report will queue up the potential error and return + * 'true'. + */ + if (sc->needStrictChecks()) { + JSAutoByteString bytes; + if (!AtomToPrintableString(context, name, &bytes)) + return false; + if (!report(ParseStrictError, pc->sc->strict, pn, + JSMSG_DUPLICATE_FORMAL, bytes.ptr())) + { + return false; + } + } + + if (disallowDuplicateArgs) { + report(ParseError, false, pn, JSMSG_BAD_DUP_ARGS); + return false; + } + + if (duplicatedArg) + *duplicatedArg = pn; + + /* ParseContext::define assumes and asserts prevDecl is not in decls. */ + JS_ASSERT(handler.getDefinitionKind(prevDecl) == Definition::ARG); + pc->prepareToAddDuplicateArg(name, prevDecl); + } + + Node argpn = newName(name); + if (!argpn) + return false; + + if (!checkStrictBinding(name, argpn)) + return false; + + handler.addFunctionArgument(funcpn, argpn); + return pc->define(tokenStream, name, argpn, Definition::ARG); +} + +template +/* static */ bool +Parser::bindDestructuringArg(BindData *data, + HandlePropertyName name, Parser *parser) +{ + ParseContext *pc = parser->pc; + JS_ASSERT(pc->sc->isFunctionBox()); + + if (pc->decls().lookupFirst(name)) { + parser->report(ParseError, false, null(), JSMSG_BAD_DUP_ARGS); + return false; + } + + if (!parser->checkStrictBinding(name, data->pn)) + return false; + + return pc->define(parser->tokenStream, name, data->pn, Definition::VAR); +} + +template +bool +Parser::functionArguments(FunctionSyntaxKind kind, Node *listp, Node funcpn, + bool *hasRest) +{ + FunctionBox *funbox = pc->sc->asFunctionBox(); + + *hasRest = false; + + bool parenFreeArrow = false; + if (kind == Arrow && tokenStream.peekToken() == TOK_NAME) { + parenFreeArrow = true; + } else { + if (tokenStream.getToken() != TOK_LP) { + report(ParseError, false, null(), + kind == Arrow ? JSMSG_BAD_ARROW_ARGS : JSMSG_PAREN_BEFORE_FORMAL); + return false; + } + + // Record the start of function source (for FunctionToString). If we + // are parenFreeArrow, we will set this below, after consuming the NAME. + funbox->setStart(tokenStream); + } + + Node argsbody = handler.newList(PNK_ARGSBODY); + if (!argsbody) + return false; + handler.setFunctionBody(funcpn, argsbody); + + if (parenFreeArrow || !tokenStream.matchToken(TOK_RP)) { + bool hasDefaults = false; + Node duplicatedArg = null(); + Node list = null(); + + do { + if (*hasRest) { + report(ParseError, false, null(), JSMSG_PARAMETER_AFTER_REST); + return false; + } + + TokenKind tt = tokenStream.getToken(); + JS_ASSERT_IF(parenFreeArrow, tt == TOK_NAME); + switch (tt) { + case TOK_LB: + case TOK_LC: + { + /* See comment below in the TOK_NAME case. */ + if (duplicatedArg) { + report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS); + return false; + } + + if (hasDefaults) { + report(ParseError, false, null(), JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT); + return false; + } + + funbox->hasDestructuringArgs = 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(context); + data.pn = ParseHandler::null(); + data.op = JSOP_DEFVAR; + data.binder = bindDestructuringArg; + Node lhs = destructuringExpr(&data, tt); + if (!lhs) + return false; + + /* + * Synthesize a destructuring assignment from the single + * anonymous positional parameter into the destructuring + * left-hand-side expression and accumulate it in list. + */ + HandlePropertyName name = context->names().empty; + Node rhs = newName(name); + if (!rhs) + return false; + + if (!pc->define(tokenStream, name, rhs, Definition::ARG)) + return false; + + Node item = handler.newBinary(PNK_ASSIGN, lhs, rhs); + if (!item) + return false; + if (list) { + handler.addList(list, item); + } else { + list = handler.newList(PNK_VAR, item); + if (!list) + return false; + *listp = list; + } + break; + } + + case TOK_YIELD: + if (!checkYieldNameValidity()) + return false; + goto TOK_NAME; + + case TOK_TRIPLEDOT: + { + *hasRest = true; + tt = tokenStream.getToken(); + if (tt != TOK_NAME) { + if (tt != TOK_ERROR) + report(ParseError, false, null(), JSMSG_NO_REST_NAME); + return false; + } + goto TOK_NAME; + } + + TOK_NAME: + case TOK_NAME: + { + if (parenFreeArrow) + funbox->setStart(tokenStream); + + RootedPropertyName name(context, tokenStream.currentName()); + bool disallowDuplicateArgs = funbox->hasDestructuringArgs || hasDefaults; + if (!defineArg(funcpn, name, disallowDuplicateArgs, &duplicatedArg)) + return false; + + if (tokenStream.matchToken(TOK_ASSIGN)) { + // A default argument without parentheses would look like: + // a = expr => body, but both operators are right-associative, so + // that would have been parsed as a = (expr => body) instead. + // Therefore it's impossible to get here with parenFreeArrow. + JS_ASSERT(!parenFreeArrow); + + if (*hasRest) { + report(ParseError, false, null(), JSMSG_REST_WITH_DEFAULT); + return false; + } + if (duplicatedArg) { + report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS); + return false; + } + if (!hasDefaults) { + hasDefaults = true; + + // The Function.length property is the number of formals + // before the first default argument. + funbox->length = pc->numArgs() - 1; + } + Node def_expr = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT); + if (!def_expr) + return false; + handler.setLastFunctionArgumentDefault(funcpn, def_expr); + } + + break; + } + + default: + report(ParseError, false, null(), JSMSG_MISSING_FORMAL); + /* FALL THROUGH */ + case TOK_ERROR: + return false; + } + } while (!parenFreeArrow && tokenStream.matchToken(TOK_COMMA)); + + if (!parenFreeArrow && tokenStream.getToken() != TOK_RP) { + report(ParseError, false, null(), JSMSG_PAREN_AFTER_FORMAL); + return false; + } + + if (!hasDefaults) + funbox->length = pc->numArgs() - *hasRest; + } + + return true; +} + +template <> +bool +Parser::checkFunctionDefinition(HandlePropertyName funName, + ParseNode **pn_, FunctionSyntaxKind kind, + bool *pbodyProcessed) +{ + ParseNode *&pn = *pn_; + *pbodyProcessed = false; + + /* Function statements add a binding to the enclosing scope. */ + bool bodyLevel = pc->atBodyLevel(); + + if (kind == Statement) { + /* + * Handle redeclaration and optimize cases where we can statically bind the + * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup). + */ + if (Definition *dn = pc->decls().lookupFirst(funName)) { + JS_ASSERT(!dn->isUsed()); + JS_ASSERT(dn->isDefn()); + + if (options().extraWarningsOption || dn->kind() == Definition::CONST) { + JSAutoByteString name; + ParseReportKind reporter = (dn->kind() != Definition::CONST) + ? ParseExtraWarning + : ParseError; + if (!AtomToPrintableString(context, funName, &name) || + !report(reporter, false, nullptr, JSMSG_REDECLARED_VAR, + Definition::kindString(dn->kind()), name.ptr())) + { + return false; + } + } + + /* + * Body-level function statements are effectively variable + * declarations where the initialization is hoisted to the + * beginning of the block. This means that any other variable + * declaration with the same name is really just an assignment to + * the function's binding (which is mutable), so turn any existing + * declaration into a use. + */ + if (bodyLevel && !makeDefIntoUse(dn, pn, funName)) + return false; + } 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 pc->lexdeps on first forward reference, and recycle pn. + */ + if (Definition *fn = pc->lexdeps.lookupDefn(funName)) { + JS_ASSERT(fn->isDefn()); + fn->setKind(PNK_FUNCTION); + fn->setArity(PN_CODE); + fn->pn_pos.begin = pn->pn_pos.begin; + fn->pn_pos.end = pn->pn_pos.end; + + fn->pn_body = nullptr; + fn->pn_cookie.makeFree(); + + pc->lexdeps->remove(funName); + handler.freeTree(pn); + pn = fn; + } + + if (!pc->define(tokenStream, funName, pn, Definition::VAR)) + return false; + } + + if (bodyLevel) { + JS_ASSERT(pn->functionIsHoisted()); + JS_ASSERT_IF(pc->sc->isFunctionBox(), !pn->pn_cookie.isFree()); + JS_ASSERT_IF(!pc->sc->isFunctionBox(), pn->pn_cookie.isFree()); + } else { + /* + * As a SpiderMonkey-specific extension, non-body-level function + * statements (e.g., functions in an "if" or "while" block) are + * dynamically bound when control flow reaches the statement. + */ + JS_ASSERT(!pc->sc->strict); + JS_ASSERT(pn->pn_cookie.isFree()); + if (pc->sc->isFunctionBox()) { + FunctionBox *funbox = pc->sc->asFunctionBox(); + funbox->setMightAliasLocals(); + funbox->setHasExtensibleScope(); + } + pn->setOp(JSOP_DEFFUN); + + /* + * Instead of setting bindingsAccessedDynamically, which would be + * overly conservative, remember the names of all function + * statements and mark any bindings with the same as aliased at the + * end of functionBody. + */ + if (!pc->funcStmts) { + pc->funcStmts = context->new_(context); + if (!pc->funcStmts || !pc->funcStmts->init()) + return false; + } + if (!pc->funcStmts->put(funName)) + return false; + + /* + * Due to the implicit declaration mechanism, 'arguments' will not + * have decls and, even if it did, they will not be noted as closed + * in the emitter. Thus, in the corner case of function statements + * overridding arguments, flag the whole scope as dynamic. + */ + if (funName == context->names().arguments) + pc->sc->setBindingsAccessedDynamically(); + } + + /* No further binding (in BindNameToSlot) is needed for functions. */ + pn->pn_dflags |= PND_BOUND; + } else { + /* A function expression does not introduce any binding. */ + pn->setOp(kind == Arrow ? JSOP_LAMBDA_ARROW : JSOP_LAMBDA); + } + + // When a lazily-parsed function is called, we only fully parse (and emit) + // that function, not any of its nested children. The initial syntax-only + // parse recorded the free variables of nested functions and their extents, + // so we can skip over them after accounting for their free variables. + if (LazyScript *lazyOuter = handler.lazyOuterFunction()) { + JSFunction *fun = handler.nextLazyInnerFunction(); + JS_ASSERT(!fun->isLegacyGenerator()); + FunctionBox *funbox = newFunctionBox(pn, fun, pc, Directives(/* strict = */ false), + fun->generatorKind()); + if (!funbox) + return false; + + if (!addFreeVariablesFromLazyFunction(fun, pc)) + return false; + + // The position passed to tokenStream.advance() is relative to + // userbuf.base() while LazyScript::{begin,end} offsets are relative to + // the outermost script source. N.B: userbuf.base() is initialized + // (in TokenStream()) to begin() - column() so that column numbers in + // the lazily parsed script are correct. + uint32_t userbufBase = lazyOuter->begin() - lazyOuter->column(); + tokenStream.advance(fun->lazyScript()->end() - userbufBase); + + *pbodyProcessed = true; + return true; + } + + return true; +} + +template +static inline void +PropagateTransitiveParseFlags(const T *inner, U *outer) +{ + if (inner->bindingsAccessedDynamically()) + outer->setBindingsAccessedDynamically(); + if (inner->hasDebuggerStatement()) + outer->setHasDebuggerStatement(); +} + +template +bool +Parser::addFreeVariablesFromLazyFunction(JSFunction *fun, + ParseContext *pc) +{ + // Update any definition nodes in this context according to free variables + // in a lazily parsed inner function. + + LazyScript *lazy = fun->lazyScript(); + HeapPtrAtom *freeVariables = lazy->freeVariables(); + for (size_t i = 0; i < lazy->numFreeVariables(); i++) { + JSAtom *atom = freeVariables[i]; + + // 'arguments' will be implicitly bound within the inner function. + if (atom == context->names().arguments) + continue; + + DefinitionNode dn = pc->decls().lookupFirst(atom); + + if (!dn) { + dn = getOrCreateLexicalDependency(pc, atom); + if (!dn) + return false; + } + + /* Mark the outer dn as escaping. */ + handler.setFlag(handler.getDefinitionNode(dn), PND_CLOSED); + } + + PropagateTransitiveParseFlags(lazy, pc->sc); + return true; +} + +template <> +bool +Parser::checkFunctionDefinition(HandlePropertyName funName, + Node *pn, FunctionSyntaxKind kind, + bool *pbodyProcessed) +{ + *pbodyProcessed = false; + + /* Function statements add a binding to the enclosing scope. */ + bool bodyLevel = pc->atBodyLevel(); + + if (kind == Statement) { + /* + * Handle redeclaration and optimize cases where we can statically bind the + * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup). + */ + if (DefinitionNode dn = pc->decls().lookupFirst(funName)) { + if (dn == Definition::CONST) { + JSAutoByteString name; + if (!AtomToPrintableString(context, funName, &name) || + !report(ParseError, false, null(), JSMSG_REDECLARED_VAR, + Definition::kindString(dn), name.ptr())) + { + return false; + } + } + } else if (bodyLevel) { + if (pc->lexdeps.lookupDefn(funName)) + pc->lexdeps->remove(funName); + + if (!pc->define(tokenStream, funName, *pn, Definition::VAR)) + return false; + } + + if (!bodyLevel && funName == context->names().arguments) + pc->sc->setBindingsAccessedDynamically(); + } + + if (kind == Arrow) { + /* Arrow functions cannot yet be parsed lazily. */ + return abortIfSyntaxParser(); + } + + return true; +} + +template +typename ParseHandler::Node +Parser::functionDef(HandlePropertyName funName, const TokenStream::Position &start, + FunctionType type, FunctionSyntaxKind kind, + GeneratorKind generatorKind) +{ + JS_ASSERT_IF(kind == Statement, funName); + + /* Make a TOK_FUNCTION node. */ + Node pn = handler.newFunctionDefinition(); + if (!pn) + return null(); + + bool bodyProcessed; + if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed)) + return null(); + + if (bodyProcessed) + return pn; + + RootedObject proto(context); + if (generatorKind == StarGenerator) { + // If we are off the main thread, the generator meta-objects have + // already been created by js::StartOffThreadParseScript, so cx will not + // be necessary. + JSContext *cx = context->maybeJSContext(); + proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global()); + if (!proto) + return null(); + } + RootedFunction fun(context, newFunction(pc, funName, kind, proto)); + if (!fun) + return null(); + + // Speculatively parse using the directives of the parent parsing context. + // If a directive is encountered (e.g., "use strict") that changes how the + // function should have been parsed, we backup and reparse with the new set + // of directives. + Directives directives(pc); + Directives newDirectives = directives; + + while (true) { + if (functionArgsAndBody(pn, fun, type, kind, generatorKind, directives, &newDirectives)) + break; + if (tokenStream.hadError() || directives == newDirectives) + return null(); + + // Assignment must be monotonic to prevent reparsing iloops + JS_ASSERT_IF(directives.strict(), newDirectives.strict()); + JS_ASSERT_IF(directives.asmJS(), newDirectives.asmJS()); + directives = newDirectives; + + tokenStream.seek(start); + if (funName && tokenStream.getToken() == TOK_ERROR) + return null(); + + // functionArgsAndBody may have already set pn->pn_body before failing. + handler.setFunctionBody(pn, null()); + } + + return pn; +} + +template <> +bool +Parser::finishFunctionDefinition(ParseNode *pn, FunctionBox *funbox, + ParseNode *prelude, ParseNode *body) +{ + pn->pn_pos.end = pos().end; + + /* + * 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, &handler); + if (!block) + return false; + block->pn_pos = body->pn_pos; + block->initList(body); + + body = block; + } + + ParseNode *item = UnaryNode::create(PNK_SEMI, &handler); + if (!item) + return false; + + 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; + } + + JS_ASSERT(pn->pn_funbox == funbox); + JS_ASSERT(pn->pn_body->isKind(PNK_ARGSBODY)); + pn->pn_body->append(body); + pn->pn_body->pn_pos = body->pn_pos; + + return true; +} + +template <> +bool +Parser::finishFunctionDefinition(Node pn, FunctionBox *funbox, + Node prelude, Node body) +{ + // The LazyScript for a lazily parsed function needs to be constructed + // while its ParseContext and associated lexdeps and inner functions are + // still available. + + if (funbox->inWith) + return abortIfSyntaxParser(); + + size_t numFreeVariables = pc->lexdeps->count(); + size_t numInnerFunctions = pc->innerFunctions.length(); + + RootedFunction fun(context, funbox->function()); + LazyScript *lazy = LazyScript::CreateRaw(context, fun, numFreeVariables, numInnerFunctions, + versionNumber(), funbox->bufStart, funbox->bufEnd, + funbox->startLine, funbox->startColumn); + if (!lazy) + return false; + + HeapPtrAtom *freeVariables = lazy->freeVariables(); + size_t i = 0; + for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) + freeVariables[i++].init(r.front().key()); + JS_ASSERT(i == numFreeVariables); + + HeapPtrFunction *innerFunctions = lazy->innerFunctions(); + for (size_t i = 0; i < numInnerFunctions; i++) + innerFunctions[i].init(pc->innerFunctions[i]); + + if (pc->sc->strict) + lazy->setStrict(); + lazy->setGeneratorKind(funbox->generatorKind()); + if (funbox->usesArguments && funbox->usesApply) + lazy->setUsesArgumentsAndApply(); + PropagateTransitiveParseFlags(funbox, lazy); + + fun->initLazyScript(lazy); + return true; +} + +template <> +bool +Parser::functionArgsAndBody(ParseNode *pn, HandleFunction fun, + FunctionType type, FunctionSyntaxKind kind, + GeneratorKind generatorKind, + Directives inheritedDirectives, + Directives *newDirectives) +{ + ParseContext *outerpc = pc; + + // Create box for fun->object early to protect against last-ditch GC. + FunctionBox *funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind); + if (!funbox) + return false; + + // Try a syntax parse for this inner function. + do { + Parser *parser = handler.syntaxParser; + if (!parser) + break; + + { + // Move the syntax parser to the current position in the stream. + TokenStream::Position position(keepAtoms); + tokenStream.tell(&position); + if (!parser->tokenStream.seek(position, tokenStream)) + return false; + + ParseContext funpc(parser, outerpc, SyntaxParseHandler::null(), funbox, + newDirectives, outerpc->staticLevel + 1, + outerpc->blockidGen, + /* blockScopeDepth = */ 0); + if (!funpc.init(tokenStream)) + return false; + + if (!parser->functionArgsAndBodyGeneric(SyntaxParseHandler::NodeGeneric, + fun, type, kind, newDirectives)) + { + if (parser->hadAbortedSyntaxParse()) { + // Try again with a full parse. + parser->clearAbortedSyntaxParse(); + break; + } + return false; + } + + outerpc->blockidGen = funpc.blockidGen; + + // Advance this parser over tokens processed by the syntax parser. + parser->tokenStream.tell(&position); + if (!tokenStream.seek(position, parser->tokenStream)) + return false; + + // Update the end position of the parse node. + pn->pn_pos.end = tokenStream.currentToken().pos.end; + } + + if (!addFreeVariablesFromLazyFunction(fun, pc)) + return false; + + pn->pn_blockid = outerpc->blockid(); + PropagateTransitiveParseFlags(funbox, outerpc->sc); + return true; + } while (false); + + // Continue doing a full parse for this inner function. + ParseContext funpc(this, pc, pn, funbox, newDirectives, + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); + if (!funpc.init(tokenStream)) + return false; + + if (!functionArgsAndBodyGeneric(pn, fun, type, kind, newDirectives)) + return false; + + if (!leaveFunction(pn, outerpc, kind)) + return false; + + pn->pn_blockid = outerpc->blockid(); + + /* + * Fruit of the poisonous tree: if a closure contains a dynamic name access + * (eval, with, etc), we consider the parent to do the same. The reason is + * that the deoptimizing effects of dynamic name access apply equally to + * parents: any local can be read at runtime. + */ + PropagateTransitiveParseFlags(funbox, outerpc->sc); + return true; +} + +template <> +bool +Parser::functionArgsAndBody(Node pn, HandleFunction fun, + FunctionType type, FunctionSyntaxKind kind, + GeneratorKind generatorKind, + Directives inheritedDirectives, + Directives *newDirectives) +{ + ParseContext *outerpc = pc; + + // Create box for fun->object early to protect against last-ditch GC. + FunctionBox *funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind); + if (!funbox) + return false; + + // Initialize early for possible flags mutation via destructuringExpr. + ParseContext funpc(this, pc, handler.null(), funbox, newDirectives, + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); + if (!funpc.init(tokenStream)) + return false; + + if (!functionArgsAndBodyGeneric(pn, fun, type, kind, newDirectives)) + return false; + + if (!leaveFunction(pn, outerpc, kind)) + return false; + + // This is a lazy function inner to another lazy function. Remember the + // inner function so that if the outer function is eventually parsed we do + // not need any further parsing or processing of the inner function. + JS_ASSERT(fun->lazyScript()); + return outerpc->innerFunctions.append(fun); +} + +template <> +ParseNode * +Parser::standaloneLazyFunction(HandleFunction fun, unsigned staticLevel, + bool strict, GeneratorKind generatorKind) +{ + Node pn = handler.newFunctionDefinition(); + if (!pn) + return null(); + + Directives directives(/* strict = */ strict); + FunctionBox *funbox = newFunctionBox(pn, fun, /* outerpc = */ nullptr, directives, + generatorKind); + if (!funbox) + return null(); + funbox->length = fun->nargs() - fun->hasRest(); + + Directives newDirectives = directives; + ParseContext funpc(this, /* parent = */ nullptr, pn, funbox, + &newDirectives, staticLevel, /* bodyid = */ 0, + /* blockScopeDepth = */ 0); + if (!funpc.init(tokenStream)) + return null(); + + if (!functionArgsAndBodyGeneric(pn, fun, Normal, Statement, &newDirectives)) { + JS_ASSERT(directives == newDirectives); + return null(); + } + + if (fun->isNamedLambda()) { + if (AtomDefnPtr p = pc->lexdeps->lookup(fun->name())) { + Definition *dn = p.value().get(); + if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn)) + return nullptr; + } + } + + InternalHandle bindings = + InternalHandle::fromMarkedLocation(&funbox->bindings); + if (!pc->generateFunctionBindings(context, tokenStream, alloc, bindings)) + return null(); + + if (!FoldConstants(context, &pn, this)) + return null(); + + return pn; +} + +template +bool +Parser::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, FunctionType type, + FunctionSyntaxKind kind, + Directives *newDirectives) +{ + // Given a properly initialized parse context, try to parse an actual + // function without concern for conversion to strict mode, use of lazy + // parsing and such. + + Node prelude = null(); + bool hasRest; + if (!functionArguments(kind, &prelude, pn, &hasRest)) + return false; + + FunctionBox *funbox = pc->sc->asFunctionBox(); + + fun->setArgCount(pc->numArgs()); + if (hasRest) + fun->setHasRest(); + + if (type == Getter && fun->nargs() > 0) { + report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "getter", "no", "s"); + return false; + } + if (type == Setter && fun->nargs() != 1) { + report(ParseError, false, null(), JSMSG_ACCESSOR_WRONG_ARGS, "setter", "one", ""); + return false; + } + + if (kind == Arrow && !tokenStream.matchToken(TOK_ARROW)) { + report(ParseError, false, null(), JSMSG_BAD_ARROW_ARGS); + return false; + } + + // Parse the function body. + FunctionBodyType bodyType = StatementListBody; + if (tokenStream.getToken(TokenStream::Operand) != TOK_LC) { + if (funbox->isStarGenerator()) { + report(ParseError, false, null(), JSMSG_CURLY_BEFORE_BODY); + return false; + } + tokenStream.ungetToken(); + bodyType = ExpressionBody; + fun->setIsExprClosure(); + } + + Node body = functionBody(kind, bodyType); + if (!body) + return false; + + if (fun->name() && !checkStrictBinding(fun->name(), pn)) + return false; + +#if JS_HAS_EXPR_CLOSURES + if (bodyType == StatementListBody) { +#endif + if (!tokenStream.matchToken(TOK_RC)) { + report(ParseError, false, null(), JSMSG_CURLY_AFTER_BODY); + return false; + } + funbox->bufEnd = pos().begin + 1; +#if JS_HAS_EXPR_CLOSURES + } else { + if (tokenStream.hadError()) + return false; + funbox->bufEnd = pos().end; + if (kind == Statement && !MatchOrInsertSemicolon(tokenStream)) + return false; + } +#endif + + return finishFunctionDefinition(pn, funbox, prelude, body); +} + +template +bool +Parser::checkYieldNameValidity() +{ + // In star generators and in JS >= 1.7, yield is a keyword. Otherwise in + // strict mode, yield is a future reserved word. + if (pc->isStarGenerator() || versionNumber() >= JSVERSION_1_7 || pc->sc->strict) { + report(ParseError, false, null(), JSMSG_RESERVED_ID, "yield"); + return false; + } + return true; +} + +template +typename ParseHandler::Node +Parser::functionStmt() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); + + TokenStream::Position start(keepAtoms); + tokenStream.tell(&start); + + RootedPropertyName name(context); + GeneratorKind generatorKind = NotGenerator; + TokenKind tt = tokenStream.getToken(); + + if (tt == TOK_MUL) { + tokenStream.tell(&start); + tt = tokenStream.getToken(); + generatorKind = StarGenerator; + } + + if (tt == TOK_NAME) { + name = tokenStream.currentName(); + } else if (tt == TOK_YIELD) { + if (!checkYieldNameValidity()) + return null(); + name = tokenStream.currentName(); + } else { + /* Unnamed function expressions are forbidden in statement context. */ + report(ParseError, false, null(), JSMSG_UNNAMED_FUNCTION_STMT); + return null(); + } + + /* We forbid function statements in strict mode code. */ + if (!pc->atBodyLevel() && pc->sc->needStrictChecks() && + !report(ParseStrictError, pc->sc->strict, null(), JSMSG_STRICT_FUNCTION_STATEMENT)) + return null(); + + return functionDef(name, start, Normal, Statement, generatorKind); +} + +template +typename ParseHandler::Node +Parser::functionExpr() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FUNCTION)); + + TokenStream::Position start(keepAtoms); + tokenStream.tell(&start); + + GeneratorKind generatorKind = NotGenerator; + TokenKind tt = tokenStream.getToken(); + + if (tt == TOK_MUL) { + tokenStream.tell(&start); + tt = tokenStream.getToken(); + generatorKind = StarGenerator; + } + + RootedPropertyName name(context); + if (tt == TOK_NAME) { + name = tokenStream.currentName(); + } else if (tt == TOK_YIELD) { + if (!checkYieldNameValidity()) + return null(); + name = tokenStream.currentName(); + } else { + tokenStream.ungetToken(); + } + + return functionDef(name, start, Normal, Expression, generatorKind); +} + +/* + * Return true if this node, known to be an unparenthesized string literal, + * could be the string of a directive in a Directive Prologue. Directive + * strings never contain escape sequences or line continuations. + * isEscapeFreeStringLiteral, below, checks whether the node itself could be + * a directive. + */ +static inline bool +IsEscapeFreeStringLiteral(const TokenPos &pos, JSAtom *str) +{ + /* + * If the string's length in the source code is its length as a value, + * accounting for the quotes, then it must not contain any escape + * sequences or line continuations. + */ + return pos.begin + str->length() + 2 == pos.end; +} + +template <> +bool +Parser::asmJS(Node list) +{ + // While asm.js could technically be validated and compiled during syntax + // parsing, we have no guarantee that some later JS wouldn't abort the + // syntax parse and cause us to re-parse (and re-compile) the asm.js module. + // For simplicity, unconditionally abort the syntax parse when "use asm" is + // encountered so that asm.js is always validated/compiled exactly once + // during a full parse. + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template <> +bool +Parser::asmJS(Node list) +{ + // If we are already inside "use asm" that means we are either actively + // compiling or we are reparsing after asm.js validation failure. In either + // case, nothing to do here. + if (pc->useAsmOrInsideUseAsm()) + return true; + + // If there is no ScriptSource, then we are doing a non-compiling parse and + // so we shouldn't (and can't, without a ScriptSource) compile. + if (ss == nullptr) + return true; + + pc->sc->asFunctionBox()->useAsm = true; + +#ifdef JS_ION + // Attempt to validate and compile this asm.js module. On success, the + // tokenStream has been advanced to the closing }. On failure, the + // tokenStream is in an indeterminate state and we must reparse the + // function from the beginning. Reparsing is triggered by marking that a + // new directive has been encountered and returning 'false'. + bool validated; + if (!CompileAsmJS(context, *this, list, &validated)) + return false; + if (!validated) { + pc->newDirectives->setAsmJS(); + return false; + } +#endif + + return true; +} + +/* + * Recognize Directive Prologue members and directives. Assuming |pn| is a + * candidate for membership in a directive prologue, recognize directives and + * set |pc|'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. + */ +template +bool +Parser::maybeParseDirective(Node list, Node pn, bool *cont) +{ + TokenPos directivePos; + JSAtom *directive = handler.isStringExprStatement(pn, &directivePos); + + *cont = !!directive; + if (!*cont) + return true; + + if (IsEscapeFreeStringLiteral(directivePos, directive)) { + // Mark this statement as being a possibly legitimate part of a + // directive prologue, so the bytecode 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. + handler.setPrologue(pn); + + if (directive == context->names().useStrict) { + // We're going to be in strict mode. Note that this scope explicitly + // had "use strict"; + pc->sc->setExplicitUseStrict(); + if (!pc->sc->strict) { + if (pc->sc->isFunctionBox()) { + // Request that this function be reparsed as strict. + pc->newDirectives->setStrict(); + return false; + } else { + // We don't reparse global scopes, so we keep track of the + // one possible strict violation that could occur in the + // directive prologue -- octal escapes -- and complain now. + if (tokenStream.sawOctalEscape()) { + report(ParseError, false, null(), JSMSG_DEPRECATED_OCTAL); + return false; + } + pc->sc->strict = true; + } + } + } else if (directive == context->names().useAsm) { + if (pc->sc->isFunctionBox()) + return asmJS(list); + return report(ParseWarning, false, pn, JSMSG_USE_ASM_DIRECTIVE_FAIL); + } + } + return true; +} + +/* + * Parse the statements in a block, creating a StatementList node that lists + * the statements. If called from block-parsing code, the caller must match + * '{' before and '}' after. + */ +template +typename ParseHandler::Node +Parser::statements() +{ + JS_CHECK_RECURSION(context, return null()); + + Node pn = handler.newStatementList(pc->blockid(), pos()); + if (!pn) + return null(); + + Node saveBlock = pc->blockNode; + pc->blockNode = pn; + + bool canHaveDirectives = pc->atBodyLevel(); + for (;;) { + TokenKind tt = tokenStream.peekToken(TokenStream::Operand); + if (tt <= TOK_EOF || tt == TOK_RC) { + if (tt == TOK_ERROR) { + if (tokenStream.isEOF()) + isUnexpectedEOF_ = true; + return null(); + } + break; + } + Node next = statement(canHaveDirectives); + if (!next) { + if (tokenStream.isEOF()) + isUnexpectedEOF_ = true; + return null(); + } + + if (canHaveDirectives) { + if (!maybeParseDirective(pn, next, &canHaveDirectives)) + return null(); + } + + handler.addStatementToList(pn, next, pc); + } + + /* + * Handle the case where there was a let declaration under this block. If + * it replaced pc->blockNode with a new block node then we must refresh pn + * and then restore pc->blockNode. + */ + if (pc->blockNode != pn) + pn = pc->blockNode; + pc->blockNode = saveBlock; + return pn; +} + +template +typename ParseHandler::Node +Parser::condition() +{ + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); + Node pn = exprInParens(); + if (!pn) + return null(); + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); + + /* Check for (a = b) and warn about possible (a == b) mistype. */ + if (handler.isOperationWithoutParens(pn, PNK_ASSIGN) && + !report(ParseExtraWarning, false, null(), JSMSG_EQUAL_AS_ASSIGN)) + { + return null(); + } + return pn; +} + +template +bool +Parser::matchLabel(MutableHandle label) +{ + TokenKind tt = tokenStream.peekTokenSameLine(TokenStream::Operand); + if (tt == TOK_ERROR) + return false; + if (tt == TOK_NAME) { + tokenStream.consumeKnownToken(TOK_NAME); + label.set(tokenStream.currentName()); + } else if (tt == TOK_YIELD) { + tokenStream.consumeKnownToken(TOK_YIELD); + if (!checkYieldNameValidity()) + return false; + label.set(tokenStream.currentName()); + } else { + label.set(nullptr); + } + return true; +} + +template +bool +Parser::reportRedeclaration(Node pn, bool isConst, JSAtom *atom) +{ + JSAutoByteString name; + if (AtomToPrintableString(context, atom, &name)) + report(ParseError, false, pn, JSMSG_REDECLARED_VAR, isConst ? "const" : "variable", name.ptr()); + return false; +} + +/* + * Define a let-variable in a block, let-expression, or comprehension scope. pc + * 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, pc->staticScope; + * populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to + * data->pn in a slot of the block object. + */ +template <> +/* static */ bool +Parser::bindLet(BindData *data, + HandlePropertyName name, Parser *parser) +{ + ParseContext *pc = parser->pc; + ParseNode *pn = data->pn; + if (!parser->checkStrictBinding(name, pn)) + return false; + + ExclusiveContext *cx = parser->context; + + Rooted blockObj(cx, data->let.blockObj); + unsigned index = blockObj->numVariables(); + if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) { + parser->report(ParseError, false, pn, data->let.overflow); + 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. + */ + if (!pn->pn_cookie.set(parser->tokenStream, pc->staticLevel, index)) + return false; + + /* + * For bindings that are hoisted to the beginning of the block/function, + * define() right now. Otherwise, delay define until PushLetScope. + */ + if (data->let.varContext == HoistVars) { + JS_ASSERT(!pc->atBodyLevel()); + Definition *dn = pc->decls().lookupFirst(name); + if (dn && dn->pn_blockid == pc->blockid()) + return parser->reportRedeclaration(pn, dn->isConst(), name); + if (!pc->define(parser->tokenStream, name, pn, Definition::LET)) + return false; + } + + bool redeclared; + RootedId id(cx, NameToId(name)); + RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, index, &redeclared)); + if (!shape) { + if (redeclared) + parser->reportRedeclaration(pn, false, name); + return false; + } + + /* Store pn in the static block object. */ + blockObj->setDefinitionParseNode(index, reinterpret_cast(pn)); + return true; +} + +template <> +/* static */ bool +Parser::bindLet(BindData *data, + HandlePropertyName name, Parser *parser) +{ + if (!parser->checkStrictBinding(name, data->pn)) + return false; + + return true; +} + +template +static inline bool +ForEachLetDef(TokenStream &ts, ParseContext *pc, + HandleStaticBlockObject blockObj, Op op) +{ + for (Shape::Range r(ts.context(), blockObj->lastProperty()); !r.empty(); r.popFront()) { + Shape &shape = r.front(); + + /* Beware the destructuring dummy slots. */ + if (JSID_IS_INT(shape.propid())) + continue; + + if (!op(ts, pc, blockObj, shape, JSID_TO_ATOM(shape.propid()))) + return false; + } + return true; +} + +template +struct PopLetDecl { + bool operator()(TokenStream &, ParseContext *pc, HandleStaticBlockObject, + const Shape &, JSAtom *atom) + { + pc->popLetDecl(atom); + return true; + } +}; + +// We compute the maximum block scope depth, in slots, of a compilation unit at +// parse-time. Each nested statement has a field indicating the maximum block +// scope depth that is nested inside it. When we leave a nested statement, we +// add the number of slots in the statement to the nested depth, and use that to +// update the maximum block scope depth of the outer statement or parse +// context. In the end, pc->blockScopeDepth will indicate the number of slots +// to reserve in the fixed part of a stack frame. +// +template +static void +AccumulateBlockScopeDepth(ParseContext *pc) +{ + uint32_t innerDepth = pc->topStmt->innerBlockScopeDepth; + StmtInfoPC *outer = pc->topStmt->down; + + if (pc->topStmt->isBlockScope) + innerDepth += pc->topStmt->staticScope->template as().numVariables(); + + if (outer) { + if (outer->innerBlockScopeDepth < innerDepth) + outer->innerBlockScopeDepth = innerDepth; + } else { + if (pc->blockScopeDepth < innerDepth) + pc->blockScopeDepth = innerDepth; + } +} + +template +static void +PopStatementPC(TokenStream &ts, ParseContext *pc) +{ + RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope); + JS_ASSERT(!!scopeObj == pc->topStmt->isNestedScope); + + AccumulateBlockScopeDepth(pc); + FinishPopStatement(pc); + + if (scopeObj) { + if (scopeObj->is()) { + RootedStaticBlockObject blockObj(ts.context(), &scopeObj->as()); + JS_ASSERT(!blockObj->inDictionaryMode()); + ForEachLetDef(ts, pc, blockObj, PopLetDecl()); + } + scopeObj->resetEnclosingNestedScopeFromParser(); + } +} + +/* + * The function LexicalLookup searches a static binding for the given name in + * the stack of statements enclosing the statement currently being parsed. Each + * statement that introduces a new scope has a corresponding scope object, on + * which the bindings for that scope are stored. LexicalLookup either returns + * the innermost statement which has a scope object containing a binding with + * the given name, or nullptr. + */ +template +typename ContextT::StmtInfo * +LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::StmtInfo *stmt) +{ + RootedId id(ct->sc->context, AtomToId(atom)); + + if (!stmt) + stmt = ct->topScopeStmt; + for (; stmt; stmt = stmt->downScope) { + /* + * With-statements introduce dynamic bindings. Since dynamic bindings + * can potentially override any static bindings introduced by statements + * further up the stack, we have to abort the search. + */ + if (stmt->type == STMT_WITH) + break; + + // Skip statements that do not introduce a new scope + if (!stmt->isBlockScope) + continue; + + StaticBlockObject &blockObj = stmt->staticBlock(); + Shape *shape = blockObj.nativeLookup(ct->sc->context, id); + if (shape) { + if (slotp) + *slotp = blockObj.shapeToIndex(*shape); + return stmt; + } + } + + if (slotp) + *slotp = -1; + return stmt; +} + +template +static inline bool +OuterLet(ParseContext *pc, StmtInfoPC *stmt, HandleAtom atom) +{ + while (stmt->downScope) { + stmt = LexicalLookup(pc, atom, nullptr, stmt->downScope); + if (!stmt) + return false; + if (stmt->type == STMT_BLOCK) + return true; + } + return false; +} + +template +/* static */ bool +Parser::bindVarOrConst(BindData *data, + HandlePropertyName name, Parser *parser) +{ + ExclusiveContext *cx = parser->context; + ParseContext *pc = parser->pc; + Node pn = data->pn; + bool isConstDecl = data->op == JSOP_DEFCONST; + + /* Default best op for pn is JSOP_NAME; we'll try to improve below. */ + parser->handler.setOp(pn, JSOP_NAME); + + if (!parser->checkStrictBinding(name, pn)) + return false; + + StmtInfoPC *stmt = LexicalLookup(pc, name, nullptr, (StmtInfoPC *)nullptr); + + if (stmt && stmt->type == STMT_WITH) { + parser->handler.setFlag(pn, PND_DEOPTIMIZED); + if (pc->sc->isFunctionBox()) { + FunctionBox *funbox = pc->sc->asFunctionBox(); + funbox->setMightAliasLocals(); + } + + /* + * This definition isn't being added to the parse context's + * declarations, so make sure to indicate the need to deoptimize + * the script's arguments object. Mark the function as if it + * contained a debugger statement, which will deoptimize arguments + * as much as possible. + */ + if (name == cx->names().arguments) + pc->sc->setHasDebuggerStatement(); + + return true; + } + + DefinitionList::Range defs = pc->decls().lookupMulti(name); + JS_ASSERT_IF(stmt, !defs.empty()); + + if (defs.empty()) { + return pc->define(parser->tokenStream, name, pn, + isConstDecl ? Definition::CONST : Definition::VAR); + } + + /* + * There was a previous declaration with the same name. The standard + * disallows several forms of redeclaration. Critically, + * let (x) { var x; } // error + * is not allowed which allows us to turn any non-error redeclaration + * into a use of the initial declaration. + */ + DefinitionNode dn = defs.front(); + Definition::Kind dn_kind = parser->handler.getDefinitionKind(dn); + if (dn_kind == Definition::ARG) { + JSAutoByteString bytes; + if (!AtomToPrintableString(cx, name, &bytes)) + return false; + + if (isConstDecl) { + parser->report(ParseError, false, pn, JSMSG_REDECLARED_PARAM, bytes.ptr()); + return false; + } + if (!parser->report(ParseExtraWarning, false, pn, JSMSG_VAR_HIDES_ARG, bytes.ptr())) + return false; + } else { + bool error = (isConstDecl || + dn_kind == Definition::CONST || + (dn_kind == Definition::LET && + (stmt->type != STMT_CATCH || OuterLet(pc, stmt, name)))); + + if (parser->options().extraWarningsOption + ? data->op != JSOP_DEFVAR || dn_kind != Definition::VAR + : error) + { + JSAutoByteString bytes; + ParseReportKind reporter = error ? ParseError : ParseExtraWarning; + if (!AtomToPrintableString(cx, name, &bytes) || + !parser->report(reporter, false, pn, JSMSG_REDECLARED_VAR, + Definition::kindString(dn_kind), bytes.ptr())) + { + return false; + } + } + } + + parser->handler.linkUseToDef(pn, dn); + return true; +} + +template <> +bool +Parser::makeSetCall(ParseNode *pn, unsigned msg) +{ + JS_ASSERT(pn->isKind(PNK_CALL)); + JS_ASSERT(pn->isArity(PN_LIST)); + JS_ASSERT(pn->isOp(JSOP_CALL) || pn->isOp(JSOP_SPREADCALL) || + pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_SPREADEVAL) || + pn->isOp(JSOP_FUNCALL) || pn->isOp(JSOP_FUNAPPLY)); + + if (!report(ParseStrictError, pc->sc->strict, pn, msg)) + return false; + handler.markAsSetCall(pn); + return true; +} + +template +bool +Parser::noteNameUse(HandlePropertyName name, Node pn) +{ + StmtInfoPC *stmt = LexicalLookup(pc, name, nullptr, (StmtInfoPC *)nullptr); + + DefinitionList::Range defs = pc->decls().lookupMulti(name); + + DefinitionNode dn; + if (!defs.empty()) { + dn = defs.front(); + } 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 = getOrCreateLexicalDependency(pc, name); + if (!dn) + return false; + } + + handler.linkUseToDef(pn, dn); + + if (stmt && stmt->type == STMT_WITH) + handler.setFlag(pn, PND_DEOPTIMIZED); + + return true; +} + +template <> +bool +Parser::bindDestructuringVar(BindData *data, ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_NAME)); + + RootedPropertyName name(context, pn->pn_atom->asPropertyName()); + + data->pn = pn; + if (!data->binder(data, name, this)) + return false; + + /* + * Select the appropriate name-setting opcode, respecting eager selection + * done by the data->binder function. + */ + if (pn->pn_dflags & PND_BOUND) + pn->setOp(JSOP_SETLOCAL); + else if (data->op == JSOP_DEFCONST) + pn->setOp(JSOP_SETCONST); + else + pn->setOp(JSOP_SETNAME); + + if (data->op == JSOP_DEFCONST) + pn->pn_dflags |= PND_CONST; + + pn->markAsAssigned(); + return 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 + * pc->inDeclDestructuring 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| nullptr (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 + * pc->inDeclDestructuring 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. + * + * The 'toplevel' is a private detail of the recursive strategy used by + * CheckDestructuring and callers should use the default value. + */ +template <> +bool +Parser::checkDestructuring(BindData *data, + ParseNode *left, bool toplevel) +{ + bool ok; + + if (left->isKind(PNK_ARRAYCOMP)) { + report(ParseError, false, left, JSMSG_ARRAY_COMP_LEFTSIDE); + return false; + } + + Rooted blockObj(context); + blockObj = data && data->binder == bindLet ? data->let.blockObj.get() : nullptr; + + if (left->isKind(PNK_ARRAY)) { + for (ParseNode *pn = left->pn_head; pn; pn = pn->pn_next) { + if (!pn->isKind(PNK_ELISION)) { + if (pn->isKind(PNK_ARRAY) || pn->isKind(PNK_OBJECT)) { + ok = checkDestructuring(data, pn, false); + } else { + if (data) { + if (!pn->isKind(PNK_NAME)) { + report(ParseError, false, pn, JSMSG_NO_VARIABLE_NAME); + return false; + } + ok = bindDestructuringVar(data, pn); + } else { + ok = checkAndMarkAsAssignmentLhs(pn, KeyedDestructuringAssignment); + } + } + if (!ok) + return false; + } + } + } else { + JS_ASSERT(left->isKind(PNK_OBJECT)); + for (ParseNode *member = left->pn_head; member; member = member->pn_next) { + MOZ_ASSERT(member->isKind(PNK_COLON)); + ParseNode *expr = member->pn_right; + + if (expr->isKind(PNK_ARRAY) || expr->isKind(PNK_OBJECT)) { + ok = checkDestructuring(data, expr, false); + } else if (data) { + if (!expr->isKind(PNK_NAME)) { + report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME); + return false; + } + ok = bindDestructuringVar(data, expr); + } else { + /* + * If this is a destructuring shorthand ({x} = ...), then + * identifierName wasn't used to parse |x|. As a result, |x| + * hasn't been officially linked to its def or registered in + * lexdeps. Do that now. + */ + if (member->pn_right == member->pn_left) { + RootedPropertyName name(context, expr->pn_atom->asPropertyName()); + if (!noteNameUse(name, expr)) + return false; + } + ok = checkAndMarkAsAssignmentLhs(expr, KeyedDestructuringAssignment); + } + if (!ok) + return false; + } + } + + return true; +} + +template <> +bool +Parser::checkDestructuring(BindData *data, + Node left, bool toplevel) +{ + return abortIfSyntaxParser(); +} + +template +typename ParseHandler::Node +Parser::destructuringExpr(BindData *data, TokenKind tt) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(tt)); + + pc->inDeclDestructuring = true; + Node pn = primaryExpr(tt); + pc->inDeclDestructuring = false; + if (!pn) + return null(); + if (!checkDestructuring(data, pn)) + return null(); + return pn; +} + +template +typename ParseHandler::Node +Parser::pushLexicalScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) +{ + JS_ASSERT(blockObj); + + ObjectBox *blockbox = newObjectBox(blockObj); + if (!blockbox) + return null(); + + PushStatementPC(pc, stmt, STMT_BLOCK); + blockObj->initEnclosingNestedScopeFromParser(pc->staticScope); + FinishPushNestedScope(pc, stmt, *blockObj.get()); + stmt->isBlockScope = true; + + Node pn = handler.newLexicalScope(blockbox); + if (!pn) + return null(); + + if (!GenerateBlockId(tokenStream, pc, stmt->blockid)) + return null(); + handler.setBlockId(pn, stmt->blockid); + return pn; +} + +template +typename ParseHandler::Node +Parser::pushLexicalScope(StmtInfoPC *stmt) +{ + RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); + if (!blockObj) + return null(); + + return pushLexicalScope(blockObj, stmt); +} + +struct AddLetDecl +{ + uint32_t blockid; + + AddLetDecl(uint32_t blockid) : blockid(blockid) {} + + bool operator()(TokenStream &ts, ParseContext *pc, + HandleStaticBlockObject blockObj, const Shape &shape, JSAtom *) + { + ParseNode *def = (ParseNode *) blockObj->getSlot(shape.slot()).toPrivate(); + def->pn_blockid = blockid; + RootedPropertyName name(ts.context(), def->name()); + return pc->define(ts, name, def, Definition::LET); + } +}; + +template <> +ParseNode * +Parser::pushLetScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) +{ + JS_ASSERT(blockObj); + ParseNode *pn = pushLexicalScope(blockObj, stmt); + if (!pn) + return null(); + + pn->pn_dflags |= PND_LET; + + /* Populate the new scope with decls found in the head with updated blockid. */ + if (!ForEachLetDef(tokenStream, pc, blockObj, AddLetDecl(stmt->blockid))) + return null(); + + return pn; +} + +template <> +SyntaxParseHandler::Node +Parser::pushLetScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt) +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +/* + * 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. + */ +template +typename ParseHandler::Node +Parser::letBlock(LetContext letContext) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LET)); + + RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); + if (!blockObj) + return null(); + + uint32_t begin = pos().begin; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_LET); + + Node vars = variables(PNK_LET, nullptr, blockObj, DontHoistVars); + if (!vars) + return null(); + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_LET); + + StmtInfoPC stmtInfo(context); + Node block = pushLetScope(blockObj, &stmtInfo); + if (!block) + return null(); + + Node pnlet = handler.newBinary(PNK_LET, vars, block); + if (!pnlet) + return null(); + handler.setBeginPosition(pnlet, begin); + + bool needExprStmt = false; + if (letContext == LetStatement && !tokenStream.matchToken(TOK_LC, TokenStream::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 (!report(ParseStrictError, pc->sc->strict, 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 PNK_LET node in a PNK_SEMI node so that we pop + * the return value of the expression. + */ + needExprStmt = true; + letContext = LetExpresion; + } + + Node expr; + if (letContext == LetStatement) { + expr = statements(); + if (!expr) + return null(); + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET); + } else { + JS_ASSERT(letContext == LetExpresion); + expr = assignExpr(); + if (!expr) + return null(); + } + handler.setLexicalScopeBody(block, expr); + PopStatementPC(tokenStream, pc); + + handler.setEndPosition(pnlet, pos().end); + + if (needExprStmt) { + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + return handler.newExprStatement(pnlet, pos().end); + } + return pnlet; +} + +template +static bool +PushBlocklikeStatement(TokenStream &ts, StmtInfoPC *stmt, StmtType type, + ParseContext *pc) +{ + PushStatementPC(pc, stmt, type); + return GenerateBlockId(ts, pc, stmt->blockid); +} + +template +typename ParseHandler::Node +Parser::blockStatement() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); + + StmtInfoPC stmtInfo(context); + if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_BLOCK, pc)) + return null(); + + Node list = statements(); + if (!list) + return null(); + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_COMPOUND); + PopStatementPC(tokenStream, pc); + return list; +} + +template +typename ParseHandler::Node +Parser::newBindingNode(PropertyName *name, bool functionScope, VarContext varContext) +{ + /* + * 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 (varContext == HoistVars) { + if (AtomDefnPtr p = pc->lexdeps->lookup(name)) { + DefinitionNode lexdep = p.value().get(); + JS_ASSERT(handler.getDefinitionKind(lexdep) == Definition::PLACEHOLDER); + + Node pn = handler.getDefinitionNode(lexdep); + if (handler.dependencyCovered(pn, pc->blockid(), functionScope)) { + handler.setBlockId(pn, pc->blockid()); + pc->lexdeps->remove(p); + handler.setPosition(pn, pos()); + return pn; + } + } + } + + /* Make a new node for this declarator name (or destructuring pattern). */ + return newName(name); +} + +/* + * 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. + */ +template +typename ParseHandler::Node +Parser::variables(ParseNodeKind kind, bool *psimple, + 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_CALL: We are parsing the head of a let block. + */ + JS_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || kind == PNK_CALL); + + /* + * The simple flag is set if the declaration has the form 'var x', with + * only one variable declared and no initializer expression. + */ + JS_ASSERT_IF(psimple, *psimple); + + JSOp op = blockObj ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST; + + Node pn = handler.newList(kind, null(), op); + if (!pn) + return null(); + + /* + * 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(context); + if (blockObj) + data.initLet(varContext, *blockObj, JSMSG_TOO_MANY_LOCALS); + else + data.initVarOrConst(op); + + bool first = true; + Node pn2; + do { + if (psimple && !first) + *psimple = false; + first = false; + + TokenKind tt = tokenStream.getToken(); + if (tt == TOK_LB || tt == TOK_LC) { + if (psimple) + *psimple = false; + + pc->inDeclDestructuring = true; + pn2 = primaryExpr(tt); + pc->inDeclDestructuring = false; + if (!pn2) + return null(); + + if (!checkDestructuring(&data, pn2)) + return null(); + bool ignored; + if (pc->parsingForInit && matchInOrOf(&ignored)) { + tokenStream.ungetToken(); + handler.addList(pn, pn2); + continue; + } + + MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL); + + Node init = assignExpr(); + if (!init) + return null(); + + pn2 = handler.newBinaryOrAppend(PNK_ASSIGN, pn2, init, pc); + if (!pn2) + return null(); + handler.addList(pn, pn2); + continue; + } + + if (tt != TOK_NAME) { + if (tt == TOK_YIELD) { + if (!checkYieldNameValidity()) + return null(); + } else { + if (tt != TOK_ERROR) + report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME); + return null(); + } + } + + RootedPropertyName name(context, tokenStream.currentName()); + pn2 = newBindingNode(name, kind == PNK_VAR || kind == PNK_CONST, varContext); + if (!pn2) + return null(); + if (data.op == JSOP_DEFCONST) + handler.setFlag(pn2, PND_CONST); + data.pn = pn2; + if (!data.binder(&data, name, this)) + return null(); + handler.addList(pn, pn2); + + if (tokenStream.matchToken(TOK_ASSIGN)) { + if (psimple) + *psimple = false; + + Node init = assignExpr(); + if (!init) + return null(); + + if (!handler.finishInitializerAssignment(pn2, init, data.op)) + return null(); + } + } while (tokenStream.matchToken(TOK_COMMA)); + + return pn; +} + +template <> +ParseNode * +Parser::letDeclaration() +{ + handler.disableSyntaxParser(); + + ParseNode *pn; + + do { + /* + * 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 + * StmtInfoPC 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 StmtInfoPC isn't yet a scope statement) then + * we also need to set pc->blockNode to be our PNK_LEXICALSCOPE. + */ + StmtInfoPC *stmt = pc->topStmt; + if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) { + report(ParseError, false, null(), JSMSG_LET_DECL_NOT_IN_BLOCK); + return null(); + } + + if (stmt && stmt->isBlockScope) { + JS_ASSERT(pc->staticScope == stmt->staticScope); + } else { + if (pc->atBodyLevel()) { + /* + * 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->isBlockScope); + JS_ASSERT(stmt != pc->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(context); + if (!blockObj) + return null(); + + ObjectBox *blockbox = newObjectBox(blockObj); + if (!blockbox) + return null(); + + /* + * Insert stmt on the pc->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->isBlockScope = stmt->isNestedScope = true; + stmt->downScope = pc->topScopeStmt; + pc->topScopeStmt = stmt; + + blockObj->initEnclosingNestedScopeFromParser(pc->staticScope); + pc->staticScope = blockObj; + stmt->staticScope = blockObj; + +#ifdef DEBUG + ParseNode *tmp = pc->blockNode; + JS_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE)); +#endif + + /* Create a new lexical scope node for these statements. */ + ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, &handler); + if (!pn1) + return null(); + + pn1->pn_pos = pc->blockNode->pn_pos; + pn1->pn_objbox = blockbox; + pn1->pn_expr = pc->blockNode; + pn1->pn_blockid = pc->blockNode->pn_blockid; + pc->blockNode = pn1; + } + + pn = variables(PNK_LET, nullptr, &pc->staticScope->as(), HoistVars); + if (!pn) + return null(); + pn->pn_xflags = PNX_POPVAR; + } while (0); + + return MatchOrInsertSemicolon(tokenStream) ? pn : nullptr; +} + +template <> +SyntaxParseHandler::Node +Parser::letDeclaration() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template <> +ParseNode * +Parser::letStatement() +{ + handler.disableSyntaxParser(); + + /* Check for a let statement or let expression. */ + ParseNode *pn; + if (tokenStream.peekToken() == TOK_LP) { + pn = letBlock(LetStatement); + JS_ASSERT_IF(pn, pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); + } else { + pn = letDeclaration(); + } + return pn; +} + +template <> +SyntaxParseHandler::Node +Parser::letStatement() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template +typename ParseHandler::Node +Parser::importDeclaration() +{ + JS_ASSERT(tokenStream.currentToken().type == TOK_IMPORT); + + if (pc->sc->isFunctionBox() || !pc->atBodyLevel()) { + report(ParseError, false, null(), JSMSG_IMPORT_DECL_AT_TOP_LEVEL); + return null(); + } + + uint32_t begin = pos().begin; + TokenKind tt = tokenStream.getToken(); + + Node importSpecSet = handler.newList(PNK_IMPORT_SPEC_LIST); + if (!importSpecSet) + return null(); + + if (tt == TOK_NAME || tt == TOK_LC) { + if (tt == TOK_NAME) { + // Handle the form |import a from 'b'|, by adding a single import + // specifier to the list, with 'default' as the import name and + // 'a' as the binding name. This is equivalent to + // |import { default as a } from 'b'|. + Node importName = newName(context->names().default_); + if (!importName) + return null(); + + Node bindingName = newName(tokenStream.currentName()); + if (!bindingName) + return null(); + + Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName); + if (!importSpec) + return null(); + + handler.addList(importSpecSet, importSpec); + } else { + do { + // Handle the forms |import {} from 'a'| and + // |import { ..., } from 'a'| (where ... is non empty), by + // escaping the loop early if the next token is }. + tt = tokenStream.peekToken(TokenStream::KeywordIsName); + if (tt == TOK_ERROR) + return null(); + if (tt == TOK_RC) + break; + + // If the next token is a keyword, the previous call to + // peekToken matched it as a TOK_NAME, and put it in the + // lookahead buffer, so this call will match keywords as well. + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_IMPORT_NAME); + Node importName = newName(tokenStream.currentName()); + if (!importName) + return null(); + + if (tokenStream.getToken() == TOK_NAME && + tokenStream.currentName() == context->names().as) + { + if (tokenStream.getToken() != TOK_NAME) { + report(ParseError, false, null(), JSMSG_NO_BINDING_NAME); + return null(); + } + } else { + // Keywords cannot be bound to themselves, so an import name + // that is a keyword is a syntax error if it is not followed + // by the keyword 'as'. + if (IsKeyword(importName->name())) { + JSAutoByteString bytes; + if (!AtomToPrintableString(context, importName->name(), &bytes)) + return null(); + report(ParseError, false, null(), JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr()); + return null(); + } + tokenStream.ungetToken(); + } + Node bindingName = newName(tokenStream.currentName()); + if (!bindingName) + return null(); + + Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName); + if (!importSpec) + return null(); + + handler.addList(importSpecSet, importSpec); + } while (tokenStream.matchToken(TOK_COMMA)); + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_IMPORT_SPEC_LIST); + } + + if (tokenStream.getToken() != TOK_NAME || + tokenStream.currentName() != context->names().from) + { + report(ParseError, false, null(), JSMSG_FROM_AFTER_IMPORT_SPEC_SET); + return null(); + } + + MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); + } else { + if (tt != TOK_STRING) { + report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_IMPORT); + return null(); + } + + // Handle the form |import 'a'| by leaving the list empty. This is + // equivalent to |import {} from 'a'|. + importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin; + } + + Node moduleSpec = stringLiteral(); + if (!moduleSpec) + return null(); + + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + + return handler.newImportDeclaration(importSpecSet, moduleSpec, + TokenPos(begin, pos().end)); +} + +template<> +SyntaxParseHandler::Node +Parser::importDeclaration() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +template +typename ParseHandler::Node +Parser::exportDeclaration() +{ + JS_ASSERT(tokenStream.currentToken().type == TOK_EXPORT); + + if (pc->sc->isFunctionBox() || !pc->atBodyLevel()) { + report(ParseError, false, null(), JSMSG_EXPORT_DECL_AT_TOP_LEVEL); + return null(); + } + + uint32_t begin = pos().begin; + + Node kid; + switch (TokenKind tt = tokenStream.getToken()) { + case TOK_LC: + case TOK_MUL: + kid = handler.newList(PNK_EXPORT_SPEC_LIST); + if (!kid) + return null(); + + if (tt == TOK_LC) { + do { + // Handle the forms |export {}| and |export { ..., }| (where ... + // is non empty), by escaping the loop early if the next token + // is }. + tt = tokenStream.peekToken(); + if (tt == TOK_ERROR) + return null(); + if (tt == TOK_RC) + break; + + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME); + Node bindingName = newName(tokenStream.currentName()); + if (!bindingName) + return null(); + + if (tokenStream.getToken() == TOK_NAME && + tokenStream.currentName() == context->names().as) + { + if (tokenStream.getToken(TokenStream::KeywordIsName) != TOK_NAME) { + report(ParseError, false, null(), JSMSG_NO_EXPORT_NAME); + return null(); + } + } else { + tokenStream.ungetToken(); + } + Node exportName = newName(tokenStream.currentName()); + if (!exportName) + return null(); + + Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName); + if (!exportSpec) + return null(); + + handler.addList(kid, exportSpec); + } while (tokenStream.matchToken(TOK_COMMA)); + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST); + } else { + // Handle the form |export *| by adding a special export batch + // specifier to the list. + Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos()); + if (!kid) + return null(); + + handler.addList(kid, exportSpec); + } + if (tokenStream.getToken() == TOK_NAME && + tokenStream.currentName() == context->names().from) + { + MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM); + + Node moduleSpec = stringLiteral(); + if (!moduleSpec) + return null(); + + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + + return handler.newExportFromDeclaration(begin, kid, moduleSpec); + } else { + tokenStream.ungetToken(); + } + + kid = MatchOrInsertSemicolon(tokenStream) ? kid : nullptr; + if (!kid) + return null(); + break; + + case TOK_FUNCTION: + kid = functionStmt(); + if (!kid) + return null(); + break; + + case TOK_VAR: + case TOK_CONST: + kid = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST); + if (!kid) + return null(); + kid->pn_xflags = PNX_POPVAR; + + kid = MatchOrInsertSemicolon(tokenStream) ? kid : nullptr; + if (!kid) + return null(); + break; + + case TOK_NAME: + // Handle the form |export a} in the same way as |export let a|, by + // acting as if we've just seen the let keyword. Simply unget the token + // and fall through. + tokenStream.ungetToken(); + case TOK_LET: + kid = letDeclaration(); + if (!kid) + return null(); + break; + + default: + report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_EXPORT); + return null(); + } + + return handler.newExportDeclaration(kid, TokenPos(begin, pos().end)); +} + +template<> +SyntaxParseHandler::Node +Parser::exportDeclaration() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + + +template +typename ParseHandler::Node +Parser::expressionStatement() +{ + tokenStream.ungetToken(); + Node pnexpr = expr(); + if (!pnexpr) + return null(); + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + return handler.newExprStatement(pnexpr, pos().end); +} + +template +typename ParseHandler::Node +Parser::ifStatement() +{ + uint32_t begin = pos().begin; + + /* An IF node has three kids: condition, then, and optional else. */ + Node cond = condition(); + if (!cond) + return null(); + + if (tokenStream.peekToken(TokenStream::Operand) == TOK_SEMI && + !report(ParseExtraWarning, false, null(), JSMSG_EMPTY_CONSEQUENT)) + { + return null(); + } + + StmtInfoPC stmtInfo(context); + PushStatementPC(pc, &stmtInfo, STMT_IF); + Node thenBranch = statement(); + if (!thenBranch) + return null(); + + Node elseBranch; + if (tokenStream.matchToken(TOK_ELSE, TokenStream::Operand)) { + stmtInfo.type = STMT_ELSE; + elseBranch = statement(); + if (!elseBranch) + return null(); + } else { + elseBranch = null(); + } + + PopStatementPC(tokenStream, pc); + return handler.newIfStatement(begin, cond, thenBranch, elseBranch); +} + +template +typename ParseHandler::Node +Parser::doWhileStatement() +{ + uint32_t begin = pos().begin; + StmtInfoPC stmtInfo(context); + PushStatementPC(pc, &stmtInfo, STMT_DO_LOOP); + Node body = statement(); + if (!body) + return null(); + MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO); + Node cond = condition(); + if (!cond) + return null(); + PopStatementPC(tokenStream, pc); + + if (versionNumber() == JSVERSION_ECMA_3) { + // Pedantically require a semicolon or line break, following ES3. + // Bug 880329 proposes removing this case. + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + } else { + // The semicolon after do-while is even more optional than most + // semicolons in JS. Web compat required this by 2004: + // http://bugzilla.mozilla.org/show_bug.cgi?id=238945 + // ES3 and ES5 disagreed, but ES6 conforms to Web reality: + // https://bugs.ecmascript.org/show_bug.cgi?id=157 + (void) tokenStream.matchToken(TOK_SEMI); + } + + return handler.newDoWhileStatement(body, cond, TokenPos(begin, pos().end)); +} + +template +typename ParseHandler::Node +Parser::whileStatement() +{ + uint32_t begin = pos().begin; + StmtInfoPC stmtInfo(context); + PushStatementPC(pc, &stmtInfo, STMT_WHILE_LOOP); + Node cond = condition(); + if (!cond) + return null(); + Node body = statement(); + if (!body) + return null(); + PopStatementPC(tokenStream, pc); + return handler.newWhileStatement(begin, cond, body); +} + +template +bool +Parser::matchInOrOf(bool *isForOfp) +{ + if (tokenStream.matchToken(TOK_IN)) { + *isForOfp = false; + return true; + } + if (tokenStream.matchContextualKeyword(context->names().of)) { + *isForOfp = true; + return true; + } + return false; +} + +template <> +bool +Parser::isValidForStatementLHS(ParseNode *pn1, JSVersion version, + bool isForDecl, bool isForEach, + ParseNodeKind headKind) +{ + if (isForDecl) { + if (pn1->pn_count > 1) + return false; + if (pn1->isOp(JSOP_DEFCONST)) + return false; + + // In JS 1.7 only, for (var [K, V] in EXPR) has a special meaning. + // Hence all other destructuring decls are banned there. + if (version == JSVERSION_1_7 && !isForEach && headKind == PNK_FORIN) { + ParseNode *lhs = pn1->pn_head; + if (lhs->isKind(PNK_ASSIGN)) + lhs = lhs->pn_left; + + if (lhs->isKind(PNK_OBJECT)) + return false; + if (lhs->isKind(PNK_ARRAY) && lhs->pn_count != 2) + return false; + } + return true; + } + + switch (pn1->getKind()) { + case PNK_NAME: + case PNK_DOT: + case PNK_CALL: + case PNK_ELEM: + return true; + + case PNK_ARRAY: + case PNK_OBJECT: + // In JS 1.7 only, for ([K, V] in EXPR) has a special meaning. + // Hence all other destructuring left-hand sides are banned there. + if (version == JSVERSION_1_7 && !isForEach && headKind == PNK_FORIN) + return pn1->isKind(PNK_ARRAY) && pn1->pn_count == 2; + return true; + + default: + return false; + } +} + +template <> +ParseNode * +Parser::forStatement() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + uint32_t begin = pos().begin; + + StmtInfoPC forStmt(context); + PushStatementPC(pc, &forStmt, STMT_FOR_LOOP); + + bool isForEach = false; + unsigned iflags = 0; + + if (allowsForEachIn() && tokenStream.matchContextualKeyword(context->names().each)) { + iflags = JSITER_FOREACH; + isForEach = true; + } + + 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 isForDecl = false; + + /* Non-null when isForDecl is true for a 'for (let ...)' statement. */ + RootedStaticBlockObject blockObj(context); + + /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ + ParseNode *pn1; + + { + TokenKind tt = tokenStream.peekToken(TokenStream::Operand); + if (tt == TOK_SEMI) { + pn1 = nullptr; + } else { + /* + * Set pn1 to a var list or an initializing expression. + * + * Set the parsingForInit 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. + */ + pc->parsingForInit = true; + if (tt == TOK_VAR || tt == TOK_CONST) { + isForDecl = true; + tokenStream.consumeKnownToken(tt); + pn1 = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST); + } + else if (tt == TOK_LET) { + handler.disableSyntaxParser(); + (void) tokenStream.getToken(); + if (tokenStream.peekToken() == TOK_LP) { + pn1 = letBlock(LetExpresion); + } else { + isForDecl = true; + blockObj = StaticBlockObject::create(context); + if (!blockObj) + return null(); + pn1 = variables(PNK_LET, nullptr, blockObj, DontHoistVars); + } + } + else { + pn1 = expr(); + } + pc->parsingForInit = false; + if (!pn1) + return null(); + } + } + + JS_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST)); + JS_ASSERT(!!blockObj == (isForDecl && pn1->isOp(JSOP_NOP))); + + // The form 'for (let ; ; ) ' generates an + // implicit block even if stmt is not a BlockStatement. + // If the loop has that exact form, then: + // - forLetImpliedBlock is the node for the implicit block scope. + // - forLetDecl is the node for the decl 'let '. + // Otherwise both are null. + ParseNode *forLetImpliedBlock = nullptr; + ParseNode *forLetDecl = nullptr; + + // If non-null, the node for the decl 'var v = expr1' in the weirdo form + // 'for (var v = expr1 in expr2) stmt'. + ParseNode *hoistedVar = nullptr; + + /* + * 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 + * pc->parsingForInit. + */ + StmtInfoPC letStmt(context); /* used if blockObj != nullptr. */ + ParseNode *pn2, *pn3; /* forHead->pn_kid2 and pn_kid3. */ + ParseNodeKind headKind = PNK_FORHEAD; + if (pn1) { + bool isForOf; + if (matchInOrOf(&isForOf)) + headKind = isForOf ? PNK_FOROF : PNK_FORIN; + } + + if (headKind == PNK_FOROF || headKind == PNK_FORIN) { + /* + * 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 nullptr, pn2 is the assignment target + * that receives the enumeration value each iteration, and pn3 is the + * rhs of 'in'. + */ + if (headKind == PNK_FOROF) { + forStmt.type = STMT_FOR_OF_LOOP; + forStmt.type = (headKind == PNK_FOROF) ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP; + if (isForEach) { + report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP); + return null(); + } + } else { + forStmt.type = STMT_FOR_IN_LOOP; + iflags |= JSITER_ENUMERATE; + } + + /* Check that the left side of the 'in' or 'of' is valid. */ + if (!isValidForStatementLHS(pn1, versionNumber(), isForDecl, isForEach, headKind)) { + report(ParseError, false, pn1, 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 nullptr. Note that the "declaration with initializer" case + * rewrites the loop-head, moving the decl and setting pn1 to nullptr. + */ + if (isForDecl) { + pn2 = pn1->pn_head; + if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr()) || pn2->isKind(PNK_ASSIGN)) { + /* + * 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 (headKind == PNK_FOROF) { + report(ParseError, false, pn2, JSMSG_INVALID_FOR_OF_INIT); + return null(); + } + if (blockObj) { + report(ParseError, false, pn2, JSMSG_INVALID_FOR_IN_INIT); + return null(); + } + + hoistedVar = pn1; + + /* + * All of 'var x = i' is hoisted above 'for (x in o)'. + * + * 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_POPVAR; + pn1 = nullptr; + + if (pn2->isKind(PNK_ASSIGN)) { + pn2 = pn2->pn_left; + JS_ASSERT(pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT) || + pn2->isKind(PNK_NAME)); + } + } + } else { + /* Not a declaration. */ + JS_ASSERT(!blockObj); + pn2 = pn1; + pn1 = nullptr; + + if (!checkAndMarkAsAssignmentLhs(pn2, PlainAssignment)) + return null(); + } + + pn3 = (headKind == PNK_FOROF) ? assignExpr() : 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 PNK_LEXICALSCOPE node + * created by PushLetScope around the for's initializer. This also + * serves to indicate the let-decl to the emitter. + */ + ParseNode *block = pushLetScope(blockObj, &letStmt); + if (!block) + return null(); + letStmt.isForLetBlock = true; + block->pn_expr = pn1; + block->pn_pos = pn1->pn_pos; + pn1 = block; + } + + if (isForDecl) { + /* + * pn2 is part of a declaration. Make a copy that can be passed to + * EmitAssignment. Take care to do this after PushLetScope. + */ + pn2 = cloneLeftHandSide(pn2); + if (!pn2) + return null(); + } + + switch (pn2->getKind()) { + case PNK_NAME: + /* Beware 'for (arguments in ...)' with or without a 'var'. */ + pn2->markAsAssigned(); + break; + + case PNK_ASSIGN: + MOZ_ASSUME_UNREACHABLE("forStatement TOK_ASSIGN"); + + case PNK_ARRAY: + case PNK_OBJECT: + if (versionNumber() == JSVERSION_1_7) { + /* + * Destructuring for-in requires [key, value] enumeration + * in JS1.7. + */ + if (!isForEach && headKind == PNK_FORIN) + iflags |= JSITER_FOREACH | JSITER_KEYVALUE; + } + break; + + default:; + } + } else { + if (isForEach) { + reportWithOffset(ParseError, false, begin, JSMSG_BAD_FOR_EACH_LOOP); + return null(); + } + + headKind = PNK_FORHEAD; + + if (blockObj) { + /* + * Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }' + * to induce the correct scoping for A. + */ + forLetImpliedBlock = pushLetScope(blockObj, &letStmt); + if (!forLetImpliedBlock) + return null(); + letStmt.isForLetBlock = true; + + forLetDecl = pn1; + pn1 = nullptr; + } + + /* Parse the loop condition or null into pn2. */ + MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); + if (tokenStream.peekToken(TokenStream::Operand) == TOK_SEMI) { + pn2 = nullptr; + } 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(TokenStream::Operand) == TOK_RP) { + pn3 = nullptr; + } else { + pn3 = expr(); + if (!pn3) + return null(); + } + } + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); + + TokenPos headPos(begin, pos().end); + ParseNode *forHead = handler.newForHead(headKind, pn1, pn2, pn3, headPos); + if (!forHead) + return null(); + + /* Parse the loop body. */ + ParseNode *body = statement(); + if (!body) + return null(); + + if (blockObj) + PopStatementPC(tokenStream, pc); + PopStatementPC(tokenStream, pc); + + ParseNode *forLoop = handler.newForStatement(begin, forHead, body, iflags); + if (!forLoop) + return null(); + + if (hoistedVar) { + ParseNode *pnseq = handler.newList(PNK_SEQ, hoistedVar); + if (!pnseq) + return null(); + pnseq->pn_pos = forLoop->pn_pos; + pnseq->append(forLoop); + return pnseq; + } + if (forLetImpliedBlock) { + forLetImpliedBlock->pn_expr = forLoop; + forLetImpliedBlock->pn_pos = forLoop->pn_pos; + ParseNode *let = handler.newBinary(PNK_LET, forLetDecl, forLetImpliedBlock); + if (!let) + return null(); + let->pn_pos = forLoop->pn_pos; + return let; + } + return forLoop; +} + +template <> +SyntaxParseHandler::Node +Parser::forStatement() +{ + /* + * 'for' statement parsing is fantastically complicated and requires being + * able to inspect the parse tree for previous parts of the 'for'. Syntax + * parsing of 'for' statements is thus done separately, and only handles + * the types of 'for' statements likely to be seen in web content. + */ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + + StmtInfoPC forStmt(context); + PushStatementPC(pc, &forStmt, STMT_FOR_LOOP); + + /* Don't parse 'for each' loops. */ + if (allowsForEachIn()) { + TokenKind tt = tokenStream.peekToken(); + // Not all "yield" tokens are names, but the ones that aren't names are + // invalid in this context anyway. + if (tt == TOK_NAME || tt == TOK_YIELD) { + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return null(); + } + } + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + + /* True if we have 'for (var ...)'. */ + bool isForDecl = false; + bool simpleForDecl = true; + + /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ + Node lhsNode; + + { + TokenKind tt = tokenStream.peekToken(TokenStream::Operand); + if (tt == TOK_SEMI) { + lhsNode = null(); + } else { + /* Set lhsNode to a var list or an initializing expression. */ + pc->parsingForInit = true; + if (tt == TOK_VAR) { + isForDecl = true; + tokenStream.consumeKnownToken(tt); + lhsNode = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST, &simpleForDecl); + } + else if (tt == TOK_CONST || tt == TOK_LET) { + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return null(); + } + else { + lhsNode = expr(); + } + if (!lhsNode) + return null(); + pc->parsingForInit = false; + } + } + + /* + * 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 + * pc->parsingForInit. + */ + bool isForOf; + if (lhsNode && matchInOrOf(&isForOf)) { + /* Parse the rest of the for/in or for/of head. */ + forStmt.type = isForOf ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP; + + /* Check that the left side of the 'in' or 'of' is valid. */ + if (!isForDecl && + lhsNode != SyntaxParseHandler::NodeName && + lhsNode != SyntaxParseHandler::NodeGetProp && + lhsNode != SyntaxParseHandler::NodeLValue) + { + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return null(); + } + + if (!simpleForDecl) { + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return null(); + } + + if (!isForDecl && !checkAndMarkAsAssignmentLhs(lhsNode, PlainAssignment)) + return null(); + + if (!expr()) + return null(); + } else { + /* Parse the loop condition or null. */ + MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); + if (tokenStream.peekToken(TokenStream::Operand) != TOK_SEMI) { + if (!expr()) + return null(); + } + + /* Parse the update expression or null. */ + MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND); + if (tokenStream.peekToken(TokenStream::Operand) != TOK_RP) { + if (!expr()) + return null(); + } + } + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); + + /* Parse the loop body. */ + if (!statement()) + return null(); + + PopStatementPC(tokenStream, pc); + return SyntaxParseHandler::NodeGeneric; +} + +template +typename ParseHandler::Node +Parser::switchStatement() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_SWITCH)); + uint32_t begin = pos().begin; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); + + Node discriminant = exprInParens(); + if (!discriminant) + return null(); + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH); + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH); + + StmtInfoPC stmtInfo(context); + PushStatementPC(pc, &stmtInfo, STMT_SWITCH); + + if (!GenerateBlockId(tokenStream, pc, pc->topStmt->blockid)) + return null(); + + Node caseList = handler.newStatementList(pc->blockid(), pos()); + if (!caseList) + return null(); + + Node saveBlock = pc->blockNode; + pc->blockNode = caseList; + + bool seenDefault = false; + TokenKind tt; + while ((tt = tokenStream.getToken()) != TOK_RC) { + uint32_t caseBegin = pos().begin; + + Node caseExpr; + switch (tt) { + case TOK_DEFAULT: + if (seenDefault) { + report(ParseError, false, null(), JSMSG_TOO_MANY_DEFAULTS); + return null(); + } + seenDefault = true; + caseExpr = null(); // The default case has pn_left == nullptr. + break; + + case TOK_CASE: + caseExpr = expr(); + if (!caseExpr) + return null(); + break; + + case TOK_ERROR: + return null(); + + default: + report(ParseError, false, null(), JSMSG_BAD_SWITCH); + return null(); + } + + MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE); + + Node body = handler.newStatementList(pc->blockid(), pos()); + if (!body) + return null(); + + while ((tt = tokenStream.peekToken(TokenStream::Operand)) != TOK_RC && + tt != TOK_CASE && tt != TOK_DEFAULT) { + if (tt == TOK_ERROR) + return null(); + Node stmt = statement(); + if (!stmt) + return null(); + handler.addList(body, stmt); + } + + Node casepn = handler.newCaseOrDefault(caseBegin, caseExpr, body); + if (!casepn) + return null(); + handler.addList(caseList, casepn); + } + + /* + * 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 + * pc->blockNode with a new block node then we must refresh caseList and + * then restore pc->blockNode. + */ + if (pc->blockNode != caseList) + caseList = pc->blockNode; + pc->blockNode = saveBlock; + + PopStatementPC(tokenStream, pc); + + handler.setEndPosition(caseList, pos().end); + + return handler.newSwitchStatement(begin, discriminant, caseList); +} + +template +typename ParseHandler::Node +Parser::continueStatement() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_CONTINUE)); + uint32_t begin = pos().begin; + + RootedPropertyName label(context); + if (!matchLabel(&label)) + return null(); + + StmtInfoPC *stmt = pc->topStmt; + if (label) { + for (StmtInfoPC *stmt2 = nullptr; ; stmt = stmt->down) { + if (!stmt) { + report(ParseError, false, null(), JSMSG_LABEL_NOT_FOUND); + return null(); + } + if (stmt->type == STMT_LABEL) { + if (stmt->label == label) { + if (!stmt2 || !stmt2->isLoop()) { + report(ParseError, false, null(), JSMSG_BAD_CONTINUE); + return null(); + } + break; + } + } else { + stmt2 = stmt; + } + } + } else { + for (; ; stmt = stmt->down) { + if (!stmt) { + report(ParseError, false, null(), JSMSG_BAD_CONTINUE); + return null(); + } + if (stmt->isLoop()) + break; + } + } + + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + + return handler.newContinueStatement(label, TokenPos(begin, pos().end)); +} + +template +typename ParseHandler::Node +Parser::breakStatement() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_BREAK)); + uint32_t begin = pos().begin; + + RootedPropertyName label(context); + if (!matchLabel(&label)) + return null(); + StmtInfoPC *stmt = pc->topStmt; + if (label) { + for (; ; stmt = stmt->down) { + if (!stmt) { + report(ParseError, false, null(), JSMSG_LABEL_NOT_FOUND); + return null(); + } + if (stmt->type == STMT_LABEL && stmt->label == label) + break; + } + } else { + for (; ; stmt = stmt->down) { + if (!stmt) { + report(ParseError, false, null(), JSMSG_TOUGH_BREAK); + return null(); + } + if (stmt->isLoop() || stmt->type == STMT_SWITCH) + break; + } + } + + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + + return handler.newBreakStatement(label, TokenPos(begin, pos().end)); +} + +template +typename ParseHandler::Node +Parser::returnStatement() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_RETURN)); + uint32_t begin = pos().begin; + + if (!pc->sc->isFunctionBox()) { + report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_return_str); + return null(); + } + + // Parse an optional operand. + // + // This is ugly, but we don't want to require a semicolon. + Node exprNode; + switch (tokenStream.peekTokenSameLine(TokenStream::Operand)) { + case TOK_ERROR: + return null(); + case TOK_EOF: + case TOK_EOL: + case TOK_SEMI: + case TOK_RC: + exprNode = null(); + pc->funHasReturnVoid = true; + break; + default: { + exprNode = expr(); + if (!exprNode) + return null(); + pc->funHasReturnExpr = true; + } + } + + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + + Node pn = handler.newReturnStatement(exprNode, TokenPos(begin, pos().end)); + if (!pn) + return null(); + + if (options().extraWarningsOption && pc->funHasReturnExpr && pc->funHasReturnVoid && + !reportBadReturn(pn, ParseExtraWarning, + JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE)) + { + return null(); + } + + if (pc->isLegacyGenerator() && exprNode) { + /* Disallow "return v;" in legacy generators. */ + reportBadReturn(pn, ParseError, JSMSG_BAD_GENERATOR_RETURN, + JSMSG_BAD_ANON_GENERATOR_RETURN); + return null(); + } + + return pn; +} + +template +typename ParseHandler::Node +Parser::yieldExpression() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_YIELD)); + uint32_t begin = pos().begin; + + switch (pc->generatorKind()) { + case StarGenerator: + { + JS_ASSERT(pc->sc->isFunctionBox()); + + pc->lastYieldOffset = begin; + + ParseNodeKind kind = tokenStream.matchToken(TOK_MUL) ? PNK_YIELD_STAR : PNK_YIELD; + + // ES6 generators require a value. + Node exprNode = assignExpr(); + if (!exprNode) + return null(); + + return handler.newUnary(kind, JSOP_NOP, begin, exprNode); + } + + case NotGenerator: + // We are in code that has not seen a yield, but we are in JS 1.7 or + // later. Try to transition to being a legacy generator. + JS_ASSERT(tokenStream.versionNumber() >= JSVERSION_1_7); + JS_ASSERT(pc->lastYieldOffset == ParseContext::NoYieldOffset); + + if (!abortIfSyntaxParser()) + return null(); + + if (!pc->sc->isFunctionBox()) { + report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_yield_str); + return null(); + } + + pc->sc->asFunctionBox()->setGeneratorKind(LegacyGenerator); + + if (pc->funHasReturnExpr) { + /* As in Python (see PEP-255), disallow return v; in generators. */ + reportBadReturn(null(), ParseError, JSMSG_BAD_GENERATOR_RETURN, + JSMSG_BAD_ANON_GENERATOR_RETURN); + return null(); + } + // Fall through. + + case LegacyGenerator: + { + // We are in a legacy generator: a function that has already seen a + // yield, or in a legacy generator comprehension. + JS_ASSERT(pc->sc->isFunctionBox()); + + pc->lastYieldOffset = begin; + + // Legacy generators do not require a value. + Node exprNode; + switch (tokenStream.peekTokenSameLine(TokenStream::Operand)) { + case TOK_ERROR: + return null(); + case TOK_EOF: + case TOK_EOL: + case TOK_SEMI: + case TOK_RC: + case TOK_RB: + case TOK_RP: + case TOK_COLON: + case TOK_COMMA: + // No value. + exprNode = null(); + // ES6 does not permit yield without an operand. We should + // encourage users of yield expressions of this kind to pass an + // operand, to bring users closer to standard syntax. + if (!reportWithOffset(ParseWarning, false, pos().begin, JSMSG_YIELD_WITHOUT_OPERAND)) + return null(); + break; + default: + exprNode = assignExpr(); + if (!exprNode) + return null(); + } + + return handler.newUnary(PNK_YIELD, JSOP_NOP, begin, exprNode); + } + } + + MOZ_ASSUME_UNREACHABLE("yieldExpr"); +} + +template <> +ParseNode * +Parser::withStatement() +{ + // test262/ch12/12.10/12.10-0-1.js fails if we try to parse with-statements + // in syntax-parse mode. See bug 892583. + if (handler.syntaxParser) { + handler.disableSyntaxParser(); + abortedSyntaxParse = true; + return null(); + } + + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_WITH)); + uint32_t begin = pos().begin; + + // In most cases, we want the constructs forbidden in strict mode code to be + // a subset of those that JSOPTION_EXTRA_WARNINGS 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_EXTRA_WARNINGS. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=514576#c1. + if (pc->sc->strict && !report(ParseStrictError, true, null(), JSMSG_STRICT_CODE_WITH)) + return null(); + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); + Node objectExpr = exprInParens(); + if (!objectExpr) + return null(); + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); + + bool oldParsingWith = pc->parsingWith; + pc->parsingWith = true; + + StmtInfoPC stmtInfo(context); + PushStatementPC(pc, &stmtInfo, STMT_WITH); + Rooted staticWith(context, StaticWithObject::create(context)); + if (!staticWith) + return null(); + staticWith->initEnclosingNestedScopeFromParser(pc->staticScope); + FinishPushNestedScope(pc, &stmtInfo, *staticWith); + + Node innerBlock = statement(); + if (!innerBlock) + return null(); + + PopStatementPC(tokenStream, pc); + + pc->sc->setBindingsAccessedDynamically(); + pc->parsingWith = oldParsingWith; + + /* + * Make sure to deoptimize lexical dependencies inside the |with| + * to safely optimize binding globals (see bug 561923). + */ + for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront()) { + DefinitionNode defn = r.front().value().get(); + DefinitionNode lexdep = handler.resolve(defn); + handler.deoptimizeUsesWithin(lexdep, TokenPos(begin, pos().begin)); + } + + ObjectBox *staticWithBox = newObjectBox(staticWith); + if (!staticWithBox) + return null(); + return handler.newWithStatement(begin, objectExpr, innerBlock, staticWithBox); +} + +template <> +SyntaxParseHandler::Node +Parser::withStatement() +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return null(); +} + +template +typename ParseHandler::Node +Parser::labeledStatement() +{ + uint32_t begin = pos().begin; + RootedPropertyName label(context, tokenStream.currentName()); + for (StmtInfoPC *stmt = pc->topStmt; stmt; stmt = stmt->down) { + if (stmt->type == STMT_LABEL && stmt->label == label) { + report(ParseError, false, null(), JSMSG_DUPLICATE_LABEL); + return null(); + } + } + + tokenStream.consumeKnownToken(TOK_COLON); + + /* Push a label struct and parse the statement. */ + StmtInfoPC stmtInfo(context); + PushStatementPC(pc, &stmtInfo, STMT_LABEL); + stmtInfo.label = label; + Node pn = statement(); + if (!pn) + return null(); + + /* Pop the label, set pn_expr, and return early. */ + PopStatementPC(tokenStream, pc); + + return handler.newLabeledStatement(label, pn, begin); +} + +template +typename ParseHandler::Node +Parser::throwStatement() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_THROW)); + uint32_t begin = pos().begin; + + /* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */ + TokenKind tt = tokenStream.peekTokenSameLine(TokenStream::Operand); + if (tt == TOK_ERROR) + return null(); + if (tt == TOK_EOF || tt == TOK_EOL || tt == TOK_SEMI || tt == TOK_RC) { + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); + } + + Node throwExpr = expr(); + if (!throwExpr) + return null(); + + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + + return handler.newThrowStatement(throwExpr, TokenPos(begin, pos().end)); +} + +template +typename ParseHandler::Node +Parser::tryStatement() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_TRY)); + uint32_t begin = pos().begin; + + /* + * 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. + */ + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY); + StmtInfoPC stmtInfo(context); + if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_TRY, pc)) + return null(); + Node innerBlock = statements(); + if (!innerBlock) + return null(); + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_TRY); + PopStatementPC(tokenStream, pc); + + bool hasUnconditionalCatch = false; + Node catchList = null(); + TokenKind tt = tokenStream.getToken(); + if (tt == TOK_CATCH) { + catchList = handler.newList(PNK_CATCH); + if (!catchList) + return null(); + + do { + Node pnblock; + BindData data(context); + + /* Check for another catch after unconditional catch. */ + if (hasUnconditionalCatch) { + report(ParseError, false, null(), JSMSG_CATCH_AFTER_GENERAL); + return null(); + } + + /* + * Create a lexical scope node around the whole catch clause, + * including the head. + */ + pnblock = pushLexicalScope(&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) + */ + 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, pc->staticScope->template as(), + JSMSG_TOO_MANY_CATCH_VARS); + JS_ASSERT(data.let.blockObj); + + tt = tokenStream.getToken(); + Node catchName; + switch (tt) { + case TOK_LB: + case TOK_LC: + catchName = destructuringExpr(&data, tt); + if (!catchName) + return null(); + break; + + case TOK_YIELD: + if (!checkYieldNameValidity()) + return null(); + // Fall through. + case TOK_NAME: + { + RootedPropertyName label(context, tokenStream.currentName()); + catchName = newBindingNode(label, false); + if (!catchName) + return null(); + data.pn = catchName; + if (!data.binder(&data, label, this)) + return null(); + break; + } + + default: + report(ParseError, false, null(), JSMSG_CATCH_IDENTIFIER); + return null(); + } + + Node catchGuard = null(); +#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)) { + catchGuard = expr(); + if (!catchGuard) + return null(); + } +#endif + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); + Node catchBody = statements(); + if (!catchBody) + return null(); + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH); + PopStatementPC(tokenStream, pc); + + if (!catchGuard) + hasUnconditionalCatch = true; + + if (!handler.addCatchBlock(catchList, pnblock, catchName, catchGuard, catchBody)) + return null(); + handler.setEndPosition(catchList, pos().end); + handler.setEndPosition(pnblock, pos().end); + + tt = tokenStream.getToken(TokenStream::Operand); + } while (tt == TOK_CATCH); + } + + Node finallyBlock = null(); + + if (tt == TOK_FINALLY) { + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY); + if (!PushBlocklikeStatement(tokenStream, &stmtInfo, STMT_FINALLY, pc)) + return null(); + finallyBlock = statements(); + if (!finallyBlock) + return null(); + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_FINALLY); + PopStatementPC(tokenStream, pc); + } else { + tokenStream.ungetToken(); + } + if (!catchList && !finallyBlock) { + report(ParseError, false, null(), JSMSG_CATCH_OR_FINALLY); + return null(); + } + + return handler.newTryStatement(begin, innerBlock, catchList, finallyBlock); +} + +template +typename ParseHandler::Node +Parser::debuggerStatement() +{ + TokenPos p; + p.begin = pos().begin; + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + p.end = pos().end; + + pc->sc->setBindingsAccessedDynamically(); + pc->sc->setHasDebuggerStatement(); + + return handler.newDebuggerStatement(p); +} + +template +typename ParseHandler::Node +Parser::statement(bool canHaveDirectives) +{ + JS_CHECK_RECURSION(context, return null()); + + switch (TokenKind tt = tokenStream.getToken(TokenStream::Operand)) { + case TOK_LC: + return blockStatement(); + + case TOK_CONST: + if (!abortIfSyntaxParser()) + return null(); + // FALL THROUGH + case TOK_VAR: { + Node pn = variables(tt == TOK_CONST ? PNK_CONST : PNK_VAR); + if (!pn) + return null(); + + // Tell js_EmitTree to generate a final POP. + handler.setListFlag(pn, PNX_POPVAR); + + if (!MatchOrInsertSemicolon(tokenStream)) + return null(); + return pn; + } + + case TOK_LET: + return letStatement(); + case TOK_IMPORT: + return importDeclaration(); + case TOK_EXPORT: + return exportDeclaration(); + case TOK_SEMI: + return handler.newEmptyStatement(pos()); + case TOK_IF: + return ifStatement(); + case TOK_DO: + return doWhileStatement(); + case TOK_WHILE: + return whileStatement(); + case TOK_FOR: + return forStatement(); + case TOK_SWITCH: + return switchStatement(); + case TOK_CONTINUE: + return continueStatement(); + case TOK_BREAK: + return breakStatement(); + case TOK_RETURN: + return returnStatement(); + case TOK_WITH: + return withStatement(); + case TOK_THROW: + return throwStatement(); + case TOK_TRY: + return tryStatement(); + case TOK_FUNCTION: + return functionStmt(); + case TOK_DEBUGGER: + return debuggerStatement(); + + /* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */ + case TOK_CATCH: + report(ParseError, false, null(), JSMSG_CATCH_WITHOUT_TRY); + return null(); + + case TOK_FINALLY: + report(ParseError, false, null(), JSMSG_FINALLY_WITHOUT_TRY); + return null(); + + case TOK_ERROR: + return null(); + + case TOK_STRING: + if (!canHaveDirectives && tokenStream.currentToken().atom() == context->names().useAsm) { + if (!abortIfSyntaxParser()) + return null(); + if (!report(ParseWarning, false, null(), JSMSG_USE_ASM_DIRECTIVE_FAIL)) + return null(); + } + return expressionStatement(); + + case TOK_YIELD: + if (tokenStream.peekToken() == TOK_COLON) { + if (!checkYieldNameValidity()) + return null(); + return labeledStatement(); + } + return expressionStatement(); + + case TOK_NAME: + if (tokenStream.peekToken() == TOK_COLON) + return labeledStatement(); + return expressionStatement(); + + default: + return expressionStatement(); + } +} + +template +typename ParseHandler::Node +Parser::expr() +{ + Node pn = assignExpr(); + if (pn && tokenStream.matchToken(TOK_COMMA)) { + Node seq = handler.newList(PNK_COMMA, pn); + if (!seq) + return null(); + do { + if (handler.isUnparenthesizedYield(pn)) { + report(ParseError, false, pn, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); + return null(); + } + + pn = assignExpr(); + if (!pn) + return null(); + handler.addList(seq, pn); + } while (tokenStream.matchToken(TOK_COMMA)); + return seq; + } + return pn; +} + +static const JSOp ParseNodeKindToJSOp[] = { + JSOP_OR, + JSOP_AND, + JSOP_BITOR, + JSOP_BITXOR, + JSOP_BITAND, + JSOP_STRICTEQ, + JSOP_EQ, + JSOP_STRICTNE, + JSOP_NE, + JSOP_LT, + JSOP_LE, + JSOP_GT, + JSOP_GE, + JSOP_INSTANCEOF, + JSOP_IN, + JSOP_LSH, + JSOP_RSH, + JSOP_URSH, + JSOP_ADD, + JSOP_SUB, + JSOP_MUL, + JSOP_DIV, + JSOP_MOD +}; + +static inline JSOp +BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) +{ + JS_ASSERT(pnk >= PNK_BINOP_FIRST); + JS_ASSERT(pnk <= PNK_BINOP_LAST); + return ParseNodeKindToJSOp[pnk - PNK_BINOP_FIRST]; +} + +static bool +IsBinaryOpToken(TokenKind tok, bool parsingForInit) +{ + return tok == TOK_IN ? !parsingForInit : TokenKindIsBinaryOp(tok); +} + +static ParseNodeKind +BinaryOpTokenKindToParseNodeKind(TokenKind tok) +{ + JS_ASSERT(TokenKindIsBinaryOp(tok)); + return ParseNodeKind(PNK_BINOP_FIRST + (tok - TOK_BINOP_FIRST)); +} + +static const int PrecedenceTable[] = { + 1, /* PNK_OR */ + 2, /* PNK_AND */ + 3, /* PNK_BITOR */ + 4, /* PNK_BITXOR */ + 5, /* PNK_BITAND */ + 6, /* PNK_STRICTEQ */ + 6, /* PNK_EQ */ + 6, /* PNK_STRICTNE */ + 6, /* PNK_NE */ + 7, /* PNK_LT */ + 7, /* PNK_LE */ + 7, /* PNK_GT */ + 7, /* PNK_GE */ + 7, /* PNK_INSTANCEOF */ + 7, /* PNK_IN */ + 8, /* PNK_LSH */ + 8, /* PNK_RSH */ + 8, /* PNK_URSH */ + 9, /* PNK_ADD */ + 9, /* PNK_SUB */ + 10, /* PNK_STAR */ + 10, /* PNK_DIV */ + 10 /* PNK_MOD */ +}; + +static const int PRECEDENCE_CLASSES = 10; + +static int +Precedence(ParseNodeKind pnk) { + // Everything binds tighter than PNK_LIMIT, because we want to reduce all + // nodes to a single node when we reach a token that is not another binary + // operator. + if (pnk == PNK_LIMIT) + return 0; + + JS_ASSERT(pnk >= PNK_BINOP_FIRST); + JS_ASSERT(pnk <= PNK_BINOP_LAST); + return PrecedenceTable[pnk - PNK_BINOP_FIRST]; +} + +template +MOZ_ALWAYS_INLINE typename ParseHandler::Node +Parser::orExpr1() +{ + // Shift-reduce parser for the left-associative binary operator part of + // the JS syntax. + + // Conceptually there's just one stack, a stack of pairs (lhs, op). + // It's implemented using two separate arrays, though. + Node nodeStack[PRECEDENCE_CLASSES]; + ParseNodeKind kindStack[PRECEDENCE_CLASSES]; + int depth = 0; + + bool oldParsingForInit = pc->parsingForInit; + pc->parsingForInit = false; + + Node pn; + for (;;) { + pn = unaryExpr(); + if (!pn) + return pn; + + // If a binary operator follows, consume it and compute the + // corresponding operator. + TokenKind tok = tokenStream.getToken(); + if (tok == TOK_ERROR) + return null(); + ParseNodeKind pnk; + if (IsBinaryOpToken(tok, oldParsingForInit)) { + pnk = BinaryOpTokenKindToParseNodeKind(tok); + } else { + tok = TOK_EOF; + pnk = PNK_LIMIT; + } + + // If pnk has precedence less than or equal to another operator on the + // stack, reduce. This combines nodes on the stack until we form the + // actual lhs of pnk. + // + // The >= in this condition works because all the operators in question + // are left-associative; if any were not, the case where two operators + // have equal precedence would need to be handled specially, and the + // stack would need to be a Vector. + while (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(pnk)) { + depth--; + ParseNodeKind combiningPnk = kindStack[depth]; + JSOp combiningOp = BinaryOpParseNodeKindToJSOp(combiningPnk); + pn = handler.newBinaryOrAppend(combiningPnk, nodeStack[depth], pn, pc, combiningOp); + if (!pn) + return pn; + } + + if (pnk == PNK_LIMIT) + break; + + nodeStack[depth] = pn; + kindStack[depth] = pnk; + depth++; + JS_ASSERT(depth <= PRECEDENCE_CLASSES); + } + + JS_ASSERT(depth == 0); + pc->parsingForInit = oldParsingForInit; + return pn; +} + +template +MOZ_ALWAYS_INLINE typename ParseHandler::Node +Parser::condExpr1() +{ + Node 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. + */ + bool oldParsingForInit = pc->parsingForInit; + pc->parsingForInit = false; + Node thenExpr = assignExpr(); + pc->parsingForInit = oldParsingForInit; + if (!thenExpr) + return null(); + + MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND); + + Node elseExpr = assignExpr(); + if (!elseExpr) + return null(); + + tokenStream.getToken(); /* read one token past the end */ + return handler.newConditional(condition, thenExpr, elseExpr); +} + +template <> +bool +Parser::checkAndMarkAsAssignmentLhs(ParseNode *pn, AssignmentFlavor flavor) +{ + switch (pn->getKind()) { + case PNK_NAME: + if (!checkStrictAssignment(pn, flavor)) + return false; + if (flavor == KeyedDestructuringAssignment) { + /* + * We may be called on a name node that has already been + * specialized, in the very weird "for (var [x] = i in o) ..." + * case. See bug 558633. + */ + if (!(js_CodeSpec[pn->getOp()].format & JOF_SET)) + pn->setOp(JSOP_SETNAME); + } else { + pn->setOp(pn->isOp(JSOP_GETLOCAL) ? JSOP_SETLOCAL : JSOP_SETNAME); + } + pn->markAsAssigned(); + break; + + case PNK_DOT: + case PNK_ELEM: + break; + + case PNK_ARRAY: + case PNK_OBJECT: + if (flavor == CompoundAssignment) { + report(ParseError, false, null(), JSMSG_BAD_DESTRUCT_ASS); + return false; + } + if (!checkDestructuring(nullptr, pn)) + return false; + break; + + case PNK_CALL: + if (!makeSetCall(pn, JSMSG_BAD_LEFTSIDE_OF_ASS)) + return false; + break; + + default: + report(ParseError, false, pn, JSMSG_BAD_LEFTSIDE_OF_ASS); + return false; + } + return true; +} + +template <> +bool +Parser::checkAndMarkAsAssignmentLhs(Node pn, AssignmentFlavor flavor) +{ + /* Full syntax checking of valid assignment LHS terms requires a parse tree. */ + if (pn != SyntaxParseHandler::NodeName && + pn != SyntaxParseHandler::NodeGetProp && + pn != SyntaxParseHandler::NodeLValue) + { + return abortIfSyntaxParser(); + } + return checkStrictAssignment(pn, flavor); +} + +template +typename ParseHandler::Node +Parser::assignExpr() +{ + JS_CHECK_RECURSION(context, return null()); + + // It's very common at this point to have a "detectably simple" expression, + // i.e. a name/number/string token followed by one of the following tokens + // that obviously isn't part of an expression: , ; : ) ] } + // + // (In Parsemark this happens 81.4% of the time; in code with large + // numeric arrays, such as some Kraken benchmarks, it happens more often.) + // + // In such cases, we can avoid the full expression parsing route through + // assignExpr(), condExpr1(), orExpr1(), unaryExpr(), memberExpr(), and + // primaryExpr(). + + TokenKind tt = tokenStream.getToken(TokenStream::Operand); + + if (tt == TOK_NAME && tokenStream.nextTokenEndsExpr()) + return identifierName(); + + if (tt == TOK_NUMBER && tokenStream.nextTokenEndsExpr()) + return newNumber(tokenStream.currentToken()); + + if (tt == TOK_STRING && tokenStream.nextTokenEndsExpr()) + return stringLiteral(); + + if (tt == TOK_YIELD && (versionNumber() >= JSVERSION_1_7 || pc->isGenerator())) + return yieldExpression(); + + tokenStream.ungetToken(); + + // Save the tokenizer state in case we find an arrow function and have to + // rewind. + TokenStream::Position start(keepAtoms); + tokenStream.tell(&start); + + Node lhs = condExpr1(); + if (!lhs) + return null(); + + ParseNodeKind kind; + JSOp op; + switch (tokenStream.currentToken().type) { + case TOK_ASSIGN: kind = PNK_ASSIGN; op = JSOP_NOP; break; + case TOK_ADDASSIGN: kind = PNK_ADDASSIGN; op = JSOP_ADD; break; + case TOK_SUBASSIGN: kind = PNK_SUBASSIGN; op = JSOP_SUB; break; + case TOK_BITORASSIGN: kind = PNK_BITORASSIGN; op = JSOP_BITOR; break; + case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; op = JSOP_BITXOR; break; + case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; op = JSOP_BITAND; break; + case TOK_LSHASSIGN: kind = PNK_LSHASSIGN; op = JSOP_LSH; break; + case TOK_RSHASSIGN: kind = PNK_RSHASSIGN; op = JSOP_RSH; break; + case TOK_URSHASSIGN: kind = PNK_URSHASSIGN; op = JSOP_URSH; break; + case TOK_MULASSIGN: kind = PNK_MULASSIGN; op = JSOP_MUL; break; + case TOK_DIVASSIGN: kind = PNK_DIVASSIGN; op = JSOP_DIV; break; + case TOK_MODASSIGN: kind = PNK_MODASSIGN; op = JSOP_MOD; break; + + case TOK_ARROW: { + tokenStream.seek(start); + if (!abortIfSyntaxParser()) + return null(); + + if (tokenStream.getToken() == TOK_ERROR) + return null(); + tokenStream.ungetToken(); + + return functionDef(NullPtr(), start, Normal, Arrow, NotGenerator); + } + + default: + JS_ASSERT(!tokenStream.isCurrentTokenAssignment()); + tokenStream.ungetToken(); + return lhs; + } + + AssignmentFlavor flavor = kind == PNK_ASSIGN ? PlainAssignment : CompoundAssignment; + if (!checkAndMarkAsAssignmentLhs(lhs, flavor)) + return null(); + + Node rhs = assignExpr(); + if (!rhs) + return null(); + + return handler.newBinaryOrAppend(kind, lhs, rhs, pc, op); +} + +static const char incop_name_str[][10] = {"increment", "decrement"}; + +template <> +bool +Parser::checkAndMarkAsIncOperand(ParseNode *kid, TokenKind tt, bool preorder) +{ + // Check. + if (!kid->isKind(PNK_NAME) && + !kid->isKind(PNK_DOT) && + !kid->isKind(PNK_ELEM) && + !(kid->isKind(PNK_CALL) && + (kid->isOp(JSOP_CALL) || kid->isOp(JSOP_SPREADCALL) || + kid->isOp(JSOP_EVAL) || kid->isOp(JSOP_SPREADEVAL) || + kid->isOp(JSOP_FUNCALL) || + kid->isOp(JSOP_FUNAPPLY)))) + { + report(ParseError, false, null(), JSMSG_BAD_OPERAND, incop_name_str[tt == TOK_DEC]); + return false; + } + + if (!checkStrictAssignment(kid, IncDecAssignment)) + return false; + + // Mark. + if (kid->isKind(PNK_NAME)) { + kid->markAsAssigned(); + } else if (kid->isKind(PNK_CALL)) { + if (!makeSetCall(kid, JSMSG_BAD_INCOP_OPERAND)) + return false; + } + return true; +} + +template <> +bool +Parser::checkAndMarkAsIncOperand(Node kid, TokenKind tt, bool preorder) +{ + // To the extent of what we support in syntax-parse mode, the rules for + // inc/dec operands are the same as for assignment. There are differences, + // such as destructuring; but if we hit any of those cases, we'll abort and + // reparse in full mode. + return checkAndMarkAsAssignmentLhs(kid, IncDecAssignment); +} + +template +typename ParseHandler::Node +Parser::unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin) +{ + Node kid = unaryExpr(); + if (!kid) + return null(); + return handler.newUnary(kind, op, begin, kid); +} + +template +typename ParseHandler::Node +Parser::unaryExpr() +{ + Node pn, pn2; + + JS_CHECK_RECURSION(context, return null()); + + TokenKind tt = tokenStream.getToken(TokenStream::Operand); + uint32_t begin = pos().begin; + switch (tt) { + case TOK_TYPEOF: + return unaryOpExpr(PNK_TYPEOF, JSOP_TYPEOF, begin); + case TOK_VOID: + return unaryOpExpr(PNK_VOID, JSOP_VOID, begin); + case TOK_NOT: + return unaryOpExpr(PNK_NOT, JSOP_NOT, begin); + case TOK_BITNOT: + return unaryOpExpr(PNK_BITNOT, JSOP_BITNOT, begin); + case TOK_ADD: + return unaryOpExpr(PNK_POS, JSOP_POS, begin); + case TOK_SUB: + return unaryOpExpr(PNK_NEG, JSOP_NEG, begin); + + case TOK_INC: + case TOK_DEC: + { + TokenKind tt2 = tokenStream.getToken(TokenStream::Operand); + pn2 = memberExpr(tt2, true); + if (!pn2) + return null(); + if (!checkAndMarkAsIncOperand(pn2, tt, true)) + return null(); + return handler.newUnary((tt == TOK_INC) ? PNK_PREINCREMENT : PNK_PREDECREMENT, + JSOP_NOP, + begin, + pn2); + } + + case TOK_DELETE: { + Node expr = unaryExpr(); + if (!expr) + return null(); + + // Per spec, deleting any unary expression is valid -- it simply + // returns true -- except for one case that is illegal in strict mode. + if (handler.isName(expr)) { + if (!report(ParseStrictError, pc->sc->strict, expr, JSMSG_DEPRECATED_DELETE_OPERAND)) + return null(); + pc->sc->setBindingsAccessedDynamically(); + } + + return handler.newDelete(begin, expr); + } + + case TOK_ERROR: + return null(); + + default: + pn = memberExpr(tt, true); + if (!pn) + return null(); + + /* Don't look across a newline boundary for a postfix incop. */ + tt = tokenStream.peekTokenSameLine(TokenStream::Operand); + if (tt == TOK_INC || tt == TOK_DEC) { + tokenStream.consumeKnownToken(tt); + if (!checkAndMarkAsIncOperand(pn, tt, false)) + return null(); + return handler.newUnary((tt == TOK_INC) ? PNK_POSTINCREMENT : PNK_POSTDECREMENT, + JSOP_NOP, + begin, + pn); + } + return pn; + } + MOZ_ASSUME_UNREACHABLE("unaryExpr"); +} + +/* + * A dedicated helper for transplanting the legacy comprehension expression E in + * + * [E for (V in I)] // legacy array comprehension + * (E for (V in I)) // legacy 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 legacy 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 LegacyCompExprTransplanter +{ + ParseNode *root; + Parser *parser; + ParseContext *outerpc; + GeneratorKind comprehensionKind; + unsigned adjust; + HashSet visitedImplicitArguments; + + public: + LegacyCompExprTransplanter(ParseNode *pn, Parser *parser, + ParseContext *outerpc, + GeneratorKind kind, unsigned adj) + : root(pn), parser(parser), outerpc(outerpc), comprehensionKind(kind), adjust(adj), + visitedImplicitArguments(parser->context) + {} + + bool init() { + return visitedImplicitArguments.init(); + } + + bool transplant(ParseNode *pn); +}; + +/* + * Any definitions nested within the legacy comprehension expression of a + * generator expression must move "down" one static level, which of course + * increases the upvar-frame-skip count. + */ +template +static bool +BumpStaticLevel(TokenStream &ts, ParseNode *pn, ParseContext *pc) +{ + if (pn->pn_cookie.isFree()) + return true; + + unsigned level = unsigned(pn->pn_cookie.level()) + 1; + JS_ASSERT(level >= pc->staticLevel); + return pn->pn_cookie.set(ts, level, pn->pn_cookie.slot()); +} + +template +static bool +AdjustBlockId(TokenStream &ts, ParseNode *pn, unsigned adjust, ParseContext *pc) +{ + JS_ASSERT(pn->isArity(PN_LIST) || pn->isArity(PN_CODE) || pn->isArity(PN_NAME)); + if (BlockIdLimit - pn->pn_blockid <= adjust + 1) { + ts.reportError(JSMSG_NEED_DIET, "program"); + return false; + } + pn->pn_blockid += adjust; + if (pn->pn_blockid >= pc->blockidGen) + pc->blockidGen = pn->pn_blockid + 1; + return true; +} + +bool +LegacyCompExprTransplanter::transplant(ParseNode *pn) +{ + ParseContext *pc = parser->pc; + + bool isGenexp = comprehensionKind != NotGenerator; + + 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) { + if (!AdjustBlockId(parser->tokenStream, pn, adjust, pc)) + return false; + } + break; + + case PN_TERNARY: + if (!transplant(pn->pn_kid1) || + !transplant(pn->pn_kid2) || + !transplant(pn->pn_kid3)) + return false; + break; + + case PN_BINARY: + case PN_BINARY_OBJ: + 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_CODE: + case PN_NAME: + if (!transplant(pn->maybeExpr())) + return false; + + if (pn->isDefn()) { + if (isGenexp && !BumpStaticLevel(parser->tokenStream, pn, pc)) + return false; + } else if (pn->isUsed()) { + 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 legacy comprehension expression (to avoid adjusting the + * blockid multiple times). + * + * Non-placeholder definitions within the legacy comprehension + * expression will be visited further below. + */ + if (dn->isPlaceholder() && dn->pn_pos >= root->pn_pos && dn->dn_uses == pn) { + if (isGenexp && !BumpStaticLevel(parser->tokenStream, dn, pc)) + return false; + if (!AdjustBlockId(parser->tokenStream, dn, adjust, pc)) + return false; + } + + RootedAtom atom(parser->context, pn->pn_atom); +#ifdef DEBUG + StmtInfoPC *stmt = LexicalLookup(pc, atom, nullptr, (StmtInfoPC *)nullptr); + JS_ASSERT(!stmt || stmt != pc->topStmt); +#endif + if (isGenexp && !dn->isOp(JSOP_CALLEE)) { + JS_ASSERT(!pc->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 legacy 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 = parser->handler.newPlaceholder(atom, parser->pc->blockid(), + parser->pos()); + 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) != nullptr && 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 = nullptr; + DefinitionSingle def = DefinitionSingle::new_(dn2); + if (!pc->lexdeps->put(atom, def)) + return false; + if (dn->isClosed()) + dn2->pn_dflags |= PND_CLOSED; + } 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. + */ + outerpc->lexdeps->remove(atom); + DefinitionSingle def = DefinitionSingle::new_(dn); + if (!pc->lexdeps->put(atom, def)) + return false; + } else if (dn->isImplicitArguments()) { + /* + * Implicit 'arguments' Definition nodes (see + * PND_IMPLICITARGUMENTS in Parser::functionBody) are only + * reachable via the lexdefs of their uses. Unfortunately, + * there may be multiple uses, so we need to maintain a set + * to only bump the definition once. + */ + if (isGenexp && !visitedImplicitArguments.has(dn)) { + if (!BumpStaticLevel(parser->tokenStream, dn, pc)) + return false; + if (!AdjustBlockId(parser->tokenStream, dn, adjust, pc)) + return false; + if (!visitedImplicitArguments.put(dn)) + return false; + } + } + } + } + + if (pn->pn_pos >= root->pn_pos) { + if (!AdjustBlockId(parser->tokenStream, pn, adjust, pc)) + return false; + } + break; + + case PN_NULLARY: + /* Nothing. */ + break; + } + return true; +} + +// Parsing legacy (JS1.7-style) comprehensions is terrible: we parse the head +// expression as if it's part of a comma expression, then when we see the "for" +// we transplant the parsed expression into the inside of a constructed +// for-of/for-in/for-each tail. Transplanting an already-parsed expression is +// tricky, but the LegacyCompExprTransplanter handles most of that. +// +// The one remaining thing to patch up is the block scope depth. We need to +// compute the maximum block scope depth of a function, so we know how much +// space to reserve in the fixed part of a stack frame. Normally this is done +// whenever we leave a statement, via AccumulateBlockScopeDepth. However if the +// head has a let expression, we need to re-assign that depth to the tail of the +// comprehension. +// +// Thing is, we don't actually know what that depth is, because the only +// information we keep is the maximum nested depth within a statement, so we +// just conservatively propagate the maximum nested depth from the top statement +// to the comprehension tail. +// +template +static unsigned +LegacyComprehensionHeadBlockScopeDepth(ParseContext *pc) +{ + return pc->topStmt ? pc->topStmt->innerBlockScopeDepth : pc->blockScopeDepth; +} + +/* + * 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|. + */ +template <> +ParseNode * +Parser::legacyComprehensionTail(ParseNode *bodyStmt, unsigned blockid, + GeneratorKind comprehensionKind, + ParseContext *outerpc, + unsigned innerBlockScopeDepth) +{ + /* + * If we saw any inner functions while processing the generator expression + * then they may have upvars referring to the let vars in this generator + * which were not correctly processed. Bail out and start over without + * allowing lazy parsing. + */ + if (handler.syntaxParser) { + handler.disableSyntaxParser(); + abortedSyntaxParse = true; + return nullptr; + } + + unsigned adjust; + ParseNode *pn, *pn2, *pn3, **pnp; + StmtInfoPC stmtInfo(context); + BindData data(context); + TokenKind tt; + + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + + bool isGenexp = comprehensionKind != NotGenerator; + + if (isGenexp) { + JS_ASSERT(comprehensionKind == LegacyGenerator); + /* + * 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(&stmtInfo); + if (!pn) + return null(); + adjust = pn->pn_blockid - blockid; + } else { + /* + * 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 = pc->blockid(); + pn = pushLexicalScope(&stmtInfo); + if (!pn) + return null(); + + JS_ASSERT(blockid <= pn->pn_blockid); + JS_ASSERT(blockid < pc->blockidGen); + JS_ASSERT(pc->bodyid < blockid); + pn->pn_blockid = stmtInfo.blockid = blockid; + JS_ASSERT(adjust < blockid); + adjust = blockid - adjust; + } + + handler.setBeginPosition(pn, bodyStmt); + + pnp = &pn->pn_expr; + + LegacyCompExprTransplanter transplanter(bodyStmt, this, outerpc, comprehensionKind, adjust); + if (!transplanter.init()) + return null(); + + if (!transplanter.transplant(bodyStmt)) + return null(); + + JS_ASSERT(pc->staticScope && pc->staticScope == pn->pn_objbox->object); + data.initLet(HoistVars, pc->staticScope->as(), 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, &handler); + if (!pn2) + return null(); + + pn2->setOp(JSOP_ITER); + pn2->pn_iflags = JSITER_ENUMERATE; + if (allowsForEachIn() && tokenStream.matchContextualKeyword(context->names().each)) + pn2->pn_iflags |= JSITER_FOREACH; + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + + uint32_t startYieldOffset = pc->lastYieldOffset; + + RootedPropertyName name(context); + tt = tokenStream.getToken(); + switch (tt) { + case TOK_LB: + case TOK_LC: + pc->inDeclDestructuring = true; + pn3 = primaryExpr(tt); + pc->inDeclDestructuring = false; + if (!pn3) + return null(); + break; + + case TOK_NAME: + name = tokenStream.currentName(); + + /* + * 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, false); + if (!pn3) + return null(); + break; + + default: + report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME); + + case TOK_ERROR: + return null(); + } + + bool isForOf; + if (!matchInOrOf(&isForOf)) { + report(ParseError, false, null(), JSMSG_IN_AFTER_FOR_NAME); + return null(); + } + ParseNodeKind headKind = PNK_FORIN; + if (isForOf) { + if (pn2->pn_iflags != JSITER_ENUMERATE) { + JS_ASSERT(pn2->pn_iflags == (JSITER_FOREACH | JSITER_ENUMERATE)); + report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP); + return null(); + } + pn2->pn_iflags = 0; + headKind = PNK_FOROF; + } + + ParseNode *pn4 = expr(); + if (!pn4) + return null(); + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); + + if (isGenexp && pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); + return null(); + } + + switch (tt) { + case TOK_LB: + case TOK_LC: + if (!checkDestructuring(&data, pn3)) + return null(); + + if (versionNumber() == JSVERSION_1_7 && + !(pn2->pn_iflags & JSITER_FOREACH) && + !isForOf) + { + /* Destructuring requires [key, value] enumeration in JS1.7. */ + if (!pn3->isKind(PNK_ARRAY) || pn3->pn_count != 2) { + report(ParseError, false, null(), JSMSG_BAD_FOR_LEFTSIDE); + return null(); + } + + JS_ASSERT(pn2->isOp(JSOP_ITER)); + JS_ASSERT(pn2->pn_iflags & JSITER_ENUMERATE); + JS_ASSERT(headKind == PNK_FORIN); + pn2->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE; + } + break; + + case TOK_NAME: + data.pn = pn3; + if (!data.binder(&data, name, this)) + 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, &handler); + if (!vars) + return null(); + vars->setOp(JSOP_NOP); + vars->pn_pos = pn3->pn_pos; + vars->makeEmpty(); + vars->append(pn3); + + /* Definitions can't be passed directly to EmitAssignment as lhs. */ + pn3 = cloneLeftHandSide(pn3); + if (!pn3) + return null(); + + pn2->pn_left = handler.newTernary(headKind, 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, &handler); + if (!pn2) + return null(); + pn2->pn_kid1 = condition(); + if (!pn2->pn_kid1) + return null(); + *pnp = pn2; + pnp = &pn2->pn_kid2; + } + + *pnp = bodyStmt; + + pc->topStmt->innerBlockScopeDepth += innerBlockScopeDepth; + PopStatementPC(tokenStream, pc); + + handler.setEndPosition(pn, pos().end); + + return pn; +} + +template <> +SyntaxParseHandler::Node +Parser::legacyComprehensionTail(SyntaxParseHandler::Node bodyStmt, + unsigned blockid, + GeneratorKind comprehensionKind, + ParseContext *outerpc, + unsigned innerBlockScopeDepth) +{ + abortIfSyntaxParser(); + return null(); +} + +template <> +ParseNode* +Parser::legacyArrayComprehension(ParseNode *array) +{ + array->setKind(PNK_ARRAYCOMP); + + // Remove the single element from array's linked list, leaving us with an + // empty array literal and a comprehension expression. + JS_ASSERT(array->pn_count == 1); + ParseNode *bodyExpr = array->last(); + array->pn_count = 0; + array->pn_tail = &array->pn_head; + *array->pn_tail = nullptr; + + ParseNode *arrayPush = handler.newUnary(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, + bodyExpr->pn_pos.begin, bodyExpr); + if (!arrayPush) + return null(); + + ParseNode *comp = legacyComprehensionTail(arrayPush, array->pn_blockid, NotGenerator, + nullptr, LegacyComprehensionHeadBlockScopeDepth(pc)); + if (!comp) + return null(); + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION); + + TokenPos p = handler.getPosition(array); + p.end = pos().end; + return handler.newArrayComprehension(comp, array->pn_blockid, p); +} + +template <> +SyntaxParseHandler::Node +Parser::legacyArrayComprehension(Node array) +{ + abortIfSyntaxParser(); + return null(); +} + +template +typename ParseHandler::Node +Parser::generatorComprehensionLambda(GeneratorKind comprehensionKind, + unsigned begin, Node innerStmt) +{ + JS_ASSERT(comprehensionKind == LegacyGenerator || comprehensionKind == StarGenerator); + JS_ASSERT(!!innerStmt == (comprehensionKind == LegacyGenerator)); + + Node genfn = handler.newFunctionDefinition(); + if (!genfn) + return null(); + handler.setOp(genfn, JSOP_LAMBDA); + + ParseContext *outerpc = pc; + + // If we are off the main thread, the generator meta-objects have + // already been created by js::StartOffThreadParseScript, so cx will not + // be necessary. + RootedObject proto(context); + if (comprehensionKind == StarGenerator) { + JSContext *cx = context->maybeJSContext(); + proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, context->global()); + if (!proto) + return null(); + } + + RootedFunction fun(context, newFunction(outerpc, /* atom = */ NullPtr(), Expression, proto)); + if (!fun) + return null(); + + // Create box for fun->object early to root it. + Directives directives(/* strict = */ outerpc->sc->strict); + FunctionBox *genFunbox = newFunctionBox(genfn, fun, outerpc, directives, comprehensionKind); + if (!genFunbox) + return null(); + + ParseContext genpc(this, outerpc, genfn, genFunbox, + /* newDirectives = */ nullptr, + outerpc->staticLevel + 1, outerpc->blockidGen, + /* blockScopeDepth = */ 0); + if (!genpc.init(tokenStream)) + return null(); + + /* + * We assume conservatively that any deoptimization flags in pc->sc + * 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 pc->sc. + */ + genFunbox->anyCxFlags = outerpc->sc->anyCxFlags; + if (outerpc->sc->isFunctionBox()) + genFunbox->funCxFlags = outerpc->sc->asFunctionBox()->funCxFlags; + + JS_ASSERT(genFunbox->generatorKind() == comprehensionKind); + genFunbox->inGenexpLambda = true; + handler.setBlockId(genfn, genpc.bodyid); + + Node body; + + if (comprehensionKind == StarGenerator) { + body = comprehension(StarGenerator); + if (!body) + return null(); + } else { + JS_ASSERT(comprehensionKind == LegacyGenerator); + body = legacyComprehensionTail(innerStmt, outerpc->blockid(), LegacyGenerator, + outerpc, LegacyComprehensionHeadBlockScopeDepth(outerpc)); + if (!body) + return null(); + } + + if (comprehensionKind == StarGenerator) + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); + + handler.setBeginPosition(body, begin); + handler.setEndPosition(body, pos().end); + + handler.setBeginPosition(genfn, begin); + handler.setEndPosition(genfn, pos().end); + + // Note that if we ever start syntax-parsing generators, we will also + // need to propagate the closed-over variable set to the inner + // lazyscript, as in finishFunctionDefinition. + handler.setFunctionBody(genfn, body); + + PropagateTransitiveParseFlags(genFunbox, outerpc->sc); + + if (!leaveFunction(genfn, outerpc)) + return null(); + + return genfn; +} + +#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 legacy 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. + */ +template <> +ParseNode * +Parser::legacyGeneratorExpr(ParseNode *expr) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + + /* Create a |yield| node for |kid|. */ + ParseNode *yieldExpr = handler.newUnary(PNK_YIELD, JSOP_NOP, expr->pn_pos.begin, expr); + if (!yieldExpr) + return null(); + yieldExpr->setInParens(true); + + // A statement to wrap the yield expression. + ParseNode *yieldStmt = handler.newExprStatement(yieldExpr, expr->pn_pos.end); + if (!yieldStmt) + return null(); + + /* Make a new node for the desugared generator function. */ + ParseNode *genfn = generatorComprehensionLambda(LegacyGenerator, expr->pn_pos.begin, yieldStmt); + if (!genfn) + return null(); + + /* + * Our result is a call expression that invokes the anonymous generator + * function object. + */ + ParseNode *result = ListNode::create(PNK_GENEXP, &handler); + if (!result) + return null(); + result->setOp(JSOP_CALL); + result->pn_pos.begin = genfn->pn_pos.begin; + result->initList(genfn); + return result; +} + +template <> +SyntaxParseHandler::Node +Parser::legacyGeneratorExpr(Node kid) +{ + JS_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + +static const char js_generator_str[] = "generator"; + +#endif /* JS_HAS_GENERATOR_EXPRS */ + +template +typename ParseHandler::Node +Parser::comprehensionFor(GeneratorKind comprehensionKind) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + + uint32_t begin = pos().begin; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + + // FIXME: Destructuring binding (bug 980828). + + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME); + RootedPropertyName name(context, tokenStream.currentName()); + if (name == context->names().let) { + report(ParseError, false, null(), JSMSG_LET_COMP_BINDING); + return null(); + } + if (!tokenStream.matchContextualKeyword(context->names().of)) { + report(ParseError, false, null(), JSMSG_OF_AFTER_FOR_NAME); + return null(); + } + + Node rhs = assignExpr(); + if (!rhs) + return null(); + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_OF_ITERABLE); + + TokenPos headPos(begin, pos().end); + + StmtInfoPC stmtInfo(context); + BindData data(context); + RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); + if (!blockObj) + return null(); + data.initLet(DontHoistVars, *blockObj, JSMSG_TOO_MANY_LOCALS); + Node lhs = newName(name); + if (!lhs) + return null(); + Node decls = handler.newList(PNK_LET, lhs, JSOP_NOP); + if (!decls) + return null(); + data.pn = lhs; + if (!data.binder(&data, name, this)) + return null(); + Node letScope = pushLetScope(blockObj, &stmtInfo); + if (!letScope) + return null(); + handler.setLexicalScopeBody(letScope, decls); + + Node assignLhs = newName(name); + if (!assignLhs) + return null(); + if (!noteNameUse(name, assignLhs)) + return null(); + handler.setOp(assignLhs, JSOP_SETNAME); + + Node head = handler.newForHead(PNK_FOROF, letScope, assignLhs, rhs, headPos); + if (!head) + return null(); + + Node tail = comprehensionTail(comprehensionKind); + if (!tail) + return null(); + + PopStatementPC(tokenStream, pc); + + return handler.newForStatement(begin, head, tail, JSOP_ITER); +} + +template +typename ParseHandler::Node +Parser::comprehensionIf(GeneratorKind comprehensionKind) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_IF)); + + uint32_t begin = pos().begin; + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); + Node cond = assignExpr(); + if (!cond) + return null(); + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); + + /* Check for (a = b) and warn about possible (a == b) mistype. */ + if (handler.isOperationWithoutParens(cond, PNK_ASSIGN) && + !report(ParseExtraWarning, false, null(), JSMSG_EQUAL_AS_ASSIGN)) + { + return null(); + } + + Node then = comprehensionTail(comprehensionKind); + if (!then) + return null(); + + return handler.newIfStatement(begin, cond, then, null()); +} + +template +typename ParseHandler::Node +Parser::comprehensionTail(GeneratorKind comprehensionKind) +{ + JS_CHECK_RECURSION(context, return null()); + + if (tokenStream.matchToken(TOK_FOR, TokenStream::Operand)) + return comprehensionFor(comprehensionKind); + + if (tokenStream.matchToken(TOK_IF, TokenStream::Operand)) + return comprehensionIf(comprehensionKind); + + uint32_t begin = pos().begin; + + Node bodyExpr = assignExpr(); + if (!bodyExpr) + return null(); + + if (comprehensionKind == NotGenerator) + return handler.newUnary(PNK_ARRAYPUSH, JSOP_ARRAYPUSH, begin, bodyExpr); + + JS_ASSERT(comprehensionKind == StarGenerator); + Node yieldExpr = handler.newUnary(PNK_YIELD, JSOP_NOP, begin, bodyExpr); + if (!yieldExpr) + return null(); + handler.setInParens(yieldExpr); + + return handler.newExprStatement(yieldExpr, pos().end); +} + +// Parse an ES6 generator or array comprehension, starting at the first 'for'. +// The caller is responsible for matching the ending TOK_RP or TOK_RB. +template +typename ParseHandler::Node +Parser::comprehension(GeneratorKind comprehensionKind) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + + uint32_t startYieldOffset = pc->lastYieldOffset; + + Node body = comprehensionFor(comprehensionKind); + if (!body) + return null(); + + if (comprehensionKind != NotGenerator && pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); + return null(); + } + + return body; +} + +template +typename ParseHandler::Node +Parser::arrayComprehension(uint32_t begin) +{ + Node inner = comprehension(NotGenerator); + if (!inner) + return null(); + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION); + + Node comp = handler.newList(PNK_ARRAYCOMP, inner); + if (!comp) + return null(); + + handler.setBeginPosition(comp, begin); + handler.setEndPosition(comp, pos().end); + + return comp; +} + +template +typename ParseHandler::Node +Parser::generatorComprehension(uint32_t begin) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_FOR)); + + // We have no problem parsing generator comprehensions inside lazy + // functions, but the bytecode emitter currently can't handle them that way, + // because when it goes to emit the code for the inner generator function, + // it expects outer functions to have non-lazy scripts. + if (!abortIfSyntaxParser()) + return null(); + + Node genfn = generatorComprehensionLambda(StarGenerator, begin, null()); + if (!genfn) + return null(); + + Node result = handler.newList(PNK_GENEXP, genfn, JSOP_CALL); + if (!result) + return null(); + handler.setBeginPosition(result, begin); + handler.setEndPosition(result, pos().end); + + return result; +} + +template +typename ParseHandler::Node +Parser::assignExprWithoutYield(unsigned msg) +{ + uint32_t startYieldOffset = pc->lastYieldOffset; + Node res = assignExpr(); + if (res && pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + msg, js_yield_str); + return null(); + } + return res; +} + +template +bool +Parser::argumentList(Node listNode, bool *isSpread) +{ + if (tokenStream.matchToken(TOK_RP, TokenStream::Operand)) { + handler.setEndPosition(listNode, pos().end); + return true; + } + + uint32_t startYieldOffset = pc->lastYieldOffset; + bool arg0 = true; + + do { + bool spread = false; + uint32_t begin = 0; + if (tokenStream.matchToken(TOK_TRIPLEDOT, TokenStream::Operand)) { + spread = true; + begin = pos().begin; + *isSpread = true; + } + + Node argNode = assignExpr(); + if (!argNode) + return false; + if (spread) { + argNode = handler.newUnary(PNK_SPREAD, JSOP_NOP, begin, argNode); + if (!argNode) + return null(); + } + + if (handler.isOperationWithoutParens(argNode, PNK_YIELD) && + tokenStream.peekToken() == TOK_COMMA) { + report(ParseError, false, argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_yield_str); + return false; + } +#if JS_HAS_GENERATOR_EXPRS + if (!spread && tokenStream.matchToken(TOK_FOR)) { + if (pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); + return false; + } + argNode = legacyGeneratorExpr(argNode); + if (!argNode) + return false; + if (!arg0 || tokenStream.peekToken() == TOK_COMMA) { + report(ParseError, false, argNode, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); + return false; + } + } +#endif + arg0 = false; + + handler.addList(listNode, argNode); + } while (tokenStream.matchToken(TOK_COMMA)); + + if (tokenStream.getToken() != TOK_RP) { + report(ParseError, false, null(), JSMSG_PAREN_AFTER_ARGS); + return false; + } + handler.setEndPosition(listNode, pos().end); + return true; +} + +template +typename ParseHandler::Node +Parser::memberExpr(TokenKind tt, bool allowCallSyntax) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(tt)); + + Node lhs; + + JS_CHECK_RECURSION(context, return null()); + + /* Check for new expression first. */ + if (tt == TOK_NEW) { + lhs = handler.newList(PNK_NEW, null(), JSOP_NEW); + if (!lhs) + return null(); + + tt = tokenStream.getToken(TokenStream::Operand); + Node ctorExpr = memberExpr(tt, false); + if (!ctorExpr) + return null(); + + handler.addList(lhs, ctorExpr); + + if (tokenStream.matchToken(TOK_LP)) { + bool isSpread = false; + if (!argumentList(lhs, &isSpread)) + return null(); + if (isSpread) + handler.setOp(lhs, JSOP_SPREADNEW); + } + } else { + lhs = primaryExpr(tt); + if (!lhs) + return null(); + } + + while ((tt = tokenStream.getToken()) > TOK_EOF) { + Node nextMember; + if (tt == TOK_DOT) { + tt = tokenStream.getToken(TokenStream::KeywordIsName); + if (tt == TOK_ERROR) + return null(); + if (tt == TOK_NAME) { + PropertyName *field = tokenStream.currentName(); + nextMember = handler.newPropertyAccess(lhs, field, pos().end); + if (!nextMember) + return null(); + } else { + report(ParseError, false, null(), JSMSG_NAME_AFTER_DOT); + return null(); + } + } else if (tt == TOK_LB) { + Node propExpr = expr(); + if (!propExpr) + return null(); + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX); + + nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end); + if (!nextMember) + return null(); + } else if (allowCallSyntax && tt == TOK_LP) { + JSOp op = JSOP_CALL; + nextMember = handler.newList(PNK_CALL, null(), JSOP_CALL); + if (!nextMember) + return null(); + + if (JSAtom *atom = handler.isName(lhs)) { + if (atom == context->names().eval) { + /* Select JSOP_EVAL and flag pc as heavyweight. */ + op = JSOP_EVAL; + pc->sc->setBindingsAccessedDynamically(); + + /* + * In non-strict mode code, direct calls to eval can add + * variables to the call object. + */ + if (pc->sc->isFunctionBox() && !pc->sc->strict) + pc->sc->asFunctionBox()->setHasExtensibleScope(); + } + } else if (JSAtom *atom = handler.isGetProp(lhs)) { + /* Select JSOP_FUNAPPLY given foo.apply(...). */ + if (atom == context->names().apply) { + op = JSOP_FUNAPPLY; + if (pc->sc->isFunctionBox()) + pc->sc->asFunctionBox()->usesApply = true; + } else if (atom == context->names().call) { + op = JSOP_FUNCALL; + } + } + + handler.setBeginPosition(nextMember, lhs); + handler.addList(nextMember, lhs); + + bool isSpread = false; + if (!argumentList(nextMember, &isSpread)) + return null(); + if (isSpread) + op = (op == JSOP_EVAL ? JSOP_SPREADEVAL : JSOP_SPREADCALL); + handler.setOp(nextMember, op); + } else { + tokenStream.ungetToken(); + return lhs; + } + + lhs = nextMember; + } + if (tt == TOK_ERROR) + return null(); + return lhs; +} + +template +typename ParseHandler::Node +Parser::newName(PropertyName *name) +{ + return handler.newName(name, pc->blockid(), pos()); +} + +template +typename ParseHandler::Node +Parser::identifierName() +{ + RootedPropertyName name(context, tokenStream.currentName()); + Node pn = newName(name); + if (!pn) + return null(); + + if (!pc->inDeclDestructuring && !noteNameUse(name, pn)) + return null(); + + return pn; +} + +template +typename ParseHandler::Node +Parser::stringLiteral() +{ + JSAtom *atom = tokenStream.currentToken().atom(); + + // Large strings are fast to parse but slow to compress. Stop compression on + // them, so we don't wait for a long time for compression to finish at the + // end of compilation. + const size_t HUGE_STRING = 50000; + if (sct && sct->active() && atom->length() >= HUGE_STRING) + sct->abort(); + + return handler.newStringLiteral(atom, pos()); +} + +template +typename ParseHandler::Node +Parser::newRegExp() +{ + // Create the regexp even when doing a syntax parse, to check the regexp's syntax. + const jschar *chars = tokenStream.getTokenbuf().begin(); + size_t length = tokenStream.getTokenbuf().length(); + RegExpFlag flags = tokenStream.currentToken().regExpFlags(); + + Rooted reobj(context); + if (RegExpStatics *res = context->global()->getRegExpStatics()) + reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream); + else + reobj = RegExpObject::createNoStatics(context, chars, length, flags, &tokenStream); + + if (!reobj) + return null(); + + return handler.newRegExp(reobj, pos(), *this); +} + +template +typename ParseHandler::Node +Parser::arrayInitializer() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LB)); + + uint32_t begin = pos().begin; + Node literal = handler.newArrayLiteral(begin, pc->blockidGen); + if (!literal) + return null(); + + if (tokenStream.matchToken(TOK_RB, TokenStream::Operand)) { + /* + * Mark empty arrays as non-constant, since we cannot easily + * determine their type. + */ + handler.setListFlag(literal, PNX_NONCONST); + } else if (tokenStream.matchToken(TOK_FOR, TokenStream::Operand)) { + // ES6 array comprehension. + return arrayComprehension(begin); + } else { + bool spread = false, missingTrailingComma = false; + uint32_t index = 0; + for (; ; index++) { + if (index == JSObject::NELEMENTS_LIMIT) { + report(ParseError, false, null(), JSMSG_ARRAY_INIT_TOO_BIG); + return null(); + } + + TokenKind tt = tokenStream.peekToken(TokenStream::Operand); + if (tt == TOK_RB) + break; + + if (tt == TOK_COMMA) { + tokenStream.consumeKnownToken(TOK_COMMA); + if (!handler.addElision(literal, pos())) + return null(); + } else if (tt == TOK_TRIPLEDOT) { + spread = true; + tokenStream.consumeKnownToken(TOK_TRIPLEDOT); + uint32_t begin = pos().begin; + Node inner = assignExpr(); + if (!inner) + return null(); + if (!handler.addSpreadElement(literal, begin, inner)) + return null(); + } else { + Node element = assignExpr(); + if (!element) + return null(); + if (foldConstants && !FoldConstants(context, &element, this)) + return null(); + if (!handler.addArrayElement(literal, element)) + return null(); + } + + if (tt != TOK_COMMA) { + /* If we didn't already match TOK_COMMA in above case. */ + if (!tokenStream.matchToken(TOK_COMMA)) { + missingTrailingComma = true; + break; + } + } + } + + /* + * At this point, (index == 0 && missingTrailingComma) implies one + * element initialiser was parsed. + * + * A legacy 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. A legacy + * array comprehension opens exactly one block scope, no matter how many + * for heads it contains. + * + * Each let () {...} or for (let ...) ... compiles to: + * + * JSOP_PUSHN // Push space for block-scoped locals. + * (JSOP_PUSHBLOCKSCOPE ) // If a local is aliased, push on scope + * // chain. + * ... + * JSOP_DEBUGLEAVEBLOCK // Invalidate any DebugScope proxies. + * JSOP_POPBLOCKSCOPE? // Pop off scope chain, if needed. + * JSOP_POPN // Pop space for block-scoped locals. + * + * 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. 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 legacy array comprehension iteration step, array.push(i * j) in + * the example above, is done by ; JSOP_ARRAYPUSH , where + * is the index of array's stack slot. + */ + if (index == 0 && !spread && tokenStream.matchToken(TOK_FOR) && missingTrailingComma) + return legacyArrayComprehension(literal); + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_LIST); + } + handler.setEndPosition(literal, pos().end); + return literal; +} + +static JSAtom* +DoubleToAtom(ExclusiveContext *cx, double value) +{ + // This is safe because doubles can not be moved. + Value tmp = DoubleValue(value); + return ToAtom(cx, HandleValue::fromMarkedLocation(&tmp)); +} + +template +typename ParseHandler::Node +Parser::objectLiteral() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LC)); + + /* + * A map from property names we've seen thus far to a mask of property + * assignment types. + */ + AtomIndexMap seen; + + enum AssignmentType { + GET = 0x1, + SET = 0x2, + VALUE = 0x4 | GET | SET + }; + + Node literal = handler.newObjectLiteral(pos().begin); + if (!literal) + return null(); + + RootedAtom atom(context); + for (;;) { + TokenKind ltok = tokenStream.getToken(TokenStream::KeywordIsName); + if (ltok == TOK_RC) + break; + + JSOp op = JSOP_INITPROP; + Node propname; + switch (ltok) { + case TOK_NUMBER: + atom = DoubleToAtom(context, tokenStream.currentToken().number()); + if (!atom) + return null(); + propname = newNumber(tokenStream.currentToken()); + break; + + case TOK_NAME: { + atom = tokenStream.currentName(); + if (atom == context->names().get) { + op = JSOP_INITPROP_GETTER; + } else if (atom == context->names().set) { + op = JSOP_INITPROP_SETTER; + } else { + propname = handler.newIdentifier(atom, pos()); + if (!propname) + return null(); + break; + } + + // We have parsed |get| or |set|. Look for an accessor property + // name next. + TokenKind tt = tokenStream.getToken(TokenStream::KeywordIsName); + if (tt == TOK_NAME) { + atom = tokenStream.currentName(); + propname = newName(atom->asPropertyName()); + if (!propname) + return null(); + } else if (tt == TOK_STRING) { + atom = tokenStream.currentToken().atom(); + + uint32_t index; + if (atom->isIndex(&index)) { + propname = handler.newNumber(index, NoDecimal, pos()); + if (!propname) + return null(); + atom = DoubleToAtom(context, index); + if (!atom) + return null(); + } else { + propname = stringLiteral(); + if (!propname) + return null(); + } + } else if (tt == TOK_NUMBER) { + atom = DoubleToAtom(context, tokenStream.currentToken().number()); + if (!atom) + return null(); + propname = newNumber(tokenStream.currentToken()); + if (!propname) + return null(); + } else { + // Not an accessor property after all. + tokenStream.ungetToken(); + propname = handler.newIdentifier(atom, pos()); + if (!propname) + return null(); + op = JSOP_INITPROP; + break; + } + + JS_ASSERT(op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER); + break; + } + + case TOK_STRING: { + atom = tokenStream.currentToken().atom(); + uint32_t index; + if (atom->isIndex(&index)) { + propname = handler.newNumber(index, NoDecimal, pos()); + if (!propname) + return null(); + } else { + propname = stringLiteral(); + if (!propname) + return null(); + } + break; + } + + default: + report(ParseError, false, null(), JSMSG_BAD_PROP_ID); + return null(); + } + + if (op == JSOP_INITPROP) { + TokenKind tt = tokenStream.getToken(); + Node propexpr; + if (tt == TOK_COLON) { + propexpr = assignExpr(); + if (!propexpr) + return null(); + + if (foldConstants && !FoldConstants(context, &propexpr, this)) + 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 (!handler.isConstant(propexpr) || atom == context->names().proto) + handler.setListFlag(literal, PNX_NONCONST); + + if (!handler.addPropertyDefinition(literal, propname, propexpr)) + return null(); + } + 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. + */ + if (!abortIfSyntaxParser()) + return null(); + tokenStream.ungetToken(); + if (!tokenStream.checkForKeyword(atom->charsZ(), atom->length(), nullptr)) + return null(); + PropertyName *name = handler.isName(propname); + JS_ASSERT(atom); + propname = newName(name); + if (!propname) + return null(); + if (!handler.addShorthandPropertyDefinition(literal, propname)) + return null(); + } + else { + report(ParseError, false, null(), JSMSG_COLON_AFTER_ID); + return null(); + } + } else { + /* NB: Getter function in { get x(){} } is unnamed. */ + Rooted funName(context, nullptr); + TokenStream::Position start(keepAtoms); + tokenStream.tell(&start); + Node accessor = functionDef(funName, start, op == JSOP_INITPROP_GETTER ? Getter : Setter, + Expression, NotGenerator); + if (!accessor) + return null(); + if (!handler.addAccessorPropertyDefinition(literal, propname, accessor, op)) + return null(); + } + + /* + * 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_INITPROP_GETTER) + assignType = GET; + else if (op == JSOP_INITPROP_SETTER) + assignType = SET; + else + MOZ_ASSUME_UNREACHABLE("bad opcode in object initializer"); + + AtomIndexAddPtr p = seen.lookupForAdd(atom); + if (p) { + jsatomid index = p.value(); + AssignmentType oldAssignType = AssignmentType(index); + if ((oldAssignType & assignType) && + (oldAssignType != VALUE || assignType != VALUE || pc->sc->needStrictChecks())) + { + JSAutoByteString name; + if (!AtomToPrintableString(context, atom, &name)) + return null(); + + ParseReportKind reportKind = + (oldAssignType == VALUE && assignType == VALUE && !pc->sc->needStrictChecks()) + ? ParseWarning + : (pc->sc->needStrictChecks() ? ParseStrictError : ParseError); + if (!report(reportKind, pc->sc->strict, null(), + JSMSG_DUPLICATE_PROPERTY, name.ptr())) + { + return null(); + } + } + p.value() = assignType | oldAssignType; + } else { + if (!seen.add(p, atom, assignType)) + return null(); + } + + TokenKind tt = tokenStream.getToken(); + if (tt == TOK_RC) + break; + if (tt != TOK_COMMA) { + report(ParseError, false, null(), JSMSG_CURLY_AFTER_LIST); + return null(); + } + } + + handler.setEndPosition(literal, pos().end); + return literal; +} + +template +typename ParseHandler::Node +Parser::primaryExpr(TokenKind tt) +{ + JS_ASSERT(tokenStream.isCurrentTokenType(tt)); + JS_CHECK_RECURSION(context, return null()); + + switch (tt) { + case TOK_FUNCTION: + return functionExpr(); + + case TOK_LB: + return arrayInitializer(); + + case TOK_LC: + return objectLiteral(); + + case TOK_LET: + return letBlock(LetExpresion); + + case TOK_LP: + return parenExprOrGeneratorComprehension(); + + case TOK_STRING: + return stringLiteral(); + + case TOK_YIELD: + if (!checkYieldNameValidity()) + return null(); + // Fall through. + case TOK_NAME: + return identifierName(); + + case TOK_REGEXP: + return newRegExp(); + + case TOK_NUMBER: + return newNumber(tokenStream.currentToken()); + + case TOK_TRUE: + return handler.newBooleanLiteral(true, pos()); + case TOK_FALSE: + return handler.newBooleanLiteral(false, pos()); + case TOK_THIS: + return handler.newThisLiteral(pos()); + case TOK_NULL: + return handler.newNullLiteral(pos()); + + case TOK_RP: + // Not valid expression syntax, but this is valid in an arrow function + // with no params: `() => body`. + if (tokenStream.peekToken() == TOK_ARROW) { + tokenStream.ungetToken(); // put back right paren + + // Now just return something that will allow parsing to continue. + // It doesn't matter what; when we reach the =>, we will rewind and + // reparse the whole arrow function. See Parser::assignExpr. + return handler.newNullLiteral(pos()); + } + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); + + case TOK_TRIPLEDOT: + // Not valid expression syntax, but this is valid in an arrow function + // with a rest param: `(a, b, ...rest) => body`. + if (tokenStream.matchToken(TOK_NAME) && + tokenStream.matchToken(TOK_RP) && + tokenStream.peekToken() == TOK_ARROW) + { + tokenStream.ungetToken(); // put back right paren + + // Return an arbitrary expression node. See case TOK_RP above. + return handler.newNullLiteral(pos()); + } + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); + + case TOK_ERROR: + /* The scanner or one of its subroutines reported the error. */ + return null(); + + default: + report(ParseError, false, null(), JSMSG_SYNTAX_ERROR); + return null(); + } +} + +template +typename ParseHandler::Node +Parser::parenExprOrGeneratorComprehension() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LP)); + uint32_t begin = pos().begin; + uint32_t startYieldOffset = pc->lastYieldOffset; + + if (tokenStream.matchToken(TOK_FOR, TokenStream::Operand)) + return generatorComprehension(begin); + + /* + * 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. + */ + bool oldParsingForInit = pc->parsingForInit; + pc->parsingForInit = false; + Node pn = expr(); + pc->parsingForInit = oldParsingForInit; + + if (!pn) + return null(); + +#if JS_HAS_GENERATOR_EXPRS + if (tokenStream.matchToken(TOK_FOR)) { + if (pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); + return null(); + } + if (handler.isOperationWithoutParens(pn, PNK_COMMA)) { + report(ParseError, false, null(), + JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); + return null(); + } + pn = legacyGeneratorExpr(pn); + if (!pn) + return null(); + handler.setBeginPosition(pn, begin); + if (tokenStream.getToken() != TOK_RP) { + report(ParseError, false, null(), + JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); + return null(); + } + handler.setEndPosition(pn, pos().end); + handler.setInParens(pn); + return pn; + } +#endif /* JS_HAS_GENERATOR_EXPRS */ + + pn = handler.setInParens(pn); + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); + + return pn; +} + +// Legacy generator comprehensions can sometimes appear without parentheses. +// For example: +// +// foo(x for (x in bar)) +// +// In this case the parens are part of the call, and not part of the generator +// comprehension. This can happen in these contexts: +// +// if (_) +// while (_) {} +// do {} while (_) +// switch (_) {} +// with (_) {} +// foo(_) // must be first and only argument +// +// This is not the case for ES6 generator comprehensions; they must always be in +// parentheses. + +template +typename ParseHandler::Node +Parser::exprInParens() +{ + JS_ASSERT(tokenStream.isCurrentTokenType(TOK_LP)); + uint32_t begin = pos().begin; + uint32_t startYieldOffset = pc->lastYieldOffset; + + /* + * 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. + */ + bool oldParsingForInit = pc->parsingForInit; + pc->parsingForInit = false; + Node pn = expr(); + pc->parsingForInit = oldParsingForInit; + + if (!pn) + return null(); + +#if JS_HAS_GENERATOR_EXPRS + if (tokenStream.matchToken(TOK_FOR)) { + if (pc->lastYieldOffset != startYieldOffset) { + reportWithOffset(ParseError, false, pc->lastYieldOffset, + JSMSG_BAD_GENEXP_BODY, js_yield_str); + return null(); + } + if (handler.isOperationWithoutParens(pn, PNK_COMMA)) { + report(ParseError, false, null(), + JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); + return null(); + } + pn = legacyGeneratorExpr(pn); + if (!pn) + return null(); + handler.setBeginPosition(pn, begin); + } +#endif /* JS_HAS_GENERATOR_EXPRS */ + + return pn; +} + +template class Parser; +template class Parser; + +} /* namespace frontend */ +} /* namespace js */