js/src/vm/ScopeObject.h

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:97efc0cbd19e
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 vm_ScopeObject_h
8 #define vm_ScopeObject_h
9
10 #include "jscntxt.h"
11 #include "jsobj.h"
12 #include "jsweakmap.h"
13
14 #include "gc/Barrier.h"
15 #include "vm/ProxyObject.h"
16
17 namespace js {
18
19 namespace frontend { struct Definition; }
20
21 class StaticWithObject;
22
23 /*****************************************************************************/
24
25 /*
26 * All function scripts have an "enclosing static scope" that refers to the
27 * innermost enclosing let or function in the program text. This allows full
28 * reconstruction of the lexical scope for debugging or compiling efficient
29 * access to variables in enclosing scopes. The static scope is represented at
30 * runtime by a tree of compiler-created objects representing each scope:
31 * - a StaticBlockObject is created for 'let' and 'catch' scopes
32 * - a JSFunction+JSScript+Bindings trio is created for function scopes
33 * (These objects are primarily used to clone objects scopes for the
34 * dynamic scope chain.)
35 *
36 * There is an additional scope for named lambdas. E.g., in:
37 *
38 * (function f() { var x; function g() { } })
39 *
40 * g's innermost enclosing scope will first be the function scope containing
41 * 'x', enclosed by a scope containing only the name 'f'. (This separate scope
42 * is necessary due to the fact that declarations in the function scope shadow
43 * (dynamically, in the case of 'eval') the lambda name.)
44 *
45 * There are two limitations to the current lexical nesting information:
46 *
47 * - 'with' is completely absent; this isn't a problem for the current use
48 * cases since 'with' causes every static scope to be on the dynamic scope
49 * chain (so the debugger can find everything) and inhibits all upvar
50 * optimization.
51 *
52 * - The "enclosing static scope" chain stops at 'eval'. For example in:
53 * let (x) { eval("function f() {}") }
54 * f does not have an enclosing static scope. This is fine for current uses
55 * for the same reason as 'with'.
56 *
57 * (See also AssertDynamicScopeMatchesStaticScope.)
58 */
59 template <AllowGC allowGC>
60 class StaticScopeIter
61 {
62 typename MaybeRooted<JSObject*, allowGC>::RootType obj;
63 bool onNamedLambda;
64
65 public:
66 StaticScopeIter(ExclusiveContext *cx, JSObject *obj)
67 : obj(cx, obj), onNamedLambda(false)
68 {
69 JS_STATIC_ASSERT(allowGC == CanGC);
70 JS_ASSERT_IF(obj, obj->is<StaticBlockObject>() || obj->is<StaticWithObject>() ||
71 obj->is<JSFunction>());
72 }
73
74 StaticScopeIter(JSObject *obj)
75 : obj((ExclusiveContext *) nullptr, obj), onNamedLambda(false)
76 {
77 JS_STATIC_ASSERT(allowGC == NoGC);
78 JS_ASSERT_IF(obj, obj->is<StaticBlockObject>() || obj->is<StaticWithObject>() ||
79 obj->is<JSFunction>());
80 }
81
82 bool done() const;
83 void operator++(int);
84
85 /* Return whether this static scope will be on the dynamic scope chain. */
86 bool hasDynamicScopeObject() const;
87 Shape *scopeShape() const;
88
89 enum Type { WITH, BLOCK, FUNCTION, NAMED_LAMBDA };
90 Type type() const;
91
92 StaticBlockObject &block() const;
93 StaticWithObject &staticWith() const;
94 JSScript *funScript() const;
95 };
96
97 /*****************************************************************************/
98
99 /*
100 * A "scope coordinate" describes how to get from head of the scope chain to a
101 * given lexically-enclosing variable. A scope coordinate has two dimensions:
102 * - hops: the number of scope objects on the scope chain to skip
103 * - slot: the slot on the scope object holding the variable's value
104 */
105 class ScopeCoordinate
106 {
107 uint32_t hops_;
108 uint32_t slot_;
109
110 /*
111 * Technically, hops_/slot_ are SCOPECOORD_(HOPS|SLOT)_BITS wide. Since
112 * ScopeCoordinate is a temporary value, don't bother with a bitfield as
113 * this only adds overhead.
114 */
115 static_assert(SCOPECOORD_HOPS_BITS <= 32, "We have enough bits below");
116 static_assert(SCOPECOORD_SLOT_BITS <= 32, "We have enough bits below");
117
118 public:
119 inline ScopeCoordinate(jsbytecode *pc)
120 : hops_(GET_SCOPECOORD_HOPS(pc)), slot_(GET_SCOPECOORD_SLOT(pc + SCOPECOORD_HOPS_LEN))
121 {
122 JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD);
123 }
124
125 inline ScopeCoordinate() {}
126
127 void setHops(uint32_t hops) { JS_ASSERT(hops < SCOPECOORD_HOPS_LIMIT); hops_ = hops; }
128 void setSlot(uint32_t slot) { JS_ASSERT(slot < SCOPECOORD_SLOT_LIMIT); slot_ = slot; }
129
130 uint32_t hops() const { JS_ASSERT(hops_ < SCOPECOORD_HOPS_LIMIT); return hops_; }
131 uint32_t slot() const { JS_ASSERT(slot_ < SCOPECOORD_SLOT_LIMIT); return slot_; }
132 };
133
134 /*
135 * Return a shape representing the static scope containing the variable
136 * accessed by the ALIASEDVAR op at 'pc'.
137 */
138 extern Shape *
139 ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc);
140
141 /* Return the name being accessed by the given ALIASEDVAR op. */
142 extern PropertyName *
143 ScopeCoordinateName(ScopeCoordinateNameCache &cache, JSScript *script, jsbytecode *pc);
144
145 /* Return the function script accessed by the given ALIASEDVAR op, or nullptr. */
146 extern JSScript *
147 ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc);
148
149 /*****************************************************************************/
150
151 /*
152 * Scope objects
153 *
154 * Scope objects are technically real JSObjects but only belong on the scope
155 * chain (that is, fp->scopeChain() or fun->environment()). The hierarchy of
156 * scope objects is:
157 *
158 * JSObject Generic object
159 * \
160 * ScopeObject Engine-internal scope
161 * \ \ \
162 * \ \ DeclEnvObject Holds name of recursive/heavyweight named lambda
163 * \ \
164 * \ CallObject Scope of entire function or strict eval
165 * \
166 * NestedScopeObject Scope created for a statement
167 * \ \ \
168 * \ \ StaticWithObject Template for "with" object in static scope chain
169 * \ \
170 * \ DynamicWithObject Run-time "with" object on scope chain
171 * \
172 * BlockObject Shared interface of cloned/static block objects
173 * \ \
174 * \ ClonedBlockObject let, switch, catch, for
175 * \
176 * StaticBlockObject See NB
177 *
178 * This hierarchy represents more than just the interface hierarchy: reserved
179 * slots in base classes are fixed for all derived classes. Thus, for example,
180 * ScopeObject::enclosingScope() can simply access a fixed slot without further
181 * dynamic type information.
182 *
183 * NB: Static block objects are a special case: these objects are created at
184 * compile time to hold the shape/binding information from which block objects
185 * are cloned at runtime. These objects should never escape into the wild and
186 * support a restricted set of ScopeObject operations.
187 *
188 * See also "Debug scope objects" below.
189 */
190
191 class ScopeObject : public JSObject
192 {
193 protected:
194 static const uint32_t SCOPE_CHAIN_SLOT = 0;
195
196 public:
197 /*
198 * Since every scope chain terminates with a global object and GlobalObject
199 * does not derive ScopeObject (it has a completely different layout), the
200 * enclosing scope of a ScopeObject is necessarily non-null.
201 */
202 inline JSObject &enclosingScope() const {
203 return getFixedSlot(SCOPE_CHAIN_SLOT).toObject();
204 }
205
206 void setEnclosingScope(HandleObject obj);
207
208 /*
209 * Get or set an aliased variable contained in this scope. Unaliased
210 * variables should instead access the stack frame. Aliased variable access
211 * is primarily made through JOF_SCOPECOORD ops which is why these members
212 * take a ScopeCoordinate instead of just the slot index.
213 */
214 inline const Value &aliasedVar(ScopeCoordinate sc);
215
216 inline void setAliasedVar(JSContext *cx, ScopeCoordinate sc, PropertyName *name, const Value &v);
217
218 /* For jit access. */
219 static size_t offsetOfEnclosingScope() {
220 return getFixedSlotOffset(SCOPE_CHAIN_SLOT);
221 }
222
223 static size_t enclosingScopeSlot() {
224 return SCOPE_CHAIN_SLOT;
225 }
226 };
227
228 class CallObject : public ScopeObject
229 {
230 static const uint32_t CALLEE_SLOT = 1;
231
232 static CallObject *
233 create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee);
234
235 public:
236 static const Class class_;
237
238 /* These functions are internal and are exposed only for JITs. */
239
240 /*
241 * Construct a bare-bones call object given a shape, a non-singleton type,
242 * and slots pointer. The call object must be further initialized to be
243 * usable.
244 */
245 static CallObject *
246 create(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots);
247
248 /*
249 * Construct a bare-bones call object given a shape and slots pointer, and
250 * make it have singleton type. The call object must be initialized to be
251 * usable.
252 */
253 static CallObject *
254 createSingleton(JSContext *cx, HandleShape shape, HeapSlot *slots);
255
256 static CallObject *
257 createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap);
258
259 static const uint32_t RESERVED_SLOTS = 2;
260
261 static CallObject *createForFunction(JSContext *cx, HandleObject enclosing, HandleFunction callee);
262
263 static CallObject *createForFunction(JSContext *cx, AbstractFramePtr frame);
264 static CallObject *createForStrictEval(JSContext *cx, AbstractFramePtr frame);
265
266 /* True if this is for a strict mode eval frame. */
267 bool isForEval() const {
268 JS_ASSERT(getFixedSlot(CALLEE_SLOT).isObjectOrNull());
269 JS_ASSERT_IF(getFixedSlot(CALLEE_SLOT).isObject(),
270 getFixedSlot(CALLEE_SLOT).toObject().is<JSFunction>());
271 return getFixedSlot(CALLEE_SLOT).isNull();
272 }
273
274 /*
275 * Returns the function for which this CallObject was created. (This may
276 * only be called if !isForEval.)
277 */
278 JSFunction &callee() const {
279 return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
280 }
281
282 /* Get/set the aliased variable referred to by 'bi'. */
283 const Value &aliasedVar(AliasedFormalIter fi) {
284 return getSlot(fi.scopeSlot());
285 }
286 inline void setAliasedVar(JSContext *cx, AliasedFormalIter fi, PropertyName *name,
287 const Value &v);
288
289 /*
290 * When an aliased var (var accessed by nested closures) is also aliased by
291 * the arguments object, it must of course exist in one canonical location
292 * and that location is always the CallObject. For this to work, the
293 * ArgumentsObject stores special MagicValue in its array for forwarded-to-
294 * CallObject variables. This MagicValue's payload is the slot of the
295 * CallObject to access.
296 */
297 const Value &aliasedVarFromArguments(const Value &argsValue) {
298 return getSlot(argsValue.magicUint32());
299 }
300 inline void setAliasedVarFromArguments(JSContext *cx, const Value &argsValue, jsid id,
301 const Value &v);
302
303 /* For jit access. */
304 static size_t offsetOfCallee() {
305 return getFixedSlotOffset(CALLEE_SLOT);
306 }
307
308 static size_t calleeSlot() {
309 return CALLEE_SLOT;
310 }
311 };
312
313 class DeclEnvObject : public ScopeObject
314 {
315 // Pre-allocated slot for the named lambda.
316 static const uint32_t LAMBDA_SLOT = 1;
317
318 public:
319 static const uint32_t RESERVED_SLOTS = 2;
320 static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND;
321
322 static const Class class_;
323
324 static DeclEnvObject *
325 createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap);
326
327 static DeclEnvObject *create(JSContext *cx, HandleObject enclosing, HandleFunction callee);
328
329 static inline size_t lambdaSlot() {
330 return LAMBDA_SLOT;
331 }
332 };
333
334 class NestedScopeObject : public ScopeObject
335 {
336 public:
337 /*
338 * A refinement of enclosingScope that returns nullptr if the enclosing
339 * scope is not a NestedScopeObject.
340 */
341 inline NestedScopeObject *enclosingNestedScope() const;
342
343 // Return true if this object is a compile-time scope template.
344 inline bool isStatic() { return !getProto(); }
345
346 // Return the static scope corresponding to this scope chain object.
347 inline NestedScopeObject* staticScope() {
348 JS_ASSERT(!isStatic());
349 return &getProto()->as<NestedScopeObject>();
350 }
351
352 // At compile-time it's possible for the scope chain to be null.
353 JSObject *enclosingScopeForStaticScopeIter() {
354 return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
355 }
356
357 void initEnclosingNestedScope(JSObject *obj) {
358 JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
359 setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
360 }
361
362 /*
363 * The parser uses 'enclosingNestedScope' as the prev-link in the
364 * pc->staticScope stack. Note: in the case of hoisting, this prev-link will
365 * not ultimately be the same as enclosingNestedScope;
366 * initEnclosingNestedScope must be called separately in the
367 * emitter. 'reset' is just for asserting stackiness.
368 */
369 void initEnclosingNestedScopeFromParser(NestedScopeObject *prev) {
370 setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
371 }
372
373 void resetEnclosingNestedScopeFromParser() {
374 setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
375 }
376 };
377
378 // With scope template objects on the static scope chain.
379 class StaticWithObject : public NestedScopeObject
380 {
381 public:
382 static const unsigned RESERVED_SLOTS = 1;
383 static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND;
384
385 static const Class class_;
386
387 static StaticWithObject *create(ExclusiveContext *cx);
388 };
389
390 // With scope objects on the run-time scope chain.
391 class DynamicWithObject : public NestedScopeObject
392 {
393 static const unsigned OBJECT_SLOT = 1;
394 static const unsigned THIS_SLOT = 2;
395
396 public:
397 static const unsigned RESERVED_SLOTS = 3;
398 static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
399
400 static const Class class_;
401
402 static DynamicWithObject *
403 create(JSContext *cx, HandleObject object, HandleObject enclosing, HandleObject staticWith);
404
405 StaticWithObject& staticWith() const {
406 return getProto()->as<StaticWithObject>();
407 }
408
409 /* Return the 'o' in 'with (o)'. */
410 JSObject &object() const {
411 return getReservedSlot(OBJECT_SLOT).toObject();
412 }
413
414 /* Return object for the 'this' class hook. */
415 JSObject &withThis() const {
416 return getReservedSlot(THIS_SLOT).toObject();
417 }
418 };
419
420 class BlockObject : public NestedScopeObject
421 {
422 protected:
423 static const unsigned DEPTH_SLOT = 1;
424
425 public:
426 static const unsigned RESERVED_SLOTS = 2;
427 static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
428
429 static const Class class_;
430
431 /* Return the abstract stack depth right before entering this nested scope. */
432 uint32_t stackDepth() const {
433 return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
434 }
435
436 /* Return the number of variables associated with this block. */
437 uint32_t numVariables() const {
438 // TODO: propertyCount() is O(n), use O(1) lastProperty()->slot() instead
439 return propertyCount();
440 }
441
442 protected:
443 /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */
444 const Value &slotValue(unsigned i) {
445 return getSlotRef(RESERVED_SLOTS + i);
446 }
447
448 void setSlotValue(unsigned i, const Value &v) {
449 setSlot(RESERVED_SLOTS + i, v);
450 }
451 };
452
453 class StaticBlockObject : public BlockObject
454 {
455 static const unsigned LOCAL_OFFSET_SLOT = 1;
456
457 public:
458 static StaticBlockObject *create(ExclusiveContext *cx);
459
460 /* See StaticScopeIter comment. */
461 JSObject *enclosingStaticScope() const {
462 return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
463 }
464
465 /*
466 * Return the index (in the range [0, numVariables()) corresponding to the
467 * given shape of a block object.
468 */
469 uint32_t shapeToIndex(const Shape &shape) {
470 uint32_t slot = shape.slot();
471 JS_ASSERT(slot - RESERVED_SLOTS < numVariables());
472 return slot - RESERVED_SLOTS;
473 }
474
475 /*
476 * A refinement of enclosingStaticScope that returns nullptr if the enclosing
477 * static scope is a JSFunction.
478 */
479 inline StaticBlockObject *enclosingBlock() const;
480
481 uint32_t localOffset() {
482 return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32();
483 }
484
485 // Return the local corresponding to the 'var'th binding where 'var' is in the
486 // range [0, numVariables()).
487 uint32_t blockIndexToLocalIndex(uint32_t index) {
488 JS_ASSERT(index < numVariables());
489 return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + index;
490 }
491
492 // Return the slot corresponding to local variable 'local', where 'local' is
493 // in the range [localOffset(), localOffset() + numVariables()). The result is
494 // in the range [RESERVED_SLOTS, RESERVED_SLOTS + numVariables()).
495 uint32_t localIndexToSlot(uint32_t local) {
496 JS_ASSERT(local >= localOffset());
497 local -= localOffset();
498 JS_ASSERT(local < numVariables());
499 return RESERVED_SLOTS + local;
500 }
501
502 /*
503 * A let binding is aliased if accessed lexically by nested functions or
504 * dynamically through dynamic name lookup (eval, with, function::, etc).
505 */
506 bool isAliased(unsigned i) {
507 return slotValue(i).isTrue();
508 }
509
510 /*
511 * A static block object is cloned (when entering the block) iff some
512 * variable of the block isAliased.
513 */
514 bool needsClone() {
515 return !getFixedSlot(RESERVED_SLOTS).isFalse();
516 }
517
518 /* Frontend-only functions ***********************************************/
519
520 /* Initialization functions for above fields. */
521 void setAliased(unsigned i, bool aliased) {
522 JS_ASSERT_IF(i > 0, slotValue(i-1).isBoolean());
523 setSlotValue(i, BooleanValue(aliased));
524 if (aliased && !needsClone()) {
525 setSlotValue(0, MagicValue(JS_BLOCK_NEEDS_CLONE));
526 JS_ASSERT(needsClone());
527 }
528 }
529
530 void setLocalOffset(uint32_t offset) {
531 JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined());
532 initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset));
533 }
534
535 /*
536 * Frontend compilation temporarily uses the object's slots to link
537 * a let var to its associated Definition parse node.
538 */
539 void setDefinitionParseNode(unsigned i, frontend::Definition *def) {
540 JS_ASSERT(slotValue(i).isUndefined());
541 setSlotValue(i, PrivateValue(def));
542 }
543
544 frontend::Definition *definitionParseNode(unsigned i) {
545 Value v = slotValue(i);
546 return reinterpret_cast<frontend::Definition *>(v.toPrivate());
547 }
548
549 /*
550 * While ScopeCoordinate can generally reference up to 2^24 slots, block objects have an
551 * additional limitation that all slot indices must be storable as uint16_t short-ids in the
552 * associated Shape. If we could remove the block dependencies on shape->shortid, we could
553 * remove INDEX_LIMIT.
554 */
555 static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16);
556
557 static Shape *addVar(ExclusiveContext *cx, Handle<StaticBlockObject*> block, HandleId id,
558 unsigned index, bool *redeclared);
559 };
560
561 class ClonedBlockObject : public BlockObject
562 {
563 public:
564 static ClonedBlockObject *create(JSContext *cx, Handle<StaticBlockObject *> block,
565 AbstractFramePtr frame);
566
567 /* The static block from which this block was cloned. */
568 StaticBlockObject &staticBlock() const {
569 return getProto()->as<StaticBlockObject>();
570 }
571
572 /* Assuming 'put' has been called, return the value of the ith let var. */
573 const Value &var(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) {
574 JS_ASSERT_IF(checkAliasing, staticBlock().isAliased(i));
575 return slotValue(i);
576 }
577
578 void setVar(unsigned i, const Value &v, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) {
579 JS_ASSERT_IF(checkAliasing, staticBlock().isAliased(i));
580 setSlotValue(i, v);
581 }
582
583 /* Copy in all the unaliased formals and locals. */
584 void copyUnaliasedValues(AbstractFramePtr frame);
585 };
586
587 template<XDRMode mode>
588 bool
589 XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope,
590 StaticBlockObject **objp);
591
592 template<XDRMode mode>
593 bool
594 XDRStaticWithObject(XDRState<mode> *xdr, HandleObject enclosingScope,
595 StaticWithObject **objp);
596
597 extern JSObject *
598 CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src);
599
600 /*****************************************************************************/
601
602 class ScopeIterKey;
603 class ScopeIterVal;
604
605 /*
606 * A scope iterator describes the active scopes enclosing the current point of
607 * execution for a single frame, proceeding from inner to outer. Here, "frame"
608 * means a single activation of: a function, eval, or global code. By design,
609 * ScopeIter exposes *all* scopes, even those that have been optimized away
610 * (i.e., no ScopeObject was created when entering the scope and thus there is
611 * no ScopeObject on fp->scopeChain representing the scope).
612 *
613 * Note: ScopeIter iterates over all scopes *within* a frame which means that
614 * all scopes are ScopeObjects. In particular, the GlobalObject enclosing
615 * global code (and any random objects passed as scopes to Execute) will not
616 * be included.
617 */
618 class ScopeIter
619 {
620 friend class ScopeIterKey;
621 friend class ScopeIterVal;
622
623 public:
624 enum Type { Call, Block, With, StrictEvalScope };
625
626 private:
627 JSContext *cx;
628 AbstractFramePtr frame_;
629 RootedObject cur_;
630 Rooted<NestedScopeObject *> staticScope_;
631 Type type_;
632 bool hasScopeObject_;
633
634 void settle();
635
636 /* ScopeIter does not have value semantics. */
637 ScopeIter(const ScopeIter &si) MOZ_DELETE;
638
639 ScopeIter(JSContext *cx) MOZ_DELETE;
640
641 public:
642
643 /* Constructing from a copy of an existing ScopeIter. */
644 ScopeIter(const ScopeIter &si, JSContext *cx
645 MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
646
647 /* Constructing from AbstractFramePtr places ScopeIter on the innermost scope. */
648 ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
649 MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
650
651 /*
652 * Without a stack frame, the resulting ScopeIter is done() with
653 * enclosingScope() as given.
654 */
655 ScopeIter(JSObject &enclosingScope, JSContext *cx
656 MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
657
658 ScopeIter(const ScopeIterVal &hashVal, JSContext *cx
659 MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
660
661 bool done() const { return !frame_; }
662
663 /* If done(): */
664
665 JSObject &enclosingScope() const { JS_ASSERT(done()); return *cur_; }
666
667 /* If !done(): */
668
669 ScopeIter &operator++();
670
671 AbstractFramePtr frame() const { JS_ASSERT(!done()); return frame_; }
672 Type type() const { JS_ASSERT(!done()); return type_; }
673 bool hasScopeObject() const { JS_ASSERT(!done()); return hasScopeObject_; }
674 ScopeObject &scope() const;
675 NestedScopeObject* staticScope() const { return staticScope_; }
676
677 StaticBlockObject &staticBlock() const {
678 JS_ASSERT(type() == Block);
679 return staticScope_->as<StaticBlockObject>();
680 }
681
682 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
683 };
684
685 class ScopeIterKey
686 {
687 friend class ScopeIterVal;
688
689 AbstractFramePtr frame_;
690 JSObject *cur_;
691 NestedScopeObject *staticScope_;
692 ScopeIter::Type type_;
693 bool hasScopeObject_;
694
695 public:
696 ScopeIterKey(const ScopeIter &si)
697 : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
698 hasScopeObject_(si.hasScopeObject_) {}
699
700 AbstractFramePtr frame() const { return frame_; }
701 JSObject *cur() const { return cur_; }
702 NestedScopeObject *staticScope() const { return staticScope_; }
703 ScopeIter::Type type() const { return type_; }
704 bool hasScopeObject() const { return hasScopeObject_; }
705 JSObject *enclosingScope() const { return cur_; }
706 JSObject *&enclosingScope() { return cur_; }
707
708 /* For use as hash policy */
709 typedef ScopeIterKey Lookup;
710 static HashNumber hash(ScopeIterKey si);
711 static bool match(ScopeIterKey si1, ScopeIterKey si2);
712 bool operator!=(const ScopeIterKey &other) const {
713 return frame_ != other.frame_ ||
714 cur_ != other.cur_ ||
715 staticScope_ != other.staticScope_ ||
716 type_ != other.type_;
717 }
718 static void rekey(ScopeIterKey &k, const ScopeIterKey& newKey) {
719 k = newKey;
720 }
721 };
722
723 class ScopeIterVal
724 {
725 friend class ScopeIter;
726 friend class DebugScopes;
727
728 AbstractFramePtr frame_;
729 RelocatablePtr<JSObject> cur_;
730 RelocatablePtr<NestedScopeObject> staticScope_;
731 ScopeIter::Type type_;
732 bool hasScopeObject_;
733
734 static void staticAsserts();
735
736 public:
737 ScopeIterVal(const ScopeIter &si)
738 : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
739 hasScopeObject_(si.hasScopeObject_) {}
740
741 AbstractFramePtr frame() const { return frame_; }
742 };
743
744 /*****************************************************************************/
745
746 /*
747 * Debug scope objects
748 *
749 * The debugger effectively turns every opcode into a potential direct eval.
750 * Naively, this would require creating a ScopeObject for every call/block
751 * scope and using JSOP_GETALIASEDVAR for every access. To optimize this, the
752 * engine assumes there is no debugger and optimizes scope access and creation
753 * accordingly. When the debugger wants to perform an unexpected eval-in-frame
754 * (or other, similar dynamic-scope-requiring operations), fp->scopeChain is
755 * now incomplete: it may not contain all, or any, of the ScopeObjects to
756 * represent the current scope.
757 *
758 * To resolve this, the debugger first calls GetDebugScopeFor(Function|Frame)
759 * to synthesize a "debug scope chain". A debug scope chain is just a chain of
760 * objects that fill in missing scopes and protect the engine from unexpected
761 * access. (The latter means that some debugger operations, like redefining a
762 * lexical binding, can fail when a true eval would succeed.) To do both of
763 * these things, GetDebugScopeFor* creates a new proxy DebugScopeObject to sit
764 * in front of every existing ScopeObject.
765 *
766 * GetDebugScopeFor* ensures the invariant that the same DebugScopeObject is
767 * always produced for the same underlying scope (optimized or not!). This is
768 * maintained by some bookkeeping information stored in DebugScopes.
769 */
770
771 extern JSObject *
772 GetDebugScopeForFunction(JSContext *cx, HandleFunction fun);
773
774 extern JSObject *
775 GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
776
777 /* Provides debugger access to a scope. */
778 class DebugScopeObject : public ProxyObject
779 {
780 /*
781 * The enclosing scope on the dynamic scope chain. This slot is analogous
782 * to the SCOPE_CHAIN_SLOT of a ScopeObject.
783 */
784 static const unsigned ENCLOSING_EXTRA = 0;
785
786 /*
787 * NullValue or a dense array holding the unaliased variables of a function
788 * frame that has been popped.
789 */
790 static const unsigned SNAPSHOT_EXTRA = 1;
791
792 public:
793 static DebugScopeObject *create(JSContext *cx, ScopeObject &scope, HandleObject enclosing);
794
795 ScopeObject &scope() const;
796 JSObject &enclosingScope() const;
797
798 /* May only be called for proxies to function call objects. */
799 JSObject *maybeSnapshot() const;
800 void initSnapshot(JSObject &snapshot);
801
802 /* Currently, the 'declarative' scopes are Call and Block. */
803 bool isForDeclarative() const;
804
805 // Get a property by 'id', but returns sentinel values instead of throwing
806 // on exceptional cases.
807 bool getMaybeSentinelValue(JSContext *cx, HandleId id, MutableHandleValue vp);
808 };
809
810 /* Maintains per-compartment debug scope bookkeeping information. */
811 class DebugScopes
812 {
813 /* The map from (non-debug) scopes to debug scopes. */
814 typedef WeakMap<EncapsulatedPtrObject, RelocatablePtrObject> ObjectWeakMap;
815 ObjectWeakMap proxiedScopes;
816 static MOZ_ALWAYS_INLINE void proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map,
817 const EncapsulatedPtrObject &key);
818
819 /*
820 * The map from live frames which have optimized-away scopes to the
821 * corresponding debug scopes.
822 */
823 typedef HashMap<ScopeIterKey,
824 ReadBarriered<DebugScopeObject>,
825 ScopeIterKey,
826 RuntimeAllocPolicy> MissingScopeMap;
827 MissingScopeMap missingScopes;
828 class MissingScopesRef;
829 static MOZ_ALWAYS_INLINE void missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map,
830 const ScopeIterKey &key);
831
832 /*
833 * The map from scope objects of live frames to the live frame. This map
834 * updated lazily whenever the debugger needs the information. In between
835 * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop*
836 * removes scopes as they are popped). Thus, two consecutive debugger lazy
837 * updates of liveScopes need only fill in the new scopes.
838 */
839 typedef HashMap<ScopeObject *,
840 ScopeIterVal,
841 DefaultHasher<ScopeObject *>,
842 RuntimeAllocPolicy> LiveScopeMap;
843 LiveScopeMap liveScopes;
844 static MOZ_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map,
845 ScopeObject *key);
846
847 public:
848 DebugScopes(JSContext *c);
849 ~DebugScopes();
850
851 private:
852 bool init();
853
854 static DebugScopes *ensureCompartmentData(JSContext *cx);
855
856 public:
857 void mark(JSTracer *trc);
858 void sweep(JSRuntime *rt);
859 #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL)
860 void checkHashTablesAfterMovingGC(JSRuntime *rt);
861 #endif
862
863 static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope);
864 static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope);
865
866 static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si);
867 static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope);
868
869 static bool updateLiveScopes(JSContext *cx);
870 static ScopeIterVal *hasLiveScope(ScopeObject &scope);
871
872 // In debug-mode, these must be called whenever exiting a scope that might
873 // have stack-allocated locals.
874 static void onPopCall(AbstractFramePtr frame, JSContext *cx);
875 static void onPopBlock(JSContext *cx, const ScopeIter &si);
876 static void onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
877 static void onPopWith(AbstractFramePtr frame);
878 static void onPopStrictEvalScope(AbstractFramePtr frame);
879 static void onCompartmentLeaveDebugMode(JSCompartment *c);
880 };
881
882 } /* namespace js */
883
884 template<>
885 inline bool
886 JSObject::is<js::NestedScopeObject>() const
887 {
888 return is<js::BlockObject>() || is<js::StaticWithObject>() || is<js::DynamicWithObject>();
889 }
890
891 template<>
892 inline bool
893 JSObject::is<js::ScopeObject>() const
894 {
895 return is<js::CallObject>() || is<js::DeclEnvObject>() || is<js::NestedScopeObject>();
896 }
897
898 template<>
899 inline bool
900 JSObject::is<js::DebugScopeObject>() const
901 {
902 extern bool js_IsDebugScopeSlow(js::ProxyObject *proxy);
903
904 // Note: don't use is<ProxyObject>() here -- it also matches subclasses!
905 return hasClass(&js::ProxyObject::uncallableClass_) &&
906 js_IsDebugScopeSlow(&const_cast<JSObject*>(this)->as<js::ProxyObject>());
907 }
908
909 template<>
910 inline bool
911 JSObject::is<js::ClonedBlockObject>() const
912 {
913 return is<js::BlockObject>() && !!getProto();
914 }
915
916 template<>
917 inline bool
918 JSObject::is<js::StaticBlockObject>() const
919 {
920 return is<js::BlockObject>() && !getProto();
921 }
922
923 inline JSObject *
924 JSObject::enclosingScope()
925 {
926 return is<js::ScopeObject>()
927 ? &as<js::ScopeObject>().enclosingScope()
928 : is<js::DebugScopeObject>()
929 ? &as<js::DebugScopeObject>().enclosingScope()
930 : getParent();
931 }
932
933 namespace js {
934
935 inline const Value &
936 ScopeObject::aliasedVar(ScopeCoordinate sc)
937 {
938 JS_ASSERT(is<CallObject>() || is<ClonedBlockObject>());
939 return getSlot(sc.slot());
940 }
941
942 inline NestedScopeObject *
943 NestedScopeObject::enclosingNestedScope() const
944 {
945 JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
946 return obj && obj->is<NestedScopeObject>() ? &obj->as<NestedScopeObject>() : nullptr;
947 }
948
949 #ifdef DEBUG
950 bool
951 AnalyzeEntrainedVariables(JSContext *cx, HandleScript script);
952 #endif
953
954 } // namespace js
955
956 #endif /* vm_ScopeObject_h */

mercurial