Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "vm/EnvironmentObject-inl.h"
#include "mozilla/Maybe.h"
#include "builtin/Array.h"
#include "builtin/ModuleObject.h"
#include "js/EnvironmentChain.h" // JS::EnvironmentChain
#include "js/Exception.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy
#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_HasProperty, JS_HasPropertyById
#include "vm/ArgumentsObject.h"
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/GeneratorObject.h" // js::GetGeneratorObjectForEnvironment
#include "vm/GlobalObject.h"
#include "vm/JSObject.h"
#include "vm/ProxyObject.h"
#include "vm/Realm.h"
#include "vm/Scope.h"
#include "vm/Shape.h"
#include "wasm/WasmDebug.h"
#include "wasm/WasmDebugFrame.h"
#include "wasm/WasmInstance.h"
#include "gc/Marking-inl.h"
#include "gc/StableCellHasher-inl.h"
#include "vm/BytecodeIterator-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
/*****************************************************************************/
/*
* Return a shape representing the static scope containing the variable
* accessed by the ALIASEDVAR op at 'pc'.
*/
static SharedShape* EnvironmentCoordinateToEnvironmentShape(JSScript* script,
jsbytecode* pc) {
MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD);
ScopeIter si(script->innermostScope(pc));
uint32_t hops = EnvironmentCoordinate(pc).hops();
while (true) {
MOZ_ASSERT(!si.done());
if (si.hasSyntacticEnvironment()) {
if (!hops) {
break;
}
hops--;
}
si++;
}
return si.environmentShape();
}
PropertyName* js::EnvironmentCoordinateNameSlow(JSScript* script,
jsbytecode* pc) {
SharedShape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc);
EnvironmentCoordinate ec(pc);
SharedShapePropertyIter<NoGC> iter(shape);
while (iter->slot() != ec.slot()) {
iter++;
}
jsid id = iter->key();
/* Beware nameless destructuring formal. */
if (!id.isAtom()) {
return script->runtimeFromAnyThread()->commonNames->empty_;
}
return id.toAtom()->asPropertyName();
}
/*****************************************************************************/
template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, Handle<SharedShape*> shape,
gc::Heap heap) {
static_assert(std::is_base_of_v<EnvironmentObject, T>,
"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);
return NativeObject::create<T>(cx, allocKind, heap, shape);
}
// Helper function for simple environment objects that don't need the overloads
// above.
template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, Handle<SharedShape*> shape,
NewObjectKind newKind = GenericObject) {
gc::Heap heap = GetInitialHeap(newKind, &T::class_);
return CreateEnvironmentObject<T>(cx, shape, heap);
}
CallObject* CallObject::createWithShape(JSContext* cx,
Handle<SharedShape*> shape) {
return CreateEnvironmentObject<CallObject>(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<SharedShape*> shape(
cx, script->bodyScope()->as<FunctionScope>().environmentShape());
MOZ_ASSERT(shape->getObjectClass() == &class_);
// The JITs assume the result is nursery allocated unless we collected the
// nursery, so don't change |heap| here.
auto* callObj = CreateEnvironmentObject<CallObject>(cx, shape, heap);
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 <class EnvT>
EnvT* FindEnclosingEnv(JSObject* env) {
for (;;) {
if (env->is<EnvT>()) {
break;
} else if (env->is<EnvironmentObject>()) {
env = &env->as<EnvironmentObject>().enclosingEnvironment();
} else if (env->is<DebugEnvironmentProxy>()) {
EnvironmentObject& unwrapped =
env->as<DebugEnvironmentProxy>().environment();
if (unwrapped.is<EnvT>()) {
env = &unwrapped;
break;
}
env = &env->as<DebugEnvironmentProxy>().enclosingEnvironment();
} else {
MOZ_ASSERT(env->is<GlobalObject>());
return nullptr;
}
}
return &env->as<EnvT>();
}
CallObject* CallObject::find(JSObject* env) {
return FindEnclosingEnv<CallObject>(env);
}
ModuleEnvironmentObject* ModuleEnvironmentObject::find(JSObject* env) {
return FindEnclosingEnv<ModuleEnvironmentObject>(env);
}
CallObject* CallObject::createHollowForDebug(JSContext* cx,
HandleFunction callee) {
MOZ_ASSERT(!callee->needsCallObject());
RootedScript script(cx, callee->nonLazyScript());
Rooted<FunctionScope*> scope(cx, &script->bodyScope()->as<FunctionScope>());
Rooted<SharedShape*> shape(cx, EmptyEnvironmentShape<CallObject>(cx));
if (!shape) {
return nullptr;
}
Rooted<CallObject*> 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<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, callobj, id, optimizedOut)) {
return nullptr;
}
}
return callobj;
}
const JSClass CallObject::class_ = {
"Call",
JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS),
};
/*****************************************************************************/
/* static */
VarEnvironmentObject* VarEnvironmentObject::createInternal(
JSContext* cx, Handle<SharedShape*> shape, HandleObject enclosing,
gc::Heap heap) {
MOZ_ASSERT(shape->getObjectClass() == &class_);
auto* env = CreateEnvironmentObject<VarEnvironmentObject>(cx, shape, heap);
if (!env) {
return nullptr;
}
MOZ_ASSERT(!env->inDictionaryMode());
if (enclosing) {
env->initEnclosingEnvironment(enclosing);
}
return env;
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::create(JSContext* cx,
Handle<Scope*> scope,
HandleObject enclosing,
gc::Heap heap) {
MOZ_ASSERT(scope->is<EvalScope>() || scope->is<VarScope>());
Rooted<SharedShape*> shape(cx, scope->environmentShape());
auto* env = createInternal(cx, shape, enclosing, heap);
if (!env) {
return nullptr;
}
env->initScope(scope);
return env;
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createForFrame(
JSContext* cx, Handle<Scope*> scope, AbstractFramePtr frame) {
#ifdef DEBUG
if (frame.isEvalFrame()) {
MOZ_ASSERT(scope->is<EvalScope>() && scope == frame.script()->bodyScope());
MOZ_ASSERT_IF(frame.isInterpreterFrame(),
cx->interpreterFrame() == frame.asInterpreterFrame());
MOZ_ASSERT_IF(frame.isInterpreterFrame(),
cx->interpreterRegs().pc == frame.script()->code());
} else {
MOZ_ASSERT(frame.environmentChain());
MOZ_ASSERT_IF(
frame.callee()->needsCallObject(),
&frame.environmentChain()->as<CallObject>().callee() == frame.callee());
}
#endif
RootedObject envChain(cx, frame.environmentChain());
return create(cx, scope, envChain, gc::Heap::Default);
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createHollowForDebug(
JSContext* cx, Handle<Scope*> scope) {
MOZ_ASSERT(scope->is<VarScope>() || scope->kind() == ScopeKind::StrictEval);
MOZ_ASSERT(!scope->hasEnvironment());
Rooted<SharedShape*> shape(cx,
EmptyEnvironmentShape<VarEnvironmentObject>(cx));
if (!shape) {
return nullptr;
}
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment());
Rooted<VarEnvironmentObject*> env(
cx, createInternal(cx, shape, enclosingEnv, gc::Heap::Default));
if (!env) {
return nullptr;
}
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, env, id, optimizedOut)) {
return nullptr;
}
}
env->initScope(scope);
return env;
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createTemplateObject(
JSContext* cx, Handle<VarScope*> scope) {
return create(cx, scope, nullptr, gc::Heap::Tenured);
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createWithoutEnclosing(
JSContext* cx, Handle<VarScope*> scope) {
return create(cx, scope, nullptr, gc::Heap::Default);
}
const JSClass VarEnvironmentObject::class_ = {
"Var",
JSCLASS_HAS_RESERVED_SLOTS(VarEnvironmentObject::RESERVED_SLOTS),
};
/*****************************************************************************/
const ObjectOps ModuleEnvironmentObject::objectOps_ = {
ModuleEnvironmentObject::lookupProperty, // lookupProperty
nullptr, // defineProperty
ModuleEnvironmentObject::hasProperty, // hasProperty
ModuleEnvironmentObject::getProperty, // getProperty
ModuleEnvironmentObject::setProperty, // setProperty
ModuleEnvironmentObject::
getOwnPropertyDescriptor, // getOwnPropertyDescriptor
ModuleEnvironmentObject::deleteProperty, // deleteProperty
nullptr, // getElements
nullptr, // funToString
};
const JSClassOps ModuleEnvironmentObject::classOps_ = {
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<ModuleObject*> module) {
RootedScript script(cx, module->script());
Rooted<SharedShape*> shape(
cx, script->bodyScope()->as<ModuleScope>().environmentShape());
MOZ_ASSERT(shape->getObjectClass() == &class_);
Rooted<ModuleEnvironmentObject*> env(
cx, CreateEnvironmentObject<ModuleEnvironmentObject>(cx, shape,
TenuredObject));
if (!env) {
return nullptr;
}
env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));
// Initialize this early so that we can manipulate the env object without
// causing assertions.
env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
// Initialize all lexical bindings and imports as uninitialized. Imports
// get uninitialized because they have a special TDZ for cyclic imports.
for (BindingIter bi(script); bi; bi++) {
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment &&
BindingKindIsLexical(bi.kind())) {
env->initSlot(loc.slot(), MagicValue(JS_UNINITIALIZED_LEXICAL));
}
}
// It is not be possible to add or remove bindings from a module environment
// after this point as module code is always strict.
#ifdef DEBUG
for (ShapePropertyIter<NoGC> iter(env->shape()); !iter.done(); iter++) {
MOZ_ASSERT(!iter->configurable());
}
MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible));
MOZ_ASSERT(!env->inDictionaryMode());
#endif
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
env->initSlot(ModuleEnvironmentObject::DISPOSABLE_RESOURCE_STACK_SLOT,
UndefinedValue());
#endif
return env;
}
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
static ArrayObject* initialiseAndSetDisposeCapabilityHelper(
JSContext* cx, JS::Handle<EnvironmentObject*> env, uint32_t slot) {
JS::Value slotData = env->getReservedSlot(slot);
ArrayObject* disposablesList = nullptr;
if (slotData.isUndefined()) {
disposablesList = NewDenseEmptyArray(cx);
if (!disposablesList) {
return nullptr;
}
env->setReservedSlot(slot, ObjectValue(*disposablesList));
} else {
disposablesList = &slotData.toObject().as<ArrayObject>();
}
return disposablesList;
}
ArrayObject* DisposableEnvironmentObject::getOrCreateDisposeCapability(
JSContext* cx) {
Rooted<DisposableEnvironmentObject*> env(cx, this);
return initialiseAndSetDisposeCapabilityHelper(
cx, env, DISPOSABLE_RESOURCE_STACK_SLOT);
}
JS::Value DisposableEnvironmentObject::getDisposables() {
return getReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT);
}
void DisposableEnvironmentObject::clearDisposables() {
setReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT, UndefinedValue());
}
#endif
/* static */
ModuleEnvironmentObject* ModuleEnvironmentObject::createSynthetic(
JSContext* cx, Handle<ModuleObject*> module) {
Rooted<SharedShape*> shape(cx,
CreateEnvironmentShapeForSyntheticModule(
cx, &class_, JSSLOT_FREE(&class_), module));
if (!shape) {
return nullptr;
}
MOZ_ASSERT(shape->getObjectClass() == &class_);
Rooted<ModuleEnvironmentObject*> env(
cx, CreateEnvironmentObject<ModuleEnvironmentObject>(cx, shape,
TenuredObject));
if (!env) {
return nullptr;
}
env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));
// Initialize this early so that we can manipulate the env object without
// causing assertions.
env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
// It is not be possible to add or remove bindings from a module environment
// after this point as module code is always strict.
#ifdef DEBUG
for (ShapePropertyIter<NoGC> iter(env->shape()); !iter.done(); iter++) {
MOZ_ASSERT(!iter->configurable());
}
MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible));
MOZ_ASSERT(!env->inDictionaryMode());
#endif
return env;
}
ModuleObject& ModuleEnvironmentObject::module() const {
return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>();
}
IndirectBindingMap& ModuleEnvironmentObject::importBindings() const {
return module().importBindings();
}
bool ModuleEnvironmentObject::createImportBinding(JSContext* cx,
Handle<JSAtom*> importName,
Handle<ModuleObject*> module,
Handle<JSAtom*> localName) {
RootedId importNameId(cx, AtomToId(importName));
RootedId localNameId(cx, AtomToId(localName));
Rooted<ModuleEnvironmentObject*> env(cx, &module->initialEnvironment());
if (!importBindings().put(cx, importNameId, env, localNameId)) {
return false;
}
return true;
}
bool ModuleEnvironmentObject::hasImportBinding(Handle<PropertyName*> name) {
return importBindings().has(NameToId(name));
}
bool ModuleEnvironmentObject::lookupImport(
jsid name, ModuleEnvironmentObject** envOut,
mozilla::Maybe<PropertyInfo>* propOut) {
return importBindings().lookup(name, envOut, propOut);
}
/* static */
bool ModuleEnvironmentObject::lookupProperty(JSContext* cx, HandleObject obj,
HandleId id,
MutableHandleObject objp,
PropertyResult* propp) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
mozilla::Maybe<PropertyInfo> propInfo;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &propInfo)) {
objp.set(env);
propp->setNativeProperty(*propInfo);
return true;
}
Rooted<NativeObject*> target(cx, &obj->as<NativeObject>());
if (!NativeLookupOwnProperty<CanGC>(cx, target, id, propp)) {
return false;
}
objp.set(obj);
return true;
}
/* static */
bool ModuleEnvironmentObject::hasProperty(JSContext* cx, HandleObject obj,
HandleId id, bool* foundp) {
if (obj->as<ModuleEnvironmentObject>().importBindings().has(id)) {
*foundp = true;
return true;
}
Rooted<NativeObject*> self(cx, &obj->as<NativeObject>());
return NativeHasProperty(cx, self, id, foundp);
}
/* static */
bool ModuleEnvironmentObject::getProperty(JSContext* cx, HandleObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
mozilla::Maybe<PropertyInfo> prop;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &prop)) {
vp.set(env->getSlot(prop->slot()));
return true;
}
Rooted<NativeObject*> self(cx, &obj->as<NativeObject>());
return NativeGetProperty(cx, self, receiver, id, vp);
}
/* static */
bool ModuleEnvironmentObject::setProperty(JSContext* cx, HandleObject obj,
HandleId id, HandleValue v,
HandleValue receiver,
JS::ObjectOpResult& result) {
Rooted<ModuleEnvironmentObject*> self(cx,
&obj->as<ModuleEnvironmentObject>());
if (self->importBindings().has(id)) {
return result.failReadOnly();
}
return NativeSetProperty<Qualified>(cx, self, id, v, receiver, result);
}
/* static */
bool ModuleEnvironmentObject::getOwnPropertyDescriptor(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
mozilla::Maybe<PropertyInfo> prop;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &prop)) {
desc.set(mozilla::Some(PropertyDescriptor::Data(
env->getSlot(prop->slot()),
{JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable})));
return true;
}
Rooted<NativeObject*> self(cx, &obj->as<NativeObject>());
return NativeGetOwnPropertyDescriptor(cx, self, id, desc);
}
/* static */
bool ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj,
HandleId id,
ObjectOpResult& result) {
return result.failCantDelete();
}
/* static */
bool ModuleEnvironmentObject::newEnumerate(JSContext* cx, HandleObject obj,
MutableHandleIdVector properties,
bool enumerableOnly) {
Rooted<ModuleEnvironmentObject*> self(cx,
&obj->as<ModuleEnvironmentObject>());
const IndirectBindingMap& bs(self->importBindings());
MOZ_ASSERT(properties.length() == 0);
size_t count = bs.count() + self->slotSpan() - RESERVED_SLOTS;
if (!properties.reserve(count)) {
ReportOutOfMemory(cx);
return false;
}
bs.forEachExportedName([&](jsid name) { properties.infallibleAppend(name); });
for (ShapePropertyIter<NoGC> iter(self->shape()); !iter.done(); iter++) {
properties.infallibleAppend(iter->key());
}
MOZ_ASSERT(properties.length() == count);
return true;
}
/*****************************************************************************/
const JSClass WasmInstanceEnvironmentObject::class_ = {
"WasmInstance",
JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceEnvironmentObject::RESERVED_SLOTS),
};
/* static */
WasmInstanceEnvironmentObject*
WasmInstanceEnvironmentObject::createHollowForDebug(
JSContext* cx, Handle<WasmInstanceScope*> scope) {
Rooted<SharedShape*> shape(
cx, EmptyEnvironmentShape<WasmInstanceEnvironmentObject>(cx));
if (!shape) {
return nullptr;
}
auto* env = CreateEnvironmentObject<WasmInstanceEnvironmentObject>(cx, shape);
if (!env) {
return nullptr;
}
env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
env->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
return env;
}
/*****************************************************************************/
const JSClass WasmFunctionCallObject::class_ = {
"WasmCall",
JSCLASS_HAS_RESERVED_SLOTS(WasmFunctionCallObject::RESERVED_SLOTS),
};
/* static */
WasmFunctionCallObject* WasmFunctionCallObject::createHollowForDebug(
JSContext* cx, HandleObject enclosing, Handle<WasmFunctionScope*> scope) {
Rooted<SharedShape*> shape(cx,
EmptyEnvironmentShape<WasmFunctionCallObject>(cx));
if (!shape) {
return nullptr;
}
auto* callobj = CreateEnvironmentObject<WasmFunctionCallObject>(cx, shape);
if (!callobj) {
return nullptr;
}
callobj->initEnclosingEnvironment(enclosing);
callobj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
return callobj;
}
/*****************************************************************************/
JSObject* js::GetThisObject(JSObject* obj) {
// Use the WindowProxy if the global is a Window, as Window must never be
// exposed to script.
if (obj->is<GlobalObject>()) {
return ToWindowProxyIfWindow(obj);
}
// We should not expose any environments except NSVOs to script. The NSVO is
// pretending to be the global object in this case.
MOZ_ASSERT_IF(obj->is<EnvironmentObject>(),
obj->is<NonSyntacticVariablesObject>());
return obj;
}
WithEnvironmentObject* WithEnvironmentObject::create(
JSContext* cx, HandleObject object, HandleObject enclosing,
Handle<WithScope*> scope, JS::SupportUnscopables supportUnscopables) {
Rooted<SharedShape*> shape(cx,
EmptyEnvironmentShape<WithEnvironmentObject>(cx));
if (!shape) {
return nullptr;
}
auto* obj = CreateEnvironmentObject<WithEnvironmentObject>(cx, shape);
if (!obj) {
return nullptr;
}
JSObject* thisObj = GetThisObject(object);
obj->initEnclosingEnvironment(enclosing);
obj->initReservedSlot(OBJECT_SLOT, ObjectValue(*object));
obj->initReservedSlot(THIS_SLOT, ObjectValue(*thisObj));
if (scope) {
MOZ_ASSERT(supportUnscopables == JS::SupportUnscopables::Yes,
"with-statements must support Symbol.unscopables");
obj->initReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT,
PrivateGCThingValue(scope));
} else {
Value v = BooleanValue(supportUnscopables == JS::SupportUnscopables::Yes);
obj->initReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT, v);
}
return obj;
}
WithEnvironmentObject* WithEnvironmentObject::createNonSyntactic(
JSContext* cx, HandleObject object, HandleObject enclosing,
JS::SupportUnscopables supportUnscopables) {
return create(cx, object, enclosing, nullptr, supportUnscopables);
}
static inline bool IsUnscopableDotName(JSContext* cx, HandleId id) {
return id.isAtom(cx->names().dot_this_) ||
id.isAtom(cx->names().dot_newTarget_);
}
#ifdef DEBUG
static bool IsInternalDotName(JSContext* cx, HandleId id) {
return id.isAtom(cx->names().dot_this_) ||
id.isAtom(cx->names().dot_generator_) ||
id.isAtom(cx->names().dot_initializers_) ||
id.isAtom(cx->names().dot_fieldKeys_) ||
id.isAtom(cx->names().dot_staticInitializers_) ||
id.isAtom(cx->names().dot_staticFieldKeys_) ||
id.isAtom(cx->names().dot_args_) ||
id.isAtom(cx->names().dot_newTarget_) ||
id.isAtom(cx->names().star_namespace_star_);
}
#endif
/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
static bool CheckUnscopables(JSContext* cx, HandleObject obj, HandleId id,
bool* scopable) {
RootedId unscopablesId(
cx, PropertyKey::Symbol(cx->wellKnownSymbols().unscopables));
RootedValue v(cx);
if (!GetProperty(cx, obj, obj, unscopablesId, &v)) {
return false;
}
if (v.isObject()) {
RootedObject unscopablesObj(cx, &v.toObject());
if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v)) {
return false;
}
*scopable = !ToBoolean(v);
} else {
*scopable = true;
}
return true;
}
static bool with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject objp,
PropertyResult* propp) {
// SpiderMonkey-specific: consider the internal '.this' and '.newTarget' names
// to be unscopable.
if (IsUnscopableDotName(cx, id)) {
objp.set(nullptr);
propp->setNotFound();
return true;
}
// Other internal dot-names shouldn't even end up in with-environments.
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
if (!LookupProperty(cx, actual, id, objp, propp)) {
return false;
}
if (propp->isFound()) {
bool scopable = true;
if (obj->as<WithEnvironmentObject>().supportUnscopables() &&
!CheckUnscopables(cx, actual, id, &scopable)) {
return false;
}
if (!scopable) {
objp.set(nullptr);
propp->setNotFound();
}
}
return true;
}
static bool with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return DefineProperty(cx, actual, id, desc, result);
}
static bool with_HasProperty(JSContext* cx, HandleObject obj, HandleId id,
bool* foundp) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
// ES 8.1.1.2.1 step 3-5.
if (!HasProperty(cx, actual, id, foundp)) {
return false;
}
if (!*foundp || !obj->as<WithEnvironmentObject>().supportUnscopables()) {
return true;
}
// Steps 7-10. (Step 6 is a no-op.)
return CheckUnscopables(cx, actual, id, foundp);
}
static bool with_GetProperty(JSContext* cx, HandleObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
RootedValue actualReceiver(cx, receiver);
if (receiver.isObject() && &receiver.toObject() == obj) {
actualReceiver.setObject(*actual);
}
return GetProperty(cx, actual, actualReceiver, id, vp);
}
static bool with_SetProperty(JSContext* cx, HandleObject obj, HandleId id,
HandleValue v, HandleValue receiver,
ObjectOpResult& result) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
RootedValue actualReceiver(cx, receiver);
if (receiver.isObject() && &receiver.toObject() == obj) {
actualReceiver.setObject(*actual);
}
return SetProperty(cx, actual, id, v, actualReceiver, result);
}
static bool with_GetOwnPropertyDescriptor(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return GetOwnPropertyDescriptor(cx, actual, id, desc);
}
static bool with_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id,
ObjectOpResult& result) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return DeleteProperty(cx, actual, id, result);
}
static const ObjectOps WithEnvironmentObjectOps = {
with_LookupProperty, // lookupProperty
with_DefineProperty, // defineProperty
with_HasProperty, // hasProperty
with_GetProperty, // getProperty
with_SetProperty, // setProperty
with_GetOwnPropertyDescriptor, // getOwnPropertyDescriptor
with_DeleteProperty, // deleteProperty
nullptr, // getElements
nullptr, // funToString
};
const JSClass WithEnvironmentObject::class_ = {
"With",
JSCLASS_HAS_RESERVED_SLOTS(WithEnvironmentObject::RESERVED_SLOTS),
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&WithEnvironmentObjectOps,
};
/* static */
NonSyntacticVariablesObject* NonSyntacticVariablesObject::create(
JSContext* cx) {
Rooted<SharedShape*> shape(
cx, EmptyEnvironmentShape<NonSyntacticVariablesObject>(cx));
if (!shape) {
return nullptr;
}
Rooted<NonSyntacticVariablesObject*> obj(
cx, CreateEnvironmentObject<NonSyntacticVariablesObject>(cx, shape,
TenuredObject));
if (!obj) {
return nullptr;
}
// An NVSO holds both variables qualified with `var` and those that are not.
MOZ_ASSERT(obj->isUnqualifiedVarObj());
MOZ_ASSERT(obj->isQualifiedVarObj());
obj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
return obj;
}
const JSClass NonSyntacticVariablesObject::class_ = {
"NonSyntacticVariablesObject",
JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS),
};
NonSyntacticLexicalEnvironmentObject* js::CreateNonSyntacticEnvironmentChain(
JSContext* cx, const JS::EnvironmentChain& envChain) {
// Callers are responsible for segregating the NonSyntactic case from simple
// compilation cases.
MOZ_RELEASE_ASSERT(!envChain.empty());
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
Rooted<WithEnvironmentObject*> env(
cx, CreateObjectsForEnvironmentChain(cx, envChain, globalLexical));
if (!env) {
return nullptr;
}
// The XPConnect subscript loader, which may pass in its own
// environments to load scripts in, expects the environment chain to
// be the holder of "var" declarations. In SpiderMonkey, such objects
// are called "qualified varobjs", the "qualified" part meaning the
// declaration was qualified by "var". There is only sadness.
//
// See JSObject::isQualifiedVarObj.
if (!JSObject::setQualifiedVarObj(cx, env)) {
return nullptr;
}
// Also get a non-syntactic lexical environment to capture 'let' and
// 'const' bindings. To persist lexical bindings, we have a 1-1
// mapping with the final unwrapped environment object (the
// environment that stores the 'var' bindings) and the lexical
// environment.
//
// TODOshu: disallow the subscript loader from using non-distinguished
// objects as dynamic scopes.
return ObjectRealm::get(env).getOrCreateNonSyntacticLexicalEnvironment(cx,
env);
}
/*****************************************************************************/
const JSClass LexicalEnvironmentObject::class_ = {
"LexicalEnvironment",
JSCLASS_HAS_RESERVED_SLOTS(LexicalEnvironmentObject::RESERVED_SLOTS),
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
JS_NULL_OBJECT_OPS,
};
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::create(
JSContext* cx, Handle<SharedShape*> shape, HandleObject enclosing,
gc::Heap heap) {
MOZ_ASSERT(shape->getObjectClass() == &LexicalEnvironmentObject::class_);
// The JITs assume the result is nursery allocated unless we collected the
// nursery, so don't change |heap| here.
auto* env =
CreateEnvironmentObject<LexicalEnvironmentObject>(cx, shape, heap);
if (!env) {
return nullptr;
}
MOZ_ASSERT(!env->inDictionaryMode());
if (enclosing) {
env->initEnclosingEnvironment(enclosing);
}
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
env->initSlot(LexicalEnvironmentObject::DISPOSABLE_RESOURCE_STACK_SLOT,
UndefinedValue());
#endif
return env;
}
bool LexicalEnvironmentObject::isExtensible() const {
return NativeObject::isExtensible();
}
/* static */
BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::create(
JSContext* cx, Handle<LexicalScope*> scope, HandleObject enclosing,
gc::Heap heap) {
cx->check(enclosing);
MOZ_ASSERT(scope->hasEnvironment());
Rooted<SharedShape*> shape(cx, scope->environmentShape());
auto* env = static_cast<BlockLexicalEnvironmentObject*>(
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<LexicalScope*> scope, AbstractFramePtr frame) {
RootedObject enclosing(cx, frame.environmentChain());
return create(cx, scope, enclosing, gc::Heap::Default);
}
/* static */
BlockLexicalEnvironmentObject*
BlockLexicalEnvironmentObject::createHollowForDebug(
JSContext* cx, Handle<LexicalScope*> scope) {
MOZ_ASSERT(!scope->hasEnvironment());
Rooted<SharedShape*> shape(
cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape) {
return nullptr;
}
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment());
Rooted<LexicalEnvironmentObject*> env(
cx, LexicalEnvironmentObject::create(cx, shape, enclosingEnv,
gc::Heap::Tenured));
if (!env) {
return nullptr;
}
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, env, id, optimizedOut)) {
return nullptr;
}
}
if (!JSObject::setFlag(cx, env, ObjectFlag::NotExtensible)) {
return nullptr;
}
env->as<ScopedLexicalEnvironmentObject>().initScope(scope);
return &env->as<BlockLexicalEnvironmentObject>();
}
/* static */
BlockLexicalEnvironmentObject*
BlockLexicalEnvironmentObject::createTemplateObject(
JSContext* cx, Handle<LexicalScope*> scope) {
return create(cx, scope, nullptr, gc::Heap::Tenured);
}
/* static */
BlockLexicalEnvironmentObject*
BlockLexicalEnvironmentObject::createWithoutEnclosing(
JSContext* cx, Handle<LexicalScope*> scope) {
return create(cx, scope, nullptr, gc::Heap::Default);
}
/* static */
BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::clone(
JSContext* cx, Handle<BlockLexicalEnvironmentObject*> env) {
Rooted<LexicalScope*> scope(cx, &env->scope());
RootedObject enclosing(cx, &env->enclosingEnvironment());
Rooted<BlockLexicalEnvironmentObject*> 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<BlockLexicalEnvironmentObject*> env) {
Rooted<LexicalScope*> scope(cx, &env->scope());
RootedObject enclosing(cx, &env->enclosingEnvironment());
return create(cx, scope, enclosing, gc::Heap::Default);
}
/* static */
NamedLambdaObject* NamedLambdaObject::create(JSContext* cx,
HandleFunction callee,
HandleObject enclosing,
gc::Heap heap) {
MOZ_ASSERT(callee->isNamedLambda());
Rooted<Scope*> scope(cx, callee->nonLazyScript()->maybeNamedLambdaScope());
MOZ_ASSERT(scope && scope->environmentShape());
#ifdef DEBUG
{
// Named lambda objects have one (non-writable) property.
SharedShapePropertyIter<NoGC> iter(scope->environmentShape());
MOZ_ASSERT(iter->slot() == lambdaSlot());
MOZ_ASSERT(!iter->writable());
iter++;
MOZ_ASSERT(iter.done());
// There should be exactly one binding in the named lambda scope.
BindingIter bi(scope);
bi++;
MOZ_ASSERT(bi.done());
}
#endif
BlockLexicalEnvironmentObject* obj = BlockLexicalEnvironmentObject::create(
cx, scope.as<LexicalScope>(), enclosing, heap);
if (!obj) {
return nullptr;
}
obj->initFixedSlot(lambdaSlot(), ObjectValue(*callee));
return static_cast<NamedLambdaObject*>(obj);
}
/* static */
NamedLambdaObject* NamedLambdaObject::createTemplateObject(
JSContext* cx, HandleFunction callee) {
return create(cx, callee, nullptr, gc::Heap::Tenured);
}
/* static */
NamedLambdaObject* NamedLambdaObject::createWithoutEnclosing(
JSContext* cx, HandleFunction callee) {
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<ClassBodyScope*> scope, HandleObject enclosing,
gc::Heap heap) {
cx->check(enclosing);
MOZ_ASSERT(scope->hasEnvironment());
Rooted<SharedShape*> shape(cx, scope->environmentShape());
auto* env = static_cast<ClassBodyLexicalEnvironmentObject*>(
LexicalEnvironmentObject::create(cx, shape, enclosing, heap));
if (!env) {
return nullptr;
}
env->initScope(scope);
return env;
}
/* static */
ClassBodyLexicalEnvironmentObject*
ClassBodyLexicalEnvironmentObject::createForFrame(JSContext* cx,
Handle<ClassBodyScope*> scope,
AbstractFramePtr frame) {
RootedObject enclosing(cx, frame.environmentChain());
return create(cx, scope, enclosing, gc::Heap::Default);
}
/* static */
ClassBodyLexicalEnvironmentObject*
ClassBodyLexicalEnvironmentObject::createTemplateObject(
JSContext* cx, Handle<ClassBodyScope*> scope) {
return create(cx, scope, nullptr, gc::Heap::Tenured);
}
/* static */
ClassBodyLexicalEnvironmentObject*
ClassBodyLexicalEnvironmentObject::createWithoutEnclosing(
JSContext* cx, Handle<ClassBodyScope*> scope) {
return create(cx, scope, nullptr, gc::Heap::Default);
}
JSObject* ExtensibleLexicalEnvironmentObject::thisObject() const {
JSObject* obj = &getReservedSlot(THIS_VALUE_OR_SCOPE_SLOT).toObject();
// Windows must never be exposed to script. initThisObject should have set
// this to the WindowProxy.
MOZ_ASSERT(!IsWindow(obj));
// WarpBuilder relies on the return value not being nursery-allocated for the
// global lexical environment.
MOZ_ASSERT_IF(isGlobal(), obj->isTenured());
return obj;
}
/* static */
ExtensibleLexicalEnvironmentObject*
ExtensibleLexicalEnvironmentObject::forVarEnvironment(JSObject* obj) {
ExtensibleLexicalEnvironmentObject* lexical = nullptr;
if (obj->is<GlobalObject>()) {
lexical = &obj->as<GlobalObject>().lexicalEnvironment();
} else {
lexical = ObjectRealm::get(obj).getNonSyntacticLexicalEnvironment(obj);
}
MOZ_ASSERT(lexical);
return lexical;
}
/* static */
GlobalLexicalEnvironmentObject* GlobalLexicalEnvironmentObject::create(
JSContext* cx, Handle<GlobalObject*> global) {
MOZ_ASSERT(global);
Rooted<SharedShape*> shape(
cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape) {
return nullptr;
}
auto* env = static_cast<GlobalLexicalEnvironmentObject*>(
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<SharedShape*> shape(
cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape) {
return nullptr;
}
auto* env = static_cast<NonSyntacticLexicalEnvironmentObject*>(
LexicalEnvironmentObject::create(cx, shape, enclosing,
gc::Heap::Tenured));
if (!env) {
return nullptr;
}
env->initThisObject(thisv);
return env;
}
/* static */
RuntimeLexicalErrorObject* RuntimeLexicalErrorObject::create(
JSContext* cx, HandleObject enclosing, unsigned errorNumber) {
Rooted<SharedShape*> shape(
cx,
EmptyEnvironmentShape(cx, &class_, JSSLOT_FREE(&class_), ObjectFlags()));
if (!shape) {
return nullptr;
}
auto* obj = CreateEnvironmentObject<RuntimeLexicalErrorObject>(cx, shape);
if (!obj) {
return nullptr;
}
obj->initEnclosingEnvironment(enclosing);
obj->initReservedSlot(ERROR_SLOT, Int32Value(int32_t(errorNumber)));
return obj;
}
static void ReportRuntimeLexicalErrorId(JSContext* cx, unsigned errorNumber,
HandleId id) {
if (id.isAtom()) {
Rooted<PropertyName*> name(cx, id.toAtom()->asPropertyName());
ReportRuntimeLexicalError(cx, errorNumber, name);
return;
}
MOZ_CRASH(
"RuntimeLexicalErrorObject should only be used with property names");
}
static bool lexicalError_LookupProperty(JSContext* cx, HandleObject obj,
HandleId id, MutableHandleObject objp,
PropertyResult* propp) {
ReportRuntimeLexicalErrorId(
cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool lexicalError_HasProperty(JSContext* cx, HandleObject obj,
HandleId id, bool* foundp) {
ReportRuntimeLexicalErrorId(
cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool lexicalError_GetProperty(JSContext* cx, HandleObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp) {
ReportRuntimeLexicalErrorId(
cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool lexicalError_SetProperty(JSContext* cx, HandleObject obj,
HandleId id, HandleValue v,
HandleValue receiver,
ObjectOpResult& result) {
ReportRuntimeLexicalErrorId(
cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool lexicalError_GetOwnPropertyDescriptor(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
ReportRuntimeLexicalErrorId(
cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool lexicalError_DeleteProperty(JSContext* cx, HandleObject obj,
HandleId id, ObjectOpResult& result) {
ReportRuntimeLexicalErrorId(
cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static const ObjectOps RuntimeLexicalErrorObjectObjectOps = {
lexicalError_LookupProperty, // lookupProperty
nullptr, // defineProperty
lexicalError_HasProperty, // hasProperty
lexicalError_GetProperty, // getProperty
lexicalError_SetProperty, // setProperty
lexicalError_GetOwnPropertyDescriptor, // getOwnPropertyDescriptor
lexicalError_DeleteProperty, // deleteProperty
nullptr, // getElements
nullptr, // funToString
};
const JSClass RuntimeLexicalErrorObject::class_ = {
"RuntimeLexicalError",
JSCLASS_HAS_RESERVED_SLOTS(RuntimeLexicalErrorObject::RESERVED_SLOTS),
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&RuntimeLexicalErrorObjectObjectOps,
};
/*****************************************************************************/
EnvironmentIter::EnvironmentIter(JSContext* cx, const EnvironmentIter& ei)
: si_(cx, ei.si_.get()), env_(cx, ei.env_), frame_(ei.frame_) {}
EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope)
: si_(cx, ScopeIter(scope)), env_(cx, env), frame_(NullFramePtr()) {
settle();
}
EnvironmentIter::EnvironmentIter(JSContext* cx, AbstractFramePtr frame,
const jsbytecode* pc)
: si_(cx, frame.script()->innermostScope(pc)),
env_(cx, frame.environmentChain()),
frame_(frame) {
cx->check(frame);
settle();
}
EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope,
AbstractFramePtr frame)
: si_(cx, ScopeIter(scope)), env_(cx, env), frame_(frame) {
cx->check(frame);
settle();
}
void EnvironmentIter::incrementScopeIter() {
if (si_.scope()->is<GlobalScope>()) {
// GlobalScopes may be syntactic or non-syntactic. Non-syntactic
// GlobalScopes correspond to zero or more non-syntactic
// EnvironmentsObjects followed by the global lexical scope, then the
// GlobalObject or another non-EnvironmentObject object.
if (!env_->is<EnvironmentObject>()) {
si_++;
}
} else {
si_++;
}
}
void EnvironmentIter::settle() {
// Check for trying to iterate a function or eval frame before the prologue
// has created the CallObject, in which case we have to skip.
if (frame_ && frame_.hasScript() &&
frame_.script()->initialEnvironmentShape() &&
!frame_.hasInitialEnvironment()) {
// Skip until we're at the enclosing scope of the script.
while (si_.scope() != frame_.script()->enclosingScope()) {
if (env_->is<BlockLexicalEnvironmentObject>() &&
&env_->as<BlockLexicalEnvironmentObject>().scope() == si_.scope()) {
MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda ||
si_.kind() == ScopeKind::StrictNamedLambda);
env_ =
&env_->as<BlockLexicalEnvironmentObject>().enclosingEnvironment();
}
incrementScopeIter();
}
}
// Check if we have left the extent of the initial frame after we've
// settled on a static scope.
if (frame_ &&
(!si_ ||
(frame_.hasScript() &&
si_.scope() == frame_.script()->enclosingScope()) ||
(frame_.isWasmDebugFrame() && !si_.scope()->is<WasmFunctionScope>()))) {
frame_ = NullFramePtr();
}
#ifdef DEBUG
if (si_) {
if (hasSyntacticEnvironment()) {
Scope* scope = si_.scope();
if (scope->is<LexicalScope>()) {
MOZ_ASSERT(scope == &env_->as<BlockLexicalEnvironmentObject>().scope());
} else if (scope->is<FunctionScope>()) {
MOZ_ASSERT(scope->as<FunctionScope>().script() ==
env_->as<CallObject>()
.callee()
.maybeCanonicalFunction()
->baseScript());
} else if (scope->is<VarScope>()) {
MOZ_ASSERT(scope == &env_->as<VarEnvironmentObject>().scope());
} else if (scope->is<WithScope>()) {
MOZ_ASSERT(scope == &env_->as<WithEnvironmentObject>().scope());
} else if (scope->is<EvalScope>()) {
MOZ_ASSERT(scope == &env_->as<VarEnvironmentObject>().scope());
} else if (scope->is<GlobalScope>()) {
MOZ_ASSERT(env_->is<GlobalObject>() ||
env_->is<GlobalLexicalEnvironmentObject>());
}
} else if (hasNonSyntacticEnvironmentObject()) {
if (env_->is<LexicalEnvironmentObject>()) {
// The global lexical environment still encloses non-syntactic
// environment objects.
MOZ_ASSERT(env_->is<NonSyntacticLexicalEnvironmentObject>() ||
env_->is<GlobalLexicalEnvironmentObject>());
} else if (env_->is<WithEnvironmentObject>()) {
MOZ_ASSERT(!env_->as<WithEnvironmentObject>().isSyntactic());
} else {
MOZ_ASSERT(env_->is<NonSyntacticVariablesObject>());
}
}
}
#endif
}
JSObject& EnvironmentIter::enclosingEnvironment() const {
// As an engine invariant (maintained internally and asserted by Execute),
// EnvironmentObjects and non-EnvironmentObjects cannot be interleaved on
// the scope chain; every scope chain must start with zero or more
// EnvironmentObjects and terminate with one or more
// non-EnvironmentObjects (viz., GlobalObject).
MOZ_ASSERT(done());
MOZ_ASSERT(!env_->is<EnvironmentObject>());
return *env_;
}
bool EnvironmentIter::hasNonSyntacticEnvironmentObject() const {
// The case we're worrying about here is a NonSyntactic static scope which
// has 0+ corresponding non-syntactic WithEnvironmentObject scopes, a
// NonSyntacticVariablesObject, or a NonSyntacticLexicalEnvironmentObject.
if (si_.kind() == ScopeKind::NonSyntactic) {
MOZ_ASSERT_IF(env_->is<WithEnvironmentObject>(),
!env_->as<WithEnvironmentObject>().isSyntactic());
return env_->is<EnvironmentObject>();
}
return false;
}
/* 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 NurseryAllocableProxyHandler {
enum Action { SET, GET };
enum AccessResult { ACCESS_UNALIASED, ACCESS_GENERIC, ACCESS_LOST };
/*
* This function handles access to unaliased locals/formals. Since they
* are unaliased, the values of these variables are not stored in the
* slots of the normal CallObject and BlockLexicalEnvironmentObject
* environments and thus must be recovered from somewhere else:
* + if the invocation for which the env was created is still executing,
* there is a JS frame live on the stack holding the values;
* + if the invocation for which the env was created finished executing:
* - and there was a DebugEnvironmentProxy associated with env, then
* the DebugEnvironments::onPop(Call|Lexical) handler copied out the
* unaliased variables. In both cases, a dense array is created in
* onPop(Call|Lexical) to hold the unaliased values and attached to
* the DebugEnvironmentProxy;
* - and there was not a DebugEnvironmentProxy yet associated with the
* scope, then the unaliased values are lost and not recoverable.
*
* Callers should check accessResult for non-failure results:
* - ACCESS_UNALIASED if the access was unaliased and completed
* - ACCESS_GENERIC if the access was aliased or the property not found
* - ACCESS_LOST if the value has been lost to the debugger and the
* action is GET; if the action is SET, we assign to the
* name of the variable on the environment object
*/
bool handleUnaliasedAccess(JSContext* cx,
Handle<DebugEnvironmentProxy*> debugEnv,
Handle<EnvironmentObject*> env, HandleId id,
Action action, MutableHandleValue vp,
AccessResult* accessResult) const {
MOZ_ASSERT(&debugEnv->environment() == env);
MOZ_ASSERT_IF(action == SET, !debugEnv->isOptimizedOut());
*accessResult = ACCESS_GENERIC;
LiveEnvironmentVal* maybeLiveEnv =
DebugEnvironments::hasLiveEnvironment(*env);
// Handle unaliased formals, vars, lets, and consts at function or module
// scope.
if (env->is<CallObject>() || env->is<ModuleEnvironmentObject>()) {
RootedScript script(cx);
if (env->is<CallObject>()) {
CallObject& callobj = env->as<CallObject>();
RootedFunction fun(cx, &callobj.callee());
script = JSFunction::getOrCreateScript(cx, fun);
} else {
script = env->as<ModuleEnvironmentObject>().module().maybeScript();
if (!script) {
return true;
}
}
BindingIter bi(script);
while (bi && NameToId(bi.name()->asPropertyName()) != id) {
bi++;
}
if (!bi) {
return true;
}
if (action == SET && bi.kind() == BindingKind::Const) {
ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, id);
return false;
}
if (bi.location().kind() == BindingLocation::Kind::Import) {
return true;
}
if (!bi.hasArgumentSlot()) {
if (bi.closedOver()) {
return true;
}
uint32_t i = bi.location().slot();
if (maybeLiveEnv) {
AbstractFramePtr frame = maybeLiveEnv->frame();
if (action == GET) {
vp.set(frame.unaliasedLocal(i));
} else {
frame.unaliasedLocal(i) = vp;
}
} else if (AbstractGeneratorObject* genObj =
GetGeneratorObjectForEnvironment(cx, env);
genObj && genObj->isSuspended() &&
genObj->hasStackStorage()) {
if (action == GET) {
vp.set(genObj->getUnaliasedLocal(i));
} else {
genObj->setUnaliasedLocal(i, vp);
}
} else if (NativeObject* snapshot = debugEnv->maybeSnapshot()) {
if (action == GET) {
vp.set(snapshot->getDenseElement(script->numArgs() + i));
} else {
snapshot->setDenseElement(script->numArgs() + i, vp);
}
} else {
/* The unaliased value has been lost to the debugger. */
if (action == GET) {
*accessResult = ACCESS_LOST;
return true;
}
}
} else {
unsigned i = bi.argumentSlot();
if (bi.closedOver()) {
return true;
}
if (maybeLiveEnv) {
AbstractFramePtr frame = maybeLiveEnv->frame();
if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
if (action == GET) {
vp.set(frame.argsObj().arg(i));
} else {
frame.argsObj().setArg(i, vp);
}
} else {
if (action == GET) {
vp.set(frame.unaliasedFormal(i, DONT_CHECK_ALIASING));
} else {
frame.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp;
}
}
} else if (NativeObject* snapshot = debugEnv->maybeSnapshot()) {
if (action == GET) {
vp.set(snapshot->getDenseElement(i));
} else {
snapshot->setDenseElement(i, vp);
}
} else {
/* The unaliased value has been lost to the debugger. */
if (action == GET) {
*accessResult = ACCESS_LOST;
return true;
}
}
}
// It is possible that an optimized out value flows to this
// location due to Debugger.Frame.prototype.eval operating on a
// live bailed-out Baseline frame. In that case, treat the access
// as lost.
if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT) {
*accessResult = ACCESS_LOST;
} else {
*accessResult = ACCESS_UNALIASED;
}
return true;
}
/*
* Handle unaliased vars in functions with parameter expressions and
* lexical bindings at block scope.
*/
if (env->is<LexicalEnvironmentObject>() ||
env->is<VarEnvironmentObject>()) {
// Currently consider all non-syntactic top-level lexical bindings to be
// aliased.
if (env->is<LexicalEnvironmentObject>() &&
!env->is<GlobalLexicalEnvironmentObject>() &&
env->as<LexicalEnvironmentObject>().isExtensible()) {
MOZ_ASSERT(!IsSyntacticEnvironment(env));
return true;
}
// Currently all vars inside non-strict eval var environments are aliased.
if (env->is<VarEnvironmentObject>() &&
env->as<VarEnvironmentObject>().isForNonStrictEval()) {
return true;
}
Rooted<Scope*> scope(cx, getEnvironmentScope(*env));
uint32_t firstFrameSlot = scope->firstFrameSlot();
BindingIter bi(scope);
while (bi && NameToId(bi.name()->asPropertyName()) != id) {
bi++;
}
if (!bi) {
return true;
}
if (action == SET && bi.kind() == BindingKind::Const) {
ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, id);
return false;
}
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment) {
return true;
}
// Named lambdas that are not closed over are lost.
if (loc.kind() == BindingLocation::Kind::NamedLambdaCallee) {
if (action == GET) {
*accessResult = ACCESS_LOST;
}
return true;
}
MOZ_ASSERT(loc.kind() == BindingLocation::Kind::Frame);
if (maybeLiveEnv) {
AbstractFramePtr frame = maybeLiveEnv->frame();
uint32_t local = loc.slot();
MOZ_ASSERT(local < frame.script()->nfixed());
Value& localVal = frame.unaliasedLocal(local);
if (action == GET) {
vp.set(localVal);
} else {
// Note: localVal could also be JS_OPTIMIZED_OUT.
if (localVal.isMagic() &&
localVal.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
return false;
}
localVal = vp;
}
} else if (AbstractGeneratorObject* genObj =
GetGeneratorObjectForEnvironment(cx, debugEnv);
genObj && genObj->isSuspended() && genObj->hasStackStorage()) {
if (action == GET) {
vp.set(genObj->getUnaliasedLocal(loc.slot()));
} else {
genObj->setUnaliasedLocal(loc.slot(), vp);
}
} else if (NativeObject* snapshot = debugEnv->maybeSnapshot()) {
// Indices in the frame snapshot are offset by the first frame
// slot. See DebugEnvironments::takeFrameSnapshot.
MOZ_ASSERT(loc.slot() >= firstFrameSlot);
uint32_t snapshotIndex = loc.slot() - firstFrameSlot;
if (action == GET) {
vp.set(snapshot->getDenseElement(snapshotIndex));
} else {
snapshot->setDenseElement(snapshotIndex, vp);
}
} else {
if (action == GET) {
// A {Lexical,Var}EnvironmentObject whose static scope
// does not have an environment shape at all is a "hollow"
// block object reflected for missing block scopes. Their
// slot values are lost.
if (!scope->hasEnvironment()) {
*accessResult = ACCESS_LOST;
return true;
}
if (!GetProperty(cx, env, env, id, vp)) {
return false;
}
} else {
if (!SetProperty(cx, env, id, vp)) {
return false;
}
}
}
// See comment above in analogous CallObject case.
if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT) {
*accessResult = ACCESS_LOST;
} else {
*accessResult = ACCESS_UNALIASED;
}
return true;
}
if (env->is<WasmFunctionCallObject>()) {
if (maybeLiveEnv) {
Rooted<Scope*> scope(cx, getEnvironmentScope(*env));
uint32_t index = 0;
for (BindingIter bi(scope); bi; bi++) {
if (id.isAtom(bi.name())) {
break;
}
MOZ_ASSERT(!bi.isLast());
index++;
}
AbstractFramePtr frame = maybeLiveEnv->frame();
MOZ_ASSERT(frame.isWasmDebugFrame());
wasm::DebugFrame* wasmFrame = frame.asWasmDebugFrame();
if (action == GET) {
if (!wasmFrame->getLocal(index, vp)) {
ReportOutOfMemory(cx);
return false;
}
*accessResult = ACCESS_UNALIASED;
} else { // if (action == SET)
// TODO
}
} else {
*accessResult = ACCESS_LOST;
}
return true;
}
if (env->is<WasmInstanceEnvironmentObject>()) {
Rooted<Scope*> scope(cx, getEnvironmentScope(*env));
MOZ_ASSERT(scope->is<WasmInstanceScope>());
uint32_t index = 0;
for (BindingIter bi(scope); bi; bi++) {
if (id.isAtom(bi.name())) {
break;
}
MOZ_ASSERT(!bi.isLast());
index++;
}
Rooted<WasmInstanceScope*> instanceScope(cx,
&scope->as<WasmInstanceScope>());
wasm::Instance& instance = instanceScope->instance()->instance();
if (action == GET) {
if (instanceScope->memoriesStart() <= index &&
index < instanceScope->globalsStart()) {
vp.set(ObjectValue(
*instance.memory(index - instanceScope->memoriesStart())));
}
if (instanceScope->globalsStart() <= index) {
MOZ_ASSERT(index < instanceScope->namesCount());
if (!instance.debug().getGlobal(
instance, index - instanceScope->globalsStart(), vp)) {
ReportOutOfMemory(cx);
return false;
}
}
*accessResult = ACCESS_UNALIASED;
} else { // if (action == SET)
// TODO
}
return true;
}
/* The rest of the internal scopes do not have unaliased vars. */
MOZ_ASSERT(!IsSyntacticEnvironment(env) ||
env->is<WithEnvironmentObject>());
return true;
}
static bool isArguments(JSContext* cx, jsid id) {
return id == NameToId(cx->names().arguments);
}
static bool isThis(JSContext* cx, jsid id) {
return id == NameToId(cx->names().dot_this_);
}
static bool isFunctionEnvironment(const JSObject& env) {
return env.is<CallObject>();
}
static bool isNonExtensibleLexicalEnvironment(const JSObject& env) {
return env.is<ScopedLexicalEnvironmentObject>();
}
static Scope* getEnvironmentScope(const JSObject& env) {
if (isFunctionEnvironment(env)) {
return env.as<CallObject>().callee().nonLazyScript()->bodyScope();
}
if (env.is<ModuleEnvironmentObject>()) {
JSScript* script =
env.as<ModuleEnvironmentObject>().module().maybeScript();
return script ? script->bodyScope() : nullptr;
}
if (isNonExtensibleLexicalEnvironment(env)) {
return &env.as<ScopedLexicalEnvironmentObject>().scope();
}
if (env.is<VarEnvironmentObject>()) {
return &env.as<VarEnvironmentObject>().scope();
}
if (env.is<WasmInstanceEnvironmentObject>()) {
return &env.as<WasmInstanceEnvironmentObject>().scope();
}
if (env.is<WasmFunctionCallObject>()) {
return &env.as<WasmFunctionCallObject>().scope();
}
if (env.is<GlobalLexicalEnvironmentObject>()) {
return &env.as<GlobalLexicalEnvironmentObject>()
.global()
.emptyGlobalScope();
}
return nullptr;
}
friend Scope* js::GetEnvironmentScope(const JSObject& env);
/*
* In theory, every non-arrow function scope contains an 'arguments'
* bindings. However, the engine only adds a binding if 'arguments' is
* used in the function body. Thus, from the debugger's perspective,
* 'arguments' may be missing from the list of bindings.
*/
static bool isMissingArgumentsBinding(EnvironmentObject& env) {
return isFunctionEnvironment(env) &&
!env.as<CallObject>().callee().baseScript()->needsArgsObj();
}
/*
* Similar to 'arguments' above, we don't add a 'this' binding to
* non-arrow functions if it's not used.
*/
static bool isMissingThisBinding(EnvironmentObject& env) {
return isFunctionEnvironmentWithThis(env) &&
!env.as<CallObject>()
.callee()
.baseScript()
->functionHasThisBinding();
}
/*
* This function checks if an arguments object needs to be created when
* the debugger requests 'arguments' for a function scope where the
* arguments object was not otherwise needed.
*/
static bool isMissingArguments(JSContext* cx, jsid id,
EnvironmentObject& env) {
return isArguments(cx, id) && isMissingArgumentsBinding(env);
}
static bool isMissingThis(JSContext* cx, jsid id, EnvironmentObject& env) {
return isThis(cx, id) && isMissingThisBinding(env);
}
/*
* If the value of |this| is requested before the this-binding has been
* initialized by JSOp::FunctionThis, the this-binding will be |undefined|.
* In that case, we have to call createMissingThis to initialize the
* this-binding.
*
* Note that an |undefined| this-binding is perfectly valid in strict-mode
* code, but that's fine: createMissingThis will do the right thing in that
* case.
*/
static bool isMaybeUninitializedThisValue(JSContext* cx, jsid id,
const Value& v) {
return isThis(cx, id) && v.isUndefined();
}
/*
* Create a missing arguments object. If the function returns true but
* argsObj is null, it means the env is dead.
*/
static bool createMissingArguments(JSContext* cx, EnvironmentObject& env,
MutableHandle<ArgumentsObject*> argsObj) {
argsObj.set(nullptr);
LiveEnvironmentVal* maybeEnv = DebugEnvironments::hasLiveEnvironment(env);
if (!maybeEnv) {
return true;
}
argsObj.set(ArgumentsObject::createUnexpected(cx, maybeEnv->frame()));
return !!argsObj;
}
/*
* Create a missing this Value. If the function returns true but
* *success is false, it means the scope is dead.
*/
static bool createMissingThis(JSContext* cx, EnvironmentObject& env,
MutableHandleValue thisv, bool* success) {
*success = false;
LiveEnvironmentVal* maybeEnv = DebugEnvironments::hasLiveEnvironment(env);
if (!maybeEnv) {
return true;
}
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()
: NurseryAllocableProxyHandler(&family) {}
static bool isFunctionEnvironmentWithThis(const JSObject& env) {
// All functions except arrows should have their own this binding.
return isFunctionEnvironment(env) &&
!env.as<CallObject>().callee().hasLexicalThis();
}
bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
bool* isOrdinary,
MutableHandleObject protop) const override {
MOZ_CRASH(
"shouldn't be possible to access the prototype chain of a "
"DebugEnvironmentProxyHandler");
}
bool preventExtensions(JSContext* cx, HandleObject proxy,
ObjectOpResult& result) const override {
// always [[Extensible]], can't be made non-[[Extensible]], like most
// proxies
return result.fail(JSMSG_CANT_CHANGE_EXTENSIBILITY);
}
bool isExtensible(JSContext* cx, HandleObject proxy,
bool* extensible) const override {
// See above.
*extensible = true;
return true;
}
bool getMissingArgumentsPropertyDescriptor(
JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
EnvironmentObject& env,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) const {
Rooted<ArgumentsObject*> argsObj(cx);
if (!createMissingArguments(cx, env, &argsObj)) {
return false;
}
if (!argsObj) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_DEBUG_NOT_ON_STACK, "Debugger scope");
return false;
}
desc.set(mozilla::Some(PropertyDescriptor::Data(
ObjectValue(*argsObj), {JS::PropertyAttribute::Enumerable})));
return true;
}
bool getMissingThisPropertyDescriptor(
JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
EnvironmentObject& env,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) const {
RootedValue thisv(cx);
bool success;
if (!createMissingThis(cx, env, &thisv, &success)) {
return false;
}
if (!success) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_DEBUG_NOT_ON_STACK, "Debugger scope");
return false;
}
desc.set(mozilla::Some(
PropertyDescriptor::Data(thisv, {JS::PropertyAttribute::Enumerable})));
return true;
}
bool getOwnPropertyDescriptor(
JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) const override {
Rooted<DebugEnvironmentProxy*> debugEnv(
cx, &proxy->as<DebugEnvironmentProxy>());
Rooted<EnvironmentObject*> env(cx, &debugEnv->environment());
if (isMissingArguments(cx, id, *env)) {
return getMissingArgumentsPropertyDescriptor(cx, debugEnv, *env, desc);
}
if (isMissingThis(cx, id, *env)) {
return getMissingThisPropertyDescriptor(cx, debugEnv, *env, desc);
}
RootedValue v(cx);
AccessResult access;
if (!handleUnaliasedAccess(cx, debugEnv, env, id, GET, &v, &access)) {
return false;
}
switch (access) {
case ACCESS_UNALIASED: {
desc.set(mozilla::Some(
PropertyDescriptor::Data(v, {JS::PropertyAttribute::Enumerable})));
return true;
}
case ACCESS_GENERIC:
return GetOwnPropertyDescriptor(cx, env, id, desc);
case ACCESS_LOST:
reportOptimizedOut(cx, id);
return false;
default:
MOZ_CRASH("bad AccessResult");
}
}
bool getMissingArguments(JSContext* cx, EnvironmentObject& env,
MutableHandleValue vp) const {
Rooted<ArgumentsObject*> argsObj(cx);
if (!createMissingArguments(cx, env, &argsObj)) {
return false;
}
if (!argsObj) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_DEBUG_NOT_ON_STACK, "Debugger env");
return false;
}
vp.setObject(*argsObj);
return true;
}
bool getMissingThis(JSContext* cx, EnvironmentObject& env,
MutableHandleValue vp) const {
RootedValue thisv(cx);
bool success;
if (!createMissingThis(cx, env, &thisv, &success)) {
return false;
}
if (!success) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_DEBUG_NOT_ON_STACK, "Debugger env");
return false;
}
vp.set(thisv);
return true;
}
bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
MutableHandleValue vp) const override {
Rooted<DebugEnvironmentProxy*> debugEnv(
cx, &proxy->as<DebugEnvironmentProxy>());
Rooted<EnvironmentObject*> env(
cx, &proxy->as<DebugEnvironmentProxy>().environment());
if (isMissingArguments(cx, id, *env)) {
return getMissingArguments(cx, *env, vp);
}
if (isMissingThis(cx, id, *env)) {
return getMissingThis(cx, *env, vp);
}
AccessResult access;
if (!handleUnaliasedAccess(cx, debugEnv, env, id, GET, vp, &access)) {
return false;
}
switch (access) {
case ACCESS_UNALIASED:
if (isMaybeUninitializedThisValue(cx, id, vp)) {
return getMissingThis(cx, *env, vp);
}
return true;
case ACCESS_GENERIC:
if (!GetProperty(cx, env, env, id, vp)) {
return false;
}
if (isMaybeUninitializedThisValue(cx, id, vp)) {
return getMissingThis(cx, *env, vp);
}
return true;
case ACCESS_LOST:
reportOptimizedOut(cx, id);
return false;
default:
MOZ_CRASH("bad AccessResult");
}
}
bool getMissingArgumentsMaybeSentinelValue(JSContext* cx,
EnvironmentObject& env,
MutableHandleValue vp) const {
Rooted<ArgumentsObject*> argsObj(cx);
if (!createMissingArguments(cx, env, &argsObj)) {
return false;
}
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<DebugEnvironmentProxy*> debugEnv,
HandleId id, MutableHandleValue vp) const {
Rooted<EnvironmentObject*> env(cx, &debugEnv->environment());
if (isMissingArguments(cx, id, *env)) {
return getMissingArgumentsMaybeSentinelValue(cx, *env, vp);
}
if (isMissingThis(cx, id, *env)) {
return getMissingThisMaybeSentinelValue(cx, *env, vp);
}
AccessResult access;
if (!handleUnaliasedAccess(cx, debugEnv, env, id, GET, vp, &access)) {
return false;
}
switch (access) {
case ACCESS_UNALIASED:
if (isMaybeUninitializedThisValue(cx, id, vp)) {
return getMissingThisMaybeSentinelValue(cx, *env, vp);
}
return true;
case ACCESS_GENERIC:
if (!GetProperty(cx, env, env, id, vp)) {
return false;
}
if (isMaybeUninitializedThisValue(cx, id, vp)) {
return getMissingThisMaybeSentinelValue(cx, *env, vp);
}
return true;
case ACCESS_LOST:
vp.setMagic(JS_OPTIMIZED_OUT);
return true;
default:
MOZ_CRASH("bad AccessResult");
}
}
bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result) const override {
Rooted<DebugEnvironmentProxy*> debugEnv(
cx, &proxy->as<DebugEnvironmentProxy>());
Rooted<EnvironmentObject*> env(
cx, &proxy->as<DebugEnvironmentProxy>().environment());
if (debugEnv->isOptimizedOut()) {
return Throw(cx, id, JSMSG_DEBUG_CANT_SET_OPT_ENV);
}
AccessResult access;
RootedValue valCopy(cx, v);
if (!handleUnaliasedAccess(cx, debugEnv, env, id, SET, &valCopy, &access)) {
return false;
}
switch (access) {
case ACCESS_UNALIASED:
return result.succeed();
case ACCESS_GENERIC: {
RootedValue envVal(cx, ObjectValue(*env));
RootedValue initialVal(cx);
if (!GetProperty(cx, env, env, id, &initialVal)) {
return false;
}
// Note: initialVal could be JS_OPTIMIZED_OUT, which is why we don't use
// .whyMagic(JS_UNINITALIZED_LEXICAL).
if (initialVal.isMagic() &&
initialVal.whyMagic() == JS_UNINITIALIZED_LEXICAL) {
ReportRuntimeLexicalErrorId(cx, JSMSG_UNINITIALIZED_LEXICAL, id);
return false;
}
return SetProperty(cx, env, id, v, envVal, result);
}
default:
MOZ_CRASH("bad AccessResult");
}
}
bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) const override {
Rooted<EnvironmentObject*> env(
cx, &proxy->as<DebugEnvironmentProxy>().environment());
bool found;
if (!has(cx, proxy, id, &found)) {
return false;
}
if (found) {
return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP);
}
return JS_DefinePropertyById(cx, env, id, desc, result);
}
bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
MutableHandleIdVector props) const override {
Rooted<EnvironmentObject*> env(
cx, &proxy->as<DebugEnvironmentProxy>().environment());
if (isMissingArgumentsBinding(*env)) {
if (!props.append(NameToId(cx->names().arguments))) {
return false;
}
}
if (isMissingThisBinding(*env)) {
if (!props.append(NameToId(cx->names().dot_this_))) {
return false;
}
}
// WithEnvironmentObject isn't a very good proxy. It doesn't have a
// JSNewEnumerateOp implementation, because if it just delegated to the
// target object, the object would indicate that native enumeration is
// the thing to do, but native enumeration over the WithEnvironmentObject
// wrapper yields no properties. So instead here we hack around the
// issue: punch a hole through to the with object target, then manually
// examine @@unscopables.
RootedObject target(cx);
bool isWith = env->is<WithEnvironmentObject>();
if (isWith) {
target = &env->as<WithEnvironmentObject>().object();
} else {
target = env;
}
if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, props)) {
return false;
}
if (isWith) {
size_t j = 0;
bool supportUnscopables =
env->as<WithEnvironmentObject>().supportUnscopables();
for (size_t i = 0; i < props.length(); i++) {
bool inScope = true;
if (supportUnscopables &&
!CheckUnscopables(cx, env, props[i], &inScope)) {
return false;
}
if (inScope) {
props[j++].set(props[i]);
}
}
if (!props.resize(j)) {
return false;
}
}
/*
* Environments with Scopes are optimized to not contain unaliased
* variables so they must be manually appended here.
*/
if (Scope* scope = getEnvironmentScope(*env)) {
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
if (!bi.closedOver() &&
!props.append(NameToId(bi.name()->asPropertyName()))) {
return false;
}
}
}
return true;
}
bool has(JSContext* cx, HandleObject proxy, HandleId id_,
bool* bp) const override {
RootedId id(cx, id_);
EnvironmentObject& envObj =
proxy->as<DebugEnvironmentProxy>().environment();
if (isArguments(cx, id) && isFunctionEnvironment(envObj)) {
*bp = true;
return true;
}
// Be careful not to look up '.this' as a normal binding below, it will
// assert in with_HasProperty.
if (isThis(cx, id)) {
*bp = isFunctionEnvironmentWithThis(envObj);
return true;
}
bool found;
RootedObject env(cx, &envObj);
if (!JS_HasPropertyById(cx, env, id, &found)) {
return false;
}
if (!found) {
if (Scope* scope = getEnvironmentScope(*env)) {
for (BindingIter bi(scope); bi; bi++) {
if (!bi.closedOver() && NameToId(bi.name()->asPropertyName()) == id) {
found = true;
break;
}
}
}
}
*bp = found;
return true;
}
bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
ObjectOpResult& result) const override {
return result.fail(JSMSG_CANT_DELETE);
}
};
} /* anonymous namespace */
Scope* js::GetEnvironmentScope(const JSObject& env) {
return DebugEnvironmentProxyHandler::getEnvironmentScope(env);
}
template <>
bool JSObject::is<js::DebugEnvironmentProxy>() const {
return IsDerivedProxyObject(this, &DebugEnvironmentProxyHandler::singleton);
}
const char DebugEnvironmentProxyHandler::family = 0;
const DebugEnvironmentProxyHandler DebugEnvironmentProxyHandler::singleton;
/* static */
DebugEnvironmentProxy* DebugEnvironmentProxy::create(JSContext* cx,
EnvironmentObject& env,
HandleObject enclosing) {
MOZ_ASSERT(env.realm() == cx->realm());
MOZ_ASSERT(!enclosing->is<EnvironmentObject>());
RootedValue priv(cx, ObjectValue(env));
JSObject* obj = NewProxyObject(cx, &DebugEnvironmentProxyHandler::singleton,
priv, nullptr /* proto */);
if (!obj) {
return nullptr;
}
DebugEnvironmentProxy* debugEnv = &obj->as<DebugEnvironmentProxy>();
debugEnv->setReservedSlot(ENCLOSING_SLOT, ObjectValue(*enclosing));
debugEnv->setReservedSlot(SNAPSHOT_SLOT, NullValue());
return debugEnv;
}
EnvironmentObject& DebugEnvironmentProxy::environment() const {
return target()->as<EnvironmentObject>();
}
JSObject& DebugEnvironmentProxy::enclosingEnvironment() const {
return reservedSlot(ENCLOSING_SLOT).toObject();
}
ArrayObject* DebugEnvironmentProxy::maybeSnapshot() const {
JSObject* obj = reservedSlot(SNAPSHOT_SLOT).toObjectOrNull();
return obj ? &obj->as<ArrayObject>() : nullptr;
}
void DebugEnvironmentProxy::initSnapshot(ArrayObject& o) {
#ifdef DEBUG
if (maybeSnapshot()) {
auto* callObj = CallObject::find(&environment());
if (callObj) {
MOZ_ASSERT(callObj->callee().isGeneratorOrAsync());
} else {
auto* moduleEnv = ModuleEnvironmentObject::find(&environment());
MOZ_ASSERT(moduleEnv);
MOZ_ASSERT(moduleEnv->module().hasTopLevelAwait());
}
}
#endif
setReservedSlot(SNAPSHOT_SLOT, ObjectValue(o));
}
bool DebugEnvironmentProxy::isForDeclarative() const {
EnvironmentObject& e = environment();
return e.is<CallObject>() || e.is<VarEnvironmentObject>() ||
e.is<ModuleEnvironmentObject>() ||
e.is<WasmInstanceEnvironmentObject>() ||
e.is<WasmFunctionCallObject>() || e.is<LexicalEnvironmentObject>();
}
/* static */
bool DebugEnvironmentProxy::getMaybeSentinelValue(
JSContext* cx, Handle<DebugEnvironmentProxy*> env, HandleId id,
MutableHandleValue vp) {
return DebugEnvironmentProxyHandler::singleton.getMaybeSentinelValue(cx, env,
id, vp);
}
bool DebugEnvironmentProxy::isFunctionEnvironmentWithThis() {
return DebugEnvironmentProxyHandler::isFunctionEnvironmentWithThis(
environment());
}
bool DebugEnvironmentProxy::isOptimizedOut() const {
EnvironmentObject& e = environment();
if (DebugEnvironments::hasLiveEnvironment(e)) {
return false;
}
if (e.is<LexicalEnvironmentObject>()) {
return e.is<BlockLexicalEnvironmentObject>() &&
!e.as<BlockLexicalEnvironmentObject>().scope().hasEnvironment();
}
if (e.is<CallObject>()) {
return !e.as<CallObject>().callee().needsCallObject() && !maybeSnapshot();
}
return false;
}
/*****************************************************************************/
[[nodiscard]] static bool GetFrameEnvironmentAndScope(
JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc,
MutableHandleObject env, MutableHandle<Scope*> scope);
DebugEnvironments::DebugEnvironments(JSContext* cx, Zone* 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| is checked automatically because it is a WeakMap.
*/
CheckTableAfterMovingGC(missingEnvs, [this](const auto& entry) {
CheckGCThingAfterMovingGC(entry.key().scope(), zone());
// Use unbarrieredGet() to prevent triggering read barrier while collecting.
CheckGCThingAfterMovingGC(entry.value().unbarrieredGet(), zone());
return entry.key();
});
CheckTableAfterMovingGC(liveEnvs, [this](const auto& entry) {
CheckGCThingAfterMovingGC(entry.key(), zone());
CheckGCThingAfterMovingGC(entry.value().scope_.get(), zone());
return entry.key().unbarrieredGet();
});
}
#endif
/*
* Unfortunately, GetDebugEnvironmentForFrame needs to work even outside debug
* mode (in particular, JS_GetFrameScopeChain does not require debug mode).
* Since DebugEnvironments::onPop* are only called in debuggee frames, this
* means we cannot use any of the maps in DebugEnvironments. This will produce
* debug scope chains that do not obey the debugger invariants but that is just
* fine.
*/
static bool CanUseDebugEnvironmentMaps(JSContext* cx) {
return cx->realm()->isDebuggee();
}
DebugEnvironments* DebugEnvironments::ensureRealmData(JSContext* cx) {
Realm* realm = cx->realm();
if (auto* debugEnvs = realm->debugEnvs()) {
return debugEnvs;
}
auto debugEnvs = cx->make_unique<DebugEnvironments>(cx, 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<DebugEnvironmentProxy>();
}
return nullptr;
}
/* static */
bool DebugEnvironments::addDebugEnvironment(
JSContext* cx, Handle<EnvironmentObject*> env,
Handle<DebugEnvironmentProxy*> debugEnv) {
MOZ_ASSERT(cx->realm() == env->realm());
MOZ_ASSERT(cx->realm() == debugEnv->nonCCWRealm());
if (!CanUseDebugEnvironmentMaps(cx)) {
return true;
}
DebugEnvironments* envs = ensureRealmData(cx);
if (!envs) {
return false;
}
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<DebugEnvironmentProxy*> debugEnv) {
MOZ_ASSERT(!ei.hasSyntacticEnvironment());
MOZ_ASSERT(cx->realm() == debugEnv->nonCCWRealm());
if (!CanUseDebugEnvironmentMaps(cx)) {
return true;
}
DebugEnvironments* envs = ensureRealmData(cx);
if (!envs) {
return false;
}
MissingEnvironmentKey key(ei);
MOZ_ASSERT(!envs->missingEnvs.has(key));
if (!envs->missingEnvs.put(key,
WeakHeapPtr<DebugEnvironmentProxy*>(debugEnv))) {
ReportOutOfMemory(cx);
return false;
}
// Only add to liveEnvs if we synthesized the debug env on a live
// frame.
if (ei.withinInitialFrame()) {
MOZ_ASSERT(!envs->liveEnvs.has(&debugEnv->environment()));
if (!envs->liveEnvs.put(&debugEnv->environment(), LiveEnvironmentVal(ei))) {
ReportOutOfMemory(cx);
return false;
}
}
return true;
}
/* static */
void DebugEnvironments::takeFrameSnapshot(
JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
AbstractFramePtr frame) {
/*
* When the JS stack frame is popped, the values of unaliased variables
* are lost. If there is any debug env referring to this environment, save a
* copy of the unaliased variables' values in an array for later debugger
* access via DebugEnvironmentProxy::handleUnaliasedAccess.
*
* Note: since it is simplest for this function to be infallible, failure
* in this code will be silently ignored. This does not break any
* invariants since DebugEnvironmentProxy::maybeSnapshot can already be
* nullptr.
*/
// Because this can be called during exception unwinding, save the exception
// state and restore it when we're done.
JS::AutoSaveExceptionState ases(cx);
JSScript* script = frame.script();
// Act like no snapshot was taken if we run OOM while taking the snapshot.
Rooted<GCVector<Value>> vec(cx, GCVector<Value>(cx));
if (debugEnv->environment().is<CallObject>()) {
FunctionScope* scope = &script->bodyScope()->as<FunctionScope>();
uint32_t frameSlotCount = scope->nextFrameSlot();
MOZ_ASSERT(frameSlotCount <= script->nfixed());
// For simplicity, copy all frame slots from 0 to the frameSlotCount,
// even if we don't need all of them (like in the case of a defaults
// parameter scope having frame slots).
uint32_t numFormals = frame.numFormalArgs();
if (!vec.resize(numFormals + frameSlotCount)) {
cx->recoverFromOutOfMemory();
return;
}
mozilla::PodCopy(vec.begin(), frame.argv(), numFormals);
for (uint32_t slot = 0; slot < frameSlotCount; slot++) {
vec[slot + frame.numFormalArgs()].set(frame.unaliasedLocal(slot));
}
/*
* Copy in formals that are not aliased via the scope chain
* but are aliased via the arguments object.
*/
if (script->needsArgsObj() && frame.hasArgsObj()) {
for (unsigned i = 0; i < frame.numFormalArgs(); ++i) {
if (script->formalLivesInArgumentsObject(i)) {
vec[i].set(frame.argsObj().arg(i));
}
}
}
} else {
uint32_t frameSlotStart;
uint32_t frameSlotEnd;
if (debugEnv->environment().is<BlockLexicalEnvironmentObject>()) {
LexicalScope* scope =
&debugEnv->environment().as<BlockLexicalEnvironmentObject>().scope();
frameSlotStart = scope->firstFrameSlot();
frameSlotEnd = scope->nextFrameSlot();
} else if (debugEnv->environment()
.is<ClassBodyLexicalEnvironmentObject>()) {
ClassBodyScope* scope = &debugEnv->environment()
.as<ClassBodyLexicalEnvironmentObject>()
.scope();
frameSlotStart = scope->firstFrameSlot();
frameSlotEnd = scope->nextFrameSlot();
} else if (debugEnv->environment().is<VarEnvironmentObject>()) {
VarEnvironmentObject* env =
&debugEnv->environment().as<VarEnvironmentObject>();
if (frame.isFunctionFrame()) {
VarScope* scope = &env->scope().as<VarScope>();
frameSlotStart = scope->firstFrameSlot();
frameSlotEnd = scope->nextFrameSlot();
} else {
EvalScope* scope = &env->scope().as<EvalScope>();
MOZ_ASSERT(scope == script->bodyScope());
frameSlotStart = 0;
frameSlotEnd = scope->nextFrameSlot();
}
} else {
MOZ_ASSERT(&debugEnv->environment().as<ModuleEnvironmentObject>() ==
script->module()->environment());
ModuleScope* scope = &script->bodyScope()->as<ModuleScope>();
frameSlotStart = 0;
frameSlotEnd = scope->nextFrameSlot();
}
uint32_t frameSlotCount = frameSlotEnd - frameSlotStart;
MOZ_ASSERT(frameSlotCount <= script->nfixed());
if (!vec.resize(frameSlotCount)) {
cx->recoverFromOutOfMemory();
return;
}
for (uint32_t slot = frameSlotStart; slot < frameSlotCount; slot++) {
vec[slot - frameSlotStart].set(frame.unaliasedLocal(slot));
}
}
if (vec.length() == 0) {
return;
}
/*
* Use a dense array as storage (since proxies do not have trace
* hooks). This array must not escape into the wild.
*/
Rooted<ArrayObject*> snapshot(
cx, NewDenseCopiedArray(cx, vec.length(), vec.begin()));
if (!snapshot) {
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<DebugEnvironmentProxy*> debugEnv(cx, nullptr);
FunctionScope* funScope = &frame.script()->bodyScope()->as<FunctionScope>();
if (funScope->hasEnvironment()) {
MOZ_ASSERT(frame.callee()->needsCallObject());
/*
* The frame may be observed before the prologue has created the
* CallObject. See EnvironmentIter::settle.
*/
if (!frame.environmentChain()->is<CallObject>()) {
return;
}
CallObject& callobj = frame.environmentChain()->as<CallObject>();
envs->liveEnvs.remove(&callobj);
if (JSObject* obj = envs->proxiedEnvs.lookup(&callobj)) {
debugEnv = &obj->as<DebugEnvironmentProxy>();
}
} else {
MissingEnvironmentKey key(frame, funScope);
if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(key)) {
debugEnv = p->value();
envs->liveEnvs.remove(&debugEnv->environment().as<CallObject>());
envs->missingEnvs.remove(p);
}
}
if (debugEnv) {
DebugEnvironments::takeFrameSnapshot(cx, debugEnv, frame);
}
}
void DebugEnvironments::onPopLexical(JSContext* cx, AbstractFramePtr frame,
const jsbytecode* pc) {
cx->check(frame);
DebugEnvironments* envs = cx->realm()->debugEnvs();
if (!envs) {
return;
}
EnvironmentIter ei(cx, frame, pc);
onPopLexical(cx, ei);
}
template <typename Environment, typename Scope>
void DebugEnvironments::onPopGeneric(JSContext* cx, const EnvironmentIter& ei) {
DebugEnvironments* envs = cx->realm()->debugEnvs();
if (!envs) {
return;
}
MOZ_ASSERT(ei.withinInitialFrame());
MOZ_ASSERT(ei.scope().is<Scope>());
Rooted<Environment*> env(cx);
if (MissingEnvironmentMap::Ptr p =
envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) {
env = &p->value()->environment().as<Environment>();
envs->missingEnvs.remove(p);
} else if (ei.hasSyntacticEnvironment()) {
env = &ei.environment().as<Environment>();
}
if (env) {
envs->liveEnvs.remove(env);
if (JSObject* obj = envs->proxiedEnvs.lookup(env)) {
Rooted<DebugEnvironmentProxy*> debugEnv(
cx, &obj->as<DebugEnvironmentProxy>());
DebugEnvironments::takeFrameSnapshot(cx, debugEnv, ei.initialFrame());
}
}
}
void DebugEnvironments::onPopLexical(JSContext* cx, const EnvironmentIter& ei) {
if (ei.scope().is<ClassBodyScope>()) {
onPopGeneric<ScopedLexicalEnvironmentObject, ClassBodyScope>(cx, ei);
} else {
onPopGeneric<ScopedLexicalEnvironmentObject, LexicalScope>(cx, ei);
}
}
void DebugEnvironments::onPopVar(JSContext* cx, const EnvironmentIter& ei) {
if (ei.scope().is<EvalScope>()) {
onPopGeneric<VarEnvironmentObject, EvalScope>(cx, ei);
} else {
onPopGeneric<VarEnvironmentObject, VarScope>(cx, ei);
}
}
void DebugEnvironments::onPopWith(AbstractFramePtr frame) {
Realm* realm = frame.realm();
if (DebugEnvironments* envs = realm->debugEnvs()) {
envs->liveEnvs.remove(
&frame.environmentChain()->as<WithEnvironmentObject>());
}
}
void DebugEnvironments::onPopModule(JSContext* cx, const EnvironmentIter& ei) {
onPopGeneric<ModuleEnvironmentObject, ModuleScope>(cx, ei);
}
void DebugEnvironments::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*> 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<GlobalScope>()) {
MOZ_ASSERT(ei.environment().realm() == cx->realm());
DebugEnvironments* envs = ensureRealmData(cx);
if (!envs) {
return false;
}
if (!envs->liveEnvs.put(&ei.environment(), LiveEnvironmentVal(ei))) {
ReportOutOfMemory(cx);
return false;
}
}
}
if (frame.prevUpToDate()) {
return true;
}
MOZ_ASSERT(frame.realm()->isDebuggee());
frame.setPrevUpToDate();
}
return true;
}
LiveEnvironmentVal* DebugEnvironments::hasLiveEnvironment(
EnvironmentObject& env) {
DebugEnvironments* envs = env.realm()->debugEnvs();
if (!envs) {
return nullptr;
}
if (LiveEnvironmentMap::Ptr p = envs->liveEnvs.lookup(&env)) {
return &p->value();
}
return nullptr;
}
/* static */
void DebugEnvironments::unsetPrevUpToDateUntil(JSContext* cx,
AbstractFramePtr until) {
// This are two exceptions where fp->prevUpToDate() is cleared without
// popping the frame. When a frame is rematerialized or has its
// debuggeeness toggled off->on, all frames younger than the frame must
// have their prevUpToDate set to false. This is because unrematerialized
// Ion frames and non-debuggee frames are skipped by updateLiveEnvironments.
// If in the future a frame suddenly gains a usable AbstractFramePtr via
// rematerialization or becomes a debuggee, the prevUpToDate invariant
// will no longer hold for older frames on its stack.
for (AllFramesIter i(cx); !i.done(); ++i) {
if (!i.hasUsableAbstractFramePtr()) {
continue;
}
AbstractFramePtr frame = i.abstractFramePtr();
if (frame == until) {
return;
}
if (frame.realm() != cx->realm()) {
continue;
}
frame.unsetPrevUpToDate();
}
}
/* static */
void DebugEnvironments::forwardLiveFrame(JSContext* cx, AbstractFramePtr from,
AbstractFramePtr to) {
DebugEnvironments* envs = cx->realm()->debugEnvs();
if (!envs) {
return;
}
for (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<EnvironmentObject*> env(cx, &ei.environment());
if (DebugEnvironmentProxy* debugEnv =
DebugEnvironments::hasDebugEnvironment(cx, *env)) {
return debugEnv;
}
EnvironmentIter copy(cx, ei);
RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
if (!enclosingDebug) {
return nullptr;
}
Rooted<DebugEnvironmentProxy*> debugEnv(
cx, DebugEnvironmentProxy::create(cx, *env, enclosingDebug));
if (!debugEnv) {
return nullptr;
}
if (!DebugEnvironments::addDebugEnvironment(cx, env, debugEnv)) {
return nullptr;
}
return debugEnv;
}
static DebugEnvironmentProxy* GetDebugEnvironmentForMissing(
JSContext* cx, const EnvironmentIter& ei) {
MOZ_ASSERT(!ei.hasSyntacticEnvironment() &&
(ei.scope().is<FunctionScope>() || ei.scope().is<LexicalScope>() ||
ei.scope().is<WasmInstanceScope>() ||
ei.scope().is<WasmFunctionScope>() || ei.scope().is<VarScope>() ||
ei.scope().kind() == ScopeKind::StrictEval));
if (DebugEnvironmentProxy* debugEnv =
DebugEnvironments::hasDebugEnvironment(cx, ei)) {
return debugEnv;
}
EnvironmentIter copy(cx, ei);
RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
if (!enclosingDebug) {
return nullptr;
}
/*
* Create the missing environment object. For lexical environment objects,
* this takes care of storing variable values after the stack frame has
* been popped. For call objects, we only use the pretend call object to
* access callee, bindings and to receive dynamically added
* properties. Together, this provides the nice invariant that every
* DebugEnvironmentProxy has a EnvironmentObject.
*
* Note: to preserve envChain depth invariants, these lazily-reified
* envs must not be put on the frame's environment chain; instead, they are
* maintained via DebugEnvironments hooks.
*/
Rooted<DebugEnvironmentProxy*> debugEnv(cx);
if (ei.scope().is<FunctionScope>()) {
RootedFunction callee(cx,
ei.scope().as<FunctionScope>().canonicalFunction());
JS::ExposeObjectToActiveJS(callee);
Rooted<CallObject*> callobj(cx,
CallObject::createHollowForDebug(cx, callee));
if (!callobj) {
return nullptr;
}
debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug);
} else if (ei.scope().is<LexicalScope>()) {
Rooted<LexicalScope*> lexicalScope(cx, &ei.scope().as<LexicalScope>());
Rooted<BlockLexicalEnvironmentObject*> env(
cx,
BlockLexicalEnvironmentObject::createHollowForDebug(cx, lexicalScope));
if (!env) {
return nullptr;
}
debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
} else if (ei.scope().is<WasmInstanceScope>()) {
Rooted<WasmInstanceScope*> wasmInstanceScope(
cx, &ei.scope().as<WasmInstanceScope>());
Rooted<WasmInstanceEnvironmentObject*> env(
cx, WasmInstanceEnvironmentObject::createHollowForDebug(
cx, wasmInstanceScope));
if (!env) {
return nullptr;
}
debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
} else if (ei.scope().is<WasmFunctionScope>()) {
Rooted<WasmFunctionScope*> wasmFunctionScope(
cx, &ei.scope().as<WasmFunctionScope>());
RootedObject enclosing(
cx, &enclosingDebug->as<DebugEnvironmentProxy>().environment());
Rooted<WasmFunctionCallObject*> callobj(
cx, WasmFunctionCallObject::createHollowForDebug(cx, enclosing,
wasmFunctionScope));
if (!callobj) {
return nullptr;
}
debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug);
} else {
Rooted<Scope*> scope(cx, &ei.scope());
MOZ_ASSERT(scope->is<VarScope>() || scope->kind() == ScopeKind::StrictEval);
Rooted<VarEnvironmentObject*> env(
cx, VarEnvironmentObject::createHollowForDebug(cx, scope));
if (!env) {
return nullptr;
}
debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
}
if (!debugEnv) {
return nullptr;
}
if (!DebugEnvironments::addDebugEnvironment(cx, ei, debugEnv)) {
return nullptr;
}
return debugEnv;
}
static JSObject* GetDebugEnvironmentForNonEnvironmentObject(
const EnvironmentIter& ei) {
JSObject& enclosing = ei.enclosingEnvironment();
#ifdef DEBUG
JSObject* o = &enclosing;
while ((o = o->enclosingEnvironment())) {
MOZ_ASSERT(!o->is<EnvironmentObject>());
}
#endif
return &enclosing;
}
static JSObject* GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei) {
AutoCheckRecursionLimit recursion(cx);
if (!recursion.check(cx)) {
return nullptr;
}
if (ei.done()) {
return GetDebugEnvironmentForNonEnvironmentObject(ei);
}
if (ei.hasAnyEnvironmentObject()) {
return GetDebugEnvironmentForEnvironmentObject(cx, ei);
}
if (ei.scope().is<FunctionScope>() || ei.scope().is<LexicalScope>() ||
ei.scope().is<WasmInstanceScope>() ||
ei.scope().is<WasmFunctionScope>() || ei.scope().is<VarScope>() ||
ei.scope().kind() == ScopeKind::StrictEval) {
return GetDebugEnvironmentForMissing(cx, ei);
}
EnvironmentIter copy(cx, ei);
return GetDebugEnvironment(cx, ++copy);
}
JSObject* js::GetDebugEnvironmentForFunction(JSContext* cx,
HandleFunction fun) {
cx->check(fun);
MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
if (!DebugEnvironments::updateLiveEnvironments(cx)) {
return nullptr;
}
JSScript* script = JSFunction::getOrCreateScript(cx, fun);
if (!script) {
return nullptr;
}
EnvironmentIter ei(cx, fun->environment(), script->enclosingScope());
return GetDebugEnvironment(cx, ei);
}
static auto GetSuspendedGeneratorEnvironmentAndScope(
AbstractGeneratorObject& genObj, JSScript* script) {
jsbytecode* pc =
script->offsetToPC(script->resumeOffsets()[genObj.resumeIndex()]);
return std::pair{&genObj.environmentChain(), script->innermostScope(pc)};
}
JSObject* js::GetDebugEnvironmentForSuspendedGenerator(
JSContext* cx, JSScript* script, AbstractGeneratorObject& genObj) {
auto [env, scope] = GetSuspendedGeneratorEnvironmentAndScope(genObj, script);
EnvironmentIter ei(cx, env, scope);
return GetDebugEnvironment(cx, ei);
}
JSObject* js::GetDebugEnvironmentForFrame(JSContext* cx, AbstractFramePtr frame,
jsbytecode* pc) {
cx->check(frame);
if (CanUseDebugEnvironmentMaps(cx) &&
!DebugEnvironments::updateLiveEnvironments(cx)) {
return nullptr;
}
RootedObject env(cx);
Rooted<Scope*> scope(cx);
if (!GetFrameEnvironmentAndScope(cx, frame, pc, &env, &scope)) {
return nullptr;
}
EnvironmentIter ei(cx, env, scope, frame);
return GetDebugEnvironment(cx, ei);
}
JSObject* js::GetDebugEnvironmentForGlobalLexicalEnvironment(JSContext* cx) {
EnvironmentIter ei(cx, &cx->global()->lexicalEnvironment(),
&cx->global()->emptyGlobalScope());
return GetDebugEnvironment(cx, ei);
}
WithEnvironmentObject* js::CreateObjectsForEnvironmentChain(
JSContext* cx, const JS::EnvironmentChain& chain,
HandleObject terminatingEnv) {
MOZ_ASSERT(!chain.empty());
#ifdef DEBUG
for (size_t i = 0; i < chain.length(); ++i) {
cx->check(chain.chain()[i]);
MOZ_ASSERT(!chain.chain()[i]->isUnqualifiedVarObj());
}
#endif
// Construct With object wrappers for the things on this environment chain
// and use the result as the thing to scope the function to.
Rooted<WithEnvironmentObject*> withEnv(cx);
RootedObject enclosingEnv(cx, terminatingEnv);
for (size_t i = chain.length(); i > 0;) {
withEnv = WithEnvironmentObject::createNonSyntactic(
cx, chain.chain()[--i], enclosingEnv, chain.supportUnscopables());
if (!withEnv) {
return nullptr;
}
enclosingEnv = withEnv;
}
return withEnv;
}
JSObject& WithEnvironmentObject::object() const {
return getReservedSlot(OBJECT_SLOT).toObject();
}
JSObject* WithEnvironmentObject::withThis() const {
JSObject* obj = &getReservedSlot(THIS_SLOT).toObject();
// Windows must never be exposed to script. WithEnvironmentObject::create
// should have set this to the WindowProxy.
MOZ_ASSERT(!IsWindow(obj));
return obj;
}
bool WithEnvironmentObject::isSyntactic() const {
Value v = getReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT);
MOZ_ASSERT(v.isPrivateGCThing() || v.isBoolean());
return v.isPrivateGCThing();
}
bool WithEnvironmentObject::supportUnscopables() const {
if (isSyntactic()) {
return true;
}
Value v = getReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT);
MOZ_ASSERT(v.isBoolean());
return v.isTrue();
}
WithScope& WithEnvironmentObject::scope() const {
MOZ_ASSERT(isSyntactic());
Value v = getReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT);
return *static_cast<WithScope*>(v.toGCThing());
}
ModuleEnvironmentObject* js::GetModuleEnvironmentForScript(JSScript* script) {
return GetModuleObjectForScript(script)->environment();
}
ModuleObject* js::GetModuleObjectForScript(JSScript* script) {
for (ScopeIter si(script); si; si++) {
if (si.kind() == ScopeKind::Module) {
return si.scope()->as<ModuleScope>().module();
}
}
MOZ_CRASH("No module scope found for script");
}
static bool GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut(
JSContext* cx, const EnvironmentIter& originalIter, HandleObject envChain,
const jsbytecode* pc, MutableHandleValue res) {
for (EnvironmentIter ei(cx, originalIter); ei; ei++) {
if (ei.scope().kind() == ScopeKind::Module) {
res.setUndefined();
return true;
}
if (!ei.scope().is<FunctionScope>() ||
ei.scope().as<FunctionScope>().canonicalFunction()->hasLexicalThis()) {
continue;
}
RootedScript script(cx, ei.scope().as<FunctionScope>().script());
if (ei.withinInitialFrame()) {
MOZ_ASSERT(pc, "must have PC if there is an initial frame");
// Figure out if we executed JSOp::FunctionThis and set it.
bool executedInitThisOp = false;
if (script->functionHasThisBinding()) {
for (const BytecodeLocation& loc : js::AllBytecodesIterable(script)) {
if (loc.getOp() == JSOp::FunctionThis) {
// The next op after JSOp::FunctionThis always sets it.
executedInitThisOp = pc > GetNextPc(loc.toRawBytecode());
break;
}
}
}
if (!executedInitThisOp) {
AbstractFramePtr initialFrame = ei.initialFrame();
// Either we're yet to initialize the this-binding
// (JSOp::FunctionThis), or the script does not have a this-binding
// (because it doesn't use |this|).
// If our this-argument is an object, or we're in strict mode,
// the this-binding is always the same as our this-argument.
if (initialFrame.thisArgument().isObject() || script->strict()) {
res.set(initialFrame.thisArgument());
return true;
}
// We didn't initialize the this-binding yet. Determine the
// correct |this| value for this frame (box primitives if not
// in strict mode), and assign it to the this-argument slot so
// JSOp::FunctionThis will use it and not box a second time.
if (!GetFunctionThis(cx, initialFrame, res)) {
return false;
}
initialFrame.thisArgument() = res;
return true;
}
}
if (!script->functionHasThisBinding()) {
res.setMagic(JS_OPTIMIZED_OUT);
return true;
}
for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
if (bi.name() != cx->names().dot_this_) {
continue;
}
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment) {
RootedObject callObj(cx, &ei.environment().as<CallObject>());
return GetProperty(cx, callObj, callObj, bi.name()->asPropertyName(),
res);
}
if (loc.kind() == BindingLocation::Kind::Frame) {
if (ei.withinInitialFrame()) {
res.set(ei.initialFrame().unaliasedLocal(loc.slot()));
return true;
}
if (ei.hasAnyEnvironmentObject()) {
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, envChain, res);
return true;
}
bool js::GetThisValueForDebuggerFrameMaybeOptimizedOut(JSContext* cx,
AbstractFramePtr frame,
const jsbytecode* pc,
MutableHandleValue res) {
RootedObject envChain(cx);
Rooted<Scope*> scope(cx);
if (!GetFrameEnvironmentAndScope(cx, frame, pc, &envChain, &scope)) {
return false;
}
EnvironmentIter ei(cx, envChain, scope, frame);
return GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut(
cx, ei, envChain, pc, res);
}
bool js::GetThisValueForDebuggerSuspendedGeneratorMaybeOptimizedOut(
JSContext* cx, AbstractGeneratorObject& genObj, JSScript* script,
MutableHandleValue res) {
auto [env, scope] = GetSuspendedGeneratorEnvironmentAndScope(genObj, script);
Rooted<JSObject*> envChain(cx, env);
EnvironmentIter ei(cx, envChain, scope);
return GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut(
cx, ei, envChain, nullptr, res);
}
static void ReportRuntimeRedeclaration(JSContext* cx,
Handle<PropertyName*> name,
const char* redeclKind) {
if (UniqueChars printable = AtomToPrintableString(cx, name)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_REDECLARED_VAR, redeclKind,
printable.get());
}
}
[[nodiscard]] static bool CheckLexicalNameConflict(
JSContext* cx, Handle<ExtensibleLexicalEnvironmentObject*> lexicalEnv,
HandleObject varObj, Handle<PropertyName*> name) {
const char* redeclKind = nullptr;
RootedId id(cx, NameToId(name));
mozilla::Maybe<PropertyInfo> prop;
bool shadowsExistingProperty = false;
if ((prop = lexicalEnv->lookup(cx, name))) {
// ES 15.1.11 step 5.b
redeclKind = prop->writable() ? "let" : "const";
} else if (varObj->is<NativeObject>() &&
(prop = varObj->as<NativeObject>().lookup(cx, name))) {
// Faster path for ES 15.1.11 step 5.c-d when the shape can be found
// without going through a resolve hook.
if (!prop->configurable()) {
redeclKind = "non-configurable global property";
} else {
shadowsExistingProperty = true;
}
} else {
// ES 15.1.11 step 5.c-d
Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc)) {
return false;
}
if (desc.isSome()) {
if (!desc->configurable()) {
redeclKind = "non-configurable global property";
} else {
shadowsExistingProperty = true;
}
}
}
if (redeclKind) {
ReportRuntimeRedeclaration(cx, name, redeclKind);
return false;
}
if (shadowsExistingProperty && varObj->is<GlobalObject>()) {
// Shadowing a configurable global property with a new lexical is one
// of the rare ways to invalidate a GetGName stub.
varObj->as<GlobalObject>().bumpGenerationCount();
}
return true;
}
[[nodiscard]] static bool CheckVarNameConflict(
JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
Handle<PropertyName*> name) {
mozilla::Maybe<PropertyInfo> prop = lexicalEnv->lookup(cx, name);
if (prop.isSome()) {
ReportRuntimeRedeclaration(cx, name, prop->writable() ? "let" : "const");
return false;
}
return true;
}
static void ReportCannotDeclareGlobalBinding(JSContext* cx,
Handle<PropertyName*> name,
const char* reason) {
if (UniqueChars printable = AtomToPrintableString(cx, name)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CANT_DECLARE_GLOBAL_BINDING,
printable.get(), reason);
}
}
[[nodiscard]] static bool CheckCanDeclareGlobalBinding(
JSContext* cx, Handle<GlobalObject*> global, Handle<PropertyName*> name,
bool isFunction) {
RootedId id(cx, NameToId(name));
Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
if (!GetOwnPropertyDescriptor(cx, global, id, &desc)) {
return false;
}
// ES 8.1.1.4.15 CanDeclareGlobalVar
// ES 8.1.1.4.16 CanDeclareGlobalFunction
// Step 4.
if (desc.isNothing()) {
// 8.1.14.15 step 6.
// 8.1.14.16 step 5.
if (global->isExtensible()) {
return true;
}
ReportCannotDeclareGlobalBinding(cx, name, "global is non-extensible");
return false;
}
// Global functions have additional restrictions.
if (isFunction) {
// 8.1.14.16 step 6.
if (desc->configurable()) {
return true;
}
// 8.1.14.16 step 7.
if (desc->isDataDescriptor() && desc->writable() && desc->enumerable()) {
return true;
}
ReportCannotDeclareGlobalBinding(cx, name,
"property must be configurable or "
"both writable and enumerable");
return false;
}
return true;
}
// Add the var/let/const bindings to the variables environment of a global or
// sloppy-eval script. The redeclaration checks should already have been
// performed.
static bool InitGlobalOrEvalDeclarations(
JSContext* cx, HandleScript script,
Handle<ExtensibleLexicalEnvironmentObject*> lexicalEnv,
HandleObject varObj) {
Rooted<BindingIter> bi(cx, BindingIter(script));
for (; bi; bi++) {
if (bi.isTopLevelFunction()) {
continue;
}
Rooted<PropertyName*> 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<GlobalObject>())) {
if (!DefineDataProperty(cx, varObj, name, UndefinedHandleValue,
attrs)) {
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<js::Scope>()) {
MOZ_ASSERT(i < 2);
continue;
}
RootedFunction fun(cx, &thing.as<JSObject>().as<JSFunction>());
Rooted<PropertyName*> name(cx, fun->fullExplicitName()->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;
}
// Done processing this function.
continue;
}
/*
* A DebugEnvironmentProxy is okay here, and sometimes necessary. If
* Debugger.Frame.prototype.eval defines a function with the same name as an
* extant variable in the frame, the DebugEnvironmentProxy takes care of
* storing the function in the stack frame (for non-aliased variables) or on
* the scope object (for aliased).
*/
MOZ_ASSERT(varObj->is<NativeObject>() ||
varObj->is<DebugEnvironmentProxy>());
if (varObj->is<GlobalObject>()) {
PropertyInfo propInfo = prop.propertyInfo();
if (propInfo.configurable()) {
if (!DefineDataProperty(cx, varObj, name, rval, attrs)) {
return false;
}
} else {
MOZ_ASSERT(propInfo.isDataProperty());
MOZ_ASSERT(propInfo.writable());
MOZ_ASSERT(propInfo.enumerable());
}
}
/*
* Non-global properties, and global properties which we aren't simply
* redefining, must be set. First, this preserves their attributes.
* Second, this will produce warnings and/or errors as necessary if the
* specified Call object property is not writable (const).
*/
RootedId id(cx, NameToId(name));
if (!PutProperty(cx, varObj, id, rval, script->strict())) {
return false;
}
}
return true;
}
[[nodiscard]] static bool CheckGlobalDeclarationConflicts(
JSContext* cx, HandleScript script,
Handle<ExtensibleLexicalEnvironmentObject*> lexicalEnv,
HandleObject varObj) {
// Due to the extensibility of the global lexical environment, we must
// check for redeclaring a binding.
//
// In the case of non-syntactic environment chains, we are checking
// redeclarations against the non-syntactic lexical environment and the
// variables object that the lexical environment corresponds to.
Rooted<PropertyName*> name(cx);
Rooted<BindingIter> bi(cx, BindingIter(script));
// ES 15.1.11 GlobalDeclarationInstantiation
// Step 6.
//
// Check 'var' declarations do not conflict with existing bindings in the
// global lexical environment.
for (; bi; bi++) {
if (bi.kind() != BindingKind::Var) {
break;
}
name = bi.name()->asPropertyName();
if (!CheckVarNameConflict(cx, lexicalEnv, name)) {
return false;
}
// Step 10 and 12.
//
// Check that global functions and vars may be declared.
if (varObj->is<GlobalObject>()) {
Handle<GlobalObject*> global = varObj.as<GlobalObject>();
if (!CheckCanDeclareGlobalBinding(cx, global, name,
bi.isTopLevelFunction())) {
return false;
}
}
}
// Step 5.
//
// Check that lexical bindings do not conflict.
for (; bi; bi++) {
name = bi.name()->asPropertyName();
if (!CheckLexicalNameConflict(cx, lexicalEnv, varObj, name)) {
return false;
}
}
return true;
}
[[nodiscard]] static bool CheckVarNameConflictsInEnv(JSContext* cx,
HandleScript script,
HandleObject obj) {
Rooted<LexicalEnvironmentObject*> env(cx);
if (obj->is<LexicalEnvironmentObject>()) {
env = &obj->as<LexicalEnvironmentObject>();
} else if (obj->is<DebugEnvironmentProxy>() &&
obj->as<DebugEnvironmentProxy>()
.environment()
.is<LexicalEnvironmentObject>()) {
env = &obj->as<DebugEnvironmentProxy>()
.environment()
.as<LexicalEnvironmentObject>();
} else {
// Environment cannot contain lexical bindings.
return true;
}
if (env->is<BlockLexicalEnvironmentObject>() &&
env->as<BlockLexicalEnvironmentObject>().scope().kind() ==
ScopeKind::SimpleCatch) {
// Annex B.3.5 allows redeclaring simple (non-destructured) catch parameters
// with var declarations.
return true;
}
Rooted<PropertyName*> name(cx);
for (BindingIter bi(script); bi; bi++) {
name = bi.name()->asPropertyName();
if (!CheckVarNameConflict(cx, env, name)) {
return false;
}
}
return true;
}
static bool CheckArgumentsRedeclaration(JSContext* cx, HandleScript script) {
for (BindingIter bi(script); bi; bi++) {
if (bi.name() == cx->names().arguments) {
ReportRuntimeRedeclaration(cx, cx->names().arguments, "let");
return false;
}
}
return true;
}
static bool CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script,
HandleObject envChain,
HandleObject varObj) {
// Strict eval has its own call objects and we shouldn't end up here.
//
// Non-strict eval may introduce 'var' bindings that conflict with lexical
// bindings in an enclosing lexical scope.
MOZ_ASSERT(!script->bodyScope()->hasEnvironment());
MOZ_ASSERT(!script->strict());
MOZ_ASSERT(script->bodyScope()->as<EvalScope>().hasBindings());
RootedObject env(cx, envChain);
// ES 18.2.1.3.
// Step 5.
//
// Check that a direct eval will not hoist 'var' bindings over lexical
// bindings with the same name.
while (env != varObj) {
if (!CheckVarNameConflictsInEnv(cx, script, env)) {
return false;
}
env = env->enclosingEnvironment();
}
// Check for redeclared "arguments" in function parameter expressions.
//
// Direct eval in function parameter expressions isn't allowed to redeclare
// the implicit "arguments" bindings:
// function f(a = eval("var arguments;")) {}
//
// |varObj| isn't a CallObject when the direct eval occurs in the function
// body and the extra function body var scope is present. The extra var scope
// is present iff the function has parameter expressions. So when we test
// that |varObj| is a CallObject and function parameter expressions are
// present, we can pinpoint the direct eval location to be in a function
// parameter expression. Additionally we must ensure the function isn't an
// arrow function, because arrow functions don't have an implicit "arguments"
// binding.
if (script->isDirectEvalInFunction() && varObj->is<CallObject>()) {
JSFunction* fun = &varObj->as<CallObject>().callee();
JSScript* funScript = fun->nonLazyScript();
if (funScript->functionHasParameterExprs() && !fun->isArrow()) {
if (!CheckArgumentsRedeclaration(cx, script)) {
return false;
}
}
}
// Step 8.
//
// Check that global functions may be declared.
if (varObj->is<GlobalObject>()) {
Handle<GlobalObject*> global = varObj.as<GlobalObject>();
Rooted<PropertyName*> name(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
name = bi.name()->asPropertyName();
if (!CheckCanDeclareGlobalBinding(cx, global, name,
bi.isTopLevelFunction())) {
return false;
}
}
}
return true;
}
bool js::GlobalOrEvalDeclInstantiation(JSContext* cx, HandleObject envChain,
HandleScript script,
GCThingIndex lastFun) {
MOZ_ASSERT(script->isGlobalCode() || script->isForEval());
MOZ_ASSERT(!script->selfHosted());
RootedObject varObj(cx, &GetVariablesObject(envChain));
Rooted<ExtensibleLexicalEnvironmentObject*> lexicalEnv(cx);
if (script->isForEval()) {
if (!CheckEvalDeclarationConflicts(cx, script, envChain, varObj)) {
return false;
}
} else {
lexicalEnv = &NearestEnclosingExtensibleLexicalEnvironment(envChain);
if (!CheckGlobalDeclarationConflicts(cx, script, lexicalEnv, varObj)) {
return false;
}
}
if (!InitGlobalOrEvalDeclarations(cx, script, lexicalEnv, varObj)) {
return false;
}
return InitHoistedFunctionDeclarations(cx, script, envChain, varObj, lastFun);
}
bool js::InitFunctionEnvironmentObjects(JSContext* cx, AbstractFramePtr frame) {
MOZ_ASSERT(frame.isFunctionFrame());
MOZ_ASSERT(frame.callee()->needsFunctionEnvironmentObjects());
RootedFunction callee(cx, frame.callee());
// 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*> scope,
AbstractFramePtr frame) {
auto* env = VarEnvironmentObject::createForFrame(cx, scope, frame);
if (!env) {
return false;
}
frame.pushOnEnvironmentChain(*env);
return true;
}
static bool GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame,
const jsbytecode* pc,
MutableHandleObject env,
MutableHandle<Scope*> scope) {
env.set(frame.environmentChain());
if (frame.isWasmDebugFrame()) {
Rooted<WasmInstanceObject*> instance(cx, frame.wasmInstance()->object());
uint32_t funcIndex = frame.asWasmDebugFrame()->funcIndex();
scope.set(WasmInstanceObject::getFunctionScope(cx, instance, funcIndex));
if (!scope) {
return false;
}
} else {
scope.set(frame.script()->innermostScope(pc));
}
return true;
}
#ifdef DEBUG
using PropertyNameSet = HashSet<PropertyName*>;
static bool RemoveReferencedNames(JSContext* cx, HandleScript script,
PropertyNameSet& remainingNames) {
// Remove from remainingNames --- the closure variables in some outer
// script --- any free variables in this script. This analysis isn't perfect:
//
// - It will not account for free variables in an inner script which are
// actually accessing some name in an intermediate script between the
// inner and outer scripts. This can cause remainingNames to be an
// underapproximation.
//
// - It will not account for new names introduced via eval. This can cause
// remainingNames to be an overapproximation. This would be easy to fix
// but is nice to have as the eval will probably not access these
// these names and putting eval in an inner script is bad news if you
// care about entraining variables unnecessarily.
AllBytecodesIterable iter(script);
for (BytecodeLocation loc : iter) {
PropertyName* name;
switch (loc.getOp()) {
case JSOp::GetName:
case JSOp::SetName:
case JSOp::StrictSetName:
name = script->getName(loc.toRawBytecode());
break;
case JSOp::GetAliasedVar:
case JSOp::SetAliasedVar:
name = EnvironmentCoordinateNameSlow(script, loc.toRawBytecode());
break;
default:
name = nullptr;
break;
}
if (name) {
remainingNames.remove(name);
}
}
RootedFunction fun(cx);
RootedScript innerScript(cx);
for (JS::GCCellPtr gcThing : script->gcthings()) {
if (!gcThing.is<JSObject>()) {
continue;
}
JSObject* obj = &gcThing.as<JSObject>();
if (!obj->is<JSFunction>()) {
continue;
}
fun = &obj->as<JSFunction>();
if (!fun->isInterpreted()) {
continue;
}
innerScript = JSFunction::getOrCreateScript(cx, fun);
if (!innerScript) {
return false;
}
if (!RemoveReferencedNames(cx, innerScript, remainingNames)) {
return false;
}
}
return true;
}
static bool AnalyzeEntrainedVariablesInScript(JSContext* cx,
HandleScript script,
HandleScript innerScript) {
PropertyNameSet remainingNames(cx);
for (BindingIter bi(script); bi; bi++) {
if (bi.closedOver()) {
PropertyName* name = bi.name()->asPropertyName();
PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(name);
if (!p && !remainingNames.add(p, name)) {
return false;
}
}
}
if (!RemoveReferencedNames(cx, innerScript, remainingNames)) {
return false;
}
if (!remainingNames.empty()) {
Sprinter buf(cx);
if (!buf.init()) {
return false;
}
buf.printf("Script ");
if (JSAtom* name = script->function()->fullDisplayAtom()) {
buf.putString(cx, name);
buf.printf(" ");
}
buf.printf("(%s:%u) has variables entrained by ", script->filename(),
script->lineno());
if (JSAtom* name = innerScript->function()->fullDisplayAtom()) {
buf.putString(cx, name);
buf.printf(" ");
}
buf.printf("(%s:%u) ::", innerScript->filename(), innerScript->lineno());
for (PropertyNameSet::Range r = remainingNames.all(); !r.empty();
r.popFront()) {
buf.printf(" ");
buf.putString(cx, r.front());
}
JS::UniqueChars str = buf.release();
if (!str) {
return false;
}
printf("%s\n", str.get());
}
RootedFunction fun(cx);
RootedScript innerInnerScript(cx);
for (JS::GCCellPtr gcThing : script->gcthings()) {
if (!gcThing.is<JSObject>()) {
continue;
}
JSObject* obj = &gcThing.as<JSObject>();
if (!obj->is<JSFunction>()) {
continue;
}
fun = &obj->as<JSFunction>();
if (!fun->isInterpreted()) {
continue;
}
innerInnerScript = JSFunction::getOrCreateScript(cx, fun);
if (!innerInnerScript) {
return false;
}
if (!AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) {
return false;
}
}
return true;
}
// Look for local variables in script or any other script inner to it, which are
// part of the script's call object and are unnecessarily entrained by their own
// inner scripts which do not refer to those variables. An example is:
//
// function foo() {
// var a, b;
// function bar() { return a; }
// function baz() { return b; }
// }
//
// |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|.
bool js::AnalyzeEntrainedVariables(JSContext* cx, HandleScript script) {
RootedFunction fun(cx);
RootedScript innerScript(cx);
for (JS::GCCellPtr gcThing : script->gcthings()) {
if (!gcThing.is<JSObject>()) {
continue;
}
JSObject* obj = &gcThing.as<JSObject>();
if (!obj->is<JSFunction>()) {
continue;
}
fun = &obj->as<JSFunction>();
if (!fun->isInterpreted()) {
continue;
}
innerScript = JSFunction::getOrCreateScript(cx, fun);
if (!innerScript) {
return false;
}
if (fun->needsCallObject()) {
if (!AnalyzeEntrainedVariablesInScript(cx, script, innerScript)) {
return false;
}
}
if (!AnalyzeEntrainedVariables(cx, innerScript)) {
return false;
}
}
return true;
}
#endif
JSObject* js::MaybeOptimizeBindUnqualifiedGlobalName(GlobalObject* global,
PropertyName* name) {
// We can bind name to the global lexical scope if the binding already
// exists, is initialized, and is writable (i.e., an initialized
// 'let') at compile time.
GlobalLexicalEnvironmentObject* env = &global->lexicalEnvironment();
mozilla::Maybe<PropertyInfo> prop = env->lookupPure(name);
if (prop.isSome()) {
if (prop->writable() &&
!env->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
return env;
}
return nullptr;
}
prop = global->lookupPure(name);
if (prop.isSome()) {
// If the property does not currently exist on the global lexical
// scope, we can bind name to the global object if the property
// exists on the global and is non-configurable, as then it cannot
// be shadowed.
if (!prop->configurable()) {
return global;
}
}
return nullptr;
}
const char* EnvironmentObject::typeString() const {
if (is<CallObject>()) {
return "CallObject";
}
if (is<VarEnvironmentObject>()) {
return "VarEnvironmentObject";
}
if (is<ModuleEnvironmentObject>()) {
return "ModuleEnvironmentObject";
}
if (is<WasmInstanceEnvironmentObject>()) {
return "WasmInstanceEnvironmentObject";
}
if (is<WasmFunctionCallObject>()) {
return "WasmFunctionCallObject";
}
if (is<LexicalEnvironmentObject>()) {
if (is<ScopedLexicalEnvironmentObject>()) {
if (is<BlockLexicalEnvironmentObject>()) {
if (is<NamedLambdaObject>()) {
return "NamedLambdaObject";
}
return "BlockLexicalEnvironmentObject";
}
if (is<ClassBodyLexicalEnvironmentObject>()) {
return "ClassBodyLexicalEnvironmentObject";
}
return "ScopedLexicalEnvironmentObject";
}
if (is<ExtensibleLexicalEnvironmentObject>()) {
if (is<GlobalLexicalEnvironmentObject>()) {
return "GlobalLexicalEnvironmentObject";
}
if (is<NonSyntacticLexicalEnvironmentObject>()) {
return "NonSyntacticLexicalEnvironmentObject";
}
return "ExtensibleLexicalEnvironmentObject";
}
return "LexicalEnvironmentObject";
}
if (is<NonSyntacticVariablesObject>()) {
return "NonSyntacticVariablesObject";
}
if (is<WithEnvironmentObject>()) {
return "WithEnvironmentObject";
}
if (is<RuntimeLexicalErrorObject>()) {
return "RuntimeLexicalErrorObject";
}
return "EnvironmentObject";
}
#if defined(DEBUG) || defined(JS_JITSPEW)
static void DumpEnvironmentObject(JSObject* unrootedEnvObj) {
JSContext* cx = TlsContext.get();
if (!cx) {
fprintf(stderr, "*** can't get JSContext for current thread\n");
return;
}
Rooted<JSObject*> envObj(cx, unrootedEnvObj);
while (envObj) {
Rooted<EnvironmentObject*> env(cx);
if (envObj->is<EnvironmentObject>()) {
env = &envObj->as<EnvironmentObject>();
} else if (envObj->is<DebugEnvironmentProxy>()) {
fprintf(stderr, "[DebugProxy] ");
env = &envObj->as<DebugEnvironmentProxy>().environment();
} else {
MOZ_ASSERT(envObj->is<GlobalObject>());
fprintf(stderr, "global\n");
break;
}
fprintf(stderr, "%s", env->typeString());
Rooted<Scope*> scope(cx);
if (env->is<VarEnvironmentObject>()) {
scope = &env->as<VarEnvironmentObject>().scope();
}
if (env->is<WasmInstanceEnvironmentObject>()) {
scope = &env->as<WasmInstanceEnvironmentObject>().scope();
} else if (env->is<WasmFunctionCallObject>()) {
scope = &env->as<WasmFunctionCallObject>().scope();
} else if (env->is<ScopedLexicalEnvironmentObject>()) {
scope = &env->as<ScopedLexicalEnvironmentObject>().scope();
}
if (scope) {
fprintf(stderr, " {\n");
for (Rooted<BindingIter> 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<DebugEnvironmentProxy>()) {
envObj = &envObj->as<DebugEnvironmentProxy>().enclosingEnvironment();
} else {
envObj = &env->enclosingEnvironment();
}
if (envObj) {
fprintf(stderr, "-> ");
}
}
}
void EnvironmentObject::dump() { DumpEnvironmentObject(this); }
void DebugEnvironmentProxy::dump() { DumpEnvironmentObject(this); }
#endif /* defined(DEBUG) || defined(JS_JITSPEW) */