/* -*- 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/. */

#include "vm/ScopeObject-inl.h"

#include "mozilla/PodOperations.h"
#include "mozilla/SizePrintfMacros.h"

#include "jscompartment.h"
#include "jsiter.h"

#include "builtin/ModuleObject.h"
#include "frontend/ParseNode.h"
#include "gc/Policy.h"
#include "vm/ArgumentsObject.h"
#include "vm/GlobalObject.h"
#include "vm/ProxyObject.h"
#include "vm/Shape.h"
#include "vm/Xdr.h"

#include "jsatominlines.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"

#include "vm/Stack-inl.h"

using namespace js;
using namespace js::gc;

using mozilla::PodZero;

typedef Rooted<ArgumentsObject*> RootedArgumentsObject;
typedef MutableHandle<ArgumentsObject*> MutableHandleArgumentsObject;


/*** Static scope objects ************************************************************************/

void
StaticScope::setEnclosingScope(HandleObject obj)
{
    MOZ_ASSERT_IF(obj->is<StaticBlockScope>(), obj->isDelegate());
    setFixedSlot(ENCLOSING_SCOPE_SLOT, ObjectValue(*obj));
}

bool
StaticBlockScope::isExtensible() const
{
    return nonProxyIsExtensible();
}

StaticBlockScope*
StaticBlockScope::create(ExclusiveContext* cx)
{
    Rooted<TaggedProto> nullProto(cx, TaggedProto(nullptr));
    JSObject* obj = NewObjectWithGivenTaggedProto(cx, &ClonedBlockObject::class_, nullProto,
                                                  TenuredObject, BaseShape::DELEGATE);
    return obj ? &obj->as<StaticBlockScope>() : nullptr;
}

Shape*
StaticBlockScope::lookupAliasedName(PropertyName* name)
{
    Shape::Range<NoGC> r(lastProperty());
    while (!r.empty()) {
        jsid id = r.front().propidRaw();
        if (JSID_TO_ATOM(id)->asPropertyName() == name && isAliased(shapeToIndex(r.front())))
            return &r.front();
        r.popFront();
    }
    return nullptr;
}

bool
StaticBlockScope::makeNonExtensible(ExclusiveContext* cx)
{
    // Do not do all the work of js::PreventExtensions, as BlockObjects are
    // known to be NativeObjects, have no lazy properties, and no dense
    // elements. Indeed, we do not have a JSContext as parsing may happen
    // off-thread.
    if (!isExtensible())
        return true;
    return setFlags(cx, BaseShape::NOT_EXTENSIBLE, JSObject::GENERATE_SHAPE);
}

/* static */ Shape*
StaticBlockScope::addVar(ExclusiveContext* cx, Handle<StaticBlockScope*> block, HandleId id,
                         bool constant, unsigned index, bool* redeclared)
{
    MOZ_ASSERT(JSID_IS_ATOM(id));
    MOZ_ASSERT(index < LOCAL_INDEX_LIMIT);

    *redeclared = false;

    /* Inline NativeObject::addProperty in order to trap the redefinition case. */
    ShapeTable::Entry* entry;
    if (Shape::search<MaybeAdding::Adding>(cx, block->lastProperty(), id, &entry)) {
        *redeclared = true;
        return nullptr;
    }

    /*
     * Don't convert this object to dictionary mode so that we can clone the
     * block's shape later.
     */
    uint32_t slot = JSSLOT_FREE(&ClonedBlockObject::class_) + index;
    uint32_t readonly = constant ? JSPROP_READONLY : 0;
    uint32_t propFlags = readonly | JSPROP_ENUMERATE | JSPROP_PERMANENT;
    return NativeObject::addPropertyInternal(cx, block, id,
                                             /* getter = */ nullptr,
                                             /* setter = */ nullptr,
                                             slot,
                                             propFlags,
                                             /* attrs = */ 0,
                                             entry,
                                             /* allowDictionary = */ false);
}

const Class StaticWithScope::class_ = {
    "WithTemplate",
    JSCLASS_HAS_RESERVED_SLOTS(StaticWithScope::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS
};

StaticWithScope*
StaticWithScope::create(ExclusiveContext* cx)
{
    return NewObjectWithNullTaggedProto<StaticWithScope>(cx, TenuredObject, BaseShape::DELEGATE);
}

template<XDRMode mode>
bool
js::XDRStaticWithScope(XDRState<mode>* xdr, HandleObject enclosingScope,
                       MutableHandle<StaticWithScope*> objp)
{
    if (mode == XDR_DECODE) {
        JSContext* cx = xdr->cx();
        Rooted<StaticWithScope*> obj(cx, StaticWithScope::create(cx));
        if (!obj)
            return false;
        obj->initEnclosingScope(enclosingScope);
        objp.set(obj);
    }
    // For encoding, there is nothing to do.  The only information that is
    // encoded by a StaticWithScope is its presence on the scope chain, and the
    // script XDR handler already takes care of that.

    return true;
}

template bool
js::XDRStaticWithScope(XDRState<XDR_ENCODE>*, HandleObject, MutableHandle<StaticWithScope*>);

template bool
js::XDRStaticWithScope(XDRState<XDR_DECODE>*, HandleObject, MutableHandle<StaticWithScope*>);


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

Shape*
js::ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc)
{
    MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
    StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
    uint32_t hops = ScopeCoordinate(pc).hops();
    while (true) {
        MOZ_ASSERT(!ssi.done());
        if (ssi.hasSyntacticDynamicScopeObject()) {
            if (!hops)
                break;
            hops--;
        }
        ssi++;
    }
    return ssi.scopeShape();
}

static const uint32_t SCOPE_COORDINATE_NAME_THRESHOLD = 20;

void
ScopeCoordinateNameCache::purge()
{
    shape = nullptr;
    if (map.initialized())
        map.finish();
}

PropertyName*
js::ScopeCoordinateName(ScopeCoordinateNameCache& cache, JSScript* script, jsbytecode* pc)
{
    Shape* shape = ScopeCoordinateToStaticScopeShape(script, pc);
    if (shape != cache.shape && shape->slot() >= SCOPE_COORDINATE_NAME_THRESHOLD) {
        cache.purge();
        if (cache.map.init(shape->slot())) {
            cache.shape = shape;
            Shape::Range<NoGC> r(shape);
            while (!r.empty()) {
                if (!cache.map.putNew(r.front().slot(), r.front().propid())) {
                    cache.purge();
                    break;
                }
                r.popFront();
            }
        }
    }

    jsid id;
    ScopeCoordinate sc(pc);
    if (shape == cache.shape) {
        ScopeCoordinateNameCache::Map::Ptr p = cache.map.lookup(sc.slot());
        id = p->value();
    } else {
        Shape::Range<NoGC> r(shape);
        while (r.front().slot() != sc.slot())
            r.popFront();
        id = r.front().propidRaw();
    }

    /* Beware nameless destructuring formal. */
    if (!JSID_IS_ATOM(id))
        return script->runtimeFromAnyThread()->commonNames->empty;
    return JSID_TO_ATOM(id)->asPropertyName();
}

JSScript*
js::ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc)
{
    MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
    StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
    uint32_t hops = ScopeCoordinate(pc).hops();
    while (true) {
        if (ssi.hasSyntacticDynamicScopeObject()) {
            if (!hops)
                break;
            hops--;
        }
        ssi++;
    }
    if (ssi.type() != StaticScopeIter<NoGC>::Function)
        return nullptr;
    return ssi.funScript();
}

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

void
ScopeObject::setEnclosingScope(HandleObject obj)
{
    MOZ_ASSERT_IF(obj->is<LexicalScopeBase>() ||
                  obj->is<DeclEnvObject>() ||
                  obj->is<ClonedBlockObject>(),
                  obj->isDelegate());
    setFixedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*obj));
}

CallObject*
CallObject::create(JSContext* cx, HandleShape shape, HandleObjectGroup group, uint32_t lexicalBegin)
{
    MOZ_ASSERT(!group->singleton(),
               "passed a singleton group to create() (use createSingleton() "
               "instead)");
    gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
    MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
    kind = gc::GetBackgroundAllocKind(kind);

    JSObject* obj = JSObject::create(cx, kind, gc::DefaultHeap, shape, group);
    if (!obj)
        return nullptr;

    obj->as<CallObject>().initRemainingSlotsToUninitializedLexicals(lexicalBegin);
    return &obj->as<CallObject>();
}

CallObject*
CallObject::createSingleton(JSContext* cx, HandleShape shape, uint32_t lexicalBegin)
{
    gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
    MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
    kind = gc::GetBackgroundAllocKind(kind);

    RootedObjectGroup group(cx, ObjectGroup::lazySingletonGroup(cx, &class_, TaggedProto(nullptr)));
    if (!group)
        return nullptr;
    RootedObject obj(cx, JSObject::create(cx, kind, gc::TenuredHeap, shape, group));
    if (!obj)
        return nullptr;

    MOZ_ASSERT(obj->isSingleton(),
               "group created inline above must be a singleton");

    obj->as<CallObject>().initRemainingSlotsToUninitializedLexicals(lexicalBegin);
    return &obj->as<CallObject>();
}

/*
 * Create a CallObject for a JSScript that is not initialized to any particular
 * callsite. This object can either be initialized (with an enclosing scope and
 * callee) or used as a template for jit compilation.
 */
CallObject*
CallObject::createTemplateObject(JSContext* cx, HandleScript script, gc::InitialHeap heap)
{
    RootedShape shape(cx, script->bindings.callObjShape());
    MOZ_ASSERT(shape->getObjectClass() == &class_);

    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
    if (!group)
        return nullptr;

    gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
    MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
    kind = gc::GetBackgroundAllocKind(kind);

    JSObject* obj = JSObject::create(cx, kind, heap, shape, group);
    if (!obj)
        return nullptr;

    // Set uninitialized lexicals even on template objects, as Ion will copy
    // over the template object's slot values in the fast path.
    obj->as<CallObject>().initAliasedLexicalsToThrowOnTouch(script);

    return &obj->as<CallObject>();
}

/*
 * Construct a call object for the given bindings.  If this is a call object
 * for a function invocation, callee should be the function being called.
 * Otherwise it must be a call object for eval of strict mode code, and callee
 * must be null.
 */
CallObject*
CallObject::create(JSContext* cx, HandleScript script, HandleObject enclosing, HandleFunction callee)
{
    gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
    CallObject* callobj = CallObject::createTemplateObject(cx, script, heap);
    if (!callobj)
        return nullptr;

    callobj->setEnclosingScope(enclosing);
    callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee));

    if (script->treatAsRunOnce()) {
        Rooted<CallObject*> ncallobj(cx, callobj);
        if (!JSObject::setSingleton(cx, ncallobj))
            return nullptr;
        return ncallobj;
    }

    return callobj;
}

CallObject*
CallObject::createForFunction(JSContext* cx, HandleObject enclosing, HandleFunction callee)
{
    RootedObject scopeChain(cx, enclosing);
    MOZ_ASSERT(scopeChain);

    /*
     * For a named function expression Call's parent points to an environment
     * object holding function's name.
     */
    if (callee->isNamedLambda()) {
        scopeChain = DeclEnvObject::create(cx, scopeChain, callee);
        if (!scopeChain)
            return nullptr;
    }

    RootedScript script(cx, callee->nonLazyScript());
    return create(cx, script, scopeChain, callee);
}

CallObject*
CallObject::createForFunction(JSContext* cx, AbstractFramePtr frame)
{
    MOZ_ASSERT(frame.isFunctionFrame());
    assertSameCompartment(cx, frame);

    RootedObject scopeChain(cx, frame.scopeChain());
    RootedFunction callee(cx, frame.callee());

    CallObject* callobj = createForFunction(cx, scopeChain, callee);
    if (!callobj)
        return nullptr;

    /* Copy in the closed-over formal arguments. */
    for (AliasedFormalIter i(frame.script()); i; i++) {
        callobj->setAliasedVar(cx, i, i->name(),
                               frame.unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING));
    }

    return callobj;
}

CallObject*
CallObject::createForStrictEval(JSContext* cx, AbstractFramePtr frame)
{
    MOZ_ASSERT(frame.isStrictEvalFrame());
    MOZ_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterFrame() == frame.asInterpreterFrame());
    MOZ_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterRegs().pc == frame.script()->code());

    RootedFunction callee(cx);
    RootedScript script(cx, frame.script());
    RootedObject scopeChain(cx, frame.scopeChain());
    return create(cx, script, scopeChain, callee);
}

CallObject*
CallObject::createHollowForDebug(JSContext* cx, HandleFunction callee)
{
    MOZ_ASSERT(!callee->needsCallObject());

    // This scope's parent link is never used: the DebugScopeObject that
    // refers to this scope carries its own parent link, which is what
    // Debugger uses to construct the tree of Debugger.Environment objects. So
    // just parent this scope directly to the global lexical scope.
    Rooted<GlobalObject*> global(cx, &callee->global());
    RootedObject globalLexical(cx, &global->lexicalScope());
    Rooted<CallObject*> callobj(cx, createForFunction(cx, globalLexical, callee));
    if (!callobj)
        return nullptr;

    RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
    RootedId id(cx);
    RootedScript script(cx, callee->nonLazyScript());
    for (BindingIter bi(script); !bi.done(); bi++) {
        id = NameToId(bi->name());
        if (!SetProperty(cx, callobj, id, optimizedOut))
            return nullptr;
    }

    return callobj;
}

const Class CallObject::class_ = {
    "Call",
    JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)
};

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

const ObjectOps ModuleEnvironmentObject::objectOps_ = {
    ModuleEnvironmentObject::lookupProperty,
    nullptr,                                             /* defineProperty */
    ModuleEnvironmentObject::hasProperty,
    ModuleEnvironmentObject::getProperty,
    ModuleEnvironmentObject::setProperty,
    ModuleEnvironmentObject::getOwnPropertyDescriptor,
    ModuleEnvironmentObject::deleteProperty,
    nullptr, nullptr,                                    /* watch/unwatch */
    nullptr,                                             /* getElements */
    ModuleEnvironmentObject::enumerate,
    nullptr
};

const Class ModuleEnvironmentObject::class_ = {
    "ModuleEnvironmentObject",
    JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS,
    JS_NULL_CLASS_OPS,
    JS_NULL_CLASS_SPEC,
    JS_NULL_CLASS_EXT,
    &ModuleEnvironmentObject::objectOps_
};

/* static */ ModuleEnvironmentObject*
ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module)
{
    RootedScript script(cx, module->script());
    RootedShape shape(cx, script->bindings.callObjShape());
    MOZ_ASSERT(shape->getObjectClass() == &class_);

    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
    if (!group)
        return nullptr;

    gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
    MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
    kind = gc::GetBackgroundAllocKind(kind);

    JSObject* obj = JSObject::create(cx, kind, TenuredHeap, shape, group);
    if (!obj)
        return nullptr;

    RootedModuleEnvironmentObject scope(cx, &obj->as<ModuleEnvironmentObject>());

    // Set uninitialized lexicals even on template objects, as Ion will use
    // copy over the template object's slot values in the fast path.
    scope->initAliasedLexicalsToThrowOnTouch(script);

    scope->initFixedSlot(MODULE_SLOT, ObjectValue(*module));
    if (!JSObject::setSingleton(cx, scope))
        return nullptr;

    // Initialize this early so that we can manipulate the scope object without
    // causing assertions.
    RootedObject globalLexical(cx, &cx->global()->lexicalScope());
    scope->setEnclosingScope(globalLexical);

    // It is not be possible to add or remove bindings from a module environment
    // after this point as module code is always strict.
#ifdef DEBUG
    for (Shape::Range<NoGC> r(scope->lastProperty()); !r.empty(); r.popFront())
        MOZ_ASSERT(!r.front().configurable());
    MOZ_ASSERT(scope->lastProperty()->getObjectFlags() & BaseShape::NOT_EXTENSIBLE);
    MOZ_ASSERT(!scope->inDictionaryMode());
#endif

    return scope;
}

ModuleObject&
ModuleEnvironmentObject::module()
{
    return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>();
}

IndirectBindingMap&
ModuleEnvironmentObject::importBindings()
{
    return module().importBindings();
}

bool
ModuleEnvironmentObject::createImportBinding(JSContext* cx, HandleAtom importName,
                                             HandleModuleObject module, HandleAtom localName)
{
    RootedId importNameId(cx, AtomToId(importName));
    RootedId localNameId(cx, AtomToId(localName));
    RootedModuleEnvironmentObject env(cx, module->environment());
    if (!importBindings().putNew(cx, importNameId, env, localNameId)) {
        ReportOutOfMemory(cx);
        return false;
    }

    return true;
}

bool
ModuleEnvironmentObject::hasImportBinding(HandlePropertyName name)
{
    return importBindings().has(NameToId(name));
}

bool
ModuleEnvironmentObject::lookupImport(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut)
{
    return importBindings().lookup(name, envOut, shapeOut);
}

/* static */ bool
ModuleEnvironmentObject::lookupProperty(JSContext* cx, HandleObject obj, HandleId id,
                                        MutableHandleObject objp, MutableHandleShape propp)
{
    const IndirectBindingMap& bindings = obj->as<ModuleEnvironmentObject>().importBindings();
    Shape* shape;
    ModuleEnvironmentObject* env;
    if (bindings.lookup(id, &env, &shape)) {
        objp.set(env);
        propp.set(shape);
        return true;
    }

    RootedNativeObject target(cx, &obj->as<NativeObject>());
    if (!NativeLookupOwnProperty<CanGC>(cx, target, id, propp))
        return false;

    objp.set(obj);
    return true;
}

/* static */ bool
ModuleEnvironmentObject::hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
    if (obj->as<ModuleEnvironmentObject>().importBindings().has(id)) {
        *foundp = true;
        return true;
    }

    RootedNativeObject self(cx, &obj->as<NativeObject>());
    return NativeHasProperty(cx, self, id, foundp);
}

/* static */ bool
ModuleEnvironmentObject::getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
                                     HandleId id, MutableHandleValue vp)
{
    const IndirectBindingMap& bindings = obj->as<ModuleEnvironmentObject>().importBindings();
    Shape* shape;
    ModuleEnvironmentObject* env;
    if (bindings.lookup(id, &env, &shape)) {
        vp.set(env->getSlot(shape->slot()));
        return true;
    }

    RootedNativeObject self(cx, &obj->as<NativeObject>());
    return NativeGetProperty(cx, self, receiver, id, vp);
}

/* static */ bool
ModuleEnvironmentObject::setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                                     HandleValue receiver, JS::ObjectOpResult& result)
{
    RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>());
    if (self->importBindings().has(id))
        return result.failReadOnly();

    return NativeSetProperty(cx, self, id, v, receiver, Qualified, result);
}

/* static */ bool
ModuleEnvironmentObject::getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
                                                  MutableHandle<PropertyDescriptor> desc)
{
    const IndirectBindingMap& bindings = obj->as<ModuleEnvironmentObject>().importBindings();
    Shape* shape;
    ModuleEnvironmentObject* env;
    if (bindings.lookup(id, &env, &shape)) {
        desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT);
        desc.object().set(obj);
        RootedValue value(cx, env->getSlot(shape->slot()));
        desc.setValue(value);
        desc.assertComplete();
        return true;
    }

    RootedNativeObject self(cx, &obj->as<NativeObject>());
    return NativeGetOwnPropertyDescriptor(cx, self, id, desc);
}

/* static */ bool
ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
                                        ObjectOpResult& result)
{
    return result.failCantDelete();
}

/* static */ bool
ModuleEnvironmentObject::enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
                                   bool enumerableOnly)
{
    RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>());
    const IndirectBindingMap& bs(self->importBindings());

    MOZ_ASSERT(properties.length() == 0);
    size_t count = bs.count() + self->slotSpan() - RESERVED_SLOTS;
    if (!properties.reserve(count)) {
        ReportOutOfMemory(cx);
        return false;
    }

    bs.forEachExportedName([&] (jsid name) {
        properties.infallibleAppend(name);
    });

    for (Shape::Range<NoGC> r(self->lastProperty()); !r.empty(); r.popFront())
        properties.infallibleAppend(r.front().propid());

    MOZ_ASSERT(properties.length() == count);
    return true;
}

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

const Class DeclEnvObject::class_ = {
    js_Object_str,
    JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) |
    JSCLASS_HAS_CACHED_PROTO(JSProto_Object)
};

/*
 * Create a DeclEnvObject for a JSScript that is not initialized to any
 * particular callsite. This object can either be initialized (with an enclosing
 * scope and callee) or used as a template for jit compilation.
 */
DeclEnvObject*
DeclEnvObject::createTemplateObject(JSContext* cx, HandleFunction fun, NewObjectKind newKind)
{
    Rooted<DeclEnvObject*> obj(cx);
    obj = NewObjectWithNullTaggedProto<DeclEnvObject>(cx, newKind, BaseShape::DELEGATE);
    if (!obj)
        return nullptr;

    // Assign a fixed slot to a property with the same name as the lambda.
    Rooted<jsid> id(cx, AtomToId(fun->name()));
    const Class* clasp = obj->getClass();
    unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;

    JSGetterOp getter = clasp->getGetProperty();
    JSSetterOp setter = clasp->getSetProperty();
    MOZ_ASSERT(getter != JS_PropertyStub);
    MOZ_ASSERT(setter != JS_StrictPropertyStub);

    if (!NativeObject::putProperty(cx, obj, id, getter, setter, lambdaSlot(), attrs, 0))
        return nullptr;

    MOZ_ASSERT(!obj->hasDynamicSlots());
    return obj;
}

DeclEnvObject*
DeclEnvObject::create(JSContext* cx, HandleObject enclosing, HandleFunction callee)
{
    Rooted<DeclEnvObject*> obj(cx, createTemplateObject(cx, callee, GenericObject));
    if (!obj)
        return nullptr;

    obj->setEnclosingScope(enclosing);
    obj->setFixedSlot(lambdaSlot(), ObjectValue(*callee));
    return obj;
}

static JSObject*
CloneStaticWithScope(JSContext* cx, HandleObject enclosingScope, Handle<StaticWithScope*> srcWith)
{
    Rooted<StaticWithScope*> clone(cx, StaticWithScope::create(cx));
    if (!clone)
        return nullptr;

    clone->initEnclosingScope(enclosingScope);

    return clone;
}

DynamicWithObject*
DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
                          HandleObject staticWith, WithKind kind)
{
    MOZ_ASSERT(staticWith->is<StaticWithScope>());

    Rooted<TaggedProto> proto(cx, TaggedProto(staticWith));
    Rooted<DynamicWithObject*> obj(cx);
    obj = NewObjectWithGivenTaggedProto<DynamicWithObject>(cx, proto, GenericObject,
                                                           BaseShape::DELEGATE);
    if (!obj)
        return nullptr;

    Value thisv = GetThisValue(object);

    obj->setEnclosingScope(enclosing);
    obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object));
    obj->setFixedSlot(THIS_SLOT, thisv);
    obj->setFixedSlot(KIND_SLOT, Int32Value(kind));

    return obj;
}

/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
static bool
CheckUnscopables(JSContext *cx, HandleObject obj, HandleId id, bool *scopable)
{
    RootedId unscopablesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols()
                                                .get(JS::SymbolCode::unscopables)));
    RootedValue v(cx);
    if (!GetProperty(cx, obj, obj, unscopablesId, &v))
        return false;
    if (v.isObject()) {
        RootedObject unscopablesObj(cx, &v.toObject());
        if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v))
            return false;
        *scopable = !ToBoolean(v);
    } else {
        *scopable = true;
    }
    return true;
}

static bool
with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
                    MutableHandleObject objp, MutableHandleShape propp)
{
    if (JSID_IS_ATOM(id, cx->names().dotThis)) {
        objp.set(nullptr);
        propp.set(nullptr);
        return true;
    }
    RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
    if (!LookupProperty(cx, actual, id, objp, propp))
        return false;

    if (propp) {
        bool scopable;
        if (!CheckUnscopables(cx, actual, id, &scopable))
            return false;
        if (!scopable) {
            objp.set(nullptr);
            propp.set(nullptr);
        }
    }
    return true;
}

static bool
with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle<PropertyDescriptor> desc,
                    ObjectOpResult& result)
{
    MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
    RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
    return DefineProperty(cx, actual, id, desc, result);
}

static bool
with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
    MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
    RootedObject actual(cx, &obj->as<DynamicWithObject>().object());

    // ES 8.1.1.2.1 step 3-5.
    if (!HasProperty(cx, actual, id, foundp))
        return false;
    if (!*foundp)
        return true;

    // Steps 7-10. (Step 6 is a no-op.)
    return CheckUnscopables(cx, actual, id, foundp);
}

static bool
with_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
                 MutableHandleValue vp)
{
    MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
    RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
    RootedValue actualReceiver(cx, receiver);
    if (receiver.isObject() && &receiver.toObject() == obj)
        actualReceiver.setObject(*actual);
    return GetProperty(cx, actual, actualReceiver, id, vp);
}

static bool
with_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                 HandleValue receiver, ObjectOpResult& result)
{
    MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
    RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
    RootedValue actualReceiver(cx, receiver);
    if (receiver.isObject() && &receiver.toObject() == obj)
        actualReceiver.setObject(*actual);
    return SetProperty(cx, actual, id, v, actualReceiver, result);
}

static bool
with_GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
                              MutableHandle<PropertyDescriptor> desc)
{
    MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
    RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
    return GetOwnPropertyDescriptor(cx, actual, id, desc);
}

static bool
with_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
{
    MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
    RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
    return DeleteProperty(cx, actual, id, result);
}

static const ObjectOps DynamicWithObjectObjectOps = {
    with_LookupProperty,
    with_DefineProperty,
    with_HasProperty,
    with_GetProperty,
    with_SetProperty,
    with_GetOwnPropertyDescriptor,
    with_DeleteProperty,
    nullptr, nullptr,    /* watch/unwatch */
    nullptr,             /* getElements */
    nullptr,             /* enumerate (native enumeration of target doesn't work) */
    nullptr,
};

const Class DynamicWithObject::class_ = {
    "With",
    JSCLASS_HAS_RESERVED_SLOTS(DynamicWithObject::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS,
    JS_NULL_CLASS_OPS,
    JS_NULL_CLASS_SPEC,
    JS_NULL_CLASS_EXT,
    &DynamicWithObjectObjectOps
};

/* static */ StaticEvalScope*
StaticEvalScope::create(JSContext* cx, HandleObject enclosing)
{
    StaticEvalScope* obj =
        NewObjectWithNullTaggedProto<StaticEvalScope>(cx, TenuredObject, BaseShape::DELEGATE);
    if (!obj)
        return nullptr;

    obj->setReservedSlot(ENCLOSING_SCOPE_SLOT, ObjectOrNullValue(enclosing));
    obj->setReservedSlot(STRICT_SLOT, BooleanValue(false));
    return obj;
}

const Class StaticEvalScope::class_ = {
    "StaticEval",
    JSCLASS_HAS_RESERVED_SLOTS(StaticEvalScope::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS
};

/* static */ StaticNonSyntacticScope*
StaticNonSyntacticScope::create(JSContext*cx, HandleObject enclosing)
{
    StaticNonSyntacticScope* obj =
        NewObjectWithNullTaggedProto<StaticNonSyntacticScope>(cx, TenuredObject,
                                                                     BaseShape::DELEGATE);
    if (!obj)
        return nullptr;

    obj->setReservedSlot(ENCLOSING_SCOPE_SLOT, ObjectOrNullValue(enclosing));
    return obj;
}

const Class StaticNonSyntacticScope::class_ = {
    "StaticNonSyntacticScope",
    JSCLASS_HAS_RESERVED_SLOTS(StaticNonSyntacticScope::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS
};

/* static */ NonSyntacticVariablesObject*
NonSyntacticVariablesObject::create(JSContext* cx, Handle<ClonedBlockObject*> globalLexical)
{
    MOZ_ASSERT(globalLexical->isGlobal());

    Rooted<NonSyntacticVariablesObject*> obj(cx,
        NewObjectWithNullTaggedProto<NonSyntacticVariablesObject>(cx, TenuredObject,
                                                                  BaseShape::DELEGATE));
    if (!obj)
        return nullptr;

    MOZ_ASSERT(obj->isUnqualifiedVarObj());
    if (!obj->setQualifiedVarObj(cx))
        return nullptr;

    obj->setEnclosingScope(globalLexical);
    return obj;
}

const Class NonSyntacticVariablesObject::class_ = {
    "NonSyntacticVariablesObject",
    JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS
};

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

bool
ClonedBlockObject::isExtensible() const
{
    return nonProxyIsExtensible();
}

/* static */ ClonedBlockObject*
ClonedBlockObject::create(JSContext* cx, Handle<StaticBlockScope*> block, HandleObject enclosing)
{
    MOZ_ASSERT(block->getClass() == &ClonedBlockObject::class_);

    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &ClonedBlockObject::class_,
                                                             TaggedProto(block.get())));
    if (!group)
        return nullptr;

    RootedShape shape(cx, block->lastProperty());

    gc::AllocKind allocKind = gc::GetGCObjectKind(&ClonedBlockObject::class_);
    if (CanBeFinalizedInBackground(allocKind, &ClonedBlockObject::class_))
        allocKind = GetBackgroundAllocKind(allocKind);
    RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, allocKind,
                                                                  gc::TenuredHeap, shape, group)));
    if (!obj)
        return nullptr;

    MOZ_ASSERT(!obj->inDictionaryMode());
    MOZ_ASSERT(obj->slotSpan() >= block->numVariables() + RESERVED_SLOTS);

    obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*enclosing));

    MOZ_ASSERT(obj->isDelegate());

    ClonedBlockObject* res = &obj->as<ClonedBlockObject>();

    if (res->isGlobal() || !res->isSyntactic())
        res->setReservedSlot(THIS_VALUE_SLOT, GetThisValue(enclosing));

    return res;
}

/* static */ ClonedBlockObject*
ClonedBlockObject::create(JSContext* cx, Handle<StaticBlockScope*> block, AbstractFramePtr frame)
{
    assertSameCompartment(cx, frame);
    RootedObject enclosing(cx, frame.scopeChain());
    return create(cx, block, enclosing);
}

/* static */ ClonedBlockObject*
ClonedBlockObject::createGlobal(JSContext* cx, Handle<GlobalObject*> global)
{
    Rooted<StaticBlockScope*> staticLexical(cx, StaticBlockScope::create(cx));
    if (!staticLexical)
        return nullptr;

    // Currently the global lexical scope cannot have any bindings with frame
    // slots.
    staticLexical->setLocalOffsetToInvalid();
    staticLexical->initEnclosingScope(nullptr);
    Rooted<ClonedBlockObject*> lexical(cx, ClonedBlockObject::create(cx, staticLexical, global));
    if (!lexical)
        return nullptr;
    if (!JSObject::setSingleton(cx, lexical))
        return nullptr;
    return lexical;
}

/* static */ ClonedBlockObject*
ClonedBlockObject::createNonSyntactic(JSContext* cx, HandleObject enclosingStatic,
                                      HandleObject enclosingScope)
{
    MOZ_ASSERT(enclosingStatic->is<StaticNonSyntacticScope>());
    MOZ_ASSERT(!IsSyntacticScope(enclosingScope));

    Rooted<StaticBlockScope*> staticLexical(cx, StaticBlockScope::create(cx));
    if (!staticLexical)
        return nullptr;

    staticLexical->setLocalOffsetToInvalid();
    staticLexical->initEnclosingScope(enclosingStatic);
    Rooted<ClonedBlockObject*> lexical(cx, ClonedBlockObject::create(cx, staticLexical,
                                                                     enclosingScope));
    if (!lexical)
        return nullptr;
    return lexical;
}

/* static */ ClonedBlockObject*
ClonedBlockObject::createHollowForDebug(JSContext* cx, Handle<StaticBlockScope*> block)
{
    MOZ_ASSERT(!block->needsClone());

    // This scope's parent link is never used: the DebugScopeObject that
    // refers to this scope carries its own parent link, which is what
    // Debugger uses to construct the tree of Debugger.Environment objects. So
    // just parent this scope directly to the global lexical scope.
    Rooted<GlobalObject*> global(cx, &block->global());
    RootedObject globalLexical(cx, &global->lexicalScope());
    Rooted<ClonedBlockObject*> obj(cx, create(cx, block, globalLexical));
    if (!obj)
        return nullptr;

    for (unsigned i = 0; i < block->numVariables(); i++)
        obj->setVar(i, MagicValue(JS_OPTIMIZED_OUT), DONT_CHECK_ALIASING);

    return obj;
}

void
ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame)
{
    StaticBlockScope& block = staticBlock();
    for (unsigned i = 0; i < numVariables(); ++i) {
        if (!block.isAliased(i)) {
            Value& val = frame.unaliasedLocal(block.blockIndexToLocalIndex(i));
            setVar(i, val, DONT_CHECK_ALIASING);
        }
    }
}

/* static */ ClonedBlockObject*
ClonedBlockObject::clone(JSContext* cx, Handle<ClonedBlockObject*> clonedBlock)
{
    Rooted<StaticBlockScope*> staticBlock(cx, &clonedBlock->staticBlock());
    MOZ_ASSERT(!staticBlock->isExtensible());
    RootedObject enclosing(cx, &clonedBlock->enclosingScope());

    Rooted<ClonedBlockObject*> copy(cx, create(cx, staticBlock, enclosing));
    if (!copy)
        return nullptr;

    for (uint32_t i = 0, count = staticBlock->numVariables(); i < count; i++)
        copy->setVar(i, clonedBlock->var(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING);

    return copy;
}

Value
ClonedBlockObject::thisValue() const
{
    MOZ_ASSERT(isGlobal() || !isSyntactic());
    Value v = getReservedSlot(THIS_VALUE_SLOT);
    if (v.isObject()) {
        // If `v` is a Window, return the WindowProxy instead. We called
        // GetThisValue (which also does ToWindowProxyIfWindow) when storing
        // the value in THIS_VALUE_SLOT, but it's possible the WindowProxy was
        // attached to the global *after* we set THIS_VALUE_SLOT.
        return ObjectValue(*ToWindowProxyIfWindow(&v.toObject()));
    }
    return v;
}

static_assert(StaticBlockScope::RESERVED_SLOTS == ClonedBlockObject::RESERVED_SLOTS,
              "static block scopes and dynamic block environments share a Class");

const Class ClonedBlockObject::class_ = {
    "Block",
    JSCLASS_HAS_RESERVED_SLOTS(ClonedBlockObject::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS,
    JS_NULL_CLASS_OPS,
    JS_NULL_CLASS_SPEC,
    JS_NULL_CLASS_EXT,
    JS_NULL_OBJECT_OPS
};

template<XDRMode mode>
bool
js::XDRStaticBlockScope(XDRState<mode>* xdr, HandleObject enclosingScope,
                        MutableHandle<StaticBlockScope*> objp)
{
    /* NB: Keep this in sync with CloneStaticBlockScope. */

    JSContext* cx = xdr->cx();

    Rooted<StaticBlockScope*> obj(cx);
    uint32_t count = 0, offset = 0;
    uint8_t extensible = 0;

    if (mode == XDR_ENCODE) {
        obj = objp;
        count = obj->numVariables();
        offset = obj->localOffset();
        extensible = obj->isExtensible() ? 1 : 0;
    }

    if (mode == XDR_DECODE) {
        obj = StaticBlockScope::create(cx);
        if (!obj)
            return false;
        obj->initEnclosingScope(enclosingScope);
        objp.set(obj);
    }

    if (!xdr->codeUint32(&count))
        return false;
    if (!xdr->codeUint32(&offset))
        return false;
    if (!xdr->codeUint8(&extensible))
        return false;

    /*
     * XDR the block object's properties. We know that there are 'count'
     * properties to XDR, stored as id/aliased pairs.  (The empty string as
     * id indicates an int id.)
     */
    if (mode == XDR_DECODE) {
        obj->setLocalOffset(offset);

        for (unsigned i = 0; i < count; i++) {
            RootedAtom atom(cx);
            if (!XDRAtom(xdr, &atom))
                return false;

            RootedId id(cx, atom != cx->runtime()->emptyString
                            ? AtomToId(atom)
                            : INT_TO_JSID(i));

            uint32_t propFlags;
            if (!xdr->codeUint32(&propFlags))
                return false;

            bool readonly = !!(propFlags & 1);

            bool redeclared;
            if (!StaticBlockScope::addVar(cx, obj, id, readonly, i, &redeclared)) {
                MOZ_ASSERT(!redeclared);
                return false;
            }

            bool aliased = !!(propFlags >> 1);
            obj->setAliased(i, aliased);
        }

        if (!extensible) {
            if (!obj->makeNonExtensible(cx))
                return false;
        }
    } else {
        Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
        if (!shapes.growBy(count))
            return false;

        for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront())
            shapes[obj->shapeToIndex(r.front())].set(&r.front());

        RootedShape shape(cx);
        RootedId propid(cx);
        RootedAtom atom(cx);
        for (unsigned i = 0; i < count; i++) {
            shape = shapes[i];
            MOZ_ASSERT(shape->hasDefaultGetter());
            MOZ_ASSERT(obj->shapeToIndex(*shape) == i);

            propid = shape->propid();
            MOZ_ASSERT(JSID_IS_ATOM(propid) || JSID_IS_INT(propid));

            atom = JSID_IS_ATOM(propid)
                   ? JSID_TO_ATOM(propid)
                   : cx->runtime()->emptyString;
            if (!XDRAtom(xdr, &atom))
                return false;

            bool aliased = obj->isAliased(i);
            bool readonly = !shape->writable();
            uint32_t propFlags = (aliased << 1) | readonly;
            if (!xdr->codeUint32(&propFlags))
                return false;
        }
    }
    return true;
}

template bool
js::XDRStaticBlockScope(XDRState<XDR_ENCODE>*, HandleObject, MutableHandle<StaticBlockScope*>);

template bool
js::XDRStaticBlockScope(XDRState<XDR_DECODE>*, HandleObject, MutableHandle<StaticBlockScope*>);

static JSObject*
CloneStaticBlockScope(JSContext* cx, HandleObject enclosingScope, Handle<StaticBlockScope*> srcBlock)
{
    /* NB: Keep this in sync with XDRStaticBlockScope. */

    Rooted<StaticBlockScope*> clone(cx, StaticBlockScope::create(cx));
    if (!clone)
        return nullptr;

    clone->initEnclosingScope(enclosingScope);
    clone->setLocalOffset(srcBlock->localOffset());

    /* Shape::Range is reverse order, so build a list in forward order. */
    Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
    if (!shapes.growBy(srcBlock->numVariables()))
        return nullptr;

    for (Shape::Range<NoGC> r(srcBlock->lastProperty()); !r.empty(); r.popFront())
        shapes[srcBlock->shapeToIndex(r.front())].set(&r.front());

    RootedId id(cx);
    for (Shape* shape : shapes) {
        id = shape->propid();
        unsigned i = srcBlock->shapeToIndex(*shape);

        bool redeclared;
        if (!StaticBlockScope::addVar(cx, clone, id, !shape->writable(), i, &redeclared)) {
            MOZ_ASSERT(!redeclared);
            return nullptr;
        }

        clone->setAliased(i, srcBlock->isAliased(i));
    }

    if (!srcBlock->isExtensible()) {
        if (!clone->makeNonExtensible(cx))
            return nullptr;
    }

    return clone;
}

JSObject*
js::CloneNestedScopeObject(JSContext* cx, HandleObject enclosingScope,
                           Handle<NestedStaticScope*> srcBlock)
{
    if (srcBlock->is<StaticBlockScope>()) {
        Rooted<StaticBlockScope*> blockScope(cx, &srcBlock->as<StaticBlockScope>());
        return CloneStaticBlockScope(cx, enclosingScope, blockScope);
    } else {
        Rooted<StaticWithScope*> withScope(cx, &srcBlock->as<StaticWithScope>());
        return CloneStaticWithScope(cx, enclosingScope, withScope);
    }
}

/* static */ RuntimeLexicalErrorObject*
RuntimeLexicalErrorObject::create(JSContext* cx, HandleObject enclosing, unsigned errorNumber)
{
    RuntimeLexicalErrorObject* obj =
        NewObjectWithNullTaggedProto<RuntimeLexicalErrorObject>(cx, GenericObject,
                                                                BaseShape::DELEGATE);
    if (!obj)
        return nullptr;
    obj->setEnclosingScope(enclosing);
    obj->setReservedSlot(ERROR_SLOT, Int32Value(int32_t(errorNumber)));
    return obj;
}

static void
ReportRuntimeLexicalErrorId(JSContext* cx, unsigned errorNumber, HandleId id)
{
    if (JSID_IS_ATOM(id)) {
        RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
        ReportRuntimeLexicalError(cx, errorNumber, name);
        return;
    }
    MOZ_CRASH("RuntimeLexicalErrorObject should only be used with property names");
}

static bool
lexicalError_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
                            MutableHandleObject objp, MutableHandleShape propp)
{
    ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
    return false;
}

static bool
lexicalError_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
    ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
    return false;
}

static bool
lexicalError_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
                         MutableHandleValue vp)
{
    ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
    return false;
}

static bool
lexicalError_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                         HandleValue receiver, ObjectOpResult& result)
{
    ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
    return false;
}

static bool
lexicalError_GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
                                      MutableHandle<PropertyDescriptor> desc)
{
    ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
    return false;
}

static bool
lexicalError_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
{
    ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
    return false;
}

static const ObjectOps RuntimeLexicalErrorObjectObjectOps = {
    lexicalError_LookupProperty,
    nullptr,             /* defineProperty */
    lexicalError_HasProperty,
    lexicalError_GetProperty,
    lexicalError_SetProperty,
    lexicalError_GetOwnPropertyDescriptor,
    lexicalError_DeleteProperty,
    nullptr, nullptr,    /* watch/unwatch */
    nullptr,             /* getElements */
    nullptr,             /* enumerate (native enumeration of target doesn't work) */
    nullptr,             /* this */
};

const Class RuntimeLexicalErrorObject::class_ = {
    "RuntimeLexicalError",
    JSCLASS_HAS_RESERVED_SLOTS(RuntimeLexicalErrorObject::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS,
    JS_NULL_CLASS_OPS,
    JS_NULL_CLASS_SPEC,
    JS_NULL_CLASS_EXT,
    &RuntimeLexicalErrorObjectObjectOps
};

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

// Any name atom for a function which will be added as a DeclEnv object to the
// scope chain above call objects for fun.
static inline JSAtom*
CallObjectLambdaName(JSFunction& fun)
{
    return fun.isNamedLambda() ? fun.name() : nullptr;
}

ScopeIter::ScopeIter(JSContext* cx, const ScopeIter& si
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
  : ssi_(cx, si.ssi_),
    scope_(cx, si.scope_),
    frame_(si.frame_)
{
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}

ScopeIter::ScopeIter(JSContext* cx, JSObject* scope, JSObject* staticScope
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
  : ssi_(cx, staticScope),
    scope_(cx, scope),
    frame_(NullFramePtr())
{
    settle();
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}

ScopeIter::ScopeIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
  : ssi_(cx, frame.script()->innermostStaticScope(pc)),
    scope_(cx, frame.scopeChain()),
    frame_(frame)
{
    assertSameCompartment(cx, frame);
    settle();
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}

void
ScopeIter::incrementStaticScopeIter()
{
    // If settled on a non-syntactic static scope, only increment ssi_ once
    // we've iterated through all the non-syntactic dynamic ScopeObjects.
    if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) {
        if (!hasNonSyntacticScopeObject())
            ssi_++;
    } else {
        ssi_++;
    }

    // For named lambdas, DeclEnvObject scopes are always attached to their
    // CallObjects. Skip it here, as they are special cased in users of
    // ScopeIter.
    if (!ssi_.done() && ssi_.type() == StaticScopeIter<CanGC>::NamedLambda)
        ssi_++;
}

void
ScopeIter::settle()
{
    // Check for trying to iterate a function frame before the prologue has
    // created the CallObject, in which case we have to skip.
    if (frame_ && frame_.isFunctionFrame() && frame_.callee()->needsCallObject() &&
        !frame_.hasCallObj())
    {
        // At the very start of a script that starts with a lexical block, the
        // static scope will be that block. Skip it if this is the case.
        if (ssi_.type() == StaticScopeIter<CanGC>::Block)
            incrementStaticScopeIter();

        // We should now be at the function scope, so since we have no
        // CallObject, skip that too.
        MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Function);
        incrementStaticScopeIter();
    }

    // Check for trying to iterate a strict eval frame before the prologue has
    // created the CallObject.
    if (frame_ && frame_.isStrictEvalFrame() && !frame_.hasCallObj() && !ssi_.done()) {
        // Eval frames always have their own lexical scope. Analogous to the
        // function frame case above, if the script starts with a lexical
        // block, the SSI could see 2 block scopes here. So skip between 1-2
        // static block scopes here.
        if (ssi_.type() == StaticScopeIter<CanGC>::Block)
            incrementStaticScopeIter();
        if (ssi_.type() == StaticScopeIter<CanGC>::Block)
            incrementStaticScopeIter();
        MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Eval);
        MOZ_ASSERT(maybeStaticScope() == frame_.script()->enclosingStaticScope());
        incrementStaticScopeIter();
        frame_ = NullFramePtr();
    }

    // Check if we have left the extent of the initial frame after we've
    // settled on a static scope.
    if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope()))
        frame_ = NullFramePtr();

#ifdef DEBUG
    if (!ssi_.done() && hasAnyScopeObject()) {
        switch (ssi_.type()) {
          case StaticScopeIter<CanGC>::Module:
            MOZ_ASSERT(scope_->as<ModuleEnvironmentObject>().module() == ssi_.module());
            break;
          case StaticScopeIter<CanGC>::Function:
            MOZ_ASSERT(scope_->as<CallObject>().callee().nonLazyScript() == ssi_.funScript());
            break;
          case StaticScopeIter<CanGC>::Block:
            MOZ_ASSERT(scope_->as<ClonedBlockObject>().staticBlock() == staticBlock());
            break;
          case StaticScopeIter<CanGC>::With:
            MOZ_ASSERT(scope_->as<DynamicWithObject>().staticScope() == &staticWith());
            break;
          case StaticScopeIter<CanGC>::Eval:
            MOZ_ASSERT(scope_->as<CallObject>().isForEval());
            break;
          case StaticScopeIter<CanGC>::NonSyntactic:
            MOZ_ASSERT(!IsSyntacticScope(scope_));
            break;
          case StaticScopeIter<CanGC>::NamedLambda:
            MOZ_CRASH("named lambda static scopes should have been skipped");
        }
    }
#endif
}

ScopeIter&
ScopeIter::operator++()
{
    if (hasAnyScopeObject()) {
        scope_ = &scope_->as<ScopeObject>().enclosingScope();
        if (scope_->is<DeclEnvObject>())
            scope_ = &scope_->as<DeclEnvObject>().enclosingScope();
    }

    incrementStaticScopeIter();
    settle();

    return *this;
}

ScopeIter::Type
ScopeIter::type() const
{
    MOZ_ASSERT(!done());

    switch (ssi_.type()) {
      case StaticScopeIter<CanGC>::Module:
        return Module;
      case StaticScopeIter<CanGC>::Function:
        return Call;
      case StaticScopeIter<CanGC>::Block:
        return Block;
      case StaticScopeIter<CanGC>::With:
        return With;
      case StaticScopeIter<CanGC>::Eval:
        return Eval;
      case StaticScopeIter<CanGC>::NonSyntactic:
        return NonSyntactic;
      case StaticScopeIter<CanGC>::NamedLambda:
        MOZ_CRASH("named lambda static scopes should have been skipped");
      default:
        MOZ_CRASH("bad SSI type");
    }
}

ScopeObject&
ScopeIter::scope() const
{
    MOZ_ASSERT(hasAnyScopeObject());
    return scope_->as<ScopeObject>();
}

JSObject*
ScopeIter::maybeStaticScope() const
{
    if (ssi_.done())
        return nullptr;

    switch (ssi_.type()) {
      case StaticScopeIter<CanGC>::Function:
        return &fun();
      case StaticScopeIter<CanGC>::Module:
        return &module();
      case StaticScopeIter<CanGC>::Block:
        return &staticBlock();
      case StaticScopeIter<CanGC>::With:
        return &staticWith();
      case StaticScopeIter<CanGC>::Eval:
        return &staticEval();
      case StaticScopeIter<CanGC>::NonSyntactic:
        return &staticNonSyntactic();
      case StaticScopeIter<CanGC>::NamedLambda:
        MOZ_CRASH("named lambda static scopes should have been skipped");
      default:
        MOZ_CRASH("bad SSI type");
    }
}

/* static */ HashNumber
MissingScopeKey::hash(MissingScopeKey sk)
{
    return size_t(sk.frame_.raw()) ^ size_t(sk.staticScope_);
}

/* static */ bool
MissingScopeKey::match(MissingScopeKey sk1, MissingScopeKey sk2)
{
    return sk1.frame_ == sk2.frame_ && sk1.staticScope_ == sk2.staticScope_;
}

bool
LiveScopeVal::needsSweep()
{
    if (staticScope_)
        MOZ_ALWAYS_FALSE(IsAboutToBeFinalized(&staticScope_));
    return false;
}

// Live ScopeIter values may be added to DebugScopes::liveScopes, as
// LiveScopeVal instances.  They need to have write barriers when they are added
// to the hash table, but no barriers when rehashing inside GC.  It's a nasty
// hack, but the important thing is that LiveScopeVal and MissingScopeKey need to
// alias each other.
void
LiveScopeVal::staticAsserts()
{
    static_assert(sizeof(LiveScopeVal) == sizeof(MissingScopeKey),
                  "LiveScopeVal must be same size of MissingScopeKey");
    static_assert(offsetof(LiveScopeVal, staticScope_) == offsetof(MissingScopeKey, staticScope_),
                  "LiveScopeVal.staticScope_ must alias MissingScopeKey.staticScope_");
}

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

namespace {

static void
ReportOptimizedOut(JSContext* cx, HandleId id)
{
    JSAutoByteString printable;
    if (ValueToPrintable(cx, IdToValue(id), &printable)) {
        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT,
                             printable.ptr());
    }
}

/*
 * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a
 * custom handler (rather than trying to reuse js::Wrapper) gives us several
 * important abilities:
 *  - We want to pass the ScopeObject as the receiver to forwarded scope
 *    property ops on aliased variables so that Call/Block/With ops do not all
 *    require a 'normalization' step.
 *  - The debug scope proxy can directly manipulate the stack frame to allow
 *    the debugger to read/write args/locals that were otherwise unaliased.
 *  - The debug scope proxy can store unaliased variables after the stack frame
 *    is popped so that they may still be read/written by the debugger.
 *  - The engine has made certain assumptions about the possible reads/writes
 *    in a scope. DebugScopeProxy allows us to prevent the debugger from
 *    breaking those assumptions.
 *  - The engine makes optimizations that are observable to the debugger. The
 *    proxy can either hide these optimizations or make the situation more
 *    clear to the debugger. An example is 'arguments'.
 */
class DebugScopeProxy : public BaseProxyHandler
{
    enum Action { SET, GET };

    enum AccessResult {
        ACCESS_UNALIASED,
        ACCESS_GENERIC,
        ACCESS_LOST
    };

    /*
     * This function handles access to unaliased locals/formals. Since they are
     * unaliased, the values of these variables are not stored in the slots of
     * the normal Call/ClonedBlockObject scope objects and thus must be
     * recovered from somewhere else:
     *  + if the invocation for which the scope was created is still executing,
     *    there is a JS frame live on the stack holding the values;
     *  + if the invocation for which the scope was created finished executing:
     *     - and there was a DebugScopeObject associated with scope, then the
     *       DebugScopes::onPop(Call|Block) handler copied out the unaliased
     *       variables:
     *        . for block scopes, the unaliased values were copied directly
     *          into the block object, since there is a slot allocated for every
     *          block binding, regardless of whether it is aliased;
     *        . for function scopes, a dense array is created in onPopCall to hold
     *          the unaliased values and attached to the DebugScopeObject;
     *     - and there was not a DebugScopeObject yet associated with the
     *       scope, then the unaliased values are lost and not recoverable.
     *
     * Callers should check accessResult for non-failure results:
     *  - ACCESS_UNALIASED if the access was unaliased and completed
     *  - ACCESS_GENERIC   if the access was aliased or the property not found
     *  - ACCESS_LOST      if the value has been lost to the debugger
     */
    bool handleUnaliasedAccess(JSContext* cx, Handle<DebugScopeObject*> debugScope,
                               Handle<ScopeObject*> scope, HandleId id, Action action,
                               MutableHandleValue vp, AccessResult* accessResult) const
    {
        MOZ_ASSERT(&debugScope->scope() == scope);
        MOZ_ASSERT_IF(action == SET, !debugScope->isOptimizedOut());
        *accessResult = ACCESS_GENERIC;
        LiveScopeVal* maybeLiveScope = DebugScopes::hasLiveScope(*scope);

        if (scope->is<ModuleEnvironmentObject>()) {
            /* Everything is aliased and stored in the environment object. */
            return true;
        }

        /* Handle unaliased formals, vars, lets, and consts at function scope. */
        if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
            CallObject& callobj = scope->as<CallObject>();
            RootedScript script(cx, callobj.callee().getOrCreateScript(cx));
            if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx))
                return false;

            Bindings& bindings = script->bindings;
            BindingIter bi(script);
            while (bi && NameToId(bi->name()) != id)
                bi++;
            if (!bi)
                return true;

            if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) {
                if (script->bindingIsAliased(bi))
                    return true;

                uint32_t i = bi.frameIndex();
                if (maybeLiveScope) {
                    AbstractFramePtr frame = maybeLiveScope->frame();
                    if (action == GET)
                        vp.set(frame.unaliasedLocal(i));
                    else
                        frame.unaliasedLocal(i) = vp;
                } else if (NativeObject* snapshot = debugScope->maybeSnapshot()) {
                    if (action == GET)
                        vp.set(snapshot->getDenseElement(bindings.numArgs() + i));
                    else
                        snapshot->setDenseElement(bindings.numArgs() + i, vp);
                } else {
                    /* The unaliased value has been lost to the debugger. */
                    if (action == GET) {
                        *accessResult = ACCESS_LOST;
                        return true;
                    }
                }
            } else {
                MOZ_ASSERT(bi->kind() == Binding::ARGUMENT);
                unsigned i = bi.argIndex();
                if (script->formalIsAliased(i))
                    return true;

                if (maybeLiveScope) {
                    AbstractFramePtr frame = maybeLiveScope->frame();
                    if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
                        if (action == GET)
                            vp.set(frame.argsObj().arg(i));
                        else
                            frame.argsObj().setArg(i, vp);
                    } else {
                        if (action == GET)
                            vp.set(frame.unaliasedFormal(i, DONT_CHECK_ALIASING));
                        else
                            frame.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp;
                    }
                } else if (NativeObject* snapshot = debugScope->maybeSnapshot()) {
                    if (action == GET)
                        vp.set(snapshot->getDenseElement(i));
                    else
                        snapshot->setDenseElement(i, vp);
                } else {
                    /* The unaliased value has been lost to the debugger. */
                    if (action == GET) {
                        *accessResult = ACCESS_LOST;
                        return true;
                    }
                }

                if (action == SET)
                    TypeScript::SetArgument(cx, script, i, vp);
            }

            // It is possible that an optimized out value flows to this
            // location due to Debugger.Frame.prototype.eval operating on a
            // live bailed-out Baseline frame. In that case, treat the access
            // as lost.
            if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT)
                *accessResult = ACCESS_LOST;
            else
                *accessResult = ACCESS_UNALIASED;

            return true;
        }

        /* Handle unaliased let and catch bindings at block scope. */
        if (scope->is<ClonedBlockObject>()) {
            Rooted<ClonedBlockObject*> block(cx, &scope->as<ClonedBlockObject>());
            Shape* shape = block->lastProperty()->search(cx, id);
            if (!shape)
                return true;

            // Currently consider all global and non-syntactic top-level lexical
            // bindings to be aliased.
            if (block->isExtensible()) {
                MOZ_ASSERT(IsGlobalLexicalScope(block) || !IsSyntacticScope(block));
                return true;
            }

            unsigned i = block->staticBlock().shapeToIndex(*shape);
            if (block->staticBlock().isAliased(i))
                return true;

            if (maybeLiveScope) {
                AbstractFramePtr frame = maybeLiveScope->frame();
                uint32_t local = block->staticBlock().blockIndexToLocalIndex(i);
                MOZ_ASSERT(local < frame.script()->nfixed());
                if (action == GET)
                    vp.set(frame.unaliasedLocal(local));
                else
                    frame.unaliasedLocal(local) = vp;
            } else {
                if (action == GET) {
                    // A ClonedBlockObject whose static block does not need
                    // cloning is a "hollow" block object reflected for
                    // missing block scopes. Their slot values are lost.
                    if (!block->staticBlock().needsClone()) {
                        *accessResult = ACCESS_LOST;
                        return true;
                    }
                    vp.set(block->var(i, DONT_CHECK_ALIASING));
                } else {
                    block->setVar(i, vp, DONT_CHECK_ALIASING);
                }
            }

            // See comment above in analogous CallObject case.
            if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT)
                *accessResult = ACCESS_LOST;
            else
                *accessResult = ACCESS_UNALIASED;

            return true;
        }

        /* The rest of the internal scopes do not have unaliased vars. */
        MOZ_ASSERT(!IsSyntacticScope(scope) ||
                   scope->is<DeclEnvObject>() ||
                   scope->is<DynamicWithObject>() ||
                   scope->as<CallObject>().isForEval());
        return true;
    }

    static bool isArguments(JSContext* cx, jsid id)
    {
        return id == NameToId(cx->names().arguments);
    }
    static bool isThis(JSContext* cx, jsid id)
    {
        return id == NameToId(cx->names().dotThis);
    }

    static bool isFunctionScope(const JSObject& scope)
    {
        return scope.is<CallObject>() && !scope.as<CallObject>().isForEval();
    }

    /*
     * In theory, every function scope contains an 'arguments' bindings.
     * However, the engine only adds a binding if 'arguments' is used in the
     * function body. Thus, from the debugger's perspective, 'arguments' may be
     * missing from the list of bindings.
     */
    static bool isMissingArgumentsBinding(ScopeObject& scope)
    {
        return isFunctionScope(scope) &&
               !scope.as<CallObject>().callee().nonLazyScript()->argumentsHasVarBinding();
    }

    /*
     * Similar to 'arguments' above, we don't add a 'this' binding to functions
     * if it's not used.
     */
    static bool isMissingThisBinding(ScopeObject& scope)
    {
        return isFunctionScopeWithThis(scope) &&
               !scope.as<CallObject>().callee().nonLazyScript()->functionHasThisBinding();
    }

    /*
     * This function checks if an arguments object needs to be created when
     * the debugger requests 'arguments' for a function scope where the
     * arguments object has been optimized away (either because the binding is
     * missing altogether or because !ScriptAnalysis::needsArgsObj).
     */
    static bool isMissingArguments(JSContext* cx, jsid id, ScopeObject& scope)
    {
        return isArguments(cx, id) && isFunctionScope(scope) &&
               !scope.as<CallObject>().callee().nonLazyScript()->needsArgsObj();
    }
    static bool isMissingThis(JSContext* cx, jsid id, ScopeObject& scope)
    {
        return isThis(cx, id) && isMissingThisBinding(scope);
    }

    /*
     * Check if the value is the magic value JS_OPTIMIZED_ARGUMENTS. The
     * arguments analysis may have optimized out the 'arguments', and this
     * magic value could have propagated to other local slots. e.g.,
     *
     *   function f() { var a = arguments; h(); }
     *   function h() { evalInFrame(1, "a.push(0)"); }
     *
     * where evalInFrame(N, str) means to evaluate str N frames up.
     *
     * In this case we don't know we need to recover a missing arguments
     * object until after we've performed the property get.
     */
    static bool isMagicMissingArgumentsValue(JSContext* cx, ScopeObject& scope, HandleValue v)
    {
        bool isMagic = v.isMagic() && v.whyMagic() == JS_OPTIMIZED_ARGUMENTS;

#ifdef DEBUG
        // The |scope| object here is not limited to CallObjects but may also
        // be block scopes in case of the following:
        //
        //   function f() { { let a = arguments; } }
        //
        // We need to check that |scope|'s static scope's nearest function
        // scope has an 'arguments' var binding. The dynamic scope chain is
        // not sufficient: |f| above will not have a CallObject because there
        // are no aliased body-level bindings.
        if (isMagic) {
            JSFunction* callee = nullptr;
            if (isFunctionScope(scope)) {
                callee = &scope.as<CallObject>().callee();
            } else {
                // We will never have a DynamicWithObject here because no
                // binding accesses on with scopes are unaliased.
                for (StaticScopeIter<NoGC> ssi(&scope.as<ClonedBlockObject>().staticBlock());
                     !ssi.done();
                     ssi++)
                {
                    if (ssi.type() == StaticScopeIter<NoGC>::Function) {
                        callee = &ssi.fun();
                        break;
                    }
                }
            }
            MOZ_ASSERT(callee && callee->nonLazyScript()->argumentsHasVarBinding());
        }
#endif

        return isMagic;
    }

    /*
     * If the value of |this| is requested before the this-binding has been
     * initialized by JSOP_FUNCTIONTHIS, the this-binding will be |undefined|.
     * In that case, we have to call createMissingThis to initialize the
     * this-binding.
     *
     * Note that an |undefined| this-binding is perfectly valid in strict-mode
     * code, but that's fine: createMissingThis will do the right thing in that
     * case.
     */
    static bool isMaybeUninitializedThisValue(JSContext* cx, jsid id, Value v)
    {
        return isThis(cx, id) && v.isUndefined();
    }

    /*
     * Create a missing arguments object. If the function returns true but
     * argsObj is null, it means the scope is dead.
     */
    static bool createMissingArguments(JSContext* cx, ScopeObject& scope,
                                       MutableHandleArgumentsObject argsObj)
    {
        argsObj.set(nullptr);

        LiveScopeVal* maybeScope = DebugScopes::hasLiveScope(scope);
        if (!maybeScope)
            return true;

        argsObj.set(ArgumentsObject::createUnexpected(cx, maybeScope->frame()));
        return !!argsObj;
    }

    /*
     * Create a missing this Value. If the function returns true but
     * *success is false, it means the scope is dead.
     */
    static bool createMissingThis(JSContext* cx, ScopeObject& scope,
                                  MutableHandleValue thisv, bool* success)
    {
        *success = false;

        LiveScopeVal* maybeScope = DebugScopes::hasLiveScope(scope);
        if (!maybeScope)
            return true;

        if (!GetFunctionThis(cx, maybeScope->frame(), thisv))
            return false;

        // Update the this-argument to avoid boxing primitive |this| more
        // than once.
        maybeScope->frame().thisArgument() = thisv;
        *success = true;
        return true;
    }

  public:
    static const char family;
    static const DebugScopeProxy singleton;

    MOZ_CONSTEXPR DebugScopeProxy() : BaseProxyHandler(&family) {}

    static bool isFunctionScopeWithThis(const JSObject& scope)
    {
        // All functions except arrows and generator expression lambdas should
        // have their own this binding.
        return isFunctionScope(scope) && !scope.as<CallObject>().callee().hasLexicalThis();
    }

    bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
                                MutableHandleObject protop) const override
    {
        MOZ_CRASH("shouldn't be possible to access the prototype chain of a DebugScopeProxy");
    }

    bool preventExtensions(JSContext* cx, HandleObject proxy,
                           ObjectOpResult& result) const override
    {
        // always [[Extensible]], can't be made non-[[Extensible]], like most
        // proxies
        return result.fail(JSMSG_CANT_CHANGE_EXTENSIBILITY);
    }

    bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override
    {
        // See above.
        *extensible = true;
        return true;
    }

    bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                               MutableHandle<PropertyDescriptor> desc) const override
    {
        return getOwnPropertyDescriptor(cx, proxy, id, desc);
    }

    bool getMissingArgumentsPropertyDescriptor(JSContext* cx,
                                               Handle<DebugScopeObject*> debugScope,
                                               ScopeObject& scope,
                                               MutableHandle<PropertyDescriptor> desc) const
    {
        RootedArgumentsObject argsObj(cx);
        if (!createMissingArguments(cx, scope, &argsObj))
            return false;

        if (!argsObj) {
            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
                                 "Debugger scope");
            return false;
        }

        desc.object().set(debugScope);
        desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
        desc.value().setObject(*argsObj);
        desc.setGetter(nullptr);
        desc.setSetter(nullptr);
        return true;
    }
    bool getMissingThisPropertyDescriptor(JSContext* cx,
                                          Handle<DebugScopeObject*> debugScope,
                                          ScopeObject& scope,
                                          MutableHandle<PropertyDescriptor> desc) const
    {
        RootedValue thisv(cx);
        bool success;
        if (!createMissingThis(cx, scope, &thisv, &success))
            return false;

        if (!success) {
            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
                                 "Debugger scope");
            return false;
        }

        desc.object().set(debugScope);
        desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
        desc.value().set(thisv);
        desc.setGetter(nullptr);
        desc.setSetter(nullptr);
        return true;
    }

    bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
                                  MutableHandle<PropertyDescriptor> desc) const override
    {
        Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
        Rooted<ScopeObject*> scope(cx, &debugScope->scope());

        if (isMissingArguments(cx, id, *scope))
            return getMissingArgumentsPropertyDescriptor(cx, debugScope, *scope, desc);

        if (isMissingThis(cx, id, *scope))
            return getMissingThisPropertyDescriptor(cx, debugScope, *scope, desc);

        RootedValue v(cx);
        AccessResult access;
        if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, &v, &access))
            return false;

        switch (access) {
          case ACCESS_UNALIASED:
            if (isMagicMissingArgumentsValue(cx, *scope, v))
                return getMissingArgumentsPropertyDescriptor(cx, debugScope, *scope, desc);
            desc.object().set(debugScope);
            desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
            desc.value().set(v);
            desc.setGetter(nullptr);
            desc.setSetter(nullptr);
            return true;
          case ACCESS_GENERIC:
            return JS_GetOwnPropertyDescriptorById(cx, scope, id, desc);
          case ACCESS_LOST:
            ReportOptimizedOut(cx, id);
            return false;
          default:
            MOZ_CRASH("bad AccessResult");
        }
    }

    bool getMissingArguments(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const
    {
        RootedArgumentsObject argsObj(cx);
        if (!createMissingArguments(cx, scope, &argsObj))
            return false;

        if (!argsObj) {
            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
                                 "Debugger scope");
            return false;
        }

        vp.setObject(*argsObj);
        return true;
    }

    bool getMissingThis(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const
    {
        RootedValue thisv(cx);
        bool success;
        if (!createMissingThis(cx, scope, &thisv, &success))
            return false;

        if (!success) {
            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
                                 "Debugger scope");
            return false;
        }

        vp.set(thisv);
        return true;
    }

    bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
             MutableHandleValue vp) const override
    {
        Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
        Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());

        if (isMissingArguments(cx, id, *scope))
            return getMissingArguments(cx, *scope, vp);

        if (isMissingThis(cx, id, *scope))
            return getMissingThis(cx, *scope, vp);

        AccessResult access;
        if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access))
            return false;

        switch (access) {
          case ACCESS_UNALIASED:
            if (isMagicMissingArgumentsValue(cx, *scope, vp))
                return getMissingArguments(cx, *scope, vp);
            if (isMaybeUninitializedThisValue(cx, id, vp))
                return getMissingThis(cx, *scope, vp);
            return true;
          case ACCESS_GENERIC:
            if (!GetProperty(cx, scope, scope, id, vp))
                return false;
            if (isMaybeUninitializedThisValue(cx, id, vp))
                return getMissingThis(cx, *scope, vp);
            return true;
          case ACCESS_LOST:
            ReportOptimizedOut(cx, id);
            return false;
          default:
            MOZ_CRASH("bad AccessResult");
        }
    }

    bool getMissingArgumentsMaybeSentinelValue(JSContext* cx, ScopeObject& scope,
                                               MutableHandleValue vp) const
    {
        RootedArgumentsObject argsObj(cx);
        if (!createMissingArguments(cx, scope, &argsObj))
            return false;
        vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_OPTIMIZED_ARGUMENTS));
        return true;
    }

    bool getMissingThisMaybeSentinelValue(JSContext* cx, ScopeObject& scope,
                                          MutableHandleValue vp) const
    {
        RootedValue thisv(cx);
        bool success;
        if (!createMissingThis(cx, scope, &thisv, &success))
            return false;
        vp.set(success ? thisv : MagicValue(JS_OPTIMIZED_OUT));
        return true;
    }

    /*
     * Like 'get', but returns sentinel values instead of throwing on
     * exceptional cases.
     */
    bool getMaybeSentinelValue(JSContext* cx, Handle<DebugScopeObject*> debugScope, HandleId id,
                               MutableHandleValue vp) const
    {
        Rooted<ScopeObject*> scope(cx, &debugScope->scope());

        if (isMissingArguments(cx, id, *scope))
            return getMissingArgumentsMaybeSentinelValue(cx, *scope, vp);
        if (isMissingThis(cx, id, *scope))
            return getMissingThisMaybeSentinelValue(cx, *scope, vp);

        AccessResult access;
        if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access))
            return false;

        switch (access) {
          case ACCESS_UNALIASED:
            if (isMagicMissingArgumentsValue(cx, *scope, vp))
                return getMissingArgumentsMaybeSentinelValue(cx, *scope, vp);
            if (isMaybeUninitializedThisValue(cx, id, vp))
                return getMissingThisMaybeSentinelValue(cx, *scope, vp);
            return true;
          case ACCESS_GENERIC:
            if (!GetProperty(cx, scope, scope, id, vp))
                return false;
            if (isMaybeUninitializedThisValue(cx, id, vp))
                return getMissingThisMaybeSentinelValue(cx, *scope, vp);
            return true;
          case ACCESS_LOST:
            vp.setMagic(JS_OPTIMIZED_OUT);
            return true;
          default:
            MOZ_CRASH("bad AccessResult");
        }
    }

    bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
             ObjectOpResult& result) const override
    {
        Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
        Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());

        if (debugScope->isOptimizedOut())
            return Throw(cx, id, JSMSG_DEBUG_CANT_SET_OPT_ENV);

        AccessResult access;
        RootedValue valCopy(cx, v);
        if (!handleUnaliasedAccess(cx, debugScope, scope, id, SET, &valCopy, &access))
            return false;

        switch (access) {
          case ACCESS_UNALIASED:
            return result.succeed();
          case ACCESS_GENERIC:
            {
                RootedValue scopeVal(cx, ObjectValue(*scope));
                return SetProperty(cx, scope, id, v, scopeVal, result);
            }
          default:
            MOZ_CRASH("bad AccessResult");
        }
    }

    bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
                        Handle<PropertyDescriptor> desc,
                        ObjectOpResult& result) const override
    {
        Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());

        bool found;
        if (!has(cx, proxy, id, &found))
            return false;
        if (found)
            return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP);

        return JS_DefinePropertyById(cx, scope, id, desc, result);
    }

    bool ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const override
    {
        Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());

        if (isMissingArgumentsBinding(*scope)) {
            if (!props.append(NameToId(cx->names().arguments)))
                return false;
        }
        if (isMissingThisBinding(*scope)) {
            if (!props.append(NameToId(cx->names().dotThis)))
                return false;
        }

        // DynamicWithObject isn't a very good proxy.  It doesn't have a
        // JSNewEnumerateOp implementation, because if it just delegated to the
        // target object, the object would indicate that native enumeration is
        // the thing to do, but native enumeration over the DynamicWithObject
        // wrapper yields no properties.  So instead here we hack around the
        // issue: punch a hole through to the with object target, then manually
        // examine @@unscopables.
        bool isWith = scope->is<DynamicWithObject>();
        Rooted<JSObject*> target(cx, (isWith ? &scope->as<DynamicWithObject>().object() : scope));
        if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props))
            return false;

        if (isWith) {
            size_t j = 0;
            for (size_t i = 0; i < props.length(); i++) {
                bool inScope;
                if (!CheckUnscopables(cx, scope, props[i], &inScope))
                    return false;
                if (inScope)
                    props[j++].set(props[i]);
            }
            if (!props.resize(j))
                return false;
        }

        /*
         * Function scopes are optimized to not contain unaliased variables so
         * they must be manually appended here.
         */
        if (isFunctionScope(*scope)) {
            RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript());
            for (BindingIter bi(script); bi; bi++) {
                if (!bi->aliased() && !props.append(NameToId(bi->name())))
                    return false;
            }
        }

        return true;
    }

    bool has(JSContext* cx, HandleObject proxy, HandleId id_, bool* bp) const override
    {
        RootedId id(cx, id_);
        ScopeObject& scopeObj = proxy->as<DebugScopeObject>().scope();

        if (isArguments(cx, id) && isFunctionScope(scopeObj)) {
            *bp = true;
            return true;
        }

        // Be careful not to look up '.this' as a normal binding below, it will
        // assert in with_HasProperty.
        if (isThis(cx, id)) {
            *bp = isFunctionScopeWithThis(scopeObj);
            return true;
        }

        bool found;
        RootedObject scope(cx, &scopeObj);
        if (!JS_HasPropertyById(cx, scope, id, &found))
            return false;

        /*
         * Function scopes are optimized to not contain unaliased variables so
         * a manual search is necessary.
         */
        if (!found && isFunctionScope(*scope)) {
            RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript());
            for (BindingIter bi(script); bi; bi++) {
                if (!bi->aliased() && NameToId(bi->name()) == id) {
                    found = true;
                    break;
                }
            }
        }

        *bp = found;
        return true;
    }

    bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
                 ObjectOpResult& result) const override
    {
        return result.fail(JSMSG_CANT_DELETE);
    }
};

} /* anonymous namespace */

template<>
bool
JSObject::is<js::DebugScopeObject>() const
{
    return IsDerivedProxyObject(this, &DebugScopeProxy::singleton);
}

const char DebugScopeProxy::family = 0;
const DebugScopeProxy DebugScopeProxy::singleton;

/* static */ DebugScopeObject*
DebugScopeObject::create(JSContext* cx, ScopeObject& scope, HandleObject enclosing)
{
    MOZ_ASSERT(scope.compartment() == cx->compartment());
    MOZ_ASSERT(!enclosing->is<ScopeObject>());

    RootedValue priv(cx, ObjectValue(scope));
    JSObject* obj = NewProxyObject(cx, &DebugScopeProxy::singleton, priv,
                                   nullptr /* proto */);
    if (!obj)
        return nullptr;

    DebugScopeObject* debugScope = &obj->as<DebugScopeObject>();
    debugScope->setExtra(ENCLOSING_EXTRA, ObjectValue(*enclosing));
    debugScope->setExtra(SNAPSHOT_EXTRA, NullValue());

    return debugScope;
}

ScopeObject&
DebugScopeObject::scope() const
{
    return target()->as<ScopeObject>();
}

JSObject&
DebugScopeObject::enclosingScope() const
{
    return extra(ENCLOSING_EXTRA).toObject();
}

ArrayObject*
DebugScopeObject::maybeSnapshot() const
{
    MOZ_ASSERT(!scope().as<CallObject>().isForEval());
    JSObject* obj = extra(SNAPSHOT_EXTRA).toObjectOrNull();
    return obj ? &obj->as<ArrayObject>() : nullptr;
}

void
DebugScopeObject::initSnapshot(ArrayObject& o)
{
    MOZ_ASSERT(maybeSnapshot() == nullptr);
    setExtra(SNAPSHOT_EXTRA, ObjectValue(o));
}

bool
DebugScopeObject::isForDeclarative() const
{
    ScopeObject& s = scope();
    return s.is<LexicalScopeBase>() || s.is<ClonedBlockObject>() || s.is<DeclEnvObject>();
}

bool
DebugScopeObject::getMaybeSentinelValue(JSContext* cx, HandleId id, MutableHandleValue vp)
{
    Rooted<DebugScopeObject*> self(cx, this);
    return DebugScopeProxy::singleton.getMaybeSentinelValue(cx, self, id, vp);
}

bool
DebugScopeObject::isFunctionScopeWithThis()
{
    return DebugScopeProxy::isFunctionScopeWithThis(scope());
}

bool
DebugScopeObject::isOptimizedOut() const
{
    ScopeObject& s = scope();

    if (DebugScopes::hasLiveScope(s))
        return false;

    if (s.is<ClonedBlockObject>())
        return !s.as<ClonedBlockObject>().staticBlock().needsClone();

    if (s.is<CallObject>()) {
        return !s.as<CallObject>().isForEval() &&
               !s.as<CallObject>().callee().needsCallObject() &&
               !maybeSnapshot();
    }

    return false;
}

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

DebugScopes::DebugScopes(JSContext* cx)
 : proxiedScopes(cx),
   missingScopes(cx->runtime()),
   liveScopes(cx->runtime())
{}

DebugScopes::~DebugScopes()
{
    MOZ_ASSERT_IF(missingScopes.initialized(), missingScopes.empty());
}

bool
DebugScopes::init()
{
    return proxiedScopes.init() && missingScopes.init() && liveScopes.init();
}

void
DebugScopes::mark(JSTracer* trc)
{
    proxiedScopes.trace(trc);
}

void
DebugScopes::sweep(JSRuntime* rt)
{
    /*
     * missingScopes points to debug scopes weakly so that debug scopes can be
     * released more eagerly.
     */
    for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) {
        if (IsAboutToBeFinalized(&e.front().value())) {
            /*
             * Note that onPopCall and onPopBlock rely on missingScopes to find
             * scope objects that we synthesized for the debugger's sake, and
             * clean up the synthetic scope objects' entries in liveScopes. So
             * if we remove an entry frcom missingScopes here, we must also
             * remove the corresponding liveScopes entry.
             *
             * Since the DebugScopeObject is the only thing using its scope
             * object, and the DSO is about to be finalized, you might assume
             * that the synthetic SO is also about to be finalized too, and thus
             * the loop below will take care of things. But complex GC behavior
             * means that marks are only conservative approximations of
             * liveness; we should assume that anything could be marked.
             *
             * Thus, we must explicitly remove the entries from both liveScopes
             * and missingScopes here.
             */
            liveScopes.remove(&e.front().value().unbarrieredGet()->scope());
            e.removeFront();
        } else {
            MissingScopeKey key = e.front().key();
            if (IsForwarded(key.staticScope())) {
                key.updateStaticScope(Forwarded(key.staticScope()));
                e.rekeyFront(key);
            }
        }
    }

    /*
     * Scopes can be finalized when a debugger-synthesized ScopeObject is
     * no longer reachable via its DebugScopeObject.
     */
    liveScopes.sweep();
}

#ifdef JSGC_HASH_TABLE_CHECKS
void
DebugScopes::checkHashTablesAfterMovingGC(JSRuntime* runtime)
{
    /*
     * This is called at the end of StoreBuffer::mark() to check that our
     * postbarriers have worked and that no hashtable keys (or values) are left
     * pointing into the nursery.
     */
    proxiedScopes.checkAfterMovingGC();
    for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) {
        CheckGCThingAfterMovingGC(r.front().key().staticScope());
        CheckGCThingAfterMovingGC(r.front().value().get());
    }
    for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) {
        CheckGCThingAfterMovingGC(r.front().key());
        CheckGCThingAfterMovingGC(r.front().value().staticScope_.get());
    }
}
#endif

/*
 * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode
 * (in particular, JS_GetFrameScopeChain does not require debug mode). Since
 * DebugScopes::onPop* are only called in debuggee frames, this means we
 * cannot use any of the maps in DebugScopes. This will produce debug scope
 * chains that do not obey the debugger invariants but that is just fine.
 */
static bool
CanUseDebugScopeMaps(JSContext* cx)
{
    return cx->compartment()->isDebuggee();
}

DebugScopes*
DebugScopes::ensureCompartmentData(JSContext* cx)
{
    JSCompartment* c = cx->compartment();
    if (c->debugScopes)
        return c->debugScopes;

    AutoInitGCManagedObject<DebugScopes> debugScopes(cx->make_unique<DebugScopes>(cx));
    if (!debugScopes || !debugScopes->init()) {
        ReportOutOfMemory(cx);
        return nullptr;
    }

    c->debugScopes = debugScopes.release();
    return c->debugScopes;
}

DebugScopeObject*
DebugScopes::hasDebugScope(JSContext* cx, ScopeObject& scope)
{
    DebugScopes* scopes = scope.compartment()->debugScopes;
    if (!scopes)
        return nullptr;

    if (JSObject* obj = scopes->proxiedScopes.lookup(&scope)) {
        MOZ_ASSERT(CanUseDebugScopeMaps(cx));
        return &obj->as<DebugScopeObject>();
    }

    return nullptr;
}

bool
DebugScopes::addDebugScope(JSContext* cx, ScopeObject& scope, DebugScopeObject& debugScope)
{
    MOZ_ASSERT(cx->compartment() == scope.compartment());
    MOZ_ASSERT(cx->compartment() == debugScope.compartment());

    if (!CanUseDebugScopeMaps(cx))
        return true;

    DebugScopes* scopes = ensureCompartmentData(cx);
    if (!scopes)
        return false;

    return scopes->proxiedScopes.add(cx, &scope, &debugScope);
}

DebugScopeObject*
DebugScopes::hasDebugScope(JSContext* cx, const ScopeIter& si)
{
    MOZ_ASSERT(!si.hasSyntacticScopeObject());

    DebugScopes* scopes = cx->compartment()->debugScopes;
    if (!scopes)
        return nullptr;

    if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
        MOZ_ASSERT(CanUseDebugScopeMaps(cx));
        return p->value();
    }
    return nullptr;
}

bool
DebugScopes::addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope)
{
    MOZ_ASSERT(!si.hasSyntacticScopeObject());
    MOZ_ASSERT(cx->compartment() == debugScope.compartment());
    // Generators should always reify their scopes.
    MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().isGenerator());

    if (!CanUseDebugScopeMaps(cx))
        return true;

    DebugScopes* scopes = ensureCompartmentData(cx);
    if (!scopes)
        return false;

    MissingScopeKey key(si);
    MOZ_ASSERT(!scopes->missingScopes.has(key));
    if (!scopes->missingScopes.put(key, ReadBarriered<DebugScopeObject*>(&debugScope))) {
        ReportOutOfMemory(cx);
        return false;
    }

    // Only add to liveScopes if we synthesized the debug scope on a live
    // frame.
    if (si.withinInitialFrame()) {
        MOZ_ASSERT(!scopes->liveScopes.has(&debugScope.scope()));
        if (!scopes->liveScopes.put(&debugScope.scope(), LiveScopeVal(si))) {
            ReportOutOfMemory(cx);
            return false;
        }
    }

    return true;
}

void
DebugScopes::onPopCall(AbstractFramePtr frame, JSContext* cx)
{
    assertSameCompartment(cx, frame);

    DebugScopes* scopes = cx->compartment()->debugScopes;
    if (!scopes)
        return;

    Rooted<DebugScopeObject*> debugScope(cx, nullptr);

    if (frame.callee()->needsCallObject()) {
        /*
         * The frame may be observed before the prologue has created the
         * CallObject. See ScopeIter::settle.
         */
        if (!frame.hasCallObj())
            return;

        if (frame.callee()->isGenerator())
            return;

        CallObject& callobj = frame.scopeChain()->as<CallObject>();
        scopes->liveScopes.remove(&callobj);
        if (JSObject* obj = scopes->proxiedScopes.lookup(&callobj))
            debugScope = &obj->as<DebugScopeObject>();
    } else {
        ScopeIter si(cx, frame, frame.script()->main());
        if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
            debugScope = p->value();
            scopes->liveScopes.remove(&debugScope->scope().as<CallObject>());
            scopes->missingScopes.remove(p);
        }
    }

    /*
     * When the JS stack frame is popped, the values of unaliased variables
     * are lost. If there is any debug scope referring to this scope, save a
     * copy of the unaliased variables' values in an array for later debugger
     * access via DebugScopeProxy::handleUnaliasedAccess.
     *
     * Note: since it is simplest for this function to be infallible, failure
     * in this code will be silently ignored. This does not break any
     * invariants since DebugScopeObject::maybeSnapshot can already be nullptr.
     */
    if (debugScope) {
        /*
         * Copy all frame values into the snapshot, regardless of
         * aliasing. This unnecessarily includes aliased variables
         * but it simplifies later indexing logic.
         */
        Rooted<GCVector<Value>> vec(cx, GCVector<Value>(cx));
        if (!frame.copyRawFrameSlots(&vec)) {
            cx->recoverFromOutOfMemory();
            return;
        }

        if (vec.length() == 0)
            return;

        /*
         * Copy in formals that are not aliased via the scope chain
         * but are aliased via the arguments object.
         */
        RootedScript script(cx, frame.script());
        if (script->analyzedArgsUsage() && script->needsArgsObj() && frame.hasArgsObj()) {
            for (unsigned i = 0; i < frame.numFormalArgs(); ++i) {
                if (script->formalLivesInArgumentsObject(i))
                    vec[i].set(frame.argsObj().arg(i));
            }
        }

        /*
         * Use a dense array as storage (since proxies do not have trace
         * hooks). This array must not escape into the wild.
         */
        RootedArrayObject snapshot(cx, NewDenseCopiedArray(cx, vec.length(), vec.begin()));
        if (!snapshot) {
            cx->recoverFromOutOfMemory();
            return;
        }

        debugScope->initSnapshot(*snapshot);
    }
}

void
DebugScopes::onPopBlock(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
    assertSameCompartment(cx, frame);

    DebugScopes* scopes = cx->compartment()->debugScopes;
    if (!scopes)
        return;

    ScopeIter si(cx, frame, pc);
    onPopBlock(cx, si);
}

void
DebugScopes::onPopBlock(JSContext* cx, const ScopeIter& si)
{
    DebugScopes* scopes = cx->compartment()->debugScopes;
    if (!scopes)
        return;

    MOZ_ASSERT(si.withinInitialFrame());
    MOZ_ASSERT(si.type() == ScopeIter::Block);

    if (si.staticBlock().needsClone()) {
        ClonedBlockObject& clone = si.scope().as<ClonedBlockObject>();
        clone.copyUnaliasedValues(si.initialFrame());
        scopes->liveScopes.remove(&clone);
    } else {
        if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
            ClonedBlockObject& clone = p->value()->scope().as<ClonedBlockObject>();
            clone.copyUnaliasedValues(si.initialFrame());
            scopes->liveScopes.remove(&clone);
            scopes->missingScopes.remove(p);
        }
    }
}

void
DebugScopes::onPopWith(AbstractFramePtr frame)
{
    DebugScopes* scopes = frame.compartment()->debugScopes;
    if (scopes)
        scopes->liveScopes.remove(&frame.scopeChain()->as<DynamicWithObject>());
}

void
DebugScopes::onPopStrictEvalScope(AbstractFramePtr frame)
{
    DebugScopes* scopes = frame.compartment()->debugScopes;
    if (!scopes)
        return;

    /*
     * The stack frame may be observed before the prologue has created the
     * CallObject. See ScopeIter::settle.
     */
    if (frame.hasCallObj())
        scopes->liveScopes.remove(&frame.scopeChain()->as<CallObject>());
}

void
DebugScopes::onCompartmentUnsetIsDebuggee(JSCompartment* c)
{
    DebugScopes* scopes = c->debugScopes;
    if (scopes) {
        scopes->proxiedScopes.clear();
        scopes->missingScopes.clear();
        scopes->liveScopes.clear();
    }
}

bool
DebugScopes::updateLiveScopes(JSContext* cx)
{
    JS_CHECK_RECURSION(cx, return false);

    /*
     * Note that we must always update the top frame's scope objects' entries
     * in liveScopes because we can't be sure code hasn't run in that frame to
     * change the scope chain since we were last called. The fp->prevUpToDate()
     * flag indicates whether the scopes of frames older than fp are already
     * included in liveScopes. It might seem simpler to have fp instead carry a
     * flag indicating whether fp itself is accurately described, but then we
     * would need to clear that flag whenever fp ran code. By storing the 'up
     * to date' bit for fp->prev() in fp, simply popping fp effectively clears
     * the flag for us, at exactly the time when execution resumes fp->prev().
     */
    for (AllFramesIter i(cx); !i.done(); ++i) {
        if (!i.hasUsableAbstractFramePtr())
            continue;

        AbstractFramePtr frame = i.abstractFramePtr();
        if (frame.scopeChain()->compartment() != cx->compartment())
            continue;

        if (frame.isFunctionFrame() && frame.callee()->isGenerator())
            continue;

        if (!frame.isDebuggee())
            continue;

        for (ScopeIter si(cx, frame, i.pc()); si.withinInitialFrame(); ++si) {
            if (si.hasSyntacticScopeObject()) {
                MOZ_ASSERT(si.scope().compartment() == cx->compartment());
                DebugScopes* scopes = ensureCompartmentData(cx);
                if (!scopes)
                    return false;
                if (!scopes->liveScopes.put(&si.scope(), LiveScopeVal(si)))
                    return false;
            }
        }

        if (frame.prevUpToDate())
            return true;
        MOZ_ASSERT(frame.scopeChain()->compartment()->isDebuggee());
        frame.setPrevUpToDate();
    }

    return true;
}

LiveScopeVal*
DebugScopes::hasLiveScope(ScopeObject& scope)
{
    DebugScopes* scopes = scope.compartment()->debugScopes;
    if (!scopes)
        return nullptr;

    if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope))
        return &p->value();

    return nullptr;
}

/* static */ void
DebugScopes::unsetPrevUpToDateUntil(JSContext* cx, AbstractFramePtr until)
{
    // This are two exceptions where fp->prevUpToDate() is cleared without
    // popping the frame. When a frame is rematerialized or has its
    // debuggeeness toggled off->on, all frames younger than the frame must
    // have their prevUpToDate set to false. This is because unrematerialized
    // Ion frames and non-debuggee frames are skipped by updateLiveScopes. If
    // in the future a frame suddenly gains a usable AbstractFramePtr via
    // rematerialization or becomes a debuggee, the prevUpToDate invariant
    // will no longer hold for older frames on its stack.
    for (AllFramesIter i(cx); !i.done(); ++i) {
        if (!i.hasUsableAbstractFramePtr())
            continue;

        AbstractFramePtr frame = i.abstractFramePtr();
        if (frame == until)
            return;

        if (frame.scopeChain()->compartment() != cx->compartment())
            continue;

        frame.unsetPrevUpToDate();
    }
}

/* static */ void
DebugScopes::forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to)
{
    DebugScopes* scopes = cx->compartment()->debugScopes;
    if (!scopes)
        return;

    for (MissingScopeMap::Enum e(scopes->missingScopes); !e.empty(); e.popFront()) {
        MissingScopeKey key = e.front().key();
        if (key.frame() == from) {
            key.updateFrame(to);
            e.rekeyFront(key);
        }
    }

    for (LiveScopeMap::Enum e(scopes->liveScopes); !e.empty(); e.popFront()) {
        LiveScopeVal& val = e.front().value();
        if (val.frame() == from)
            val.updateFrame(to);
    }
}

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

static JSObject*
GetDebugScope(JSContext* cx, const ScopeIter& si);

static DebugScopeObject*
GetDebugScopeForScope(JSContext* cx, const ScopeIter& si)
{
    Rooted<ScopeObject*> scope(cx, &si.scope());
    if (DebugScopeObject* debugScope = DebugScopes::hasDebugScope(cx, *scope))
        return debugScope;

    ScopeIter copy(cx, si);
    RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy));
    if (!enclosingDebug)
        return nullptr;

    JSObject& maybeDecl = scope->enclosingScope();
    if (maybeDecl.is<DeclEnvObject>()) {
        MOZ_ASSERT(CallObjectLambdaName(scope->as<CallObject>().callee()));
        enclosingDebug = DebugScopeObject::create(cx, maybeDecl.as<DeclEnvObject>(), enclosingDebug);
        if (!enclosingDebug)
            return nullptr;
    }

    DebugScopeObject* debugScope = DebugScopeObject::create(cx, *scope, enclosingDebug);
    if (!debugScope)
        return nullptr;

    if (!DebugScopes::addDebugScope(cx, *scope, *debugScope))
        return nullptr;

    return debugScope;
}

static DebugScopeObject*
GetDebugScopeForMissing(JSContext* cx, const ScopeIter& si)
{
    MOZ_ASSERT(!si.hasSyntacticScopeObject() && si.canHaveSyntacticScopeObject());

    if (DebugScopeObject* debugScope = DebugScopes::hasDebugScope(cx, si))
        return debugScope;

    ScopeIter copy(cx, si);
    RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy));
    if (!enclosingDebug)
        return nullptr;

    /*
     * Create the missing scope object. For block objects, this takes care of
     * storing variable values after the stack frame has been popped. For call
     * objects, we only use the pretend call object to access callee, bindings
     * and to receive dynamically added properties. Together, this provides the
     * nice invariant that every DebugScopeObject has a ScopeObject.
     *
     * Note: to preserve scopeChain depth invariants, these lazily-reified
     * scopes must not be put on the frame's scope chain; instead, they are
     * maintained via DebugScopes hooks.
     */
    DebugScopeObject* debugScope = nullptr;
    switch (si.type()) {
      case ScopeIter::Module:
          MOZ_CRASH(); // TODO: Implement debug scopes for modules.
          break;

      case ScopeIter::Call: {
        RootedFunction callee(cx, &si.fun());
        // Generators should always reify their scopes.
        MOZ_ASSERT(!callee->isGenerator());

        Rooted<CallObject*> callobj(cx);
        if (si.withinInitialFrame())
            callobj = CallObject::createForFunction(cx, si.initialFrame());
        else
            callobj = CallObject::createHollowForDebug(cx, callee);
        if (!callobj)
            return nullptr;

        if (callobj->enclosingScope().is<DeclEnvObject>()) {
            MOZ_ASSERT(CallObjectLambdaName(callobj->callee()));
            DeclEnvObject& declenv = callobj->enclosingScope().as<DeclEnvObject>();
            enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug);
            if (!enclosingDebug)
                return nullptr;
        }

        debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug);
        break;
      }
      case ScopeIter::Block: {
        // Generators should always reify their scopes, except in this one
        // weird case of deprecated let expressions where we can create a
        // 0-variable StaticBlockScope inside a generator that does not need
        // cloning.
        //
        // For example, |let ({} = "") { yield evalInFrame("foo"); }|.
        MOZ_ASSERT_IF(si.staticBlock().numVariables() > 0 &&
                      si.withinInitialFrame() &&
                      si.initialFrame().isFunctionFrame(),
                      !si.initialFrame().callee()->isGenerator());

        Rooted<StaticBlockScope*> staticBlock(cx, &si.staticBlock());
        ClonedBlockObject* block;
        if (si.withinInitialFrame())
            block = ClonedBlockObject::create(cx, staticBlock, si.initialFrame());
        else
            block = ClonedBlockObject::createHollowForDebug(cx, staticBlock);
        if (!block)
            return nullptr;

        debugScope = DebugScopeObject::create(cx, *block, enclosingDebug);
        break;
      }
      case ScopeIter::With:
      case ScopeIter::Eval:
        MOZ_CRASH("should already have a scope");
      case ScopeIter::NonSyntactic:
        MOZ_CRASH("non-syntactic scopes cannot be synthesized");
    }
    if (!debugScope)
        return nullptr;

    if (!DebugScopes::addDebugScope(cx, si, *debugScope))
        return nullptr;

    return debugScope;
}

static JSObject*
GetDebugScopeForNonScopeObject(const ScopeIter& si)
{
    JSObject& enclosing = si.enclosingScope();
    MOZ_ASSERT(!enclosing.is<ScopeObject>());
#ifdef DEBUG
    JSObject* o = &enclosing;
    while ((o = o->enclosingScope()))
        MOZ_ASSERT(!o->is<ScopeObject>());
#endif
    return &enclosing;
}

static JSObject*
GetDebugScope(JSContext* cx, const ScopeIter& si)
{
    JS_CHECK_RECURSION(cx, return nullptr);

    if (si.done())
        return GetDebugScopeForNonScopeObject(si);

    if (si.hasAnyScopeObject())
        return GetDebugScopeForScope(cx, si);

    if (si.canHaveSyntacticScopeObject())
        return GetDebugScopeForMissing(cx, si);

    ScopeIter copy(cx, si);
    return GetDebugScope(cx, ++copy);
}

JSObject*
js::GetDebugScopeForFunction(JSContext* cx, HandleFunction fun)
{
    assertSameCompartment(cx, fun);
    MOZ_ASSERT(CanUseDebugScopeMaps(cx));
    if (!DebugScopes::updateLiveScopes(cx))
        return nullptr;
    JSScript* script = fun->getOrCreateScript(cx);
    if (!script)
        return nullptr;
    ScopeIter si(cx, fun->environment(), script->enclosingStaticScope());
    return GetDebugScope(cx, si);
}

JSObject*
js::GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
    assertSameCompartment(cx, frame);
    if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx))
        return nullptr;

    ScopeIter si(cx, frame, pc);
    return GetDebugScope(cx, si);
}

JSObject*
js::GetDebugScopeForGlobalLexicalScope(JSContext* cx)
{
    ScopeIter si(cx, &cx->global()->lexicalScope(), &cx->global()->lexicalScope().staticBlock());
    return GetDebugScope(cx, si);
}

// See declaration and documentation in jsfriendapi.h
JS_FRIEND_API(JSObject*)
js::GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun)
{
    if (!fun->isInterpreted())
        return &fun->global();

    JSObject* env = fun->environment();
    while (env && !env->is<DynamicWithObject>())
        env = env->enclosingScope();

    if (!env)
        return &fun->global();

    return &env->as<DynamicWithObject>().object();
}

bool
js::CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
                                    HandleObject dynamicTerminatingScope,
                                    MutableHandleObject dynamicScopeObj)
{
#ifdef DEBUG
    for (size_t i = 0; i < scopeChain.length(); ++i) {
        assertSameCompartment(cx, scopeChain[i]);
        MOZ_ASSERT(!scopeChain[i]->is<GlobalObject>());
    }
#endif

    // Construct With object wrappers for the things on this scope
    // chain and use the result as the thing to scope the function to.
    Rooted<StaticWithScope*> staticWith(cx);
    RootedObject staticEnclosingScope(cx);
    Rooted<DynamicWithObject*> dynamicWith(cx);
    RootedObject dynamicEnclosingScope(cx, dynamicTerminatingScope);
    for (size_t i = scopeChain.length(); i > 0; ) {
        staticWith = StaticWithScope::create(cx);
        if (!staticWith)
            return false;
        staticWith->initEnclosingScope(staticEnclosingScope);
        staticEnclosingScope = staticWith;

        dynamicWith = DynamicWithObject::create(cx, scopeChain[--i], dynamicEnclosingScope,
                                                staticWith, DynamicWithObject::NonSyntacticWith);
        if (!dynamicWith)
            return false;
        dynamicEnclosingScope = dynamicWith;
    }

    dynamicScopeObj.set(dynamicEnclosingScope);
    return true;
}

bool
js::HasNonSyntacticStaticScopeChain(JSObject* staticScope)
{
    for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++) {
        // If we hit a function scope, we can short circuit the logic, as
        // scripts cache whether they are under a non-syntactic scope.
        if (ssi.type() == StaticScopeIter<NoGC>::Function)
            return ssi.funScript()->hasNonSyntacticScope();
        if (ssi.type() == StaticScopeIter<NoGC>::NonSyntactic)
            return true;
    }
    return false;
}

uint32_t
js::StaticScopeChainLength(JSObject* staticScope)
{
    uint32_t length = 0;
    for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++)
        length++;
    return length;
}

ModuleEnvironmentObject*
js::GetModuleEnvironmentForScript(JSScript* script)
{
    StaticScopeIter<NoGC> ssi(script->enclosingStaticScope());
    while (!ssi.done() && ssi.type() != StaticScopeIter<NoGC>::Module)
        ssi++;
    if (ssi.done())
        return nullptr;

    return ssi.module().environment();
}

bool
js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
                                             MutableHandleValue res)
{
    for (ScopeIter si(cx, frame, pc); !si.done(); ++si) {
        if (si.type() == ScopeIter::Module) {
            res.setUndefined();
            return true;
        }

        if (si.type() != ScopeIter::Call || si.fun().hasLexicalThis())
            continue;

        RootedScript script(cx, si.fun().nonLazyScript());

        if (si.withinInitialFrame() &&
            (pc < script->main() || !script->functionHasThisBinding()))
        {
            // Either we're in the script prologue and we may still have to
            // initialize the this-binding (JSOP_FUNCTIONTHIS), or the script
            // does not have a this-binding (because it doesn't use |this|).

            // If our this-argument is an object, or we're in strict mode,
            // the this-binding is always the same as our this-argument.
            if (frame.thisArgument().isObject() || script->strict()) {
                res.set(frame.thisArgument());
                return true;
            }

            // Figure out if we already executed JSOP_FUNCTIONTHIS.
            bool executedInitThisOp = false;
            if (script->functionHasThisBinding()) {
                jsbytecode* initThisPc = script->code();
                while (*initThisPc != JSOP_FUNCTIONTHIS && initThisPc < script->main())
                    initThisPc = GetNextPc(initThisPc);
                executedInitThisOp = (pc > initThisPc);
            }

            if (!executedInitThisOp) {
                // We didn't initialize the this-binding yet. Determine the
                // correct |this| value for this frame (box primitives if not
                // in strict mode), and assign it to the this-argument slot so
                // JSOP_FUNCTIONTHIS will use it and not box a second time.
                if (!GetFunctionThis(cx, frame, res))
                    return false;
                frame.thisArgument() = res;
                return true;
            }
        }

        if (!script->functionHasThisBinding()) {
            res.setMagic(JS_OPTIMIZED_OUT);
            return true;
        }

        BindingIter bi = Bindings::thisBinding(cx, script);

        if (script->bindingIsAliased(bi)) {
            RootedObject callObj(cx, &si.scope().as<CallObject>());
            return GetProperty(cx, callObj, callObj, cx->names().dotThis, res);
        }

        if (si.withinInitialFrame())
            res.set(frame.unaliasedLocal(bi.frameIndex()));
        else
            res.setMagic(JS_OPTIMIZED_OUT);
        return true;
    }

    RootedObject scopeChain(cx, frame.scopeChain());
    return GetNonSyntacticGlobalThis(cx, scopeChain, res);
}

bool
js::CheckLexicalNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
                             HandleObject varObj, HandlePropertyName name)
{
    mozilla::Maybe<frontend::Definition::Kind> redeclKind;
    RootedId id(cx, NameToId(name));
    RootedShape shape(cx);
    if ((shape = lexicalScope->lookup(cx, name))) {
        redeclKind = mozilla::Some(shape->writable() ? frontend::Definition::LET
                                                     : frontend::Definition::CONSTANT);
    } else if (varObj->isNative() && (shape = varObj->as<NativeObject>().lookup(cx, name))) {
        if (!shape->configurable())
            redeclKind = mozilla::Some(frontend::Definition::VAR);
    } else {
        Rooted<PropertyDescriptor> desc(cx);
        if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc))
            return false;
        if (desc.object() && desc.hasConfigurable() && !desc.configurable())
            redeclKind = mozilla::Some(frontend::Definition::VAR);
    }

    if (redeclKind.isSome()) {
        ReportRuntimeRedeclaration(cx, name, *redeclKind);
        return false;
    }

    return true;
}

bool
js::CheckVarNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
                         HandlePropertyName name)
{
    if (Shape* shape = lexicalScope->lookup(cx, name)) {
        ReportRuntimeRedeclaration(cx, name, shape->writable() ? frontend::Definition::LET
                                                               : frontend::Definition::CONSTANT);
        return false;
    }
    return true;
}

static bool
CheckVarNameConflict(JSContext* cx, Handle<CallObject*> callObj, HandlePropertyName name)
{
    RootedFunction fun(cx, &callObj->callee());
    RootedScript script(cx, fun->nonLazyScript());
    uint32_t bodyLevelLexicalsStart = script->bindings.numVars();

    for (BindingIter bi(script); !bi.done(); bi++) {
        if (name == bi->name() &&
            bi.isBodyLevelLexical() &&
            bi.localIndex() >= bodyLevelLexicalsStart)
        {
            ReportRuntimeRedeclaration(cx, name,
                                       bi->kind() == Binding::CONSTANT
                                       ? frontend::Definition::CONSTANT
                                       : frontend::Definition::LET);
            return false;
        }
    }

    return true;
}

bool
js::CheckGlobalDeclarationConflicts(JSContext* cx, HandleScript script,
                                    Handle<ClonedBlockObject*> lexicalScope,
                                    HandleObject varObj)
{
    // Due to the extensibility of the global lexical scope, we must check for
    // redeclaring a binding.
    //
    // In the case of non-syntactic scope chains, we are checking
    // redeclarations against the non-syntactic lexical scope and the
    // variables object that the lexical scope corresponds to.
    RootedPropertyName name(cx);
    BindingIter bi(script);

    for (uint32_t i = 0; i < script->bindings.numVars(); i++, bi++) {
        name = bi->name();
        if (!CheckVarNameConflict(cx, lexicalScope, name))
            return false;
    }

    for (uint32_t i = 0; i < script->bindings.numBodyLevelLexicals(); i++, bi++) {
        name = bi->name();
        if (!CheckLexicalNameConflict(cx, lexicalScope, varObj, name))
            return false;
    }

    return true;
}

template <class ScopeT>
static bool
CheckVarNameConflictsInScope(JSContext* cx, HandleScript script, HandleObject obj)
{
    Rooted<ScopeT*> scope(cx);

    // We return true when the scope object is not ScopeT below, because
    // ScopeT is either ClonedBlockObject or CallObject. No other scope
    // objects can contain lexical bindings, and there are no other overloads
    // for CheckVarNameConflict.

    if (obj->is<ScopeT>())
        scope = &obj->as<ScopeT>();
    else if (obj->is<DebugScopeObject>() && obj->as<DebugScopeObject>().scope().is<ScopeT>())
        scope = &obj->as<DebugScopeObject>().scope().as<ScopeT>();
    else
        return true;

    RootedPropertyName name(cx);

    for (BindingIter bi(script); !bi.done(); bi++) {
        name = bi->name();
        if (!CheckVarNameConflict(cx, scope, name))
            return false;
    }

    return true;
}

bool
js::CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script,
                                  HandleObject scopeChain, HandleObject varObj)
{
    // We don't need to check body-level lexical bindings for conflict. Eval
    // scripts always execute under their own lexical scope.
    if (script->bindings.numVars() == 0)
        return true;

    RootedObject obj(cx, scopeChain);

    // ES6 18.2.1.2 step d
    //
    // Check that a direct eval will not hoist 'var' bindings over lexical
    // bindings with the same name.
    while (obj != varObj) {
        // Annex B.3.5 says 'var' declarations with the same name as catch
        // parameters are allowed.
        if (!obj->is<ClonedBlockObject>() || !obj->as<ClonedBlockObject>().isForCatchParameters()) {
            if (!CheckVarNameConflictsInScope<ClonedBlockObject>(cx, script, obj))
                return false;
        }
        obj = obj->enclosingScope();
    }

    return CheckVarNameConflictsInScope<CallObject>(cx, script, varObj);
}

#ifdef DEBUG

void
js::DumpStaticScopeChain(JSScript* script)
{
    DumpStaticScopeChain(script->enclosingStaticScope());
}

void
js::DumpStaticScopeChain(JSObject* staticScope)
{
    for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++) {
        switch (ssi.type()) {
          case StaticScopeIter<NoGC>::Module:
            fprintf(stdout, "module [%p]", &ssi.module());
            break;
          case StaticScopeIter<NoGC>::Function:
            if (ssi.fun().isBeingParsed())
                fprintf(stdout, "funbox [%p fun=%p]", ssi.maybeFunctionBox(), &ssi.fun());
            else
                fprintf(stdout, "function [%p]", &ssi.fun());
            break;
          case StaticScopeIter<NoGC>::Block:
            fprintf(stdout, "block [%p]", &ssi.block());
            break;
          case StaticScopeIter<NoGC>::With:
            fprintf(stdout, "with [%p]", &ssi.staticWith());
            break;
          case StaticScopeIter<NoGC>::NamedLambda:
            fprintf(stdout, "named lambda");
            break;
          case StaticScopeIter<NoGC>::Eval:
            fprintf(stdout, "eval [%p]", &ssi.eval());
            break;
          case StaticScopeIter<NoGC>::NonSyntactic:
            fprintf(stdout, "non-syntactic [%p]", &ssi.nonSyntactic());
            break;
        }
        fprintf(stdout, " -> ");
    }
    fprintf(stdout, "global\n");
}

typedef HashSet<PropertyName*> PropertyNameSet;

static bool
RemoveReferencedNames(JSContext* cx, HandleScript script, PropertyNameSet& remainingNames)
{
    // Remove from remainingNames --- the closure variables in some outer
    // script --- any free variables in this script. This analysis isn't perfect:
    //
    // - It will not account for free variables in an inner script which are
    //   actually accessing some name in an intermediate script between the
    //   inner and outer scripts. This can cause remainingNames to be an
    //   underapproximation.
    //
    // - It will not account for new names introduced via eval. This can cause
    //   remainingNames to be an overapproximation. This would be easy to fix
    //   but is nice to have as the eval will probably not access these
    //   these names and putting eval in an inner script is bad news if you
    //   care about entraining variables unnecessarily.

    for (jsbytecode* pc = script->code(); pc != script->codeEnd(); pc += GetBytecodeLength(pc)) {
        PropertyName* name;

        switch (JSOp(*pc)) {
          case JSOP_GETNAME:
          case JSOP_SETNAME:
          case JSOP_STRICTSETNAME:
            name = script->getName(pc);
            break;

          case JSOP_GETGNAME:
          case JSOP_SETGNAME:
          case JSOP_STRICTSETGNAME:
            if (script->hasNonSyntacticScope())
                name = script->getName(pc);
            else
                name = nullptr;
            break;

          case JSOP_GETALIASEDVAR:
          case JSOP_SETALIASEDVAR:
            name = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
            break;

          default:
            name = nullptr;
            break;
        }

        if (name)
            remainingNames.remove(name);
    }

    if (script->hasObjects()) {
        ObjectArray* objects = script->objects();
        for (size_t i = 0; i < objects->length; i++) {
            JSObject* obj = objects->vector[i];
            if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
                JSFunction* fun = &obj->as<JSFunction>();
                RootedScript innerScript(cx, fun->getOrCreateScript(cx));
                if (!innerScript)
                    return false;

                if (!RemoveReferencedNames(cx, innerScript, remainingNames))
                    return false;
            }
        }
    }

    return true;
}

static bool
AnalyzeEntrainedVariablesInScript(JSContext* cx, HandleScript script, HandleScript innerScript)
{
    PropertyNameSet remainingNames(cx);
    if (!remainingNames.init())
        return false;

    for (BindingIter bi(script); bi; bi++) {
        if (bi->aliased()) {
            PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(bi->name());
            if (!p && !remainingNames.add(p, bi->name()))
                return false;
        }
    }

    if (!RemoveReferencedNames(cx, innerScript, remainingNames))
        return false;

    if (!remainingNames.empty()) {
        Sprinter buf(cx);
        if (!buf.init())
            return false;

        buf.printf("Script ");

        if (JSAtom* name = script->functionNonDelazifying()->displayAtom()) {
            buf.putString(name);
            buf.printf(" ");
        }

        buf.printf("(%s:%" PRIuSIZE ") has variables entrained by ", script->filename(), script->lineno());

        if (JSAtom* name = innerScript->functionNonDelazifying()->displayAtom()) {
            buf.putString(name);
            buf.printf(" ");
        }

        buf.printf("(%s:%" PRIuSIZE ") ::", innerScript->filename(), innerScript->lineno());

        for (PropertyNameSet::Range r = remainingNames.all(); !r.empty(); r.popFront()) {
            buf.printf(" ");
            buf.putString(r.front());
        }

        printf("%s\n", buf.string());
    }

    if (innerScript->hasObjects()) {
        ObjectArray* objects = innerScript->objects();
        for (size_t i = 0; i < objects->length; i++) {
            JSObject* obj = objects->vector[i];
            if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
                JSFunction* fun = &obj->as<JSFunction>();
                RootedScript innerInnerScript(cx, fun->getOrCreateScript(cx));
                if (!innerInnerScript ||
                    !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript))
                {
                    return false;
                }
            }
        }
    }

    return true;
}

// Look for local variables in script or any other script inner to it, which are
// part of the script's call object and are unnecessarily entrained by their own
// inner scripts which do not refer to those variables. An example is:
//
// function foo() {
//   var a, b;
//   function bar() { return a; }
//   function baz() { return b; }
// }
//
// |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|.
bool
js::AnalyzeEntrainedVariables(JSContext* cx, HandleScript script)
{
    if (!script->hasObjects())
        return true;

    ObjectArray* objects = script->objects();
    for (size_t i = 0; i < objects->length; i++) {
        JSObject* obj = objects->vector[i];
        if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
            JSFunction* fun = &obj->as<JSFunction>();
            RootedScript innerScript(cx, fun->getOrCreateScript(cx));
            if (!innerScript)
                return false;

            if (script->functionDelazifying() && script->functionDelazifying()->needsCallObject()) {
                if (!AnalyzeEntrainedVariablesInScript(cx, script, innerScript))
                    return false;
            }

            if (!AnalyzeEntrainedVariables(cx, innerScript))
                return false;
        }
    }

    return true;
}

#endif
