js/src/vm/ScopeObject.h

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/. */
     7 #ifndef vm_ScopeObject_h
     8 #define vm_ScopeObject_h
    10 #include "jscntxt.h"
    11 #include "jsobj.h"
    12 #include "jsweakmap.h"
    14 #include "gc/Barrier.h"
    15 #include "vm/ProxyObject.h"
    17 namespace js {
    19 namespace frontend { struct Definition; }
    21 class StaticWithObject;
    23 /*****************************************************************************/
    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;
    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     }
    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     }
    82     bool done() const;
    83     void operator++(int);
    85     /* Return whether this static scope will be on the dynamic scope chain. */
    86     bool hasDynamicScopeObject() const;
    87     Shape *scopeShape() const;
    89     enum Type { WITH, BLOCK, FUNCTION, NAMED_LAMBDA };
    90     Type type() const;
    92     StaticBlockObject &block() const;
    93     StaticWithObject &staticWith() const;
    94     JSScript *funScript() const;
    95 };
    97 /*****************************************************************************/
    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_;
   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");
   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     }
   125     inline ScopeCoordinate() {}
   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; }
   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 };
   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);
   141 /* Return the name being accessed by the given ALIASEDVAR op. */
   142 extern PropertyName *
   143 ScopeCoordinateName(ScopeCoordinateNameCache &cache, JSScript *script, jsbytecode *pc);
   145 /* Return the function script accessed by the given ALIASEDVAR op, or nullptr. */
   146 extern JSScript *
   147 ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc);
   149 /*****************************************************************************/
   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  */
   191 class ScopeObject : public JSObject
   192 {
   193   protected:
   194     static const uint32_t SCOPE_CHAIN_SLOT = 0;
   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     }
   206     void setEnclosingScope(HandleObject obj);
   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);
   216     inline void setAliasedVar(JSContext *cx, ScopeCoordinate sc, PropertyName *name, const Value &v);
   218     /* For jit access. */
   219     static size_t offsetOfEnclosingScope() {
   220         return getFixedSlotOffset(SCOPE_CHAIN_SLOT);
   221     }
   223     static size_t enclosingScopeSlot() {
   224         return SCOPE_CHAIN_SLOT;
   225     }
   226 };
   228 class CallObject : public ScopeObject
   229 {
   230     static const uint32_t CALLEE_SLOT = 1;
   232     static CallObject *
   233     create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee);
   235   public:
   236     static const Class class_;
   238     /* These functions are internal and are exposed only for JITs. */
   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);
   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);
   256     static CallObject *
   257     createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap);
   259     static const uint32_t RESERVED_SLOTS = 2;
   261     static CallObject *createForFunction(JSContext *cx, HandleObject enclosing, HandleFunction callee);
   263     static CallObject *createForFunction(JSContext *cx, AbstractFramePtr frame);
   264     static CallObject *createForStrictEval(JSContext *cx, AbstractFramePtr frame);
   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     }
   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     }
   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);
   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);
   303     /* For jit access. */
   304     static size_t offsetOfCallee() {
   305         return getFixedSlotOffset(CALLEE_SLOT);
   306     }
   308     static size_t calleeSlot() {
   309         return CALLEE_SLOT;
   310     }
   311 };
   313 class DeclEnvObject : public ScopeObject
   314 {
   315     // Pre-allocated slot for the named lambda.
   316     static const uint32_t LAMBDA_SLOT = 1;
   318   public:
   319     static const uint32_t RESERVED_SLOTS = 2;
   320     static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND;
   322     static const Class class_;
   324     static DeclEnvObject *
   325     createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap);
   327     static DeclEnvObject *create(JSContext *cx, HandleObject enclosing, HandleFunction callee);
   329     static inline size_t lambdaSlot() {
   330         return LAMBDA_SLOT;
   331     }
   332 };
   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;
   343     // Return true if this object is a compile-time scope template.
   344     inline bool isStatic() { return !getProto(); }
   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     }
   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     }
   357     void initEnclosingNestedScope(JSObject *obj) {
   358         JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
   359         setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
   360     }
   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     }
   373     void resetEnclosingNestedScopeFromParser() {
   374         setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
   375     }
   376 };
   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;
   385     static const Class class_;
   387     static StaticWithObject *create(ExclusiveContext *cx);
   388 };
   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;
   396   public:
   397     static const unsigned RESERVED_SLOTS = 3;
   398     static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
   400     static const Class class_;
   402     static DynamicWithObject *
   403     create(JSContext *cx, HandleObject object, HandleObject enclosing, HandleObject staticWith);
   405     StaticWithObject& staticWith() const {
   406         return getProto()->as<StaticWithObject>();
   407     }
   409     /* Return the 'o' in 'with (o)'. */
   410     JSObject &object() const {
   411         return getReservedSlot(OBJECT_SLOT).toObject();
   412     }
   414     /* Return object for the 'this' class hook. */
   415     JSObject &withThis() const {
   416         return getReservedSlot(THIS_SLOT).toObject();
   417     }
   418 };
   420 class BlockObject : public NestedScopeObject
   421 {
   422   protected:
   423     static const unsigned DEPTH_SLOT = 1;
   425   public:
   426     static const unsigned RESERVED_SLOTS = 2;
   427     static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
   429     static const Class class_;
   431     /* Return the abstract stack depth right before entering this nested scope. */
   432     uint32_t stackDepth() const {
   433         return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
   434     }
   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     }
   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     }
   448     void setSlotValue(unsigned i, const Value &v) {
   449         setSlot(RESERVED_SLOTS + i, v);
   450     }
   451 };
   453 class StaticBlockObject : public BlockObject
   454 {
   455     static const unsigned LOCAL_OFFSET_SLOT = 1;
   457   public:
   458     static StaticBlockObject *create(ExclusiveContext *cx);
   460     /* See StaticScopeIter comment. */
   461     JSObject *enclosingStaticScope() const {
   462         return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
   463     }
   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     }
   475     /*
   476      * A refinement of enclosingStaticScope that returns nullptr if the enclosing
   477      * static scope is a JSFunction.
   478      */
   479     inline StaticBlockObject *enclosingBlock() const;
   481     uint32_t localOffset() {
   482         return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32();
   483     }
   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     }
   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     }
   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     }
   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     }
   518     /* Frontend-only functions ***********************************************/
   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     }
   530     void setLocalOffset(uint32_t offset) {
   531         JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined());
   532         initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset));
   533     }
   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     }
   544     frontend::Definition *definitionParseNode(unsigned i) {
   545         Value v = slotValue(i);
   546         return reinterpret_cast<frontend::Definition *>(v.toPrivate());
   547     }
   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);
   557     static Shape *addVar(ExclusiveContext *cx, Handle<StaticBlockObject*> block, HandleId id,
   558                          unsigned index, bool *redeclared);
   559 };
   561 class ClonedBlockObject : public BlockObject
   562 {
   563   public:
   564     static ClonedBlockObject *create(JSContext *cx, Handle<StaticBlockObject *> block,
   565                                      AbstractFramePtr frame);
   567     /* The static block from which this block was cloned. */
   568     StaticBlockObject &staticBlock() const {
   569         return getProto()->as<StaticBlockObject>();
   570     }
   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     }
   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     }
   583     /* Copy in all the unaliased formals and locals. */
   584     void copyUnaliasedValues(AbstractFramePtr frame);
   585 };
   587 template<XDRMode mode>
   588 bool
   589 XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope,
   590                      StaticBlockObject **objp);
   592 template<XDRMode mode>
   593 bool
   594 XDRStaticWithObject(XDRState<mode> *xdr, HandleObject enclosingScope,
   595                     StaticWithObject **objp);
   597 extern JSObject *
   598 CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src);
   600 /*****************************************************************************/
   602 class ScopeIterKey;
   603 class ScopeIterVal;
   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;
   623   public:
   624     enum Type { Call, Block, With, StrictEvalScope };
   626   private:
   627     JSContext *cx;
   628     AbstractFramePtr frame_;
   629     RootedObject cur_;
   630     Rooted<NestedScopeObject *> staticScope_;
   631     Type type_;
   632     bool hasScopeObject_;
   634     void settle();
   636     /* ScopeIter does not have value semantics. */
   637     ScopeIter(const ScopeIter &si) MOZ_DELETE;
   639     ScopeIter(JSContext *cx) MOZ_DELETE;
   641   public:
   643     /* Constructing from a copy of an existing ScopeIter. */
   644     ScopeIter(const ScopeIter &si, JSContext *cx
   645               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   647     /* Constructing from AbstractFramePtr places ScopeIter on the innermost scope. */
   648     ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
   649               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   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);
   658     ScopeIter(const ScopeIterVal &hashVal, JSContext *cx
   659               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   661     bool done() const { return !frame_; }
   663     /* If done(): */
   665     JSObject &enclosingScope() const { JS_ASSERT(done()); return *cur_; }
   667     /* If !done(): */
   669     ScopeIter &operator++();
   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_; }
   677     StaticBlockObject &staticBlock() const {
   678         JS_ASSERT(type() == Block);
   679         return staticScope_->as<StaticBlockObject>();
   680     }
   682     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   683 };
   685 class ScopeIterKey
   686 {
   687     friend class ScopeIterVal;
   689     AbstractFramePtr frame_;
   690     JSObject *cur_;
   691     NestedScopeObject *staticScope_;
   692     ScopeIter::Type type_;
   693     bool hasScopeObject_;
   695   public:
   696     ScopeIterKey(const ScopeIter &si)
   697       : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
   698         hasScopeObject_(si.hasScopeObject_) {}
   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_; }
   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 };
   723 class ScopeIterVal
   724 {
   725     friend class ScopeIter;
   726     friend class DebugScopes;
   728     AbstractFramePtr frame_;
   729     RelocatablePtr<JSObject> cur_;
   730     RelocatablePtr<NestedScopeObject> staticScope_;
   731     ScopeIter::Type type_;
   732     bool hasScopeObject_;
   734     static void staticAsserts();
   736   public:
   737     ScopeIterVal(const ScopeIter &si)
   738       : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
   739         hasScopeObject_(si.hasScopeObject_) {}
   741     AbstractFramePtr frame() const { return frame_; }
   742 };
   744 /*****************************************************************************/
   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  */
   771 extern JSObject *
   772 GetDebugScopeForFunction(JSContext *cx, HandleFunction fun);
   774 extern JSObject *
   775 GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
   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;
   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;
   792   public:
   793     static DebugScopeObject *create(JSContext *cx, ScopeObject &scope, HandleObject enclosing);
   795     ScopeObject &scope() const;
   796     JSObject &enclosingScope() const;
   798     /* May only be called for proxies to function call objects. */
   799     JSObject *maybeSnapshot() const;
   800     void initSnapshot(JSObject &snapshot);
   802     /* Currently, the 'declarative' scopes are Call and Block. */
   803     bool isForDeclarative() const;
   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 };
   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);
   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);
   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);
   847   public:
   848     DebugScopes(JSContext *c);
   849     ~DebugScopes();
   851   private:
   852     bool init();
   854     static DebugScopes *ensureCompartmentData(JSContext *cx);
   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
   863     static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope);
   864     static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope);
   866     static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si);
   867     static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope);
   869     static bool updateLiveScopes(JSContext *cx);
   870     static ScopeIterVal *hasLiveScope(ScopeObject &scope);
   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 };
   882 }  /* namespace js */
   884 template<>
   885 inline bool
   886 JSObject::is<js::NestedScopeObject>() const
   887 {
   888     return is<js::BlockObject>() || is<js::StaticWithObject>() || is<js::DynamicWithObject>();
   889 }
   891 template<>
   892 inline bool
   893 JSObject::is<js::ScopeObject>() const
   894 {
   895     return is<js::CallObject>() || is<js::DeclEnvObject>() || is<js::NestedScopeObject>();
   896 }
   898 template<>
   899 inline bool
   900 JSObject::is<js::DebugScopeObject>() const
   901 {
   902     extern bool js_IsDebugScopeSlow(js::ProxyObject *proxy);
   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 }
   909 template<>
   910 inline bool
   911 JSObject::is<js::ClonedBlockObject>() const
   912 {
   913     return is<js::BlockObject>() && !!getProto();
   914 }
   916 template<>
   917 inline bool
   918 JSObject::is<js::StaticBlockObject>() const
   919 {
   920     return is<js::BlockObject>() && !getProto();
   921 }
   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 }
   933 namespace js {
   935 inline const Value &
   936 ScopeObject::aliasedVar(ScopeCoordinate sc)
   937 {
   938     JS_ASSERT(is<CallObject>() || is<ClonedBlockObject>());
   939     return getSlot(sc.slot());
   940 }
   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 }
   949 #ifdef DEBUG
   950 bool
   951 AnalyzeEntrainedVariables(JSContext *cx, HandleScript script);
   952 #endif
   954 } // namespace js
   956 #endif /* vm_ScopeObject_h */

mercurial