|
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 #ifndef frontend_SharedContext_h |
|
8 #define frontend_SharedContext_h |
|
9 |
|
10 #include "jsatom.h" |
|
11 #include "jsopcode.h" |
|
12 #include "jspubtd.h" |
|
13 #include "jsscript.h" |
|
14 #include "jstypes.h" |
|
15 |
|
16 #include "frontend/ParseMaps.h" |
|
17 #include "frontend/ParseNode.h" |
|
18 #include "frontend/TokenStream.h" |
|
19 #include "vm/ScopeObject.h" |
|
20 |
|
21 namespace js { |
|
22 namespace frontend { |
|
23 |
|
24 // These flags apply to both global and function contexts. |
|
25 class AnyContextFlags |
|
26 { |
|
27 // This class's data is all private and so only visible to these friends. |
|
28 friend class SharedContext; |
|
29 |
|
30 // True if "use strict"; appears in the body instead of being inherited. |
|
31 bool hasExplicitUseStrict:1; |
|
32 |
|
33 // The (static) bindings of this script need to support dynamic name |
|
34 // read/write access. Here, 'dynamic' means dynamic dictionary lookup on |
|
35 // the scope chain for a dynamic set of keys. The primary examples are: |
|
36 // - direct eval |
|
37 // - function:: |
|
38 // - with |
|
39 // since both effectively allow any name to be accessed. Non-examples are: |
|
40 // - upvars of nested functions |
|
41 // - function statement |
|
42 // since the set of assigned name is known dynamically. 'with' could be in |
|
43 // the non-example category, provided the set of all free variables within |
|
44 // the with block was noted. However, we do not optimize 'with' so, for |
|
45 // simplicity, 'with' is treated like eval. |
|
46 // |
|
47 // Note: access through the arguments object is not considered dynamic |
|
48 // binding access since it does not go through the normal name lookup |
|
49 // mechanism. This is debatable and could be changed (although care must be |
|
50 // taken not to turn off the whole 'arguments' optimization). To answer the |
|
51 // more general "is this argument aliased" question, script->needsArgsObj |
|
52 // should be tested (see JSScript::argIsAlised). |
|
53 // |
|
54 bool bindingsAccessedDynamically:1; |
|
55 |
|
56 // Whether this script, or any of its inner scripts contains a debugger |
|
57 // statement which could potentially read or write anywhere along the |
|
58 // scope chain. |
|
59 bool hasDebuggerStatement:1; |
|
60 |
|
61 public: |
|
62 AnyContextFlags() |
|
63 : hasExplicitUseStrict(false), |
|
64 bindingsAccessedDynamically(false), |
|
65 hasDebuggerStatement(false) |
|
66 { } |
|
67 }; |
|
68 |
|
69 class FunctionContextFlags |
|
70 { |
|
71 // This class's data is all private and so only visible to these friends. |
|
72 friend class FunctionBox; |
|
73 |
|
74 // The function or a function that encloses it may define new local names |
|
75 // at runtime through means other than calling eval. |
|
76 bool mightAliasLocals:1; |
|
77 |
|
78 // This function does something that can extend the set of bindings in its |
|
79 // call objects --- it does a direct eval in non-strict code, or includes a |
|
80 // function statement (as opposed to a function definition). |
|
81 // |
|
82 // This flag is *not* inherited by enclosed or enclosing functions; it |
|
83 // applies only to the function in whose flags it appears. |
|
84 // |
|
85 bool hasExtensibleScope:1; |
|
86 |
|
87 // This function refers directly to its name in a way which requires the |
|
88 // name to be a separate object on the scope chain. |
|
89 bool needsDeclEnvObject:1; |
|
90 |
|
91 // Technically, every function has a binding named 'arguments'. Internally, |
|
92 // this binding is only added when 'arguments' is mentioned by the function |
|
93 // body. This flag indicates whether 'arguments' has been bound either |
|
94 // through implicit use: |
|
95 // function f() { return arguments } |
|
96 // or explicit redeclaration: |
|
97 // function f() { var arguments; return arguments } |
|
98 // |
|
99 // Note 1: overwritten arguments (function() { arguments = 3 }) will cause |
|
100 // this flag to be set but otherwise require no special handling: |
|
101 // 'arguments' is just a local variable and uses of 'arguments' will just |
|
102 // read the local's current slot which may have been assigned. The only |
|
103 // special semantics is that the initial value of 'arguments' is the |
|
104 // arguments object (not undefined, like normal locals). |
|
105 // |
|
106 // Note 2: if 'arguments' is bound as a formal parameter, there will be an |
|
107 // 'arguments' in Bindings, but, as the "LOCAL" in the name indicates, this |
|
108 // flag will not be set. This is because, as a formal, 'arguments' will |
|
109 // have no special semantics: the initial value is unconditionally the |
|
110 // actual argument (or undefined if nactual < nformal). |
|
111 // |
|
112 bool argumentsHasLocalBinding:1; |
|
113 |
|
114 // In many cases where 'arguments' has a local binding (as described above) |
|
115 // we do not need to actually create an arguments object in the function |
|
116 // prologue: instead we can analyze how 'arguments' is used (using the |
|
117 // simple dataflow analysis in analyzeSSA) to determine that uses of |
|
118 // 'arguments' can just read from the stack frame directly. However, the |
|
119 // dataflow analysis only looks at how JSOP_ARGUMENTS is used, so it will |
|
120 // be unsound in several cases. The frontend filters out such cases by |
|
121 // setting this flag which eagerly sets script->needsArgsObj to true. |
|
122 // |
|
123 bool definitelyNeedsArgsObj:1; |
|
124 |
|
125 public: |
|
126 FunctionContextFlags() |
|
127 : mightAliasLocals(false), |
|
128 hasExtensibleScope(false), |
|
129 needsDeclEnvObject(false), |
|
130 argumentsHasLocalBinding(false), |
|
131 definitelyNeedsArgsObj(false) |
|
132 { } |
|
133 }; |
|
134 |
|
135 class GlobalSharedContext; |
|
136 |
|
137 // List of directives that may be encountered in a Directive Prologue (ES5 15.1). |
|
138 class Directives |
|
139 { |
|
140 bool strict_; |
|
141 bool asmJS_; |
|
142 |
|
143 public: |
|
144 explicit Directives(bool strict) : strict_(strict), asmJS_(false) {} |
|
145 template <typename ParseHandler> explicit Directives(ParseContext<ParseHandler> *parent); |
|
146 |
|
147 void setStrict() { strict_ = true; } |
|
148 bool strict() const { return strict_; } |
|
149 |
|
150 void setAsmJS() { asmJS_ = true; } |
|
151 bool asmJS() const { return asmJS_; } |
|
152 |
|
153 Directives &operator=(Directives rhs) { |
|
154 strict_ = rhs.strict_; |
|
155 asmJS_ = rhs.asmJS_; |
|
156 return *this; |
|
157 } |
|
158 bool operator==(const Directives &rhs) const { |
|
159 return strict_ == rhs.strict_ && asmJS_ == rhs.asmJS_; |
|
160 } |
|
161 bool operator!=(const Directives &rhs) const { |
|
162 return !(*this == rhs); |
|
163 } |
|
164 }; |
|
165 |
|
166 /* |
|
167 * The struct SharedContext is part of the current parser context (see |
|
168 * ParseContext). It stores information that is reused between the parser and |
|
169 * the bytecode emitter. Note however, that this information is not shared |
|
170 * between the two; they simply reuse the same data structure. |
|
171 */ |
|
172 class SharedContext |
|
173 { |
|
174 public: |
|
175 ExclusiveContext *const context; |
|
176 AnyContextFlags anyCxFlags; |
|
177 bool strict; |
|
178 bool extraWarnings; |
|
179 |
|
180 // If it's function code, funbox must be non-nullptr and scopeChain must be |
|
181 // nullptr. If it's global code, funbox must be nullptr. |
|
182 SharedContext(ExclusiveContext *cx, Directives directives, bool extraWarnings) |
|
183 : context(cx), |
|
184 anyCxFlags(), |
|
185 strict(directives.strict()), |
|
186 extraWarnings(extraWarnings) |
|
187 {} |
|
188 |
|
189 virtual ObjectBox *toObjectBox() = 0; |
|
190 inline bool isGlobalSharedContext() { return toObjectBox() == nullptr; } |
|
191 inline bool isFunctionBox() { return toObjectBox() && toObjectBox()->isFunctionBox(); } |
|
192 inline GlobalSharedContext *asGlobalSharedContext(); |
|
193 inline FunctionBox *asFunctionBox(); |
|
194 |
|
195 bool hasExplicitUseStrict() const { return anyCxFlags.hasExplicitUseStrict; } |
|
196 bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; } |
|
197 bool hasDebuggerStatement() const { return anyCxFlags.hasDebuggerStatement; } |
|
198 |
|
199 void setExplicitUseStrict() { anyCxFlags.hasExplicitUseStrict = true; } |
|
200 void setBindingsAccessedDynamically() { anyCxFlags.bindingsAccessedDynamically = true; } |
|
201 void setHasDebuggerStatement() { anyCxFlags.hasDebuggerStatement = true; } |
|
202 |
|
203 inline bool allLocalsAliased(); |
|
204 |
|
205 // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors. |
|
206 bool needStrictChecks() { |
|
207 return strict || extraWarnings; |
|
208 } |
|
209 }; |
|
210 |
|
211 class GlobalSharedContext : public SharedContext |
|
212 { |
|
213 private: |
|
214 const RootedObject scopeChain_; /* scope chain object for the script */ |
|
215 |
|
216 public: |
|
217 GlobalSharedContext(ExclusiveContext *cx, JSObject *scopeChain, |
|
218 Directives directives, bool extraWarnings) |
|
219 : SharedContext(cx, directives, extraWarnings), |
|
220 scopeChain_(cx, scopeChain) |
|
221 {} |
|
222 |
|
223 ObjectBox *toObjectBox() { return nullptr; } |
|
224 JSObject *scopeChain() const { return scopeChain_; } |
|
225 }; |
|
226 |
|
227 inline GlobalSharedContext * |
|
228 SharedContext::asGlobalSharedContext() |
|
229 { |
|
230 JS_ASSERT(isGlobalSharedContext()); |
|
231 return static_cast<GlobalSharedContext*>(this); |
|
232 } |
|
233 |
|
234 class FunctionBox : public ObjectBox, public SharedContext |
|
235 { |
|
236 public: |
|
237 Bindings bindings; /* bindings for this function */ |
|
238 uint32_t bufStart; |
|
239 uint32_t bufEnd; |
|
240 uint32_t startLine; |
|
241 uint32_t startColumn; |
|
242 uint16_t length; |
|
243 |
|
244 uint8_t generatorKindBits_; /* The GeneratorKind of this function. */ |
|
245 bool inWith:1; /* some enclosing scope is a with-statement */ |
|
246 bool inGenexpLambda:1; /* lambda from generator expression */ |
|
247 bool hasDestructuringArgs:1; /* arguments list contains destructuring expression */ |
|
248 bool useAsm:1; /* function contains "use asm" directive */ |
|
249 bool insideUseAsm:1; /* nested function of function of "use asm" directive */ |
|
250 |
|
251 // Fields for use in heuristics. |
|
252 bool usesArguments:1; /* contains a free use of 'arguments' */ |
|
253 bool usesApply:1; /* contains an f.apply() call */ |
|
254 |
|
255 FunctionContextFlags funCxFlags; |
|
256 |
|
257 template <typename ParseHandler> |
|
258 FunctionBox(ExclusiveContext *cx, ObjectBox* traceListHead, JSFunction *fun, |
|
259 ParseContext<ParseHandler> *pc, Directives directives, |
|
260 bool extraWarnings, GeneratorKind generatorKind); |
|
261 |
|
262 ObjectBox *toObjectBox() { return this; } |
|
263 JSFunction *function() const { return &object->as<JSFunction>(); } |
|
264 |
|
265 GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); } |
|
266 bool isGenerator() const { return generatorKind() != NotGenerator; } |
|
267 bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; } |
|
268 bool isStarGenerator() const { return generatorKind() == StarGenerator; } |
|
269 |
|
270 void setGeneratorKind(GeneratorKind kind) { |
|
271 // A generator kind can be set at initialization, or when "yield" is |
|
272 // first seen. In both cases the transition can only happen from |
|
273 // NotGenerator. |
|
274 JS_ASSERT(!isGenerator()); |
|
275 generatorKindBits_ = GeneratorKindAsBits(kind); |
|
276 } |
|
277 |
|
278 bool mightAliasLocals() const { return funCxFlags.mightAliasLocals; } |
|
279 bool hasExtensibleScope() const { return funCxFlags.hasExtensibleScope; } |
|
280 bool needsDeclEnvObject() const { return funCxFlags.needsDeclEnvObject; } |
|
281 bool argumentsHasLocalBinding() const { return funCxFlags.argumentsHasLocalBinding; } |
|
282 bool definitelyNeedsArgsObj() const { return funCxFlags.definitelyNeedsArgsObj; } |
|
283 |
|
284 void setMightAliasLocals() { funCxFlags.mightAliasLocals = true; } |
|
285 void setHasExtensibleScope() { funCxFlags.hasExtensibleScope = true; } |
|
286 void setNeedsDeclEnvObject() { funCxFlags.needsDeclEnvObject = true; } |
|
287 void setArgumentsHasLocalBinding() { funCxFlags.argumentsHasLocalBinding = true; } |
|
288 void setDefinitelyNeedsArgsObj() { JS_ASSERT(funCxFlags.argumentsHasLocalBinding); |
|
289 funCxFlags.definitelyNeedsArgsObj = true; } |
|
290 |
|
291 bool hasDefaults() const { |
|
292 return length != function()->nargs() - function()->hasRest(); |
|
293 } |
|
294 |
|
295 // Return whether this function has either specified "use asm" or is |
|
296 // (transitively) nested inside a function that has. |
|
297 bool useAsmOrInsideUseAsm() const { |
|
298 return useAsm || insideUseAsm; |
|
299 } |
|
300 |
|
301 void setStart(const TokenStream &tokenStream) { |
|
302 bufStart = tokenStream.currentToken().pos.begin; |
|
303 startLine = tokenStream.getLineno(); |
|
304 startColumn = tokenStream.getColumn(); |
|
305 } |
|
306 |
|
307 bool isHeavyweight() |
|
308 { |
|
309 // Note: this should be kept in sync with JSFunction::isHeavyweight(). |
|
310 return bindings.hasAnyAliasedBindings() || |
|
311 hasExtensibleScope() || |
|
312 needsDeclEnvObject() || |
|
313 isGenerator(); |
|
314 } |
|
315 }; |
|
316 |
|
317 inline FunctionBox * |
|
318 SharedContext::asFunctionBox() |
|
319 { |
|
320 JS_ASSERT(isFunctionBox()); |
|
321 return static_cast<FunctionBox*>(this); |
|
322 } |
|
323 |
|
324 // In generators, we treat all locals as aliased so that they get stored on the |
|
325 // heap. This way there is less information to copy off the stack when |
|
326 // suspending, and back on when resuming. It also avoids the need to create and |
|
327 // invalidate DebugScope proxies for unaliased locals in a generator frame, as |
|
328 // the generator frame will be copied out to the heap and released only by GC. |
|
329 inline bool |
|
330 SharedContext::allLocalsAliased() |
|
331 { |
|
332 return bindingsAccessedDynamically() || (isFunctionBox() && asFunctionBox()->isGenerator()); |
|
333 } |
|
334 |
|
335 |
|
336 /* |
|
337 * NB: If you add a new type of statement that is a scope, add it between |
|
338 * STMT_WITH and STMT_CATCH, or you will break StmtInfoBase::linksScope. If you |
|
339 * add a non-looping statement type, add it before STMT_DO_LOOP or you will |
|
340 * break StmtInfoBase::isLoop(). |
|
341 * |
|
342 * Also remember to keep the statementName array in BytecodeEmitter.cpp in |
|
343 * sync. |
|
344 */ |
|
345 enum StmtType { |
|
346 STMT_LABEL, /* labeled statement: L: s */ |
|
347 STMT_IF, /* if (then) statement */ |
|
348 STMT_ELSE, /* else clause of if statement */ |
|
349 STMT_SEQ, /* synthetic sequence of statements */ |
|
350 STMT_BLOCK, /* compound statement: { s1[;... sN] } */ |
|
351 STMT_SWITCH, /* switch statement */ |
|
352 STMT_WITH, /* with statement */ |
|
353 STMT_CATCH, /* catch block */ |
|
354 STMT_TRY, /* try block */ |
|
355 STMT_FINALLY, /* finally block */ |
|
356 STMT_SUBROUTINE, /* gosub-target subroutine body */ |
|
357 STMT_DO_LOOP, /* do/while loop statement */ |
|
358 STMT_FOR_LOOP, /* for loop statement */ |
|
359 STMT_FOR_IN_LOOP, /* for/in loop statement */ |
|
360 STMT_FOR_OF_LOOP, /* for/of loop statement */ |
|
361 STMT_WHILE_LOOP, /* while loop statement */ |
|
362 STMT_LIMIT |
|
363 }; |
|
364 |
|
365 /* |
|
366 * A comment on the encoding of the js::StmtType enum and StmtInfoBase |
|
367 * type-testing methods: |
|
368 * |
|
369 * StmtInfoBase::maybeScope() tells whether a statement type is always, or may |
|
370 * become, a lexical scope. It therefore includes block and switch (the two |
|
371 * low-numbered "maybe" scope types) and excludes with (with has dynamic scope |
|
372 * pending the "reformed with" in ES4/JS2). It includes all try-catch-finally |
|
373 * types, which are high-numbered maybe-scope types. |
|
374 * |
|
375 * StmtInfoBase::linksScope() tells whether a js::StmtInfo{PC,BCE} of the given |
|
376 * type eagerly links to other scoping statement info records. It excludes the |
|
377 * two early "maybe" types, block and switch, as well as the try and both |
|
378 * finally types, since try and the other trailing maybe-scope types don't need |
|
379 * block scope unless they contain let declarations. |
|
380 * |
|
381 * We treat WITH as a static scope because it prevents lexical binding from |
|
382 * continuing further up the static scope chain. With the lost "reformed with" |
|
383 * proposal for ES4, we would be able to model it statically, too. |
|
384 */ |
|
385 |
|
386 // StmtInfoPC is used by the Parser. StmtInfoBCE is used by the |
|
387 // BytecodeEmitter. The two types have some overlap, encapsulated by |
|
388 // StmtInfoBase. Several functions below (e.g. PushStatement) are templated to |
|
389 // work with both types. |
|
390 |
|
391 struct StmtInfoBase { |
|
392 // Statement type (StmtType). |
|
393 uint16_t type; |
|
394 |
|
395 // True if type is STMT_BLOCK, STMT_TRY, STMT_SWITCH, or STMT_FINALLY and |
|
396 // the block contains at least one let-declaration, or if type is |
|
397 // STMT_CATCH. |
|
398 bool isBlockScope:1; |
|
399 |
|
400 // True if isBlockScope or type == STMT_WITH. |
|
401 bool isNestedScope:1; |
|
402 |
|
403 // for (let ...) induced block scope |
|
404 bool isForLetBlock:1; |
|
405 |
|
406 // Block label. |
|
407 RootedAtom label; |
|
408 |
|
409 // Compile-time scope chain node for this scope. Only set if |
|
410 // isNestedScope. |
|
411 Rooted<NestedScopeObject *> staticScope; |
|
412 |
|
413 StmtInfoBase(ExclusiveContext *cx) |
|
414 : isBlockScope(false), isNestedScope(false), isForLetBlock(false), |
|
415 label(cx), staticScope(cx) |
|
416 {} |
|
417 |
|
418 bool maybeScope() const { |
|
419 return STMT_BLOCK <= type && type <= STMT_SUBROUTINE && type != STMT_WITH; |
|
420 } |
|
421 |
|
422 bool linksScope() const { |
|
423 return isNestedScope; |
|
424 } |
|
425 |
|
426 StaticBlockObject& staticBlock() const { |
|
427 JS_ASSERT(isNestedScope); |
|
428 JS_ASSERT(isBlockScope); |
|
429 return staticScope->as<StaticBlockObject>(); |
|
430 } |
|
431 |
|
432 bool isLoop() const { |
|
433 return type >= STMT_DO_LOOP; |
|
434 } |
|
435 |
|
436 bool isTrying() const { |
|
437 return STMT_TRY <= type && type <= STMT_SUBROUTINE; |
|
438 } |
|
439 }; |
|
440 |
|
441 // Push the C-stack-allocated struct at stmt onto the StmtInfoPC stack. |
|
442 template <class ContextT> |
|
443 void |
|
444 PushStatement(ContextT *ct, typename ContextT::StmtInfo *stmt, StmtType type) |
|
445 { |
|
446 stmt->type = type; |
|
447 stmt->isBlockScope = false; |
|
448 stmt->isNestedScope = false; |
|
449 stmt->isForLetBlock = false; |
|
450 stmt->label = nullptr; |
|
451 stmt->staticScope = nullptr; |
|
452 stmt->down = ct->topStmt; |
|
453 ct->topStmt = stmt; |
|
454 if (stmt->linksScope()) { |
|
455 stmt->downScope = ct->topScopeStmt; |
|
456 ct->topScopeStmt = stmt; |
|
457 } else { |
|
458 stmt->downScope = nullptr; |
|
459 } |
|
460 } |
|
461 |
|
462 template <class ContextT> |
|
463 void |
|
464 FinishPushNestedScope(ContextT *ct, typename ContextT::StmtInfo *stmt, NestedScopeObject &staticScope) |
|
465 { |
|
466 stmt->isNestedScope = true; |
|
467 stmt->downScope = ct->topScopeStmt; |
|
468 ct->topScopeStmt = stmt; |
|
469 ct->staticScope = &staticScope; |
|
470 stmt->staticScope = &staticScope; |
|
471 } |
|
472 |
|
473 // Pop pc->topStmt. If the top StmtInfoPC struct is not stack-allocated, it |
|
474 // is up to the caller to free it. The dummy argument is just to make the |
|
475 // template matching work. |
|
476 template <class ContextT> |
|
477 void |
|
478 FinishPopStatement(ContextT *ct) |
|
479 { |
|
480 typename ContextT::StmtInfo *stmt = ct->topStmt; |
|
481 ct->topStmt = stmt->down; |
|
482 if (stmt->linksScope()) { |
|
483 ct->topScopeStmt = stmt->downScope; |
|
484 if (stmt->isNestedScope) { |
|
485 JS_ASSERT(stmt->staticScope); |
|
486 ct->staticScope = stmt->staticScope->enclosingNestedScope(); |
|
487 } |
|
488 } |
|
489 } |
|
490 |
|
491 /* |
|
492 * Find a lexically scoped variable (one declared by let, catch, or an array |
|
493 * comprehension) named by atom, looking in sc's compile-time scopes. |
|
494 * |
|
495 * If a WITH statement is reached along the scope stack, return its statement |
|
496 * info record, so callers can tell that atom is ambiguous. If slotp is not |
|
497 * null, then if atom is found, set *slotp to its stack slot, otherwise to -1. |
|
498 * This means that if slotp is not null, all the block objects on the lexical |
|
499 * scope chain must have had their depth slots computed by the code generator, |
|
500 * so the caller must be under EmitTree. |
|
501 * |
|
502 * In any event, directly return the statement info record in which atom was |
|
503 * found. Otherwise return null. |
|
504 */ |
|
505 template <class ContextT> |
|
506 typename ContextT::StmtInfo * |
|
507 LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::StmtInfo *stmt); |
|
508 |
|
509 } // namespace frontend |
|
510 |
|
511 } // namespace js |
|
512 |
|
513 #endif /* frontend_SharedContext_h */ |