Source code

Revision control

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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "vm/EnvironmentObject-inl.h"
#include "builtin/ModuleObject.h"
#include "gc/Policy.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::CheckRecursionLimit
#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy
#include "vm/ArgumentsObject.h"
#include "vm/AsyncFunction.h"
#include "vm/GlobalObject.h"
#include "vm/Iteration.h"
#include "vm/JSObject.h"
#include "vm/ProxyObject.h"
#include "vm/Realm.h"
#include "vm/Shape.h"
#include "vm/Xdr.h"
#include "wasm/WasmInstance.h"
#ifdef DEBUG
# include "vm/BytecodeIterator.h"
# include "vm/BytecodeLocation.h"
#endif
#include "gc/Marking-inl.h"
#include "vm/JSAtom-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectGroup-inl.h" // JSObject::setSingleton
#include "vm/Stack-inl.h"
#include "vm/TypeInference-inl.h"
#ifdef DEBUG
# include "vm/BytecodeIterator-inl.h"
# include "vm/BytecodeLocation-inl.h"
#endif
using namespace js;
using RootedArgumentsObject = Rooted<ArgumentsObject*>;
using MutableHandleArgumentsObject = MutableHandle<ArgumentsObject*>;
/*****************************************************************************/
Shape* js::EnvironmentCoordinateToEnvironmentShape(JSScript* script,
jsbytecode* pc) {
MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD);
ScopeIter si(script->innermostScope(pc));
uint32_t hops = EnvironmentCoordinate(pc).hops();
while (true) {
MOZ_ASSERT(!si.done());
if (si.hasSyntacticEnvironment()) {
if (!hops) {
break;
}
hops--;
}
si++;
}
return si.environmentShape();
}
PropertyName* js::EnvironmentCoordinateNameSlow(JSScript* script,
jsbytecode* pc) {
Shape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc);
EnvironmentCoordinate ec(pc);
Shape::Range<NoGC> r(shape);
while (r.front().slot() != ec.slot()) {
r.popFront();
}
jsid id = r.front().propidRaw();
/* Beware nameless destructuring formal. */
if (!JSID_IS_ATOM(id)) {
return script->runtimeFromAnyThread()->commonNames->empty;
}
return JSID_TO_ATOM(id)->asPropertyName();
}
/*****************************************************************************/
template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, HandleShape shape,
HandleObjectGroup group, gc::InitialHeap heap,
IsSingletonEnv isSingleton) {
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);
JSObject* obj;
JS_TRY_VAR_OR_RETURN_NULL(
cx, obj, NativeObject::create(cx, allocKind, heap, shape, group));
if (isSingleton == IsSingletonEnv::Yes) {
RootedObject objRoot(cx, obj);
if (!JSObject::setSingleton(cx, objRoot)) {
return nullptr;
}
obj = objRoot;
}
return &obj->as<T>();
}
// As above, but the caller does not have to pass the group.
template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, HandleShape shape,
gc::InitialHeap heap,
IsSingletonEnv isSingleton) {
RootedObjectGroup group(
cx, ObjectGroup::defaultNewGroup(cx, &T::class_, TaggedProto(nullptr)));
if (!group) {
return nullptr;
}
return CreateEnvironmentObject<T>(cx, shape, group, heap, isSingleton);
}
// Helper function for simple environment objects that don't need the overloads
// above.
template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, HandleShape shape,
NewObjectKind newKind = GenericObject) {
RootedObjectGroup group(
cx, ObjectGroup::defaultNewGroup(cx, &T::class_, TaggedProto(nullptr)));
if (!group) {
return nullptr;
}
gc::InitialHeap heap = GetInitialHeap(newKind, group);
return CreateEnvironmentObject<T>(cx, shape, group, heap, IsSingletonEnv::No);
}
CallObject* CallObject::create(JSContext* cx, HandleShape shape,
HandleObjectGroup group) {
MOZ_ASSERT(!group->singleton());
gc::InitialHeap heap = GetInitialHeap(GenericObject, group);
return CreateEnvironmentObject<CallObject>(cx, shape, group, heap,
IsSingletonEnv::No);
}
/*
* Create a CallObject for a JSScript that is not initialized to any particular
* callsite. This object can either be initialized (with an enclosing scope and
* callee) or used as a template for jit compilation.
*/
CallObject* CallObject::createTemplateObject(JSContext* cx, HandleScript script,
HandleObject enclosing,
gc::InitialHeap heap) {
Rooted<FunctionScope*> scope(cx, &script->bodyScope()->as<FunctionScope>());
RootedShape shape(cx, scope->environmentShape());
MOZ_ASSERT(shape->getObjectClass() == &class_);
// 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, IsSingletonEnv::No);
if (!callObj) {
return nullptr;
}
callObj->initEnclosingEnvironment(enclosing);
if (scope->hasParameterExprs()) {
// If there are parameter expressions, all parameters are lexical and
// have TDZ.
for (BindingIter bi(script->bodyScope()); bi; bi++) {
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment &&
BindingKindIsLexical(bi.kind())) {
callObj->initSlot(loc.slot(), MagicValue(JS_UNINITIALIZED_LEXICAL));
}
}
}
return callObj;
}
/*
* Construct a call object for the given bindings. If this is a call object
* for a function invocation, callee should be the function being called.
* Otherwise it must be a call object for eval of strict mode code, and callee
* must be null.
*/
CallObject* CallObject::create(JSContext* cx, HandleFunction callee,
HandleObject enclosing) {
RootedScript script(cx, callee->nonLazyScript());
gc::InitialHeap heap = gc::DefaultHeap;
CallObject* callobj =
CallObject::createTemplateObject(cx, script, enclosing, heap);
if (!callobj) {
return nullptr;
}
callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee));
return callobj;
}
CallObject* CallObject::create(JSContext* cx, AbstractFramePtr frame) {
MOZ_ASSERT(frame.isFunctionFrame());
cx->check(frame);
RootedObject envChain(cx, frame.environmentChain());
RootedFunction callee(cx, frame.callee());
CallObject* callobj = create(cx, callee, envChain);
if (!callobj) {
return nullptr;
}
if (!frame.script()->bodyScope()->as<FunctionScope>().hasParameterExprs()) {
// If there are no defaults, copy the aliased arguments into the call
// object manually. If there are defaults, bytecode is generated to do
// the copying.
for (PositionalFormalParameterIter fi(frame.script()); fi; fi++) {
if (!fi.closedOver()) {
continue;
}
callobj->setAliasedBinding(
cx, fi,
frame.unaliasedFormal(fi.argumentSlot(), DONT_CHECK_ALIASING));
}
}
return callobj;
}
CallObject* CallObject::createHollowForDebug(JSContext* cx,
HandleFunction callee) {
MOZ_ASSERT(!callee->needsCallObject());
RootedScript script(cx, callee->nonLazyScript());
Rooted<FunctionScope*> scope(cx, &script->bodyScope()->as<FunctionScope>());
RootedShape shape(cx, EmptyEnvironmentShape<CallObject>(cx));
if (!shape) {
return nullptr;
}
RootedObjectGroup group(
cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
if (!group) {
return nullptr;
}
Rooted<CallObject*> callobj(cx, create(cx, shape, group));
if (!callobj) {
return nullptr;
}
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee));
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, callobj, id, optimizedOut)) {
return nullptr;
}
}
return callobj;
}
const JSClass CallObject::class_ = {
"Call", JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)};
/*****************************************************************************/
/* static */
VarEnvironmentObject* VarEnvironmentObject::create(JSContext* cx,
HandleShape shape,
HandleObject enclosing,
gc::InitialHeap heap) {
MOZ_ASSERT(shape->getObjectClass() == &class_);
auto* env = CreateEnvironmentObject<VarEnvironmentObject>(cx, shape);
if (!env) {
return nullptr;
}
MOZ_ASSERT(!env->inDictionaryMode());
env->initEnclosingEnvironment(enclosing);
return env;
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::create(JSContext* cx,
HandleScope scope,
AbstractFramePtr frame) {
#ifdef DEBUG
if (frame.isEvalFrame()) {
MOZ_ASSERT(scope->is<EvalScope>() && scope == frame.script()->bodyScope());
MOZ_ASSERT_IF(frame.isInterpreterFrame(),
cx->interpreterFrame() == frame.asInterpreterFrame());
MOZ_ASSERT_IF(frame.isInterpreterFrame(),
cx->interpreterRegs().pc == frame.script()->code());
} else {
MOZ_ASSERT(frame.environmentChain());
MOZ_ASSERT_IF(
frame.callee()->needsCallObject(),
&frame.environmentChain()->as<CallObject>().callee() == frame.callee());
}
#endif
RootedScript script(cx, frame.script());
RootedObject envChain(cx, frame.environmentChain());
RootedShape shape(cx, scope->environmentShape());
VarEnvironmentObject* env = create(cx, shape, envChain, gc::DefaultHeap);
if (!env) {
return nullptr;
}
env->initScope(scope);
return env;
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createHollowForDebug(
JSContext* cx, Handle<VarScope*> scope) {
MOZ_ASSERT(!scope->hasEnvironment());
RootedShape shape(cx, 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, create(cx, shape, enclosingEnv, gc::TenuredHeap));
if (!env) {
return nullptr;
}
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, env, id, optimizedOut)) {
return nullptr;
}
}
env->initScope(scope);
return env;
}
const 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, // hasInstance
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, HandleModuleObject module) {
RootedScript script(cx, module->script());
RootedShape shape(cx,
script->bodyScope()->as<ModuleScope>().environmentShape());
MOZ_ASSERT(shape->getObjectClass() == &class_);
RootedModuleEnvironmentObject 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 (Shape::Range<NoGC> r(env->lastProperty()); !r.empty(); r.popFront()) {
MOZ_ASSERT(!r.front().configurable());
}
MOZ_ASSERT(env->lastProperty()->getObjectFlags() & BaseShape::NOT_EXTENSIBLE);
MOZ_ASSERT(!env->inDictionaryMode());
#endif
return env;
}
ModuleObject& ModuleEnvironmentObject::module() const {
return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>();
}
IndirectBindingMap& ModuleEnvironmentObject::importBindings() const {
return module().importBindings();
}
bool ModuleEnvironmentObject::createImportBinding(JSContext* cx,
HandleAtom importName,
HandleModuleObject module,
HandleAtom localName) {
RootedId importNameId(cx, AtomToId(importName));
RootedId localNameId(cx, AtomToId(localName));
RootedModuleEnvironmentObject env(cx, &module->initialEnvironment());
if (!importBindings().put(cx, importNameId, env, localNameId)) {
return false;
}
return true;
}
bool ModuleEnvironmentObject::hasImportBinding(HandlePropertyName name) {
return importBindings().has(NameToId(name));
}
bool ModuleEnvironmentObject::lookupImport(jsid name,
ModuleEnvironmentObject** envOut,
Shape** shapeOut) {
return importBindings().lookup(name, envOut, shapeOut);
}
void ModuleEnvironmentObject::fixEnclosingEnvironmentAfterRealmMerge(
GlobalObject& global) {
setEnclosingEnvironment(&global.lexicalEnvironment());
}
/* static */
bool ModuleEnvironmentObject::lookupProperty(
JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp,
MutableHandle<PropertyResult> propp) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
Shape* shape;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &shape)) {
objp.set(env);
propp.setNativeProperty(shape);
return true;
}
RootedNativeObject target(cx, &obj->as<NativeObject>());
if (!NativeLookupOwnProperty<CanGC>(cx, target, id, propp)) {
return false;
}
objp.set(obj);
return true;
}
/* static */
bool ModuleEnvironmentObject::hasProperty(JSContext* cx, HandleObject obj,
HandleId id, bool* foundp) {
if (obj->as<ModuleEnvironmentObject>().importBindings().has(id)) {
*foundp = true;
return true;
}
RootedNativeObject self(cx, &obj->as<NativeObject>());
return NativeHasProperty(cx, self, id, foundp);
}
/* static */
bool ModuleEnvironmentObject::getProperty(JSContext* cx, HandleObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
Shape* shape;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &shape)) {
vp.set(env->getSlot(shape->slot()));
return true;
}
RootedNativeObject self(cx, &obj->as<NativeObject>());
return NativeGetProperty(cx, self, receiver, id, vp);
}
/* static */
bool ModuleEnvironmentObject::setProperty(JSContext* cx, HandleObject obj,
HandleId id, HandleValue v,
HandleValue receiver,
JS::ObjectOpResult& result) {
RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>());
if (self->importBindings().has(id)) {
return result.failReadOnly();
}
return NativeSetProperty<Qualified>(cx, self, id, v, receiver, result);
}
/* static */
bool ModuleEnvironmentObject::getOwnPropertyDescriptor(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<PropertyDescriptor> desc) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
Shape* shape;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &shape)) {
desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT);
desc.object().set(obj);
RootedValue value(cx, env->getSlot(shape->slot()));
desc.setValue(value);
desc.assertComplete();
return true;
}
RootedNativeObject self(cx, &obj->as<NativeObject>());
return NativeGetOwnPropertyDescriptor(cx, self, id, desc);
}
/* static */
bool ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj,
HandleId id,
ObjectOpResult& result) {
return result.failCantDelete();
}
/* static */
bool ModuleEnvironmentObject::newEnumerate(JSContext* cx, HandleObject obj,
MutableHandleIdVector properties,
bool enumerableOnly) {
RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>());
const IndirectBindingMap& bs(self->importBindings());
MOZ_ASSERT(properties.length() == 0);
size_t count = bs.count() + self->slotSpan() - RESERVED_SLOTS;
if (!properties.reserve(count)) {
ReportOutOfMemory(cx);
return false;
}
bs.forEachExportedName([&](jsid name) { properties.infallibleAppend(name); });
for (Shape::Range<NoGC> r(self->lastProperty()); !r.empty(); r.popFront()) {
properties.infallibleAppend(r.front().propid());
}
MOZ_ASSERT(properties.length() == count);
return true;
}
/*****************************************************************************/
const JSClass WasmInstanceEnvironmentObject::class_ = {
"WasmInstance",
JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceEnvironmentObject::RESERVED_SLOTS)};
/* static */
WasmInstanceEnvironmentObject*
WasmInstanceEnvironmentObject::createHollowForDebug(
JSContext* cx, Handle<WasmInstanceScope*> scope) {
RootedShape 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) {
RootedShape 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;
}
/*****************************************************************************/
WithEnvironmentObject* WithEnvironmentObject::create(JSContext* cx,
HandleObject object,
HandleObject enclosing,
Handle<WithScope*> scope) {
RootedShape 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) {
obj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
} else {
obj->initReservedSlot(SCOPE_SLOT, NullValue());
}
return obj;
}
WithEnvironmentObject* WithEnvironmentObject::createNonSyntactic(
JSContext* cx, HandleObject object, HandleObject enclosing) {
return create(cx, object, enclosing, nullptr);
}
static inline bool IsUnscopableDotName(JSContext* cx, HandleId id) {
return JSID_IS_ATOM(id, cx->names().dotThis);
}
#ifdef DEBUG
static bool IsInternalDotName(JSContext* cx, HandleId id) {
return JSID_IS_ATOM(id, cx->names().dotThis) ||
JSID_IS_ATOM(id, cx->names().dotGenerator) ||
JSID_IS_ATOM(id, cx->names().dotInitializers) ||
JSID_IS_ATOM(id, cx->names().dotFieldKeys) ||
JSID_IS_ATOM(id, cx->names().dotStaticInitializers) ||
JSID_IS_ATOM(id, cx->names().dotStaticFieldKeys) ||
JSID_IS_ATOM(id, cx->names().starNamespaceStar);
}
#endif
/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
static bool CheckUnscopables(JSContext* cx, HandleObject obj, HandleId id,
bool* scopable) {
RootedId unscopablesId(
cx,
SYMBOL_TO_JSID(cx->wellKnownSymbols().get(JS::SymbolCode::unscopables)));
RootedValue v(cx);
if (!GetProperty(cx, obj, obj, unscopablesId, &v)) {
return false;
}
if (v.isObject()) {
RootedObject unscopablesObj(cx, &v.toObject());
if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v)) {
return false;
}
*scopable = !ToBoolean(v);
} else {
*scopable = true;
}
return true;
}
static bool with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject objp,
MutableHandle<PropertyResult> propp) {
// SpiderMonkey-specific: consider the internal '.this' name 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) {
bool scopable;
if (!CheckUnscopables(cx, actual, id, &scopable)) {
return false;
}
if (!scopable) {
objp.set(nullptr);
propp.setNotFound();
}
}
return true;
}
static bool with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id,
Handle<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) {
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<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) {
RootedShape shape(cx, EmptyEnvironmentShape<NonSyntacticVariablesObject>(cx));
if (!shape) {
return nullptr;
}
Rooted<NonSyntacticVariablesObject*> obj(
cx, CreateEnvironmentObject<NonSyntacticVariablesObject>(cx, shape,
TenuredObject));
if (!obj) {
return nullptr;
}
MOZ_ASSERT(obj->isUnqualifiedVarObj());
if (!JSObject::setQualifiedVarObj(cx, obj)) {
return nullptr;
}
obj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
return obj;
}
const JSClass NonSyntacticVariablesObject::class_ = {
"NonSyntacticVariablesObject",
JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS)};
bool js::CreateNonSyntacticEnvironmentChain(JSContext* cx,
HandleObjectVector envChain,
MutableHandleObject env,
MutableHandleScope scope) {
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
if (!CreateObjectsForEnvironmentChain(cx, envChain, globalLexical, env)) {
return false;
}
if (!envChain.empty()) {
scope.set(GlobalScope::createEmpty(cx, ScopeKind::NonSyntactic));
if (!scope) {
return false;
}
// The XPConnect subscript loader, which may pass in its own
// environments to load scripts in, expects the environment chain to
// be the holder of "var" declarations. In SpiderMonkey, such objects
// are called "qualified varobjs", the "qualified" part meaning the
// declaration was qualified by "var". There is only sadness.
//
// See JSObject::isQualifiedVarObj.
if (!JSObject::setQualifiedVarObj(cx, env)) {
return false;
}
// Also get a non-syntactic lexical environment to capture 'let' and
// 'const' bindings. To persist lexical bindings, we have a 1-1
// mapping with the final unwrapped environment object (the
// environment that stores the 'var' bindings) and the lexical
// environment.
//
// TODOshu: disallow the subscript loader from using non-distinguished
// objects as dynamic scopes.
env.set(ObjectRealm::get(env).getOrCreateNonSyntacticLexicalEnvironment(
cx, env));
if (!env) {
return false;
}
} else {
scope.set(&cx->global()->emptyGlobalScope());
}
return true;
}
/*****************************************************************************/
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::createTemplateObject(
JSContext* cx, HandleShape shape, HandleObject enclosing,
gc::InitialHeap heap, IsSingletonEnv isSingleton) {
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,
isSingleton);
if (!env) {
return nullptr;
}
MOZ_ASSERT(!env->inDictionaryMode());
if (enclosing) {
env->initEnclosingEnvironment(enclosing);
}
return env;
}
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::create(
JSContext* cx, Handle<LexicalScope*> scope, HandleObject enclosing,
gc::InitialHeap heap) {
cx->check(enclosing);
MOZ_ASSERT(scope->hasEnvironment());
RootedShape shape(cx, scope->environmentShape());
LexicalEnvironmentObject* env =
createTemplateObject(cx, shape, enclosing, heap, IsSingletonEnv::No);
if (!env) {
return nullptr;
}
// All lexical bindings start off uninitialized for TDZ.
uint32_t lastSlot = shape->slot();
MOZ_ASSERT(lastSlot == env->lastProperty()->slot());
for (uint32_t slot = JSSLOT_FREE(&class_); slot <= lastSlot; slot++) {
env->initSlot(slot, MagicValue(JS_UNINITIALIZED_LEXICAL));
}
env->initScopeUnchecked(scope);
return env;
}
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::createForFrame(
JSContext* cx, Handle<LexicalScope*> scope, AbstractFramePtr frame) {
RootedObject enclosing(cx, frame.environmentChain());
return create(cx, scope, enclosing, gc::DefaultHeap);
}
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::createGlobal(
JSContext* cx, Handle<GlobalObject*> global) {
MOZ_ASSERT(global);
RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape) {
return nullptr;
}
LexicalEnvironmentObject* env =
LexicalEnvironmentObject::createTemplateObject(
cx, shape, global, gc::TenuredHeap, IsSingletonEnv::Yes);
if (!env) {
return nullptr;
}
env->initThisObject(global);
return env;
}
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::createNonSyntactic(
JSContext* cx, HandleObject enclosing, HandleObject thisv) {
MOZ_ASSERT(enclosing);
MOZ_ASSERT(!IsSyntacticEnvironment(enclosing));
RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape) {
return nullptr;
}
LexicalEnvironmentObject* env =
LexicalEnvironmentObject::createTemplateObject(
cx, shape, enclosing, gc::TenuredHeap, IsSingletonEnv::No);
if (!env) {
return nullptr;
}
env->initThisObject(thisv);
return env;
}
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::createHollowForDebug(
JSContext* cx, Handle<LexicalScope*> scope) {
MOZ_ASSERT(!scope->hasEnvironment());
RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape) {
return nullptr;
}
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment());
Rooted<LexicalEnvironmentObject*> env(
cx, createTemplateObject(cx, shape, enclosingEnv, gc::TenuredHeap,
IsSingletonEnv::No));
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::setFlags(cx, env, BaseShape::NOT_EXTENSIBLE,
JSObject::GENERATE_SHAPE)) {
return nullptr;
}
env->initScopeUnchecked(scope);
return env;
}
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::clone(
JSContext* cx, Handle<LexicalEnvironmentObject*> env) {
Rooted<LexicalScope*> scope(cx, &env->scope());
RootedObject enclosing(cx, &env->enclosingEnvironment());
Rooted<LexicalEnvironmentObject*> copy(
cx, create(cx, scope, enclosing, gc::DefaultHeap));
if (!copy) {
return nullptr;
}
// We can't assert that the clone has the same shape, because it could
// have been reshaped by ReshapeForShadowedProp.
MOZ_ASSERT(env->slotSpan() == copy->slotSpan());
for (uint32_t i = JSSLOT_FREE(&class_); i < copy->slotSpan(); i++) {
copy->setSlot(i, env->getSlot(i));
}
return copy;
}
/* static */
LexicalEnvironmentObject* LexicalEnvironmentObject::recreate(
JSContext* cx, Handle<LexicalEnvironmentObject*> env) {
Rooted<LexicalScope*> scope(cx, &env->scope());
RootedObject enclosing(cx, &env->enclosingEnvironment());
return create(cx, scope, enclosing, gc::DefaultHeap);
}
bool LexicalEnvironmentObject::isExtensible() const {
return NativeObject::isExtensible();
}
JSObject* LexicalEnvironmentObject::thisObject() const {
MOZ_ASSERT(isExtensible());
JSObject* obj = &getReservedSlot(THIS_VALUE_OR_SCOPE_SLOT).toObject();
// Windows must never be exposed to script. setWindowProxyThisValue should
// have set this to the WindowProxy.
MOZ_ASSERT(!IsWindow(obj));
// WarpBuilder relies on the return value not being nursery-allocated for the
// global lexical environment.
MOZ_ASSERT_IF(isGlobal(), obj->isTenured());
return obj;
}
void LexicalEnvironmentObject::setWindowProxyThisObject(JSObject* obj) {
MOZ_ASSERT(isGlobal());
MOZ_ASSERT(IsWindowProxy(obj));
setReservedSlot(THIS_VALUE_OR_SCOPE_SLOT, ObjectValue(*obj));
}
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 */
NamedLambdaObject* NamedLambdaObject::create(JSContext* cx,
HandleFunction callee,
HandleFunction func,
HandleObject enclosing,
gc::InitialHeap heap) {
MOZ_ASSERT(callee->isNamedLambda());
RootedScope scope(cx, callee->nonLazyScript()->maybeNamedLambdaScope());
MOZ_ASSERT(scope && scope->environmentShape());
MOZ_ASSERT(scope->environmentShape()->slot() == lambdaSlot());
MOZ_ASSERT(!scope->environmentShape()->writable());
#ifdef DEBUG
// There should be exactly one binding in the named lambda scope.
BindingIter bi(scope);
bi++;
MOZ_ASSERT(bi.done());
#endif
LexicalEnvironmentObject* obj = LexicalEnvironmentObject::create(
cx, scope.as<LexicalScope>(), enclosing, heap);
if (!obj) {
return nullptr;
}
obj->initFixedSlot(lambdaSlot(), ObjectValue(*func));
return static_cast<NamedLambdaObject*>(obj);
}
/* static */
NamedLambdaObject* NamedLambdaObject::createTemplateObject(
JSContext* cx, HandleFunction callee, gc::InitialHeap heap) {
return create(cx, callee, callee, nullptr, heap);
}
/* static */
NamedLambdaObject* NamedLambdaObject::create(JSContext* cx,
AbstractFramePtr frame) {
RootedFunction fun(cx, frame.callee());
RootedObject enclosing(cx, frame.environmentChain());
return create(cx, fun, fun, enclosing, gc::DefaultHeap);
}
/* static */
size_t NamedLambdaObject::lambdaSlot() {
// Named lambda environments have exactly one name.
return JSSLOT_FREE(&LexicalEnvironmentObject::class_);
}
/* static */
RuntimeLexicalErrorObject* RuntimeLexicalErrorObject::create(
JSContext* cx, HandleObject enclosing, unsigned errorNumber) {
RootedShape shape(cx, EmptyEnvironmentShape(cx, &class_, JSSLOT_FREE(&class_),
/* baseShapeFlags = */ 0));
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 (JSID_IS_ATOM(id)) {
RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
ReportRuntimeLexicalError(cx, errorNumber, name);
return;
}
MOZ_CRASH(
"RuntimeLexicalErrorObject should only be used with property names");
}
static bool lexicalError_LookupProperty(JSContext* cx, HandleObject obj,
HandleId id, MutableHandleObject objp,
MutableHandle<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<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,
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<LexicalEnvironmentObject>() &&
!env_->as<LexicalEnvironmentObject>().isExtensible() &&
&env_->as<LexicalEnvironmentObject>().scope() == si_.scope()) {
MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda ||
si_.kind() == ScopeKind::StrictNamedLambda);
env_ = &env_->as<EnvironmentObject>().enclosingEnvironment();
}
incrementScopeIter();
}
}
// Check if we have left the extent of the initial frame after we've
// settled on a static scope.
if (frame_ &&
(!si_ ||
(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<LexicalEnvironmentObject>().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>() ||
IsGlobalLexicalEnvironment(env_));
}
} else if (hasNonSyntacticEnvironmentObject()) {
if (env_->is<LexicalEnvironmentObject>()) {
// The global lexical environment still encloses non-syntactic
// environment objects.
MOZ_ASSERT(!env_->as<LexicalEnvironmentObject>().isSyntactic() ||
env_->as<LexicalEnvironmentObject>().isGlobal());
} else if (env_->is<WithEnvironmentObject>()) {
MOZ_ASSERT(!env_->as<WithEnvironmentObject>().isSyntactic());
} else {
MOZ_ASSERT(env_->is<NonSyntacticVariablesObject>());
}
}
}
#endif
}
JSObject& EnvironmentIter::enclosingEnvironment() const {
// As an engine invariant (maintained internally and asserted by Execute),
// EnvironmentObjects and non-EnvironmentObjects cannot be interleaved on
// the scope chain; every scope chain must start with zero or more
// EnvironmentObjects and terminate with one or more
// non-EnvironmentObjects (viz., GlobalObject).
MOZ_ASSERT(done());
MOZ_ASSERT(!env_->is<EnvironmentObject>());
return *env_;
}
bool EnvironmentIter::hasNonSyntacticEnvironmentObject() const {
// The case we're worrying about here is a NonSyntactic static scope
// which has 0+ corresponding non-syntactic WithEnvironmentObject
// scopes, a NonSyntacticVariablesObject, or a non-syntactic
// LexicalEnvironmentObject.
if (si_.kind() == ScopeKind::NonSyntactic) {
MOZ_ASSERT_IF(env_->is<WithEnvironmentObject>(),
!env_->as<WithEnvironmentObject>().isSyntactic());
return env_->is<EnvironmentObject>();
}
return false;
}
/* static */
HashNumber MissingEnvironmentKey::hash(MissingEnvironmentKey ek) {
return size_t(ek.frame_.raw()) ^ size_t(ek.scope_);
}
/* static */
bool MissingEnvironmentKey::match(MissingEnvironmentKey ek1,
MissingEnvironmentKey ek2) {
return ek1.frame_ == ek2.frame_ && ek1.scope_ == ek2.scope_;
}
bool LiveEnvironmentVal::needsSweep() {
if (scope_) {
MOZ_ALWAYS_FALSE(IsAboutToBeFinalized(&scope_));
}
return false;
}
// Live EnvironmentIter values may be added to DebugEnvironments::liveEnvs, as
// LiveEnvironmentVal instances. They need to have write barriers when they are
// added to the hash table, but no barriers when rehashing inside GC. It's a
// nasty hack, but the important thing is that LiveEnvironmentVal and
// MissingEnvironmentKey need to alias each other.
void LiveEnvironmentVal::staticAsserts() {
static_assert(
sizeof(LiveEnvironmentVal) == sizeof(MissingEnvironmentKey),
"LiveEnvironmentVal must be same size of MissingEnvironmentKey");
static_assert(
offsetof(LiveEnvironmentVal, scope_) ==
offsetof(MissingEnvironmentKey, scope_),
"LiveEnvironmentVal.scope_ must alias MissingEnvironmentKey.scope_");
}
/*****************************************************************************/
namespace {
/*
* DebugEnvironmentProxy is the handler for DebugEnvironmentProxy proxy
* objects. Having a custom handler (rather than trying to reuse js::Wrapper)
* gives us several important abilities:
* - We want to pass the EnvironmentObject as the receiver to forwarded scope
* property ops on aliased variables so that Call/Block/With ops do not all
* require a 'normalization' step.
* - The debug scope proxy can directly manipulate the stack frame to allow
* the debugger to read/write args/locals that were otherwise unaliased.
* - The debug scope proxy can store unaliased variables after the stack frame
* is popped so that they may still be read/written by the debugger.
* - The engine has made certain assumptions about the possible reads/writes
* in a scope. DebugEnvironmentProxy allows us to prevent the debugger from
* breaking those assumptions.
* - The engine makes optimizations that are observable to the debugger. The
* proxy can either hide these optimizations or make the situation more
* clear to the debugger. An example is 'arguments'.
*/
class DebugEnvironmentProxyHandler : public BaseProxyHandler {
enum Action { SET, GET };
enum AccessResult { ACCESS_UNALIASED, ACCESS_GENERIC, ACCESS_LOST };
/*
* This function handles access to unaliased locals/formals. Since they
* are unaliased, the values of these variables are not stored in the
* slots of the normal CallObject and LexicalEnvironmentObject
* environments and thus must be recovered from somewhere else:
* + if the invocation for which the env was created is still executing,
* there is a JS frame live on the stack holding the values;
* + if the invocation for which the env was created finished executing:
* - and there was a DebugEnvironmentProxy associated with env, then
* the DebugEnvironments::onPop(Call|Lexical) handler copied out the
* unaliased variables. In both cases, a dense array is created in
* onPop(Call|Lexical) to hold the unaliased values and attached to
* the DebugEnvironmentProxy;
* - and there was not a DebugEnvironmentProxy yet associated with the
* scope, then the unaliased values are lost and not recoverable.
*
* Callers should check accessResult for non-failure results:
* - ACCESS_UNALIASED if the access was unaliased and completed
* - ACCESS_GENERIC if the access was aliased or the property not found
* - ACCESS_LOST if the value has been lost to the debugger and the
* action is GET; if the action is SET, we assign to the
* name of the variable on the environment object
*/
bool handleUnaliasedAccess(JSContext* cx,
Handle<DebugEnvironmentProxy*> debugEnv,
Handle<EnvironmentObject*> env, HandleId id,
Action action, MutableHandleValue vp,
AccessResult* accessResult) const {
MOZ_ASSERT(&debugEnv->environment() == env);
MOZ_ASSERT_IF(action == SET, !debugEnv->isOptimizedOut());
*accessResult = ACCESS_GENERIC;
LiveEnvironmentVal* maybeLiveEnv =
DebugEnvironments::hasLiveEnvironment(*env);
// 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);
if (!script->ensureHasAnalyzedArgsUsage(cx)) {
return false;
}
} 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 (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