/* 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/Maybe.h"

#include "builtin/Array.h"
#include "builtin/ModuleObject.h"
#include "js/EnvironmentChain.h"  // JS::EnvironmentChain
#include "js/Exception.h"
#include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h"    // js::AutoCheckRecursionLimit
#include "js/friend/WindowProxy.h"    // js::IsWindow, js::IsWindowProxy
#include "js/PropertyAndElement.h"  // JS_DefineProperty, JS_DefinePropertyById, JS_HasProperty, JS_HasPropertyById
#include "vm/ArgumentsObject.h"
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/GeneratorObject.h"  // js::GetGeneratorObjectForEnvironment
#include "vm/GlobalObject.h"
#include "vm/JSObject.h"
#include "vm/ProxyObject.h"
#include "vm/Realm.h"
#include "vm/Scope.h"
#include "vm/Shape.h"
#include "wasm/WasmDebug.h"
#include "wasm/WasmDebugFrame.h"
#include "wasm/WasmInstance.h"

#include "gc/Marking-inl.h"
#include "gc/StableCellHasher-inl.h"
#include "gc/WeakMap-inl.h"
#include "vm/ArgumentsObject-inl.h"
#include "vm/BytecodeIterator-inl.h"
#include "vm/Stack-inl.h"

using namespace js;

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

/*
 * Return a shape representing the static scope containing the variable
 * accessed by the ALIASEDVAR op at 'pc'.
 */
static SharedShape* 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();
}

PropertyName* js::EnvironmentCoordinateNameSlow(JSScript* script,
                                                jsbytecode* pc) {
  SharedShape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc);
  EnvironmentCoordinate ec(pc);

  SharedShapePropertyIter<NoGC> iter(shape);
  while (iter->slot() != ec.slot()) {
    iter++;
  }
  jsid id = iter->key();

  /* Beware nameless destructuring formal. */
  if (!id.isAtom()) {
    return script->runtimeFromAnyThread()->commonNames->empty_;
  }
  return id.toAtom()->asPropertyName();
}

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

template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, Handle<SharedShape*> shape,
                                  gc::Heap heap,
                                  gc::AllocSite* site = nullptr) {
  static_assert(std::is_base_of_v<EnvironmentObject, T>,
                "T must be an EnvironmentObject");

  // Environment objects do not have finalizers.
  gc::AllocKind allocKind = gc::GetGCObjectKind(shape->numFixedSlots());
  MOZ_ASSERT(gc::GetObjectFinalizeKind(&T::class_) == gc::FinalizeKind::None);
  MOZ_ASSERT(!gc::IsFinalizedKind(allocKind));

  return NativeObject::create<T>(cx, allocKind, heap, shape, site);
}

// Helper function for simple environment objects that don't need the overloads
// above.
template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, Handle<SharedShape*> shape,
                                  NewObjectKind newKind = GenericObject) {
  gc::Heap heap = GetInitialHeap(newKind, &T::class_);
  return CreateEnvironmentObject<T>(cx, shape, heap);
}

CallObject* CallObject::createWithShape(JSContext* cx,
                                        Handle<SharedShape*> shape,
                                        gc::Heap heap) {
  return CreateEnvironmentObject<CallObject>(cx, shape, heap);
}

/*
 * 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::create(JSContext* cx, HandleScript script,
                               HandleObject enclosing, gc::Heap heap,
                               gc::AllocSite* site) {
  Rooted<SharedShape*> shape(
      cx, script->bodyScope()->as<FunctionScope>().environmentShape());
  MOZ_ASSERT(shape->getObjectClass() == &class_);

  // The JITs assume the result is nursery allocated unless we collected the
  // nursery, so don't change |heap| here.

  auto* callObj = CreateEnvironmentObject<CallObject>(cx, shape, heap, site);
  if (!callObj) {
    return nullptr;
  }

  if (enclosing) {
    callObj->initEnclosingEnvironment(enclosing);
  }

  return callObj;
}

CallObject* CallObject::createTemplateObject(JSContext* cx, HandleScript script,
                                             HandleObject enclosing) {
  return create(cx, script, enclosing, gc::Heap::Tenured);
}

CallObject* CallObject::createForFrame(JSContext* cx, AbstractFramePtr frame,
                                       gc::AllocSite* site) {
  MOZ_ASSERT(frame.isFunctionFrame());
  cx->check(frame);

  RootedObject envChain(cx, frame.environmentChain());
  RootedFunction callee(cx, frame.callee());
  RootedScript script(cx, callee->nonLazyScript());
  gc::Heap heap = site ? site->initialHeap() : gc::Heap::Default;

  CallObject* callobj = create(cx, script, envChain, heap, site);
  if (!callobj) {
    return nullptr;
  }

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

  return callobj;
}

template <class EnvT>
EnvT* FindEnclosingEnv(JSObject* env) {
  for (;;) {
    if (env->is<EnvT>()) {
      break;
    } else if (env->is<EnvironmentObject>()) {
      env = &env->as<EnvironmentObject>().enclosingEnvironment();
    } else if (env->is<DebugEnvironmentProxy>()) {
      EnvironmentObject& unwrapped =
          env->as<DebugEnvironmentProxy>().environment();
      if (unwrapped.is<EnvT>()) {
        env = &unwrapped;
        break;
      }
      env = &env->as<DebugEnvironmentProxy>().enclosingEnvironment();
    } else {
      MOZ_ASSERT(env->is<GlobalObject>());
      return nullptr;
    }
  }
  return &env->as<EnvT>();
}

CallObject* CallObject::find(JSObject* env) {
  return FindEnclosingEnv<CallObject>(env);
}

ModuleEnvironmentObject* ModuleEnvironmentObject::find(JSObject* env) {
  return FindEnclosingEnv<ModuleEnvironmentObject>(env);
}

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

  RootedTuple<JSScript*, FunctionScope*, SharedShape*, CallObject*, Value, jsid>
      roots(cx);
  RootedField<JSScript*> script(roots, callee->nonLazyScript());
  RootedField<FunctionScope*> scope(roots,
                                    &script->bodyScope()->as<FunctionScope>());
  RootedField<SharedShape*> shape(roots, EmptyEnvironmentShape<CallObject>(cx));
  if (!shape) {
    return nullptr;
  }
  RootedField<CallObject*> callobj(roots, createWithShape(cx, shape));
  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));

  RootedField<Value> optimizedOut(roots, MagicValue(JS_OPTIMIZED_OUT));
  RootedField<jsid> id(roots);
  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 JSClass CallObject::class_ = {
    "Call",
    JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS),
};

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

/* static */
VarEnvironmentObject* VarEnvironmentObject::createInternal(
    JSContext* cx, Handle<SharedShape*> shape, HandleObject enclosing,
    gc::Heap heap) {
  MOZ_ASSERT(shape->getObjectClass() == &class_);

  auto* env = CreateEnvironmentObject<VarEnvironmentObject>(cx, shape, heap);
  if (!env) {
    return nullptr;
  }

  MOZ_ASSERT(!env->inDictionaryMode());

  if (enclosing) {
    env->initEnclosingEnvironment(enclosing);
  }

  return env;
}

/* static */
VarEnvironmentObject* VarEnvironmentObject::create(JSContext* cx,
                                                   Handle<Scope*> scope,
                                                   HandleObject enclosing,
                                                   gc::Heap heap) {
  MOZ_ASSERT(scope->is<EvalScope>() || scope->is<VarScope>());

  Rooted<SharedShape*> shape(cx, scope->environmentShape());
  auto* env = createInternal(cx, shape, enclosing, heap);
  if (!env) {
    return nullptr;
  }
  env->initScope(scope);
  return env;
}

/* static */
VarEnvironmentObject* VarEnvironmentObject::createForFrame(
    JSContext* cx, Handle<Scope*> 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

  RootedObject envChain(cx, frame.environmentChain());
  return create(cx, scope, envChain, gc::Heap::Default);
}

/* static */
VarEnvironmentObject* VarEnvironmentObject::createHollowForDebug(
    JSContext* cx, Handle<Scope*> scope) {
  MOZ_ASSERT(scope->is<VarScope>() || scope->kind() == ScopeKind::StrictEval);
  MOZ_ASSERT(!scope->hasEnvironment());

  Rooted<SharedShape*> shape(cx,
                             EmptyEnvironmentShape<VarEnvironmentObject>(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, createInternal(cx, shape, enclosingEnv, gc::Heap::Default));
  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;
}

/* static */
VarEnvironmentObject* VarEnvironmentObject::createTemplateObject(
    JSContext* cx, Handle<VarScope*> scope) {
  return create(cx, scope, nullptr, gc::Heap::Tenured);
}

/* static */
VarEnvironmentObject* VarEnvironmentObject::createWithoutEnclosing(
    JSContext* cx, Handle<VarScope*> scope) {
  return create(cx, scope, nullptr, gc::Heap::Default);
}

const JSClass VarEnvironmentObject::class_ = {
    "Var",
    JSCLASS_HAS_RESERVED_SLOTS(VarEnvironmentObject::RESERVED_SLOTS),
};

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

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

const JSClassOps ModuleEnvironmentObject::classOps_ = {
    .newEnumerate = ModuleEnvironmentObject::newEnumerate,
};

const JSClass ModuleEnvironmentObject::class_ = {
    "ModuleEnvironmentObject",
    JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS),
    &ModuleEnvironmentObject::classOps_,
    JS_NULL_CLASS_SPEC,
    JS_NULL_CLASS_EXT,
    &ModuleEnvironmentObject::objectOps_,
};

/* static */
ModuleEnvironmentObject* ModuleEnvironmentObject::create(
    JSContext* cx, Handle<ModuleObject*> module) {
  RootedScript script(cx, module->script());
  Rooted<SharedShape*> shape(
      cx, script->bodyScope()->as<ModuleScope>().environmentShape());
  MOZ_ASSERT(shape->getObjectClass() == &class_);

  ModuleEnvironmentObject* env =
      CreateEnvironmentObject<ModuleEnvironmentObject>(cx, shape,
                                                       TenuredObject);
  if (!env) {
    return nullptr;
  }

  env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));

  // 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 (ShapePropertyIter<NoGC> iter(env->shape()); !iter.done(); iter++) {
    MOZ_ASSERT(!iter->configurable());
  }
  MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible));
  MOZ_ASSERT(!env->inDictionaryMode());
#endif

#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
  env->initSlot(ModuleEnvironmentObject::DISPOSABLE_RESOURCE_STACK_SLOT,
                UndefinedValue());
#endif

  return env;
}

#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
static ArrayObject* initialiseAndSetDisposeCapabilityHelper(
    JSContext* cx, JS::Handle<EnvironmentObject*> env, uint32_t slot) {
  JS::Value slotData = env->getReservedSlot(slot);
  ArrayObject* disposablesList = nullptr;
  if (slotData.isUndefined()) {
    disposablesList = NewDenseEmptyArray(cx);
    if (!disposablesList) {
      return nullptr;
    }
    env->setReservedSlot(slot, ObjectValue(*disposablesList));
  } else {
    disposablesList = &slotData.toObject().as<ArrayObject>();
  }
  return disposablesList;
}

ArrayObject* DisposableEnvironmentObject::getOrCreateDisposeCapability(
    JSContext* cx) {
  Rooted<DisposableEnvironmentObject*> env(cx, this);
  return initialiseAndSetDisposeCapabilityHelper(
      cx, env, DISPOSABLE_RESOURCE_STACK_SLOT);
}

// TODO: The get & clear disposables function can be merged. (bug 1907736)
JS::Value DisposableEnvironmentObject::getDisposables() {
  return getReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT);
}

void DisposableEnvironmentObject::clearDisposables() {
  setReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT, UndefinedValue());
}
#endif

/* static */
ModuleEnvironmentObject* ModuleEnvironmentObject::createSynthetic(
    JSContext* cx, Handle<ModuleObject*> module) {
  Rooted<SharedShape*> shape(cx,
                             CreateEnvironmentShapeForSyntheticModule(
                                 cx, &class_, JSSLOT_FREE(&class_), module));
  if (!shape) {
    return nullptr;
  }

  MOZ_ASSERT(shape->getObjectClass() == &class_);

  ModuleEnvironmentObject* env =
      CreateEnvironmentObject<ModuleEnvironmentObject>(cx, shape,
                                                       TenuredObject);
  if (!env) {
    return nullptr;
  }

  env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));

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

  // 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 (ShapePropertyIter<NoGC> iter(env->shape()); !iter.done(); iter++) {
    MOZ_ASSERT(!iter->configurable());
  }
  MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible));
  MOZ_ASSERT(!env->inDictionaryMode());
#endif

  return env;
}

/* static */
ModuleEnvironmentObject* ModuleEnvironmentObject::createForWasmModule(
    JSContext* cx, Handle<ModuleObject*> module) {
  // Wasm source-phase modules have no JavaScript bindings, so the environment
  // has no property slots.
  Rooted<SharedPropMap*> map(cx);
  uint32_t mapLength = 0;
  uint32_t numSlots = JSSLOT_FREE(&class_);
  uint32_t numFixed = gc::GetGCKindSlots(gc::GetGCObjectKind(numSlots));
  Rooted<SharedShape*> shape(
      cx, SharedShape::getInitialOrPropMapShape(cx, &class_, cx->realm(),
                                                TaggedProto(nullptr), numFixed,
                                                map, mapLength, OBJECT_FLAGS));
  if (!shape) {
    return nullptr;
  }
  MOZ_ASSERT(shape->getObjectClass() == &class_);

  ModuleEnvironmentObject* env =
      CreateEnvironmentObject<ModuleEnvironmentObject>(cx, shape,
                                                       TenuredObject);
  if (!env) {
    return nullptr;
  }

  env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));
  env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
  MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible));
  MOZ_ASSERT(!env->inDictionaryMode());

  return env;
}

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

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

bool ModuleEnvironmentObject::createImportBinding(JSContext* cx,
                                                  Handle<JSAtom*> importName,
                                                  Handle<ModuleObject*> module,
                                                  Handle<JSAtom*> localName) {
  RootedId importNameId(cx, AtomToId(importName));
  RootedId localNameId(cx, AtomToId(localName));
  Rooted<ModuleEnvironmentObject*> env(cx, &module->initialEnvironment());
  return importBindings().put(cx, importNameId, env, localNameId);
}

bool ModuleEnvironmentObject::hasImportBinding(Handle<PropertyName*> name) {
  return importBindings().has(NameToId(name));
}

bool ModuleEnvironmentObject::lookupImport(
    jsid name, ModuleEnvironmentObject** envOut,
    mozilla::Maybe<PropertyInfo>* propOut) {
  return importBindings().lookup(name, envOut, propOut);
}

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

  Rooted<NativeObject*> 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;
  }

  Rooted<NativeObject*> 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();
  mozilla::Maybe<PropertyInfo> prop;
  ModuleEnvironmentObject* env;
  if (bindings.lookup(id, &env, &prop)) {
    vp.set(env->getSlot(prop->slot()));
    return true;
  }

  Rooted<NativeObject*> 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) {
  Rooted<ModuleEnvironmentObject*> self(cx,
                                        &obj->as<ModuleEnvironmentObject>());
  if (self->importBindings().has(id)) {
    return result.failReadOnly();
  }

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

/* static */
bool ModuleEnvironmentObject::getOwnPropertyDescriptor(
    JSContext* cx, HandleObject obj, HandleId id,
    MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
  const IndirectBindingMap& bindings =
      obj->as<ModuleEnvironmentObject>().importBindings();
  mozilla::Maybe<PropertyInfo> prop;
  ModuleEnvironmentObject* env;
  if (bindings.lookup(id, &env, &prop)) {
    desc.set(mozilla::Some(PropertyDescriptor::Data(
        env->getSlot(prop->slot()),
        {JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable})));
    return true;
  }

  Rooted<NativeObject*> 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::newEnumerate(JSContext* cx, HandleObject obj,
                                           MutableHandleIdVector properties,
                                           bool enumerableOnly) {
  Rooted<ModuleEnvironmentObject*> 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 (ShapePropertyIter<NoGC> iter(self->shape()); !iter.done(); iter++) {
    properties.infallibleAppend(iter->key());
  }

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

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

const JSClass WasmInstanceEnvironmentObject::class_ = {
    "WasmInstance",
    JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceEnvironmentObject::RESERVED_SLOTS),
};

/* static */
WasmInstanceEnvironmentObject*
WasmInstanceEnvironmentObject::createHollowForDebug(
    JSContext* cx, Handle<WasmInstanceScope*> scope) {
  Rooted<SharedShape*> shape(
      cx, EmptyEnvironmentShape<WasmInstanceEnvironmentObject>(cx));
  if (!shape) {
    return nullptr;
  }

  auto* env = CreateEnvironmentObject<WasmInstanceEnvironmentObject>(cx, shape);
  if (!env) {
    return nullptr;
  }

  env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
  env->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));

  return env;
}

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

const JSClass WasmFunctionCallObject::class_ = {
    "WasmCall",
    JSCLASS_HAS_RESERVED_SLOTS(WasmFunctionCallObject::RESERVED_SLOTS),
};

/* static */
WasmFunctionCallObject* WasmFunctionCallObject::createHollowForDebug(
    JSContext* cx, HandleObject enclosing, Handle<WasmFunctionScope*> scope) {
  Rooted<SharedShape*> shape(cx,
                             EmptyEnvironmentShape<WasmFunctionCallObject>(cx));
  if (!shape) {
    return nullptr;
  }

  auto* callobj = CreateEnvironmentObject<WasmFunctionCallObject>(cx, shape);
  if (!callobj) {
    return nullptr;
  }

  callobj->initEnclosingEnvironment(enclosing);
  callobj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));

  return callobj;
}

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

JSObject* js::GetThisObject(JSObject* obj) {
  // Use the WindowProxy if the global is a Window, as Window must never be
  // exposed to script.
  if (obj->is<GlobalObject>()) {
    return ToWindowProxyIfWindow(obj);
  }

  // We should not expose any environments except NSVOs to script. The NSVO is
  // pretending to be the global object in this case.
  MOZ_ASSERT_IF(obj->is<EnvironmentObject>(),
                obj->is<NonSyntacticVariablesObject>());

  return obj;
}

WithEnvironmentObject* WithEnvironmentObject::create(
    JSContext* cx, HandleObject object, HandleObject enclosing,
    Handle<WithScope*> scope, JS::SupportUnscopables supportUnscopables) {
  Rooted<SharedShape*> shape(cx,
                             EmptyEnvironmentShape<WithEnvironmentObject>(cx));
  if (!shape) {
    return nullptr;
  }

  auto* obj = CreateEnvironmentObject<WithEnvironmentObject>(cx, shape);
  if (!obj) {
    return nullptr;
  }

  JSObject* thisObj = GetThisObject(object);

  obj->initEnclosingEnvironment(enclosing);
  obj->initReservedSlot(OBJECT_SLOT, ObjectValue(*object));
  obj->initReservedSlot(THIS_SLOT, ObjectValue(*thisObj));
  if (scope) {
    MOZ_ASSERT(supportUnscopables == JS::SupportUnscopables::Yes,
               "with-statements must support Symbol.unscopables");
    obj->initReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT,
                          PrivateGCThingValue(scope));
  } else {
    Value v = BooleanValue(supportUnscopables == JS::SupportUnscopables::Yes);
    obj->initReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT, v);
  }

  return obj;
}

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

static inline bool IsUnscopableDotName(JSContext* cx, HandleId id) {
  return id.isAtom(cx->names().dot_this_) ||
         id.isAtom(cx->names().dot_newTarget_);
}

#ifdef DEBUG
static bool IsInternalDotName(JSContext* cx, HandleId id) {
  return id.isAtom(cx->names().dot_this_) ||
         id.isAtom(cx->names().dot_generator_) ||
         id.isAtom(cx->names().dot_initializers_) ||
         id.isAtom(cx->names().dot_fieldKeys_) ||
         id.isAtom(cx->names().dot_staticInitializers_) ||
         id.isAtom(cx->names().dot_staticFieldKeys_) ||
         id.isAtom(cx->names().dot_args_) ||
         id.isAtom(cx->names().dot_newTarget_) ||
         id.isAtom(cx->names().star_namespace_star_);
}
#endif

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

  // Other internal dot-names shouldn't even end up in with-environments.
  MOZ_ASSERT(!IsInternalDotName(cx, id));

  RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
  if (!LookupProperty(cx, actual, id, objp, propp)) {
    return false;
  }

  if (propp->isFound()) {
    bool scopable = true;
    if (obj->as<WithEnvironmentObject>().supportUnscopables() &&
        !CheckUnscopables(cx, actual, id, &scopable)) {
      return false;
    }
    if (!scopable) {
      objp.set(nullptr);
      propp->setNotFound();
    }
  }
  return true;
}

static bool with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id,
                                Handle<PropertyDescriptor> desc,
                                ObjectOpResult& result) {
  MOZ_ASSERT(!IsInternalDotName(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(!IsInternalDotName(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 || !obj->as<WithEnvironmentObject>().supportUnscopables()) {
    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(!IsInternalDotName(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(!IsInternalDotName(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<mozilla::Maybe<PropertyDescriptor>> desc) {
  MOZ_ASSERT(!IsInternalDotName(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(!IsInternalDotName(cx, id));
  RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
  return DeleteProperty(cx, actual, id, result);
}

static const ObjectOps WithEnvironmentObjectOps = {
    with_LookupProperty,            // lookupProperty
    with_DefineProperty,            // defineProperty
    with_HasProperty,               // hasProperty
    with_GetProperty,               // getProperty
    with_SetProperty,               // setProperty
    with_GetOwnPropertyDescriptor,  // getOwnPropertyDescriptor
    with_DeleteProperty,            // deleteProperty
    nullptr,                        // getElements
    nullptr,                        // funToString
};

const JSClass WithEnvironmentObject::class_ = {
    "With",
    JSCLASS_HAS_RESERVED_SLOTS(WithEnvironmentObject::RESERVED_SLOTS),
    JS_NULL_CLASS_OPS,
    JS_NULL_CLASS_SPEC,
    JS_NULL_CLASS_EXT,
    &WithEnvironmentObjectOps,
};

/* static */
NonSyntacticVariablesObject* NonSyntacticVariablesObject::create(
    JSContext* cx) {
  Rooted<SharedShape*> shape(
      cx, EmptyEnvironmentShape<NonSyntacticVariablesObject>(cx));
  if (!shape) {
    return nullptr;
  }

  NonSyntacticVariablesObject* obj =
      CreateEnvironmentObject<NonSyntacticVariablesObject>(cx, shape,
                                                           TenuredObject);
  if (!obj) {
    return nullptr;
  }

  // An NVSO holds both variables qualified with `var` and those that are not.
  MOZ_ASSERT(obj->isUnqualifiedVarObj());
  MOZ_ASSERT(obj->isQualifiedVarObj());

  obj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
  return obj;
}

const JSClass NonSyntacticVariablesObject::class_ = {
    "NonSyntacticVariablesObject",
    JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS),
};

NonSyntacticLexicalEnvironmentObject* js::CreateNonSyntacticEnvironmentChain(
    JSContext* cx, const JS::EnvironmentChain& envChain) {
  // Callers are responsible for segregating the NonSyntactic case from simple
  // compilation cases.
  MOZ_RELEASE_ASSERT(!envChain.empty());

  RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
  Rooted<WithEnvironmentObject*> env(
      cx, CreateObjectsForEnvironmentChain(cx, envChain, globalLexical));
  if (!env) {
    return nullptr;
  }

  // The XPConnect subscript loader, which may pass in its own
  // environments to load scripts in, expects the environment chain to
  // be the holder of "var" declarations. In SpiderMonkey, such objects
  // are called "qualified varobjs", the "qualified" part meaning the
  // declaration was qualified by "var". There is only sadness.
  //
  // See JSObject::isQualifiedVarObj.
  if (!JSObject::setQualifiedVarObj(cx, env)) {
    return nullptr;
  }

  // Also get a non-syntactic lexical environment to capture 'let' and
  // 'const' bindings. To persist lexical bindings, we have a 1-1
  // mapping with the final unwrapped environment object (the
  // environment that stores the 'var' bindings) and the lexical
  // environment.
  //
  // TODOshu: disallow the subscript loader from using non-distinguished
  // objects as dynamic scopes.
  return ObjectRealm::get(env).getOrCreateNonSyntacticLexicalEnvironment(cx,
                                                                         env);
}

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

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

/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::create(
    JSContext* cx, Handle<SharedShape*> shape, HandleObject enclosing,
    gc::Heap heap, gc::AllocSite* site) {
  MOZ_ASSERT(shape->getObjectClass() == &LexicalEnvironmentObject::class_);

  // The JITs assume the result is nursery allocated unless we collected the
  // nursery, so don't change |heap| here.

  auto* env =
      CreateEnvironmentObject<LexicalEnvironmentObject>(cx, shape, heap, site);
  if (!env) {
    return nullptr;
  }

  MOZ_ASSERT(!env->inDictionaryMode());

  if (enclosing) {
    env->initEnclosingEnvironment(enclosing);
  }

#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
  env->initSlot(LexicalEnvironmentObject::DISPOSABLE_RESOURCE_STACK_SLOT,
                UndefinedValue());
#endif

  return env;
}

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

/* static */
BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::create(
    JSContext* cx, Handle<LexicalScope*> scope, HandleObject enclosing,
    gc::Heap heap, gc::AllocSite* site) {
  cx->check(enclosing);
  MOZ_ASSERT(scope->hasEnvironment());

  Rooted<SharedShape*> shape(cx, scope->environmentShape());
  auto* env = static_cast<BlockLexicalEnvironmentObject*>(
      LexicalEnvironmentObject::create(cx, shape, enclosing, heap, site));
  if (!env) {
    return nullptr;
  }

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

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

/* static */
BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::createForFrame(
    JSContext* cx, Handle<LexicalScope*> scope, AbstractFramePtr frame) {
  RootedObject enclosing(cx, frame.environmentChain());
  return create(cx, scope, enclosing, gc::Heap::Default);
}

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

  RootedTuple<SharedShape*, JSObject*, LexicalEnvironmentObject*, Value, jsid>
      roots(cx);
  RootedField<SharedShape*> shape(
      roots, 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.
  RootedField<JSObject*> enclosingEnv(roots,
                                      &cx->global()->lexicalEnvironment());
  RootedField<LexicalEnvironmentObject*> env(
      roots, LexicalEnvironmentObject::create(cx, shape, enclosingEnv,
                                              gc::Heap::Tenured));
  if (!env) {
    return nullptr;
  }

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

  if (!JSObject::setFlag(cx, env, ObjectFlag::NotExtensible)) {
    return nullptr;
  }

  env->as<ScopedLexicalEnvironmentObject>().initScope(scope);
  return &env->as<BlockLexicalEnvironmentObject>();
}

/* static */
BlockLexicalEnvironmentObject*
BlockLexicalEnvironmentObject::createTemplateObject(
    JSContext* cx, Handle<LexicalScope*> scope) {
  return create(cx, scope, nullptr, gc::Heap::Tenured);
}

/* static */
BlockLexicalEnvironmentObject*
BlockLexicalEnvironmentObject::createWithoutEnclosing(
    JSContext* cx, Handle<LexicalScope*> scope) {
  return create(cx, scope, nullptr, gc::Heap::Default);
}

/* static */
BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::clone(
    JSContext* cx, Handle<BlockLexicalEnvironmentObject*> env) {
  Rooted<LexicalScope*> scope(cx, &env->scope());
  RootedObject enclosing(cx, &env->enclosingEnvironment());
  BlockLexicalEnvironmentObject* copy =
      create(cx, scope, enclosing, gc::Heap::Default);
  if (!copy) {
    return nullptr;
  }

  MOZ_ASSERT(env->shape() == copy->shape());
  for (uint32_t i = JSSLOT_FREE(&class_); i < copy->slotSpan(); i++) {
    copy->setSlot(i, env->getSlot(i));
  }

  return copy;
}

/* static */
BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::recreate(
    JSContext* cx, Handle<BlockLexicalEnvironmentObject*> env) {
  Rooted<LexicalScope*> scope(cx, &env->scope());
  RootedObject enclosing(cx, &env->enclosingEnvironment());
  return create(cx, scope, enclosing, gc::Heap::Default);
}

/* static */
NamedLambdaObject* NamedLambdaObject::create(JSContext* cx,
                                             HandleFunction callee,
                                             HandleObject enclosing,
                                             gc::Heap heap,
                                             gc::AllocSite* site) {
  MOZ_ASSERT(callee->isNamedLambda());
  Rooted<Scope*> scope(cx, callee->nonLazyScript()->maybeNamedLambdaScope());
  MOZ_ASSERT(scope && scope->environmentShape());

#ifdef DEBUG
  {
    // Named lambda objects have one (non-writable) property.
    SharedShapePropertyIter<NoGC> iter(scope->environmentShape());
    MOZ_ASSERT(iter->slot() == lambdaSlot());
    MOZ_ASSERT(!iter->writable());
    iter++;
    MOZ_ASSERT(iter.done());

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

  BlockLexicalEnvironmentObject* obj = BlockLexicalEnvironmentObject::create(
      cx, scope.as<LexicalScope>(), enclosing, heap, site);
  if (!obj) {
    return nullptr;
  }

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

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

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

/* static */
NamedLambdaObject* NamedLambdaObject::createForFrame(JSContext* cx,
                                                     AbstractFramePtr frame,
                                                     gc::AllocSite* site) {
  RootedFunction fun(cx, frame.callee());
  RootedObject enclosing(cx, frame.environmentChain());
  gc::Heap heap = site ? site->initialHeap() : gc::Heap::Default;
  return create(cx, fun, enclosing, heap, site);
}

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

/* static */
ClassBodyLexicalEnvironmentObject* ClassBodyLexicalEnvironmentObject::create(
    JSContext* cx, Handle<ClassBodyScope*> scope, HandleObject enclosing,
    gc::Heap heap) {
  cx->check(enclosing);
  MOZ_ASSERT(scope->hasEnvironment());

  Rooted<SharedShape*> shape(cx, scope->environmentShape());
  auto* env = static_cast<ClassBodyLexicalEnvironmentObject*>(
      LexicalEnvironmentObject::create(cx, shape, enclosing, heap));
  if (!env) {
    return nullptr;
  }

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

/* static */
ClassBodyLexicalEnvironmentObject*
ClassBodyLexicalEnvironmentObject::createForFrame(JSContext* cx,
                                                  Handle<ClassBodyScope*> scope,
                                                  AbstractFramePtr frame) {
  RootedObject enclosing(cx, frame.environmentChain());
  return create(cx, scope, enclosing, gc::Heap::Default);
}

/* static */
ClassBodyLexicalEnvironmentObject*
ClassBodyLexicalEnvironmentObject::createTemplateObject(
    JSContext* cx, Handle<ClassBodyScope*> scope) {
  return create(cx, scope, nullptr, gc::Heap::Tenured);
}

/* static */
ClassBodyLexicalEnvironmentObject*
ClassBodyLexicalEnvironmentObject::createWithoutEnclosing(
    JSContext* cx, Handle<ClassBodyScope*> scope) {
  return create(cx, scope, nullptr, gc::Heap::Default);
}

JSObject* ExtensibleLexicalEnvironmentObject::thisObject() const {
  JSObject* obj = &getReservedSlot(THIS_VALUE_OR_SCOPE_SLOT).toObject();

  // Windows must never be exposed to script. initThisObject should have set
  // this to the WindowProxy.
  MOZ_ASSERT(!IsWindow(obj));

  // WarpBuilder relies on the return value not being nursery-allocated for the
  // global lexical environment.
  MOZ_ASSERT_IF(isGlobal(), obj->isTenured());

  return obj;
}

/* static */
ExtensibleLexicalEnvironmentObject*
ExtensibleLexicalEnvironmentObject::forVarEnvironment(JSObject* obj) {
  ExtensibleLexicalEnvironmentObject* lexical = nullptr;
  if (obj->is<GlobalObject>()) {
    lexical = &obj->as<GlobalObject>().lexicalEnvironment();
  } else {
    lexical = ObjectRealm::get(obj).getNonSyntacticLexicalEnvironment(obj);
  }
  MOZ_ASSERT(lexical);
  return lexical;
}

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

  Rooted<SharedShape*> shape(
      cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
  if (!shape) {
    return nullptr;
  }

  Rooted<GlobalLexicalEnvironmentObject*> env(cx);
  env = static_cast<GlobalLexicalEnvironmentObject*>(
      LexicalEnvironmentObject::create(cx, shape, global, gc::Heap::Tenured));
  if (!env) {
    return nullptr;
  }

  if (ShouldUseObjectFuses() && JS::Prefs::objectfuse_for_global()) {
    if (!NativeObject::setHasObjectFuse(cx, env)) {
      return nullptr;
    }
  }

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

void GlobalLexicalEnvironmentObject::setWindowProxyThisObject(JSObject* obj) {
  MOZ_ASSERT(IsWindowProxy(obj));
  setReservedSlot(THIS_VALUE_OR_SCOPE_SLOT, ObjectValue(*obj));
}

/* static */
NonSyntacticLexicalEnvironmentObject*
NonSyntacticLexicalEnvironmentObject::create(JSContext* cx,
                                             HandleObject enclosing,
                                             HandleObject thisv) {
  MOZ_ASSERT(enclosing);
  MOZ_ASSERT(!IsSyntacticEnvironment(enclosing));

  Rooted<SharedShape*> shape(
      cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
  if (!shape) {
    return nullptr;
  }

  auto* env = static_cast<NonSyntacticLexicalEnvironmentObject*>(
      LexicalEnvironmentObject::create(cx, shape, enclosing,
                                       gc::Heap::Tenured));
  if (!env) {
    return nullptr;
  }

  env->initThisObject(thisv);

  return env;
}

/* static */
RuntimeLexicalErrorObject* RuntimeLexicalErrorObject::create(
    JSContext* cx, HandleObject enclosing, unsigned errorNumber) {
  Rooted<SharedShape*> shape(
      cx,
      EmptyEnvironmentShape(cx, &class_, JSSLOT_FREE(&class_), ObjectFlags()));
  if (!shape) {
    return nullptr;
  }

  auto* obj = CreateEnvironmentObject<RuntimeLexicalErrorObject>(cx, shape);
  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 (id.isAtom()) {
    Rooted<PropertyName*> name(cx, id.toAtom()->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,
                                        PropertyResult* 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<mozilla::Maybe<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,            // lookupProperty
    nullptr,                                // defineProperty
    lexicalError_HasProperty,               // hasProperty
    lexicalError_GetProperty,               // getProperty
    lexicalError_SetProperty,               // setProperty
    lexicalError_GetOwnPropertyDescriptor,  // getOwnPropertyDescriptor
    lexicalError_DeleteProperty,            // deleteProperty
    nullptr,                                // getElements
    nullptr,                                // funToString
};

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

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

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

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

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

EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope,
                                 AbstractFramePtr frame)
    : si_(cx, ScopeIter(scope)), env_(cx, env), frame_(frame) {
  cx->check(frame);
  settle();
}

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_.hasScript() &&
      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<BlockLexicalEnvironmentObject>() &&
          &env_->as<BlockLexicalEnvironmentObject>().scope() == si_.scope()) {
        MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda ||
                   si_.kind() == ScopeKind::StrictNamedLambda);
        env_ =
            &env_->as<BlockLexicalEnvironmentObject>().enclosingEnvironment();
      }
      incrementScopeIter();
    }
  }

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

#ifdef DEBUG
  if (si_) {
    if (hasSyntacticEnvironment()) {
      Scope* scope = si_.scope();
      if (scope->is<LexicalScope>()) {
        MOZ_ASSERT(scope == &env_->as<BlockLexicalEnvironmentObject>().scope());
      } else if (scope->is<FunctionScope>()) {
        MOZ_ASSERT(scope->as<FunctionScope>().script() ==
                   env_->as<CallObject>()
                       .callee()
                       .maybeCanonicalFunction()
                       ->baseScript());
      } 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>() ||
                   env_->is<GlobalLexicalEnvironmentObject>());
      }
    } else if (hasNonSyntacticEnvironmentObject()) {
      if (env_->is<LexicalEnvironmentObject>()) {
        // The global lexical environment still encloses non-syntactic
        // environment objects.
        MOZ_ASSERT(env_->is<NonSyntacticLexicalEnvironmentObject>() ||
                   env_->is<GlobalLexicalEnvironmentObject>());
      } 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 NonSyntacticLexicalEnvironmentObject.
  if (si_.kind() == ScopeKind::NonSyntactic) {
    MOZ_ASSERT_IF(env_->is<WithEnvironmentObject>(),
                  !env_->as<WithEnvironmentObject>().isSyntactic());
    return env_->is<EnvironmentObject>();
  }
  return false;
}

bool MissingEnvironmentKey::initFromEnvironmentIter(JSContext* cx,
                                                    const EnvironmentIter& ei) {
  frame_ = ei.maybeInitialFrame();
  scope_ = ei.maybeScope();
  if (frame_) {
    nearestEnvId_ = 0;
    return true;
  }

  EnvironmentObject* env = nullptr;
  EnvironmentIter copy(cx, ei);
  while (copy) {
    if (copy.hasAnyEnvironmentObject()) {
      env = &copy.environment();
      break;
    }
    ++copy;
  }

  // In general, we should find an environment object for the global etc even
  // if we don't find anything else.
  //
  // In certain situation where OOM and too much recursion happens and the
  // debugger is trying to recover from it, we might not find anything, and in
  // that case, there's nothing we can do. (see bug 1976630).
  if (!env) {
    ReportOutOfMemory(cx);
    return false;
  }

  if (!gc::GetOrCreateUniqueId(env, &nearestEnvId_)) {
    ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

/* static */
HashNumber MissingEnvironmentKey::hash(MissingEnvironmentKey ek) {
  return mozilla::HashGeneric(ek.frame_.raw(), ek.nearestEnvId_, ek.scope_);
}

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

bool LiveEnvironmentVal::traceWeak(JSTracer* trc) {
  return TraceWeakEdge(trc, &scope_, "LiveEnvironmentVal::scope_");
}

// 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 {

/*
 * 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 NurseryAllocableProxyHandler {
  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 BlockLexicalEnvironmentObject
   * 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);

    // Handle unaliased formals, vars, lets, and consts at function or module
    // scope.
    if (env->is<CallObject>() || env->is<ModuleEnvironmentObject>()) {
      RootedScript script(cx);
      if (env->is<CallObject>()) {
        CallObject& callobj = env->as<CallObject>();
        RootedFunction fun(cx, &callobj.callee());
        script = JSFunction::getOrCreateScript(cx, fun);
      } else {
        script = env->as<ModuleEnvironmentObject>().module().maybeScript();
        if (!script) {
          return true;
        }
      }

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

      if (action == SET && bi.kind() == BindingKind::Const) {
        ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, id);
        return false;
      }

      if (bi.location().kind() == BindingLocation::Kind::Import) {
        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 (AbstractGeneratorObject* genObj =
                       GetGeneratorObjectForEnvironment(cx, env);
                   genObj && genObj->isSuspended() &&
                   genObj->hasStackStorage()) {
          if (action == GET) {
            vp.set(genObj->getUnaliasedLocal(i));
          } else {
            genObj->setUnaliasedLocal(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;
          }
        }
      }

      // 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 non-syntactic top-level lexical bindings to be
      // aliased.
      if (env->is<LexicalEnvironmentObject>() &&
          !env->is<GlobalLexicalEnvironmentObject>() &&
          env->as<LexicalEnvironmentObject>().isExtensible()) {
        MOZ_ASSERT(!IsSyntacticEnvironment(env));
        return true;
      }

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

      Rooted<Scope*> scope(cx, getEnvironmentScope(*env));
      uint32_t firstFrameSlot = scope->firstFrameSlot();

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

      if (action == SET && bi.kind() == BindingKind::Const) {
        ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, id);
        return false;
      }

      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());
        Value& localVal = frame.unaliasedLocal(local);
        if (action == GET) {
          vp.set(localVal);
        } else {
          // Note: localVal could also be JS_OPTIMIZED_OUT.
          if (localVal.isMagic() &&
              localVal.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
            ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
            return false;
          }

          localVal = vp;
        }
      } else if (AbstractGeneratorObject* genObj =
                     GetGeneratorObjectForEnvironment(cx, debugEnv);
                 genObj && genObj->isSuspended() && genObj->hasStackStorage()) {
        if (action == GET) {
          vp.set(genObj->getUnaliasedLocal(loc.slot()));
        } else {
          genObj->setUnaliasedLocal(loc.slot(), 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;
    }

    if (env->is<WasmFunctionCallObject>()) {
      if (maybeLiveEnv) {
        Rooted<Scope*> scope(cx, getEnvironmentScope(*env));
        uint32_t index = 0;
        for (BindingIter bi(scope); bi; bi++) {
          if (id.isAtom(bi.name())) {
            break;
          }
          MOZ_ASSERT(!bi.isLast());
          index++;
        }

        AbstractFramePtr frame = maybeLiveEnv->frame();
        MOZ_ASSERT(frame.isWasmDebugFrame());
        wasm::DebugFrame* wasmFrame = frame.asWasmDebugFrame();
        if (action == GET) {
          if (!wasmFrame->getLocal(index, vp)) {
            ReportOutOfMemory(cx);
            return false;
          }
          *accessResult = ACCESS_UNALIASED;
        } else {  // if (action == SET)
                  // TODO
        }
      } else {
        *accessResult = ACCESS_LOST;
      }
      return true;
    }

    if (env->is<WasmInstanceEnvironmentObject>()) {
      Rooted<Scope*> scope(cx, getEnvironmentScope(*env));
      MOZ_ASSERT(scope->is<WasmInstanceScope>());
      uint32_t index = 0;
      for (BindingIter bi(scope); bi; bi++) {
        if (id.isAtom(bi.name())) {
          break;
        }
        MOZ_ASSERT(!bi.isLast());
        index++;
      }
      Rooted<WasmInstanceScope*> instanceScope(cx,
                                               &scope->as<WasmInstanceScope>());
      wasm::Instance& instance = instanceScope->instance()->instance();

      if (action == GET) {
        if (instanceScope->memoriesStart() <= index &&
            index < instanceScope->globalsStart()) {
          vp.set(ObjectValue(
              *instance.memory(index - instanceScope->memoriesStart())));
        }
        if (instanceScope->globalsStart() <= index) {
          MOZ_ASSERT(index < instanceScope->namesCount());
          if (!instance.debug().getGlobal(
                  instance, index - instanceScope->globalsStart(), vp)) {
            ReportOutOfMemory(cx);
            return false;
          }
        }
        *accessResult = ACCESS_UNALIASED;
      } else {  // if (action == SET)
                // TODO
      }
      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().dot_this_);
  }

  static bool isFunctionEnvironment(const JSObject& env) {
    return env.is<CallObject>();
  }

  static bool isNonExtensibleLexicalEnvironment(const JSObject& env) {
    return env.is<ScopedLexicalEnvironmentObject>();
  }

  static Scope* getEnvironmentScope(const JSObject& env) {
    if (isFunctionEnvironment(env)) {
      return env.as<CallObject>().callee().nonLazyScript()->bodyScope();
    }
    if (env.is<ModuleEnvironmentObject>()) {
      JSScript* script =
          env.as<ModuleEnvironmentObject>().module().maybeScript();
      return script ? script->bodyScope() : nullptr;
    }
    if (isNonExtensibleLexicalEnvironment(env)) {
      return &env.as<ScopedLexicalEnvironmentObject>().scope();
    }
    if (env.is<VarEnvironmentObject>()) {
      return &env.as<VarEnvironmentObject>().scope();
    }
    if (env.is<WasmInstanceEnvironmentObject>()) {
      return &env.as<WasmInstanceEnvironmentObject>().scope();
    }
    if (env.is<WasmFunctionCallObject>()) {
      return &env.as<WasmFunctionCallObject>().scope();
    }
    if (env.is<GlobalLexicalEnvironmentObject>()) {
      return &env.as<GlobalLexicalEnvironmentObject>()
                  .global()
                  .emptyGlobalScope();
    }
    return nullptr;
  }

  friend Scope* js::GetEnvironmentScope(const JSObject& env);

  /*
   * 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().baseScript()->needsArgsObj();
  }

  /*
   * 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()
                .baseScript()
                ->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 was not otherwise needed.
   */
  static bool isMissingArguments(JSContext* cx, jsid id,
                                 EnvironmentObject& env) {
    return isArguments(cx, id) && isMissingArgumentsBinding(env);
  }
  static bool isMissingThis(JSContext* cx, jsid id, EnvironmentObject& env) {
    return isThis(cx, id) && isMissingThisBinding(env);
  }

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

    AbstractFramePtr frame = maybeEnv->frame();
    if (!GetFunctionThis(cx, frame, thisv)) {
      return false;
    }

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

  static void reportOptimizedOut(JSContext* cx, HandleId id) {
    if (isThis(cx, id)) {
      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               JSMSG_DEBUG_OPTIMIZED_OUT, "this");
      return;
    }

    if (UniqueChars printable =
            IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier)) {
      JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                               JSMSG_DEBUG_OPTIMIZED_OUT, printable.get());
    }
  }

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

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

  static bool isFunctionEnvironmentWithThis(const JSObject& env) {
    // All functions except arrows 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;
  }

  static bool argumentsElementIsOptimizedOut(Handle<ArgumentsObject*> argsObj) {
    for (uint32_t i = 0; i < argsObj->initialLength(); i++) {
      if (argsObj->element(i).isMagic(JS_OPTIMIZED_OUT)) {
        return true;
      }
    }
    return false;
  }

  bool getMissingArgumentsPropertyDescriptor(
      JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
      EnvironmentObject& env,
      MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) const {
    Rooted<ArgumentsObject*> argsObj(cx);
    if (!createMissingArguments(cx, env, &argsObj)) {
      return false;
    }

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

    if (argumentsElementIsOptimizedOut(argsObj)) {
      RootedId id(cx, NameToId(cx->names().arguments));
      reportOptimizedOut(cx, id);
      return false;
    }

    desc.set(mozilla::Some(PropertyDescriptor::Data(
        ObjectValue(*argsObj), {JS::PropertyAttribute::Enumerable})));
    return true;
  }
  bool getMissingThisPropertyDescriptor(
      JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
      EnvironmentObject& env,
      MutableHandle<mozilla::Maybe<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_ON_STACK, "Debugger scope");
      return false;
    }

    desc.set(mozilla::Some(
        PropertyDescriptor::Data(thisv, {JS::PropertyAttribute::Enumerable})));
    return true;
  }

  bool getOwnPropertyDescriptor(
      JSContext* cx, HandleObject proxy, HandleId id,
      MutableHandle<mozilla::Maybe<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: {
        desc.set(mozilla::Some(
            PropertyDescriptor::Data(v, {JS::PropertyAttribute::Enumerable})));
        return true;
      }
      case ACCESS_GENERIC:
        return GetOwnPropertyDescriptor(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 {
    Rooted<ArgumentsObject*> argsObj(cx);
    if (!createMissingArguments(cx, env, &argsObj)) {
      return false;
    }

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

    if (argumentsElementIsOptimizedOut(argsObj)) {
      RootedId id(cx, NameToId(cx->names().arguments));
      reportOptimizedOut(cx, id);
      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_ON_STACK, "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 (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 {
    Rooted<ArgumentsObject*> argsObj(cx);
    if (!createMissingArguments(cx, env, &argsObj)) {
      return false;
    }
    bool optimizedOut = !argsObj || argumentsElementIsOptimizedOut(argsObj);
    vp.set(optimizedOut ? MagicValue(JS_MISSING_ARGUMENTS)
                        : ObjectValue(*argsObj));
    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 (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));
        RootedValue initialVal(cx);
        if (!GetProperty(cx, env, env, id, &initialVal)) {
          return false;
        }
        // Note: initialVal could be JS_OPTIMIZED_OUT, which is why we don't use
        // .whyMagic(JS_UNINITALIZED_LEXICAL).
        if (initialVal.isMagic() &&
            initialVal.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
          ReportRuntimeLexicalErrorId(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
          return false;
        }

        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,
                       MutableHandleIdVector 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().dot_this_))) {
        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;
      bool supportUnscopables =
          env->as<WithEnvironmentObject>().supportUnscopables();
      for (size_t i = 0; i < props.length(); i++) {
        bool inScope = true;
        if (supportUnscopables &&
            !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 */

Scope* js::GetEnvironmentScope(const JSObject& env) {
  return DebugEnvironmentProxyHandler::getEnvironmentScope(env);
}

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.realm() == cx->realm());
  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->setReservedSlot(ENCLOSING_SLOT, ObjectValue(*enclosing));
  debugEnv->setReservedSlot(SNAPSHOT_SLOT, NullValue());

  return debugEnv;
}

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

JSObject& DebugEnvironmentProxy::enclosingEnvironment() const {
  return reservedSlot(ENCLOSING_SLOT).toObject();
}

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

void DebugEnvironmentProxy::initSnapshot(ArrayObject& o) {
#ifdef DEBUG
  if (maybeSnapshot()) {
    auto* callObj = CallObject::find(&environment());
    if (callObj) {
      MOZ_ASSERT(callObj->callee().isGeneratorOrAsync());
    } else {
      auto* moduleEnv = ModuleEnvironmentObject::find(&environment());
      MOZ_ASSERT(moduleEnv);
      MOZ_ASSERT(moduleEnv->module().hasTopLevelAwait());
    }
  }
#endif

  setReservedSlot(SNAPSHOT_SLOT, ObjectValue(o));
}

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

/* static */
bool DebugEnvironmentProxy::getMaybeSentinelValue(
    JSContext* cx, Handle<DebugEnvironmentProxy*> env, HandleId id,
    MutableHandleValue vp) {
  return DebugEnvironmentProxyHandler::singleton.getMaybeSentinelValue(cx, env,
                                                                       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.is<BlockLexicalEnvironmentObject>() &&
           !e.as<BlockLexicalEnvironmentObject>().scope().hasEnvironment();
  }

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

  return false;
}

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

[[nodiscard]] static bool GetFrameEnvironmentAndScope(
    JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc,
    MutableHandleObject env, MutableHandle<Scope*> scope);

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

DebugEnvironments::~DebugEnvironments() { MOZ_ASSERT(missingEnvs.empty()); }

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

void DebugEnvironments::traceWeak(JSTracer* trc) {
  /*
   * missingEnvs points to debug envs weakly so that debug envs can be
   * released more eagerly.
   */
  for (auto iter = missingEnvs.modIter(); !iter.done(); iter.next()) {
    auto result =
        TraceWeakEdge(trc, &iter.get().value(), "MissingEnvironmentMap value");
    if (result.isDead()) {
      /*
       * 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(&result.initialTarget()->environment());
      iter.remove();
    } else {
      MissingEnvironmentKey key = iter.get().key();
      Scope* scope = key.scope();
      MOZ_ALWAYS_TRUE(TraceManuallyBarrieredWeakEdge(
          trc, &scope, "MissingEnvironmentKey scope"));
      if (scope != key.scope()) {
        key.updateScope(scope);
        iter.rekey(key);
      }
    }
  }

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

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

#ifdef JSGC_HASH_TABLE_CHECKS
void DebugEnvironments::checkHashTablesAfterMovingGC() {
  /*
   * 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| is checked automatically because it is a WeakMap.
   */
  CheckTableAfterMovingGC(missingEnvs, [this](const auto& entry) {
    CheckGCThingAfterMovingGC(entry.key().scope(), zone());
    // Use unbarrieredGet() to prevent triggering read barrier while collecting.
    CheckGCThingAfterMovingGC(entry.value().unbarrieredGet(), zone());
    return entry.key();
  });
  CheckTableAfterMovingGC(liveEnvs, [this](const auto& entry) {
    CheckGCThingAfterMovingGC(entry.key(), zone());
    CheckGCThingAfterMovingGC(entry.value().scope_.get(), zone());
    return entry.key().unbarrieredGet();
  });
}
#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->realm()->isDebuggee();
}

DebugEnvironments* DebugEnvironments::ensureRealmData(JSContext* cx) {
  Realm* realm = cx->realm();
  if (auto* debugEnvs = realm->debugEnvs()) {
    return debugEnvs;
  }

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

  realm->debugEnvsRef() = std::move(debugEnvs);
  return realm->debugEnvs();
}

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

  if (JSObject* obj = envs->proxiedEnvs.get(&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->realm() == env->realm());
  MOZ_ASSERT(cx->realm() == debugEnv->nonCCWRealm());

  if (!CanUseDebugEnvironmentMaps(cx)) {
    return true;
  }

  DebugEnvironments* envs = ensureRealmData(cx);
  if (!envs) {
    return false;
  }

  if (!envs->proxiedEnvs.put(env, debugEnv)) {
    ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

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

  DebugEnvironments* envs = cx->realm()->debugEnvs();
  if (!envs) {
    *out = nullptr;
    return true;
  }

  MissingEnvironmentKey key;
  if (!key.initFromEnvironmentIter(cx, ei)) {
    return false;
  }

  if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(key)) {
    MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
    *out = p->value();
    return true;
  }

  *out = nullptr;
  return true;
}

/* static */
bool DebugEnvironments::addDebugEnvironment(
    JSContext* cx, const EnvironmentIter& ei,
    Handle<DebugEnvironmentProxy*> debugEnv) {
  MOZ_ASSERT(!ei.hasSyntacticEnvironment());
  MOZ_ASSERT(cx->realm() == debugEnv->nonCCWRealm());

  if (!CanUseDebugEnvironmentMaps(cx)) {
    return true;
  }

  DebugEnvironments* envs = ensureRealmData(cx);
  if (!envs) {
    return false;
  }

  MissingEnvironmentKey key;
  if (!key.initFromEnvironmentIter(cx, ei)) {
    return false;
  }
  MOZ_ASSERT(!envs->missingEnvs.has(key));
  if (!envs->missingEnvs.put(key,
                             WeakHeapPtr<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.
   */

  // Because this can be called during exception unwinding, save the exception
  // state and restore it when we're done.
  JS::AutoSaveExceptionState ases(cx);

  JSScript* script = frame.script();

  // 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>()) {
    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->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<BlockLexicalEnvironmentObject>()) {
      LexicalScope* scope =
          &debugEnv->environment().as<BlockLexicalEnvironmentObject>().scope();
      frameSlotStart = scope->firstFrameSlot();
      frameSlotEnd = scope->nextFrameSlot();
    } else if (debugEnv->environment()
                   .is<ClassBodyLexicalEnvironmentObject>()) {
      ClassBodyScope* scope = &debugEnv->environment()
                                   .as<ClassBodyLexicalEnvironmentObject>()
                                   .scope();
      frameSlotStart = scope->firstFrameSlot();
      frameSlotEnd = scope->nextFrameSlot();
    } else if (debugEnv->environment().is<VarEnvironmentObject>()) {
      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 == script->bodyScope());
        frameSlotStart = 0;
        frameSlotEnd = scope->nextFrameSlot();
      }
    } else {
      MOZ_ASSERT(&debugEnv->environment().as<ModuleEnvironmentObject>() ==
                 script->module()->environment());
      ModuleScope* scope = &script->bodyScope()->as<ModuleScope>();
      frameSlotStart = 0;
      frameSlotEnd = scope->nextFrameSlot();
    }

    uint32_t frameSlotCount = frameSlotEnd - frameSlotStart;
    MOZ_ASSERT(frameSlotCount <= 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.
   */
  Rooted<ArrayObject*> snapshot(
      cx, NewDenseCopiedArray(cx, vec.length(), vec.begin()));
  if (!snapshot) {
    cx->recoverFromResourceExhaustion();
    return;
  }

  debugEnv->initSnapshot(*snapshot);
}

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

  DebugEnvironments* envs = cx->realm()->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;
    }

    CallObject& callobj = frame.environmentChain()->as<CallObject>();
    envs->liveEnvs.remove(&callobj);
    if (JSObject* obj = envs->proxiedEnvs.get(&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,
                                     const jsbytecode* pc) {
  cx->check(frame);

  DebugEnvironments* envs = cx->realm()->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->realm()->debugEnvs();
  if (!envs) {
    return;
  }

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

  MissingEnvironmentKey key;
  {
    js::AutoEnterOOMUnsafeRegion oomUnsafe;
    if (!key.initFromEnvironmentIter(cx, ei)) {
      oomUnsafe.crash("OOM during onPopGeneric");
      return;
    }
  }
  Rooted<Environment*> env(cx);
  if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(key)) {
    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.get(env)) {
      Rooted<DebugEnvironmentProxy*> debugEnv(
          cx, &obj->as<DebugEnvironmentProxy>());
      DebugEnvironments::takeFrameSnapshot(cx, debugEnv, ei.initialFrame());
    }
  }
}

void DebugEnvironments::onPopLexical(JSContext* cx, const EnvironmentIter& ei) {
  if (ei.scope().is<ClassBodyScope>()) {
    onPopGeneric<ScopedLexicalEnvironmentObject, ClassBodyScope>(cx, ei);
  } else {
    onPopGeneric<ScopedLexicalEnvironmentObject, LexicalScope>(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) {
  Realm* realm = frame.realm();
  if (DebugEnvironments* envs = realm->debugEnvs()) {
    envs->liveEnvs.remove(
        &frame.environmentChain()->as<WithEnvironmentObject>());
  }
}

void DebugEnvironments::onPopModule(JSContext* cx, const EnvironmentIter& ei) {
  onPopGeneric<ModuleEnvironmentObject, ModuleScope>(cx, ei);
}

void DebugEnvironments::onPopWasm(JSContext* cx, AbstractFramePtr frame) {
  MOZ_ASSERT(frame.isWasmDebugFrame());

  DebugEnvironments* envs = cx->realm()->debugEnvs();
  if (!envs) {
    return;
  }

  Rooted<WasmInstanceObject*> instance(cx, frame.wasmInstance()->object());
  uint32_t funcIndex = frame.asWasmDebugFrame()->funcIndex();
  Rooted<Scope*> wasmFunctionScope(
      cx, instance->getExistingFunctionScope(funcIndex));
  if (!wasmFunctionScope) {
    return;
  }

  MissingEnvironmentKey key(frame, wasmFunctionScope);
  if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(key)) {
    EnvironmentObject& env = p->value()->environment();
    envs->liveEnvs.remove(&env);
    envs->missingEnvs.remove(p);
  }
}

void DebugEnvironments::onRealmUnsetIsDebuggee(Realm* realm) {
  if (DebugEnvironments* envs = realm->debugEnvs()) {
    envs->proxiedEnvs.clear();
    envs->missingEnvs.clear();
    envs->liveEnvs.clear();
  }
}

bool DebugEnvironments::updateLiveEnvironments(JSContext* cx) {
  AutoCheckRecursionLimit recursion(cx);
  if (!recursion.check(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().
   */
  RootedObject env(cx);
  Rooted<Scope*> scope(cx);
  for (AllFramesIter i(cx); !i.done(); ++i) {
    if (!i.hasUsableAbstractFramePtr()) {
      continue;
    }

    AbstractFramePtr frame = i.abstractFramePtr();
    if (frame.realm() != cx->realm()) {
      continue;
    }

    if (!frame.isDebuggee()) {
      continue;
    }

    if (!GetFrameEnvironmentAndScope(cx, frame, i.pc(), &env, &scope)) {
      return false;
    }

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

    if (frame.prevUpToDate()) {
      return true;
    }
    MOZ_ASSERT(frame.realm()->isDebuggee());
    frame.setPrevUpToDate();
  }

  return true;
}

LiveEnvironmentVal* DebugEnvironments::hasLiveEnvironment(
    EnvironmentObject& env) {
  DebugEnvironments* envs = env.realm()->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.realm() != cx->realm()) {
      continue;
    }

    frame.unsetPrevUpToDate();
  }
}

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

  for (auto iter = envs->missingEnvs.modIter(); !iter.done(); iter.next()) {
    MissingEnvironmentKey key = iter.get().key();
    if (key.frame() == from) {
      key.updateFrame(to);
      iter.rekey(key);
    }
  }

  for (auto iter = envs->liveEnvs.iter(); !iter.done(); iter.next()) {
    LiveEnvironmentVal& val = iter.get().value();
    if (val.frame() == from) {
      val.updateFrame(to);
    }
  }
}

/* static */
void DebugEnvironments::traceLiveFrame(JSTracer* trc, AbstractFramePtr frame) {
  for (auto iter = missingEnvs.iter(); !iter.done(); iter.next()) {
    if (iter.get().key().frame() == frame) {
      TraceEdge(trc, &iter.get().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<WasmInstanceScope>() ||
              ei.scope().is<WasmFunctionScope>() || ei.scope().is<VarScope>() ||
              ei.scope().kind() == ScopeKind::StrictEval));

  DebugEnvironmentProxy* maybeDebugEnv;
  if (!DebugEnvironments::getExistingDebugEnvironment(cx, ei, &maybeDebugEnv)) {
    return nullptr;
  }
  if (maybeDebugEnv) {
    return maybeDebugEnv;
  }

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

    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<BlockLexicalEnvironmentObject*> env(
        cx,
        BlockLexicalEnvironmentObject::createHollowForDebug(cx, lexicalScope));
    if (!env) {
      return nullptr;
    }

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

    debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
  } else if (ei.scope().is<WasmFunctionScope>()) {
    Rooted<WasmFunctionScope*> wasmFunctionScope(
        cx, &ei.scope().as<WasmFunctionScope>());
    RootedObject enclosing(
        cx, &enclosingDebug->as<DebugEnvironmentProxy>().environment());
    Rooted<WasmFunctionCallObject*> callobj(
        cx, WasmFunctionCallObject::createHollowForDebug(cx, enclosing,
                                                         wasmFunctionScope));
    if (!callobj) {
      return nullptr;
    }

    debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug);
  } else {
    Rooted<Scope*> scope(cx, &ei.scope());
    MOZ_ASSERT(scope->is<VarScope>() || scope->kind() == ScopeKind::StrictEval);

    Rooted<VarEnvironmentObject*> env(
        cx, VarEnvironmentObject::createHollowForDebug(cx, scope));
    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) {
  AutoCheckRecursionLimit recursion(cx);
  if (!recursion.check(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<WasmInstanceScope>() ||
      ei.scope().is<WasmFunctionScope>() || ei.scope().is<VarScope>() ||
      ei.scope().kind() == ScopeKind::StrictEval) {
    return GetDebugEnvironmentForMissing(cx, ei);
  }

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

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

static auto GetSuspendedGeneratorEnvironmentAndScope(
    AbstractGeneratorObject& genObj, JSScript* script) {
  jsbytecode* pc =
      script->offsetToPC(script->resumeOffsets()[genObj.resumeIndex()]);
  return std::pair{&genObj.environmentChain(), script->innermostScope(pc)};
}

JSObject* js::GetDebugEnvironmentForSuspendedGenerator(
    JSContext* cx, JSScript* script, AbstractGeneratorObject& genObj) {
  auto [env, scope] = GetSuspendedGeneratorEnvironmentAndScope(genObj, script);

  EnvironmentIter ei(cx, env, scope);
  return GetDebugEnvironment(cx, ei);
}

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

  RootedObject env(cx);
  Rooted<Scope*> scope(cx);
  if (!GetFrameEnvironmentAndScope(cx, frame, pc, &env, &scope)) {
    return nullptr;
  }

  EnvironmentIter ei(cx, env, scope, frame);
  return GetDebugEnvironment(cx, ei);
}

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

WithEnvironmentObject* js::CreateObjectsForEnvironmentChain(
    JSContext* cx, const JS::EnvironmentChain& chain,
    HandleObject terminatingEnv) {
  MOZ_ASSERT(!chain.empty());

#ifdef DEBUG
  for (size_t i = 0; i < chain.length(); ++i) {
    cx->check(chain.chain()[i]);
    MOZ_ASSERT(!chain.chain()[i]->isUnqualifiedVarObj());
  }
#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.chain()[--i], enclosingEnv, chain.supportUnscopables());
    if (!withEnv) {
      return nullptr;
    }
    enclosingEnv = withEnv;
  }

  return withEnv;
}

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

JSObject* WithEnvironmentObject::withThis() const {
  JSObject* obj = &getReservedSlot(THIS_SLOT).toObject();

  // Windows must never be exposed to script. WithEnvironmentObject::create
  // should have set this to the WindowProxy.
  MOZ_ASSERT(!IsWindow(obj));

  return obj;
}

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

bool WithEnvironmentObject::supportUnscopables() const {
  if (isSyntactic()) {
    return true;
  }
  Value v = getReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT);
  MOZ_ASSERT(v.isBoolean());
  return v.isTrue();
}

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

ModuleEnvironmentObject* js::GetModuleEnvironmentForScript(JSScript* script) {
  return GetModuleObjectForScript(script)->environment();
}

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

  MOZ_CRASH("No module scope found for script");
}

static bool GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut(
    JSContext* cx, const EnvironmentIter& originalIter, HandleObject envChain,
    const jsbytecode* pc, MutableHandleValue res) {
  RootedTuple<JSScript*, JSObject*, JSObject*> thisRoots(cx);
  RootedField<JSScript*> script(thisRoots);
  RootedField<JSObject*, 1> callObj(thisRoots);
  RootedField<JSObject*, 2> env(thisRoots);
  for (EnvironmentIter ei(cx, originalIter); ei; ei++) {
    if (ei.scope().kind() == ScopeKind::Module) {
      res.setUndefined();
      return true;
    }

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

    script = ei.scope().as<FunctionScope>().script();

    if (ei.withinInitialFrame()) {
      MOZ_ASSERT(pc, "must have PC if there is an initial frame");

      // Figure out if we executed JSOp::FunctionThis and set it.
      bool executedInitThisOp = false;
      if (script->functionHasThisBinding()) {
        for (const BytecodeLocation& loc : js::AllBytecodesIterable(script)) {
          if (loc.getOp() == JSOp::FunctionThis) {
            // The next op after JSOp::FunctionThis always sets it.
            executedInitThisOp = pc > GetNextPc(loc.toRawBytecode());
            break;
          }
        }
      }

      if (!executedInitThisOp) {
        AbstractFramePtr initialFrame = ei.initialFrame();
        // 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 (initialFrame.thisArgument().isObject() || script->strict()) {
          res.set(initialFrame.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, initialFrame, res)) {
          return false;
        }
        initialFrame.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().dot_this_) {
        continue;
      }

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

      if (loc.kind() == BindingLocation::Kind::Frame) {
        if (ei.withinInitialFrame()) {
          res.set(ei.initialFrame().unaliasedLocal(loc.slot()));
          return true;
        }

        if (ei.hasAnyEnvironmentObject()) {
          env = &ei.environment();
          AbstractGeneratorObject* genObj =
              GetGeneratorObjectForEnvironment(cx, env);
          if (genObj && genObj->isSuspended() && genObj->hasStackStorage()) {
            res.set(genObj->getUnaliasedLocal(loc.slot()));
            return true;
          }
        }
      }

      res.setMagic(JS_OPTIMIZED_OUT);
      return true;
    }

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

  GetNonSyntacticGlobalThis(cx, envChain, res);
  return true;
}

bool js::GetThisValueForDebuggerFrameMaybeOptimizedOut(JSContext* cx,
                                                       AbstractFramePtr frame,
                                                       const jsbytecode* pc,
                                                       MutableHandleValue res) {
  RootedObject envChain(cx);
  Rooted<Scope*> scope(cx);
  if (!GetFrameEnvironmentAndScope(cx, frame, pc, &envChain, &scope)) {
    return false;
  }

  EnvironmentIter ei(cx, envChain, scope, frame);
  return GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut(
      cx, ei, envChain, pc, res);
}

bool js::GetThisValueForDebuggerSuspendedGeneratorMaybeOptimizedOut(
    JSContext* cx, AbstractGeneratorObject& genObj, JSScript* script,
    MutableHandleValue res) {
  auto [env, scope] = GetSuspendedGeneratorEnvironmentAndScope(genObj, script);
  Rooted<JSObject*> envChain(cx, env);

  EnvironmentIter ei(cx, envChain, scope);
  return GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut(
      cx, ei, envChain, nullptr, res);
}

static void ReportRuntimeRedeclaration(JSContext* cx,
                                       Handle<PropertyName*> name,
                                       const char* redeclKind) {
  if (UniqueChars printable = AtomToPrintableString(cx, name)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_REDECLARED_VAR, redeclKind,
                              printable.get());
  }
}

[[nodiscard]] static bool CheckLexicalNameConflict(
    JSContext* cx, Handle<ExtensibleLexicalEnvironmentObject*> lexicalEnv,
    HandleObject varObj, Handle<PropertyName*> name) {
  const char* redeclKind = nullptr;
  RootedId id(cx, NameToId(name));
  mozilla::Maybe<PropertyInfo> prop, shadowedExistingProp;

  if ((prop = lexicalEnv->lookup(cx, name))) {
    // ES 15.1.11 step 5.b
    redeclKind = prop->writable() ? "let" : "const";
  } else if (varObj->is<NativeObject>() &&
             (prop = 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 (!prop->configurable()) {
      redeclKind = "non-configurable global property";
    } else {
      shadowedExistingProp = std::move(prop);
    }
  } else {
    // ES 15.1.11 step 5.c-d
    Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
    if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc)) {
      return false;
    }
    if (desc.isSome()) {
      if (!desc->configurable()) {
        redeclKind = "non-configurable global property";
      } else {
        // Note: we don't have to set |shadowedExistingProp| here because if
        // |varObj| is a global object, the NativeObject::lookup call above
        // ensures this wasn't an existing property (that might require JIT/IC
        // invalidation) but a new property defined by a resolve hook.
        MOZ_ASSERT_IF(varObj->is<GlobalObject>(),
                      varObj->getClass()->getResolve());
        MOZ_ASSERT_IF(varObj->is<GlobalObject>(),
                      varObj->as<GlobalObject>().containsPure(name));
      }
    }
  }

  if (redeclKind) {
    ReportRuntimeRedeclaration(cx, name, redeclKind);
    return false;
  }
  if (shadowedExistingProp && varObj->is<GlobalObject>()) {
    // Shadowing a configurable global property with a new lexical is one
    // of the rare ways to invalidate a GetGName stub.
    auto* global = &varObj->as<GlobalObject>();
    global->bumpGenerationCount();

    // Also invalidate GetGName stubs and Ion code relying on an object fuse
    // guard to bake in the property's value.
    if (global->hasObjectFuse()) {
      if (auto* objFuse = cx->zone()->objectFuses.get(global)) {
        objFuse->handleShadowedGlobalProperty(cx, *shadowedExistingProp);
      }
    }
  }

  return true;
}

[[nodiscard]] static bool CheckVarNameConflict(
    JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
    Handle<PropertyName*> name) {
  mozilla::Maybe<PropertyInfo> prop = lexicalEnv->lookup(cx, name);
  if (prop.isSome()) {
    ReportRuntimeRedeclaration(cx, name, prop->writable() ? "let" : "const");
    return false;
  }
  return true;
}

static void ReportCannotDeclareGlobalBinding(JSContext* cx,
                                             Handle<PropertyName*> name,
                                             const char* reason) {
  if (UniqueChars printable = AtomToPrintableString(cx, name)) {
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                              JSMSG_CANT_DECLARE_GLOBAL_BINDING,
                              printable.get(), reason);
  }
}

[[nodiscard]] static bool CheckCanDeclareGlobalBinding(
    JSContext* cx, Handle<GlobalObject*> global, Handle<PropertyName*> name,
    bool isFunction) {
  RootedId id(cx, NameToId(name));
  Rooted<mozilla::Maybe<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.isNothing()) {
    // 8.1.14.15 step 6.
    // 8.1.14.16 step 5.
    if (global->isExtensible()) {
      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;
}

// Add the var/let/const bindings to the variables environment of a global or
// sloppy-eval script. The redeclaration checks should already have been
// performed.
static bool InitGlobalOrEvalDeclarations(
    JSContext* cx, HandleScript script,
    Handle<ExtensibleLexicalEnvironmentObject*> lexicalEnv,
    HandleObject varObj) {
  Rooted<BindingIter> bi(cx, BindingIter(script));
  RootedTuple<PropertyName*, JSObject*, jsid, Value> declRoots(cx);
  RootedField<PropertyName*> name(declRoots);
  RootedField<JSObject*> obj2(declRoots);
  RootedField<jsid> id(declRoots);
  RootedField<Value> uninitialized(declRoots);
  for (; bi; bi++) {
    if (bi.isTopLevelFunction()) {
      continue;
    }

    name = bi.name()->asPropertyName();
    unsigned attrs = script->isForEval() ? JSPROP_ENUMERATE
                                         : JSPROP_ENUMERATE | JSPROP_PERMANENT;

    switch (bi.kind()) {
      case BindingKind::Var: {
        PropertyResult prop;
        if (!LookupProperty(cx, varObj, name, &obj2, &prop)) {
          return false;
        }

        if (prop.isNotFound() ||
            (obj2 != varObj && varObj->is<GlobalObject>())) {
          if (!DefineDataProperty(cx, varObj, name, UndefinedHandleValue,
                                  attrs)) {
            return false;
          }
        }

        break;
      }

      case BindingKind::Const:
        attrs |= JSPROP_READONLY;
        [[fallthrough]];

      case BindingKind::Let: {
        id = NameToId(name);
        uninitialized = MagicValue(JS_UNINITIALIZED_LEXICAL);
        if (!NativeDefineDataProperty(cx, lexicalEnv, id, uninitialized,
                                      attrs)) {
          return false;
        }

        break;
      }

      default:
        MOZ_CRASH("Expected binding kind");
        return false;
    }
  }

  return true;
}

// Define the hoisted top-level functions on the variables environment of a
// global or sloppy-eval script. Redeclaration checks must already have been
// performed.
static bool InitHoistedFunctionDeclarations(JSContext* cx, HandleScript script,
                                            HandleObject envChain,
                                            HandleObject varObj,
                                            GCThingIndex lastFun) {
  // The inner-functions up to `lastFun` are the hoisted function declarations
  // of the script. We must clone and bind them now.
  RootedTuple<JSFunction*, PropertyName*, Value, JSObject*, jsid> funRoots(cx);
  RootedField<JSFunction*> fun(funRoots);
  RootedField<PropertyName*> name(funRoots);
  RootedField<Value> rval(funRoots);
  RootedField<JSObject*> pobj(funRoots);
  RootedField<jsid> id(funRoots);
  for (size_t i = 0; i <= lastFun; ++i) {
    JS::GCCellPtr thing = script->gcthings()[i];

    // Skip the initial scopes. In practice, there is at most one variables and
    // one lexical scope.
    if (thing.is<js::Scope>()) {
      MOZ_ASSERT(i < 2);
      continue;
    }

    fun = &thing.as<JSObject>().as<JSFunction>();
    name = fun->fullExplicitName()->asPropertyName();

    // Clone the function before exposing to script as a binding.
    JSObject* clone = Lambda(cx, fun, envChain);
    if (!clone) {
      return false;
    }
    rval = ObjectValue(*clone);

    PropertyResult prop;
    if (!LookupProperty(cx, varObj, name, &pobj, &prop)) {
      return false;
    }

    // ECMA requires functions defined when entering Eval code to be
    // impermanent.
    unsigned attrs = script->isForEval() ? JSPROP_ENUMERATE
                                         : JSPROP_ENUMERATE | JSPROP_PERMANENT;

    if (prop.isNotFound() || pobj != varObj) {
      if (!DefineDataProperty(cx, varObj, name, rval, attrs)) {
        return false;
      }

      // Done processing this function.
      continue;
    }

    /*
     * A DebugEnvironmentProxy is okay here, and sometimes necessary. If
     * Debugger.Frame.prototype.eval defines a function with the same name as an
     * extant variable in the frame, the DebugEnvironmentProxy takes care of
     * storing the function in the stack frame (for non-aliased variables) or on
     * the scope object (for aliased).
     */
    MOZ_ASSERT(varObj->is<NativeObject>() ||
               varObj->is<DebugEnvironmentProxy>());
    if (varObj->is<GlobalObject>()) {
      PropertyInfo propInfo = prop.propertyInfo();
      if (propInfo.configurable()) {
        if (!DefineDataProperty(cx, varObj, name, rval, attrs)) {
          return false;
        }
      } else {
        MOZ_ASSERT(propInfo.isDataProperty());
        MOZ_ASSERT(propInfo.writable());
        MOZ_ASSERT(propInfo.enumerable());
      }
    }

    /*
     * Non-global properties, and global properties which we aren't simply
     * redefining, must be set.  First, this preserves their attributes.
     * Second, this will produce warnings and/or errors as necessary if the
     * specified Call object property is not writable (const).
     */

    id = NameToId(name);
    if (!PutProperty(cx, varObj, id, rval, script->strict())) {
      return false;
    }
  }

  return true;
}

[[nodiscard]] static bool CheckGlobalDeclarationConflicts(
    JSContext* cx, HandleScript script,
    Handle<ExtensibleLexicalEnvironmentObject*> 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.
  Rooted<PropertyName*> 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;
}

[[nodiscard]] 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->is<BlockLexicalEnvironmentObject>() &&
      env->as<BlockLexicalEnvironmentObject>().scope().kind() ==
          ScopeKind::SimpleCatch) {
    // Annex B.3.5 allows redeclaring simple (non-destructured) catch parameters
    // with var declarations.
    return true;
  }

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

  return true;
}

static bool CheckArgumentsRedeclaration(JSContext* cx, HandleScript script) {
  for (BindingIter bi(script); bi; bi++) {
    if (bi.name() == cx->names().arguments) {
      ReportRuntimeRedeclaration(cx, cx->names().arguments, "let");
      return false;
    }
  }

  return true;
}

static bool CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script,
                                          HandleObject envChain,
                                          HandleObject varObj) {
  // Strict eval has its own call objects and we shouldn't end up here.
  //
  // Non-strict eval may introduce 'var' bindings that conflict with lexical
  // bindings in an enclosing lexical scope.
  MOZ_ASSERT(!script->bodyScope()->hasEnvironment());
  MOZ_ASSERT(!script->strict());

  MOZ_ASSERT(script->bodyScope()->as<EvalScope>().hasBindings());

  RootedObject env(cx, envChain);

  // 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 (env != varObj) {
    if (!CheckVarNameConflictsInEnv(cx, script, env)) {
      return false;
    }
    env = env->enclosingEnvironment();
  }

  // Check for redeclared "arguments" in function parameter expressions.
  //
  // Direct eval in function parameter expressions isn't allowed to redeclare
  // the implicit "arguments" bindings:
  //   function f(a = eval("var arguments;")) {}
  //
  // |varObj| isn't a CallObject when the direct eval occurs in the function
  // body and the extra function body var scope is present. The extra var scope
  // is present iff the function has parameter expressions. So when we test
  // that |varObj| is a CallObject and function parameter expressions are
  // present, we can pinpoint the direct eval location to be in a function
  // parameter expression. Additionally we must ensure the function isn't an
  // arrow function, because arrow functions don't have an implicit "arguments"
  // binding.
  if (script->isDirectEvalInFunction() && varObj->is<CallObject>()) {
    JSFunction* fun = &varObj->as<CallObject>().callee();
    JSScript* funScript = fun->nonLazyScript();
    if (funScript->functionHasParameterExprs() && !fun->isArrow()) {
      if (!CheckArgumentsRedeclaration(cx, script)) {
        return false;
      }
    }
  }

  // Step 8.
  //
  // Check that global functions may be declared.
  if (varObj->is<GlobalObject>()) {
    Handle<GlobalObject*> global = varObj.as<GlobalObject>();
    Rooted<PropertyName*> 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::GlobalOrEvalDeclInstantiation(JSContext* cx, HandleObject envChain,
                                       HandleScript script,
                                       GCThingIndex lastFun) {
  MOZ_ASSERT(script->isGlobalCode() || script->isForEval());
  MOZ_ASSERT(!script->selfHosted());

  RootedObject varObj(cx, &GetVariablesObject(envChain));
  Rooted<ExtensibleLexicalEnvironmentObject*> lexicalEnv(cx);

  if (script->isForEval()) {
    if (!CheckEvalDeclarationConflicts(cx, script, envChain, varObj)) {
      return false;
    }
  } else {
    lexicalEnv = &NearestEnclosingExtensibleLexicalEnvironment(envChain);
    if (!CheckGlobalDeclarationConflicts(cx, script, lexicalEnv, varObj)) {
      return false;
    }
  }

  if (!InitGlobalOrEvalDeclarations(cx, script, lexicalEnv, varObj)) {
    return false;
  }

  return InitHoistedFunctionDeclarations(cx, script, envChain, varObj, lastFun);
}

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

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

  gc::AllocSite* site = nullptr;
  if (frame.isBaselineFrame()) {
    site = frame.asBaselineFrame()->icScript()->maybeEnvAllocSite();
  }

  // Named lambdas may have an environment that holds itself for recursion.
  if (callee->needsNamedLambdaEnvironment()) {
    NamedLambdaObject* declEnv =
        NamedLambdaObject::createForFrame(cx, frame, site);
    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::createForFrame(cx, frame, site);
    if (!callObj) {
      return false;
    }
    frame.pushOnEnvironmentChain(*callObj);
  }

  return true;
}

bool js::PushVarEnvironmentObject(JSContext* cx, Handle<Scope*> scope,
                                  AbstractFramePtr frame) {
  auto* env = VarEnvironmentObject::createForFrame(cx, scope, frame);
  if (!env) {
    return false;
  }
  frame.pushOnEnvironmentChain(*env);
  return true;
}

static bool GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame,
                                        const jsbytecode* pc,
                                        MutableHandleObject env,
                                        MutableHandle<Scope*> scope) {
  env.set(frame.environmentChain());

  if (frame.isWasmDebugFrame()) {
    Rooted<WasmInstanceObject*> instance(cx, frame.wasmInstance()->object());
    uint32_t funcIndex = frame.asWasmDebugFrame()->funcIndex();
    scope.set(WasmInstanceObject::getFunctionScope(cx, instance, funcIndex));
    if (!scope) {
      return false;
    }
  } else {
    scope.set(frame.script()->innermostScope(pc));
  }
  return true;
}

#ifdef DEBUG

using PropertyNameSet = HashSet<PropertyName*>;

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.

  AllBytecodesIterable iter(script);
  for (BytecodeLocation loc : iter) {
    PropertyName* name;

    switch (loc.getOp()) {
      case JSOp::GetName:
      case JSOp::SetName:
      case JSOp::StrictSetName:
        name = script->getName(loc.toRawBytecode());
        break;

      case JSOp::GetAliasedVar:
      case JSOp::SetAliasedVar:
        name = EnvironmentCoordinateNameSlow(script, loc.toRawBytecode());
        break;

      default:
        name = nullptr;
        break;
    }

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

  RootedFunction fun(cx);
  RootedScript innerScript(cx);
  for (JS::GCCellPtr gcThing : script->gcthings()) {
    if (!gcThing.is<JSObject>()) {
      continue;
    }
    JSObject* obj = &gcThing.as<JSObject>();

    if (!obj->is<JSFunction>()) {
      continue;
    }
    fun = &obj->as<JSFunction>();

    if (!fun->isInterpreted()) {
      continue;
    }

    innerScript = JSFunction::getOrCreateScript(cx, fun);
    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);

  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->function()->fullDisplayAtom()) {
      buf.putString(cx, name);
      buf.printf(" ");
    }

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

    if (JSAtom* name = innerScript->function()->fullDisplayAtom()) {
      buf.putString(cx, name);
      buf.printf(" ");
    }

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

    for (auto iter = remainingNames.iter(); !iter.done(); iter.next()) {
      buf.printf(" ");
      buf.putString(cx, iter.get());
    }

    JS::UniqueChars str = buf.release();
    if (!str) {
      return false;
    }
    printf("%s\n", str.get());
  }

  RootedFunction fun(cx);
  RootedScript innerInnerScript(cx);
  for (JS::GCCellPtr gcThing : script->gcthings()) {
    if (!gcThing.is<JSObject>()) {
      continue;
    }
    JSObject* obj = &gcThing.as<JSObject>();

    if (!obj->is<JSFunction>()) {
      continue;
    }
    fun = &obj->as<JSFunction>();

    if (!fun->isInterpreted()) {
      continue;
    }

    innerInnerScript = JSFunction::getOrCreateScript(cx, fun);
    if (!innerInnerScript) {
      return false;
    }

    if (!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) {
  RootedFunction fun(cx);
  RootedScript innerScript(cx);
  for (JS::GCCellPtr gcThing : script->gcthings()) {
    if (!gcThing.is<JSObject>()) {
      continue;
    }
    JSObject* obj = &gcThing.as<JSObject>();

    if (!obj->is<JSFunction>()) {
      continue;
    }
    fun = &obj->as<JSFunction>();

    if (!fun->isInterpreted()) {
      continue;
    }

    innerScript = JSFunction::getOrCreateScript(cx, fun);
    if (!innerScript) {
      return false;
    }

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

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

  return true;
}
#endif

JSObject* js::MaybeOptimizeBindUnqualifiedGlobalName(GlobalObject* global,
                                                     PropertyName* name) {
  // We can bind name to the global lexical scope if the binding already
  // exists, is initialized, and is writable (i.e., an initialized
  // 'let') at compile time.
  GlobalLexicalEnvironmentObject* env = &global->lexicalEnvironment();
  mozilla::Maybe<PropertyInfo> prop = env->lookupPure(name);
  if (prop.isSome()) {
    if (prop->writable() &&
        !env->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
      return env;
    }
    return nullptr;
  }

  prop = global->lookupPure(name);
  if (prop.isSome()) {
    // If the property does not currently exist on the global lexical
    // scope, we can bind name to the global object if the property
    // exists on the global and is non-configurable, as then it cannot
    // be shadowed.
    if (!prop->configurable()) {
      return global;
    }
  }

  return nullptr;
}

const char* EnvironmentObject::typeString() const {
  if (is<CallObject>()) {
    return "CallObject";
  }
  if (is<VarEnvironmentObject>()) {
    return "VarEnvironmentObject";
  }
  if (is<ModuleEnvironmentObject>()) {
    return "ModuleEnvironmentObject";
  }
  if (is<WasmInstanceEnvironmentObject>()) {
    return "WasmInstanceEnvironmentObject";
  }
  if (is<WasmFunctionCallObject>()) {
    return "WasmFunctionCallObject";
  }
  if (is<LexicalEnvironmentObject>()) {
    if (is<ScopedLexicalEnvironmentObject>()) {
      if (is<BlockLexicalEnvironmentObject>()) {
        if (is<NamedLambdaObject>()) {
          return "NamedLambdaObject";
        }
        return "BlockLexicalEnvironmentObject";
      }
      if (is<ClassBodyLexicalEnvironmentObject>()) {
        return "ClassBodyLexicalEnvironmentObject";
      }
      return "ScopedLexicalEnvironmentObject";
    }

    if (is<ExtensibleLexicalEnvironmentObject>()) {
      if (is<GlobalLexicalEnvironmentObject>()) {
        return "GlobalLexicalEnvironmentObject";
      }
      if (is<NonSyntacticLexicalEnvironmentObject>()) {
        return "NonSyntacticLexicalEnvironmentObject";
      }
      return "ExtensibleLexicalEnvironmentObject";
    }

    return "LexicalEnvironmentObject";
  }
  if (is<NonSyntacticVariablesObject>()) {
    return "NonSyntacticVariablesObject";
  }
  if (is<WithEnvironmentObject>()) {
    return "WithEnvironmentObject";
  }
  if (is<RuntimeLexicalErrorObject>()) {
    return "RuntimeLexicalErrorObject";
  }

  return "EnvironmentObject";
}

#if defined(DEBUG) || defined(JS_JITSPEW)
static void DumpEnvironmentObject(JSObject* unrootedEnvObj) {
  JSContext* cx = TlsContext.get();
  if (!cx) {
    fprintf(stderr, "*** can't get JSContext for current thread\n");
    return;
  }

  Rooted<JSObject*> envObj(cx, unrootedEnvObj);
  while (envObj) {
    Rooted<EnvironmentObject*> env(cx);
    if (envObj->is<EnvironmentObject>()) {
      env = &envObj->as<EnvironmentObject>();
    } else if (envObj->is<DebugEnvironmentProxy>()) {
      fprintf(stderr, "[DebugProxy] ");
      env = &envObj->as<DebugEnvironmentProxy>().environment();
    } else {
      MOZ_ASSERT(envObj->is<GlobalObject>());
      fprintf(stderr, "global\n");
      break;
    }

    fprintf(stderr, "%s (%p)", env->typeString(), env.get());

    Rooted<Scope*> scope(cx);
    if (env->is<VarEnvironmentObject>()) {
      scope = &env->as<VarEnvironmentObject>().scope();
    }
    if (env->is<WasmInstanceEnvironmentObject>()) {
      scope = &env->as<WasmInstanceEnvironmentObject>().scope();
    } else if (env->is<WasmFunctionCallObject>()) {
      scope = &env->as<WasmFunctionCallObject>().scope();
    } else if (env->is<ScopedLexicalEnvironmentObject>()) {
      scope = &env->as<ScopedLexicalEnvironmentObject>().scope();
    }

    bool hadProp = false;

    // Set of names in the scope.
    // This is used for filtering out those names from properties bwlow.
    Rooted<GCHashSet<JSAtom*>> names(cx, GCHashSet<JSAtom*>(cx, 0));

    if (scope) {
      if (!hadProp) {
        fprintf(stderr, " {\n");
      }
      hadProp = true;
      for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
        fprintf(stderr, "  ");

        switch (bi.location().kind()) {
          case BindingLocation::Kind::Global:
            if (bi.isTopLevelFunction()) {
              fprintf(stderr, "global function: ");
            } else {
              fprintf(stderr, "global: ");
            }
            break;
          case BindingLocation::Kind::Argument:
            fprintf(stderr, "arg slot %u: ", bi.location().argumentSlot());
            break;
          case BindingLocation::Kind::Frame:
            fprintf(stderr, "frame slot %u: ", bi.location().slot());
            break;
          case BindingLocation::Kind::Environment:
            fprintf(stderr, "env slot %u: %s ", bi.location().slot(),
                    BindingKindString(bi.kind()));
            break;
          case BindingLocation::Kind::NamedLambdaCallee:
            fprintf(stderr, "named lambda callee: ");
            break;
          case BindingLocation::Kind::Import:
            fprintf(stderr, "import: ");
            break;
        }

        JSAtom* name = bi.name();
        if (!names.put(name)) {
          fprintf(stderr, "  *** out of memory\n");
          return;
        }

        UniqueChars bytes = AtomToPrintableString(cx, name);
        if (!bytes) {
          fprintf(stderr, "  *** out of memory\n");
          return;
        }
        fprintf(stderr, "%s\n", bytes.get());
      }
    }

    // The environment object can have random properties that can be found in
    // the name lookup.  Show them as well, excluding the properties which
    // are already shown above for the scope.
    if (PropMap* map = env->shape()->propMap()) {
      Vector<PropMap*, 8, SystemAllocPolicy> maps;
      while (true) {
        if (!maps.append(map)) {
          fprintf(stderr, "  *** out of memory\n");
          return;
        }
        if (!map->hasPrevious()) {
          break;
        }
        map = map->asLinked()->previous();
      }

      for (size_t i = maps.length(); i > 0; i--) {
        size_t index = i - 1;
        PropMap* map = maps[index];
        uint32_t len = (index == 0) ? env->shape()->asNative().propMapLength()
                                    : PropMap::Capacity;
        for (uint32_t j = 0; j < len; j++) {
          if (!map->hasKey(j)) {
            MOZ_ASSERT(map->isDictionary());
            continue;
          }

          PropertyKey propKey = map->getKey(j);
          if (propKey.isAtom()) {
            JSAtom* name = propKey.toAtom();
            if (names.has(name)) {
              continue;
            }
          }

          JS::UniqueChars propChars = map->getPropertyNameAt(j);
          if (!propChars) {
            fprintf(stderr, "  *** out of memory\n");
            return;
          }

          if (!hadProp) {
            fprintf(stderr, " {\n");
          }
          hadProp = true;

          PropertyInfo prop = map->getPropertyInfo(j);
          if (prop.hasSlot()) {
            fprintf(stderr, "  prop %u: %s\n", prop.slot(), propChars.get());
          } else {
            fprintf(stderr, "  prop: %s\n", propChars.get());
          }
        }
      }
    }

    if (hadProp) {
      fprintf(stderr, "}");
    }

    fprintf(stderr, "\n");

    if (envObj->is<DebugEnvironmentProxy>()) {
      envObj = &envObj->as<DebugEnvironmentProxy>().enclosingEnvironment();
    } else {
      envObj = &env->enclosingEnvironment();
    }

    if (envObj) {
      fprintf(stderr, "-> ");
    }
  }
}

void EnvironmentObject::dump() { DumpEnvironmentObject(this); }

void DebugEnvironmentProxy::dump() { DumpEnvironmentObject(this); }
#endif /* defined(DEBUG) || defined(JS_JITSPEW) */
