js/src/vm/ScopeObject.h
author Jon Coppeard <jcoppeard@mozilla.com>
Mon, 24 Aug 2015 15:58:35 +0100
changeset 259027 f78c80504443f2f66b2550837dbd6de3724e54ff
parent 258514 527553e5ca434ae1bf468b6729872eb3614f6059
child 259032 9ac1f5052b91cdc341570cb2f8c03efc561faa54
permissions -rw-r--r--
Bug 930414 - Add ModuleObject and CompileModule() function r=shu

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_ScopeObject_h
#define vm_ScopeObject_h

#include "jscntxt.h"
#include "jsobj.h"
#include "jsweakmap.h"

#include "gc/Barrier.h"
#include "vm/ArgumentsObject.h"
#include "vm/ProxyObject.h"
#include "vm/WeakMapObject.h"

namespace js {

namespace frontend {
struct Definition;
class FunctionBox;
class ModuleBox;
}

class StaticWithObject;
class StaticEvalObject;
class StaticNonSyntacticScopeObjects;

/*****************************************************************************/

/*
 * The static scope chain is the canonical truth for lexical scope contour of
 * a program. The dynamic scope chain is derived from the static scope chain:
 * it is the chain of scopes whose static scopes have a runtime
 * representation, for example, due to aliased bindings.
 *
 * Static scopes roughly correspond to a scope in the program text. They are
 * divided into scopes that have direct correspondence to program text (i.e.,
 * syntactic) and ones used internally for scope walking (i.e., non-syntactic).
 *
 * The following are syntactic static scopes:
 *
 * StaticBlockObject
 *   Scope for non-function body blocks. e.g., |{ let x; }|
 *
 * JSFunction
 *   Scope for function bodies. e.g., |function f() { var x; let y; }|
 *
 * ModuleObject
 *   Scope for moddules.
 *
 * StaticWithObject
 *   Scope for |with|. e.g., |with ({}) { ... }|
 *
 * StaticEvalObject
 *   Scope for |eval|. e.g., |eval(...)|
 *
 * The following are non-syntactic static scopes:
 *
 * StaticNonSyntacticScopeObjects
 *   Signals presence of "polluting" scope objects. Used by Gecko.
 *
 * There is an additional scope for named lambdas without a static scope
 * object. E.g., in:
 *
 *   (function f() { var x; function g() { } })
 *
 * All static scope objects are ScopeObjects with the exception of JSFunction
 * and ModuleObject, which keeps their enclosing scope link on
 * |JSScript::enclosingStaticScope()|.
 */
template <AllowGC allowGC>
class StaticScopeIter
{
    typename MaybeRooted<JSObject*, allowGC>::RootType obj;
    bool onNamedLambda;

    static bool IsStaticScope(JSObject* obj) {
        return obj->is<StaticBlockObject>() ||
               obj->is<StaticWithObject>() ||
               obj->is<StaticEvalObject>() ||
               obj->is<StaticNonSyntacticScopeObjects>() ||
               obj->is<JSFunction>() ||
               obj->is<ModuleObject>();
    }

  public:
    StaticScopeIter(ExclusiveContext* cx, JSObject* obj)
      : obj(cx, obj), onNamedLambda(false)
    {
        static_assert(allowGC == CanGC,
                      "the context-accepting constructor should only be used "
                      "in CanGC code");
        MOZ_ASSERT_IF(obj, IsStaticScope(obj));
    }

    StaticScopeIter(ExclusiveContext* cx, const StaticScopeIter<CanGC>& ssi)
      : obj(cx, ssi.obj), onNamedLambda(ssi.onNamedLambda)
    {
        JS_STATIC_ASSERT(allowGC == CanGC);
    }

    explicit StaticScopeIter(JSObject* obj)
      : obj((ExclusiveContext*) nullptr, obj), onNamedLambda(false)
    {
        static_assert(allowGC == NoGC,
                      "the constructor not taking a context should only be "
                      "used in NoGC code");
        MOZ_ASSERT_IF(obj, IsStaticScope(obj));
    }

    explicit StaticScopeIter(const StaticScopeIter<NoGC>& ssi)
      : obj((ExclusiveContext*) nullptr, ssi.obj), onNamedLambda(ssi.onNamedLambda)
    {
        static_assert(allowGC == NoGC,
                      "the constructor not taking a context should only be "
                      "used in NoGC code");
    }

    bool done() const { return !obj; }
    void operator++(int);

    JSObject* staticScope() const { MOZ_ASSERT(!done()); return obj; }

    // Return whether this static scope will have a syntactic scope (i.e. a
    // ScopeObject that isn't a non-syntactic With or
    // NonSyntacticVariablesObject) on the dynamic scope chain.
    bool hasSyntacticDynamicScopeObject() const;
    Shape* scopeShape() const;

    enum Type { Function, Block, With, NamedLambda, Eval, NonSyntactic };
    Type type() const;

    StaticBlockObject& block() const;
    StaticWithObject& staticWith() const;
    StaticEvalObject& eval() const;
    StaticNonSyntacticScopeObjects& nonSyntactic() const;
    JSScript* funScript() const;
    JSFunction& fun() const;
    frontend::FunctionBox* maybeFunctionBox() const;
};

/*****************************************************************************/

/*
 * A "scope coordinate" describes how to get from head of the scope chain to a
 * given lexically-enclosing variable. A scope coordinate has two dimensions:
 *  - hops: the number of scope objects on the scope chain to skip
 *  - slot: the slot on the scope object holding the variable's value
 */
class ScopeCoordinate
{
    uint32_t hops_;
    uint32_t slot_;

    /*
     * Technically, hops_/slot_ are SCOPECOORD_(HOPS|SLOT)_BITS wide.  Since
     * ScopeCoordinate is a temporary value, don't bother with a bitfield as
     * this only adds overhead.
     */
    static_assert(SCOPECOORD_HOPS_BITS <= 32, "We have enough bits below");
    static_assert(SCOPECOORD_SLOT_BITS <= 32, "We have enough bits below");

  public:
    explicit inline ScopeCoordinate(jsbytecode* pc)
      : hops_(GET_SCOPECOORD_HOPS(pc)), slot_(GET_SCOPECOORD_SLOT(pc + SCOPECOORD_HOPS_LEN))
    {
        MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
    }

    inline ScopeCoordinate() {}

    void setHops(uint32_t hops) { MOZ_ASSERT(hops < SCOPECOORD_HOPS_LIMIT); hops_ = hops; }
    void setSlot(uint32_t slot) { MOZ_ASSERT(slot < SCOPECOORD_SLOT_LIMIT); slot_ = slot; }

    uint32_t hops() const { MOZ_ASSERT(hops_ < SCOPECOORD_HOPS_LIMIT); return hops_; }
    uint32_t slot() const { MOZ_ASSERT(slot_ < SCOPECOORD_SLOT_LIMIT); return slot_; }

    bool operator==(const ScopeCoordinate& rhs) const {
        return hops() == rhs.hops() && slot() == rhs.slot();
    }
};

/*
 * Return a shape representing the static scope containing the variable
 * accessed by the ALIASEDVAR op at 'pc'.
 */
extern Shape*
ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc);

/* Return the name being accessed by the given ALIASEDVAR op. */
extern PropertyName*
ScopeCoordinateName(ScopeCoordinateNameCache& cache, JSScript* script, jsbytecode* pc);

/* Return the function script accessed by the given ALIASEDVAR op, or nullptr. */
extern JSScript*
ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc);

/*****************************************************************************/

/*
 * Scope objects
 *
 * Scope objects are technically real JSObjects but only belong on the scope
 * chain (that is, fp->scopeChain() or fun->environment()). The hierarchy of
 * scope objects is:
 *
 *   JSObject                      Generic object
 *     |
 *   ScopeObject---+---+           Engine-internal scope
 *     |   |   |   |   |
 *     |   |   |   |  StaticNonSyntacticScopeObjects  See NB2
 *     |   |   |   |
 *     |   |   |  StaticEvalObject  Placeholder so eval scopes may be iterated through
 *     |   |   |
 *     |   |  DeclEnvObject         Holds name of recursive/heavyweight named lambda
 *     |   |
 *     |  CallObject                Scope of entire function or strict eval
 *     |
 *   NestedScopeObject              Statement scopes; don't cross script boundaries
 *     |   |   |
 *     |   |  StaticWithObject      Template for "with" object in static scope chain
 *     |   |
 *     |  DynamicWithObject         Run-time "with" object on scope chain
 *     |
 *   BlockObject                    Shared interface of cloned/static block objects
 *     |   |
 *     |  ClonedBlockObject         let, switch, catch, for
 *     |
 *   StaticBlockObject              See NB
 *
 * This hierarchy represents more than just the interface hierarchy: reserved
 * slots in base classes are fixed for all derived classes. Thus, for example,
 * ScopeObject::enclosingScope() can simply access a fixed slot without further
 * dynamic type information.
 *
 * NB: Static block objects are a special case: these objects are created at
 * compile time to hold the shape/binding information from which block objects
 * are cloned at runtime. These objects should never escape into the wild and
 * support a restricted set of ScopeObject operations.
 *
 * NB2: StaticNonSyntacticScopeObjects notify either of 0+ non-syntactic
 * DynamicWithObjects on the dynamic scope chain or a NonSyntacticScopeObject.
 *
 * See also "Debug scope objects" below.
 */

class ScopeObject : public NativeObject
{
  protected:
    static const uint32_t SCOPE_CHAIN_SLOT = 0;

  public:
    /*
     * Since every scope chain terminates with a global object and GlobalObject
     * does not derive ScopeObject (it has a completely different layout), the
     * enclosing scope of a ScopeObject is necessarily non-null.
     */
    inline JSObject& enclosingScope() const {
        return getFixedSlot(SCOPE_CHAIN_SLOT).toObject();
    }

    void setEnclosingScope(HandleObject obj);

    /*
     * Get or set an aliased variable contained in this scope. Unaliased
     * variables should instead access the stack frame. Aliased variable access
     * is primarily made through JOF_SCOPECOORD ops which is why these members
     * take a ScopeCoordinate instead of just the slot index.
     */
    inline const Value& aliasedVar(ScopeCoordinate sc);

    inline void setAliasedVar(JSContext* cx, ScopeCoordinate sc, PropertyName* name, const Value& v);

    /* For jit access. */
    static size_t offsetOfEnclosingScope() {
        return getFixedSlotOffset(SCOPE_CHAIN_SLOT);
    }

    static size_t enclosingScopeSlot() {
        return SCOPE_CHAIN_SLOT;
    }
};

class CallObject : public ScopeObject
{
    static const uint32_t CALLEE_SLOT = 1;

    static CallObject*
    create(JSContext* cx, HandleScript script, HandleObject enclosing, HandleFunction callee);

    inline void initRemainingSlotsToUninitializedLexicals(uint32_t begin);
    inline void initAliasedLexicalsToThrowOnTouch(JSScript* script);

  public:
    static const Class class_;

    /* These functions are internal and are exposed only for JITs. */

    /*
     * Construct a bare-bones call object given a shape and a non-singleton
     * group.  The call object must be further initialized to be usable.
     */
    static CallObject*
    create(JSContext* cx, HandleShape shape, HandleObjectGroup group, uint32_t lexicalBegin);

    /*
     * Construct a bare-bones call object given a shape and make it into
     * a singleton.  The call object must be initialized to be usable.
     */
    static CallObject*
    createSingleton(JSContext* cx, HandleShape shape, uint32_t lexicalBegin);

    static CallObject*
    createTemplateObject(JSContext* cx, HandleScript script, gc::InitialHeap heap);

    static const uint32_t RESERVED_SLOTS = 2;

    static CallObject* createForFunction(JSContext* cx, HandleObject enclosing, HandleFunction callee);

    static CallObject* createForFunction(JSContext* cx, AbstractFramePtr frame);
    static CallObject* createForStrictEval(JSContext* cx, AbstractFramePtr frame);
    static CallObject* createHollowForDebug(JSContext* cx, HandleFunction callee);

    /* True if this is for a strict mode eval frame. */
    bool isForEval() const {
        MOZ_ASSERT(getFixedSlot(CALLEE_SLOT).isObjectOrNull());
        MOZ_ASSERT_IF(getFixedSlot(CALLEE_SLOT).isObject(),
                      getFixedSlot(CALLEE_SLOT).toObject().is<JSFunction>());
        return getFixedSlot(CALLEE_SLOT).isNull();
    }

    /*
     * Returns the function for which this CallObject was created. (This may
     * only be called if !isForEval.)
     */
    JSFunction& callee() const {
        return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
    }

    /* Get/set the aliased variable referred to by 'bi'. */
    const Value& aliasedVar(AliasedFormalIter fi) {
        return getSlot(fi.scopeSlot());
    }
    inline void setAliasedVar(JSContext* cx, AliasedFormalIter fi, PropertyName* name,
                              const Value& v);

    /*
     * When an aliased var (var accessed by nested closures) is also aliased by
     * the arguments object, it must of course exist in one canonical location
     * and that location is always the CallObject. For this to work, the
     * ArgumentsObject stores special MagicValue in its array for forwarded-to-
     * CallObject variables. This MagicValue's payload is the slot of the
     * CallObject to access.
     */
    const Value& aliasedVarFromArguments(const Value& argsValue) {
        return getSlot(ArgumentsObject::SlotFromMagicScopeSlotValue(argsValue));
    }
    inline void setAliasedVarFromArguments(JSContext* cx, const Value& argsValue, jsid id,
                                           const Value& v);

    /* For jit access. */
    static size_t offsetOfCallee() {
        return getFixedSlotOffset(CALLEE_SLOT);
    }

    static size_t calleeSlot() {
        return CALLEE_SLOT;
    }
};

class DeclEnvObject : public ScopeObject
{
    // Pre-allocated slot for the named lambda.
    static const uint32_t LAMBDA_SLOT = 1;

  public:
    static const uint32_t RESERVED_SLOTS = 2;
    static const Class class_;

    static DeclEnvObject*
    createTemplateObject(JSContext* cx, HandleFunction fun, NewObjectKind newKind);

    static DeclEnvObject* create(JSContext* cx, HandleObject enclosing, HandleFunction callee);

    static inline size_t lambdaSlot() {
        return LAMBDA_SLOT;
    }
};

// Static eval scope placeholder objects on the static scope chain. Created at
// the time of compiling the eval script, and set as its static enclosing
// scope.
class StaticEvalObject : public ScopeObject
{
    static const uint32_t STRICT_SLOT = 1;

  public:
    static const unsigned RESERVED_SLOTS = 2;
    static const Class class_;

    static StaticEvalObject* create(JSContext* cx, HandleObject enclosing);

    JSObject* enclosingScopeForStaticScopeIter() {
        return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
    }

    void setStrict() {
        setReservedSlot(STRICT_SLOT, BooleanValue(true));
    }

    bool isStrict() const {
        return getReservedSlot(STRICT_SLOT).isTrue();
    }

    // Indirect evals terminate in the global at run time, and has no static
    // enclosing scope.
    bool isDirect() const {
        return getReservedSlot(SCOPE_CHAIN_SLOT).isObject();
    }
};

// Static scope objects that stand in for one or more "polluting global"
// scopes on the dynamic scope chain.
//
// There are two flavors of polluting global scopes on the dynamic scope
// chain: either 0+ non-syntactic DynamicWithObjects, or 1
// NonSyntacticVariablesObject, created exclusively in
// js::ExecuteInGlobalAndReturnScope.
class StaticNonSyntacticScopeObjects : public ScopeObject
{
  public:
    static const unsigned RESERVED_SLOTS = 1;
    static const Class class_;

    static StaticNonSyntacticScopeObjects* create(JSContext* cx, HandleObject enclosing);

    JSObject* enclosingScopeForStaticScopeIter() {
        return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
    }
};

// A non-syntactic dynamic scope object that captures non-lexical
// bindings. That is, a scope object that captures both qualified var
// assignments and unqualified bareword assignments. Its parent is always the
// real global.
//
// This is used in ExecuteInGlobalAndReturnScope and sits in front of the
// global scope to capture 'var' and bareword asignments.
class NonSyntacticVariablesObject : public ScopeObject
{
  public:
    static const unsigned RESERVED_SLOTS = 1;
    static const Class class_;

    static NonSyntacticVariablesObject* create(JSContext* cx, Handle<GlobalObject*> global);
};

class NestedScopeObject : public ScopeObject
{
  public:
    /*
     * A refinement of enclosingScope that returns nullptr if the enclosing
     * scope is not a NestedScopeObject.
     */
    inline NestedScopeObject* enclosingNestedScope() const;

    // Return true if this object is a compile-time scope template.
    inline bool isStatic() { return !getProto(); }

    // Return the static scope corresponding to this scope chain object.
    inline NestedScopeObject* staticScope() {
        MOZ_ASSERT(!isStatic());
        return &getProto()->as<NestedScopeObject>();
    }

    // At compile-time it's possible for the scope chain to be null.
    JSObject* enclosingScopeForStaticScopeIter() {
        return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
    }

    void initEnclosingScope(JSObject* obj) {
        MOZ_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
    }

    /*
     * Note: in the case of hoisting, this prev-link will not ultimately be
     * the same as enclosingNestedScope; initEnclosingNestedScope must be
     * called separately in the emitter. 'reset' is just for asserting
     * stackiness.
     */
    void initEnclosingScopeFromParser(JSObject* prev) {
        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
    }

    void resetEnclosingScopeFromParser() {
        setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
    }
};

// With scope template objects on the static scope chain.
class StaticWithObject : public NestedScopeObject
{
  public:
    static const unsigned RESERVED_SLOTS = 1;
    static const Class class_;

    static StaticWithObject* create(ExclusiveContext* cx);
};

// With scope objects on the run-time scope chain.
class DynamicWithObject : public NestedScopeObject
{
    static const unsigned OBJECT_SLOT = 1;
    static const unsigned THIS_SLOT = 2;
    static const unsigned KIND_SLOT = 3;

  public:
    static const unsigned RESERVED_SLOTS = 4;
    static const Class class_;

    enum WithKind {
        SyntacticWith,
        NonSyntacticWith
    };

    static DynamicWithObject*
    create(JSContext* cx, HandleObject object, HandleObject enclosing, HandleObject staticWith,
           WithKind kind = SyntacticWith);

    StaticWithObject& staticWith() const {
        return getProto()->as<StaticWithObject>();
    }

    /* Return the 'o' in 'with (o)'. */
    JSObject& object() const {
        return getReservedSlot(OBJECT_SLOT).toObject();
    }

    /* Return object for the 'this' class hook. */
    JSObject& withThis() const {
        return getReservedSlot(THIS_SLOT).toObject();
    }

    /*
     * Return whether this object is a syntactic with object.  If not, this is a
     * With object we inserted between the outermost syntactic scope and the
     * global object to wrap the scope chain someone explicitly passed via JSAPI
     * to CompileFunction or script evaluation.
     */
    bool isSyntactic() const {
        return getReservedSlot(KIND_SLOT).toInt32() == SyntacticWith;
    }

    static inline size_t objectSlot() {
        return OBJECT_SLOT;
    }

    static inline size_t thisSlot() {
        return THIS_SLOT;
    }
};

class BlockObject : public NestedScopeObject
{
  protected:
    static const unsigned DEPTH_SLOT = 1;

  public:
    static const unsigned RESERVED_SLOTS = 2;
    static const Class class_;

    /* Return the abstract stack depth right before entering this nested scope. */
    uint32_t stackDepth() const {
        return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
    }

    /* Return the number of variables associated with this block. */
    uint32_t numVariables() const {
        // TODO: propertyCount() is O(n), use O(1) lastProperty()->slot() instead
        return propertyCount();
    }

  protected:
    /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */
    const Value& slotValue(unsigned i) {
        return getSlotRef(RESERVED_SLOTS + i);
    }

    void setSlotValue(unsigned i, const Value& v) {
        setSlot(RESERVED_SLOTS + i, v);
    }
};

class StaticBlockObject : public BlockObject
{
    static const unsigned LOCAL_OFFSET_SLOT = 1;

  public:
    static StaticBlockObject* create(ExclusiveContext* cx);

    /* See StaticScopeIter comment. */
    JSObject* enclosingStaticScope() const {
        return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
    }

    /*
     * Return the index (in the range [0, numVariables()) corresponding to the
     * given shape of a block object.
     */
    uint32_t shapeToIndex(const Shape& shape) {
        uint32_t slot = shape.slot();
        MOZ_ASSERT(slot - RESERVED_SLOTS < numVariables());
        return slot - RESERVED_SLOTS;
    }

    /*
     * A refinement of enclosingStaticScope that returns nullptr if the enclosing
     * static scope is a JSFunction.
     */
    inline StaticBlockObject* enclosingBlock() const;

    uint32_t localOffset() {
        return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32();
    }

    // Return the local corresponding to the 'var'th binding where 'var' is in the
    // range [0, numVariables()).
    uint32_t blockIndexToLocalIndex(uint32_t index) {
        MOZ_ASSERT(index < numVariables());
        return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + index;
    }

    // Return the slot corresponding to block index 'index', where 'index' is
    // in the range [0, numVariables()).  The result is in the range
    // [RESERVED_SLOTS, RESERVED_SLOTS + numVariables()).
    uint32_t blockIndexToSlot(uint32_t index) {
        MOZ_ASSERT(index < numVariables());
        return RESERVED_SLOTS + index;
    }

    // Return the slot corresponding to local variable 'local', where 'local' is
    // in the range [localOffset(), localOffset() + numVariables()).  The result is
    // in the range [RESERVED_SLOTS, RESERVED_SLOTS + numVariables()).
    uint32_t localIndexToSlot(uint32_t local) {
        MOZ_ASSERT(local >= localOffset());
        return blockIndexToSlot(local - localOffset());
    }

    /*
     * A let binding is aliased if accessed lexically by nested functions or
     * dynamically through dynamic name lookup (eval, with, function::, etc).
     */
    bool isAliased(unsigned i) {
        return slotValue(i).isTrue();
    }

    // Look up if the block has an aliased binding named |name|.
    Shape* lookupAliasedName(PropertyName* name);

    /*
     * A static block object is cloned (when entering the block) iff some
     * variable of the block isAliased.
     */
    bool needsClone() {
        return numVariables() > 0 && !getSlot(RESERVED_SLOTS).isFalse();
    }

    /* Frontend-only functions ***********************************************/

    /* Initialization functions for above fields. */
    void setAliased(unsigned i, bool aliased) {
        MOZ_ASSERT_IF(i > 0, slotValue(i-1).isBoolean());
        setSlotValue(i, BooleanValue(aliased));
        if (aliased && !needsClone()) {
            setSlotValue(0, MagicValue(JS_BLOCK_NEEDS_CLONE));
            MOZ_ASSERT(needsClone());
        }
    }

    void setLocalOffset(uint32_t offset) {
        MOZ_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined());
        initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset));
    }

    /*
     * Frontend compilation temporarily uses the object's slots to link
     * a let var to its associated Definition parse node.
     */
    void setDefinitionParseNode(unsigned i, frontend::Definition* def) {
        MOZ_ASSERT(slotValue(i).isUndefined());
        setSlotValue(i, PrivateValue(def));
    }

    frontend::Definition* definitionParseNode(unsigned i) {
        Value v = slotValue(i);
        return reinterpret_cast<frontend::Definition*>(v.toPrivate());
    }

    /*
     * While ScopeCoordinate can generally reference up to 2^24 slots, block objects have an
     * additional limitation that all slot indices must be storable as uint16_t short-ids in the
     * associated Shape. If we could remove the block dependencies on shape->shortid, we could
     * remove INDEX_LIMIT.
     */
    static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16);

    static Shape* addVar(ExclusiveContext* cx, Handle<StaticBlockObject*> block, HandleId id,
                         bool constant, unsigned index, bool* redeclared);
};

class ClonedBlockObject : public BlockObject
{
    static ClonedBlockObject* create(JSContext* cx, Handle<StaticBlockObject*> block,
                                     HandleObject enclosing);

  public:
    static ClonedBlockObject* create(JSContext* cx, Handle<StaticBlockObject*> block,
                                     AbstractFramePtr frame);

    static ClonedBlockObject* createHollowForDebug(JSContext* cx,
                                                   Handle<StaticBlockObject*> block);

    /* The static block from which this block was cloned. */
    StaticBlockObject& staticBlock() const {
        return getProto()->as<StaticBlockObject>();
    }

    /* Assuming 'put' has been called, return the value of the ith let var. */
    const Value& var(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) {
        MOZ_ASSERT_IF(checkAliasing, staticBlock().isAliased(i));
        return slotValue(i);
    }

    void setVar(unsigned i, const Value& v, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) {
        MOZ_ASSERT_IF(checkAliasing, staticBlock().isAliased(i));
        setSlotValue(i, v);
    }

    /* Copy in all the unaliased formals and locals. */
    void copyUnaliasedValues(AbstractFramePtr frame);

    /*
     * Create a new ClonedBlockObject with the same enclosing scope and
     * variable values as this.
     */
    static ClonedBlockObject* clone(JSContext* cx, Handle<ClonedBlockObject*> block);
};

// Internal scope object used by JSOP_BINDNAME upon encountering an
// uninitialized lexical slot.
//
// ES6 lexical bindings cannot be accessed in any way (throwing
// ReferenceErrors) until initialized. Normally, NAME operations
// unconditionally check for uninitialized lexical slots. When getting or
// looking up names, this can be done without slowing down normal operations
// on the return value. When setting names, however, we do not want to pollute
// all set-property paths with uninitialized lexical checks. For setting names
// (i.e. JSOP_SETNAME), we emit an accompanying, preceding JSOP_BINDNAME which
// finds the right scope on which to set the name. Moreover, when the name on
// the scope is an uninitialized lexical, we cannot throw eagerly, as the spec
// demands that the error be thrown after evaluating the RHS of
// assignments. Instead, this sentinel scope object is pushed on the stack.
// Attempting to access anything on this scope throws the appropriate
// ReferenceError.
class UninitializedLexicalObject : public ScopeObject
{
  public:
    static const unsigned RESERVED_SLOTS = 1;
    static const Class class_;

    static UninitializedLexicalObject* create(JSContext* cx, HandleObject enclosing);
};

template<XDRMode mode>
bool
XDRStaticBlockObject(XDRState<mode>* xdr, HandleObject enclosingScope,
                     MutableHandle<StaticBlockObject*> objp);

template<XDRMode mode>
bool
XDRStaticWithObject(XDRState<mode>* xdr, HandleObject enclosingScope,
                    MutableHandle<StaticWithObject*> objp);

extern JSObject*
CloneNestedScopeObject(JSContext* cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src);

/*****************************************************************************/

// A scope iterator describes the active scopes starting from a dynamic scope,
// static scope pair. This pair may be derived from the current point of
// execution in a frame. If derived in such a fashion, the ScopeIter tracks
// whether the current scope is within the extent of this initial frame.
// Here, "frame" means a single activation of: a function, eval, or global
// code.
class ScopeIter
{
    StaticScopeIter<CanGC> ssi_;
    RootedObject scope_;
    AbstractFramePtr frame_;

    void incrementStaticScopeIter();
    void settle();

    // No value semantics.
    ScopeIter(const ScopeIter& si) = delete;

  public:
    // Constructing from a copy of an existing ScopeIter.
    ScopeIter(JSContext* cx, const ScopeIter& si
              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);

    // Constructing from a dynamic scope, static scope pair. All scopes are
    // considered not to be withinInitialFrame, since no frame is given.
    ScopeIter(JSContext* cx, JSObject* scope, JSObject* staticScope
              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);

    // Constructing from a frame. Places the ScopeIter on the innermost scope
    // at pc.
    ScopeIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);

    inline bool done() const;
    ScopeIter& operator++();

    // If done():
    inline JSObject& enclosingScope() const;

    // If !done():
    enum Type { Call, Block, With, Eval, NonSyntactic };
    Type type() const;

    inline bool hasNonSyntacticScopeObject() const;
    inline bool hasSyntacticScopeObject() const;
    inline bool hasAnyScopeObject() const;
    inline bool canHaveSyntacticScopeObject() const;
    ScopeObject& scope() const;

    JSObject* maybeStaticScope() const;
    StaticBlockObject& staticBlock() const { return ssi_.block(); }
    StaticWithObject& staticWith() const { return ssi_.staticWith(); }
    StaticEvalObject& staticEval() const { return ssi_.eval(); }
    StaticNonSyntacticScopeObjects& staticNonSyntactic() const { return ssi_.nonSyntactic(); }
    JSFunction& fun() const { return ssi_.fun(); }

    bool withinInitialFrame() const { return !!frame_; }
    AbstractFramePtr initialFrame() const { MOZ_ASSERT(withinInitialFrame()); return frame_; }
    AbstractFramePtr maybeInitialFrame() const { return frame_; }

    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};

// The key in MissingScopeMap. For live frames, maps live frames to their
// synthesized scopes. For completely optimized-out scopes, maps the static
// scope objects to their synthesized scopes. The scopes we synthesize for
// static scope objects are read-only, and we never use their parent links, so
// they don't need to be distinct.
//
// That is, completely optimized out scopes can't be distinguished by
// frame. Note that even if the frame corresponding to the static scope is
// live on the stack, it is unsound to synthesize a scope from that live
// frame. In other words, the provenance of the scope chain is from allocated
// closures (i.e., allocation sites) and is irrecoverable from simple stack
// inspection (i.e., call sites).
class MissingScopeKey
{
    friend class LiveScopeVal;

    AbstractFramePtr frame_;
    JSObject* staticScope_;

  public:
    explicit MissingScopeKey(const ScopeIter& si)
      : frame_(si.maybeInitialFrame()),
        staticScope_(si.maybeStaticScope())
    { }

    AbstractFramePtr frame() const { return frame_; }
    JSObject* staticScope() const { return staticScope_; }

    void updateStaticScope(JSObject* obj) { staticScope_ = obj; }
    void updateFrame(AbstractFramePtr frame) { frame_ = frame; }

    // For use as hash policy.
    typedef MissingScopeKey Lookup;
    static HashNumber hash(MissingScopeKey sk);
    static bool match(MissingScopeKey sk1, MissingScopeKey sk2);
    bool operator!=(const MissingScopeKey& other) const {
        return frame_ != other.frame_ || staticScope_ != other.staticScope_;
    }
    static void rekey(MissingScopeKey& k, const MissingScopeKey& newKey) {
        k = newKey;
    }
};

// The value in LiveScopeMap, mapped from by live scope objects.
class LiveScopeVal
{
    friend class DebugScopes;
    friend class MissingScopeKey;

    AbstractFramePtr frame_;
    RelocatablePtrObject staticScope_;

    void sweep();
    static void staticAsserts();

  public:
    explicit LiveScopeVal(const ScopeIter& si)
      : frame_(si.initialFrame()),
        staticScope_(si.maybeStaticScope())
    { }

    AbstractFramePtr frame() const { return frame_; }
    JSObject* staticScope() const { return staticScope_; }

    void updateFrame(AbstractFramePtr frame) { frame_ = frame; }
};

/*****************************************************************************/

/*
 * Debug scope objects
 *
 * The debugger effectively turns every opcode into a potential direct eval.
 * Naively, this would require creating a ScopeObject for every call/block
 * scope and using JSOP_GETALIASEDVAR for every access. To optimize this, the
 * engine assumes there is no debugger and optimizes scope access and creation
 * accordingly. When the debugger wants to perform an unexpected eval-in-frame
 * (or other, similar dynamic-scope-requiring operations), fp->scopeChain is
 * now incomplete: it may not contain all, or any, of the ScopeObjects to
 * represent the current scope.
 *
 * To resolve this, the debugger first calls GetDebugScopeFor(Function|Frame)
 * to synthesize a "debug scope chain". A debug scope chain is just a chain of
 * objects that fill in missing scopes and protect the engine from unexpected
 * access. (The latter means that some debugger operations, like redefining a
 * lexical binding, can fail when a true eval would succeed.) To do both of
 * these things, GetDebugScopeFor* creates a new proxy DebugScopeObject to sit
 * in front of every existing ScopeObject.
 *
 * GetDebugScopeFor* ensures the invariant that the same DebugScopeObject is
 * always produced for the same underlying scope (optimized or not!). This is
 * maintained by some bookkeeping information stored in DebugScopes.
 */

extern JSObject*
GetDebugScopeForFunction(JSContext* cx, HandleFunction fun);

extern JSObject*
GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc);

/* Provides debugger access to a scope. */
class DebugScopeObject : public ProxyObject
{
    /*
     * The enclosing scope on the dynamic scope chain. This slot is analogous
     * to the SCOPE_CHAIN_SLOT of a ScopeObject.
     */
    static const unsigned ENCLOSING_EXTRA = 0;

    /*
     * NullValue or a dense array holding the unaliased variables of a function
     * frame that has been popped.
     */
    static const unsigned SNAPSHOT_EXTRA = 1;

  public:
    static DebugScopeObject* create(JSContext* cx, ScopeObject& scope, HandleObject enclosing);

    ScopeObject& scope() const;
    JSObject& enclosingScope() const;

    /* May only be called for proxies to function call objects. */
    ArrayObject* maybeSnapshot() const;
    void initSnapshot(ArrayObject& snapshot);

    /* Currently, the 'declarative' scopes are Call and Block. */
    bool isForDeclarative() const;

    // Get a property by 'id', but returns sentinel values instead of throwing
    // on exceptional cases.
    bool getMaybeSentinelValue(JSContext* cx, HandleId id, MutableHandleValue vp);

    // Does this debug scope not have a dynamic counterpart or was never live
    // (and thus does not have a synthesized ScopeObject or a snapshot)?
    bool isOptimizedOut() const;
};

/* Maintains per-compartment debug scope bookkeeping information. */
class DebugScopes
{
    /* The map from (non-debug) scopes to debug scopes. */
    ObjectWeakMap proxiedScopes;

    /*
     * The map from live frames which have optimized-away scopes to the
     * corresponding debug scopes.
     */
    typedef HashMap<MissingScopeKey,
                    ReadBarrieredDebugScopeObject,
                    MissingScopeKey,
                    RuntimeAllocPolicy> MissingScopeMap;
    MissingScopeMap missingScopes;

    /*
     * The map from scope objects of live frames to the live frame. This map
     * updated lazily whenever the debugger needs the information. In between
     * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop*
     * removes scopes as they are popped). Thus, two consecutive debugger lazy
     * updates of liveScopes need only fill in the new scopes.
     */
    typedef HashMap<ScopeObject*,
                    LiveScopeVal,
                    DefaultHasher<ScopeObject*>,
                    RuntimeAllocPolicy> LiveScopeMap;
    LiveScopeMap liveScopes;
    static MOZ_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime* rt, LiveScopeMap* map,
                                                             ScopeObject* key);

  public:
    explicit DebugScopes(JSContext* c);
    ~DebugScopes();

  private:
    bool init();

    static DebugScopes* ensureCompartmentData(JSContext* cx);

  public:
    void mark(JSTracer* trc);
    void sweep(JSRuntime* rt);
#ifdef JS_GC_ZEAL
    void checkHashTablesAfterMovingGC(JSRuntime* rt);
#endif

    static DebugScopeObject* hasDebugScope(JSContext* cx, ScopeObject& scope);
    static bool addDebugScope(JSContext* cx, ScopeObject& scope, DebugScopeObject& debugScope);

    static DebugScopeObject* hasDebugScope(JSContext* cx, const ScopeIter& si);
    static bool addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope);

    static bool updateLiveScopes(JSContext* cx);
    static LiveScopeVal* hasLiveScope(ScopeObject& scope);
    static void unsetPrevUpToDateUntil(JSContext* cx, AbstractFramePtr frame);

    // When a frame bails out from Ion to Baseline, there might be missing
    // scopes keyed on, and live scopes containing, the old
    // RematerializedFrame. Forward those values to the new BaselineFrame.
    static void forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to);

    // In debug-mode, these must be called whenever exiting a scope that might
    // have stack-allocated locals.
    static void onPopCall(AbstractFramePtr frame, JSContext* cx);
    static void onPopBlock(JSContext* cx, const ScopeIter& si);
    static void onPopBlock(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc);
    static void onPopWith(AbstractFramePtr frame);
    static void onPopStrictEvalScope(AbstractFramePtr frame);
    static void onCompartmentUnsetIsDebuggee(JSCompartment* c);
};

extern bool
IsDebugScopeSlow(ProxyObject* proxy);

}  /* namespace js */

template<>
inline bool
JSObject::is<js::NestedScopeObject>() const
{
    return is<js::BlockObject>() ||
           is<js::StaticWithObject>() ||
           is<js::DynamicWithObject>();
}

template<>
inline bool
JSObject::is<js::ScopeObject>() const
{
    return is<js::CallObject>() ||
           is<js::DeclEnvObject>() ||
           is<js::NestedScopeObject>() ||
           is<js::UninitializedLexicalObject>() ||
           is<js::NonSyntacticVariablesObject>();
}

template<>
inline bool
JSObject::is<js::DebugScopeObject>() const
{
    // Note: don't use is<ProxyObject>() here -- it also matches subclasses!
    return hasClass(&js::ProxyObject::class_) &&
           IsDebugScopeSlow(&const_cast<JSObject*>(this)->as<js::ProxyObject>());
}

template<>
inline bool
JSObject::is<js::ClonedBlockObject>() const
{
    return is<js::BlockObject>() && !!getProto();
}

template<>
inline bool
JSObject::is<js::StaticBlockObject>() const
{
    return is<js::BlockObject>() && !getProto();
}

namespace js {

inline bool
IsSyntacticScope(JSObject* scope)
{
    return scope->is<ScopeObject>() &&
           (!scope->is<DynamicWithObject>() || scope->as<DynamicWithObject>().isSyntactic()) &&
           !scope->is<NonSyntacticVariablesObject>();
}

inline const Value&
ScopeObject::aliasedVar(ScopeCoordinate sc)
{
    MOZ_ASSERT(is<CallObject>() || is<ClonedBlockObject>());
    return getSlot(sc.slot());
}

inline NestedScopeObject*
NestedScopeObject::enclosingNestedScope() const
{
    JSObject* obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
    return obj && obj->is<NestedScopeObject>() ? &obj->as<NestedScopeObject>() : nullptr;
}

inline bool
ScopeIter::done() const
{
    return ssi_.done();
}

inline bool
ScopeIter::hasSyntacticScopeObject() const
{
    return ssi_.hasSyntacticDynamicScopeObject();
}

inline bool
ScopeIter::hasNonSyntacticScopeObject() const
{
    // The case we're worrying about here is a NonSyntactic static scope which
    // has 0+ corresponding non-syntactic DynamicWithObject scopes or a
    // NonSyntacticVariablesObject.
    if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) {
        MOZ_ASSERT_IF(scope_->is<DynamicWithObject>(),
                      !scope_->as<DynamicWithObject>().isSyntactic());
        return scope_->is<DynamicWithObject>() ||
               scope_->is<NonSyntacticVariablesObject>();
    }
    return false;
}

inline bool
ScopeIter::hasAnyScopeObject() const
{
    return hasSyntacticScopeObject() || hasNonSyntacticScopeObject();
}

inline bool
ScopeIter::canHaveSyntacticScopeObject() const
{
    if (ssi_.done())
        return false;

    switch (type()) {
      case Call:
        return true;
      case Block:
        return true;
      case With:
        return true;
      case Eval:
        // Only strict eval scopes can have dynamic scope objects.
        return staticEval().isStrict();
      case NonSyntactic:
        return false;
    }

    // Silence warnings.
    return false;
}

inline JSObject&
ScopeIter::enclosingScope() const
{
    // As an engine invariant (maintained internally and asserted by Execute),
    // ScopeObjects and non-ScopeObjects cannot be interleaved on the scope
    // chain; every scope chain must start with zero or more ScopeObjects and
    // terminate with one or more non-ScopeObjects (viz., GlobalObject).
    MOZ_ASSERT(done());
    MOZ_ASSERT(!IsSyntacticScope(scope_));
    return *scope_;
}

extern bool
CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
                                HandleObject dynamicTerminatingScope,
                                MutableHandleObject dynamicScopeObj);

bool HasNonSyntacticStaticScopeChain(JSObject* staticScope);
uint32_t StaticScopeChainLength(JSObject* staticScope);

#ifdef DEBUG
void DumpStaticScopeChain(JSScript* script);
void DumpStaticScopeChain(JSObject* staticScope);
bool
AnalyzeEntrainedVariables(JSContext* cx, HandleScript script);
#endif

} // namespace js

#endif /* vm_ScopeObject_h */