Raw File
EnvironmentObject.cpp
/* -*- 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/EnvironmentObject-inl.h"

#include "mozilla/PodOperations.h"
#include "mozilla/ScopeExit.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/AsyncFunction.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;
using mozilla::Maybe;
using mozilla::Some;
using mozilla::Nothing;
using mozilla::MakeScopeExit;

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


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

Shape*
js::EnvironmentCoordinateToEnvironmentShape(JSScript* script, jsbytecode* pc)
{
    MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD);
    ScopeIter si(script->innermostScope(pc));
    uint32_t hops = EnvironmentCoordinate(pc).hops();
    while (true) {
        MOZ_ASSERT(!si.done());
        if (si.hasSyntacticEnvironment()) {
            if (!hops)
                break;
            hops--;
        }
        si++;
    }
    return si.environmentShape();
}

static const uint32_t ENV_COORDINATE_NAME_THRESHOLD = 20;

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

PropertyName*
js::EnvironmentCoordinateName(EnvironmentCoordinateNameCache& cache, JSScript* script,
                              jsbytecode* pc)
{
    Shape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc);
    if (shape != cache.shape && shape->slot() >= ENV_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;
    EnvironmentCoordinate ec(pc);
    if (shape == cache.shape) {
        EnvironmentCoordinateNameCache::Map::Ptr p = cache.map.lookup(ec.slot());
        id = p->value();
    } else {
        Shape::Range<NoGC> r(shape);
        while (r.front().slot() != ec.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::EnvironmentCoordinateFunctionScript(JSScript* script, jsbytecode* pc)
{
    MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD);
    ScopeIter si(script->innermostScope(pc));
    uint32_t hops = EnvironmentCoordinate(pc).hops();
    while (true) {
        if (si.hasSyntacticEnvironment()) {
            if (!hops)
                break;
            hops--;
        }
        si++;
    }
    if (si.kind() != ScopeKind::Function)
        return nullptr;
    return si.scope()->as<FunctionScope>().script();
}

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

CallObject*
CallObject::create(JSContext* cx, HandleShape shape, HandleObjectGroup group)
{
    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;

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

CallObject*
CallObject::createSingleton(JSContext* cx, HandleShape shape)
{
    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");

    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, HandleObject enclosing,
                                 gc::InitialHeap heap)
{
    Rooted<FunctionScope*> scope(cx, &script->bodyScope()->as<FunctionScope>());
    RootedShape shape(cx, scope->environmentShape());
    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;

    CallObject* callObj = &obj->as<CallObject>();
    callObj->initEnclosingEnvironment(enclosing);

    if (scope->hasParameterExprs()) {
        // If there are parameter expressions, all parameters are lexical and
        // have TDZ.
        for (BindingIter bi(script->bodyScope()); bi; bi++) {
            BindingLocation loc = bi.location();
            if (loc.kind() == BindingLocation::Kind::Environment && BindingKindIsLexical(bi.kind()))
                callObj->initSlot(loc.slot(), MagicValue(JS_UNINITIALIZED_LEXICAL));
        }
    }

    return callObj;
}

/*
 * 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, HandleFunction callee, HandleObject enclosing)
{
    RootedScript script(cx, callee->nonLazyScript());
    gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
    CallObject* callobj = CallObject::createTemplateObject(cx, script, enclosing, heap);
    if (!callobj)
        return nullptr;

    callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee));

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

    return callobj;
}

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

    RootedObject envChain(cx, frame.environmentChain());
    RootedFunction callee(cx, frame.callee());

    CallObject* callobj = create(cx, callee, envChain);
    if (!callobj)
        return nullptr;

    if (!frame.script()->bodyScope()->as<FunctionScope>().hasParameterExprs()) {
        // If there are no defaults, copy the aliased arguments into the call
        // object manually. If there are defaults, bytecode is generated to do
        // the copying.

        for (PositionalFormalParameterIter fi(frame.script()); fi; fi++) {
            if (!fi.closedOver())
                continue;
            callobj->setAliasedBinding(cx, fi, frame.unaliasedFormal(fi.argumentSlot(),
                                                                     DONT_CHECK_ALIASING));
        }
    }

    return callobj;
}

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

    RootedScript script(cx, callee->nonLazyScript());
    Rooted<FunctionScope*> scope(cx, &script->bodyScope()->as<FunctionScope>());
    RootedShape shape(cx, FunctionScope::getEmptyEnvironmentShape(cx, scope->hasParameterExprs()));
    if (!shape)
        return nullptr;
    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
    if (!group)
        return nullptr;
    Rooted<CallObject*> callobj(cx, create(cx, shape, group));
    if (!callobj)
        return nullptr;

    // This environment's enclosing link is never used: the
    // DebugEnvironmentProxy that refers to this scope carries its own
    // enclosing link, which is what Debugger uses to construct the tree of
    // Debugger.Environment objects.
    callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
    callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee));

    RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
    RootedId id(cx);
    for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
        id = NameToId(bi.name()->asPropertyName());
        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)
};

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

/* static */ VarEnvironmentObject*
VarEnvironmentObject::create(JSContext* cx, HandleShape shape, HandleObject enclosing,
                             gc::InitialHeap heap)
{
    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);

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

    MOZ_ASSERT(!obj->inDictionaryMode());
    MOZ_ASSERT(obj->isDelegate());

    VarEnvironmentObject* env = &obj->as<VarEnvironmentObject>();
    env->initEnclosingEnvironment(enclosing);

    return env;
}

/* static */ VarEnvironmentObject*
VarEnvironmentObject::create(JSContext* cx, HandleScope scope, AbstractFramePtr frame)
{
#ifdef DEBUG
    if (frame.isEvalFrame()) {
        MOZ_ASSERT(scope->is<EvalScope>() && scope == frame.script()->bodyScope());
        MOZ_ASSERT_IF(frame.isInterpreterFrame(),
                      cx->interpreterFrame() == frame.asInterpreterFrame());
        MOZ_ASSERT_IF(frame.isInterpreterFrame(),
                      cx->interpreterRegs().pc == frame.script()->code());
    } else {
        MOZ_ASSERT(frame.environmentChain());
        MOZ_ASSERT_IF(frame.callee()->needsCallObject(),
                      &frame.environmentChain()->as<CallObject>().callee() == frame.callee());
    }
#endif

    RootedScript script(cx, frame.script());
    RootedObject envChain(cx, frame.environmentChain());
    gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
    RootedShape shape(cx, scope->environmentShape());
    VarEnvironmentObject* env = create(cx, shape, envChain, heap);
    if (!env)
        return nullptr;
    env->initScope(scope);
    return env;
}

/* static */ VarEnvironmentObject*
VarEnvironmentObject::createHollowForDebug(JSContext* cx, Handle<VarScope*> scope)
{
    MOZ_ASSERT(!scope->hasEnvironment());

    RootedShape shape(cx, VarScope::getEmptyEnvironmentShape(cx));
    if (!shape)
        return nullptr;

    // This environment's enclosing link is never used: the
    // DebugEnvironmentProxy that refers to this scope carries its own
    // enclosing link, which is what Debugger uses to construct the tree of
    // Debugger.Environment objects.
    RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment());
    Rooted<VarEnvironmentObject*> env(cx, create(cx, shape, enclosingEnv, gc::TenuredHeap));
    if (!env)
        return nullptr;

    RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
    RootedId id(cx);
    for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
        id = NameToId(bi.name()->asPropertyName());
        if (!SetProperty(cx, env, id, optimizedOut))
            return nullptr;
    }

    env->initScope(scope);
    return env;
}

const Class VarEnvironmentObject::class_ = {
    "Var",
    JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(VarEnvironmentObject::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->bodyScope()->as<ModuleScope>().environmentShape());
    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 env(cx, &obj->as<ModuleEnvironmentObject>());

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

    // Initialize this early so that we can manipulate the env object without
    // causing assertions.
    env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());

    // Initialize all lexical bindings and imports as uninitialized. Imports
    // get uninitialized because they have a special TDZ for cyclic imports.
    for (BindingIter bi(script); bi; bi++) {
        BindingLocation loc = bi.location();
        if (loc.kind() == BindingLocation::Kind::Environment && BindingKindIsLexical(bi.kind()))
            env->initSlot(loc.slot(), MagicValue(JS_UNINITIALIZED_LEXICAL));
    }

    // 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(env->lastProperty()); !r.empty(); r.popFront())
        MOZ_ASSERT(!r.front().configurable());
    MOZ_ASSERT(env->lastProperty()->getObjectFlags() & BaseShape::NOT_EXTENSIBLE);
    MOZ_ASSERT(!env->inDictionaryMode());
#endif

    return env;
}

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);
}

void
ModuleEnvironmentObject::fixEnclosingEnvironmentAfterCompartmentMerge(GlobalObject& global)
{
    setEnclosingEnvironment(&global.lexicalEnvironment());
}

/* 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;
}

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

WithEnvironmentObject*
WithEnvironmentObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
                              Handle<WithScope*> scope)
{
    Rooted<WithEnvironmentObject*> obj(cx);
    obj = NewObjectWithNullTaggedProto<WithEnvironmentObject>(cx, GenericObject,
                                                              BaseShape::DELEGATE);
    if (!obj)
        return nullptr;

    Value thisv = GetThisValue(object);

    obj->initEnclosingEnvironment(enclosing);
    obj->initReservedSlot(OBJECT_SLOT, ObjectValue(*object));
    obj->initReservedSlot(THIS_SLOT, thisv);
    if (scope)
        obj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
    else
        obj->initReservedSlot(SCOPE_SLOT, NullValue());

    return obj;
}

WithEnvironmentObject*
WithEnvironmentObject::createNonSyntactic(JSContext* cx, HandleObject object,
                                          HandleObject enclosing)
{
    return create(cx, object, enclosing, nullptr);
}

static inline bool
IsUnscopableDotName(JSContext* cx, HandleId id)
{
    return JSID_IS_ATOM(id, cx->names().dotThis) || JSID_IS_ATOM(id, cx->names().dotGenerator);
}

/* 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)
{
    // SpiderMonkey-specific: consider internal '.generator' and '.this' names
    // to be unscopable.
    if (IsUnscopableDotName(cx, id)) {
        objp.set(nullptr);
        propp.set(nullptr);
        return true;
    }

    RootedObject actual(cx, &obj->as<WithEnvironmentObject>().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(!IsUnscopableDotName(cx, id));
    RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
    return DefineProperty(cx, actual, id, desc, result);
}

static bool
with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
    MOZ_ASSERT(!IsUnscopableDotName(cx, id));
    RootedObject actual(cx, &obj->as<WithEnvironmentObject>().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(!IsUnscopableDotName(cx, id));
    RootedObject actual(cx, &obj->as<WithEnvironmentObject>().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(!IsUnscopableDotName(cx, id));
    RootedObject actual(cx, &obj->as<WithEnvironmentObject>().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(!IsUnscopableDotName(cx, id));
    RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
    return GetOwnPropertyDescriptor(cx, actual, id, desc);
}

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

static const ObjectOps WithEnvironmentObjectOps = {
    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 WithEnvironmentObject::class_ = {
    "With",
    JSCLASS_HAS_RESERVED_SLOTS(WithEnvironmentObject::RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS,
    JS_NULL_CLASS_OPS,
    JS_NULL_CLASS_SPEC,
    JS_NULL_CLASS_EXT,
    &WithEnvironmentObjectOps
};

/* static */ NonSyntacticVariablesObject*
NonSyntacticVariablesObject::create(JSContext* cx)
{
    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->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
    return obj;
}

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

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

/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createTemplateObject(JSContext* cx, HandleShape shape,
                                               HandleObject enclosing, gc::InitialHeap heap)
{
    MOZ_ASSERT(shape->getObjectClass() == &LexicalEnvironmentObject::class_);

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

    gc::AllocKind allocKind = gc::GetGCObjectKind(shape->numFixedSlots());
    MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &LexicalEnvironmentObject::class_));
    allocKind = GetBackgroundAllocKind(allocKind);
    RootedNativeObject obj(cx,
        MaybeNativeObject(JSObject::create(cx, allocKind, heap, shape, group)));
    if (!obj)
        return nullptr;

    MOZ_ASSERT(!obj->inDictionaryMode());
    MOZ_ASSERT(obj->isDelegate());

    LexicalEnvironmentObject* env = &obj->as<LexicalEnvironmentObject>();
    if (enclosing)
        env->initEnclosingEnvironment(enclosing);

    return env;
}

/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createTemplateObject(JSContext* cx, Handle<LexicalScope*> scope,
                                               HandleObject enclosing, gc::InitialHeap heap)
{
    assertSameCompartment(cx, enclosing);
    MOZ_ASSERT(scope->hasEnvironment());

    RootedShape shape(cx, scope->environmentShape());
    LexicalEnvironmentObject* env = createTemplateObject(cx, shape, enclosing, heap);
    if (!env)
        return nullptr;

    // All lexical bindings start off uninitialized for TDZ.
    uint32_t lastSlot = shape->slot();
    MOZ_ASSERT(lastSlot == env->lastProperty()->slot());
    for (uint32_t slot = JSSLOT_FREE(&class_); slot <= lastSlot; slot++)
        env->initSlot(slot, MagicValue(JS_UNINITIALIZED_LEXICAL));

    env->initScopeUnchecked(scope);
    return env;
}

/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::create(JSContext* cx, Handle<LexicalScope*> scope,
                                 AbstractFramePtr frame)
{
    RootedObject enclosing(cx, frame.environmentChain());
    return createTemplateObject(cx, scope, enclosing, gc::DefaultHeap);
}

/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createGlobal(JSContext* cx, Handle<GlobalObject*> global)
{
    MOZ_ASSERT(global);

    RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
    if (!shape)
        return nullptr;

    Rooted<LexicalEnvironmentObject*> env(cx,
        LexicalEnvironmentObject::createTemplateObject(cx, shape, global, gc::TenuredHeap));
    if (!env)
        return nullptr;

    if (!JSObject::setSingleton(cx, env))
        return nullptr;

    env->initThisValue(global);
    return env;
}

/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createNonSyntactic(JSContext* cx, HandleObject enclosing)
{
    MOZ_ASSERT(enclosing);
    MOZ_ASSERT(!IsSyntacticEnvironment(enclosing));

    RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
    if (!shape)
        return nullptr;

    LexicalEnvironmentObject* env =
        LexicalEnvironmentObject::createTemplateObject(cx, shape, enclosing, gc::TenuredHeap);
    if (!env)
        return nullptr;

    env->initThisValue(enclosing);
    return env;
}

/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createHollowForDebug(JSContext* cx, Handle<LexicalScope*> scope)
{
    MOZ_ASSERT(!scope->hasEnvironment());

    RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
    if (!shape)
        return nullptr;

    // This environment's enclosing link is never used: the
    // DebugEnvironmentProxy that refers to this scope carries its own
    // enclosing link, which is what Debugger uses to construct the tree of
    // Debugger.Environment objects.
    RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment());
    Rooted<LexicalEnvironmentObject*> env(cx, createTemplateObject(cx, shape, enclosingEnv,
                                                                   gc::TenuredHeap));
    if (!env)
        return nullptr;

    RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
    RootedId id(cx);
    for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
        id = NameToId(bi.name()->asPropertyName());
        if (!SetProperty(cx, env, id, optimizedOut))
            return nullptr;
    }

    if (!env->setFlags(cx, BaseShape::NOT_EXTENSIBLE, JSObject::GENERATE_SHAPE))
        return nullptr;

    env->initScopeUnchecked(scope);
    return env;
}

/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::clone(JSContext* cx, Handle<LexicalEnvironmentObject*> env)
{
    Rooted<LexicalScope*> scope(cx, &env->scope());
    RootedObject enclosing(cx, &env->enclosingEnvironment());
    Rooted<LexicalEnvironmentObject*> copy(cx, createTemplateObject(cx, scope, enclosing,
                                                                    gc::TenuredHeap));
    if (!copy)
        return nullptr;

    // We can't assert that the clone has the same shape, because it could
    // have been reshaped by PurgeEnvironmentChain.
    MOZ_ASSERT(env->slotSpan() == copy->slotSpan());
    for (uint32_t i = JSSLOT_FREE(&class_); i < copy->slotSpan(); i++)
        copy->setSlot(i, env->getSlot(i));

    return copy;
}

/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::recreate(JSContext* cx, Handle<LexicalEnvironmentObject*> env)
{
    Rooted<LexicalScope*> scope(cx, &env->scope());
    RootedObject enclosing(cx, &env->enclosingEnvironment());
    return createTemplateObject(cx, scope, enclosing, gc::TenuredHeap);
}

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

Value
LexicalEnvironmentObject::thisValue() const
{
    MOZ_ASSERT(isExtensible());
    Value v = getReservedSlot(THIS_VALUE_OR_SCOPE_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_OR_SCOPE_SLOT, but it's possible the
        // WindowProxy was attached to the global *after* we set
        // THIS_VALUE_OR_SCOPE_SLOT.
        return ObjectValue(*ToWindowProxyIfWindow(&v.toObject()));
    }
    return v;
}

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

/* static */ NamedLambdaObject*
NamedLambdaObject::create(JSContext* cx, HandleFunction callee,
                          HandleFunction func,
                          HandleObject enclosing,
                          gc::InitialHeap heap)
{
    MOZ_ASSERT(callee->isNamedLambda());
    RootedScope scope(cx, callee->nonLazyScript()->maybeNamedLambdaScope());
    MOZ_ASSERT(scope && scope->environmentShape());
    MOZ_ASSERT(scope->environmentShape()->slot() == lambdaSlot());
    MOZ_ASSERT(!scope->environmentShape()->writable());

#ifdef DEBUG
    // There should be exactly one binding in the named lambda scope.
    BindingIter bi(scope);
    bi++;
    MOZ_ASSERT(bi.done());
#endif

    LexicalEnvironmentObject* obj =
        LexicalEnvironmentObject::createTemplateObject(cx, scope.as<LexicalScope>(),
                                                       enclosing, heap);
    if (!obj)
        return nullptr;

    obj->initFixedSlot(lambdaSlot(), ObjectValue(*func));
    return static_cast<NamedLambdaObject*>(obj);
}

/* static */ NamedLambdaObject*
NamedLambdaObject::createTemplateObject(JSContext* cx, HandleFunction callee, gc::InitialHeap heap)
{
    return create(cx, callee, callee, nullptr, heap);
}

/* static */ NamedLambdaObject*
NamedLambdaObject::create(JSContext* cx, AbstractFramePtr frame)
{
    RootedFunction fun(cx, frame.callee());
    RootedObject enclosing(cx, frame.environmentChain());
    return create(cx, fun, fun, enclosing, gc::DefaultHeap);
}

/* static */ NamedLambdaObject*
NamedLambdaObject::create(JSContext* cx, AbstractFramePtr frame, HandleFunction replacement)
{
    RootedFunction fun(cx, frame.callee());
    RootedObject enclosing(cx, frame.environmentChain());
    return create(cx, fun, replacement, enclosing, gc::DefaultHeap);
}

/* static */ size_t
NamedLambdaObject::lambdaSlot()
{
    // Named lambda environments have exactly one name.
    return JSSLOT_FREE(&LexicalEnvironmentObject::class_);
}

/* static */ RuntimeLexicalErrorObject*
RuntimeLexicalErrorObject::create(JSContext* cx, HandleObject enclosing, unsigned errorNumber)
{
    RuntimeLexicalErrorObject* obj =
        NewObjectWithNullTaggedProto<RuntimeLexicalErrorObject>(cx, GenericObject,
                                                                BaseShape::DELEGATE);
    if (!obj)
        return nullptr;
    obj->initEnclosingEnvironment(enclosing);
    obj->initReservedSlot(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
};

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

EnvironmentIter::EnvironmentIter(JSContext* cx, const EnvironmentIter& ei
                                 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
  : si_(cx, ei.si_.get()),
    env_(cx, ei.env_),
    frame_(ei.frame_)
{
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}

EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope
                                 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
  : si_(cx, ScopeIter(scope)),
    env_(cx, env),
    frame_(NullFramePtr())
{
    settle();
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}

EnvironmentIter::EnvironmentIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
                                 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
  : si_(cx, frame.script()->innermostScope(pc)),
    env_(cx, frame.environmentChain()),
    frame_(frame)
{
    assertSameCompartment(cx, frame);
    settle();
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}

void
EnvironmentIter::incrementScopeIter()
{
    if (si_.scope()->is<GlobalScope>()) {
        // GlobalScopes may be syntactic or non-syntactic. Non-syntactic
        // GlobalScopes correspond to zero or more non-syntactic
        // EnvironmentsObjects followed by the global lexical scope, then the
        // GlobalObject or another non-EnvironmentObject object.
        if (!env_->is<EnvironmentObject>())
            si_++;
    } else {
        si_++;
    }
}

void
EnvironmentIter::settle()
{
    // Check for trying to iterate a function or eval frame before the prologue has
    // created the CallObject, in which case we have to skip.
    if (frame_ && frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment()) {
        // Skip until we're at the enclosing scope of the script.
        while (si_.scope() != frame_.script()->enclosingScope()) {
            if (env_->is<LexicalEnvironmentObject>() &&
                !env_->as<LexicalEnvironmentObject>().isExtensible() &&
                &env_->as<LexicalEnvironmentObject>().scope() == si_.scope())
            {
                MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda ||
                           si_.kind() == ScopeKind::StrictNamedLambda);
                env_ = &env_->as<EnvironmentObject>().enclosingEnvironment();
            }
            incrementScopeIter();
        }
    }

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

#ifdef DEBUG
    if (si_) {
        if (hasSyntacticEnvironment()) {
            Scope* scope = si_.scope();
            if (scope->is<LexicalScope>()) {
                MOZ_ASSERT(scope == &env_->as<LexicalEnvironmentObject>().scope());
            } else if (scope->is<FunctionScope>()) {
                MOZ_ASSERT(scope->as<FunctionScope>().script() ==
                           env_->as<CallObject>().callee().existingScriptNonDelazifying());
            } else if (scope->is<VarScope>()) {
                MOZ_ASSERT(scope == &env_->as<VarEnvironmentObject>().scope());
            } else if (scope->is<WithScope>()) {
                MOZ_ASSERT(scope == &env_->as<WithEnvironmentObject>().scope());
            } else if (scope->is<EvalScope>()) {
                MOZ_ASSERT(scope == &env_->as<VarEnvironmentObject>().scope());
            } else if (scope->is<GlobalScope>()) {
                MOZ_ASSERT(env_->is<GlobalObject>() || IsGlobalLexicalEnvironment(env_));
            }
        } else if (hasNonSyntacticEnvironmentObject()) {
            if (env_->is<LexicalEnvironmentObject>()) {
                // The global lexical environment still encloses non-syntactic
                // environment objects.
                MOZ_ASSERT(!env_->as<LexicalEnvironmentObject>().isSyntactic() ||
                           env_->as<LexicalEnvironmentObject>().isGlobal());
            } else if (env_->is<WithEnvironmentObject>()) {
                MOZ_ASSERT(!env_->as<WithEnvironmentObject>().isSyntactic());
            } else {
                MOZ_ASSERT(env_->is<NonSyntacticVariablesObject>());
            }
        }
    }
#endif
}

JSObject&
EnvironmentIter::enclosingEnvironment() const
{
    // As an engine invariant (maintained internally and asserted by Execute),
    // EnvironmentObjects and non-EnvironmentObjects cannot be interleaved on
    // the scope chain; every scope chain must start with zero or more
    // EnvironmentObjects and terminate with one or more
    // non-EnvironmentObjects (viz., GlobalObject).
    MOZ_ASSERT(done());
    MOZ_ASSERT(!env_->is<EnvironmentObject>());
    return *env_;
}

bool
EnvironmentIter::hasNonSyntacticEnvironmentObject() const
{
    // The case we're worrying about here is a NonSyntactic static scope
    // which has 0+ corresponding non-syntactic WithEnvironmentObject
    // scopes, a NonSyntacticVariablesObject, or a non-syntactic
    // LexicalEnvironmentObject.
    if (si_.kind() == ScopeKind::NonSyntactic) {
        MOZ_ASSERT_IF(env_->is<WithEnvironmentObject>(),
                      !env_->as<WithEnvironmentObject>().isSyntactic());
        return env_->is<EnvironmentObject>();
    }
    return false;
}

/* static */ HashNumber
MissingEnvironmentKey::hash(MissingEnvironmentKey ek)
{
    return size_t(ek.frame_.raw()) ^ size_t(ek.scope_);
}

/* static */ bool
MissingEnvironmentKey::match(MissingEnvironmentKey ek1, MissingEnvironmentKey ek2)
{
    return ek1.frame_ == ek2.frame_ && ek1.scope_ == ek2.scope_;
}

bool
LiveEnvironmentVal::needsSweep()
{
    if (scope_)
        MOZ_ALWAYS_FALSE(IsAboutToBeFinalized(&scope_));
    return false;
}

// Live EnvironmentIter values may be added to DebugEnvironments::liveEnvs, as
// LiveEnvironmentVal 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 LiveEnvironmentVal and MissingEnvironmentKey need to
// alias each other.
void
LiveEnvironmentVal::staticAsserts()
{
    static_assert(sizeof(LiveEnvironmentVal) == sizeof(MissingEnvironmentKey),
                  "LiveEnvironmentVal must be same size of MissingEnvironmentKey");
    static_assert(offsetof(LiveEnvironmentVal, scope_) == offsetof(MissingEnvironmentKey, scope_),
                  "LiveEnvironmentVal.scope_ must alias MissingEnvironmentKey.scope_");
}

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

namespace {

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

/*
 * DebugEnvironmentProxy is the handler for DebugEnvironmentProxy proxy
 * objects. Having a custom handler (rather than trying to reuse js::Wrapper)
 * gives us several important abilities:
 *  - We want to pass the EnvironmentObject 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. DebugEnvironmentProxy 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 DebugEnvironmentProxyHandler : 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 CallObject and LexicalEnvironmentObject
     * environments and thus must be recovered from somewhere else:
     *  + if the invocation for which the env was created is still executing,
     *    there is a JS frame live on the stack holding the values;
     *  + if the invocation for which the env was created finished executing:
     *     - and there was a DebugEnvironmentProxy associated with env, then
     *       the DebugEnvironments::onPop(Call|Lexical) handler copied out the
     *       unaliased variables. In both cases, a dense array is created in
     *       onPop(Call|Lexical) to hold the unaliased values and attached to
     *       the DebugEnvironmentProxy;
     *     - and there was not a DebugEnvironmentProxy 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 and the
     *                     action is GET; if the action is SET, we assign to the
     *                     name of the variable on the environment object
     */
    bool handleUnaliasedAccess(JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
                               Handle<EnvironmentObject*> env, HandleId id, Action action,
                               MutableHandleValue vp, AccessResult* accessResult) const
    {
        MOZ_ASSERT(&debugEnv->environment() == env);
        MOZ_ASSERT_IF(action == SET, !debugEnv->isOptimizedOut());
        *accessResult = ACCESS_GENERIC;
        LiveEnvironmentVal* maybeLiveEnv = DebugEnvironments::hasLiveEnvironment(*env);

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

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

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

            if (!bi.hasArgumentSlot()) {
                if (bi.closedOver())
                    return true;

                uint32_t i = bi.location().slot();
                if (maybeLiveEnv) {
                    AbstractFramePtr frame = maybeLiveEnv->frame();
                    if (action == GET)
                        vp.set(frame.unaliasedLocal(i));
                    else
                        frame.unaliasedLocal(i) = vp;
                } else if (NativeObject* snapshot = debugEnv->maybeSnapshot()) {
                    if (action == GET)
                        vp.set(snapshot->getDenseElement(script->numArgs() + i));
                    else
                        snapshot->setDenseElement(script->numArgs() + i, vp);
                } else {
                    /* The unaliased value has been lost to the debugger. */
                    if (action == GET) {
                        *accessResult = ACCESS_LOST;
                        return true;
                    }
                }
            } else {
                unsigned i = bi.argumentSlot();
                if (bi.closedOver())
                    return true;

                if (maybeLiveEnv) {
                    AbstractFramePtr frame = maybeLiveEnv->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 = debugEnv->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 vars in functions with parameter expressions and
         * lexical bindings at block scope.
         */
        if (env->is<LexicalEnvironmentObject>() || env->is<VarEnvironmentObject>()) {
            // Currently consider all global and non-syntactic top-level lexical
            // bindings to be aliased.
            if (env->is<LexicalEnvironmentObject>() &&
                env->as<LexicalEnvironmentObject>().isExtensible())
            {
                MOZ_ASSERT(IsGlobalLexicalEnvironment(env) || !IsSyntacticEnvironment(env));
                return true;
            }

            // Currently all vars inside eval var environments are aliased.
            if (env->is<VarEnvironmentObject>() && env->as<VarEnvironmentObject>().isForEval())
                return true;

            RootedScope scope(cx, getEnvironmentScope(*env));
            uint32_t firstFrameSlot;
            if (env->is<LexicalEnvironmentObject>())
                firstFrameSlot = scope->as<LexicalScope>().firstFrameSlot();
            else
                firstFrameSlot = scope->as<VarScope>().firstFrameSlot();

            BindingIter bi(scope);
            while (bi && NameToId(bi.name()->asPropertyName()) != id)
                bi++;
            if (!bi)
                return true;

            BindingLocation loc = bi.location();
            if (loc.kind() == BindingLocation::Kind::Environment)
                return true;

            // Named lambdas that are not closed over are lost.
            if (loc.kind() == BindingLocation::Kind::NamedLambdaCallee) {
                if (action == GET)
                    *accessResult = ACCESS_LOST;
                return true;
            }

            MOZ_ASSERT(loc.kind() == BindingLocation::Kind::Frame);

            if (maybeLiveEnv) {
                AbstractFramePtr frame = maybeLiveEnv->frame();
                uint32_t local = loc.slot();
                MOZ_ASSERT(local < frame.script()->nfixed());
                if (action == GET)
                    vp.set(frame.unaliasedLocal(local));
                else
                    frame.unaliasedLocal(local) = vp;
            } else if (NativeObject* snapshot = debugEnv->maybeSnapshot()) {
                // Indices in the frame snapshot are offset by the first frame
                // slot. See DebugEnvironments::takeFrameSnapshot.
                MOZ_ASSERT(loc.slot() >= firstFrameSlot);
                uint32_t snapshotIndex = loc.slot() - firstFrameSlot;
                if (action == GET)
                    vp.set(snapshot->getDenseElement(snapshotIndex));
                else
                    snapshot->setDenseElement(snapshotIndex, vp);
            } else {
                if (action == GET) {
                    // A {Lexical,Var}EnvironmentObject whose static scope
                    // does not have an environment shape at all is a "hollow"
                    // block object reflected for missing block scopes. Their
                    // slot values are lost.
                    if (!scope->hasEnvironment()) {
                        *accessResult = ACCESS_LOST;
                        return true;
                    }

                    if (!GetProperty(cx, env, env, id, vp))
                        return false;
                } else {
                    if (!SetProperty(cx, env, id, vp))
                        return false;
                }
            }

            // 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(!IsSyntacticEnvironment(env) ||
                   env->is<WithEnvironmentObject>());
        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 isFunctionEnvironment(const JSObject& env)
    {
        return env.is<CallObject>();
    }

    static bool isNonExtensibleLexicalEnvironment(const JSObject& env)
    {
        return env.is<LexicalEnvironmentObject>() &&
               !env.as<LexicalEnvironmentObject>().isExtensible();
    }

    static Scope* getEnvironmentScope(const JSObject& env)
    {
        if (isFunctionEnvironment(env))
            return env.as<CallObject>().callee().nonLazyScript()->bodyScope();
        if (isNonExtensibleLexicalEnvironment(env))
            return &env.as<LexicalEnvironmentObject>().scope();
        if (env.is<VarEnvironmentObject>())
            return &env.as<VarEnvironmentObject>().scope();
        return nullptr;
    }

    /*
     * In theory, every non-arrow 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(EnvironmentObject& env)
    {
        return isFunctionEnvironment(env) &&
               !env.as<CallObject>().callee().nonLazyScript()->argumentsHasVarBinding();
    }

    /*
     * Similar to 'arguments' above, we don't add a 'this' binding to
     * non-arrow functions if it's not used.
     */
    static bool isMissingThisBinding(EnvironmentObject& env)
    {
        return isFunctionEnvironmentWithThis(env) &&
               !env.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, EnvironmentObject& env)
    {
        return isArguments(cx, id) && isFunctionEnvironment(env) &&
               !env.as<CallObject>().callee().nonLazyScript()->needsArgsObj();
    }
    static bool isMissingThis(JSContext* cx, jsid id, EnvironmentObject& env)
    {
        return isThis(cx, id) && isMissingThisBinding(env);
    }

    /*
     * 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, EnvironmentObject& env, HandleValue v)
    {
        bool isMagic = v.isMagic() && v.whyMagic() == JS_OPTIMIZED_ARGUMENTS;

#ifdef DEBUG
        // The |env| object here is not limited to CallObjects but may also
        // be lexical envs in case of the following:
        //
        //   function f() { { let a = arguments; } }
        //
        // We need to check that |env|'s scope's nearest function scope has an
        // 'arguments' var binding. The environment 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 (isFunctionEnvironment(env)) {
                callee = &env.as<CallObject>().callee();
            } else {
                // We will never have a WithEnvironmentObject here because no
                // binding accesses on with scopes are unaliased.
                for (ScopeIter si(getEnvironmentScope(env)); si; si++) {
                    if (si.kind() == ScopeKind::Function) {
                        callee = si.scope()->as<FunctionScope>().canonicalFunction();
                        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, const 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 env is dead.
     */
    static bool createMissingArguments(JSContext* cx, EnvironmentObject& env,
                                       MutableHandleArgumentsObject argsObj)
    {
        argsObj.set(nullptr);

        LiveEnvironmentVal* maybeEnv = DebugEnvironments::hasLiveEnvironment(env);
        if (!maybeEnv)
            return true;

        argsObj.set(ArgumentsObject::createUnexpected(cx, maybeEnv->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, EnvironmentObject& env,
                                  MutableHandleValue thisv, bool* success)
    {
        *success = false;

        LiveEnvironmentVal* maybeEnv = DebugEnvironments::hasLiveEnvironment(env);
        if (!maybeEnv)
            return true;

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

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

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

    constexpr DebugEnvironmentProxyHandler() : BaseProxyHandler(&family) {}

    static bool isFunctionEnvironmentWithThis(const JSObject& env)
    {
        // All functions except arrows and generator expression lambdas should
        // have their own this binding.
        return isFunctionEnvironment(env) && !env.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 DebugEnvironmentProxyHandler");
    }

    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<DebugEnvironmentProxy*> debugEnv,
                                               EnvironmentObject& env,
                                               MutableHandle<PropertyDescriptor> desc) const
    {
        RootedArgumentsObject argsObj(cx);
        if (!createMissingArguments(cx, env, &argsObj))
            return false;

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

        desc.object().set(debugEnv);
        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<DebugEnvironmentProxy*> debugEnv,
                                          EnvironmentObject& env,
                                          MutableHandle<PropertyDescriptor> desc) const
    {
        RootedValue thisv(cx);
        bool success;
        if (!createMissingThis(cx, env, &thisv, &success))
            return false;

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

        desc.object().set(debugEnv);
        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<DebugEnvironmentProxy*> debugEnv(cx, &proxy->as<DebugEnvironmentProxy>());
        Rooted<EnvironmentObject*> env(cx, &debugEnv->environment());

        if (isMissingArguments(cx, id, *env))
            return getMissingArgumentsPropertyDescriptor(cx, debugEnv, *env, desc);

        if (isMissingThis(cx, id, *env))
            return getMissingThisPropertyDescriptor(cx, debugEnv, *env, desc);

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

        switch (access) {
          case ACCESS_UNALIASED:
            if (isMagicMissingArgumentsValue(cx, *env, v))
                return getMissingArgumentsPropertyDescriptor(cx, debugEnv, *env, desc);
            desc.object().set(debugEnv);
            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, env, id, desc);
          case ACCESS_LOST:
            ReportOptimizedOut(cx, id);
            return false;
          default:
            MOZ_CRASH("bad AccessResult");
        }
    }

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

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

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

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

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

        vp.set(thisv);
        return true;
    }

    bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
             MutableHandleValue vp) const override
    {
        Rooted<DebugEnvironmentProxy*> debugEnv(cx, &proxy->as<DebugEnvironmentProxy>());
        Rooted<EnvironmentObject*> env(cx, &proxy->as<DebugEnvironmentProxy>().environment());

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

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

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

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

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

    bool getMissingThisMaybeSentinelValue(JSContext* cx, EnvironmentObject& env,
                                          MutableHandleValue vp) const
    {
        RootedValue thisv(cx);
        bool success;
        if (!createMissingThis(cx, env, &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<DebugEnvironmentProxy*> debugEnv,
                               HandleId id, MutableHandleValue vp) const
    {
        Rooted<EnvironmentObject*> env(cx, &debugEnv->environment());

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

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

        switch (access) {
          case ACCESS_UNALIASED:
            if (isMagicMissingArgumentsValue(cx, *env, vp))
                return getMissingArgumentsMaybeSentinelValue(cx, *env, vp);
            if (isMaybeUninitializedThisValue(cx, id, vp))
                return getMissingThisMaybeSentinelValue(cx, *env, vp);
            return true;
          case ACCESS_GENERIC:
            if (!GetProperty(cx, env, env, id, vp))
                return false;
            if (isMaybeUninitializedThisValue(cx, id, vp))
                return getMissingThisMaybeSentinelValue(cx, *env, 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<DebugEnvironmentProxy*> debugEnv(cx, &proxy->as<DebugEnvironmentProxy>());
        Rooted<EnvironmentObject*> env(cx, &proxy->as<DebugEnvironmentProxy>().environment());

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

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

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

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

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

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

    bool ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const override
    {
        Rooted<EnvironmentObject*> env(cx, &proxy->as<DebugEnvironmentProxy>().environment());

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

        // WithEnvironmentObject 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 WithEnvironmentObject
        // 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.
        RootedObject target(cx);
        bool isWith = env->is<WithEnvironmentObject>();
        if (isWith)
            target = &env->as<WithEnvironmentObject>().object();
        else
            target = env;
        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, env, props[i], &inScope))
                    return false;
                if (inScope)
                    props[j++].set(props[i]);
            }
            if (!props.resize(j))
                return false;
        }

        /*
         * Environments with Scopes are optimized to not contain unaliased
         * variables so they must be manually appended here.
         */
        if (Scope* scope = getEnvironmentScope(*env)) {
            for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
                if (!bi.closedOver() && !props.append(NameToId(bi.name()->asPropertyName())))
                    return false;
            }
        }

        return true;
    }

    bool has(JSContext* cx, HandleObject proxy, HandleId id_, bool* bp) const override
    {
        RootedId id(cx, id_);
        EnvironmentObject& envObj = proxy->as<DebugEnvironmentProxy>().environment();

        if (isArguments(cx, id) && isFunctionEnvironment(envObj)) {
            *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 = isFunctionEnvironmentWithThis(envObj);
            return true;
        }

        bool found;
        RootedObject env(cx, &envObj);
        if (!JS_HasPropertyById(cx, env, id, &found))
            return false;

        if (!found) {
            if (Scope* scope = getEnvironmentScope(*env)) {
                for (BindingIter bi(scope); bi; bi++) {
                    if (!bi.closedOver() && NameToId(bi.name()->asPropertyName()) == 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::DebugEnvironmentProxy>() const
{
    return IsDerivedProxyObject(this, &DebugEnvironmentProxyHandler::singleton);
}

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

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

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

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

    return debugEnv;
}

EnvironmentObject&
DebugEnvironmentProxy::environment() const
{
    return target()->as<EnvironmentObject>();
}

JSObject&
DebugEnvironmentProxy::enclosingEnvironment() const
{
    return extra(ENCLOSING_EXTRA).toObject();
}

ArrayObject*
DebugEnvironmentProxy::maybeSnapshot() const
{
    JSObject* obj = extra(SNAPSHOT_EXTRA).toObjectOrNull();
    return obj ? &obj->as<ArrayObject>() : nullptr;
}

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

bool
DebugEnvironmentProxy::isForDeclarative() const
{
    EnvironmentObject& e = environment();
    return e.is<CallObject>() ||
           e.is<VarEnvironmentObject>() ||
           e.is<ModuleEnvironmentObject>() ||
           e.is<LexicalEnvironmentObject>();
}

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

bool
DebugEnvironmentProxy::isFunctionEnvironmentWithThis()
{
    return DebugEnvironmentProxyHandler::isFunctionEnvironmentWithThis(environment());
}

bool
DebugEnvironmentProxy::isOptimizedOut() const
{
    EnvironmentObject& e = environment();

    if (DebugEnvironments::hasLiveEnvironment(e))
        return false;

    if (e.is<LexicalEnvironmentObject>()) {
        return !e.as<LexicalEnvironmentObject>().isExtensible() &&
               !e.as<LexicalEnvironmentObject>().scope().hasEnvironment();
    }

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

    return false;
}

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

DebugEnvironments::DebugEnvironments(JSContext* cx)
 : proxiedEnvs(cx),
   missingEnvs(cx->runtime()),
   liveEnvs(cx->runtime())
{}

DebugEnvironments::~DebugEnvironments()
{
    MOZ_ASSERT_IF(missingEnvs.initialized(), missingEnvs.empty());
}

bool
DebugEnvironments::init()
{
    return proxiedEnvs.init() && missingEnvs.init() && liveEnvs.init();
}

void
DebugEnvironments::mark(JSTracer* trc)
{
    proxiedEnvs.trace(trc);
}

void
DebugEnvironments::sweep(JSRuntime* rt)
{
    /*
     * missingEnvs points to debug envs weakly so that debug envs can be
     * released more eagerly.
     */
    for (MissingEnvironmentMap::Enum e(missingEnvs); !e.empty(); e.popFront()) {
        if (IsAboutToBeFinalized(&e.front().value())) {
            /*
             * Note that onPopCall, onPopVar, and onPopLexical rely on
             * missingEnvs to find environment objects that we synthesized for
             * the debugger's sake, and clean up the synthetic environment
             * objects' entries in liveEnvs. So if we remove an entry from
             * missingEnvs here, we must also remove the corresponding
             * liveEnvs entry.
             *
             * Since the DebugEnvironmentProxy is the only thing using its environment
             * 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 liveEnvs
             * and missingEnvs here.
             */
            liveEnvs.remove(&e.front().value().unbarrieredGet()->environment());
            e.removeFront();
        } else {
            MissingEnvironmentKey key = e.front().key();
            if (IsForwarded(key.scope())) {
                key.updateScope(Forwarded(key.scope()));
                e.rekeyFront(key);
            }
        }
    }

    /*
     * Scopes can be finalized when a debugger-synthesized EnvironmentObject is
     * no longer reachable via its DebugEnvironmentProxy.
     */
    liveEnvs.sweep();
}

void
DebugEnvironments::finish()
{
    proxiedEnvs.clear();
}

#ifdef JSGC_HASH_TABLE_CHECKS
void
DebugEnvironments::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.
     */
    proxiedEnvs.checkAfterMovingGC();
    for (MissingEnvironmentMap::Range r = missingEnvs.all(); !r.empty(); r.popFront()) {
        CheckGCThingAfterMovingGC(r.front().key().scope());
        CheckGCThingAfterMovingGC(r.front().value().get());
    }
    for (LiveEnvironmentMap::Range r = liveEnvs.all(); !r.empty(); r.popFront()) {
        CheckGCThingAfterMovingGC(r.front().key());
        CheckGCThingAfterMovingGC(r.front().value().scope_.get());
    }
}
#endif

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

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

    auto debugEnvs = cx->make_unique<DebugEnvironments>(cx);
    if (!debugEnvs || !debugEnvs->init()) {
        ReportOutOfMemory(cx);
        return nullptr;
    }

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

/* static */ DebugEnvironmentProxy*
DebugEnvironments::hasDebugEnvironment(JSContext* cx, EnvironmentObject& env)
{
    DebugEnvironments* envs = env.compartment()->debugEnvs;
    if (!envs)
        return nullptr;

    if (JSObject* obj = envs->proxiedEnvs.lookup(&env)) {
        MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
        return &obj->as<DebugEnvironmentProxy>();
    }

    return nullptr;
}

/* static */ bool
DebugEnvironments::addDebugEnvironment(JSContext* cx, Handle<EnvironmentObject*> env,
                                       Handle<DebugEnvironmentProxy*> debugEnv)
{
    MOZ_ASSERT(cx->compartment() == env->compartment());
    MOZ_ASSERT(cx->compartment() == debugEnv->compartment());

    if (!CanUseDebugEnvironmentMaps(cx))
        return true;

    DebugEnvironments* envs = ensureCompartmentData(cx);
    if (!envs)
        return false;

    return envs->proxiedEnvs.add(cx, env, debugEnv);
}

/* static */ DebugEnvironmentProxy*
DebugEnvironments::hasDebugEnvironment(JSContext* cx, const EnvironmentIter& ei)
{
    MOZ_ASSERT(!ei.hasSyntacticEnvironment());

    DebugEnvironments* envs = cx->compartment()->debugEnvs;
    if (!envs)
        return nullptr;

    if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) {
        MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
        return p->value();
    }
    return nullptr;
}

/* static */ bool
DebugEnvironments::addDebugEnvironment(JSContext* cx, const EnvironmentIter& ei,
                                       Handle<DebugEnvironmentProxy*> debugEnv)
{
    MOZ_ASSERT(!ei.hasSyntacticEnvironment());
    MOZ_ASSERT(cx->compartment() == debugEnv->compartment());
    // Generators should always have environments.
    MOZ_ASSERT_IF(ei.scope().is<FunctionScope>(),
                  !ei.scope().as<FunctionScope>().canonicalFunction()->isGenerator());

    if (!CanUseDebugEnvironmentMaps(cx))
        return true;

    DebugEnvironments* envs = ensureCompartmentData(cx);
    if (!envs)
        return false;

    MissingEnvironmentKey key(ei);
    MOZ_ASSERT(!envs->missingEnvs.has(key));
    if (!envs->missingEnvs.put(key, ReadBarriered<DebugEnvironmentProxy*>(debugEnv))) {
        ReportOutOfMemory(cx);
        return false;
    }

    // Only add to liveEnvs if we synthesized the debug env on a live
    // frame.
    if (ei.withinInitialFrame()) {
        MOZ_ASSERT(!envs->liveEnvs.has(&debugEnv->environment()));
        if (!envs->liveEnvs.put(&debugEnv->environment(), LiveEnvironmentVal(ei))) {
            ReportOutOfMemory(cx);
            return false;
        }
    }

    return true;
}

/* static */ void
DebugEnvironments::takeFrameSnapshot(JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
                                     AbstractFramePtr frame)
{
    /*
     * When the JS stack frame is popped, the values of unaliased variables
     * are lost. If there is any debug env referring to this environment, save a
     * copy of the unaliased variables' values in an array for later debugger
     * access via DebugEnvironmentProxy::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 DebugEnvironmentProxy::maybeSnapshot can already be nullptr.
     */

    // Act like no snapshot was taken if we run OOM while taking the snapshot.
    Rooted<GCVector<Value>> vec(cx, GCVector<Value>(cx));
    if (debugEnv->environment().is<CallObject>()) {
        JSScript* script = frame.script();

        FunctionScope* scope = &script->bodyScope()->as<FunctionScope>();
        uint32_t frameSlotCount = scope->nextFrameSlot();
        MOZ_ASSERT(frameSlotCount <= script->nfixed());

        // For simplicity, copy all frame slots from 0 to the frameSlotCount,
        // even if we don't need all of them (like in the case of a defaults
        // parameter scope having frame slots).
        uint32_t numFormals = frame.numFormalArgs();
        if (!vec.resize(numFormals + frameSlotCount)) {
            cx->recoverFromOutOfMemory();
            return;
        }
        mozilla::PodCopy(vec.begin(), frame.argv(), numFormals);
        for (uint32_t slot = 0; slot < frameSlotCount; slot++)
            vec[slot + frame.numFormalArgs()].set(frame.unaliasedLocal(slot));

        /*
         * Copy in formals that are not aliased via the scope chain
         * but are aliased via the arguments object.
         */
        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));
            }
        }
    } else {
        uint32_t frameSlotStart;
        uint32_t frameSlotEnd;

        if (debugEnv->environment().is<LexicalEnvironmentObject>()) {
            LexicalScope* scope = &debugEnv->environment().as<LexicalEnvironmentObject>().scope();
            frameSlotStart = scope->firstFrameSlot();
            frameSlotEnd = scope->nextFrameSlot();
        } else {
            VarEnvironmentObject* env = &debugEnv->environment().as<VarEnvironmentObject>();
            if (frame.isFunctionFrame()) {
                VarScope* scope = &env->scope().as<VarScope>();
                frameSlotStart = scope->firstFrameSlot();
                frameSlotEnd = scope->nextFrameSlot();
            } else {
                EvalScope* scope = &env->scope().as<EvalScope>();
                MOZ_ASSERT(scope == frame.script()->bodyScope());
                frameSlotStart = 0;
                frameSlotEnd = scope->nextFrameSlot();
            }
        }

        uint32_t frameSlotCount = frameSlotEnd - frameSlotStart;
        MOZ_ASSERT(frameSlotCount <= frame.script()->nfixed());

        if (!vec.resize(frameSlotCount)) {
            cx->recoverFromOutOfMemory();
            return;
        }
        for (uint32_t slot = frameSlotStart; slot < frameSlotCount; slot++)
            vec[slot - frameSlotStart].set(frame.unaliasedLocal(slot));
    }

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

    /*
     * 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;
    }

    debugEnv->initSnapshot(*snapshot);
}

/* static */ void
DebugEnvironments::onPopCall(JSContext* cx, AbstractFramePtr frame)
{
    assertSameCompartment(cx, frame);

    DebugEnvironments* envs = cx->compartment()->debugEnvs;
    if (!envs)
        return;

    Rooted<DebugEnvironmentProxy*> debugEnv(cx, nullptr);

    FunctionScope* funScope = &frame.script()->bodyScope()->as<FunctionScope>();
    if (funScope->hasEnvironment()) {
        MOZ_ASSERT(frame.callee()->needsCallObject());

        /*
         * The frame may be observed before the prologue has created the
         * CallObject. See EnvironmentIter::settle.
         */
        if (!frame.environmentChain()->is<CallObject>())
            return;

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

        CallObject& callobj = frame.environmentChain()->as<CallObject>();
        envs->liveEnvs.remove(&callobj);
        if (JSObject* obj = envs->proxiedEnvs.lookup(&callobj))
            debugEnv = &obj->as<DebugEnvironmentProxy>();
    } else {
        MissingEnvironmentKey key(frame, funScope);
        if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(key)) {
            debugEnv = p->value();
            envs->liveEnvs.remove(&debugEnv->environment().as<CallObject>());
            envs->missingEnvs.remove(p);
        }
    }

    if (debugEnv)
        DebugEnvironments::takeFrameSnapshot(cx, debugEnv, frame);
}

void
DebugEnvironments::onPopLexical(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
    assertSameCompartment(cx, frame);

    DebugEnvironments* envs = cx->compartment()->debugEnvs;
    if (!envs)
        return;

    EnvironmentIter ei(cx, frame, pc);
    onPopLexical(cx, ei);
}

template <typename Environment, typename Scope>
void
DebugEnvironments::onPopGeneric(JSContext* cx, const EnvironmentIter& ei)
{
    DebugEnvironments* envs = cx->compartment()->debugEnvs;
    if (!envs)
        return;

    MOZ_ASSERT(ei.withinInitialFrame());
    MOZ_ASSERT(ei.scope().is<Scope>());

    Rooted<Environment*> env(cx);
    if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) {
        env = &p->value()->environment().as<Environment>();
        envs->missingEnvs.remove(p);
    } else if (ei.hasSyntacticEnvironment()) {
        env = &ei.environment().as<Environment>();
    }

    if (env) {
        envs->liveEnvs.remove(env);

        if (JSObject* obj = envs->proxiedEnvs.lookup(env)) {
            Rooted<DebugEnvironmentProxy*> debugEnv(cx, &obj->as<DebugEnvironmentProxy>());
            DebugEnvironments::takeFrameSnapshot(cx, debugEnv, ei.initialFrame());
        }
    }
}

void
DebugEnvironments::onPopLexical(JSContext* cx, const EnvironmentIter& ei)
{
    onPopGeneric<LexicalEnvironmentObject, LexicalScope>(cx, ei);
}

void
DebugEnvironments::onPopVar(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
    assertSameCompartment(cx, frame);

    DebugEnvironments* envs = cx->compartment()->debugEnvs;
    if (!envs)
        return;

    EnvironmentIter ei(cx, frame, pc);
    onPopVar(cx, ei);
}

void
DebugEnvironments::onPopVar(JSContext* cx, const EnvironmentIter& ei)
{
    if (ei.scope().is<EvalScope>())
        onPopGeneric<VarEnvironmentObject, EvalScope>(cx, ei);
    else
        onPopGeneric<VarEnvironmentObject, VarScope>(cx, ei);
}

void
DebugEnvironments::onPopWith(AbstractFramePtr frame)
{
    if (DebugEnvironments* envs = frame.compartment()->debugEnvs)
        envs->liveEnvs.remove(&frame.environmentChain()->as<WithEnvironmentObject>());
}

void
DebugEnvironments::onCompartmentUnsetIsDebuggee(JSCompartment* c)
{
    if (DebugEnvironments* envs = c->debugEnvs) {
        envs->proxiedEnvs.clear();
        envs->missingEnvs.clear();
        envs->liveEnvs.clear();
    }
}

bool
DebugEnvironments::updateLiveEnvironments(JSContext* cx)
{
    JS_CHECK_RECURSION(cx, return false);

    /*
     * Note that we must always update the top frame's environment objects'
     * entries in liveEnvs because we can't be sure code hasn't run in that
     * frame to change the environment chain since we were last called. The
     * fp->prevUpToDate() flag indicates whether the environments of frames
     * older than fp are already included in liveEnvs. 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.environmentChain()->compartment() != cx->compartment())
            continue;

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

        if (!frame.isDebuggee())
            continue;

        for (EnvironmentIter ei(cx, frame, i.pc()); ei.withinInitialFrame(); ei++) {
            if (ei.hasSyntacticEnvironment() && !ei.scope().is<GlobalScope>()) {
                MOZ_ASSERT(ei.environment().compartment() == cx->compartment());
                DebugEnvironments* envs = ensureCompartmentData(cx);
                if (!envs)
                    return false;
                if (!envs->liveEnvs.put(&ei.environment(), LiveEnvironmentVal(ei)))
                    return false;
            }
        }

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

    return true;
}

LiveEnvironmentVal*
DebugEnvironments::hasLiveEnvironment(EnvironmentObject& env)
{
    DebugEnvironments* envs = env.compartment()->debugEnvs;
    if (!envs)
        return nullptr;

    if (LiveEnvironmentMap::Ptr p = envs->liveEnvs.lookup(&env))
        return &p->value();

    return nullptr;
}

/* static */ void
DebugEnvironments::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 updateLiveEnvironments. 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.environmentChain()->compartment() != cx->compartment())
            continue;

        frame.unsetPrevUpToDate();
    }
}

/* static */ void
DebugEnvironments::forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to)
{
    DebugEnvironments* envs = cx->compartment()->debugEnvs;
    if (!envs)
        return;

    for (MissingEnvironmentMap::Enum e(envs->missingEnvs); !e.empty(); e.popFront()) {
        MissingEnvironmentKey key = e.front().key();
        if (key.frame() == from) {
            key.updateFrame(to);
            e.rekeyFront(key);
        }
    }

    for (LiveEnvironmentMap::Enum e(envs->liveEnvs); !e.empty(); e.popFront()) {
        LiveEnvironmentVal& val = e.front().value();
        if (val.frame() == from)
            val.updateFrame(to);
    }
}

/* static */ void
DebugEnvironments::markLiveFrame(JSTracer* trc, AbstractFramePtr frame)
{
    for (MissingEnvironmentMap::Enum e(missingEnvs); !e.empty(); e.popFront()) {
        if (e.front().key().frame() == frame)
            TraceEdge(trc, &e.front().value(), "debug-env-live-frame-missing-env");
    }
}

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

static JSObject*
GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei);

static DebugEnvironmentProxy*
GetDebugEnvironmentForEnvironmentObject(JSContext* cx, const EnvironmentIter& ei)
{
    Rooted<EnvironmentObject*> env(cx, &ei.environment());
    if (DebugEnvironmentProxy* debugEnv = DebugEnvironments::hasDebugEnvironment(cx, *env))
        return debugEnv;

    EnvironmentIter copy(cx, ei);
    RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
    if (!enclosingDebug)
        return nullptr;

    Rooted<DebugEnvironmentProxy*> debugEnv(cx,
        DebugEnvironmentProxy::create(cx, *env, enclosingDebug));
    if (!debugEnv)
        return nullptr;

    if (!DebugEnvironments::addDebugEnvironment(cx, env, debugEnv))
        return nullptr;

    return debugEnv;
}

static DebugEnvironmentProxy*
GetDebugEnvironmentForMissing(JSContext* cx, const EnvironmentIter& ei)
{
    MOZ_ASSERT(!ei.hasSyntacticEnvironment() &&
               (ei.scope().is<FunctionScope>() ||
                ei.scope().is<LexicalScope>() ||
                ei.scope().is<VarScope>()));

    if (DebugEnvironmentProxy* debugEnv = DebugEnvironments::hasDebugEnvironment(cx, ei))
        return debugEnv;

    EnvironmentIter copy(cx, ei);
    RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
    if (!enclosingDebug)
        return nullptr;

    /*
     * Create the missing environment object. For lexical environment 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
     * DebugEnvironmentProxy has a EnvironmentObject.
     *
     * Note: to preserve envChain depth invariants, these lazily-reified
     * envs must not be put on the frame's environment chain; instead, they are
     * maintained via DebugEnvironments hooks.
     */
    Rooted<DebugEnvironmentProxy*> debugEnv(cx);
    if (ei.scope().is<FunctionScope>()) {
        RootedFunction callee(cx, ei.scope().as<FunctionScope>().canonicalFunction());
        // Generators should always reify their scopes.
        MOZ_ASSERT(!callee->isGenerator());

        JS::ExposeObjectToActiveJS(callee);
        Rooted<CallObject*> callobj(cx, CallObject::createHollowForDebug(cx, callee));
        if (!callobj)
            return nullptr;

        debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug);
    } else if (ei.scope().is<LexicalScope>()) {
        Rooted<LexicalScope*> lexicalScope(cx, &ei.scope().as<LexicalScope>());
        Rooted<LexicalEnvironmentObject*> env(cx,
            LexicalEnvironmentObject::createHollowForDebug(cx, lexicalScope));
        if (!env)
            return nullptr;

        debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
    } else {
        Rooted<VarScope*> varScope(cx, &ei.scope().as<VarScope>());
        Rooted<VarEnvironmentObject*> env(cx,
            VarEnvironmentObject::createHollowForDebug(cx, varScope));
        if (!env)
            return nullptr;

        debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
    }

    if (!debugEnv)
        return nullptr;

    if (!DebugEnvironments::addDebugEnvironment(cx, ei, debugEnv))
        return nullptr;

    return debugEnv;
}

static JSObject*
GetDebugEnvironmentForNonEnvironmentObject(const EnvironmentIter& ei)
{
    JSObject& enclosing = ei.enclosingEnvironment();
#ifdef DEBUG
    JSObject* o = &enclosing;
    while ((o = o->enclosingEnvironment()))
        MOZ_ASSERT(!o->is<EnvironmentObject>());
#endif
    return &enclosing;
}

static JSObject*
GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei)
{
    JS_CHECK_RECURSION(cx, return nullptr);

    if (ei.done())
        return GetDebugEnvironmentForNonEnvironmentObject(ei);

    if (ei.hasAnyEnvironmentObject())
        return GetDebugEnvironmentForEnvironmentObject(cx, ei);

    if (ei.scope().is<FunctionScope>() ||
        ei.scope().is<LexicalScope>() ||
        ei.scope().is<VarScope>())
    {
        return GetDebugEnvironmentForMissing(cx, ei);
    }

    EnvironmentIter copy(cx, ei);
    return GetDebugEnvironment(cx, ++copy);
}

JSObject*
js::GetDebugEnvironmentForFunction(JSContext* cx, HandleFunction fun)
{
    assertSameCompartment(cx, fun);
    MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
    if (!DebugEnvironments::updateLiveEnvironments(cx))
        return nullptr;
    JSScript* script = fun->getOrCreateScript(cx);
    if (!script)
        return nullptr;
    EnvironmentIter ei(cx, fun->environment(), script->enclosingScope());
    return GetDebugEnvironment(cx, ei);
}

JSObject*
js::GetDebugEnvironmentForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
    assertSameCompartment(cx, frame);
    if (CanUseDebugEnvironmentMaps(cx) && !DebugEnvironments::updateLiveEnvironments(cx))
        return nullptr;

    EnvironmentIter ei(cx, frame, pc);
    return GetDebugEnvironment(cx, ei);
}

JSObject*
js::GetDebugEnvironmentForGlobalLexicalEnvironment(JSContext* cx)
{
    EnvironmentIter ei(cx, &cx->global()->lexicalEnvironment(), &cx->global()->emptyGlobalScope());
    return GetDebugEnvironment(cx, ei);
}

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

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

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

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

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

    // Construct With object wrappers for the things on this environment chain
    // and use the result as the thing to scope the function to.
    Rooted<WithEnvironmentObject*> withEnv(cx);
    RootedObject enclosingEnv(cx, terminatingEnv);
    for (size_t i = chain.length(); i > 0; ) {
        withEnv = WithEnvironmentObject::createNonSyntactic(cx, chain[--i], enclosingEnv);
        if (!withEnv)
            return false;
        enclosingEnv = withEnv;
    }

    envObj.set(enclosingEnv);
    return true;
}

JSObject&
WithEnvironmentObject::object() const
{
    return getReservedSlot(OBJECT_SLOT).toObject();
}

JSObject*
WithEnvironmentObject::withThis() const
{
    return &getReservedSlot(THIS_SLOT).toObject();
}

bool
WithEnvironmentObject::isSyntactic() const
{
    Value v = getReservedSlot(SCOPE_SLOT);
    MOZ_ASSERT(v.isPrivateGCThing() || v.isNull());
    return v.isPrivateGCThing();
}

WithScope&
WithEnvironmentObject::scope() const
{
    MOZ_ASSERT(isSyntactic());
    return *static_cast<WithScope*>(getReservedSlot(SCOPE_SLOT).toGCThing());
}

ModuleEnvironmentObject*
js::GetModuleEnvironmentForScript(JSScript* script)
{
    for (ScopeIter si(script); si; si++) {
        if (si.kind() == ScopeKind::Module)
            return si.scope()->as<ModuleScope>().module()->environment();
    }
    return nullptr;
}

bool
js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
                                             MutableHandleValue res)
{
    for (EnvironmentIter ei(cx, frame, pc); ei; ei++) {
        if (ei.scope().kind() == ScopeKind::Module) {
            res.setUndefined();
            return true;
        }

        if (!ei.scope().is<FunctionScope>() ||
            ei.scope().as<FunctionScope>().canonicalFunction()->hasLexicalThis())
        {
            continue;
        }

        RootedScript script(cx, ei.scope().as<FunctionScope>().script());

        // Figure out if we executed JSOP_FUNCTIONTHIS and set it.
        bool executedInitThisOp = false;
        if (script->functionHasThisBinding()) {
            for (jsbytecode* it = script->code(); it < script->codeEnd(); it = GetNextPc(it)) {
                if (*it == JSOP_FUNCTIONTHIS) {
                    // The next op after JSOP_FUNCTIONTHIS always sets it.
                    executedInitThisOp = pc > GetNextPc(it);
                    break;
                }
            }
        }

        if (ei.withinInitialFrame() && !executedInitThisOp) {
            // Either we're yet 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;
            }

            // 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;
        }

        for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
            if (bi.name() != cx->names().dotThis)
                continue;

            BindingLocation loc = bi.location();
            if (loc.kind() == BindingLocation::Kind::Environment) {
                RootedObject callObj(cx, &ei.environment().as<CallObject>());
                return GetProperty(cx, callObj, callObj, bi.name()->asPropertyName(), res);
            }

            if (loc.kind() == BindingLocation::Kind::Frame && ei.withinInitialFrame())
                res.set(frame.unaliasedLocal(loc.slot()));
            else
                res.setMagic(JS_OPTIMIZED_OUT);

            return true;
        }

        MOZ_CRASH("'this' binding must be found");
    }

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

bool
js::CheckLexicalNameConflict(JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
                             HandleObject varObj, HandlePropertyName name)
{
    const char* redeclKind = nullptr;
    RootedId id(cx, NameToId(name));
    RootedShape shape(cx);
    if (varObj->is<GlobalObject>() && varObj->compartment()->isInVarNames(name)) {
        // ES 15.1.11 step 5.a
        redeclKind = "var";
    } else if ((shape = lexicalEnv->lookup(cx, name))) {
        // ES 15.1.11 step 5.b
        redeclKind = shape->writable() ? "let" : "const";
    } else if (varObj->isNative() && (shape = varObj->as<NativeObject>().lookup(cx, name))) {
        // Faster path for ES 15.1.11 step 5.c-d when the shape can be found
        // without going through a resolve hook.
        if (!shape->configurable())
            redeclKind = "non-configurable global property";
    } else {
        // ES 15.1.11 step 5.c-d
        Rooted<PropertyDescriptor> desc(cx);
        if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc))
            return false;
        if (desc.object() && desc.hasConfigurable() && !desc.configurable())
            redeclKind = "non-configurable global property";
    }

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

    return true;
}

bool
js::CheckVarNameConflict(JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
                         HandlePropertyName name)
{
    if (Shape* shape = lexicalEnv->lookup(cx, name)) {
        ReportRuntimeRedeclaration(cx, name, shape->writable() ? "let" : "const");
        return false;
    }
    return true;
}

static void
ReportCannotDeclareGlobalBinding(JSContext* cx, HandlePropertyName name, const char* reason)
{
    JSAutoByteString printable;
    if (AtomToPrintableString(cx, name, &printable)) {
        JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                                   JSMSG_CANT_DECLARE_GLOBAL_BINDING,
                                   printable.ptr(), reason);
    }
}

bool
js::CheckCanDeclareGlobalBinding(JSContext* cx, Handle<GlobalObject*> global,
                                 HandlePropertyName name, bool isFunction)
{
    RootedId id(cx, NameToId(name));
    Rooted<PropertyDescriptor> desc(cx);
    if (!GetOwnPropertyDescriptor(cx, global, id, &desc))
        return false;

    // ES 8.1.1.4.15 CanDeclareGlobalVar
    // ES 8.1.1.4.16 CanDeclareGlobalFunction

    // Step 4.
    if (!desc.object()) {
        // 8.1.14.15 step 6.
        // 8.1.14.16 step 5.
        if (global->nonProxyIsExtensible())
            return true;

        ReportCannotDeclareGlobalBinding(cx, name, "global is non-extensible");
        return false;
    }

    // Global functions have additional restrictions.
    if (isFunction) {
        // 8.1.14.16 step 6.
        if (desc.configurable())
            return true;

        // 8.1.14.16 step 7.
        if (desc.isDataDescriptor() && desc.writable() && desc.enumerable())
            return true;

        ReportCannotDeclareGlobalBinding(cx, name,
                                         "property must be configurable or "
                                         "both writable and enumerable");
        return false;
    }

    return true;
}

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

    // ES 15.1.11 GlobalDeclarationInstantiation

    // Step 6.
    //
    // Check 'var' declarations do not conflict with existing bindings in the
    // global lexical environment.
    for (; bi; bi++) {
        if (bi.kind() != BindingKind::Var)
            break;
        name = bi.name()->asPropertyName();
        if (!CheckVarNameConflict(cx, lexicalEnv, name))
            return false;

        // Step 10 and 12.
        //
        // Check that global functions and vars may be declared.
        if (varObj->is<GlobalObject>()) {
            Handle<GlobalObject*> global = varObj.as<GlobalObject>();
            if (!CheckCanDeclareGlobalBinding(cx, global, name, bi.isTopLevelFunction()))
                return false;
        }
    }

    // Step 5.
    //
    // Check that lexical bindings do not conflict.
    for (; bi; bi++) {
        name = bi.name()->asPropertyName();
        if (!CheckLexicalNameConflict(cx, lexicalEnv, varObj, name))
            return false;
    }

    return true;
}

static bool
CheckVarNameConflictsInEnv(JSContext* cx, HandleScript script, HandleObject obj)
{
    Rooted<LexicalEnvironmentObject*> env(cx);

    if (obj->is<LexicalEnvironmentObject>()) {
        env = &obj->as<LexicalEnvironmentObject>();
    } else if (obj->is<DebugEnvironmentProxy>() &&
               obj->as<DebugEnvironmentProxy>().environment().is<LexicalEnvironmentObject>())
    {
        env = &obj->as<DebugEnvironmentProxy>().environment().as<LexicalEnvironmentObject>();
    } else {
        // Environment cannot contain lexical bindings.
        return true;
    }

    if (env->isSyntactic() && !env->isGlobal() && env->scope().kind() == ScopeKind::SimpleCatch) {
        // Annex B.3.5 allows redeclaring simple (non-destructured) catch
        // parameters with var declarations, except when it appears in a
        // for-of. The for-of allowance is computed in
        // Parser::isVarRedeclaredInEval.
        return true;
    }

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

    return true;
}

bool
js::CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script,
                                  HandleObject scopeChain, HandleObject varObj)
{
    if (!script->bodyScope()->as<EvalScope>().hasBindings())
        return true;

    RootedObject obj(cx, scopeChain);

    // ES 18.2.1.3.

    // Step 5.
    //
    // Check that a direct eval will not hoist 'var' bindings over lexical
    // bindings with the same name.
    while (obj != varObj) {
        if (!CheckVarNameConflictsInEnv(cx, script, obj))
            return false;
        obj = obj->enclosingEnvironment();
    }

    // Step 8.
    //
    // Check that global functions may be declared.
    if (varObj->is<GlobalObject>()) {
        Handle<GlobalObject*> global = varObj.as<GlobalObject>();
        RootedPropertyName name(cx);
        for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
            name = bi.name()->asPropertyName();
            if (!CheckCanDeclareGlobalBinding(cx, global, name, bi.isTopLevelFunction()))
                return false;
        }
    }

    return true;
}

bool
js::InitFunctionEnvironmentObjects(JSContext* cx, AbstractFramePtr frame)
{
    MOZ_ASSERT(frame.isFunctionFrame());
    MOZ_ASSERT(frame.callee()->needsSomeEnvironmentObject());

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

    // Named lambdas may have an environment that holds itself for recursion.
    if (callee->needsNamedLambdaEnvironment()) {
        NamedLambdaObject* declEnv;
        if (callee->isAsync()) {
            // Named async function needs special environment to return
            // wrapped function for the binding.
            RootedFunction fun(cx, GetWrappedAsyncFunction(callee));
            declEnv = NamedLambdaObject::create(cx, frame, fun);
        } else {
            declEnv = NamedLambdaObject::create(cx, frame);
        }
        if (!declEnv)
            return false;
        frame.pushOnEnvironmentChain(*declEnv);
    }

    // If the function has parameter default expressions, there may be an
    // extra environment to hold the parameters.
    if (callee->needsCallObject()) {
        CallObject* callObj = CallObject::create(cx, frame);
        if (!callObj)
            return false;
        frame.pushOnEnvironmentChain(*callObj);
    }

    return true;
}

bool
js::PushVarEnvironmentObject(JSContext* cx, HandleScope scope, AbstractFramePtr frame)
{
    VarEnvironmentObject* env = VarEnvironmentObject::create(cx, scope, frame);
    if (!env)
        return false;
    frame.pushOnEnvironmentChain(*env);
    return true;
}

#ifdef DEBUG

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 = EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, 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.closedOver()) {
            PropertyName* name = bi.name()->asPropertyName();
            PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(name);
            if (!p && !remainingNames.add(p, 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
back to top