From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- js/src/vm/EnvironmentObject.cpp | 4399 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 4399 insertions(+) create mode 100644 js/src/vm/EnvironmentObject.cpp (limited to 'js/src/vm/EnvironmentObject.cpp') diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp new file mode 100644 index 0000000000..664eb39bd6 --- /dev/null +++ b/js/src/vm/EnvironmentObject.cpp @@ -0,0 +1,4399 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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/ModuleObject.h" +#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 "vm/BytecodeIterator-inl.h" +#include "vm/NativeObject-inl.h" +#include "vm/Stack-inl.h" + +using namespace js; + +using RootedArgumentsObject = Rooted; +using MutableHandleArgumentsObject = MutableHandle; + +/*****************************************************************************/ + +SharedShape* js::EnvironmentCoordinateToEnvironmentShape(JSScript* script, + jsbytecode* pc) { + MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD); + ScopeIter si(script->innermostScope(pc)); + uint32_t hops = EnvironmentCoordinate(pc).hops(); + while (true) { + MOZ_ASSERT(!si.done()); + if (si.hasSyntacticEnvironment()) { + if (!hops) { + break; + } + hops--; + } + si++; + } + return si.environmentShape(); +} + +PropertyName* js::EnvironmentCoordinateNameSlow(JSScript* script, + jsbytecode* pc) { + SharedShape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc); + EnvironmentCoordinate ec(pc); + + SharedShapePropertyIter 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 +static T* CreateEnvironmentObject(JSContext* cx, Handle shape, + gc::Heap heap) { + static_assert(std::is_base_of_v, + "T must be an EnvironmentObject"); + + // All environment objects can be background-finalized. + gc::AllocKind allocKind = gc::GetGCObjectKind(shape->numFixedSlots()); + MOZ_ASSERT(CanChangeToBackgroundAllocKind(allocKind, &T::class_)); + allocKind = gc::ForegroundToBackgroundAllocKind(allocKind); + + JSObject* obj = NativeObject::create(cx, allocKind, heap, shape); + if (!obj) { + return nullptr; + } + + return &obj->as(); +} + +// Helper function for simple environment objects that don't need the overloads +// above. +template +static T* CreateEnvironmentObject(JSContext* cx, Handle shape, + NewObjectKind newKind = GenericObject) { + gc::Heap heap = GetInitialHeap(newKind, &T::class_); + return CreateEnvironmentObject(cx, shape, heap); +} + +CallObject* CallObject::createWithShape(JSContext* cx, + Handle shape) { + return CreateEnvironmentObject(cx, shape); +} + +/* + * 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) { + Rooted shape( + cx, script->bodyScope()->as().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(cx, shape, heap); + 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::create(JSContext* cx, AbstractFramePtr frame) { + 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 = gc::Heap::Default; + CallObject* callobj = create(cx, script, envChain, heap); + if (!callobj) { + return nullptr; + } + + callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee)); + + return callobj; +} + +template +EnvT* FindEnclosingEnv(JSObject* env) { + for (;;) { + if (env->is()) { + break; + } else if (env->is()) { + env = &env->as().enclosingEnvironment(); + } else if (env->is()) { + EnvironmentObject& unwrapped = + env->as().environment(); + if (unwrapped.is()) { + env = &unwrapped; + break; + } + env = &env->as().enclosingEnvironment(); + } else { + MOZ_ASSERT(env->is()); + return nullptr; + } + } + return &env->as(); +} + +CallObject* CallObject::find(JSObject* env) { + return FindEnclosingEnv(env); +} + +ModuleEnvironmentObject* ModuleEnvironmentObject::find(JSObject* env) { + return FindEnclosingEnv(env); +} + +CallObject* CallObject::createHollowForDebug(JSContext* cx, + HandleFunction callee) { + MOZ_ASSERT(!callee->needsCallObject()); + + RootedScript script(cx, callee->nonLazyScript()); + Rooted scope(cx, &script->bodyScope()->as()); + Rooted shape(cx, EmptyEnvironmentShape(cx)); + if (!shape) { + return nullptr; + } + Rooted callobj(cx, 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)); + + RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT)); + RootedId id(cx); + for (Rooted 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 shape, HandleObject enclosing, + gc::Heap heap) { + MOZ_ASSERT(shape->getObjectClass() == &class_); + + auto* env = CreateEnvironmentObject(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, + HandleObject enclosing, + gc::Heap heap) { + MOZ_ASSERT(scope->is() || scope->is()); + + Rooted 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, AbstractFramePtr frame) { +#ifdef DEBUG + if (frame.isEvalFrame()) { + MOZ_ASSERT(scope->is() && 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().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) { + MOZ_ASSERT(scope->is() || scope->kind() == ScopeKind::StrictEval); + MOZ_ASSERT(!scope->hasEnvironment()); + + Rooted shape(cx, + EmptyEnvironmentShape(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 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 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 scope) { + return create(cx, scope, nullptr, gc::Heap::Tenured); +} + +/* static */ +VarEnvironmentObject* VarEnvironmentObject::createWithoutEnclosing( + JSContext* cx, Handle 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_ = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + ModuleEnvironmentObject::newEnumerate, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + nullptr, // finalize + nullptr, // call + nullptr, // construct + nullptr, // trace +}; + +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 module) { + RootedScript script(cx, module->script()); + Rooted shape( + cx, script->bodyScope()->as().environmentShape()); + MOZ_ASSERT(shape->getObjectClass() == &class_); + + Rooted env( + cx, CreateEnvironmentObject(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 iter(env->shape()); !iter.done(); iter++) { + MOZ_ASSERT(!iter->configurable()); + } + MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible)); + MOZ_ASSERT(!env->inDictionaryMode()); +#endif + + return env; +} + +ModuleObject& ModuleEnvironmentObject::module() const { + return getReservedSlot(MODULE_SLOT).toObject().as(); +} + +IndirectBindingMap& ModuleEnvironmentObject::importBindings() const { + return module().importBindings(); +} + +bool ModuleEnvironmentObject::createImportBinding(JSContext* cx, + Handle importName, + Handle module, + Handle localName) { + RootedId importNameId(cx, AtomToId(importName)); + RootedId localNameId(cx, AtomToId(localName)); + Rooted env(cx, &module->initialEnvironment()); + if (!importBindings().put(cx, importNameId, env, localNameId)) { + return false; + } + + return true; +} + +bool ModuleEnvironmentObject::hasImportBinding(Handle name) { + return importBindings().has(NameToId(name)); +} + +bool ModuleEnvironmentObject::lookupImport( + jsid name, ModuleEnvironmentObject** envOut, + mozilla::Maybe* 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().importBindings(); + mozilla::Maybe propInfo; + ModuleEnvironmentObject* env; + if (bindings.lookup(id, &env, &propInfo)) { + objp.set(env); + propp->setNativeProperty(*propInfo); + return true; + } + + Rooted target(cx, &obj->as()); + if (!NativeLookupOwnProperty(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().importBindings().has(id)) { + *foundp = true; + return true; + } + + Rooted self(cx, &obj->as()); + 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().importBindings(); + mozilla::Maybe prop; + ModuleEnvironmentObject* env; + if (bindings.lookup(id, &env, &prop)) { + vp.set(env->getSlot(prop->slot())); + return true; + } + + Rooted self(cx, &obj->as()); + 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 self(cx, + &obj->as()); + if (self->importBindings().has(id)) { + return result.failReadOnly(); + } + + return NativeSetProperty(cx, self, id, v, receiver, result); +} + +/* static */ +bool ModuleEnvironmentObject::getOwnPropertyDescriptor( + JSContext* cx, HandleObject obj, HandleId id, + MutableHandle> desc) { + const IndirectBindingMap& bindings = + obj->as().importBindings(); + mozilla::Maybe 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 self(cx, &obj->as()); + 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 self(cx, + &obj->as()); + 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 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 scope) { + Rooted shape( + cx, EmptyEnvironmentShape(cx)); + if (!shape) { + return nullptr; + } + + auto* env = CreateEnvironmentObject(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 scope) { + Rooted shape(cx, + EmptyEnvironmentShape(cx)); + if (!shape) { + return nullptr; + } + + auto* callobj = CreateEnvironmentObject(cx, shape); + if (!callobj) { + return nullptr; + } + + callobj->initEnclosingEnvironment(enclosing); + callobj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope)); + + return callobj; +} + +/*****************************************************************************/ + +WithEnvironmentObject* WithEnvironmentObject::create(JSContext* cx, + HandleObject object, + HandleObject enclosing, + Handle scope) { + Rooted shape(cx, + EmptyEnvironmentShape(cx)); + if (!shape) { + return nullptr; + } + + auto* obj = CreateEnvironmentObject(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) { + obj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope)); + } else { + obj->initReservedSlot(SCOPE_SLOT, NullValue()); + } + + return obj; +} + +WithEnvironmentObject* WithEnvironmentObject::createNonSyntactic( + JSContext* cx, HandleObject object, HandleObject enclosing) { + return create(cx, object, enclosing, nullptr); +} + +static inline bool IsUnscopableDotName(JSContext* cx, HandleId id) { + return id.isAtom(cx->names().dotThis) || id.isAtom(cx->names().dotNewTarget); +} + +#ifdef DEBUG +static bool IsInternalDotName(JSContext* cx, HandleId id) { + return id.isAtom(cx->names().dotThis) || + id.isAtom(cx->names().dotGenerator) || + id.isAtom(cx->names().dotInitializers) || + id.isAtom(cx->names().dotFieldKeys) || + id.isAtom(cx->names().dotStaticInitializers) || + id.isAtom(cx->names().dotStaticFieldKeys) || + id.isAtom(cx->names().dotArgs) || + id.isAtom(cx->names().dotNewTarget) || + id.isAtom(cx->names().starNamespaceStar); +} +#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().object()); + if (!LookupProperty(cx, actual, id, objp, propp)) { + return false; + } + + if (propp->isFound()) { + bool scopable; + if (!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 desc, + ObjectOpResult& result) { + MOZ_ASSERT(!IsInternalDotName(cx, id)); + RootedObject actual(cx, &obj->as().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().object()); + + // ES 8.1.1.2.1 step 3-5. + if (!HasProperty(cx, actual, id, foundp)) { + return false; + } + if (!*foundp) { + return true; + } + + // Steps 7-10. (Step 6 is a no-op.) + return CheckUnscopables(cx, actual, id, foundp); +} + +static bool with_GetProperty(JSContext* cx, HandleObject obj, + HandleValue receiver, HandleId id, + MutableHandleValue vp) { + MOZ_ASSERT(!IsInternalDotName(cx, id)); + RootedObject actual(cx, &obj->as().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().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> desc) { + MOZ_ASSERT(!IsInternalDotName(cx, id)); + RootedObject actual(cx, &obj->as().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().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 shape( + cx, EmptyEnvironmentShape(cx)); + if (!shape) { + return nullptr; + } + + Rooted obj( + cx, CreateEnvironmentObject(cx, shape, + TenuredObject)); + if (!obj) { + return nullptr; + } + + MOZ_ASSERT(obj->isUnqualifiedVarObj()); + if (!JSObject::setQualifiedVarObj(cx, obj)) { + return nullptr; + } + + obj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment()); + return obj; +} + +const JSClass NonSyntacticVariablesObject::class_ = { + "NonSyntacticVariablesObject", + JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS)}; + +bool js::CreateNonSyntacticEnvironmentChain(JSContext* cx, + HandleObjectVector envChain, + MutableHandleObject env) { + // Callers are responsible for segregating the NonSyntactic case from simple + // compilation cases. + MOZ_RELEASE_ASSERT(!envChain.empty()); + + RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment()); + if (!CreateObjectsForEnvironmentChain(cx, envChain, globalLexical, env)) { + return false; + } + + // 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 false; + } + + // 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. + env.set( + ObjectRealm::get(env).getOrCreateNonSyntacticLexicalEnvironment(cx, env)); + return !!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 shape, HandleObject enclosing, + gc::Heap heap) { + 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(cx, shape, heap); + if (!env) { + return nullptr; + } + + MOZ_ASSERT(!env->inDictionaryMode()); + + if (enclosing) { + env->initEnclosingEnvironment(enclosing); + } + + return env; +} + +bool LexicalEnvironmentObject::isExtensible() const { + return NativeObject::isExtensible(); +} + +/* static */ +BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::create( + JSContext* cx, Handle scope, HandleObject enclosing, + gc::Heap heap) { + cx->check(enclosing); + MOZ_ASSERT(scope->hasEnvironment()); + + Rooted shape(cx, scope->environmentShape()); + auto* env = static_cast( + LexicalEnvironmentObject::create(cx, shape, enclosing, heap)); + 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 scope, AbstractFramePtr frame) { + RootedObject enclosing(cx, frame.environmentChain()); + return create(cx, scope, enclosing, gc::Heap::Default); +} + +/* static */ +BlockLexicalEnvironmentObject* +BlockLexicalEnvironmentObject::createHollowForDebug( + JSContext* cx, Handle scope) { + MOZ_ASSERT(!scope->hasEnvironment()); + + Rooted shape( + cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx)); + if (!shape) { + return nullptr; + } + + // This environment's enclosing link is never used: the + // DebugEnvironmentProxy that refers to this scope carries its own + // enclosing link, which is what Debugger uses to construct the tree of + // Debugger.Environment objects. + RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment()); + Rooted env( + cx, LexicalEnvironmentObject::create(cx, shape, enclosingEnv, + gc::Heap::Tenured)); + if (!env) { + return nullptr; + } + + RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT)); + RootedId id(cx); + for (Rooted 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().initScope(scope); + return &env->as(); +} + +/* static */ +BlockLexicalEnvironmentObject* +BlockLexicalEnvironmentObject::createTemplateObject( + JSContext* cx, Handle scope) { + return create(cx, scope, nullptr, gc::Heap::Tenured); +} + +/* static */ +BlockLexicalEnvironmentObject* +BlockLexicalEnvironmentObject::createWithoutEnclosing( + JSContext* cx, Handle scope) { + return create(cx, scope, nullptr, gc::Heap::Default); +} + +/* static */ +BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::clone( + JSContext* cx, Handle env) { + Rooted scope(cx, &env->scope()); + RootedObject enclosing(cx, &env->enclosingEnvironment()); + Rooted copy( + cx, 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 env) { + Rooted 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) { + MOZ_ASSERT(callee->isNamedLambda()); + Rooted scope(cx, callee->nonLazyScript()->maybeNamedLambdaScope()); + MOZ_ASSERT(scope && scope->environmentShape()); + +#ifdef DEBUG + { + // Named lambda objects have one (non-writable) property. + SharedShapePropertyIter 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(), enclosing, heap); + if (!obj) { + return nullptr; + } + + obj->initFixedSlot(lambdaSlot(), ObjectValue(*callee)); + return static_cast(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) { + return create(cx, callee, nullptr, gc::Heap::Default); +} + +/* static */ +NamedLambdaObject* NamedLambdaObject::create(JSContext* cx, + AbstractFramePtr frame) { + RootedFunction fun(cx, frame.callee()); + RootedObject enclosing(cx, frame.environmentChain()); + return create(cx, fun, enclosing, gc::Heap::Default); +} + +/* static */ +size_t NamedLambdaObject::lambdaSlot() { + // Named lambda environments have exactly one name. + return JSSLOT_FREE(&LexicalEnvironmentObject::class_); +} + +/* static */ +ClassBodyLexicalEnvironmentObject* ClassBodyLexicalEnvironmentObject::create( + JSContext* cx, Handle scope, HandleObject enclosing, + gc::Heap heap) { + cx->check(enclosing); + MOZ_ASSERT(scope->hasEnvironment()); + + Rooted shape(cx, scope->environmentShape()); + auto* env = static_cast( + LexicalEnvironmentObject::create(cx, shape, enclosing, heap)); + if (!env) { + return nullptr; + } + + env->initScope(scope); + return env; +} + +/* static */ +ClassBodyLexicalEnvironmentObject* +ClassBodyLexicalEnvironmentObject::createForFrame(JSContext* cx, + Handle scope, + AbstractFramePtr frame) { + RootedObject enclosing(cx, frame.environmentChain()); + return create(cx, scope, enclosing, gc::Heap::Default); +} + +/* static */ +ClassBodyLexicalEnvironmentObject* +ClassBodyLexicalEnvironmentObject::createTemplateObject( + JSContext* cx, Handle scope) { + return create(cx, scope, nullptr, gc::Heap::Tenured); +} + +/* static */ +ClassBodyLexicalEnvironmentObject* +ClassBodyLexicalEnvironmentObject::createWithoutEnclosing( + JSContext* cx, Handle 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. setWindowProxyThisValue 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()) { + lexical = &obj->as().lexicalEnvironment(); + } else { + lexical = ObjectRealm::get(obj).getNonSyntacticLexicalEnvironment(obj); + } + MOZ_ASSERT(lexical); + return lexical; +} + +/* static */ +GlobalLexicalEnvironmentObject* GlobalLexicalEnvironmentObject::create( + JSContext* cx, Handle global) { + MOZ_ASSERT(global); + + Rooted shape( + cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx)); + if (!shape) { + return nullptr; + } + + auto* env = static_cast( + LexicalEnvironmentObject::create(cx, shape, global, gc::Heap::Tenured)); + if (!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 shape( + cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx)); + if (!shape) { + return nullptr; + } + + auto* env = static_cast( + 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 shape( + cx, + EmptyEnvironmentShape(cx, &class_, JSSLOT_FREE(&class_), ObjectFlags())); + if (!shape) { + return nullptr; + } + + auto* obj = CreateEnvironmentObject(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 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().errorNumber(), id); + return false; +} + +static bool lexicalError_HasProperty(JSContext* cx, HandleObject obj, + HandleId id, bool* foundp) { + ReportRuntimeLexicalErrorId( + cx, obj->as().errorNumber(), id); + return false; +} + +static bool lexicalError_GetProperty(JSContext* cx, HandleObject obj, + HandleValue receiver, HandleId id, + MutableHandleValue vp) { + ReportRuntimeLexicalErrorId( + cx, obj->as().errorNumber(), id); + return false; +} + +static bool lexicalError_SetProperty(JSContext* cx, HandleObject obj, + HandleId id, HandleValue v, + HandleValue receiver, + ObjectOpResult& result) { + ReportRuntimeLexicalErrorId( + cx, obj->as().errorNumber(), id); + return false; +} + +static bool lexicalError_GetOwnPropertyDescriptor( + JSContext* cx, HandleObject obj, HandleId id, + MutableHandle> desc) { + ReportRuntimeLexicalErrorId( + cx, obj->as().errorNumber(), id); + return false; +} + +static bool lexicalError_DeleteProperty(JSContext* cx, HandleObject obj, + HandleId id, ObjectOpResult& result) { + ReportRuntimeLexicalErrorId( + cx, obj->as().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()) { + // 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()) { + 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() && + &env_->as().scope() == si_.scope()) { + MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda || + si_.kind() == ScopeKind::StrictNamedLambda); + env_ = + &env_->as().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()))) { + frame_ = NullFramePtr(); + } + +#ifdef DEBUG + if (si_) { + if (hasSyntacticEnvironment()) { + Scope* scope = si_.scope(); + if (scope->is()) { + MOZ_ASSERT(scope == &env_->as().scope()); + } else if (scope->is()) { + MOZ_ASSERT(scope->as().script() == + env_->as() + .callee() + .maybeCanonicalFunction() + ->baseScript()); + } else if (scope->is()) { + MOZ_ASSERT(scope == &env_->as().scope()); + } else if (scope->is()) { + MOZ_ASSERT(scope == &env_->as().scope()); + } else if (scope->is()) { + MOZ_ASSERT(scope == &env_->as().scope()); + } else if (scope->is()) { + MOZ_ASSERT(env_->is() || + IsGlobalLexicalEnvironment(env_)); + } + } else if (hasNonSyntacticEnvironmentObject()) { + if (env_->is()) { + // The global lexical environment still encloses non-syntactic + // environment objects. + MOZ_ASSERT(env_->is() || + env_->is()); + } else if (env_->is()) { + MOZ_ASSERT(!env_->as().isSyntactic()); + } else { + MOZ_ASSERT(env_->is()); + } + } + } +#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()); + 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(), + !env_->as().isSyntactic()); + return env_->is(); + } + return false; +} + +/* static */ +HashNumber MissingEnvironmentKey::hash(MissingEnvironmentKey ek) { + return size_t(ek.frame_.raw()) ^ size_t(ek.scope_); +} + +/* static */ +bool MissingEnvironmentKey::match(MissingEnvironmentKey ek1, + MissingEnvironmentKey ek2) { + return ek1.frame_ == ek2.frame_ && ek1.scope_ == ek2.scope_; +} + +bool LiveEnvironmentVal::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 BaseProxyHandler { + enum Action { SET, GET }; + + enum AccessResult { ACCESS_UNALIASED, ACCESS_GENERIC, ACCESS_LOST }; + + /* + * This function handles access to unaliased locals/formals. Since they + * are unaliased, the values of these variables are not stored in the + * slots of the normal CallObject and 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 debugEnv, + Handle 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() || env->is()) { + RootedScript script(cx); + if (env->is()) { + CallObject& callobj = env->as(); + RootedFunction fun(cx, &callobj.callee()); + script = JSFunction::getOrCreateScript(cx, fun); + } else { + script = env->as().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() || + env->is()) { + // Currently consider all global and non-syntactic top-level lexical + // bindings to be aliased. + if (env->is() && + env->as().isExtensible()) { + MOZ_ASSERT(IsGlobalLexicalEnvironment(env) || + !IsSyntacticEnvironment(env)); + return true; + } + + // Currently all vars inside non-strict eval var environments are aliased. + if (env->is() && + env->as().isForNonStrictEval()) { + return true; + } + + Rooted 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()); + if (action == GET) { + vp.set(frame.unaliasedLocal(local)); + } else { + frame.unaliasedLocal(local) = 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()) { + if (maybeLiveEnv) { + Rooted 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()) { + Rooted scope(cx, getEnvironmentScope(*env)); + MOZ_ASSERT(scope->is()); + uint32_t index = 0; + for (BindingIter bi(scope); bi; bi++) { + if (id.isAtom(bi.name())) { + break; + } + MOZ_ASSERT(!bi.isLast()); + index++; + } + Rooted instanceScope(cx, + &scope->as()); + wasm::Instance& instance = instanceScope->instance()->instance(); + + if (action == GET) { + if (instanceScope->memoriesStart() <= index && + index < instanceScope->globalsStart()) { + MOZ_ASSERT(instanceScope->memoriesStart() + 1 == + instanceScope->globalsStart()); + vp.set(ObjectValue(*instance.memory())); + } + 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()); + return true; + } + + static bool isArguments(JSContext* cx, jsid id) { + return id == NameToId(cx->names().arguments); + } + static bool isThis(JSContext* cx, jsid id) { + return id == NameToId(cx->names().dotThis); + } + + static bool isFunctionEnvironment(const JSObject& env) { + return env.is(); + } + + static bool isNonExtensibleLexicalEnvironment(const JSObject& env) { + return env.is(); + } + + static Scope* getEnvironmentScope(const JSObject& env) { + if (isFunctionEnvironment(env)) { + return env.as().callee().nonLazyScript()->bodyScope(); + } + if (env.is()) { + JSScript* script = + env.as().module().maybeScript(); + return script ? script->bodyScope() : nullptr; + } + if (isNonExtensibleLexicalEnvironment(env)) { + return &env.as().scope(); + } + if (env.is()) { + return &env.as().scope(); + } + if (env.is()) { + return &env.as().scope(); + } + if (env.is()) { + return &env.as().scope(); + } + 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().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() + .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, + MutableHandleArgumentsObject argsObj) { + argsObj.set(nullptr); + + LiveEnvironmentVal* maybeEnv = DebugEnvironments::hasLiveEnvironment(env); + if (!maybeEnv) { + return true; + } + + argsObj.set(ArgumentsObject::createUnexpected(cx, maybeEnv->frame())); + return !!argsObj; + } + + /* + * Create a missing this Value. If the function returns true but + * *success is false, it means the scope is dead. + */ + static bool createMissingThis(JSContext* cx, EnvironmentObject& env, + MutableHandleValue thisv, bool* success) { + *success = false; + + LiveEnvironmentVal* maybeEnv = DebugEnvironments::hasLiveEnvironment(env); + if (!maybeEnv) { + return true; + } + + if (!GetFunctionThis(cx, maybeEnv->frame(), thisv)) { + return false; + } + + // Update the this-argument to avoid boxing primitive |this| more + // than once. + maybeEnv->frame().thisArgument() = thisv; + *success = true; + return true; + } + + 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() : BaseProxyHandler(&family) {} + + static bool isFunctionEnvironmentWithThis(const JSObject& env) { + // All functions except arrows should have their own this binding. + return isFunctionEnvironment(env) && + !env.as().callee().hasLexicalThis(); + } + + bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, + bool* isOrdinary, + MutableHandleObject protop) const override { + MOZ_CRASH( + "shouldn't be possible to access the prototype chain of a " + "DebugEnvironmentProxyHandler"); + } + + bool preventExtensions(JSContext* cx, HandleObject proxy, + ObjectOpResult& result) const override { + // always [[Extensible]], can't be made non-[[Extensible]], like most + // proxies + return result.fail(JSMSG_CANT_CHANGE_EXTENSIBILITY); + } + + bool isExtensible(JSContext* cx, HandleObject proxy, + bool* extensible) const override { + // See above. + *extensible = true; + return true; + } + + bool getMissingArgumentsPropertyDescriptor( + JSContext* cx, Handle debugEnv, + EnvironmentObject& env, + MutableHandle> desc) const { + RootedArgumentsObject 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; + } + + desc.set(mozilla::Some(PropertyDescriptor::Data( + ObjectValue(*argsObj), {JS::PropertyAttribute::Enumerable}))); + return true; + } + bool getMissingThisPropertyDescriptor( + JSContext* cx, Handle debugEnv, + EnvironmentObject& env, + MutableHandle> 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> desc) const override { + Rooted debugEnv( + cx, &proxy->as()); + Rooted 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 { + RootedArgumentsObject 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; + } + + 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 debugEnv( + cx, &proxy->as()); + Rooted env( + cx, &proxy->as().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 { + RootedArgumentsObject argsObj(cx); + if (!createMissingArguments(cx, env, &argsObj)) { + return false; + } + vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_MISSING_ARGUMENTS)); + return true; + } + + bool getMissingThisMaybeSentinelValue(JSContext* cx, EnvironmentObject& env, + MutableHandleValue vp) const { + RootedValue thisv(cx); + bool success; + if (!createMissingThis(cx, env, &thisv, &success)) { + return false; + } + vp.set(success ? thisv : MagicValue(JS_OPTIMIZED_OUT)); + return true; + } + + /* + * Like 'get', but returns sentinel values instead of throwing on + * exceptional cases. + */ + bool getMaybeSentinelValue(JSContext* cx, + Handle debugEnv, + HandleId id, MutableHandleValue vp) const { + Rooted 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 debugEnv( + cx, &proxy->as()); + Rooted env( + cx, &proxy->as().environment()); + + if (debugEnv->isOptimizedOut()) { + return Throw(cx, id, JSMSG_DEBUG_CANT_SET_OPT_ENV); + } + + AccessResult access; + RootedValue valCopy(cx, v); + if (!handleUnaliasedAccess(cx, debugEnv, env, id, SET, &valCopy, &access)) { + return false; + } + + switch (access) { + case ACCESS_UNALIASED: + return result.succeed(); + case ACCESS_GENERIC: { + RootedValue envVal(cx, ObjectValue(*env)); + return SetProperty(cx, env, id, v, envVal, result); + } + default: + MOZ_CRASH("bad AccessResult"); + } + } + + bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id, + Handle desc, + ObjectOpResult& result) const override { + Rooted env( + cx, &proxy->as().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 env( + cx, &proxy->as().environment()); + + if (isMissingArgumentsBinding(*env)) { + if (!props.append(NameToId(cx->names().arguments))) { + return false; + } + } + if (isMissingThisBinding(*env)) { + if (!props.append(NameToId(cx->names().dotThis))) { + return false; + } + } + + // WithEnvironmentObject isn't a very good proxy. It doesn't have a + // JSNewEnumerateOp implementation, because if it just delegated to the + // target object, the object would indicate that native enumeration is + // the thing to do, but native enumeration over the WithEnvironmentObject + // wrapper yields no properties. So instead here we hack around the + // issue: punch a hole through to the with object target, then manually + // examine @@unscopables. + RootedObject target(cx); + bool isWith = env->is(); + if (isWith) { + target = &env->as().object(); + } else { + target = env; + } + if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, props)) { + return false; + } + + if (isWith) { + size_t j = 0; + for (size_t i = 0; i < props.length(); i++) { + bool inScope; + if (!CheckUnscopables(cx, env, props[i], &inScope)) { + return false; + } + if (inScope) { + props[j++].set(props[i]); + } + } + if (!props.resize(j)) { + return false; + } + } + + /* + * Environments with Scopes are optimized to not contain unaliased + * variables so they must be manually appended here. + */ + if (Scope* scope = getEnvironmentScope(*env)) { + for (Rooted 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().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() 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()); + + RootedValue priv(cx, ObjectValue(env)); + JSObject* obj = NewProxyObject(cx, &DebugEnvironmentProxyHandler::singleton, + priv, nullptr /* proto */); + if (!obj) { + return nullptr; + } + + DebugEnvironmentProxy* debugEnv = &obj->as(); + debugEnv->setReservedSlot(ENCLOSING_SLOT, ObjectValue(*enclosing)); + debugEnv->setReservedSlot(SNAPSHOT_SLOT, NullValue()); + + return debugEnv; +} + +EnvironmentObject& DebugEnvironmentProxy::environment() const { + return target()->as(); +} + +JSObject& DebugEnvironmentProxy::enclosingEnvironment() const { + return reservedSlot(ENCLOSING_SLOT).toObject(); +} + +ArrayObject* DebugEnvironmentProxy::maybeSnapshot() const { + JSObject* obj = reservedSlot(SNAPSHOT_SLOT).toObjectOrNull(); + return obj ? &obj->as() : 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() || e.is() || + e.is() || + e.is() || + e.is() || e.is(); +} + +/* static */ +bool DebugEnvironmentProxy::getMaybeSentinelValue( + JSContext* cx, Handle 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()) { + return e.is() && + !e.as().scope().hasEnvironment(); + } + + if (e.is()) { + return !e.as().callee().needsCallObject() && !maybeSnapshot(); + } + + return false; +} + +/*****************************************************************************/ + +DebugEnvironments::DebugEnvironments(JSContext* cx, Zone* zone) + : zone_(zone), + proxiedEnvs(cx), + 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 (MissingEnvironmentMap::Enum e(missingEnvs); !e.empty(); e.popFront()) { + auto result = + TraceWeakEdge(trc, &e.front().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()); + e.removeFront(); + } else { + MissingEnvironmentKey key = e.front().key(); + Scope* scope = key.scope(); + MOZ_ALWAYS_TRUE(TraceManuallyBarrieredWeakEdge( + trc, &scope, "MissingEnvironmentKey scope")); + if (scope != key.scope()) { + key.updateScope(scope); + e.rekeyFront(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.checkAfterMovingGC(); + for (MissingEnvironmentMap::Range r = missingEnvs.all(); !r.empty(); + r.popFront()) { + CheckGCThingAfterMovingGC(r.front().key().scope()); + // Use unbarrieredGet() to prevent triggering read barrier while collecting. + CheckGCThingAfterMovingGC(r.front().value().unbarrieredGet()); + } + for (LiveEnvironmentMap::Range r = liveEnvs.all(); !r.empty(); r.popFront()) { + CheckGCThingAfterMovingGC(r.front().key()); + CheckGCThingAfterMovingGC(r.front().value().scope_.get()); + } +} +#endif + +/* + * Unfortunately, GetDebugEnvironmentForFrame needs to work even outside debug + * mode (in particular, JS_GetFrameScopeChain does not require debug mode). + * Since DebugEnvironments::onPop* are only called in debuggee frames, this + * means we cannot use any of the maps in DebugEnvironments. This will produce + * debug scope chains that do not obey the debugger invariants but that is just + * fine. + */ +static bool CanUseDebugEnvironmentMaps(JSContext* cx) { + return cx->realm()->isDebuggee(); +} + +DebugEnvironments* DebugEnvironments::ensureRealmData(JSContext* cx) { + Realm* realm = cx->realm(); + if (auto* debugEnvs = realm->debugEnvs()) { + return debugEnvs; + } + + auto debugEnvs = cx->make_unique(cx, cx->zone()); + 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.lookup(&env)) { + MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx)); + return &obj->as(); + } + + return nullptr; +} + +/* static */ +bool DebugEnvironments::addDebugEnvironment( + JSContext* cx, Handle env, + Handle 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; + } + + return envs->proxiedEnvs.add(cx, env, debugEnv); +} + +/* static */ +DebugEnvironmentProxy* DebugEnvironments::hasDebugEnvironment( + JSContext* cx, const EnvironmentIter& ei) { + MOZ_ASSERT(!ei.hasSyntacticEnvironment()); + + DebugEnvironments* envs = cx->realm()->debugEnvs(); + if (!envs) { + return nullptr; + } + + if (MissingEnvironmentMap::Ptr p = + envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) { + MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx)); + return p->value(); + } + return nullptr; +} + +/* static */ +bool DebugEnvironments::addDebugEnvironment( + JSContext* cx, const EnvironmentIter& ei, + Handle 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(ei); + MOZ_ASSERT(!envs->missingEnvs.has(key)); + if (!envs->missingEnvs.put(key, + WeakHeapPtr(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 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> vec(cx, GCVector(cx)); + if (debugEnv->environment().is()) { + FunctionScope* scope = &script->bodyScope()->as(); + 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()) { + LexicalScope* scope = + &debugEnv->environment().as().scope(); + frameSlotStart = scope->firstFrameSlot(); + frameSlotEnd = scope->nextFrameSlot(); + } else if (debugEnv->environment() + .is()) { + ClassBodyScope* scope = &debugEnv->environment() + .as() + .scope(); + frameSlotStart = scope->firstFrameSlot(); + frameSlotEnd = scope->nextFrameSlot(); + } else if (debugEnv->environment().is()) { + VarEnvironmentObject* env = + &debugEnv->environment().as(); + if (frame.isFunctionFrame()) { + VarScope* scope = &env->scope().as(); + frameSlotStart = scope->firstFrameSlot(); + frameSlotEnd = scope->nextFrameSlot(); + } else { + EvalScope* scope = &env->scope().as(); + MOZ_ASSERT(scope == script->bodyScope()); + frameSlotStart = 0; + frameSlotEnd = scope->nextFrameSlot(); + } + } else { + MOZ_ASSERT(&debugEnv->environment().as() == + script->module()->environment()); + ModuleScope* scope = &script->bodyScope()->as(); + 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 snapshot( + cx, NewDenseCopiedArray(cx, vec.length(), vec.begin())); + if (!snapshot) { + MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed()); + cx->clearPendingException(); + return; + } + + debugEnv->initSnapshot(*snapshot); +} + +/* static */ +void DebugEnvironments::onPopCall(JSContext* cx, AbstractFramePtr frame) { + cx->check(frame); + + DebugEnvironments* envs = cx->realm()->debugEnvs(); + if (!envs) { + return; + } + + Rooted debugEnv(cx, nullptr); + + FunctionScope* funScope = &frame.script()->bodyScope()->as(); + 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()) { + return; + } + + CallObject& callobj = frame.environmentChain()->as(); + envs->liveEnvs.remove(&callobj); + if (JSObject* obj = envs->proxiedEnvs.lookup(&callobj)) { + debugEnv = &obj->as(); + } + } else { + MissingEnvironmentKey key(frame, funScope); + if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(key)) { + debugEnv = p->value(); + envs->liveEnvs.remove(&debugEnv->environment().as()); + 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 +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()); + + Rooted env(cx); + if (MissingEnvironmentMap::Ptr p = + envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) { + env = &p->value()->environment().as(); + envs->missingEnvs.remove(p); + } else if (ei.hasSyntacticEnvironment()) { + env = &ei.environment().as(); + } + + if (env) { + envs->liveEnvs.remove(env); + + if (JSObject* obj = envs->proxiedEnvs.lookup(env)) { + Rooted debugEnv( + cx, &obj->as()); + DebugEnvironments::takeFrameSnapshot(cx, debugEnv, ei.initialFrame()); + } + } +} + +void DebugEnvironments::onPopLexical(JSContext* cx, const EnvironmentIter& ei) { + if (ei.scope().is()) { + onPopGeneric(cx, ei); + } else { + onPopGeneric(cx, ei); + } +} + +void DebugEnvironments::onPopVar(JSContext* cx, const EnvironmentIter& ei) { + if (ei.scope().is()) { + onPopGeneric(cx, ei); + } else { + onPopGeneric(cx, ei); + } +} + +void DebugEnvironments::onPopWith(AbstractFramePtr frame) { + Realm* realm = frame.realm(); + if (DebugEnvironments* envs = realm->debugEnvs()) { + envs->liveEnvs.remove( + &frame.environmentChain()->as()); + } +} + +void DebugEnvironments::onPopModule(JSContext* cx, const EnvironmentIter& ei) { + onPopGeneric(cx, ei); +} + +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(). + */ + 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; + } + + RootedObject env(cx); + Rooted scope(cx); + 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()) { + 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 (MissingEnvironmentMap::Enum e(envs->missingEnvs); !e.empty(); + e.popFront()) { + MissingEnvironmentKey key = e.front().key(); + if (key.frame() == from) { + key.updateFrame(to); + e.rekeyFront(key); + } + } + + for (LiveEnvironmentMap::Enum e(envs->liveEnvs); !e.empty(); e.popFront()) { + LiveEnvironmentVal& val = e.front().value(); + if (val.frame() == from) { + val.updateFrame(to); + } + } +} + +/* static */ +void DebugEnvironments::traceLiveFrame(JSTracer* trc, AbstractFramePtr frame) { + for (MissingEnvironmentMap::Enum e(missingEnvs); !e.empty(); e.popFront()) { + if (e.front().key().frame() == frame) { + TraceEdge(trc, &e.front().value(), "debug-env-live-frame-missing-env"); + } + } +} + +/*****************************************************************************/ + +static JSObject* GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei); + +static DebugEnvironmentProxy* GetDebugEnvironmentForEnvironmentObject( + JSContext* cx, const EnvironmentIter& ei) { + Rooted 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 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() || ei.scope().is() || + ei.scope().is() || + ei.scope().is() || ei.scope().is() || + ei.scope().kind() == ScopeKind::StrictEval)); + + if (DebugEnvironmentProxy* debugEnv = + DebugEnvironments::hasDebugEnvironment(cx, ei)) { + return debugEnv; + } + + EnvironmentIter copy(cx, ei); + RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy)); + if (!enclosingDebug) { + return nullptr; + } + + /* + * Create the missing environment object. For lexical environment objects, + * this takes care of storing variable values after the stack frame has + * been popped. For call objects, we only use the pretend call object to + * access callee, bindings and to receive dynamically added + * properties. Together, this provides the nice invariant that every + * DebugEnvironmentProxy has a EnvironmentObject. + * + * Note: to preserve envChain depth invariants, these lazily-reified + * envs must not be put on the frame's environment chain; instead, they are + * maintained via DebugEnvironments hooks. + */ + Rooted debugEnv(cx); + if (ei.scope().is()) { + RootedFunction callee(cx, + ei.scope().as().canonicalFunction()); + + JS::ExposeObjectToActiveJS(callee); + Rooted callobj(cx, + CallObject::createHollowForDebug(cx, callee)); + if (!callobj) { + return nullptr; + } + + debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug); + } else if (ei.scope().is()) { + Rooted lexicalScope(cx, &ei.scope().as()); + Rooted env( + cx, + BlockLexicalEnvironmentObject::createHollowForDebug(cx, lexicalScope)); + if (!env) { + return nullptr; + } + + debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug); + } else if (ei.scope().is()) { + Rooted wasmInstanceScope( + cx, &ei.scope().as()); + Rooted env( + cx, WasmInstanceEnvironmentObject::createHollowForDebug( + cx, wasmInstanceScope)); + if (!env) { + return nullptr; + } + + debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug); + } else if (ei.scope().is()) { + Rooted wasmFunctionScope( + cx, &ei.scope().as()); + RootedObject enclosing( + cx, &enclosingDebug->as().environment()); + Rooted callobj( + cx, WasmFunctionCallObject::createHollowForDebug(cx, enclosing, + wasmFunctionScope)); + if (!callobj) { + return nullptr; + } + + debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug); + } else { + Rooted scope(cx, &ei.scope()); + MOZ_ASSERT(scope->is() || scope->kind() == ScopeKind::StrictEval); + + Rooted 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()); + } +#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() || ei.scope().is() || + ei.scope().is() || + ei.scope().is() || ei.scope().is() || + 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); +} + +JSObject* js::GetDebugEnvironmentForSuspendedGenerator( + JSContext* cx, JSScript* script, AbstractGeneratorObject& genObj) { + RootedObject env(cx); + Rooted scope(cx); + GetSuspendedGeneratorEnvironmentAndScope(genObj, script, &env, &scope); + + 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(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); +} + +bool js::CreateObjectsForEnvironmentChain(JSContext* cx, + HandleObjectVector chain, + HandleObject terminatingEnv, + MutableHandleObject envObj) { +#ifdef DEBUG + for (size_t i = 0; i < chain.length(); ++i) { + cx->check(chain[i]); + MOZ_ASSERT(!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 withEnv(cx); + RootedObject enclosingEnv(cx, terminatingEnv); + for (size_t i = chain.length(); i > 0;) { + withEnv = + WithEnvironmentObject::createNonSyntactic(cx, chain[--i], enclosingEnv); + if (!withEnv) { + return false; + } + enclosingEnv = withEnv; + } + + envObj.set(enclosingEnv); + return true; +} + +JSObject& WithEnvironmentObject::object() const { + return getReservedSlot(OBJECT_SLOT).toObject(); +} + +JSObject* WithEnvironmentObject::withThis() const { + return &getReservedSlot(THIS_SLOT).toObject(); +} + +bool WithEnvironmentObject::isSyntactic() const { + Value v = getReservedSlot(SCOPE_SLOT); + MOZ_ASSERT(v.isPrivateGCThing() || v.isNull()); + return v.isPrivateGCThing(); +} + +WithScope& WithEnvironmentObject::scope() const { + MOZ_ASSERT(isSyntactic()); + return *static_cast(getReservedSlot(SCOPE_SLOT).toGCThing()); +} + +ModuleEnvironmentObject* js::GetModuleEnvironmentForScript(JSScript* script) { + ModuleObject* module = GetModuleObjectForScript(script); + if (!module) { + return nullptr; + } + + return module->environment(); +} + +ModuleObject* js::GetModuleObjectForScript(JSScript* script) { + for (ScopeIter si(script); si; si++) { + if (si.kind() == ScopeKind::Module) { + return si.scope()->as().module(); + } + } + return nullptr; +} + +static bool GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut( + JSContext* cx, const EnvironmentIter& originalIter, HandleObject scopeChain, + const jsbytecode* pc, MutableHandleValue res) { + for (EnvironmentIter ei(cx, originalIter); ei; ei++) { + if (ei.scope().kind() == ScopeKind::Module) { + res.setUndefined(); + return true; + } + + if (!ei.scope().is() || + ei.scope().as().canonicalFunction()->hasLexicalThis()) { + continue; + } + + RootedScript script(cx, ei.scope().as().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 bi(cx, BindingIter(script)); bi; bi++) { + if (bi.name() != cx->names().dotThis) { + continue; + } + + BindingLocation loc = bi.location(); + if (loc.kind() == BindingLocation::Kind::Environment) { + RootedObject callObj(cx, &ei.environment().as()); + 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()) { + RootedObject env(cx, &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, scopeChain, res); + return true; +} + +bool js::GetThisValueForDebuggerFrameMaybeOptimizedOut(JSContext* cx, + AbstractFramePtr frame, + const jsbytecode* pc, + MutableHandleValue res) { + RootedObject scopeChain(cx); + Rooted scope(cx); + if (!GetFrameEnvironmentAndScope(cx, frame, pc, &scopeChain, &scope)) { + return false; + } + + EnvironmentIter ei(cx, scopeChain, scope, frame); + return GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut( + cx, ei, scopeChain, pc, res); +} + +bool js::GetThisValueForDebuggerSuspendedGeneratorMaybeOptimizedOut( + JSContext* cx, AbstractGeneratorObject& genObj, JSScript* script, + MutableHandleValue res) { + RootedObject scopeChain(cx); + Rooted scope(cx); + GetSuspendedGeneratorEnvironmentAndScope(genObj, script, &scopeChain, &scope); + + EnvironmentIter ei(cx, scopeChain, scope); + return GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut( + cx, ei, scopeChain, nullptr, res); +} + +bool js::CheckLexicalNameConflict( + JSContext* cx, Handle lexicalEnv, + HandleObject varObj, Handle name) { + const char* redeclKind = nullptr; + RootedId id(cx, NameToId(name)); + mozilla::Maybe prop; + bool shadowsExistingProperty = false; + if (varObj->is() && + varObj->as().isInVarNames(name)) { + // ES 15.1.11 step 5.a + redeclKind = "var"; + } else if ((prop = lexicalEnv->lookup(cx, name))) { + // ES 15.1.11 step 5.b + redeclKind = prop->writable() ? "let" : "const"; + } else if (varObj->is() && + (prop = varObj->as().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 { + shadowsExistingProperty = true; + } + } else { + // ES 15.1.11 step 5.c-d + Rooted> desc(cx); + if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc)) { + return false; + } + if (desc.isSome()) { + if (!desc->configurable()) { + redeclKind = "non-configurable global property"; + } else { + shadowsExistingProperty = true; + } + } + } + + if (redeclKind) { + ReportRuntimeRedeclaration(cx, name, redeclKind); + return false; + } + if (shadowsExistingProperty && varObj->is()) { + // Shadowing a configurable global property with a new lexical is one + // of the rare ways to invalidate a GetGName stub. + varObj->as().bumpGenerationCount(); + } + + return true; +} + +[[nodiscard]] static bool CheckVarNameConflict( + JSContext* cx, Handle lexicalEnv, + Handle name) { + mozilla::Maybe 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 name, + const char* reason) { + if (UniqueChars printable = AtomToPrintableString(cx, name)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_CANT_DECLARE_GLOBAL_BINDING, + printable.get(), reason); + } +} + +bool js::CheckCanDeclareGlobalBinding(JSContext* cx, + Handle global, + Handle name, + bool isFunction) { + RootedId id(cx, NameToId(name)); + Rooted> 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 lexicalEnv, + HandleObject varObj) { + Rooted bi(cx, BindingIter(script)); + for (; bi; bi++) { + if (bi.isTopLevelFunction()) { + continue; + } + + Rooted name(cx, bi.name()->asPropertyName()); + unsigned attrs = script->isForEval() ? JSPROP_ENUMERATE + : JSPROP_ENUMERATE | JSPROP_PERMANENT; + + switch (bi.kind()) { + case BindingKind::Var: { + PropertyResult prop; + RootedObject obj2(cx); + if (!LookupProperty(cx, varObj, name, &obj2, &prop)) { + return false; + } + + if (prop.isNotFound() || + (obj2 != varObj && varObj->is())) { + if (!DefineDataProperty(cx, varObj, name, UndefinedHandleValue, + attrs)) { + return false; + } + } + + if (varObj->is()) { + if (!varObj->as().addToVarNames(cx, name)) { + return false; + } + } + + break; + } + + case BindingKind::Const: + attrs |= JSPROP_READONLY; + [[fallthrough]]; + + case BindingKind::Let: { + RootedId id(cx, NameToId(name)); + RootedValue uninitialized(cx, 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. + 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()) { + MOZ_ASSERT(i < 2); + continue; + } + + RootedFunction fun(cx, &thing.as().as()); + Rooted name(cx, fun->explicitName()->asPropertyName()); + + // Clone the function before exposing to script as a binding. + JSObject* clone = Lambda(cx, fun, envChain); + if (!clone) { + return false; + } + RootedValue rval(cx, ObjectValue(*clone)); + + PropertyResult prop; + RootedObject pobj(cx); + 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; + } + + if (varObj->is()) { + if (!varObj->as().addToVarNames(cx, name)) { + 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() || + varObj->is()); + if (varObj->is()) { + 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()); + } + + // Careful: the presence of a shape, even one appearing to derive from + // a variable declaration, doesn't mean it's in [[VarNames]]. + if (!varObj->as().addToVarNames(cx, name)) { + return false; + } + } + + /* + * 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). + */ + + RootedId id(cx, NameToId(name)); + if (!PutProperty(cx, varObj, id, rval, script->strict())) { + return false; + } + } + + return true; +} + +bool js::CheckGlobalDeclarationConflicts( + JSContext* cx, HandleScript script, + Handle 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 name(cx); + Rooted 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()) { + Handle global = varObj.as(); + 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 env(cx); + + if (obj->is()) { + env = &obj->as(); + } else if (obj->is() && + obj->as() + .environment() + .is()) { + env = &obj->as() + .environment() + .as(); + } else { + // Environment cannot contain lexical bindings. + return true; + } + + if (env->is() && + env->as().scope().kind() == + ScopeKind::SimpleCatch) { + // Annex B.3.5 allows redeclaring simple (non-destructured) catch parameters + // with var declarations. + return true; + } + + Rooted 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 scopeChain, + 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().hasBindings()); + + RootedObject obj(cx, scopeChain); + + // ES 18.2.1.3. + + // Step 5. + // + // Check that a direct eval will not hoist 'var' bindings over lexical + // bindings with the same name. + while (obj != varObj) { + if (!CheckVarNameConflictsInEnv(cx, script, obj)) { + return false; + } + obj = obj->enclosingEnvironment(); + } + + // 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()) { + JSFunction* fun = &varObj->as().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()) { + Handle global = varObj.as(); + Rooted name(cx); + for (Rooted 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 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()); + + // Named lambdas may have an environment that holds itself for recursion. + if (callee->needsNamedLambdaEnvironment()) { + NamedLambdaObject* declEnv = NamedLambdaObject::create(cx, frame); + if (!declEnv) { + return false; + } + frame.pushOnEnvironmentChain(*declEnv); + } + + // If the function has parameter default expressions, there may be an + // extra environment to hold the parameters. + if (callee->needsCallObject()) { + CallObject* callObj = CallObject::create(cx, frame); + if (!callObj) { + return false; + } + frame.pushOnEnvironmentChain(*callObj); + } + + return true; +} + +bool js::PushVarEnvironmentObject(JSContext* cx, Handle scope, + AbstractFramePtr frame) { + auto* env = VarEnvironmentObject::createForFrame(cx, scope, frame); + if (!env) { + return false; + } + frame.pushOnEnvironmentChain(*env); + return true; +} + +bool js::GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, + const jsbytecode* pc, + MutableHandleObject env, + MutableHandle scope) { + env.set(frame.environmentChain()); + + if (frame.isWasmDebugFrame()) { + Rooted 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; +} + +void js::GetSuspendedGeneratorEnvironmentAndScope( + AbstractGeneratorObject& genObj, JSScript* script, MutableHandleObject env, + MutableHandle scope) { + env.set(&genObj.environmentChain()); + + jsbytecode* pc = + script->offsetToPC(script->resumeOffsets()[genObj.resumeIndex()]); + scope.set(script->innermostScope(pc)); +} + +#ifdef DEBUG + +typedef HashSet PropertyNameSet; + +static bool RemoveReferencedNames(JSContext* cx, HandleScript script, + PropertyNameSet& remainingNames) { + // Remove from remainingNames --- the closure variables in some outer + // script --- any free variables in this script. This analysis isn't perfect: + // + // - It will not account for free variables in an inner script which are + // actually accessing some name in an intermediate script between the + // inner and outer scripts. This can cause remainingNames to be an + // underapproximation. + // + // - It will not account for new names introduced via eval. This can cause + // remainingNames to be an overapproximation. This would be easy to fix + // but is nice to have as the eval will probably not access these + // these names and putting eval in an inner script is bad news if you + // care about entraining variables unnecessarily. + + 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()) { + continue; + } + JSObject* obj = &gcThing.as(); + + if (!obj->is()) { + continue; + } + fun = &obj->as(); + + 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()->displayAtom()) { + buf.putString(name); + buf.printf(" "); + } + + buf.printf("(%s:%u) has variables entrained by ", script->filename(), + script->lineno()); + + if (JSAtom* name = innerScript->function()->displayAtom()) { + buf.putString(name); + buf.printf(" "); + } + + buf.printf("(%s:%u) ::", innerScript->filename(), innerScript->lineno()); + + for (PropertyNameSet::Range r = remainingNames.all(); !r.empty(); + r.popFront()) { + buf.printf(" "); + buf.putString(r.front()); + } + + printf("%s\n", buf.string()); + } + + RootedFunction fun(cx); + RootedScript innerInnerScript(cx); + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (!gcThing.is()) { + continue; + } + JSObject* obj = &gcThing.as(); + + if (!obj->is()) { + continue; + } + fun = &obj->as(); + + 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()) { + continue; + } + JSObject* obj = &gcThing.as(); + + if (!obj->is()) { + continue; + } + fun = &obj->as(); + + 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::MaybeOptimizeBindGlobalName(JSContext* cx, + Handle global, + Handle 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. + Rooted env(cx, + &global->lexicalEnvironment()); + mozilla::Maybe prop = env->lookup(cx, name); + if (prop.isSome()) { + if (prop->writable() && + !env->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) { + return env; + } + return nullptr; + } + + prop = global->lookup(cx, 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()) { + return "CallObject"; + } + if (is()) { + return "VarEnvironmentObject"; + } + if (is()) { + return "ModuleEnvironmentObject"; + } + if (is()) { + return "WasmInstanceEnvironmentObject"; + } + if (is()) { + return "WasmFunctionCallObject"; + } + if (is()) { + if (is()) { + if (is()) { + if (is()) { + return "NamedLambdaObject"; + } + return "BlockLexicalEnvironmentObject"; + } + if (is()) { + return "ClassBodyLexicalEnvironmentObject"; + } + return "ScopedLexicalEnvironmentObject"; + } + + if (is()) { + if (is()) { + return "GlobalLexicalEnvironmentObject"; + } + if (is()) { + return "NonSyntacticLexicalEnvironmentObject"; + } + return "ExtensibleLexicalEnvironmentObject"; + } + + return "LexicalEnvironmentObject"; + } + if (is()) { + return "NonSyntacticVariablesObject"; + } + if (is()) { + return "WithEnvironmentObject"; + } + if (is()) { + 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 envObj(cx, unrootedEnvObj); + while (envObj) { + Rooted env(cx); + if (envObj->is()) { + env = &envObj->as(); + } else if (envObj->is()) { + fprintf(stderr, "[DebugProxy] "); + env = &envObj->as().environment(); + } else { + MOZ_ASSERT(envObj->is()); + fprintf(stderr, "global\n"); + break; + } + + fprintf(stderr, "%s", env->typeString()); + + Rooted scope(cx); + if (env->is()) { + scope = &env->as().scope(); + } + if (env->is()) { + scope = &env->as().scope(); + } else if (env->is()) { + scope = &env->as().scope(); + } else if (env->is()) { + scope = &env->as().scope(); + } + + if (scope) { + fprintf(stderr, " {\n"); + for (Rooted bi(cx, BindingIter(scope)); bi; bi++) { + if (bi.location().kind() == BindingLocation::Kind::Environment) { + UniqueChars bytes = AtomToPrintableString(cx, bi.name()); + if (!bytes) { + fprintf(stderr, " *** out of memory\n"); + return; + } + + fprintf(stderr, " %u: %s %s\n", bi.location().slot(), + BindingKindString(bi.kind()), bytes.get()); + } + } + fprintf(stderr, "}"); + } + + fprintf(stderr, "\n"); + + if (envObj->is()) { + envObj = &envObj->as().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) */ -- cgit v1.2.3