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; }
// Compile a JS function body, which might appear as the value of an event // handler attribute in an HTML <INPUT> tag, or in a Function() constructor. bool frontend::CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options, const AutoNameVector &formals, const jschar *chars, size_t length) { // FIXME: make Function pass in two strings and parse them as arguments and // ProgramElements respectively. SkipRoot skip(cx, &chars); MaybeCallSourceHandler(cx, options, chars, length); if (!CheckLength(cx, length)) return false; ScriptSource *ss = cx->new_<ScriptSource>(options.originPrincipals()); if (!ss) return false; if (options.filename && !ss->setFilename(cx, options.filename)) return false; JS::RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, ss)); if (!sourceObject) return false; SourceCompressionToken sct(cx); JS_ASSERT(options.sourcePolicy != CompileOptions::LAZY_SOURCE); if (options.sourcePolicy == CompileOptions::SAVE_SOURCE) { if (!ss->setSourceCopy(cx, chars, length, true, &sct)) return false; } bool canLazilyParse = CanLazilyParse(cx, options); Maybe<Parser<SyntaxParseHandler> > syntaxParser; if (canLazilyParse) { syntaxParser.construct(cx, &cx->tempLifoAlloc(), options, chars, length, /* foldConstants = */ false, (Parser<SyntaxParseHandler> *) NULL, (LazyScript *) NULL); } JS_ASSERT(!options.forEval); Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars, length, /* foldConstants = */ true, canLazilyParse ? &syntaxParser.ref() : NULL, NULL); parser.sct = &sct; parser.ss = ss; JS_ASSERT(fun); JS_ASSERT(fun->isTenured()); fun->setArgCount(formals.length()); // Speculatively parse using the default directives implied by the 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(options.strictOption); TokenStream::Position start(parser.keepAtoms); parser.tokenStream.tell(&start); ParseNode *fn; while (true) { Directives newDirectives = directives; fn = parser.standaloneFunctionBody(fun, formals, directives, &newDirectives); if (fn) break; if (parser.hadAbortedSyntaxParse()) { // Hit some unrecoverable ambiguity during an inner syntax parse. // Syntax parsing has now been disabled in the parser, so retry // the parse. parser.clearAbortedSyntaxParse(); } else { if (parser.tokenStream.hadError() || directives == newDirectives) return false; // Assignment must be monotonic to prevent reparsing iloops JS_ASSERT_IF(directives.strict(), newDirectives.strict()); JS_ASSERT_IF(directives.asmJS(), newDirectives.asmJS()); directives = newDirectives; } parser.tokenStream.seek(start); } if (!NameFunctions(cx, fn)) return false; if (fn->pn_funbox->function()->isInterpreted()) { JS_ASSERT(fun == fn->pn_funbox->function()); Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), false, options, /* staticLevel = */ 0, sourceObject, /* sourceStart = */ 0, length)); if (!script) return false; script->bindings = fn->pn_funbox->bindings; /* * The reason for checking fun->environment() below is that certain * consumers of JS::CompileFunction, namely * nsEventListenerManager::CompileEventHandlerInternal, passes in a * NULL environment. This compiled function is never used, but instead * is cloned immediately onto the right scope chain. */ BytecodeEmitter funbce(/* parent = */ NULL, &parser, fn->pn_funbox, script, /* insideEval = */ false, /* evalCaller = */ NullPtr(), fun->environment() && fun->environment()->is<GlobalObject>(), options.lineno); if (!funbce.init()) return false; if (!EmitFunctionScript(cx, &funbce, fn->pn_body)) return false; } else { fun.set(fn->pn_funbox->function()); JS_ASSERT(IsAsmJSModuleNative(fun->native())); } if (!SetSourceMap(cx, parser.tokenStream, ss)) return false; if (!sct.complete()) return false; return true; }
// Compile a JS function body, which might appear as the value of an event // handler attribute in an HTML <INPUT> tag, or in a Function() constructor. bool frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions options, Bindings *bindings, const jschar *chars, size_t length) { if (!CheckLength(cx, length)) return NULL; ScriptSource *ss = cx->new_<ScriptSource>(); if (!ss) return NULL; AutoAttachToRuntime attacher(cx->runtime, ss); SourceCompressionToken sct(cx); if (!ss->setSourceCopy(cx, chars, length, true, &sct)) return NULL; options.setCompileAndGo(false); Parser parser(cx, options, chars, length, /* foldConstants = */ true); if (!parser.init()) return false; parser.sct = &sct; JS_ASSERT(fun); SharedContext funsc(cx, /* scopeChain = */ NULL, fun, /* funbox = */ NULL, StrictModeFromContext(cx)); funsc.bindings.transfer(bindings); fun->setArgCount(funsc.bindings.numArgs()); unsigned staticLevel = 0; TreeContext funtc(&parser, &funsc, staticLevel, /* bodyid = */ 0); if (!funtc.init()) return false; Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), false, options, staticLevel, ss, 0, length)); if (!script) return false; StackFrame *nullCallerFrame = NULL; BytecodeEmitter funbce(/* parent = */ NULL, &parser, &funsc, script, nullCallerFrame, /* hasGlobalScope = */ false, options.lineno); if (!funbce.init()) return false; /* FIXME: make Function format the source for a function definition. */ ParseNode *fn = FunctionNode::create(PNK_NAME, &parser); if (!fn) return false; fn->pn_body = NULL; fn->pn_cookie.makeFree(); ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &parser); if (!argsbody) return false; argsbody->setOp(JSOP_NOP); argsbody->makeEmpty(); fn->pn_body = argsbody; unsigned nargs = fun->nargs; if (nargs) { /* * NB: do not use AutoLocalNameArray because it will release space * allocated from cx->tempLifoAlloc by DefineArg. */ BindingVector names(cx); if (!GetOrderedBindings(cx, funsc.bindings, &names)) return false; for (unsigned i = 0; i < nargs; i++) { if (!DefineArg(fn, names[i].maybeName, i, &parser)) return false; } } /* * After we're done parsing, we must fold constants, analyze any nested * functions, and generate code for this function, including a stop opcode * at the end. */ ParseNode *pn = parser.functionBody(Parser::StatementListBody); if (!pn) return false; if (!parser.tokenStream.matchToken(TOK_EOF)) { parser.reportError(NULL, JSMSG_SYNTAX_ERROR); return false; } if (!FoldConstants(cx, pn, &parser)) return false; if (!AnalyzeFunctions(&parser, nullCallerFrame)) return false; if (fn->pn_body) { JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY)); fn->pn_body->append(pn); fn->pn_body->pn_pos = pn->pn_pos; pn = fn->pn_body; } if (!EmitFunctionScript(cx, &funbce, pn)) return false; return true; }
// Compile a JS function body, which might appear as the value of an event // handler attribute in an HTML <INPUT> tag, or in a Function() constructor. bool frontend::CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options, const AutoNameVector &formals, const jschar *chars, size_t length, bool isAsmJSRecompile) { SkipRoot skip(cx, &chars); if (!CheckLength(cx, length)) return false; ScriptSource *ss = cx->new_<ScriptSource>(); if (!ss) return false; if (options.filename && !ss->setFilename(cx, options.filename)) return false; JS::RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, ss)); if (!sourceObject) return false; SourceCompressionToken sct(cx); JS_ASSERT(options.sourcePolicy != CompileOptions::LAZY_SOURCE); if (options.sourcePolicy == CompileOptions::SAVE_SOURCE) { if (!ss->setSourceCopy(cx, chars, length, true, &sct)) return false; } bool canLazilyParse = CanLazilyParse(cx, options); Maybe<Parser<SyntaxParseHandler> > syntaxParser; if (canLazilyParse) { syntaxParser.construct(cx, options, chars, length, /* foldConstants = */ false, (Parser<SyntaxParseHandler> *) NULL, (LazyScript *) NULL); } JS_ASSERT(!options.forEval); Parser<FullParseHandler> parser(cx, options, chars, length, /* foldConstants = */ true, canLazilyParse ? &syntaxParser.ref() : NULL, NULL); parser.sct = &sct; JS_ASSERT(fun); JS_ASSERT(fun->isTenured()); fun->setArgCount(formals.length()); /* FIXME: make Function format the source for a function definition. */ ParseNode *fn = CodeNode::create(PNK_FUNCTION, &parser.handler); if (!fn) return false; fn->pn_body = NULL; fn->pn_funbox = NULL; fn->pn_cookie.makeFree(); ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &parser.handler); if (!argsbody) return false; argsbody->setOp(JSOP_NOP); argsbody->makeEmpty(); fn->pn_body = argsbody; Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), false, options, /* staticLevel = */ 0, sourceObject, /* sourceStart = */ 0, length)); if (!script) return false; // If the context is strict, immediately parse the body in strict // mode. Otherwise, we parse it normally. If we see a "use strict" // directive, we backup and reparse it as strict. TokenStream::Position start(parser.keepAtoms); parser.tokenStream.tell(&start); bool strict = StrictModeFromContext(cx); bool becameStrict; FunctionBox *funbox; ParseNode *pn; while (true) { pn = parser.standaloneFunctionBody(fun, formals, script, fn, &funbox, strict, &becameStrict); if (pn) break; if (parser.hadAbortedSyntaxParse()) { // Hit some unrecoverable ambiguity during an inner syntax parse. // Syntax parsing has now been disabled in the parser, so retry // the parse. parser.clearAbortedSyntaxParse(); } else { // If the function became strict, reparse in strict mode. if (strict || !becameStrict || parser.tokenStream.hadError()) return false; strict = true; } parser.tokenStream.seek(start); } if (!NameFunctions(cx, pn)) return false; if (fn->pn_body) { JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY)); fn->pn_body->append(pn); fn->pn_body->pn_pos = pn->pn_pos; pn = fn->pn_body; } bool generateBytecode = true; #ifdef JS_ION JS_ASSERT_IF(isAsmJSRecompile, fn->pn_funbox->useAsm); if (fn->pn_funbox->useAsm && !isAsmJSRecompile) { RootedFunction moduleFun(cx); if (!CompileAsmJS(cx, parser.tokenStream, fn, options, ss, /* bufStart = */ 0, /* bufEnd = */ length, &moduleFun)) return false; if (moduleFun) { funbox->object = moduleFun; fun.set(moduleFun); // replace the existing function with the LinkAsmJS native generateBytecode = false; } } #endif if (generateBytecode) { /* * The reason for checking fun->environment() below is that certain * consumers of JS::CompileFunction, namely * nsEventListenerManager::CompileEventHandlerInternal, passes in a * NULL environment. This compiled function is never used, but instead * is cloned immediately onto the right scope chain. */ BytecodeEmitter funbce(/* parent = */ NULL, &parser, funbox, script, /* insideEval = */ false, /* evalCaller = */ NullPtr(), fun->environment() && fun->environment()->is<GlobalObject>(), options.lineno); if (!funbce.init()) return false; if (!EmitFunctionScript(cx, &funbce, pn)) return false; } if (!SetSourceMap(cx, parser.tokenStream, ss, script)) return false; if (!sct.complete()) return false; return true; }
JSScript * frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, 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_); SkipRoot skip(cx, &chars); if (cx->isJSContext()) MaybeCallSourceHandler(cx->asJSContext(), options, chars, length); /* * 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(evalCaller, options.forEval); 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>(options.originPrincipals()); if (!ss) return NULL; if (options.filename && !ss->setFilename(cx, options.filename)) return NULL; JS::RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, ss)); if (!sourceObject) return NULL; // Saving source is not yet supported when parsing off thread. JS_ASSERT_IF(!cx->isJSContext(), !extraSct && options.sourcePolicy == CompileOptions::NO_SOURCE); SourceCompressionToken *sct = extraSct; Maybe<SourceCompressionToken> mysct; if (cx->isJSContext() && !sct) { mysct.construct(cx->asJSContext()); sct = mysct.addr(); } switch (options.sourcePolicy) { case CompileOptions::SAVE_SOURCE: if (!ss->setSourceCopy(cx->asJSContext(), chars, length, false, sct)) return NULL; break; case CompileOptions::LAZY_SOURCE: ss->setSourceRetrievable(); break; case CompileOptions::NO_SOURCE: break; } bool canLazilyParse = CanLazilyParse(cx, options); Maybe<Parser<SyntaxParseHandler> > syntaxParser; if (canLazilyParse) { syntaxParser.construct(cx, alloc, options, chars, length, /* foldConstants = */ false, (Parser<SyntaxParseHandler> *) NULL, (LazyScript *) NULL); } Parser<FullParseHandler> parser(cx, alloc, options, chars, length, /* foldConstants = */ true, canLazilyParse ? &syntaxParser.ref() : NULL, NULL); parser.sct = sct; parser.ss = ss; Directives directives(options.strictOption); GlobalSharedContext globalsc(cx, scopeChain, directives, options.extraWarningsOption); bool savedCallerFun = options.compileAndGo && evalCaller && (evalCaller->function() || evalCaller->savedCallerFun); Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun, options, staticLevel, sourceObject, 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::EmitterMode emitterMode = options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; BytecodeEmitter bce(/* parent = */ NULL, &parser, &globalsc, script, options.forEval, evalCaller, !!globalScope, options.lineno, emitterMode); if (!bce.init()) return NULL; // Syntax parsing may cause us to restart processing of top level // statements in the script. Use Maybe<> so that the parse context can be // reset when this occurs. Maybe<ParseContext<FullParseHandler> > pc; pc.construct(&parser, (GenericParseContext *) NULL, (ParseNode *) NULL, &globalsc, (Directives *) NULL, staticLevel, /* bodyid = */ 0); if (!pc.ref().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(); Directives directives(/* strict = */ fun->strict()); ObjectBox *funbox = parser.newFunctionBox(/* fn = */ NULL, fun, pc.addr(), directives); if (!funbox) return NULL; bce.objectList.add(funbox); } } bool canHaveDirectives = true; for (;;) { TokenKind tt = parser.tokenStream.peekToken(TokenStream::Operand); if (tt <= TOK_EOF) { if (tt == TOK_EOF) break; JS_ASSERT(tt == TOK_ERROR); return NULL; } TokenStream::Position pos(parser.keepAtoms); parser.tokenStream.tell(&pos); ParseNode *pn = parser.statement(canHaveDirectives); if (!pn) { if (parser.hadAbortedSyntaxParse()) { // Parsing inner functions lazily may lead the parser into an // unrecoverable state and may require starting over on the top // level statement. Restart the parse; syntax parsing has // already been disabled for the parser and the result will not // be ambiguous. parser.clearAbortedSyntaxParse(); parser.tokenStream.seek(pos); // Destroying the parse context will destroy its free // variables, so check if any deoptimization is needed. if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref())) return NULL; pc.destroy(); pc.construct(&parser, (GenericParseContext *) NULL, (ParseNode *) NULL, &globalsc, (Directives *) NULL, staticLevel, /* bodyid = */ 0); if (!pc.ref().init()) return NULL; JS_ASSERT(parser.pc == pc.addr()); pn = parser.statement(); } if (!pn) { JS_ASSERT(!parser.hadAbortedSyntaxParse()); return NULL; } } if (canHaveDirectives) { if (!parser.maybeParseDirective(/* stmtList = */ NULL, pn, &canHaveDirectives)) return NULL; } if (!FoldConstants(cx, &pn, &parser)) return NULL; // Inferring names for functions in compiled scripts is currently only // supported while on the main thread. See bug 895395. if (cx->isJSContext() && !NameFunctions(cx->asJSContext(), pn)) return NULL; if (!EmitTree(cx, &bce, pn)) return NULL; parser.handler.freeTree(pn); } if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref())) return NULL; if (!SetSourceMap(cx, parser.tokenStream, ss)) return 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 NULL; if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return NULL; bce.tellDebuggerAboutCompiledScript(cx); if (sct && !extraSct && !sct->complete()) return NULL; return script; }
// Compile a JS function body, which might appear as the value of an event // handler attribute in an HTML <INPUT> tag, or in a Function() constructor. bool frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions options, const AutoNameVector &formals, StableCharPtr chars, size_t length) { if (!CheckLength(cx, length)) return false; ScriptSource *ss = cx->new_<ScriptSource>(); if (!ss) return false; ScriptSourceHolder ssh(ss); SourceCompressionToken sct(cx); JS_ASSERT(options.sourcePolicy != CompileOptions::LAZY_SOURCE); if (options.sourcePolicy == CompileOptions::SAVE_SOURCE) { if (!ss->setSourceCopy(cx, chars, length, true, &sct)) return false; } options.setCompileAndGo(false); Parser parser(cx, options, chars, length, /* foldConstants = */ true); if (!parser.init()) return false; parser.sct = &sct; JS_ASSERT(fun); fun->setArgCount(formals.length()); /* FIXME: make Function format the source for a function definition. */ ParseNode *fn = FunctionNode::create(PNK_FUNCTION, &parser); if (!fn) return false; fn->pn_body = NULL; fn->pn_funbox = NULL; fn->pn_cookie.makeFree(); ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &parser); if (!argsbody) return false; argsbody->setOp(JSOP_NOP); argsbody->makeEmpty(); fn->pn_body = argsbody; Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), false, options, /* staticLevel = */ 0, ss, /* sourceStart = */ 0, length)); if (!script) return false; // If the context is strict, immediately parse the body in strict // mode. Otherwise, we parse it normally. If we see a "use strict" // directive, we backup and reparse it as strict. TokenStream::Position start; parser.tokenStream.tell(&start); bool initiallyStrict = StrictModeFromContext(cx); bool becameStrict; FunctionBox *funbox; ParseNode *pn = parser.standaloneFunctionBody(fun, formals, script, fn, &funbox, initiallyStrict, &becameStrict); if (!pn) { if (initiallyStrict || !becameStrict || parser.tokenStream.hadError()) return false; // Reparse in strict mode. parser.tokenStream.seek(start); pn = parser.standaloneFunctionBody(fun, formals, script, fn, &funbox, /* strict = */ true); if (!pn) return false; } BytecodeEmitter funbce(/* parent = */ NULL, &parser, funbox, script, /* callerFrame = */ NullFramePtr(), /* hasGlobalScope = */ false, options.lineno); if (!funbce.init()) return false; if (!NameFunctions(cx, pn)) return false; if (fn->pn_body) { JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY)); fn->pn_body->append(pn); fn->pn_body->pn_pos = pn->pn_pos; pn = fn->pn_body; } if (!SetSourceMap(cx, parser.tokenStream, ss, script)) return false; if (!EmitFunctionScript(cx, &funbce, pn)) return false; if (!sct.complete()) return false; return true; }
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; }
// Compile a JS function body, which might appear as the value of an event // handler attribute in an HTML <INPUT> tag, or in a Function() constructor. bool frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun, CompileOptions options, const AutoNameVector &formals, const jschar *chars, size_t length) { if (!CheckLength(cx, length)) return false; ScriptSource *ss = cx->new_<ScriptSource>(); if (!ss) return false; ScriptSourceHolder ssh(cx->runtime, ss); SourceCompressionToken sct(cx); JS_ASSERT(options.sourcePolicy != CompileOptions::LAZY_SOURCE); if (options.sourcePolicy == CompileOptions::SAVE_SOURCE) { if (!ss->setSourceCopy(cx, chars, length, true, &sct)) return false; } options.setCompileAndGo(false); Parser parser(cx, options, chars, length, /* foldConstants = */ true); if (!parser.init()) return false; parser.sct = &sct; JS_ASSERT(fun); StrictMode sms = StrictModeFromContext(cx); FunctionBox *funbox = parser.newFunctionBox(fun, /* outerpc = */ NULL, sms); SharedContext funsc(cx, /* scopeChain = */ NULL, funbox, sms); fun->setArgCount(formals.length()); unsigned staticLevel = 0; ParseContext funpc(&parser, &funsc, staticLevel, /* bodyid = */ 0); if (!funpc.init()) return false; /* FIXME: make Function format the source for a function definition. */ ParseNode *fn = FunctionNode::create(PNK_NAME, &parser); if (!fn) return false; fn->pn_body = NULL; fn->pn_cookie.makeFree(); ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, &parser); if (!argsbody) return false; argsbody->setOp(JSOP_NOP); argsbody->makeEmpty(); fn->pn_body = argsbody; for (unsigned i = 0; i < formals.length(); i++) { if (!DefineArg(&parser, fn, formals[i])) return false; } /* * After we're done parsing, we must fold constants, analyze any nested * functions, and generate code for this function, including a stop opcode * at the end. */ ParseNode *pn = parser.functionBody(Parser::StatementListBody); if (!pn) return false; if (!parser.tokenStream.matchToken(TOK_EOF)) { parser.reportError(NULL, JSMSG_SYNTAX_ERROR); return false; } if (!FoldConstants(cx, pn, &parser)) return false; Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), false, options, staticLevel, ss, 0, length)); if (!script) return false; if (!funpc.generateFunctionBindings(cx, &script->bindings)) return false; BytecodeEmitter funbce(/* parent = */ NULL, &parser, &funsc, script, /* callerFrame = */ NULL, /* hasGlobalScope = */ false, options.lineno); if (!funbce.init()) return false; if (!NameFunctions(cx, pn)) return false; if (fn->pn_body) { JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY)); fn->pn_body->append(pn); fn->pn_body->pn_pos = pn->pn_pos; pn = fn->pn_body; } if (!SetSourceMap(cx, parser.tokenStream, ss, script)) return false; if (!EmitFunctionScript(cx, &funbce, pn)) return false; return true; }
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; }
JSScript * frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject scopeChain, HandleScript evalCaller, const ReadOnlyCompileOptions &options, const jschar *chars, size_t length, JSString *source_ /* = nullptr */, unsigned staticLevel /* = 0 */, SourceCompressionTask *extraSct /* = nullptr */) { RootedString source(cx, source_); js::TraceLogger *logger = nullptr; if (cx->isJSContext()) logger = TraceLoggerForMainThread(cx->asJSContext()->runtime()); else logger = TraceLoggerForCurrentThread(); uint32_t logId = js::TraceLogCreateTextId(logger, options); js::AutoTraceLog scriptLogger(logger, logId); js::AutoTraceLog typeLogger(logger, TraceLogger::ParserCompileScript); if (cx->isJSContext()) MaybeCallSourceHandler(cx->asJSContext(), options, chars, length); /* * 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(evalCaller, options.forEval); JS_ASSERT_IF(staticLevel != 0, evalCaller); if (!CheckLength(cx, length)) return nullptr; JS_ASSERT_IF(staticLevel != 0, options.sourcePolicy != CompileOptions::LAZY_SOURCE); RootedScriptSource sourceObject(cx, CreateScriptSourceObject(cx, options)); if (!sourceObject) return nullptr; ScriptSource *ss = sourceObject->source(); SourceCompressionTask mysct(cx); SourceCompressionTask *sct = extraSct ? extraSct : &mysct; switch (options.sourcePolicy) { case CompileOptions::SAVE_SOURCE: if (!ss->setSourceCopy(cx, chars, length, false, sct)) return nullptr; break; case CompileOptions::LAZY_SOURCE: ss->setSourceRetrievable(); break; case CompileOptions::NO_SOURCE: break; } bool canLazilyParse = CanLazilyParse(cx, options); Maybe<Parser<SyntaxParseHandler> > syntaxParser; if (canLazilyParse) { syntaxParser.construct(cx, alloc, options, chars, length, /* foldConstants = */ false, (Parser<SyntaxParseHandler> *) nullptr, (LazyScript *) nullptr); } Parser<FullParseHandler> parser(cx, alloc, options, chars, length, /* foldConstants = */ true, canLazilyParse ? &syntaxParser.ref() : nullptr, nullptr); parser.sct = sct; parser.ss = ss; Directives directives(options.strictOption); GlobalSharedContext globalsc(cx, scopeChain, directives, options.extraWarningsOption); bool savedCallerFun = options.compileAndGo && evalCaller && evalCaller->functionOrCallerFunction(); Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun, options, staticLevel, sourceObject, 0, length)); if (!script) return nullptr; // 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 : nullptr; JS_ASSERT_IF(globalScope, globalScope->isNative()); JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass())); BytecodeEmitter::EmitterMode emitterMode = options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script, options.forEval, evalCaller, !!globalScope, options.lineno, emitterMode); if (!bce.init()) return nullptr; // Syntax parsing may cause us to restart processing of top level // statements in the script. Use Maybe<> so that the parse context can be // reset when this occurs. Maybe<ParseContext<FullParseHandler> > pc; pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0, /* blockScopeDepth = */ 0); if (!pc.ref().init(parser.tokenStream)) return nullptr; /* 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(cx, source); jsatomid _; if (!atom || !bce.makeAtomIndex(atom, &_)) return nullptr; } 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(); Directives directives(/* strict = */ fun->strict()); ObjectBox *funbox = parser.newFunctionBox(/* fn = */ nullptr, fun, pc.addr(), directives, fun->generatorKind()); if (!funbox) return nullptr; bce.objectList.add(funbox); } } bool canHaveDirectives = true; for (;;) { TokenKind tt = parser.tokenStream.peekToken(TokenStream::Operand); if (tt <= TOK_EOF) { if (tt == TOK_EOF) break; JS_ASSERT(tt == TOK_ERROR); return nullptr; } TokenStream::Position pos(parser.keepAtoms); parser.tokenStream.tell(&pos); ParseNode *pn = parser.statement(canHaveDirectives); if (!pn) { if (parser.hadAbortedSyntaxParse()) { // Parsing inner functions lazily may lead the parser into an // unrecoverable state and may require starting over on the top // level statement. Restart the parse; syntax parsing has // already been disabled for the parser and the result will not // be ambiguous. parser.clearAbortedSyntaxParse(); parser.tokenStream.seek(pos); // Destroying the parse context will destroy its free // variables, so check if any deoptimization is needed. if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref())) return nullptr; pc.destroy(); pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0, script->bindings.numBlockScoped()); if (!pc.ref().init(parser.tokenStream)) return nullptr; JS_ASSERT(parser.pc == pc.addr()); pn = parser.statement(); } if (!pn) { JS_ASSERT(!parser.hadAbortedSyntaxParse()); return nullptr; } } // Accumulate the maximum block scope depth, so that EmitTree can assert // when emitting JSOP_GETLOCAL that the local is indeed within the fixed // part of the stack frame. script->bindings.updateNumBlockScoped(pc.ref().blockScopeDepth); if (canHaveDirectives) { if (!parser.maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives)) return nullptr; } if (!FoldConstants(cx, &pn, &parser)) return nullptr; if (!NameFunctions(cx, pn)) return nullptr; if (!EmitTree(cx, &bce, pn)) return nullptr; parser.handler.freeTree(pn); } if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref())) return nullptr; if (!SetDisplayURL(cx, parser.tokenStream, ss)) return nullptr; if (!SetSourceMap(cx, parser.tokenStream, ss)) return nullptr; /* * Source map URLs passed as a compile option (usually via a HTTP source map * header) override any source map urls passed as comment pragmas. */ if (options.sourceMapURL()) { if (!ss->setSourceMapURL(cx, options.sourceMapURL())) return nullptr; } /* * Nowadays the threaded interpreter needs a last return instruction, so we * do have to emit that here. */ if (Emit1(cx, &bce, JSOP_RETRVAL) < 0) return nullptr; // Global/eval script bindings are always empty (all names are added to the // scope dynamically via JSOP_DEFFUN/VAR). They may have block-scoped // locals, however, which are allocated to the fixed part of the stack // frame. InternalHandle<Bindings*> bindings(script, &script->bindings); if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr, pc.ref().blockScopeDepth)) return nullptr; if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return nullptr; bce.tellDebuggerAboutCompiledScript(cx); if (sct && !extraSct && !sct->complete()) return nullptr; return script; }
// Compile a JS function body, which might appear as the value of an event // handler attribute in an HTML <INPUT> tag, or in a Function() constructor. static bool CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, const ReadOnlyCompileOptions &options, const AutoNameVector &formals, SourceBufferHolder &srcBuf, HandleObject enclosingScope, GeneratorKind generatorKind) { js::TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime()); uint32_t logId = js::TraceLogCreateTextId(logger, options); js::AutoTraceLog scriptLogger(logger, logId); js::AutoTraceLog typeLogger(logger, TraceLogger::ParserCompileFunction); // FIXME: make Function pass in two strings and parse them as arguments and // ProgramElements respectively. if (!CheckLength(cx, srcBuf)) return false; RootedScriptSource sourceObject(cx, CreateScriptSourceObject(cx, options)); if (!sourceObject) return false; ScriptSource *ss = sourceObject->source(); SourceCompressionTask sct(cx); MOZ_ASSERT(!options.sourceIsLazy); if (!cx->compartment()->options().discardSource()) { if (!ss->setSourceCopy(cx, srcBuf, true, &sct)) return false; } bool canLazilyParse = CanLazilyParse(cx, options); Maybe<Parser<SyntaxParseHandler> > syntaxParser; if (canLazilyParse) { syntaxParser.emplace(cx, &cx->tempLifoAlloc(), options, srcBuf.get(), srcBuf.length(), /* foldConstants = */ false, (Parser<SyntaxParseHandler> *) nullptr, (LazyScript *) nullptr); } MOZ_ASSERT(!options.forEval); Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, srcBuf.get(), srcBuf.length(), /* foldConstants = */ true, canLazilyParse ? syntaxParser.ptr() : nullptr, nullptr); parser.sct = &sct; parser.ss = ss; MOZ_ASSERT(fun); MOZ_ASSERT(fun->isTenured()); fun->setArgCount(formals.length()); // Speculatively parse using the default directives implied by the 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(options.strictOption); TokenStream::Position start(parser.keepAtoms); parser.tokenStream.tell(&start); ParseNode *fn; while (true) { Directives newDirectives = directives; fn = parser.standaloneFunctionBody(fun, formals, generatorKind, directives, &newDirectives); if (fn) break; if (parser.hadAbortedSyntaxParse()) { // Hit some unrecoverable ambiguity during an inner syntax parse. // Syntax parsing has now been disabled in the parser, so retry // the parse. parser.clearAbortedSyntaxParse(); } else { if (parser.tokenStream.hadError() || directives == newDirectives) return false; // Assignment must be monotonic to prevent reparsing iloops MOZ_ASSERT_IF(directives.strict(), newDirectives.strict()); MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS()); directives = newDirectives; } parser.tokenStream.seek(start); } if (!NameFunctions(cx, fn)) return false; if (!SetDisplayURL(cx, parser.tokenStream, ss)) return false; if (!SetSourceMap(cx, parser.tokenStream, ss)) return false; if (fn->pn_funbox->function()->isInterpreted()) { MOZ_ASSERT(fun == fn->pn_funbox->function()); Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options, /* staticLevel = */ 0, sourceObject, /* sourceStart = */ 0, srcBuf.length())); if (!script) return false; script->bindings = fn->pn_funbox->bindings; /* * The reason for checking fun->environment() below is that certain * consumers of JS::CompileFunction, namely * EventListenerManager::CompileEventHandlerInternal, passes in a * nullptr environment. This compiled function is never used, but * instead is cloned immediately onto the right scope chain. */ BytecodeEmitter funbce(/* parent = */ nullptr, &parser, fn->pn_funbox, script, /* lazyScript = */ js::NullPtr(), /* insideEval = */ false, /* evalCaller = */ js::NullPtr(), fun->environment() && fun->environment()->is<GlobalObject>(), options.lineno); if (!funbce.init()) return false; if (!EmitFunctionScript(cx, &funbce, fn->pn_body)) return false; } else { fun.set(fn->pn_funbox->function()); MOZ_ASSERT(IsAsmJSModuleNative(fun->native())); } if (!sct.complete()) return false; return true; }
JSScript* frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject scopeChain, HandleScript evalCaller, Handle<StaticEvalObject*> evalStaticScope, const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf, JSString* source_ /* = nullptr */, unsigned staticLevel /* = 0 */, SourceCompressionTask* extraSct /* = nullptr */) { MOZ_ASSERT(srcBuf.get()); RootedString source(cx, source_); js::TraceLoggerThread* logger = nullptr; if (cx->isJSContext()) logger = TraceLoggerForMainThread(cx->asJSContext()->runtime()); else logger = TraceLoggerForCurrentThread(); js::TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, options); js::AutoTraceLog scriptLogger(logger, event); js::AutoTraceLog typeLogger(logger, TraceLogger_ParserCompileScript); /* * The scripted callerFrame can only be given for compile-and-go scripts * and non-zero static level requires callerFrame. */ MOZ_ASSERT_IF(evalCaller, options.compileAndGo); MOZ_ASSERT_IF(evalCaller, options.forEval); MOZ_ASSERT_IF(evalCaller && evalCaller->strict(), options.strictOption); MOZ_ASSERT_IF(staticLevel != 0, evalCaller); if (!CheckLength(cx, srcBuf)) return nullptr; MOZ_ASSERT_IF(staticLevel != 0, !options.sourceIsLazy); RootedScriptSource sourceObject(cx, CreateScriptSourceObject(cx, options)); if (!sourceObject) return nullptr; ScriptSource* ss = sourceObject->source(); SourceCompressionTask mysct(cx); SourceCompressionTask* sct = extraSct ? extraSct : &mysct; if (!cx->compartment()->options().discardSource()) { if (options.sourceIsLazy) ss->setSourceRetrievable(); else if (!ss->setSourceCopy(cx, srcBuf, false, sct)) return nullptr; } bool canLazilyParse = CanLazilyParse(cx, options); Maybe<Parser<SyntaxParseHandler> > syntaxParser; if (canLazilyParse) { syntaxParser.emplace(cx, alloc, options, srcBuf.get(), srcBuf.length(), /* foldConstants = */ false, (Parser<SyntaxParseHandler>*) nullptr, (LazyScript*) nullptr); if (!syntaxParser->checkOptions()) return nullptr; } Parser<FullParseHandler> parser(cx, alloc, options, srcBuf.get(), srcBuf.length(), /* foldConstants = */ true, canLazilyParse ? syntaxParser.ptr() : nullptr, nullptr); parser.sct = sct; parser.ss = ss; if (!parser.checkOptions()) return nullptr; Directives directives(options.strictOption); GlobalSharedContext globalsc(cx, scopeChain, directives, options.extraWarningsOption); bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction(); Rooted<JSScript*> script(cx, JSScript::Create(cx, evalStaticScope, savedCallerFun, options, staticLevel, sourceObject, 0, srcBuf.length())); if (!script) return nullptr; // 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 : nullptr; MOZ_ASSERT_IF(globalScope, globalScope->isNative()); MOZ_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass())); BytecodeEmitter::EmitterMode emitterMode = options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script, /* lazyScript = */ js::NullPtr(), options.forEval, evalCaller, evalStaticScope, !!globalScope, options.lineno, emitterMode); if (!bce.init()) return nullptr; // Syntax parsing may cause us to restart processing of top level // statements in the script. Use Maybe<> so that the parse context can be // reset when this occurs. Maybe<ParseContext<FullParseHandler> > pc; pc.emplace(&parser, (GenericParseContext*) nullptr, (ParseNode*) nullptr, &globalsc, (Directives*) nullptr, staticLevel, /* bodyid = */ 0, /* blockScopeDepth = */ 0); if (!pc->init(parser.tokenStream)) return nullptr; if (savedCallerFun) { /* * 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(); MOZ_ASSERT_IF(fun->strict(), options.strictOption); Directives directives(/* strict = */ options.strictOption); ObjectBox* funbox = parser.newFunctionBox(/* fn = */ nullptr, fun, pc.ptr(), directives, fun->generatorKind()); if (!funbox) return nullptr; bce.objectList.add(funbox); } bool canHaveDirectives = true; for (;;) { TokenKind tt; if (!parser.tokenStream.peekToken(&tt, TokenStream::Operand)) return nullptr; if (tt == TOK_EOF) break; TokenStream::Position pos(parser.keepAtoms); parser.tokenStream.tell(&pos); ParseNode* pn = parser.statement(canHaveDirectives); if (!pn) { if (parser.hadAbortedSyntaxParse()) { // Parsing inner functions lazily may lead the parser into an // unrecoverable state and may require starting over on the top // level statement. Restart the parse; syntax parsing has // already been disabled for the parser and the result will not // be ambiguous. parser.clearAbortedSyntaxParse(); parser.tokenStream.seek(pos); // Destroying the parse context will destroy its free // variables, so check if any deoptimization is needed. if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, *pc)) return nullptr; pc.reset(); pc.emplace(&parser, (GenericParseContext*) nullptr, (ParseNode*) nullptr, &globalsc, (Directives*) nullptr, staticLevel, /* bodyid = */ 0, script->bindings.numBlockScoped()); if (!pc->init(parser.tokenStream)) return nullptr; MOZ_ASSERT(parser.pc == pc.ptr()); pn = parser.statement(); } if (!pn) { MOZ_ASSERT(!parser.hadAbortedSyntaxParse()); return nullptr; } } // Accumulate the maximum block scope depth, so that EmitTree can assert // when emitting JSOP_GETLOCAL that the local is indeed within the fixed // part of the stack frame. script->bindings.updateNumBlockScoped(pc->blockScopeDepth); if (canHaveDirectives) { if (!parser.maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives)) return nullptr; } if (!FoldConstants(cx, &pn, &parser)) return nullptr; if (!NameFunctions(cx, pn)) return nullptr; if (!bce.updateLocalsToFrameSlots()) return nullptr; if (!EmitTree(cx, &bce, pn)) return nullptr; parser.handler.freeTree(pn); } if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, *pc)) return nullptr; if (!SetDisplayURL(cx, parser.tokenStream, ss)) return nullptr; if (!SetSourceMap(cx, parser.tokenStream, ss)) return nullptr; /* * Source map URLs passed as a compile option (usually via a HTTP source map * header) override any source map urls passed as comment pragmas. */ if (options.sourceMapURL()) { // Warn about the replacement, but use the new one. if (ss->hasSourceMapURL()) { if(!parser.report(ParseWarning, false, nullptr, JSMSG_ALREADY_HAS_PRAGMA, ss->filename(), "//# sourceMappingURL")) return nullptr; } if (!ss->setSourceMapURL(cx, options.sourceMapURL())) return nullptr; } /* * Nowadays the threaded interpreter needs a last return instruction, so we * do have to emit that here. */ if (Emit1(cx, &bce, JSOP_RETRVAL) < 0) return nullptr; // Global/eval script bindings are always empty (all names are added to the // scope dynamically via JSOP_DEFFUN/VAR). They may have block-scoped // locals, however, which are allocated to the fixed part of the stack // frame. InternalHandle<Bindings*> bindings(script, &script->bindings); if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, 0, pc->blockScopeDepth, 0, 0, nullptr)) { return nullptr; } if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) return nullptr; // Note that this marking must happen before we tell Debugger // about the new script, in case Debugger delazifies the script's // inner functions. if (options.forEval) MarkFunctionsWithinEvalScript(script); bce.tellDebuggerAboutCompiledScript(cx); if (sct && !extraSct && !sct->complete()) return nullptr; MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending()); return script; }