|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "frontend/BytecodeCompiler.h" |
|
8 |
|
9 #include "jscntxt.h" |
|
10 #include "jsscript.h" |
|
11 |
|
12 #include "frontend/BytecodeEmitter.h" |
|
13 #include "frontend/FoldConstants.h" |
|
14 #include "frontend/NameFunctions.h" |
|
15 #include "frontend/Parser.h" |
|
16 #include "jit/AsmJSLink.h" |
|
17 #include "vm/GlobalObject.h" |
|
18 #include "vm/TraceLogging.h" |
|
19 |
|
20 #include "jsobjinlines.h" |
|
21 #include "jsscriptinlines.h" |
|
22 |
|
23 #include "frontend/Parser-inl.h" |
|
24 |
|
25 using namespace js; |
|
26 using namespace js::frontend; |
|
27 using mozilla::Maybe; |
|
28 |
|
29 static bool |
|
30 CheckLength(ExclusiveContext *cx, SourceBufferHolder &srcBuf) |
|
31 { |
|
32 // Note this limit is simply so we can store sourceStart and sourceEnd in |
|
33 // JSScript as 32-bits. It could be lifted fairly easily, since the compiler |
|
34 // is using size_t internally already. |
|
35 if (srcBuf.length() > UINT32_MAX) { |
|
36 if (cx->isJSContext()) |
|
37 JS_ReportErrorNumber(cx->asJSContext(), js_GetErrorMessage, nullptr, |
|
38 JSMSG_SOURCE_TOO_LONG); |
|
39 return false; |
|
40 } |
|
41 return true; |
|
42 } |
|
43 |
|
44 static bool |
|
45 SetDisplayURL(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss) |
|
46 { |
|
47 if (tokenStream.hasDisplayURL()) { |
|
48 if (!ss->setDisplayURL(cx, tokenStream.displayURL())) |
|
49 return false; |
|
50 } |
|
51 return true; |
|
52 } |
|
53 |
|
54 static bool |
|
55 SetSourceMap(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss) |
|
56 { |
|
57 if (tokenStream.hasSourceMapURL()) { |
|
58 if (!ss->setSourceMapURL(cx, tokenStream.sourceMapURL())) |
|
59 return false; |
|
60 } |
|
61 return true; |
|
62 } |
|
63 |
|
64 static bool |
|
65 CheckArgumentsWithinEval(JSContext *cx, Parser<FullParseHandler> &parser, HandleFunction fun) |
|
66 { |
|
67 if (fun->hasRest()) { |
|
68 // It's an error to use |arguments| in a function that has a rest |
|
69 // parameter. |
|
70 parser.report(ParseError, false, nullptr, JSMSG_ARGUMENTS_AND_REST); |
|
71 return false; |
|
72 } |
|
73 |
|
74 // Force construction of arguments objects for functions that use |
|
75 // |arguments| within an eval. |
|
76 RootedScript script(cx, fun->getOrCreateScript(cx)); |
|
77 if (!script) |
|
78 return false; |
|
79 if (script->argumentsHasVarBinding()) { |
|
80 if (!JSScript::argumentsOptimizationFailed(cx, script)) |
|
81 return false; |
|
82 } |
|
83 |
|
84 // It's an error to use |arguments| in a legacy generator expression. |
|
85 if (script->isGeneratorExp() && script->isLegacyGenerator()) { |
|
86 parser.report(ParseError, false, nullptr, JSMSG_BAD_GENEXP_BODY, js_arguments_str); |
|
87 return false; |
|
88 } |
|
89 |
|
90 return true; |
|
91 } |
|
92 |
|
93 static bool |
|
94 MaybeCheckEvalFreeVariables(ExclusiveContext *cxArg, HandleScript evalCaller, HandleObject scopeChain, |
|
95 Parser<FullParseHandler> &parser, |
|
96 ParseContext<FullParseHandler> &pc) |
|
97 { |
|
98 if (!evalCaller || !evalCaller->functionOrCallerFunction()) |
|
99 return true; |
|
100 |
|
101 // Eval scripts are only compiled on the main thread. |
|
102 JSContext *cx = cxArg->asJSContext(); |
|
103 |
|
104 // Watch for uses of 'arguments' within the evaluated script, both as |
|
105 // free variables and as variables redeclared with 'var'. |
|
106 RootedFunction fun(cx, evalCaller->functionOrCallerFunction()); |
|
107 HandlePropertyName arguments = cx->names().arguments; |
|
108 for (AtomDefnRange r = pc.lexdeps->all(); !r.empty(); r.popFront()) { |
|
109 if (r.front().key() == arguments) { |
|
110 if (!CheckArgumentsWithinEval(cx, parser, fun)) |
|
111 return false; |
|
112 } |
|
113 } |
|
114 for (AtomDefnListMap::Range r = pc.decls().all(); !r.empty(); r.popFront()) { |
|
115 if (r.front().key() == arguments) { |
|
116 if (!CheckArgumentsWithinEval(cx, parser, fun)) |
|
117 return false; |
|
118 } |
|
119 } |
|
120 |
|
121 // If the eval'ed script contains any debugger statement, force construction |
|
122 // of arguments objects for the caller script and any other scripts it is |
|
123 // transitively nested inside. The debugger can access any variable on the |
|
124 // scope chain. |
|
125 if (pc.sc->hasDebuggerStatement()) { |
|
126 RootedObject scope(cx, scopeChain); |
|
127 while (scope->is<ScopeObject>() || scope->is<DebugScopeObject>()) { |
|
128 if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { |
|
129 RootedScript script(cx, scope->as<CallObject>().callee().getOrCreateScript(cx)); |
|
130 if (!script) |
|
131 return false; |
|
132 if (script->argumentsHasVarBinding()) { |
|
133 if (!JSScript::argumentsOptimizationFailed(cx, script)) |
|
134 return false; |
|
135 } |
|
136 } |
|
137 scope = scope->enclosingScope(); |
|
138 } |
|
139 } |
|
140 |
|
141 return true; |
|
142 } |
|
143 |
|
144 static inline bool |
|
145 CanLazilyParse(ExclusiveContext *cx, const ReadOnlyCompileOptions &options) |
|
146 { |
|
147 return options.canLazilyParse && |
|
148 options.compileAndGo && |
|
149 !cx->compartment()->options().discardSource() && |
|
150 !options.sourceIsLazy && |
|
151 !(cx->compartment()->debugMode() && |
|
152 cx->compartment()->runtimeFromAnyThread()->debugHooks.newScriptHook); |
|
153 } |
|
154 |
|
155 static void |
|
156 MarkFunctionsWithinEvalScript(JSScript *script) |
|
157 { |
|
158 // Mark top level functions in an eval script as being within an eval and, |
|
159 // if applicable, inside a with statement. |
|
160 |
|
161 if (!script->hasObjects()) |
|
162 return; |
|
163 |
|
164 ObjectArray *objects = script->objects(); |
|
165 size_t start = script->innerObjectsStart(); |
|
166 |
|
167 for (size_t i = start; i < objects->length; i++) { |
|
168 JSObject *obj = objects->vector[i]; |
|
169 if (obj->is<JSFunction>()) { |
|
170 JSFunction *fun = &obj->as<JSFunction>(); |
|
171 if (fun->hasScript()) |
|
172 fun->nonLazyScript()->setDirectlyInsideEval(); |
|
173 else if (fun->isInterpretedLazy()) |
|
174 fun->lazyScript()->setDirectlyInsideEval(); |
|
175 } |
|
176 } |
|
177 } |
|
178 |
|
179 void |
|
180 frontend::MaybeCallSourceHandler(JSContext *cx, const ReadOnlyCompileOptions &options, |
|
181 SourceBufferHolder &srcBuf) |
|
182 { |
|
183 JSSourceHandler listener = cx->runtime()->debugHooks.sourceHandler; |
|
184 void *listenerData = cx->runtime()->debugHooks.sourceHandlerData; |
|
185 |
|
186 if (listener) { |
|
187 void *listenerTSData; |
|
188 listener(options.filename(), options.lineno, srcBuf.get(), srcBuf.length(), |
|
189 &listenerTSData, listenerData); |
|
190 } |
|
191 } |
|
192 |
|
193 ScriptSourceObject * |
|
194 frontend::CreateScriptSourceObject(ExclusiveContext *cx, const ReadOnlyCompileOptions &options) |
|
195 { |
|
196 ScriptSource *ss = cx->new_<ScriptSource>(); |
|
197 if (!ss) |
|
198 return nullptr; |
|
199 ScriptSourceHolder ssHolder(ss); |
|
200 |
|
201 if (!ss->initFromOptions(cx, options)) |
|
202 return nullptr; |
|
203 |
|
204 return ScriptSourceObject::create(cx, ss, options); |
|
205 } |
|
206 |
|
207 JSScript * |
|
208 frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject scopeChain, |
|
209 HandleScript evalCaller, |
|
210 const ReadOnlyCompileOptions &options, |
|
211 SourceBufferHolder &srcBuf, |
|
212 JSString *source_ /* = nullptr */, |
|
213 unsigned staticLevel /* = 0 */, |
|
214 SourceCompressionTask *extraSct /* = nullptr */) |
|
215 { |
|
216 JS_ASSERT(srcBuf.get()); |
|
217 |
|
218 RootedString source(cx, source_); |
|
219 |
|
220 js::TraceLogger *logger = nullptr; |
|
221 if (cx->isJSContext()) |
|
222 logger = TraceLoggerForMainThread(cx->asJSContext()->runtime()); |
|
223 else |
|
224 logger = TraceLoggerForCurrentThread(); |
|
225 uint32_t logId = js::TraceLogCreateTextId(logger, options); |
|
226 js::AutoTraceLog scriptLogger(logger, logId); |
|
227 js::AutoTraceLog typeLogger(logger, TraceLogger::ParserCompileScript); |
|
228 |
|
229 if (cx->isJSContext()) |
|
230 MaybeCallSourceHandler(cx->asJSContext(), options, srcBuf); |
|
231 |
|
232 /* |
|
233 * The scripted callerFrame can only be given for compile-and-go scripts |
|
234 * and non-zero static level requires callerFrame. |
|
235 */ |
|
236 JS_ASSERT_IF(evalCaller, options.compileAndGo); |
|
237 JS_ASSERT_IF(evalCaller, options.forEval); |
|
238 JS_ASSERT_IF(staticLevel != 0, evalCaller); |
|
239 |
|
240 if (!CheckLength(cx, srcBuf)) |
|
241 return nullptr; |
|
242 JS_ASSERT_IF(staticLevel != 0, !options.sourceIsLazy); |
|
243 |
|
244 RootedScriptSource sourceObject(cx, CreateScriptSourceObject(cx, options)); |
|
245 if (!sourceObject) |
|
246 return nullptr; |
|
247 |
|
248 ScriptSource *ss = sourceObject->source(); |
|
249 |
|
250 SourceCompressionTask mysct(cx); |
|
251 SourceCompressionTask *sct = extraSct ? extraSct : &mysct; |
|
252 |
|
253 if (!cx->compartment()->options().discardSource()) { |
|
254 if (options.sourceIsLazy) |
|
255 ss->setSourceRetrievable(); |
|
256 else if (!ss->setSourceCopy(cx, srcBuf, false, sct)) |
|
257 return nullptr; |
|
258 } |
|
259 |
|
260 bool canLazilyParse = CanLazilyParse(cx, options); |
|
261 |
|
262 Maybe<Parser<SyntaxParseHandler> > syntaxParser; |
|
263 if (canLazilyParse) { |
|
264 syntaxParser.construct(cx, alloc, options, srcBuf.get(), srcBuf.length(), |
|
265 /* foldConstants = */ false, |
|
266 (Parser<SyntaxParseHandler> *) nullptr, |
|
267 (LazyScript *) nullptr); |
|
268 } |
|
269 |
|
270 Parser<FullParseHandler> parser(cx, alloc, options, srcBuf.get(), srcBuf.length(), |
|
271 /* foldConstants = */ true, |
|
272 canLazilyParse ? &syntaxParser.ref() : nullptr, nullptr); |
|
273 parser.sct = sct; |
|
274 parser.ss = ss; |
|
275 |
|
276 Directives directives(options.strictOption); |
|
277 GlobalSharedContext globalsc(cx, scopeChain, directives, options.extraWarningsOption); |
|
278 |
|
279 bool savedCallerFun = options.compileAndGo && |
|
280 evalCaller && evalCaller->functionOrCallerFunction(); |
|
281 Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun, |
|
282 options, staticLevel, sourceObject, 0, |
|
283 srcBuf.length())); |
|
284 if (!script) |
|
285 return nullptr; |
|
286 |
|
287 // We can specialize a bit for the given scope chain if that scope chain is the global object. |
|
288 JSObject *globalScope = |
|
289 scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : nullptr; |
|
290 JS_ASSERT_IF(globalScope, globalScope->isNative()); |
|
291 JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass())); |
|
292 |
|
293 BytecodeEmitter::EmitterMode emitterMode = |
|
294 options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal; |
|
295 BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script, options.forEval, |
|
296 evalCaller, !!globalScope, options.lineno, emitterMode); |
|
297 if (!bce.init()) |
|
298 return nullptr; |
|
299 |
|
300 // Syntax parsing may cause us to restart processing of top level |
|
301 // statements in the script. Use Maybe<> so that the parse context can be |
|
302 // reset when this occurs. |
|
303 Maybe<ParseContext<FullParseHandler> > pc; |
|
304 |
|
305 pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, &globalsc, |
|
306 (Directives *) nullptr, staticLevel, /* bodyid = */ 0, |
|
307 /* blockScopeDepth = */ 0); |
|
308 if (!pc.ref().init(parser.tokenStream)) |
|
309 return nullptr; |
|
310 |
|
311 /* If this is a direct call to eval, inherit the caller's strictness. */ |
|
312 if (evalCaller && evalCaller->strict()) |
|
313 globalsc.strict = true; |
|
314 |
|
315 if (options.compileAndGo) { |
|
316 if (source) { |
|
317 /* |
|
318 * Save eval program source in script->atoms[0] for the |
|
319 * eval cache (see EvalCacheLookup in jsobj.cpp). |
|
320 */ |
|
321 JSAtom *atom = AtomizeString(cx, source); |
|
322 jsatomid _; |
|
323 if (!atom || !bce.makeAtomIndex(atom, &_)) |
|
324 return nullptr; |
|
325 } |
|
326 |
|
327 if (evalCaller && evalCaller->functionOrCallerFunction()) { |
|
328 /* |
|
329 * An eval script in a caller frame needs to have its enclosing |
|
330 * function captured in case it refers to an upvar, and someone |
|
331 * wishes to decompile it while it's running. |
|
332 */ |
|
333 JSFunction *fun = evalCaller->functionOrCallerFunction(); |
|
334 Directives directives(/* strict = */ fun->strict()); |
|
335 ObjectBox *funbox = parser.newFunctionBox(/* fn = */ nullptr, fun, pc.addr(), |
|
336 directives, fun->generatorKind()); |
|
337 if (!funbox) |
|
338 return nullptr; |
|
339 bce.objectList.add(funbox); |
|
340 } |
|
341 } |
|
342 |
|
343 bool canHaveDirectives = true; |
|
344 for (;;) { |
|
345 TokenKind tt = parser.tokenStream.peekToken(TokenStream::Operand); |
|
346 if (tt <= TOK_EOF) { |
|
347 if (tt == TOK_EOF) |
|
348 break; |
|
349 JS_ASSERT(tt == TOK_ERROR); |
|
350 return nullptr; |
|
351 } |
|
352 |
|
353 TokenStream::Position pos(parser.keepAtoms); |
|
354 parser.tokenStream.tell(&pos); |
|
355 |
|
356 ParseNode *pn = parser.statement(canHaveDirectives); |
|
357 if (!pn) { |
|
358 if (parser.hadAbortedSyntaxParse()) { |
|
359 // Parsing inner functions lazily may lead the parser into an |
|
360 // unrecoverable state and may require starting over on the top |
|
361 // level statement. Restart the parse; syntax parsing has |
|
362 // already been disabled for the parser and the result will not |
|
363 // be ambiguous. |
|
364 parser.clearAbortedSyntaxParse(); |
|
365 parser.tokenStream.seek(pos); |
|
366 |
|
367 // Destroying the parse context will destroy its free |
|
368 // variables, so check if any deoptimization is needed. |
|
369 if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref())) |
|
370 return nullptr; |
|
371 |
|
372 pc.destroy(); |
|
373 pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, |
|
374 &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0, |
|
375 script->bindings.numBlockScoped()); |
|
376 if (!pc.ref().init(parser.tokenStream)) |
|
377 return nullptr; |
|
378 JS_ASSERT(parser.pc == pc.addr()); |
|
379 |
|
380 pn = parser.statement(); |
|
381 } |
|
382 if (!pn) { |
|
383 JS_ASSERT(!parser.hadAbortedSyntaxParse()); |
|
384 return nullptr; |
|
385 } |
|
386 } |
|
387 |
|
388 // Accumulate the maximum block scope depth, so that EmitTree can assert |
|
389 // when emitting JSOP_GETLOCAL that the local is indeed within the fixed |
|
390 // part of the stack frame. |
|
391 script->bindings.updateNumBlockScoped(pc.ref().blockScopeDepth); |
|
392 |
|
393 if (canHaveDirectives) { |
|
394 if (!parser.maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives)) |
|
395 return nullptr; |
|
396 } |
|
397 |
|
398 if (!FoldConstants(cx, &pn, &parser)) |
|
399 return nullptr; |
|
400 |
|
401 if (!NameFunctions(cx, pn)) |
|
402 return nullptr; |
|
403 |
|
404 if (!EmitTree(cx, &bce, pn)) |
|
405 return nullptr; |
|
406 |
|
407 parser.handler.freeTree(pn); |
|
408 } |
|
409 |
|
410 if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref())) |
|
411 return nullptr; |
|
412 |
|
413 if (!SetDisplayURL(cx, parser.tokenStream, ss)) |
|
414 return nullptr; |
|
415 |
|
416 if (!SetSourceMap(cx, parser.tokenStream, ss)) |
|
417 return nullptr; |
|
418 |
|
419 /* |
|
420 * Source map URLs passed as a compile option (usually via a HTTP source map |
|
421 * header) override any source map urls passed as comment pragmas. |
|
422 */ |
|
423 if (options.sourceMapURL()) { |
|
424 if (!ss->setSourceMapURL(cx, options.sourceMapURL())) |
|
425 return nullptr; |
|
426 } |
|
427 |
|
428 /* |
|
429 * Nowadays the threaded interpreter needs a last return instruction, so we |
|
430 * do have to emit that here. |
|
431 */ |
|
432 if (Emit1(cx, &bce, JSOP_RETRVAL) < 0) |
|
433 return nullptr; |
|
434 |
|
435 // Global/eval script bindings are always empty (all names are added to the |
|
436 // scope dynamically via JSOP_DEFFUN/VAR). They may have block-scoped |
|
437 // locals, however, which are allocated to the fixed part of the stack |
|
438 // frame. |
|
439 InternalHandle<Bindings*> bindings(script, &script->bindings); |
|
440 if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr, |
|
441 pc.ref().blockScopeDepth)) |
|
442 return nullptr; |
|
443 |
|
444 if (!JSScript::fullyInitFromEmitter(cx, script, &bce)) |
|
445 return nullptr; |
|
446 |
|
447 // Note that this marking must happen before we tell Debugger |
|
448 // about the new script, in case Debugger delazifies the script's |
|
449 // inner functions. |
|
450 if (options.forEval) |
|
451 MarkFunctionsWithinEvalScript(script); |
|
452 |
|
453 bce.tellDebuggerAboutCompiledScript(cx); |
|
454 |
|
455 if (sct && !extraSct && !sct->complete()) |
|
456 return nullptr; |
|
457 |
|
458 return script; |
|
459 } |
|
460 |
|
461 bool |
|
462 frontend::CompileLazyFunction(JSContext *cx, Handle<LazyScript*> lazy, const jschar *chars, size_t length) |
|
463 { |
|
464 JS_ASSERT(cx->compartment() == lazy->functionNonDelazifying()->compartment()); |
|
465 |
|
466 CompileOptions options(cx, lazy->version()); |
|
467 options.setOriginPrincipals(lazy->originPrincipals()) |
|
468 .setFileAndLine(lazy->source()->filename(), lazy->lineno()) |
|
469 .setColumn(lazy->column()) |
|
470 .setCompileAndGo(true) |
|
471 .setNoScriptRval(false) |
|
472 .setSelfHostingMode(false); |
|
473 |
|
474 js::TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime()); |
|
475 uint32_t logId = js::TraceLogCreateTextId(logger, options); |
|
476 js::AutoTraceLog scriptLogger(logger, logId); |
|
477 js::AutoTraceLog typeLogger(logger, TraceLogger::ParserCompileLazy); |
|
478 |
|
479 Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars, length, |
|
480 /* foldConstants = */ true, nullptr, lazy); |
|
481 |
|
482 uint32_t staticLevel = lazy->staticLevel(cx); |
|
483 |
|
484 Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying()); |
|
485 JS_ASSERT(!lazy->isLegacyGenerator()); |
|
486 ParseNode *pn = parser.standaloneLazyFunction(fun, staticLevel, lazy->strict(), |
|
487 lazy->generatorKind()); |
|
488 if (!pn) |
|
489 return false; |
|
490 |
|
491 if (!NameFunctions(cx, pn)) |
|
492 return false; |
|
493 |
|
494 RootedObject enclosingScope(cx, lazy->enclosingScope()); |
|
495 RootedScriptSource sourceObject(cx, lazy->sourceObject()); |
|
496 JS_ASSERT(sourceObject); |
|
497 |
|
498 Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, |
|
499 options, staticLevel, |
|
500 sourceObject, lazy->begin(), lazy->end())); |
|
501 if (!script) |
|
502 return false; |
|
503 |
|
504 script->bindings = pn->pn_funbox->bindings; |
|
505 |
|
506 if (lazy->directlyInsideEval()) |
|
507 script->setDirectlyInsideEval(); |
|
508 if (lazy->usesArgumentsAndApply()) |
|
509 script->setUsesArgumentsAndApply(); |
|
510 if (lazy->hasBeenCloned()) |
|
511 script->setHasBeenCloned(); |
|
512 |
|
513 BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, options.forEval, |
|
514 /* evalCaller = */ NullPtr(), /* hasGlobalScope = */ true, |
|
515 options.lineno, BytecodeEmitter::LazyFunction); |
|
516 if (!bce.init()) |
|
517 return false; |
|
518 |
|
519 if (lazy->treatAsRunOnce()) |
|
520 bce.lazyRunOnceLambda = true; |
|
521 |
|
522 return EmitFunctionScript(cx, &bce, pn->pn_body); |
|
523 } |
|
524 |
|
525 // Compile a JS function body, which might appear as the value of an event |
|
526 // handler attribute in an HTML <INPUT> tag, or in a Function() constructor. |
|
527 static bool |
|
528 CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, const ReadOnlyCompileOptions &options, |
|
529 const AutoNameVector &formals, SourceBufferHolder &srcBuf, |
|
530 GeneratorKind generatorKind) |
|
531 { |
|
532 js::TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime()); |
|
533 uint32_t logId = js::TraceLogCreateTextId(logger, options); |
|
534 js::AutoTraceLog scriptLogger(logger, logId); |
|
535 js::AutoTraceLog typeLogger(logger, TraceLogger::ParserCompileFunction); |
|
536 |
|
537 // FIXME: make Function pass in two strings and parse them as arguments and |
|
538 // ProgramElements respectively. |
|
539 |
|
540 MaybeCallSourceHandler(cx, options, srcBuf); |
|
541 |
|
542 if (!CheckLength(cx, srcBuf)) |
|
543 return false; |
|
544 |
|
545 RootedScriptSource sourceObject(cx, CreateScriptSourceObject(cx, options)); |
|
546 if (!sourceObject) |
|
547 return nullptr; |
|
548 ScriptSource *ss = sourceObject->source(); |
|
549 |
|
550 SourceCompressionTask sct(cx); |
|
551 JS_ASSERT(!options.sourceIsLazy); |
|
552 if (!cx->compartment()->options().discardSource()) { |
|
553 if (!ss->setSourceCopy(cx, srcBuf, true, &sct)) |
|
554 return false; |
|
555 } |
|
556 |
|
557 bool canLazilyParse = CanLazilyParse(cx, options); |
|
558 |
|
559 Maybe<Parser<SyntaxParseHandler> > syntaxParser; |
|
560 if (canLazilyParse) { |
|
561 syntaxParser.construct(cx, &cx->tempLifoAlloc(), |
|
562 options, srcBuf.get(), srcBuf.length(), |
|
563 /* foldConstants = */ false, |
|
564 (Parser<SyntaxParseHandler> *) nullptr, |
|
565 (LazyScript *) nullptr); |
|
566 } |
|
567 |
|
568 JS_ASSERT(!options.forEval); |
|
569 |
|
570 Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), |
|
571 options, srcBuf.get(), srcBuf.length(), |
|
572 /* foldConstants = */ true, |
|
573 canLazilyParse ? &syntaxParser.ref() : nullptr, nullptr); |
|
574 parser.sct = &sct; |
|
575 parser.ss = ss; |
|
576 |
|
577 JS_ASSERT(fun); |
|
578 JS_ASSERT(fun->isTenured()); |
|
579 |
|
580 fun->setArgCount(formals.length()); |
|
581 |
|
582 // Speculatively parse using the default directives implied by the context. |
|
583 // If a directive is encountered (e.g., "use strict") that changes how the |
|
584 // function should have been parsed, we backup and reparse with the new set |
|
585 // of directives. |
|
586 Directives directives(options.strictOption); |
|
587 |
|
588 TokenStream::Position start(parser.keepAtoms); |
|
589 parser.tokenStream.tell(&start); |
|
590 |
|
591 ParseNode *fn; |
|
592 while (true) { |
|
593 Directives newDirectives = directives; |
|
594 fn = parser.standaloneFunctionBody(fun, formals, generatorKind, directives, &newDirectives); |
|
595 if (fn) |
|
596 break; |
|
597 |
|
598 if (parser.hadAbortedSyntaxParse()) { |
|
599 // Hit some unrecoverable ambiguity during an inner syntax parse. |
|
600 // Syntax parsing has now been disabled in the parser, so retry |
|
601 // the parse. |
|
602 parser.clearAbortedSyntaxParse(); |
|
603 } else { |
|
604 if (parser.tokenStream.hadError() || directives == newDirectives) |
|
605 return false; |
|
606 |
|
607 // Assignment must be monotonic to prevent reparsing iloops |
|
608 JS_ASSERT_IF(directives.strict(), newDirectives.strict()); |
|
609 JS_ASSERT_IF(directives.asmJS(), newDirectives.asmJS()); |
|
610 directives = newDirectives; |
|
611 } |
|
612 |
|
613 parser.tokenStream.seek(start); |
|
614 } |
|
615 |
|
616 if (!NameFunctions(cx, fn)) |
|
617 return false; |
|
618 |
|
619 if (fn->pn_funbox->function()->isInterpreted()) { |
|
620 JS_ASSERT(fun == fn->pn_funbox->function()); |
|
621 |
|
622 Rooted<JSScript*> script(cx, JSScript::Create(cx, js::NullPtr(), false, options, |
|
623 /* staticLevel = */ 0, sourceObject, |
|
624 /* sourceStart = */ 0, srcBuf.length())); |
|
625 if (!script) |
|
626 return false; |
|
627 |
|
628 script->bindings = fn->pn_funbox->bindings; |
|
629 |
|
630 /* |
|
631 * The reason for checking fun->environment() below is that certain |
|
632 * consumers of JS::CompileFunction, namely |
|
633 * EventListenerManager::CompileEventHandlerInternal, passes in a |
|
634 * nullptr environment. This compiled function is never used, but |
|
635 * instead is cloned immediately onto the right scope chain. |
|
636 */ |
|
637 BytecodeEmitter funbce(/* parent = */ nullptr, &parser, fn->pn_funbox, script, |
|
638 /* insideEval = */ false, /* evalCaller = */ js::NullPtr(), |
|
639 fun->environment() && fun->environment()->is<GlobalObject>(), |
|
640 options.lineno); |
|
641 if (!funbce.init()) |
|
642 return false; |
|
643 |
|
644 if (!EmitFunctionScript(cx, &funbce, fn->pn_body)) |
|
645 return false; |
|
646 } else { |
|
647 fun.set(fn->pn_funbox->function()); |
|
648 JS_ASSERT(IsAsmJSModuleNative(fun->native())); |
|
649 } |
|
650 |
|
651 if (!SetDisplayURL(cx, parser.tokenStream, ss)) |
|
652 return false; |
|
653 |
|
654 if (!SetSourceMap(cx, parser.tokenStream, ss)) |
|
655 return false; |
|
656 |
|
657 if (!sct.complete()) |
|
658 return false; |
|
659 |
|
660 return true; |
|
661 } |
|
662 |
|
663 bool |
|
664 frontend::CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, |
|
665 const ReadOnlyCompileOptions &options, |
|
666 const AutoNameVector &formals, JS::SourceBufferHolder &srcBuf) |
|
667 { |
|
668 return CompileFunctionBody(cx, fun, options, formals, srcBuf, NotGenerator); |
|
669 } |
|
670 |
|
671 bool |
|
672 frontend::CompileStarGeneratorBody(JSContext *cx, MutableHandleFunction fun, |
|
673 const ReadOnlyCompileOptions &options, const AutoNameVector &formals, |
|
674 JS::SourceBufferHolder &srcBuf) |
|
675 { |
|
676 return CompileFunctionBody(cx, fun, options, formals, srcBuf, StarGenerator); |
|
677 } |