void DumpAtomDefnMap(const AtomDefnMapPtr &map) { if (map->empty()) { fprintf(stderr, "empty\n"); return; } for (AtomDefnRange r = map->all(); !r.empty(); r.popFront()) { fprintf(stderr, "atom: "); js_DumpAtom(r.front().key()); fprintf(stderr, "defn: %p\n", (void *) r.front().value()); } }
static bool MaybeCheckEvalFreeVariables(ExclusiveContext *cxArg, HandleScript evalCaller, HandleObject scopeChain, Parser<FullParseHandler> &parser, ParseContext<FullParseHandler> &pc) { if (!evalCaller || !evalCaller->functionOrCallerFunction()) return true; // Eval scripts are only compiled on the main thread. JSContext *cx = cxArg->asJSContext(); // Watch for uses of 'arguments' within the evaluated script, both as // free variables and as variables redeclared with 'var'. RootedFunction fun(cx, evalCaller->functionOrCallerFunction()); HandlePropertyName arguments = cx->names().arguments; for (AtomDefnRange r = pc.lexdeps->all(); !r.empty(); r.popFront()) { if (r.front().key() == arguments) { if (!CheckArgumentsWithinEval(cx, parser, fun)) return false; } } for (AtomDefnListMap::Range r = pc.decls().all(); !r.empty(); r.popFront()) { if (r.front().key() == arguments) { if (!CheckArgumentsWithinEval(cx, parser, fun)) return false; } } // If the eval'ed script contains any debugger statement, force construction // of arguments objects for the caller script and any other scripts it is // transitively nested inside. The debugger can access any variable on the // scope chain. if (pc.sc->hasDebuggerStatement()) { RootedObject scope(cx, scopeChain); while (scope->is<ScopeObject>() || scope->is<DebugScopeObject>()) { if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { RootedScript script(cx, scope->as<CallObject>().callee().getOrCreateScript(cx)); if (!script) return false; if (script->argumentsHasVarBinding()) { if (!JSScript::argumentsOptimizationFailed(cx, script)) return false; } } scope = scope->enclosingScope(); } } return true; }
static void SetFunctionKinds(FunctionBox *funbox, uint32 *tcflags, bool isDirectEval) { for (; funbox; funbox = funbox->siblings) { ParseNode *fn = funbox->node; ParseNode *pn = fn->pn_body; if (funbox->kids) SetFunctionKinds(funbox->kids, tcflags, isDirectEval); JSFunction *fun = funbox->function(); JS_ASSERT(fun->kind() == JSFUN_INTERPRETED); if (funbox->tcflags & TCF_FUN_HEAVYWEIGHT) { /* nothing to do */ } else if (isDirectEval || funbox->inAnyDynamicScope()) { /* * Either we are in a with-block or a function scope that is * subject to direct eval; or we are compiling strict direct eval * code. * * In either case, fun may reference names that are not bound but * are not necessarily global either. (In the strict direct eval * case, we could bind them, but currently do not bother; see * the comment about strict mode code in BindTopLevelVar.) */ JS_ASSERT(!fun->isNullClosure()); } else { bool hasUpvars = false; bool canFlatten = true; if (pn->isKind(PNK_UPVARS)) { AtomDefnMapPtr upvars = pn->pn_names; JS_ASSERT(!upvars->empty()); /* * For each lexical dependency from this closure to an outer * binding, analyze whether it is safe to copy the binding's * value into a flat closure slot when the closure is formed. */ for (AtomDefnRange r = upvars->all(); !r.empty(); r.popFront()) { Definition *defn = r.front().value(); Definition *lexdep = defn->resolve(); if (!lexdep->isFreeVar()) { hasUpvars = true; if (!CanFlattenUpvar(lexdep, funbox, *tcflags)) { /* * Can't flatten. Enclosing functions holding * variables used by this function will be flagged * heavyweight below. FIXME bug 545759: re-enable * partial flat closures. */ canFlatten = false; break; } } } } if (!hasUpvars) { /* No lexical dependencies => null closure, for best performance. */ fun->setKind(JSFUN_NULL_CLOSURE); } else if (canFlatten) { fun->setKind(JSFUN_FLAT_CLOSURE); switch (fn->getOp()) { case JSOP_DEFFUN: fn->setOp(JSOP_DEFFUN_FC); break; case JSOP_DEFLOCALFUN: fn->setOp(JSOP_DEFLOCALFUN_FC); break; case JSOP_LAMBDA: fn->setOp(JSOP_LAMBDA_FC); break; default: /* js::frontend::EmitTree's PNK_FUNCTION case sets op. */ JS_ASSERT(fn->isOp(JSOP_NOP)); } } } if (fun->kind() == JSFUN_INTERPRETED && pn->isKind(PNK_UPVARS)) { /* * One or more upvars cannot be safely snapshot into a flat * closure's non-reserved slot (see JSOP_GETFCSLOT), so we loop * again over all upvars, and for each non-free upvar, ensure that * its containing function has been flagged as heavyweight. * * The emitter must see TCF_FUN_HEAVYWEIGHT accurately before * generating any code for a tree of nested functions. */ AtomDefnMapPtr upvars = pn->pn_names; JS_ASSERT(!upvars->empty()); for (AtomDefnRange r = upvars->all(); !r.empty(); r.popFront()) { Definition *defn = r.front().value(); Definition *lexdep = defn->resolve(); if (!lexdep->isFreeVar()) FlagHeavyweights(lexdep, funbox, tcflags); } } if (funbox->joinable()) fun->setJoinable(); } }
static bool MarkFunArgs(JSContext *cx, FunctionBox *funbox, uint32 functionCount) { FunctionBoxQueue queue; if (!queue.init(functionCount)) { js_ReportOutOfMemory(cx); return false; } FindFunArgs(funbox, -1, &queue); while ((funbox = queue.pull()) != NULL) { ParseNode *fn = funbox->node; JS_ASSERT(fn->isFunArg()); ParseNode *pn = fn->pn_body; if (pn->isKind(PNK_UPVARS)) { AtomDefnMapPtr upvars = pn->pn_names; JS_ASSERT(!upvars->empty()); for (AtomDefnRange r = upvars->all(); !r.empty(); r.popFront()) { Definition *defn = r.front().value(); Definition *lexdep = defn->resolve(); if (!lexdep->isFreeVar() && !lexdep->isFunArg() && (lexdep->kind() == Definition::FUNCTION || lexdep->isOp(JSOP_CALLEE))) { /* * Mark this formerly-Algol-like function as an escaping * function (i.e., as a funarg), because it is used from * another funarg. * * Progress is guaranteed because we set the funarg flag * here, which suppresses revisiting this function (thanks * to the !lexdep->isFunArg() test just above). */ lexdep->setFunArg(); FunctionBox *afunbox; if (lexdep->isOp(JSOP_CALLEE)) { /* * A named function expression will not appear to be a * funarg if it is immediately applied. However, if its * name is used in an escaping function nested within * it, then it must become flagged as a funarg again. * See bug 545980. */ afunbox = funbox; uintN calleeLevel = lexdep->pn_cookie.level(); uintN staticLevel = afunbox->level + 1U; while (staticLevel != calleeLevel) { afunbox = afunbox->parent; --staticLevel; } JS_ASSERT(afunbox->level + 1U == calleeLevel); afunbox->node->setFunArg(); } else { afunbox = lexdep->pn_funbox; } queue.push(afunbox); /* * Walk over nested functions again, now that we have * changed the level across which it is unsafe to access * upvars using the runtime dynamic link (frame chain). */ if (afunbox->kids) FindFunArgs(afunbox->kids, afunbox->level, &queue); } } } } return true; }
/* * Mark as funargs any functions that reach up to one or more upvars across an * already-known funarg. The parser will flag the o_m lambda as a funarg in: * * function f(o, p) { * o.m = function o_m(a) { * function g() { return p; } * function h() { return a; } * return g() + h(); * } * } * * but without this extra marking phase, function g will not be marked as a * funarg since it is called from within its parent scope. But g reaches up to * f's parameter p, so if o_m escapes f's activation scope, g does too and * cannot assume that p's stack slot is still alive. In contast function h * neither escapes nor uses an upvar "above" o_m's level. * * If function g itself contained lambdas that contained non-lambdas that reach * up above its level, then those non-lambdas would have to be marked too. This * process is potentially exponential in the number of functions, but generally * not so complex. But it can't be done during a single recursive traversal of * the funbox tree, so we must use a work queue. * * Return the minimal "skipmin" for funbox and its siblings. This is the delta * between the static level of the bodies of funbox and its peers (which must * be funbox->level + 1), and the static level of the nearest upvar among all * the upvars contained by funbox and its peers. If there are no upvars, return * FREE_STATIC_LEVEL. Thus this function never returns 0. */ static uintN FindFunArgs(FunctionBox *funbox, int level, FunctionBoxQueue *queue) { uintN allskipmin = UpvarCookie::FREE_LEVEL; do { ParseNode *fn = funbox->node; JS_ASSERT(fn->isArity(PN_FUNC)); int fnlevel = level; /* * An eval can leak funbox, functions along its ancestor line, and its * immediate kids. Since FindFunArgs uses DFS and the parser propagates * TCF_FUN_HEAVYWEIGHT bottom up, funbox's ancestor function nodes have * already been marked as funargs by this point. Therefore we have to * flag only funbox->node and funbox->kids' nodes here. * * Generators need to be treated in the same way. Even if the value * of a generator function doesn't escape, anything defined or referred * to inside the generator can escape through a call to the generator. * We could imagine doing static analysis to track the calls and see * if any iterators or values returned by iterators escape, but that * would be hard, so instead we just assume everything might escape. */ if (funbox->tcflags & (TCF_FUN_HEAVYWEIGHT | TCF_FUN_IS_GENERATOR)) { fn->setFunArg(); for (FunctionBox *kid = funbox->kids; kid; kid = kid->siblings) kid->node->setFunArg(); } /* * Compute in skipmin the least distance from fun's static level up to * an upvar, whether used directly by fun, or indirectly by a function * nested in fun. */ uintN skipmin = UpvarCookie::FREE_LEVEL; ParseNode *pn = fn->pn_body; if (pn->isKind(PNK_UPVARS)) { AtomDefnMapPtr &upvars = pn->pn_names; JS_ASSERT(upvars->count() != 0); for (AtomDefnRange r = upvars->all(); !r.empty(); r.popFront()) { Definition *defn = r.front().value(); Definition *lexdep = defn->resolve(); if (!lexdep->isFreeVar()) { uintN upvarLevel = lexdep->frameLevel(); if (int(upvarLevel) <= fnlevel) fn->setFunArg(); uintN skip = (funbox->level + 1) - upvarLevel; if (skip < skipmin) skipmin = skip; } } } /* * If this function escapes, whether directly (the parser detects such * escapes) or indirectly (because this non-escaping function uses an * upvar that reaches across an outer function boundary where the outer * function escapes), enqueue it for further analysis, and bump fnlevel * to trap any non-escaping children. */ if (fn->isFunArg()) { queue->push(funbox); fnlevel = int(funbox->level); } /* * Now process the current function's children, and recalibrate their * cumulative skipmin to be relative to the current static level. */ if (funbox->kids) { uintN kidskipmin = FindFunArgs(funbox->kids, fnlevel, queue); JS_ASSERT(kidskipmin != 0); if (kidskipmin != UpvarCookie::FREE_LEVEL) { --kidskipmin; if (kidskipmin != 0 && kidskipmin < skipmin) skipmin = kidskipmin; } } /* * Finally, after we've traversed all of the current function's kids, * minimize allskipmin against our accumulated skipmin. Minimize across * funbox and all of its siblings, to compute our return value. */ if (skipmin != UpvarCookie::FREE_LEVEL) { if (skipmin < allskipmin) allskipmin = skipmin; } } while ((funbox = funbox->siblings) != NULL); return allskipmin; }
JSScript * frontend::CompileScript(JSContext *cx, HandleObject scopeChain, StackFrame *callerFrame, const CompileOptions &options, const jschar *chars, size_t length, JSString *source_ /* = NULL */, unsigned staticLevel /* = 0 */) { RootedString source(cx, source_); class ProbesManager { const char* filename; unsigned lineno; public: ProbesManager(const char *f, unsigned l) : filename(f), lineno(l) { Probes::compileScriptBegin(filename, lineno); } ~ProbesManager() { Probes::compileScriptEnd(filename, lineno); } }; ProbesManager probesManager(options.filename, options.lineno); /* * The scripted callerFrame can only be given for compile-and-go scripts * and non-zero static level requires callerFrame. */ JS_ASSERT_IF(callerFrame, options.compileAndGo); JS_ASSERT_IF(staticLevel != 0, callerFrame); if (!CheckLength(cx, length)) return NULL; ScriptSource *ss = cx->new_<ScriptSource>(); if (!ss) return NULL; AutoAttachToRuntime attacher(cx->runtime, ss); SourceCompressionToken sct(cx); if (!cx->hasRunOption(JSOPTION_ONLY_CNG_SOURCE) || options.compileAndGo) { if (!ss->setSourceCopy(cx, chars, length, false, &sct)) return NULL; } Parser parser(cx, options, chars, length, /* foldConstants = */ true); if (!parser.init()) return NULL; parser.sct = &sct; SharedContext sc(cx, scopeChain, /* fun = */ NULL, /* funbox = */ NULL, StrictModeFromContext(cx)); TreeContext tc(&parser, &sc, staticLevel, /* bodyid = */ 0); if (!tc.init()) return NULL; bool savedCallerFun = options.compileAndGo && callerFrame && callerFrame->isFunctionFrame(); Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun, options, staticLevel, ss, 0, length)); if (!script) return NULL; // We can specialize a bit for the given scope chain if that scope chain is the global object. JSObject *globalScope = scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : NULL; JS_ASSERT_IF(globalScope, globalScope->isNative()); JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass())); BytecodeEmitter bce(/* parent = */ NULL, &parser, &sc, script, callerFrame, !!globalScope, options.lineno); if (!bce.init()) return NULL; /* If this is a direct call to eval, inherit the caller's strictness. */ if (callerFrame && callerFrame->isScriptFrame() && callerFrame->script()->strictModeCode) sc.strictModeState = StrictMode::STRICT; if (options.compileAndGo) { if (source) { /* * Save eval program source in script->atoms[0] for the * eval cache (see EvalCacheLookup in jsobj.cpp). */ JSAtom *atom = js_AtomizeString(cx, source); jsatomid _; if (!atom || !bce.makeAtomIndex(atom, &_)) return NULL; } if (callerFrame && callerFrame->isFunctionFrame()) { /* * An eval script in a caller frame needs to have its enclosing * function captured in case it refers to an upvar, and someone * wishes to decompile it while it's running. */ ObjectBox *funbox = parser.newObjectBox(callerFrame->fun()); if (!funbox) return NULL; funbox->emitLink = bce.objectList.lastbox; bce.objectList.lastbox = funbox; bce.objectList.length++; } } ParseNode *pn; #if JS_HAS_XML_SUPPORT pn = NULL; bool onlyXML; onlyXML = true; #endif TokenStream &tokenStream = parser.tokenStream; { ParseNode *stringsAtStart = ListNode::create(PNK_STATEMENTLIST, &parser); if (!stringsAtStart) return NULL; stringsAtStart->makeEmpty(); bool ok = parser.processDirectives(stringsAtStart) && EmitTree(cx, &bce, stringsAtStart); parser.freeTree(stringsAtStart); if (!ok) return NULL; } JS_ASSERT(sc.strictModeState != StrictMode::UNKNOWN); for (;;) { TokenKind tt = tokenStream.peekToken(TSF_OPERAND); if (tt <= TOK_EOF) { if (tt == TOK_EOF) break; JS_ASSERT(tt == TOK_ERROR); return NULL; } pn = parser.statement(); if (!pn) return NULL; if (!FoldConstants(cx, pn, &parser)) return NULL; if (!AnalyzeFunctions(&parser, callerFrame)) return NULL; tc.functionList = NULL; if (!EmitTree(cx, &bce, pn)) return NULL; #if JS_HAS_XML_SUPPORT if (!pn->isKind(PNK_SEMI) || !pn->pn_kid || !pn->pn_kid->isXMLItem()) onlyXML = false; #endif parser.freeTree(pn); } #if JS_HAS_XML_SUPPORT /* * Prevent XML data theft via <script src="http://victim.com/foo.xml">. * For background, see: * * https://bugzilla.mozilla.org/show_bug.cgi?id=336551 */ if (pn && onlyXML && !callerFrame) { parser.reportError(NULL, JSMSG_XML_WHOLE_PROGRAM); return NULL; } #endif // It's an error to use |arguments| in a function that has a rest parameter. if (callerFrame && callerFrame->isFunctionFrame() && callerFrame->fun()->hasRest()) { PropertyName *arguments = cx->runtime->atomState.argumentsAtom; for (AtomDefnRange r = tc.lexdeps->all(); !r.empty(); r.popFront()) { if (r.front().key() == arguments) { parser.reportError(NULL, JSMSG_ARGUMENTS_AND_REST); return NULL; } } // We're not in a function context, so we don't expect any bindings. JS_ASSERT(!sc.bindings.hasBinding(cx, arguments)); } /* * Nowadays the threaded interpreter needs a stop instruction, so we * do have to emit that here. */ if (Emit1(cx, &bce, JSOP_STOP) < 0) return NULL; if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return NULL; bce.tellDebuggerAboutCompiledScript(cx); return script; }
UnrootedScript frontend::CompileScript(JSContext *cx, HandleObject scopeChain, AbstractFramePtr callerFrame, const CompileOptions &options, StableCharPtr chars, size_t length, JSString *source_ /* = NULL */, unsigned staticLevel /* = 0 */) { RootedString source(cx, source_); class ProbesManager { const char* filename; unsigned lineno; public: ProbesManager(const char *f, unsigned l) : filename(f), lineno(l) { Probes::compileScriptBegin(filename, lineno); } ~ProbesManager() { Probes::compileScriptEnd(filename, lineno); } }; ProbesManager probesManager(options.filename, options.lineno); /* * The scripted callerFrame can only be given for compile-and-go scripts * and non-zero static level requires callerFrame. */ JS_ASSERT_IF(callerFrame, options.compileAndGo); JS_ASSERT_IF(staticLevel != 0, callerFrame); if (!CheckLength(cx, length)) return UnrootedScript(NULL); JS_ASSERT_IF(staticLevel != 0, options.sourcePolicy != CompileOptions::LAZY_SOURCE); ScriptSource *ss = cx->new_<ScriptSource>(); if (!ss) return UnrootedScript(NULL); ScriptSourceHolder ssh(ss); SourceCompressionToken sct(cx); switch (options.sourcePolicy) { case CompileOptions::SAVE_SOURCE: if (!ss->setSourceCopy(cx, chars, length, false, &sct)) return UnrootedScript(NULL); break; case CompileOptions::LAZY_SOURCE: ss->setSourceRetrievable(); break; case CompileOptions::NO_SOURCE: break; } Parser parser(cx, options, chars, length, /* foldConstants = */ true); if (!parser.init()) return UnrootedScript(NULL); parser.sct = &sct; GlobalSharedContext globalsc(cx, scopeChain, StrictModeFromContext(cx)); ParseContext pc(&parser, &globalsc, staticLevel, /* bodyid = */ 0); if (!pc.init()) return UnrootedScript(NULL); bool savedCallerFun = options.compileAndGo && callerFrame && callerFrame.isFunctionFrame(); Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun, options, staticLevel, ss, 0, length)); if (!script) return UnrootedScript(NULL); // Global/eval script bindings are always empty (all names are added to the // scope dynamically via JSOP_DEFFUN/VAR). InternalHandle<Bindings*> bindings(script, &script->bindings); if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, NULL)) return UnrootedScript(NULL); // We can specialize a bit for the given scope chain if that scope chain is the global object. JSObject *globalScope = scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : NULL; JS_ASSERT_IF(globalScope, globalScope->isNative()); JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass())); BytecodeEmitter bce(/* parent = */ NULL, &parser, &globalsc, script, callerFrame, !!globalScope, options.lineno, options.selfHostingMode); if (!bce.init()) return UnrootedScript(NULL); /* If this is a direct call to eval, inherit the caller's strictness. */ if (callerFrame && callerFrame.script()->strict) globalsc.strict = true; if (options.compileAndGo) { if (source) { /* * Save eval program source in script->atoms[0] for the * eval cache (see EvalCacheLookup in jsobj.cpp). */ JSAtom *atom = AtomizeString<CanGC>(cx, source); jsatomid _; if (!atom || !bce.makeAtomIndex(atom, &_)) return UnrootedScript(NULL); } if (callerFrame && callerFrame.isFunctionFrame()) { /* * An eval script in a caller frame needs to have its enclosing * function captured in case it refers to an upvar, and someone * wishes to decompile it while it's running. */ JSFunction *fun = callerFrame.fun(); ObjectBox *funbox = parser.newFunctionBox(fun, &pc, fun->strict()); if (!funbox) return UnrootedScript(NULL); bce.objectList.add(funbox); } } TokenStream &tokenStream = parser.tokenStream; bool canHaveDirectives = true; for (;;) { TokenKind tt = tokenStream.peekToken(TSF_OPERAND); if (tt <= TOK_EOF) { if (tt == TOK_EOF) break; JS_ASSERT(tt == TOK_ERROR); return UnrootedScript(NULL); } ParseNode *pn = parser.statement(); if (!pn) return UnrootedScript(NULL); if (canHaveDirectives) { if (!parser.maybeParseDirective(pn, &canHaveDirectives)) return UnrootedScript(NULL); } if (!FoldConstants(cx, &pn, &parser)) return UnrootedScript(NULL); if (!NameFunctions(cx, pn)) return UnrootedScript(NULL); if (!EmitTree(cx, &bce, pn)) return UnrootedScript(NULL); parser.freeTree(pn); } if (!SetSourceMap(cx, tokenStream, ss, script)) return UnrootedScript(NULL); // It's an error to use |arguments| in a function that has a rest parameter. if (callerFrame && callerFrame.isFunctionFrame() && callerFrame.fun()->hasRest()) { HandlePropertyName arguments = cx->names().arguments; for (AtomDefnRange r = pc.lexdeps->all(); !r.empty(); r.popFront()) { if (r.front().key() == arguments) { parser.reportError(NULL, JSMSG_ARGUMENTS_AND_REST); return UnrootedScript(NULL); } } } /* * Nowadays the threaded interpreter needs a stop instruction, so we * do have to emit that here. */ if (Emit1(cx, &bce, JSOP_STOP) < 0) return UnrootedScript(NULL); if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return UnrootedScript(NULL); bce.tellDebuggerAboutCompiledScript(cx); if (!sct.complete()) return UnrootedScript(NULL); return script; }
RawScript frontend::CompileScript(JSContext *cx, HandleObject scopeChain, HandleScript evalCaller, const CompileOptions &options, const jschar *chars, size_t length, JSString *source_ /* = NULL */, unsigned staticLevel /* = 0 */, SourceCompressionToken *extraSct /* = NULL */) { RootedString source(cx, source_); /* * The scripted callerFrame can only be given for compile-and-go scripts * and non-zero static level requires callerFrame. */ JS_ASSERT_IF(evalCaller, options.compileAndGo); JS_ASSERT_IF(staticLevel != 0, evalCaller); if (!CheckLength(cx, length)) return NULL; JS_ASSERT_IF(staticLevel != 0, options.sourcePolicy != CompileOptions::LAZY_SOURCE); ScriptSource *ss = cx->new_<ScriptSource>(); if (!ss) return NULL; if (options.filename && !ss->setFilename(cx, options.filename)) return NULL; ScriptSourceHolder ssh(ss); SourceCompressionToken mysct(cx); SourceCompressionToken *sct = (extraSct) ? extraSct : &mysct; switch (options.sourcePolicy) { case CompileOptions::SAVE_SOURCE: if (!ss->setSourceCopy(cx, chars, length, false, sct)) return NULL; break; case CompileOptions::LAZY_SOURCE: ss->setSourceRetrievable(); break; case CompileOptions::NO_SOURCE: break; } Parser<FullParseHandler> parser(cx, options, chars, length, /* foldConstants = */ true); if (!parser.init()) return NULL; parser.sct = sct; GlobalSharedContext globalsc(cx, scopeChain, StrictModeFromContext(cx)); ParseContext<FullParseHandler> pc(&parser, NULL, &globalsc, staticLevel, /* bodyid = */ 0); if (!pc.init()) return NULL; bool savedCallerFun = options.compileAndGo && evalCaller && (evalCaller->function() || evalCaller->savedCallerFun); Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun, options, staticLevel, ss, 0, length)); if (!script) return NULL; // Global/eval script bindings are always empty (all names are added to the // scope dynamically via JSOP_DEFFUN/VAR). InternalHandle<Bindings*> bindings(script, &script->bindings); if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, NULL)) return NULL; // We can specialize a bit for the given scope chain if that scope chain is the global object. JSObject *globalScope = scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : NULL; JS_ASSERT_IF(globalScope, globalScope->isNative()); JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass())); BytecodeEmitter bce(/* parent = */ NULL, &parser, &globalsc, script, evalCaller, !!globalScope, options.lineno, options.selfHostingMode); if (!bce.init()) return NULL; /* If this is a direct call to eval, inherit the caller's strictness. */ if (evalCaller && evalCaller->strict) globalsc.strict = true; if (options.compileAndGo) { if (source) { /* * Save eval program source in script->atoms[0] for the * eval cache (see EvalCacheLookup in jsobj.cpp). */ JSAtom *atom = AtomizeString<CanGC>(cx, source); jsatomid _; if (!atom || !bce.makeAtomIndex(atom, &_)) return NULL; } if (evalCaller && evalCaller->functionOrCallerFunction()) { /* * An eval script in a caller frame needs to have its enclosing * function captured in case it refers to an upvar, and someone * wishes to decompile it while it's running. */ JSFunction *fun = evalCaller->functionOrCallerFunction(); ObjectBox *funbox = parser.newFunctionBox(fun, &pc, fun->strict()); if (!funbox) return NULL; bce.objectList.add(funbox); } } bool canHaveDirectives = true; for (;;) { TokenKind tt = parser.tokenStream.peekToken(TSF_OPERAND); if (tt <= TOK_EOF) { if (tt == TOK_EOF) break; JS_ASSERT(tt == TOK_ERROR); return NULL; } ParseNode *pn = parser.statement(); if (!pn) return NULL; if (canHaveDirectives) { if (!parser.maybeParseDirective(pn, &canHaveDirectives)) return NULL; } if (!FoldConstants(cx, &pn, &parser)) return NULL; if (!NameFunctions(cx, pn)) return NULL; if (!EmitTree(cx, &bce, pn)) return NULL; parser.handler.freeTree(pn); } if (!SetSourceMap(cx, parser.tokenStream, ss, script)) return NULL; if (evalCaller && evalCaller->functionOrCallerFunction()) { // Watch for uses of 'arguments' within the evaluated script, both as // free variables and as variables redeclared with 'var'. RootedFunction fun(cx, evalCaller->functionOrCallerFunction()); HandlePropertyName arguments = cx->names().arguments; for (AtomDefnRange r = pc.lexdeps->all(); !r.empty(); r.popFront()) { if (r.front().key() == arguments) { if (!CheckArgumentsWithinEval(cx, parser, fun)) return NULL; } } for (AtomDefnListMap::Range r = pc.decls().all(); !r.empty(); r.popFront()) { if (r.front().key() == arguments) { if (!CheckArgumentsWithinEval(cx, parser, fun)) return NULL; } } // If the eval'ed script contains any debugger statement, force construction // of arguments objects for the caller script and any other scripts it is // transitively nested inside. if (pc.sc->hasDebuggerStatement()) { RootedObject scope(cx, scopeChain); while (scope->isScope() || scope->isDebugScope()) { if (scope->isCall() && !scope->asCall().isForEval()) { RootedScript script(cx, scope->asCall().callee().nonLazyScript()); if (script->argumentsHasVarBinding()) { if (!JSScript::argumentsOptimizationFailed(cx, script)) return NULL; } } scope = scope->enclosingScope(); } } } /* * Nowadays the threaded interpreter needs a stop instruction, so we * do have to emit that here. */ if (Emit1(cx, &bce, JSOP_STOP) < 0) return NULL; if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return NULL; bce.tellDebuggerAboutCompiledScript(cx); if (sct == &mysct && !sct->complete()) return NULL; return script; }
static void SetFunctionKinds(FunctionBox *funbox, bool *isHeavyweight, bool topInFunction, bool isDirectEval) { for (; funbox; funbox = funbox->siblings) { ParseNode *fn = funbox->node; if (!fn) continue; ParseNode *pn = fn->pn_body; if (!pn) continue; if (funbox->kids) SetFunctionKinds(funbox->kids, isHeavyweight, topInFunction, isDirectEval); JSFunction *fun = funbox->function(); JS_ASSERT(fun->kind() == JSFUN_INTERPRETED); if (funbox->funIsHeavyweight()) { /* nothing to do */ } else if (isDirectEval || funbox->inAnyDynamicScope()) { /* * Either we are in a with-block or a function scope that is * subject to direct eval; or we are compiling strict direct eval * code. * * In either case, fun may reference names that are not bound but * are not necessarily global either. (In the strict direct eval * case, we could bind them, but currently do not bother; see * the comment about strict mode code in BindTopLevelVar.) */ JS_ASSERT(!fun->isNullClosure()); } else { bool hasUpvars = false; if (pn->isKind(PNK_UPVARS)) { AtomDefnMapPtr upvars = pn->pn_names; JS_ASSERT(!upvars->empty()); /* Determine whether the this function contains upvars. */ for (AtomDefnRange r = upvars->all(); !r.empty(); r.popFront()) { if (!r.front().value()->resolve()->isFreeVar()) { hasUpvars = true; break; } } } if (!hasUpvars) { /* No lexical dependencies => null closure, for best performance. */ fun->setKind(JSFUN_NULL_CLOSURE); } } if (fun->kind() == JSFUN_INTERPRETED && pn->isKind(PNK_UPVARS)) { /* * We loop again over all upvars, and for each non-free upvar, * ensure that its containing function has been flagged as * heavyweight. * * The emitter must see funIsHeavyweight() accurately before * generating any code for a tree of nested functions. */ AtomDefnMapPtr upvars = pn->pn_names; JS_ASSERT(!upvars->empty()); for (AtomDefnRange r = upvars->all(); !r.empty(); r.popFront()) { Definition *defn = r.front().value(); Definition *lexdep = defn->resolve(); if (!lexdep->isFreeVar()) FlagHeavyweights(lexdep, funbox, isHeavyweight, topInFunction); } } } }