Mypal/js/src/vm/EnvironmentObject.cpp

3610 lines
123 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "vm/EnvironmentObject-inl.h"
#include "mozilla/PodOperations.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/SizePrintfMacros.h"
#include "jscompartment.h"
#include "jsiter.h"
#include "builtin/ModuleObject.h"
#include "frontend/ParseNode.h"
#include "gc/Policy.h"
#include "vm/ArgumentsObject.h"
#include "vm/AsyncFunction.h"
#include "vm/GlobalObject.h"
#include "vm/ProxyObject.h"
#include "vm/Shape.h"
#include "vm/Xdr.h"
#include "jsatominlines.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
#include "vm/Stack-inl.h"
using namespace js;
using namespace js::gc;
using mozilla::PodZero;
using mozilla::Maybe;
using mozilla::Some;
using mozilla::Nothing;
using mozilla::MakeScopeExit;
typedef Rooted<ArgumentsObject*> RootedArgumentsObject;
typedef MutableHandle<ArgumentsObject*> MutableHandleArgumentsObject;
/*****************************************************************************/
Shape*
js::EnvironmentCoordinateToEnvironmentShape(JSScript* script, jsbytecode* pc)
{
MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD);
ScopeIter si(script->innermostScope(pc));
uint32_t hops = EnvironmentCoordinate(pc).hops();
while (true) {
MOZ_ASSERT(!si.done());
if (si.hasSyntacticEnvironment()) {
if (!hops)
break;
hops--;
}
si++;
}
return si.environmentShape();
}
static const uint32_t ENV_COORDINATE_NAME_THRESHOLD = 20;
void
EnvironmentCoordinateNameCache::purge()
{
shape = nullptr;
if (map.initialized())
map.finish();
}
PropertyName*
js::EnvironmentCoordinateName(EnvironmentCoordinateNameCache& cache, JSScript* script,
jsbytecode* pc)
{
Shape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc);
if (shape != cache.shape && shape->slot() >= ENV_COORDINATE_NAME_THRESHOLD) {
cache.purge();
if (cache.map.init(shape->slot())) {
cache.shape = shape;
Shape::Range<NoGC> r(shape);
while (!r.empty()) {
if (!cache.map.putNew(r.front().slot(), r.front().propid())) {
cache.purge();
break;
}
r.popFront();
}
}
}
jsid id;
EnvironmentCoordinate ec(pc);
if (shape == cache.shape) {
EnvironmentCoordinateNameCache::Map::Ptr p = cache.map.lookup(ec.slot());
id = p->value();
} else {
Shape::Range<NoGC> r(shape);
while (r.front().slot() != ec.slot())
r.popFront();
id = r.front().propidRaw();
}
/* Beware nameless destructuring formal. */
if (!JSID_IS_ATOM(id))
return script->runtimeFromAnyThread()->commonNames->empty;
return JSID_TO_ATOM(id)->asPropertyName();
}
JSScript*
js::EnvironmentCoordinateFunctionScript(JSScript* script, jsbytecode* pc)
{
MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD);
ScopeIter si(script->innermostScope(pc));
uint32_t hops = EnvironmentCoordinate(pc).hops();
while (true) {
if (si.hasSyntacticEnvironment()) {
if (!hops)
break;
hops--;
}
si++;
}
if (si.kind() != ScopeKind::Function)
return nullptr;
return si.scope()->as<FunctionScope>().script();
}
/*****************************************************************************/
CallObject*
CallObject::create(JSContext* cx, HandleShape shape, HandleObjectGroup group)
{
MOZ_ASSERT(!group->singleton(),
"passed a singleton group to create() (use createSingleton() "
"instead)");
gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
kind = gc::GetBackgroundAllocKind(kind);
JSObject* obj = JSObject::create(cx, kind, gc::DefaultHeap, shape, group);
if (!obj)
return nullptr;
return &obj->as<CallObject>();
}
CallObject*
CallObject::createSingleton(JSContext* cx, HandleShape shape)
{
gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
kind = gc::GetBackgroundAllocKind(kind);
RootedObjectGroup group(cx, ObjectGroup::lazySingletonGroup(cx, &class_, TaggedProto(nullptr)));
if (!group)
return nullptr;
RootedObject obj(cx, JSObject::create(cx, kind, gc::TenuredHeap, shape, group));
if (!obj)
return nullptr;
MOZ_ASSERT(obj->isSingleton(),
"group created inline above must be a singleton");
return &obj->as<CallObject>();
}
/*
* Create a CallObject for a JSScript that is not initialized to any particular
* callsite. This object can either be initialized (with an enclosing scope and
* callee) or used as a template for jit compilation.
*/
CallObject*
CallObject::createTemplateObject(JSContext* cx, HandleScript script, HandleObject enclosing,
gc::InitialHeap heap)
{
Rooted<FunctionScope*> scope(cx, &script->bodyScope()->as<FunctionScope>());
RootedShape shape(cx, scope->environmentShape());
MOZ_ASSERT(shape->getObjectClass() == &class_);
RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
if (!group)
return nullptr;
gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
kind = gc::GetBackgroundAllocKind(kind);
JSObject* obj = JSObject::create(cx, kind, heap, shape, group);
if (!obj)
return nullptr;
CallObject* callObj = &obj->as<CallObject>();
callObj->initEnclosingEnvironment(enclosing);
if (scope->hasParameterExprs()) {
// If there are parameter expressions, all parameters are lexical and
// have TDZ.
for (BindingIter bi(script->bodyScope()); bi; bi++) {
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment && BindingKindIsLexical(bi.kind()))
callObj->initSlot(loc.slot(), MagicValue(JS_UNINITIALIZED_LEXICAL));
}
}
return callObj;
}
/*
* Construct a call object for the given bindings. If this is a call object
* for a function invocation, callee should be the function being called.
* Otherwise it must be a call object for eval of strict mode code, and callee
* must be null.
*/
CallObject*
CallObject::create(JSContext* cx, HandleFunction callee, HandleObject enclosing)
{
RootedScript script(cx, callee->nonLazyScript());
gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
CallObject* callobj = CallObject::createTemplateObject(cx, script, enclosing, heap);
if (!callobj)
return nullptr;
callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee));
if (script->treatAsRunOnce()) {
Rooted<CallObject*> ncallobj(cx, callobj);
if (!JSObject::setSingleton(cx, ncallobj))
return nullptr;
return ncallobj;
}
return callobj;
}
CallObject*
CallObject::create(JSContext* cx, AbstractFramePtr frame)
{
MOZ_ASSERT(frame.isFunctionFrame());
assertSameCompartment(cx, frame);
RootedObject envChain(cx, frame.environmentChain());
RootedFunction callee(cx, frame.callee());
CallObject* callobj = create(cx, callee, envChain);
if (!callobj)
return nullptr;
if (!frame.script()->bodyScope()->as<FunctionScope>().hasParameterExprs()) {
// If there are no defaults, copy the aliased arguments into the call
// object manually. If there are defaults, bytecode is generated to do
// the copying.
for (PositionalFormalParameterIter fi(frame.script()); fi; fi++) {
if (!fi.closedOver())
continue;
callobj->setAliasedBinding(cx, fi, frame.unaliasedFormal(fi.argumentSlot(),
DONT_CHECK_ALIASING));
}
}
return callobj;
}
CallObject*
CallObject::createHollowForDebug(JSContext* cx, HandleFunction callee)
{
MOZ_ASSERT(!callee->needsCallObject());
RootedScript script(cx, callee->nonLazyScript());
Rooted<FunctionScope*> scope(cx, &script->bodyScope()->as<FunctionScope>());
RootedShape shape(cx, FunctionScope::getEmptyEnvironmentShape(cx, scope->hasParameterExprs()));
if (!shape)
return nullptr;
RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
if (!group)
return nullptr;
Rooted<CallObject*> callobj(cx, create(cx, shape, group));
if (!callobj)
return nullptr;
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee));
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, callobj, id, optimizedOut))
return nullptr;
}
return callobj;
}
const Class CallObject::class_ = {
"Call",
JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)
};
/*****************************************************************************/
/* static */ VarEnvironmentObject*
VarEnvironmentObject::create(JSContext* cx, HandleShape shape, HandleObject enclosing,
gc::InitialHeap heap)
{
MOZ_ASSERT(shape->getObjectClass() == &class_);
RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
if (!group)
return nullptr;
gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
kind = gc::GetBackgroundAllocKind(kind);
NativeObject* obj = MaybeNativeObject(JSObject::create(cx, kind, heap, shape, group));
if (!obj)
return nullptr;
MOZ_ASSERT(!obj->inDictionaryMode());
MOZ_ASSERT(obj->isDelegate());
VarEnvironmentObject* env = &obj->as<VarEnvironmentObject>();
env->initEnclosingEnvironment(enclosing);
return env;
}
/* static */ VarEnvironmentObject*
VarEnvironmentObject::create(JSContext* cx, HandleScope scope, AbstractFramePtr frame)
{
#ifdef DEBUG
if (frame.isEvalFrame()) {
MOZ_ASSERT(scope->is<EvalScope>() && scope == frame.script()->bodyScope());
MOZ_ASSERT_IF(frame.isInterpreterFrame(),
cx->interpreterFrame() == frame.asInterpreterFrame());
MOZ_ASSERT_IF(frame.isInterpreterFrame(),
cx->interpreterRegs().pc == frame.script()->code());
} else {
MOZ_ASSERT(frame.environmentChain());
MOZ_ASSERT_IF(frame.callee()->needsCallObject(),
&frame.environmentChain()->as<CallObject>().callee() == frame.callee());
}
#endif
RootedScript script(cx, frame.script());
RootedObject envChain(cx, frame.environmentChain());
gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
RootedShape shape(cx, scope->environmentShape());
VarEnvironmentObject* env = create(cx, shape, envChain, heap);
if (!env)
return nullptr;
env->initScope(scope);
return env;
}
/* static */ VarEnvironmentObject*
VarEnvironmentObject::createHollowForDebug(JSContext* cx, Handle<VarScope*> scope)
{
MOZ_ASSERT(!scope->hasEnvironment());
RootedShape shape(cx, VarScope::getEmptyEnvironmentShape(cx));
if (!shape)
return nullptr;
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment());
Rooted<VarEnvironmentObject*> env(cx, create(cx, shape, enclosingEnv, gc::TenuredHeap));
if (!env)
return nullptr;
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, env, id, optimizedOut))
return nullptr;
}
env->initScope(scope);
return env;
}
const Class VarEnvironmentObject::class_ = {
"Var",
JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(VarEnvironmentObject::RESERVED_SLOTS)
};
/*****************************************************************************/
const ObjectOps ModuleEnvironmentObject::objectOps_ = {
ModuleEnvironmentObject::lookupProperty,
nullptr, /* defineProperty */
ModuleEnvironmentObject::hasProperty,
ModuleEnvironmentObject::getProperty,
ModuleEnvironmentObject::setProperty,
ModuleEnvironmentObject::getOwnPropertyDescriptor,
ModuleEnvironmentObject::deleteProperty,
nullptr, /* getElements */
ModuleEnvironmentObject::enumerate,
nullptr
};
const Class ModuleEnvironmentObject::class_ = {
"ModuleEnvironmentObject",
JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS,
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&ModuleEnvironmentObject::objectOps_
};
/* static */ ModuleEnvironmentObject*
ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module)
{
RootedScript script(cx, module->script());
RootedShape shape(cx, script->bodyScope()->as<ModuleScope>().environmentShape());
MOZ_ASSERT(shape->getObjectClass() == &class_);
RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
if (!group)
return nullptr;
gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
kind = gc::GetBackgroundAllocKind(kind);
JSObject* obj = JSObject::create(cx, kind, TenuredHeap, shape, group);
if (!obj)
return nullptr;
RootedModuleEnvironmentObject env(cx, &obj->as<ModuleEnvironmentObject>());
env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));
if (!JSObject::setSingleton(cx, env))
return nullptr;
// Initialize this early so that we can manipulate the env object without
// causing assertions.
env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
// Initialize all lexical bindings and imports as uninitialized. Imports
// get uninitialized because they have a special TDZ for cyclic imports.
for (BindingIter bi(script); bi; bi++) {
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment && BindingKindIsLexical(bi.kind()))
env->initSlot(loc.slot(), MagicValue(JS_UNINITIALIZED_LEXICAL));
}
// It is not be possible to add or remove bindings from a module environment
// after this point as module code is always strict.
#ifdef DEBUG
for (Shape::Range<NoGC> r(env->lastProperty()); !r.empty(); r.popFront())
MOZ_ASSERT(!r.front().configurable());
MOZ_ASSERT(env->lastProperty()->getObjectFlags() & BaseShape::NOT_EXTENSIBLE);
MOZ_ASSERT(!env->inDictionaryMode());
#endif
return env;
}
ModuleObject&
ModuleEnvironmentObject::module()
{
return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>();
}
IndirectBindingMap&
ModuleEnvironmentObject::importBindings()
{
return module().importBindings();
}
bool
ModuleEnvironmentObject::createImportBinding(JSContext* cx, HandleAtom importName,
HandleModuleObject module, HandleAtom localName)
{
RootedId importNameId(cx, AtomToId(importName));
RootedId localNameId(cx, AtomToId(localName));
RootedModuleEnvironmentObject env(cx, &module->initialEnvironment());
if (!importBindings().put(cx, importNameId, env, localNameId)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
bool
ModuleEnvironmentObject::hasImportBinding(HandlePropertyName name)
{
return importBindings().has(NameToId(name));
}
bool
ModuleEnvironmentObject::lookupImport(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut)
{
return importBindings().lookup(name, envOut, shapeOut);
}
void
ModuleEnvironmentObject::fixEnclosingEnvironmentAfterCompartmentMerge(GlobalObject& global)
{
setEnclosingEnvironment(&global.lexicalEnvironment());
}
/* static */ bool
ModuleEnvironmentObject::lookupProperty(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject objp, MutableHandleShape propp)
{
const IndirectBindingMap& bindings = obj->as<ModuleEnvironmentObject>().importBindings();
Shape* shape;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &shape)) {
objp.set(env);
propp.set(shape);
return true;
}
RootedNativeObject target(cx, &obj->as<NativeObject>());
if (!NativeLookupOwnProperty<CanGC>(cx, target, id, propp))
return false;
objp.set(obj);
return true;
}
/* static */ bool
ModuleEnvironmentObject::hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
if (obj->as<ModuleEnvironmentObject>().importBindings().has(id)) {
*foundp = true;
return true;
}
RootedNativeObject self(cx, &obj->as<NativeObject>());
return NativeHasProperty(cx, self, id, foundp);
}
/* static */ bool
ModuleEnvironmentObject::getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
HandleId id, MutableHandleValue vp)
{
const IndirectBindingMap& bindings = obj->as<ModuleEnvironmentObject>().importBindings();
Shape* shape;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &shape)) {
vp.set(env->getSlot(shape->slot()));
return true;
}
RootedNativeObject self(cx, &obj->as<NativeObject>());
return NativeGetProperty(cx, self, receiver, id, vp);
}
/* static */ bool
ModuleEnvironmentObject::setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
HandleValue receiver, JS::ObjectOpResult& result)
{
RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>());
if (self->importBindings().has(id))
return result.failReadOnly();
return NativeSetProperty(cx, self, id, v, receiver, Qualified, result);
}
/* static */ bool
ModuleEnvironmentObject::getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
const IndirectBindingMap& bindings = obj->as<ModuleEnvironmentObject>().importBindings();
Shape* shape;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &shape)) {
desc.setAttributes(JSPROP_ENUMERATE | JSPROP_PERMANENT);
desc.object().set(obj);
RootedValue value(cx, env->getSlot(shape->slot()));
desc.setValue(value);
desc.assertComplete();
return true;
}
RootedNativeObject self(cx, &obj->as<NativeObject>());
return NativeGetOwnPropertyDescriptor(cx, self, id, desc);
}
/* static */ bool
ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
ObjectOpResult& result)
{
return result.failCantDelete();
}
/* static */ bool
ModuleEnvironmentObject::enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
bool enumerableOnly)
{
RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>());
const IndirectBindingMap& bs(self->importBindings());
MOZ_ASSERT(properties.length() == 0);
size_t count = bs.count() + self->slotSpan() - RESERVED_SLOTS;
if (!properties.reserve(count)) {
ReportOutOfMemory(cx);
return false;
}
bs.forEachExportedName([&] (jsid name) {
properties.infallibleAppend(name);
});
for (Shape::Range<NoGC> r(self->lastProperty()); !r.empty(); r.popFront())
properties.infallibleAppend(r.front().propid());
MOZ_ASSERT(properties.length() == count);
return true;
}
/*****************************************************************************/
WithEnvironmentObject*
WithEnvironmentObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
Handle<WithScope*> scope)
{
Rooted<WithEnvironmentObject*> obj(cx);
obj = NewObjectWithNullTaggedProto<WithEnvironmentObject>(cx, GenericObject,
BaseShape::DELEGATE);
if (!obj)
return nullptr;
Value thisv = GetThisValue(object);
obj->initEnclosingEnvironment(enclosing);
obj->initReservedSlot(OBJECT_SLOT, ObjectValue(*object));
obj->initReservedSlot(THIS_SLOT, thisv);
if (scope)
obj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
else
obj->initReservedSlot(SCOPE_SLOT, NullValue());
return obj;
}
WithEnvironmentObject*
WithEnvironmentObject::createNonSyntactic(JSContext* cx, HandleObject object,
HandleObject enclosing)
{
return create(cx, object, enclosing, nullptr);
}
static inline bool
IsUnscopableDotName(JSContext* cx, HandleId id)
{
return JSID_IS_ATOM(id, cx->names().dotThis) || JSID_IS_ATOM(id, cx->names().dotGenerator);
}
/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
static bool
CheckUnscopables(JSContext *cx, HandleObject obj, HandleId id, bool *scopable)
{
RootedId unscopablesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols()
.get(JS::SymbolCode::unscopables)));
RootedValue v(cx);
if (!GetProperty(cx, obj, obj, unscopablesId, &v))
return false;
if (v.isObject()) {
RootedObject unscopablesObj(cx, &v.toObject());
if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v))
return false;
*scopable = !ToBoolean(v);
} else {
*scopable = true;
}
return true;
}
static bool
with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject objp, MutableHandleShape propp)
{
// SpiderMonkey-specific: consider internal '.generator' and '.this' names
// to be unscopable.
if (IsUnscopableDotName(cx, id)) {
objp.set(nullptr);
propp.set(nullptr);
return true;
}
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
if (!LookupProperty(cx, actual, id, objp, propp))
return false;
if (propp) {
bool scopable;
if (!CheckUnscopables(cx, actual, id, &scopable))
return false;
if (!scopable) {
objp.set(nullptr);
propp.set(nullptr);
}
}
return true;
}
static bool
with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle<PropertyDescriptor> desc,
ObjectOpResult& result)
{
MOZ_ASSERT(!IsUnscopableDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return DefineProperty(cx, actual, id, desc, result);
}
static bool
with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
MOZ_ASSERT(!IsUnscopableDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
// ES 8.1.1.2.1 step 3-5.
if (!HasProperty(cx, actual, id, foundp))
return false;
if (!*foundp)
return true;
// Steps 7-10. (Step 6 is a no-op.)
return CheckUnscopables(cx, actual, id, foundp);
}
static bool
with_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
MutableHandleValue vp)
{
MOZ_ASSERT(!IsUnscopableDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
RootedValue actualReceiver(cx, receiver);
if (receiver.isObject() && &receiver.toObject() == obj)
actualReceiver.setObject(*actual);
return GetProperty(cx, actual, actualReceiver, id, vp);
}
static bool
with_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result)
{
MOZ_ASSERT(!IsUnscopableDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
RootedValue actualReceiver(cx, receiver);
if (receiver.isObject() && &receiver.toObject() == obj)
actualReceiver.setObject(*actual);
return SetProperty(cx, actual, id, v, actualReceiver, result);
}
static bool
with_GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
MOZ_ASSERT(!IsUnscopableDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return GetOwnPropertyDescriptor(cx, actual, id, desc);
}
static bool
with_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
{
MOZ_ASSERT(!IsUnscopableDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return DeleteProperty(cx, actual, id, result);
}
static const ObjectOps WithEnvironmentObjectOps = {
with_LookupProperty,
with_DefineProperty,
with_HasProperty,
with_GetProperty,
with_SetProperty,
with_GetOwnPropertyDescriptor,
with_DeleteProperty,
nullptr, /* getElements */
nullptr, /* enumerate (native enumeration of target doesn't work) */
nullptr,
};
const Class WithEnvironmentObject::class_ = {
"With",
JSCLASS_HAS_RESERVED_SLOTS(WithEnvironmentObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS,
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&WithEnvironmentObjectOps
};
/* static */ NonSyntacticVariablesObject*
NonSyntacticVariablesObject::create(JSContext* cx)
{
Rooted<NonSyntacticVariablesObject*> obj(cx,
NewObjectWithNullTaggedProto<NonSyntacticVariablesObject>(cx, TenuredObject,
BaseShape::DELEGATE));
if (!obj)
return nullptr;
MOZ_ASSERT(obj->isUnqualifiedVarObj());
if (!JSObject::setQualifiedVarObj(cx, obj))
return nullptr;
obj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
return obj;
}
const Class NonSyntacticVariablesObject::class_ = {
"NonSyntacticVariablesObject",
JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS
};
/*****************************************************************************/
/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createTemplateObject(JSContext* cx, HandleShape shape,
HandleObject enclosing, gc::InitialHeap heap)
{
MOZ_ASSERT(shape->getObjectClass() == &LexicalEnvironmentObject::class_);
RootedObjectGroup group(cx,
ObjectGroup::defaultNewGroup(cx, &LexicalEnvironmentObject::class_, TaggedProto(nullptr)));
if (!group)
return nullptr;
gc::AllocKind allocKind = gc::GetGCObjectKind(shape->numFixedSlots());
MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &LexicalEnvironmentObject::class_));
allocKind = GetBackgroundAllocKind(allocKind);
RootedNativeObject obj(cx,
MaybeNativeObject(JSObject::create(cx, allocKind, heap, shape, group)));
if (!obj)
return nullptr;
MOZ_ASSERT(!obj->inDictionaryMode());
MOZ_ASSERT(obj->isDelegate());
LexicalEnvironmentObject* env = &obj->as<LexicalEnvironmentObject>();
if (enclosing)
env->initEnclosingEnvironment(enclosing);
return env;
}
/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createTemplateObject(JSContext* cx, Handle<LexicalScope*> scope,
HandleObject enclosing, gc::InitialHeap heap)
{
assertSameCompartment(cx, enclosing);
MOZ_ASSERT(scope->hasEnvironment());
RootedShape shape(cx, scope->environmentShape());
LexicalEnvironmentObject* env = createTemplateObject(cx, shape, enclosing, heap);
if (!env)
return nullptr;
// All lexical bindings start off uninitialized for TDZ.
uint32_t lastSlot = shape->slot();
MOZ_ASSERT(lastSlot == env->lastProperty()->slot());
for (uint32_t slot = JSSLOT_FREE(&class_); slot <= lastSlot; slot++)
env->initSlot(slot, MagicValue(JS_UNINITIALIZED_LEXICAL));
env->initScopeUnchecked(scope);
return env;
}
/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::create(JSContext* cx, Handle<LexicalScope*> scope,
AbstractFramePtr frame)
{
RootedObject enclosing(cx, frame.environmentChain());
return createTemplateObject(cx, scope, enclosing, gc::DefaultHeap);
}
/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createGlobal(JSContext* cx, Handle<GlobalObject*> global)
{
MOZ_ASSERT(global);
RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape)
return nullptr;
Rooted<LexicalEnvironmentObject*> env(cx,
LexicalEnvironmentObject::createTemplateObject(cx, shape, global, gc::TenuredHeap));
if (!env)
return nullptr;
if (!JSObject::setSingleton(cx, env))
return nullptr;
env->initThisValue(global);
return env;
}
/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createNonSyntactic(JSContext* cx, HandleObject enclosing)
{
MOZ_ASSERT(enclosing);
MOZ_ASSERT(!IsSyntacticEnvironment(enclosing));
RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape)
return nullptr;
LexicalEnvironmentObject* env =
LexicalEnvironmentObject::createTemplateObject(cx, shape, enclosing, gc::TenuredHeap);
if (!env)
return nullptr;
env->initThisValue(enclosing);
return env;
}
/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::createHollowForDebug(JSContext* cx, Handle<LexicalScope*> scope)
{
MOZ_ASSERT(!scope->hasEnvironment());
RootedShape shape(cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx));
if (!shape)
return nullptr;
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment());
Rooted<LexicalEnvironmentObject*> env(cx, createTemplateObject(cx, shape, enclosingEnv,
gc::TenuredHeap));
if (!env)
return nullptr;
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, env, id, optimizedOut))
return nullptr;
}
if (!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, createTemplateObject(cx, scope, enclosing,
gc::TenuredHeap));
if (!copy)
return nullptr;
// We can't assert that the clone has the same shape, because it could
// have been reshaped by PurgeEnvironmentChain.
MOZ_ASSERT(env->slotSpan() == copy->slotSpan());
for (uint32_t i = JSSLOT_FREE(&class_); i < copy->slotSpan(); i++)
copy->setSlot(i, env->getSlot(i));
return copy;
}
/* static */ LexicalEnvironmentObject*
LexicalEnvironmentObject::recreate(JSContext* cx, Handle<LexicalEnvironmentObject*> env)
{
Rooted<LexicalScope*> scope(cx, &env->scope());
RootedObject enclosing(cx, &env->enclosingEnvironment());
return createTemplateObject(cx, scope, enclosing, gc::TenuredHeap);
}
bool
LexicalEnvironmentObject::isExtensible() const
{
return nonProxyIsExtensible();
}
Value
LexicalEnvironmentObject::thisValue() const
{
MOZ_ASSERT(isExtensible());
Value v = getReservedSlot(THIS_VALUE_OR_SCOPE_SLOT);
if (v.isObject()) {
// If `v` is a Window, return the WindowProxy instead. We called
// GetThisValue (which also does ToWindowProxyIfWindow) when storing
// the value in THIS_VALUE_OR_SCOPE_SLOT, but it's possible the
// WindowProxy was attached to the global *after* we set
// THIS_VALUE_OR_SCOPE_SLOT.
return ObjectValue(*ToWindowProxyIfWindow(&v.toObject()));
}
return v;
}
const Class LexicalEnvironmentObject::class_ = {
"LexicalEnvironment",
JSCLASS_HAS_RESERVED_SLOTS(LexicalEnvironmentObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS,
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
JS_NULL_OBJECT_OPS
};
/* static */ NamedLambdaObject*
NamedLambdaObject::create(JSContext* cx, HandleFunction callee,
HandleFunction func,
HandleObject enclosing,
gc::InitialHeap heap)
{
MOZ_ASSERT(callee->isNamedLambda());
RootedScope scope(cx, callee->nonLazyScript()->maybeNamedLambdaScope());
MOZ_ASSERT(scope && scope->environmentShape());
MOZ_ASSERT(scope->environmentShape()->slot() == lambdaSlot());
MOZ_ASSERT(!scope->environmentShape()->writable());
#ifdef DEBUG
// There should be exactly one binding in the named lambda scope.
BindingIter bi(scope);
bi++;
MOZ_ASSERT(bi.done());
#endif
LexicalEnvironmentObject* obj =
LexicalEnvironmentObject::createTemplateObject(cx, scope.as<LexicalScope>(),
enclosing, heap);
if (!obj)
return nullptr;
obj->initFixedSlot(lambdaSlot(), ObjectValue(*func));
return static_cast<NamedLambdaObject*>(obj);
}
/* static */ NamedLambdaObject*
NamedLambdaObject::createTemplateObject(JSContext* cx, HandleFunction callee, gc::InitialHeap heap)
{
return create(cx, callee, callee, nullptr, heap);
}
/* static */ NamedLambdaObject*
NamedLambdaObject::create(JSContext* cx, AbstractFramePtr frame)
{
RootedFunction fun(cx, frame.callee());
RootedObject enclosing(cx, frame.environmentChain());
return create(cx, fun, fun, enclosing, gc::DefaultHeap);
}
/* static */ NamedLambdaObject*
NamedLambdaObject::create(JSContext* cx, AbstractFramePtr frame, HandleFunction replacement)
{
RootedFunction fun(cx, frame.callee());
RootedObject enclosing(cx, frame.environmentChain());
return create(cx, fun, replacement, enclosing, gc::DefaultHeap);
}
/* static */ size_t
NamedLambdaObject::lambdaSlot()
{
// Named lambda environments have exactly one name.
return JSSLOT_FREE(&LexicalEnvironmentObject::class_);
}
/* static */ RuntimeLexicalErrorObject*
RuntimeLexicalErrorObject::create(JSContext* cx, HandleObject enclosing, unsigned errorNumber)
{
RuntimeLexicalErrorObject* obj =
NewObjectWithNullTaggedProto<RuntimeLexicalErrorObject>(cx, GenericObject,
BaseShape::DELEGATE);
if (!obj)
return nullptr;
obj->initEnclosingEnvironment(enclosing);
obj->initReservedSlot(ERROR_SLOT, Int32Value(int32_t(errorNumber)));
return obj;
}
static void
ReportRuntimeLexicalErrorId(JSContext* cx, unsigned errorNumber, HandleId id)
{
if (JSID_IS_ATOM(id)) {
RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
ReportRuntimeLexicalError(cx, errorNumber, name);
return;
}
MOZ_CRASH("RuntimeLexicalErrorObject should only be used with property names");
}
static bool
lexicalError_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject objp, MutableHandleShape propp)
{
ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool
lexicalError_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool
lexicalError_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
MutableHandleValue vp)
{
ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool
lexicalError_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result)
{
ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool
lexicalError_GetOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static bool
lexicalError_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
{
ReportRuntimeLexicalErrorId(cx, obj->as<RuntimeLexicalErrorObject>().errorNumber(), id);
return false;
}
static const ObjectOps RuntimeLexicalErrorObjectObjectOps = {
lexicalError_LookupProperty,
nullptr, /* defineProperty */
lexicalError_HasProperty,
lexicalError_GetProperty,
lexicalError_SetProperty,
lexicalError_GetOwnPropertyDescriptor,
lexicalError_DeleteProperty,
nullptr, /* getElements */
nullptr, /* enumerate (native enumeration of target doesn't work) */
nullptr, /* this */
};
const Class RuntimeLexicalErrorObject::class_ = {
"RuntimeLexicalError",
JSCLASS_HAS_RESERVED_SLOTS(RuntimeLexicalErrorObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS,
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&RuntimeLexicalErrorObjectObjectOps
};
/*****************************************************************************/
EnvironmentIter::EnvironmentIter(JSContext* cx, const EnvironmentIter& ei
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: si_(cx, ei.si_.get()),
env_(cx, ei.env_),
frame_(ei.frame_)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: si_(cx, ScopeIter(scope)),
env_(cx, env),
frame_(NullFramePtr())
{
settle();
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
EnvironmentIter::EnvironmentIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: si_(cx, frame.script()->innermostScope(pc)),
env_(cx, frame.environmentChain()),
frame_(frame)
{
assertSameCompartment(cx, frame);
settle();
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
void
EnvironmentIter::incrementScopeIter()
{
if (si_.scope()->is<GlobalScope>()) {
// GlobalScopes may be syntactic or non-syntactic. Non-syntactic
// GlobalScopes correspond to zero or more non-syntactic
// EnvironmentsObjects followed by the global lexical scope, then the
// GlobalObject or another non-EnvironmentObject object.
if (!env_->is<EnvironmentObject>())
si_++;
} else {
si_++;
}
}
void
EnvironmentIter::settle()
{
// Check for trying to iterate a function or eval frame before the prologue has
// created the CallObject, in which case we have to skip.
if (frame_ && frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment()) {
// Skip until we're at the enclosing scope of the script.
while (si_.scope() != frame_.script()->enclosingScope()) {
if (env_->is<LexicalEnvironmentObject>() &&
!env_->as<LexicalEnvironmentObject>().isExtensible() &&
&env_->as<LexicalEnvironmentObject>().scope() == si_.scope())
{
MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda ||
si_.kind() == ScopeKind::StrictNamedLambda);
env_ = &env_->as<EnvironmentObject>().enclosingEnvironment();
}
incrementScopeIter();
}
}
// Check if we have left the extent of the initial frame after we've
// settled on a static scope.
if (frame_ && (!si_ || si_.scope() == frame_.script()->enclosingScope()))
frame_ = NullFramePtr();
#ifdef DEBUG
if (si_) {
if (hasSyntacticEnvironment()) {
Scope* scope = si_.scope();
if (scope->is<LexicalScope>()) {
MOZ_ASSERT(scope == &env_->as<LexicalEnvironmentObject>().scope());
} else if (scope->is<FunctionScope>()) {
MOZ_ASSERT(scope->as<FunctionScope>().script() ==
env_->as<CallObject>().callee().existingScriptNonDelazifying());
} else if (scope->is<VarScope>()) {
MOZ_ASSERT(scope == &env_->as<VarEnvironmentObject>().scope());
} else if (scope->is<WithScope>()) {
MOZ_ASSERT(scope == &env_->as<WithEnvironmentObject>().scope());
} else if (scope->is<EvalScope>()) {
MOZ_ASSERT(scope == &env_->as<VarEnvironmentObject>().scope());
} else if (scope->is<GlobalScope>()) {
MOZ_ASSERT(env_->is<GlobalObject>() || IsGlobalLexicalEnvironment(env_));
}
} else if (hasNonSyntacticEnvironmentObject()) {
if (env_->is<LexicalEnvironmentObject>()) {
// The global lexical environment still encloses non-syntactic
// environment objects.
MOZ_ASSERT(!env_->as<LexicalEnvironmentObject>().isSyntactic() ||
env_->as<LexicalEnvironmentObject>().isGlobal());
} else if (env_->is<WithEnvironmentObject>()) {
MOZ_ASSERT(!env_->as<WithEnvironmentObject>().isSyntactic());
} else {
MOZ_ASSERT(env_->is<NonSyntacticVariablesObject>());
}
}
}
#endif
}
JSObject&
EnvironmentIter::enclosingEnvironment() const
{
// As an engine invariant (maintained internally and asserted by Execute),
// EnvironmentObjects and non-EnvironmentObjects cannot be interleaved on
// the scope chain; every scope chain must start with zero or more
// EnvironmentObjects and terminate with one or more
// non-EnvironmentObjects (viz., GlobalObject).
MOZ_ASSERT(done());
MOZ_ASSERT(!env_->is<EnvironmentObject>());
return *env_;
}
bool
EnvironmentIter::hasNonSyntacticEnvironmentObject() const
{
// The case we're worrying about here is a NonSyntactic static scope
// which has 0+ corresponding non-syntactic WithEnvironmentObject
// scopes, a NonSyntacticVariablesObject, or a non-syntactic
// LexicalEnvironmentObject.
if (si_.kind() == ScopeKind::NonSyntactic) {
MOZ_ASSERT_IF(env_->is<WithEnvironmentObject>(),
!env_->as<WithEnvironmentObject>().isSyntactic());
return env_->is<EnvironmentObject>();
}
return false;
}
/* static */ HashNumber
MissingEnvironmentKey::hash(MissingEnvironmentKey ek)
{
return size_t(ek.frame_.raw()) ^ size_t(ek.scope_);
}
/* static */ bool
MissingEnvironmentKey::match(MissingEnvironmentKey ek1, MissingEnvironmentKey ek2)
{
return ek1.frame_ == ek2.frame_ && ek1.scope_ == ek2.scope_;
}
bool
LiveEnvironmentVal::needsSweep()
{
if (scope_)
MOZ_ALWAYS_FALSE(IsAboutToBeFinalized(&scope_));
return false;
}
// Live EnvironmentIter values may be added to DebugEnvironments::liveEnvs, as
// LiveEnvironmentVal instances. They need to have write barriers when they are added
// to the hash table, but no barriers when rehashing inside GC. It's a nasty
// hack, but the important thing is that LiveEnvironmentVal and MissingEnvironmentKey need to
// alias each other.
void
LiveEnvironmentVal::staticAsserts()
{
static_assert(sizeof(LiveEnvironmentVal) == sizeof(MissingEnvironmentKey),
"LiveEnvironmentVal must be same size of MissingEnvironmentKey");
static_assert(offsetof(LiveEnvironmentVal, scope_) == offsetof(MissingEnvironmentKey, scope_),
"LiveEnvironmentVal.scope_ must alias MissingEnvironmentKey.scope_");
}
/*****************************************************************************/
namespace {
static void
ReportOptimizedOut(JSContext* cx, HandleId id)
{
JSAutoByteString printable;
if (ValueToPrintable(cx, IdToValue(id), &printable)) {
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT,
printable.ptr());
}
}
/*
* DebugEnvironmentProxy is the handler for DebugEnvironmentProxy proxy
* objects. Having a custom handler (rather than trying to reuse js::Wrapper)
* gives us several important abilities:
* - We want to pass the EnvironmentObject as the receiver to forwarded scope
* property ops on aliased variables so that Call/Block/With ops do not all
* require a 'normalization' step.
* - The debug scope proxy can directly manipulate the stack frame to allow
* the debugger to read/write args/locals that were otherwise unaliased.
* - The debug scope proxy can store unaliased variables after the stack frame
* is popped so that they may still be read/written by the debugger.
* - The engine has made certain assumptions about the possible reads/writes
* in a scope. DebugEnvironmentProxy allows us to prevent the debugger from
* breaking those assumptions.
* - The engine makes optimizations that are observable to the debugger. The
* proxy can either hide these optimizations or make the situation more
* clear to the debugger. An example is 'arguments'.
*/
class DebugEnvironmentProxyHandler : public BaseProxyHandler
{
enum Action { SET, GET };
enum AccessResult {
ACCESS_UNALIASED,
ACCESS_GENERIC,
ACCESS_LOST
};
/*
* This function handles access to unaliased locals/formals. Since they
* are unaliased, the values of these variables are not stored in the
* slots of the normal CallObject and LexicalEnvironmentObject
* environments and thus must be recovered from somewhere else:
* + if the invocation for which the env was created is still executing,
* there is a JS frame live on the stack holding the values;
* + if the invocation for which the env was created finished executing:
* - and there was a DebugEnvironmentProxy associated with env, then
* the DebugEnvironments::onPop(Call|Lexical) handler copied out the
* unaliased variables. In both cases, a dense array is created in
* onPop(Call|Lexical) to hold the unaliased values and attached to
* the DebugEnvironmentProxy;
* - and there was not a DebugEnvironmentProxy yet associated with the
* scope, then the unaliased values are lost and not recoverable.
*
* Callers should check accessResult for non-failure results:
* - ACCESS_UNALIASED if the access was unaliased and completed
* - ACCESS_GENERIC if the access was aliased or the property not found
* - ACCESS_LOST if the value has been lost to the debugger and the
* action is GET; if the action is SET, we assign to the
* name of the variable on the environment object
*/
bool handleUnaliasedAccess(JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
Handle<EnvironmentObject*> env, HandleId id, Action action,
MutableHandleValue vp, AccessResult* accessResult) const
{
MOZ_ASSERT(&debugEnv->environment() == env);
MOZ_ASSERT_IF(action == SET, !debugEnv->isOptimizedOut());
*accessResult = ACCESS_GENERIC;
LiveEnvironmentVal* maybeLiveEnv = DebugEnvironments::hasLiveEnvironment(*env);
if (env->is<ModuleEnvironmentObject>()) {
/* Everything is aliased and stored in the environment object. */
return true;
}
/* Handle unaliased formals, vars, lets, and consts at function scope. */
if (env->is<CallObject>()) {
CallObject& callobj = env->as<CallObject>();
RootedFunction fun(cx, &callobj.callee());
RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx))
return false;
BindingIter bi(script);
while (bi && NameToId(bi.name()->asPropertyName()) != id)
bi++;
if (!bi)
return true;
if (!bi.hasArgumentSlot()) {
if (bi.closedOver())
return true;
uint32_t i = bi.location().slot();
if (maybeLiveEnv) {
AbstractFramePtr frame = maybeLiveEnv->frame();
if (action == GET)
vp.set(frame.unaliasedLocal(i));
else
frame.unaliasedLocal(i) = vp;
} else if (NativeObject* snapshot = debugEnv->maybeSnapshot()) {
if (action == GET)
vp.set(snapshot->getDenseElement(script->numArgs() + i));
else
snapshot->setDenseElement(script->numArgs() + i, vp);
} else {
/* The unaliased value has been lost to the debugger. */
if (action == GET) {
*accessResult = ACCESS_LOST;
return true;
}
}
} else {
unsigned i = bi.argumentSlot();
if (bi.closedOver())
return true;
if (maybeLiveEnv) {
AbstractFramePtr frame = maybeLiveEnv->frame();
if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
if (action == GET)
vp.set(frame.argsObj().arg(i));
else
frame.argsObj().setArg(i, vp);
} else {
if (action == GET)
vp.set(frame.unaliasedFormal(i, DONT_CHECK_ALIASING));
else
frame.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp;
}
} else if (NativeObject* snapshot = debugEnv->maybeSnapshot()) {
if (action == GET)
vp.set(snapshot->getDenseElement(i));
else
snapshot->setDenseElement(i, vp);
} else {
/* The unaliased value has been lost to the debugger. */
if (action == GET) {
*accessResult = ACCESS_LOST;
return true;
}
}
if (action == SET)
TypeScript::SetArgument(cx, script, i, vp);
}
// It is possible that an optimized out value flows to this
// location due to Debugger.Frame.prototype.eval operating on a
// live bailed-out Baseline frame. In that case, treat the access
// as lost.
if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT)
*accessResult = ACCESS_LOST;
else
*accessResult = ACCESS_UNALIASED;
return true;
}
/*
* Handle unaliased vars in functions with parameter expressions and
* lexical bindings at block scope.
*/
if (env->is<LexicalEnvironmentObject>() || env->is<VarEnvironmentObject>()) {
// Currently consider all global and non-syntactic top-level lexical
// bindings to be aliased.
if (env->is<LexicalEnvironmentObject>() &&
env->as<LexicalEnvironmentObject>().isExtensible())
{
MOZ_ASSERT(IsGlobalLexicalEnvironment(env) || !IsSyntacticEnvironment(env));
return true;
}
// Currently all vars inside eval var environments are aliased.
if (env->is<VarEnvironmentObject>() && env->as<VarEnvironmentObject>().isForEval())
return true;
RootedScope scope(cx, getEnvironmentScope(*env));
uint32_t firstFrameSlot;
if (env->is<LexicalEnvironmentObject>())
firstFrameSlot = scope->as<LexicalScope>().firstFrameSlot();
else
firstFrameSlot = scope->as<VarScope>().firstFrameSlot();
BindingIter bi(scope);
while (bi && NameToId(bi.name()->asPropertyName()) != id)
bi++;
if (!bi)
return true;
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment)
return true;
// Named lambdas that are not closed over are lost.
if (loc.kind() == BindingLocation::Kind::NamedLambdaCallee) {
if (action == GET)
*accessResult = ACCESS_LOST;
return true;
}
MOZ_ASSERT(loc.kind() == BindingLocation::Kind::Frame);
if (maybeLiveEnv) {
AbstractFramePtr frame = maybeLiveEnv->frame();
uint32_t local = loc.slot();
MOZ_ASSERT(local < frame.script()->nfixed());
if (action == GET)
vp.set(frame.unaliasedLocal(local));
else
frame.unaliasedLocal(local) = vp;
} else if (NativeObject* snapshot = debugEnv->maybeSnapshot()) {
// Indices in the frame snapshot are offset by the first frame
// slot. See DebugEnvironments::takeFrameSnapshot.
MOZ_ASSERT(loc.slot() >= firstFrameSlot);
uint32_t snapshotIndex = loc.slot() - firstFrameSlot;
if (action == GET)
vp.set(snapshot->getDenseElement(snapshotIndex));
else
snapshot->setDenseElement(snapshotIndex, vp);
} else {
if (action == GET) {
// A {Lexical,Var}EnvironmentObject whose static scope
// does not have an environment shape at all is a "hollow"
// block object reflected for missing block scopes. Their
// slot values are lost.
if (!scope->hasEnvironment()) {
*accessResult = ACCESS_LOST;
return true;
}
if (!GetProperty(cx, env, env, id, vp))
return false;
} else {
if (!SetProperty(cx, env, id, vp))
return false;
}
}
// See comment above in analogous CallObject case.
if (vp.isMagic() && vp.whyMagic() == JS_OPTIMIZED_OUT)
*accessResult = ACCESS_LOST;
else
*accessResult = ACCESS_UNALIASED;
return true;
}
/* The rest of the internal scopes do not have unaliased vars. */
MOZ_ASSERT(!IsSyntacticEnvironment(env) ||
env->is<WithEnvironmentObject>());
return true;
}
static bool isArguments(JSContext* cx, jsid id)
{
return id == NameToId(cx->names().arguments);
}
static bool isThis(JSContext* cx, jsid id)
{
return id == NameToId(cx->names().dotThis);
}
static bool isFunctionEnvironment(const JSObject& env)
{
return env.is<CallObject>();
}
static bool isNonExtensibleLexicalEnvironment(const JSObject& env)
{
return env.is<LexicalEnvironmentObject>() &&
!env.as<LexicalEnvironmentObject>().isExtensible();
}
static Scope* getEnvironmentScope(const JSObject& env)
{
if (isFunctionEnvironment(env))
return env.as<CallObject>().callee().nonLazyScript()->bodyScope();
if (isNonExtensibleLexicalEnvironment(env))
return &env.as<LexicalEnvironmentObject>().scope();
if (env.is<VarEnvironmentObject>())
return &env.as<VarEnvironmentObject>().scope();
return nullptr;
}
/*
* In theory, every non-arrow function scope contains an 'arguments'
* bindings. However, the engine only adds a binding if 'arguments' is
* used in the function body. Thus, from the debugger's perspective,
* 'arguments' may be missing from the list of bindings.
*/
static bool isMissingArgumentsBinding(EnvironmentObject& env)
{
return isFunctionEnvironment(env) &&
!env.as<CallObject>().callee().nonLazyScript()->argumentsHasVarBinding();
}
/*
* Similar to 'arguments' above, we don't add a 'this' binding to
* non-arrow functions if it's not used.
*/
static bool isMissingThisBinding(EnvironmentObject& env)
{
return isFunctionEnvironmentWithThis(env) &&
!env.as<CallObject>().callee().nonLazyScript()->functionHasThisBinding();
}
/*
* This function checks if an arguments object needs to be created when
* the debugger requests 'arguments' for a function scope where the
* arguments object has been optimized away (either because the binding is
* missing altogether or because !ScriptAnalysis::needsArgsObj).
*/
static bool isMissingArguments(JSContext* cx, jsid id, EnvironmentObject& env)
{
return isArguments(cx, id) && isFunctionEnvironment(env) &&
!env.as<CallObject>().callee().nonLazyScript()->needsArgsObj();
}
static bool isMissingThis(JSContext* cx, jsid id, EnvironmentObject& env)
{
return isThis(cx, id) && isMissingThisBinding(env);
}
/*
* Check if the value is the magic value JS_OPTIMIZED_ARGUMENTS. The
* arguments analysis may have optimized out the 'arguments', and this
* magic value could have propagated to other local slots. e.g.,
*
* function f() { var a = arguments; h(); }
* function h() { evalInFrame(1, "a.push(0)"); }
*
* where evalInFrame(N, str) means to evaluate str N frames up.
*
* In this case we don't know we need to recover a missing arguments
* object until after we've performed the property get.
*/
static bool isMagicMissingArgumentsValue(JSContext* cx, EnvironmentObject& env, HandleValue v)
{
bool isMagic = v.isMagic() && v.whyMagic() == JS_OPTIMIZED_ARGUMENTS;
#ifdef DEBUG
// The |env| object here is not limited to CallObjects but may also
// be lexical envs in case of the following:
//
// function f() { { let a = arguments; } }
//
// We need to check that |env|'s scope's nearest function scope has an
// 'arguments' var binding. The environment chain is not sufficient:
// |f| above will not have a CallObject because there are no aliased
// body-level bindings.
if (isMagic) {
JSFunction* callee = nullptr;
if (isFunctionEnvironment(env)) {
callee = &env.as<CallObject>().callee();
} else {
// We will never have a WithEnvironmentObject here because no
// binding accesses on with scopes are unaliased.
for (ScopeIter si(getEnvironmentScope(env)); si; si++) {
if (si.kind() == ScopeKind::Function) {
callee = si.scope()->as<FunctionScope>().canonicalFunction();
break;
}
}
}
MOZ_ASSERT(callee && callee->nonLazyScript()->argumentsHasVarBinding());
}
#endif
return isMagic;
}
/*
* If the value of |this| is requested before the this-binding has been
* initialized by JSOP_FUNCTIONTHIS, the this-binding will be |undefined|.
* In that case, we have to call createMissingThis to initialize the
* this-binding.
*
* Note that an |undefined| this-binding is perfectly valid in strict-mode
* code, but that's fine: createMissingThis will do the right thing in that
* case.
*/
static bool isMaybeUninitializedThisValue(JSContext* cx, jsid id, const Value& v)
{
return isThis(cx, id) && v.isUndefined();
}
/*
* Create a missing arguments object. If the function returns true but
* argsObj is null, it means the env is dead.
*/
static bool createMissingArguments(JSContext* cx, EnvironmentObject& env,
MutableHandleArgumentsObject argsObj)
{
argsObj.set(nullptr);
LiveEnvironmentVal* maybeEnv = DebugEnvironments::hasLiveEnvironment(env);
if (!maybeEnv)
return true;
argsObj.set(ArgumentsObject::createUnexpected(cx, maybeEnv->frame()));
return !!argsObj;
}
/*
* Create a missing this Value. If the function returns true but
* *success is false, it means the scope is dead.
*/
static bool createMissingThis(JSContext* cx, EnvironmentObject& env,
MutableHandleValue thisv, bool* success)
{
*success = false;
LiveEnvironmentVal* maybeEnv = DebugEnvironments::hasLiveEnvironment(env);
if (!maybeEnv)
return true;
if (!GetFunctionThis(cx, maybeEnv->frame(), thisv))
return false;
// Update the this-argument to avoid boxing primitive |this| more
// than once.
maybeEnv->frame().thisArgument() = thisv;
*success = true;
return true;
}
public:
static const char family;
static const DebugEnvironmentProxyHandler singleton;
constexpr DebugEnvironmentProxyHandler() : BaseProxyHandler(&family) {}
static bool isFunctionEnvironmentWithThis(const JSObject& env)
{
// All functions except arrows and generator expression lambdas should
// have their own this binding.
return isFunctionEnvironment(env) && !env.as<CallObject>().callee().hasLexicalThis();
}
bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const override
{
MOZ_CRASH("shouldn't be possible to access the prototype chain of a DebugEnvironmentProxyHandler");
}
bool preventExtensions(JSContext* cx, HandleObject proxy,
ObjectOpResult& result) const override
{
// always [[Extensible]], can't be made non-[[Extensible]], like most
// proxies
return result.fail(JSMSG_CANT_CHANGE_EXTENSIBILITY);
}
bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override
{
// See above.
*extensible = true;
return true;
}
bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) const override
{
return getOwnPropertyDescriptor(cx, proxy, id, desc);
}
bool getMissingArgumentsPropertyDescriptor(JSContext* cx,
Handle<DebugEnvironmentProxy*> debugEnv,
EnvironmentObject& env,
MutableHandle<PropertyDescriptor> desc) const
{
RootedArgumentsObject argsObj(cx);
if (!createMissingArguments(cx, env, &argsObj))
return false;
if (!argsObj) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger scope");
return false;
}
desc.object().set(debugEnv);
desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
desc.value().setObject(*argsObj);
desc.setGetter(nullptr);
desc.setSetter(nullptr);
return true;
}
bool getMissingThisPropertyDescriptor(JSContext* cx,
Handle<DebugEnvironmentProxy*> debugEnv,
EnvironmentObject& env,
MutableHandle<PropertyDescriptor> desc) const
{
RootedValue thisv(cx);
bool success;
if (!createMissingThis(cx, env, &thisv, &success))
return false;
if (!success) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger scope");
return false;
}
desc.object().set(debugEnv);
desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
desc.value().set(thisv);
desc.setGetter(nullptr);
desc.setSetter(nullptr);
return true;
}
bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) const override
{
Rooted<DebugEnvironmentProxy*> debugEnv(cx, &proxy->as<DebugEnvironmentProxy>());
Rooted<EnvironmentObject*> env(cx, &debugEnv->environment());
if (isMissingArguments(cx, id, *env))
return getMissingArgumentsPropertyDescriptor(cx, debugEnv, *env, desc);
if (isMissingThis(cx, id, *env))
return getMissingThisPropertyDescriptor(cx, debugEnv, *env, desc);
RootedValue v(cx);
AccessResult access;
if (!handleUnaliasedAccess(cx, debugEnv, env, id, GET, &v, &access))
return false;
switch (access) {
case ACCESS_UNALIASED:
if (isMagicMissingArgumentsValue(cx, *env, v))
return getMissingArgumentsPropertyDescriptor(cx, debugEnv, *env, desc);
desc.object().set(debugEnv);
desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
desc.value().set(v);
desc.setGetter(nullptr);
desc.setSetter(nullptr);
return true;
case ACCESS_GENERIC:
return JS_GetOwnPropertyDescriptorById(cx, env, id, desc);
case ACCESS_LOST:
ReportOptimizedOut(cx, id);
return false;
default:
MOZ_CRASH("bad AccessResult");
}
}
bool getMissingArguments(JSContext* cx, EnvironmentObject& env, MutableHandleValue vp) const
{
RootedArgumentsObject argsObj(cx);
if (!createMissingArguments(cx, env, &argsObj))
return false;
if (!argsObj) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger env");
return false;
}
vp.setObject(*argsObj);
return true;
}
bool getMissingThis(JSContext* cx, EnvironmentObject& env, MutableHandleValue vp) const
{
RootedValue thisv(cx);
bool success;
if (!createMissingThis(cx, env, &thisv, &success))
return false;
if (!success) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger env");
return false;
}
vp.set(thisv);
return true;
}
bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
MutableHandleValue vp) const override
{
Rooted<DebugEnvironmentProxy*> debugEnv(cx, &proxy->as<DebugEnvironmentProxy>());
Rooted<EnvironmentObject*> env(cx, &proxy->as<DebugEnvironmentProxy>().environment());
if (isMissingArguments(cx, id, *env))
return getMissingArguments(cx, *env, vp);
if (isMissingThis(cx, id, *env))
return getMissingThis(cx, *env, vp);
AccessResult access;
if (!handleUnaliasedAccess(cx, debugEnv, env, id, GET, vp, &access))
return false;
switch (access) {
case ACCESS_UNALIASED:
if (isMagicMissingArgumentsValue(cx, *env, vp))
return getMissingArguments(cx, *env, vp);
if (isMaybeUninitializedThisValue(cx, id, vp))
return getMissingThis(cx, *env, vp);
return true;
case ACCESS_GENERIC:
if (!GetProperty(cx, env, env, id, vp))
return false;
if (isMaybeUninitializedThisValue(cx, id, vp))
return getMissingThis(cx, *env, vp);
return true;
case ACCESS_LOST:
ReportOptimizedOut(cx, id);
return false;
default:
MOZ_CRASH("bad AccessResult");
}
}
bool getMissingArgumentsMaybeSentinelValue(JSContext* cx, EnvironmentObject& env,
MutableHandleValue vp) const
{
RootedArgumentsObject argsObj(cx);
if (!createMissingArguments(cx, env, &argsObj))
return false;
vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_OPTIMIZED_ARGUMENTS));
return true;
}
bool getMissingThisMaybeSentinelValue(JSContext* cx, EnvironmentObject& env,
MutableHandleValue vp) const
{
RootedValue thisv(cx);
bool success;
if (!createMissingThis(cx, env, &thisv, &success))
return false;
vp.set(success ? thisv : MagicValue(JS_OPTIMIZED_OUT));
return true;
}
/*
* Like 'get', but returns sentinel values instead of throwing on
* exceptional cases.
*/
bool getMaybeSentinelValue(JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
HandleId id, MutableHandleValue vp) const
{
Rooted<EnvironmentObject*> env(cx, &debugEnv->environment());
if (isMissingArguments(cx, id, *env))
return getMissingArgumentsMaybeSentinelValue(cx, *env, vp);
if (isMissingThis(cx, id, *env))
return getMissingThisMaybeSentinelValue(cx, *env, vp);
AccessResult access;
if (!handleUnaliasedAccess(cx, debugEnv, env, id, GET, vp, &access))
return false;
switch (access) {
case ACCESS_UNALIASED:
if (isMagicMissingArgumentsValue(cx, *env, vp))
return getMissingArgumentsMaybeSentinelValue(cx, *env, vp);
if (isMaybeUninitializedThisValue(cx, id, vp))
return getMissingThisMaybeSentinelValue(cx, *env, vp);
return true;
case ACCESS_GENERIC:
if (!GetProperty(cx, env, env, id, vp))
return false;
if (isMaybeUninitializedThisValue(cx, id, vp))
return getMissingThisMaybeSentinelValue(cx, *env, vp);
return true;
case ACCESS_LOST:
vp.setMagic(JS_OPTIMIZED_OUT);
return true;
default:
MOZ_CRASH("bad AccessResult");
}
}
bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver,
ObjectOpResult& result) const override
{
Rooted<DebugEnvironmentProxy*> debugEnv(cx, &proxy->as<DebugEnvironmentProxy>());
Rooted<EnvironmentObject*> env(cx, &proxy->as<DebugEnvironmentProxy>().environment());
if (debugEnv->isOptimizedOut())
return Throw(cx, id, JSMSG_DEBUG_CANT_SET_OPT_ENV);
AccessResult access;
RootedValue valCopy(cx, v);
if (!handleUnaliasedAccess(cx, debugEnv, env, id, SET, &valCopy, &access))
return false;
switch (access) {
case ACCESS_UNALIASED:
return result.succeed();
case ACCESS_GENERIC: {
RootedValue envVal(cx, ObjectValue(*env));
return SetProperty(cx, env, id, v, envVal, result);
}
default:
MOZ_CRASH("bad AccessResult");
}
}
bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) const override
{
Rooted<EnvironmentObject*> env(cx, &proxy->as<DebugEnvironmentProxy>().environment());
bool found;
if (!has(cx, proxy, id, &found))
return false;
if (found)
return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP);
return JS_DefinePropertyById(cx, env, id, desc, result);
}
bool ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const override
{
Rooted<EnvironmentObject*> env(cx, &proxy->as<DebugEnvironmentProxy>().environment());
if (isMissingArgumentsBinding(*env)) {
if (!props.append(NameToId(cx->names().arguments)))
return false;
}
if (isMissingThisBinding(*env)) {
if (!props.append(NameToId(cx->names().dotThis)))
return false;
}
// WithEnvironmentObject isn't a very good proxy. It doesn't have a
// JSNewEnumerateOp implementation, because if it just delegated to the
// target object, the object would indicate that native enumeration is
// the thing to do, but native enumeration over the WithEnvironmentObject
// wrapper yields no properties. So instead here we hack around the
// issue: punch a hole through to the with object target, then manually
// examine @@unscopables.
RootedObject target(cx);
bool isWith = env->is<WithEnvironmentObject>();
if (isWith)
target = &env->as<WithEnvironmentObject>().object();
else
target = env;
if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props))
return false;
if (isWith) {
size_t j = 0;
for (size_t i = 0; i < props.length(); i++) {
bool inScope;
if (!CheckUnscopables(cx, env, props[i], &inScope))
return false;
if (inScope)
props[j++].set(props[i]);
}
if (!props.resize(j))
return false;
}
/*
* Environments with Scopes are optimized to not contain unaliased
* variables so they must be manually appended here.
*/
if (Scope* scope = getEnvironmentScope(*env)) {
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
if (!bi.closedOver() && !props.append(NameToId(bi.name()->asPropertyName())))
return false;
}
}
return true;
}
bool has(JSContext* cx, HandleObject proxy, HandleId id_, bool* bp) const override
{
RootedId id(cx, id_);
EnvironmentObject& envObj = proxy->as<DebugEnvironmentProxy>().environment();
if (isArguments(cx, id) && isFunctionEnvironment(envObj)) {
*bp = true;
return true;
}
// Be careful not to look up '.this' as a normal binding below, it will
// assert in with_HasProperty.
if (isThis(cx, id)) {
*bp = isFunctionEnvironmentWithThis(envObj);
return true;
}
bool found;
RootedObject env(cx, &envObj);
if (!JS_HasPropertyById(cx, env, id, &found))
return false;
if (!found) {
if (Scope* scope = getEnvironmentScope(*env)) {
for (BindingIter bi(scope); bi; bi++) {
if (!bi.closedOver() && NameToId(bi.name()->asPropertyName()) == id) {
found = true;
break;
}
}
}
}
*bp = found;
return true;
}
bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
ObjectOpResult& result) const override
{
return result.fail(JSMSG_CANT_DELETE);
}
};
} /* anonymous namespace */
template<>
bool
JSObject::is<js::DebugEnvironmentProxy>() const
{
return IsDerivedProxyObject(this, &DebugEnvironmentProxyHandler::singleton);
}
const char DebugEnvironmentProxyHandler::family = 0;
const DebugEnvironmentProxyHandler DebugEnvironmentProxyHandler::singleton;
/* static */ DebugEnvironmentProxy*
DebugEnvironmentProxy::create(JSContext* cx, EnvironmentObject& env, HandleObject enclosing)
{
MOZ_ASSERT(env.compartment() == cx->compartment());
MOZ_ASSERT(!enclosing->is<EnvironmentObject>());
RootedValue priv(cx, ObjectValue(env));
JSObject* obj = NewProxyObject(cx, &DebugEnvironmentProxyHandler::singleton, priv,
nullptr /* proto */);
if (!obj)
return nullptr;
DebugEnvironmentProxy* debugEnv = &obj->as<DebugEnvironmentProxy>();
debugEnv->setExtra(ENCLOSING_EXTRA, ObjectValue(*enclosing));
debugEnv->setExtra(SNAPSHOT_EXTRA, NullValue());
return debugEnv;
}
EnvironmentObject&
DebugEnvironmentProxy::environment() const
{
return target()->as<EnvironmentObject>();
}
JSObject&
DebugEnvironmentProxy::enclosingEnvironment() const
{
return extra(ENCLOSING_EXTRA).toObject();
}
ArrayObject*
DebugEnvironmentProxy::maybeSnapshot() const
{
JSObject* obj = extra(SNAPSHOT_EXTRA).toObjectOrNull();
return obj ? &obj->as<ArrayObject>() : nullptr;
}
void
DebugEnvironmentProxy::initSnapshot(ArrayObject& o)
{
MOZ_ASSERT(maybeSnapshot() == nullptr);
setExtra(SNAPSHOT_EXTRA, ObjectValue(o));
}
bool
DebugEnvironmentProxy::isForDeclarative() const
{
EnvironmentObject& e = environment();
return e.is<CallObject>() ||
e.is<VarEnvironmentObject>() ||
e.is<ModuleEnvironmentObject>() ||
e.is<LexicalEnvironmentObject>();
}
/* 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.as<LexicalEnvironmentObject>().isExtensible() &&
!e.as<LexicalEnvironmentObject>().scope().hasEnvironment();
}
if (e.is<CallObject>()) {
return !e.as<CallObject>().callee().needsCallObject() &&
!maybeSnapshot();
}
return false;
}
/*****************************************************************************/
DebugEnvironments::DebugEnvironments(JSContext* cx)
: proxiedEnvs(cx),
missingEnvs(cx->runtime()),
liveEnvs(cx->runtime())
{}
DebugEnvironments::~DebugEnvironments()
{
MOZ_ASSERT_IF(missingEnvs.initialized(), missingEnvs.empty());
}
bool
DebugEnvironments::init()
{
return proxiedEnvs.init() && missingEnvs.init() && liveEnvs.init();
}
void
DebugEnvironments::mark(JSTracer* trc)
{
proxiedEnvs.trace(trc);
}
void
DebugEnvironments::sweep(JSRuntime* rt)
{
/*
* missingEnvs points to debug envs weakly so that debug envs can be
* released more eagerly.
*/
for (MissingEnvironmentMap::Enum e(missingEnvs); !e.empty(); e.popFront()) {
if (IsAboutToBeFinalized(&e.front().value())) {
/*
* Note that onPopCall, onPopVar, and onPopLexical rely on
* missingEnvs to find environment objects that we synthesized for
* the debugger's sake, and clean up the synthetic environment
* objects' entries in liveEnvs. So if we remove an entry from
* missingEnvs here, we must also remove the corresponding
* liveEnvs entry.
*
* Since the DebugEnvironmentProxy is the only thing using its environment
* object, and the DSO is about to be finalized, you might assume
* that the synthetic SO is also about to be finalized too, and thus
* the loop below will take care of things. But complex GC behavior
* means that marks are only conservative approximations of
* liveness; we should assume that anything could be marked.
*
* Thus, we must explicitly remove the entries from both liveEnvs
* and missingEnvs here.
*/
liveEnvs.remove(&e.front().value().unbarrieredGet()->environment());
e.removeFront();
} else {
MissingEnvironmentKey key = e.front().key();
if (IsForwarded(key.scope())) {
key.updateScope(Forwarded(key.scope()));
e.rekeyFront(key);
}
}
}
/*
* Scopes can be finalized when a debugger-synthesized EnvironmentObject is
* no longer reachable via its DebugEnvironmentProxy.
*/
liveEnvs.sweep();
}
void
DebugEnvironments::finish()
{
proxiedEnvs.clear();
}
#ifdef JSGC_HASH_TABLE_CHECKS
void
DebugEnvironments::checkHashTablesAfterMovingGC(JSRuntime* runtime)
{
/*
* This is called at the end of StoreBuffer::mark() to check that our
* postbarriers have worked and that no hashtable keys (or values) are left
* pointing into the nursery.
*/
proxiedEnvs.checkAfterMovingGC();
for (MissingEnvironmentMap::Range r = missingEnvs.all(); !r.empty(); r.popFront()) {
CheckGCThingAfterMovingGC(r.front().key().scope());
CheckGCThingAfterMovingGC(r.front().value().get());
}
for (LiveEnvironmentMap::Range r = liveEnvs.all(); !r.empty(); r.popFront()) {
CheckGCThingAfterMovingGC(r.front().key());
CheckGCThingAfterMovingGC(r.front().value().scope_.get());
}
}
#endif
/*
* Unfortunately, GetDebugEnvironmentForFrame needs to work even outside debug mode
* (in particular, JS_GetFrameScopeChain does not require debug mode). Since
* DebugEnvironments::onPop* are only called in debuggee frames, this means we
* cannot use any of the maps in DebugEnvironments. This will produce debug scope
* chains that do not obey the debugger invariants but that is just fine.
*/
static bool
CanUseDebugEnvironmentMaps(JSContext* cx)
{
return cx->compartment()->isDebuggee();
}
DebugEnvironments*
DebugEnvironments::ensureCompartmentData(JSContext* cx)
{
JSCompartment* c = cx->compartment();
if (c->debugEnvs)
return c->debugEnvs;
auto debugEnvs = cx->make_unique<DebugEnvironments>(cx);
if (!debugEnvs || !debugEnvs->init()) {
ReportOutOfMemory(cx);
return nullptr;
}
c->debugEnvs = debugEnvs.release();
return c->debugEnvs;
}
/* static */ DebugEnvironmentProxy*
DebugEnvironments::hasDebugEnvironment(JSContext* cx, EnvironmentObject& env)
{
DebugEnvironments* envs = env.compartment()->debugEnvs;
if (!envs)
return nullptr;
if (JSObject* obj = envs->proxiedEnvs.lookup(&env)) {
MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
return &obj->as<DebugEnvironmentProxy>();
}
return nullptr;
}
/* static */ bool
DebugEnvironments::addDebugEnvironment(JSContext* cx, Handle<EnvironmentObject*> env,
Handle<DebugEnvironmentProxy*> debugEnv)
{
MOZ_ASSERT(cx->compartment() == env->compartment());
MOZ_ASSERT(cx->compartment() == debugEnv->compartment());
if (!CanUseDebugEnvironmentMaps(cx))
return true;
DebugEnvironments* envs = ensureCompartmentData(cx);
if (!envs)
return false;
return envs->proxiedEnvs.add(cx, env, debugEnv);
}
/* static */ DebugEnvironmentProxy*
DebugEnvironments::hasDebugEnvironment(JSContext* cx, const EnvironmentIter& ei)
{
MOZ_ASSERT(!ei.hasSyntacticEnvironment());
DebugEnvironments* envs = cx->compartment()->debugEnvs;
if (!envs)
return nullptr;
if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) {
MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
return p->value();
}
return nullptr;
}
/* static */ bool
DebugEnvironments::addDebugEnvironment(JSContext* cx, const EnvironmentIter& ei,
Handle<DebugEnvironmentProxy*> debugEnv)
{
MOZ_ASSERT(!ei.hasSyntacticEnvironment());
MOZ_ASSERT(cx->compartment() == debugEnv->compartment());
// Generators should always have environments.
MOZ_ASSERT_IF(ei.scope().is<FunctionScope>(),
!ei.scope().as<FunctionScope>().canonicalFunction()->isStarGenerator() &&
!ei.scope().as<FunctionScope>().canonicalFunction()->isLegacyGenerator() &&
!ei.scope().as<FunctionScope>().canonicalFunction()->isAsync());
if (!CanUseDebugEnvironmentMaps(cx))
return true;
DebugEnvironments* envs = ensureCompartmentData(cx);
if (!envs)
return false;
MissingEnvironmentKey key(ei);
MOZ_ASSERT(!envs->missingEnvs.has(key));
if (!envs->missingEnvs.put(key, ReadBarriered<DebugEnvironmentProxy*>(debugEnv))) {
ReportOutOfMemory(cx);
return false;
}
// Only add to liveEnvs if we synthesized the debug env on a live
// frame.
if (ei.withinInitialFrame()) {
MOZ_ASSERT(!envs->liveEnvs.has(&debugEnv->environment()));
if (!envs->liveEnvs.put(&debugEnv->environment(), LiveEnvironmentVal(ei))) {
ReportOutOfMemory(cx);
return false;
}
}
return true;
}
/* static */ void
DebugEnvironments::takeFrameSnapshot(JSContext* cx, Handle<DebugEnvironmentProxy*> debugEnv,
AbstractFramePtr frame)
{
/*
* When the JS stack frame is popped, the values of unaliased variables
* are lost. If there is any debug env referring to this environment, save a
* copy of the unaliased variables' values in an array for later debugger
* access via DebugEnvironmentProxy::handleUnaliasedAccess.
*
* Note: since it is simplest for this function to be infallible, failure
* in this code will be silently ignored. This does not break any
* invariants since DebugEnvironmentProxy::maybeSnapshot can already be nullptr.
*/
// Act like no snapshot was taken if we run OOM while taking the snapshot.
Rooted<GCVector<Value>> vec(cx, GCVector<Value>(cx));
if (debugEnv->environment().is<CallObject>()) {
JSScript* script = frame.script();
FunctionScope* scope = &script->bodyScope()->as<FunctionScope>();
uint32_t frameSlotCount = scope->nextFrameSlot();
MOZ_ASSERT(frameSlotCount <= script->nfixed());
// For simplicity, copy all frame slots from 0 to the frameSlotCount,
// even if we don't need all of them (like in the case of a defaults
// parameter scope having frame slots).
uint32_t numFormals = frame.numFormalArgs();
if (!vec.resize(numFormals + frameSlotCount)) {
cx->recoverFromOutOfMemory();
return;
}
mozilla::PodCopy(vec.begin(), frame.argv(), numFormals);
for (uint32_t slot = 0; slot < frameSlotCount; slot++)
vec[slot + frame.numFormalArgs()].set(frame.unaliasedLocal(slot));
/*
* Copy in formals that are not aliased via the scope chain
* but are aliased via the arguments object.
*/
if (script->analyzedArgsUsage() && script->needsArgsObj() && frame.hasArgsObj()) {
for (unsigned i = 0; i < frame.numFormalArgs(); ++i) {
if (script->formalLivesInArgumentsObject(i))
vec[i].set(frame.argsObj().arg(i));
}
}
} else {
uint32_t frameSlotStart;
uint32_t frameSlotEnd;
if (debugEnv->environment().is<LexicalEnvironmentObject>()) {
LexicalScope* scope = &debugEnv->environment().as<LexicalEnvironmentObject>().scope();
frameSlotStart = scope->firstFrameSlot();
frameSlotEnd = scope->nextFrameSlot();
} else {
VarEnvironmentObject* env = &debugEnv->environment().as<VarEnvironmentObject>();
if (frame.isFunctionFrame()) {
VarScope* scope = &env->scope().as<VarScope>();
frameSlotStart = scope->firstFrameSlot();
frameSlotEnd = scope->nextFrameSlot();
} else {
EvalScope* scope = &env->scope().as<EvalScope>();
MOZ_ASSERT(scope == frame.script()->bodyScope());
frameSlotStart = 0;
frameSlotEnd = scope->nextFrameSlot();
}
}
uint32_t frameSlotCount = frameSlotEnd - frameSlotStart;
MOZ_ASSERT(frameSlotCount <= frame.script()->nfixed());
if (!vec.resize(frameSlotCount)) {
cx->recoverFromOutOfMemory();
return;
}
for (uint32_t slot = frameSlotStart; slot < frameSlotCount; slot++)
vec[slot - frameSlotStart].set(frame.unaliasedLocal(slot));
}
if (vec.length() == 0)
return;
/*
* Use a dense array as storage (since proxies do not have trace
* hooks). This array must not escape into the wild.
*/
RootedArrayObject snapshot(cx, NewDenseCopiedArray(cx, vec.length(), vec.begin()));
if (!snapshot) {
cx->recoverFromOutOfMemory();
return;
}
debugEnv->initSnapshot(*snapshot);
}
/* static */ void
DebugEnvironments::onPopCall(JSContext* cx, AbstractFramePtr frame)
{
assertSameCompartment(cx, frame);
DebugEnvironments* envs = cx->compartment()->debugEnvs;
if (!envs)
return;
Rooted<DebugEnvironmentProxy*> debugEnv(cx, nullptr);
FunctionScope* funScope = &frame.script()->bodyScope()->as<FunctionScope>();
if (funScope->hasEnvironment()) {
MOZ_ASSERT(frame.callee()->needsCallObject());
/*
* The frame may be observed before the prologue has created the
* CallObject. See EnvironmentIter::settle.
*/
if (!frame.environmentChain()->is<CallObject>())
return;
if (frame.callee()->isStarGenerator() || frame.callee()->isLegacyGenerator() ||
frame.callee()->isAsync())
{
return;
}
CallObject& callobj = frame.environmentChain()->as<CallObject>();
envs->liveEnvs.remove(&callobj);
if (JSObject* obj = envs->proxiedEnvs.lookup(&callobj))
debugEnv = &obj->as<DebugEnvironmentProxy>();
} else {
MissingEnvironmentKey key(frame, funScope);
if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(key)) {
debugEnv = p->value();
envs->liveEnvs.remove(&debugEnv->environment().as<CallObject>());
envs->missingEnvs.remove(p);
}
}
if (debugEnv)
DebugEnvironments::takeFrameSnapshot(cx, debugEnv, frame);
}
void
DebugEnvironments::onPopLexical(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
assertSameCompartment(cx, frame);
DebugEnvironments* envs = cx->compartment()->debugEnvs;
if (!envs)
return;
EnvironmentIter ei(cx, frame, pc);
onPopLexical(cx, ei);
}
template <typename Environment, typename Scope>
void
DebugEnvironments::onPopGeneric(JSContext* cx, const EnvironmentIter& ei)
{
DebugEnvironments* envs = cx->compartment()->debugEnvs;
if (!envs)
return;
MOZ_ASSERT(ei.withinInitialFrame());
MOZ_ASSERT(ei.scope().is<Scope>());
Rooted<Environment*> env(cx);
if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) {
env = &p->value()->environment().as<Environment>();
envs->missingEnvs.remove(p);
} else if (ei.hasSyntacticEnvironment()) {
env = &ei.environment().as<Environment>();
}
if (env) {
envs->liveEnvs.remove(env);
if (JSObject* obj = envs->proxiedEnvs.lookup(env)) {
Rooted<DebugEnvironmentProxy*> debugEnv(cx, &obj->as<DebugEnvironmentProxy>());
DebugEnvironments::takeFrameSnapshot(cx, debugEnv, ei.initialFrame());
}
}
}
void
DebugEnvironments::onPopLexical(JSContext* cx, const EnvironmentIter& ei)
{
onPopGeneric<LexicalEnvironmentObject, LexicalScope>(cx, ei);
}
void
DebugEnvironments::onPopVar(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
assertSameCompartment(cx, frame);
DebugEnvironments* envs = cx->compartment()->debugEnvs;
if (!envs)
return;
EnvironmentIter ei(cx, frame, pc);
onPopVar(cx, ei);
}
void
DebugEnvironments::onPopVar(JSContext* cx, const EnvironmentIter& ei)
{
if (ei.scope().is<EvalScope>())
onPopGeneric<VarEnvironmentObject, EvalScope>(cx, ei);
else
onPopGeneric<VarEnvironmentObject, VarScope>(cx, ei);
}
void
DebugEnvironments::onPopWith(AbstractFramePtr frame)
{
if (DebugEnvironments* envs = frame.compartment()->debugEnvs)
envs->liveEnvs.remove(&frame.environmentChain()->as<WithEnvironmentObject>());
}
void
DebugEnvironments::onCompartmentUnsetIsDebuggee(JSCompartment* c)
{
if (DebugEnvironments* envs = c->debugEnvs) {
envs->proxiedEnvs.clear();
envs->missingEnvs.clear();
envs->liveEnvs.clear();
}
}
bool
DebugEnvironments::updateLiveEnvironments(JSContext* cx)
{
JS_CHECK_RECURSION(cx, return false);
/*
* Note that we must always update the top frame's environment objects'
* entries in liveEnvs because we can't be sure code hasn't run in that
* frame to change the environment chain since we were last called. The
* fp->prevUpToDate() flag indicates whether the environments of frames
* older than fp are already included in liveEnvs. It might seem simpler
* to have fp instead carry a flag indicating whether fp itself is
* accurately described, but then we would need to clear that flag
* whenever fp ran code. By storing the 'up to date' bit for fp->prev() in
* fp, simply popping fp effectively clears the flag for us, at exactly
* the time when execution resumes fp->prev().
*/
for (AllFramesIter i(cx); !i.done(); ++i) {
if (!i.hasUsableAbstractFramePtr())
continue;
AbstractFramePtr frame = i.abstractFramePtr();
if (frame.environmentChain()->compartment() != cx->compartment())
continue;
if (frame.isFunctionFrame()) {
if (frame.callee()->isStarGenerator() || frame.callee()->isLegacyGenerator() ||
frame.callee()->isAsync())
{
continue;
}
}
if (!frame.isDebuggee())
continue;
for (EnvironmentIter ei(cx, frame, i.pc()); ei.withinInitialFrame(); ei++) {
if (ei.hasSyntacticEnvironment() && !ei.scope().is<GlobalScope>()) {
MOZ_ASSERT(ei.environment().compartment() == cx->compartment());
DebugEnvironments* envs = ensureCompartmentData(cx);
if (!envs)
return false;
if (!envs->liveEnvs.put(&ei.environment(), LiveEnvironmentVal(ei)))
return false;
}
}
if (frame.prevUpToDate())
return true;
MOZ_ASSERT(frame.environmentChain()->compartment()->isDebuggee());
frame.setPrevUpToDate();
}
return true;
}
LiveEnvironmentVal*
DebugEnvironments::hasLiveEnvironment(EnvironmentObject& env)
{
DebugEnvironments* envs = env.compartment()->debugEnvs;
if (!envs)
return nullptr;
if (LiveEnvironmentMap::Ptr p = envs->liveEnvs.lookup(&env))
return &p->value();
return nullptr;
}
/* static */ void
DebugEnvironments::unsetPrevUpToDateUntil(JSContext* cx, AbstractFramePtr until)
{
// This are two exceptions where fp->prevUpToDate() is cleared without
// popping the frame. When a frame is rematerialized or has its
// debuggeeness toggled off->on, all frames younger than the frame must
// have their prevUpToDate set to false. This is because unrematerialized
// Ion frames and non-debuggee frames are skipped by updateLiveEnvironments. If
// in the future a frame suddenly gains a usable AbstractFramePtr via
// rematerialization or becomes a debuggee, the prevUpToDate invariant
// will no longer hold for older frames on its stack.
for (AllFramesIter i(cx); !i.done(); ++i) {
if (!i.hasUsableAbstractFramePtr())
continue;
AbstractFramePtr frame = i.abstractFramePtr();
if (frame == until)
return;
if (frame.environmentChain()->compartment() != cx->compartment())
continue;
frame.unsetPrevUpToDate();
}
}
/* static */ void
DebugEnvironments::forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to)
{
DebugEnvironments* envs = cx->compartment()->debugEnvs;
if (!envs)
return;
for (MissingEnvironmentMap::Enum e(envs->missingEnvs); !e.empty(); e.popFront()) {
MissingEnvironmentKey key = e.front().key();
if (key.frame() == from) {
key.updateFrame(to);
e.rekeyFront(key);
}
}
for (LiveEnvironmentMap::Enum e(envs->liveEnvs); !e.empty(); e.popFront()) {
LiveEnvironmentVal& val = e.front().value();
if (val.frame() == from)
val.updateFrame(to);
}
}
/* static */ void
DebugEnvironments::markLiveFrame(JSTracer* trc, AbstractFramePtr frame)
{
for (MissingEnvironmentMap::Enum e(missingEnvs); !e.empty(); e.popFront()) {
if (e.front().key().frame() == frame)
TraceEdge(trc, &e.front().value(), "debug-env-live-frame-missing-env");
}
}
/*****************************************************************************/
static JSObject*
GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei);
static DebugEnvironmentProxy*
GetDebugEnvironmentForEnvironmentObject(JSContext* cx, const EnvironmentIter& ei)
{
Rooted<EnvironmentObject*> env(cx, &ei.environment());
if (DebugEnvironmentProxy* debugEnv = DebugEnvironments::hasDebugEnvironment(cx, *env))
return debugEnv;
EnvironmentIter copy(cx, ei);
RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
if (!enclosingDebug)
return nullptr;
Rooted<DebugEnvironmentProxy*> debugEnv(cx,
DebugEnvironmentProxy::create(cx, *env, enclosingDebug));
if (!debugEnv)
return nullptr;
if (!DebugEnvironments::addDebugEnvironment(cx, env, debugEnv))
return nullptr;
return debugEnv;
}
static DebugEnvironmentProxy*
GetDebugEnvironmentForMissing(JSContext* cx, const EnvironmentIter& ei)
{
MOZ_ASSERT(!ei.hasSyntacticEnvironment() &&
(ei.scope().is<FunctionScope>() ||
ei.scope().is<LexicalScope>() ||
ei.scope().is<VarScope>()));
if (DebugEnvironmentProxy* debugEnv = DebugEnvironments::hasDebugEnvironment(cx, ei))
return debugEnv;
EnvironmentIter copy(cx, ei);
RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
if (!enclosingDebug)
return nullptr;
/*
* Create the missing environment object. For lexical environment objects,
* this takes care of storing variable values after the stack frame has
* been popped. For call objects, we only use the pretend call object to
* access callee, bindings and to receive dynamically added
* properties. Together, this provides the nice invariant that every
* DebugEnvironmentProxy has a EnvironmentObject.
*
* Note: to preserve envChain depth invariants, these lazily-reified
* envs must not be put on the frame's environment chain; instead, they are
* maintained via DebugEnvironments hooks.
*/
Rooted<DebugEnvironmentProxy*> debugEnv(cx);
if (ei.scope().is<FunctionScope>()) {
RootedFunction callee(cx, ei.scope().as<FunctionScope>().canonicalFunction());
// Generators should always reify their scopes.
MOZ_ASSERT(!callee->isStarGenerator() && !callee->isLegacyGenerator() &&
!callee->isAsync());
JS::ExposeObjectToActiveJS(callee);
Rooted<CallObject*> callobj(cx, CallObject::createHollowForDebug(cx, callee));
if (!callobj)
return nullptr;
debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug);
} else if (ei.scope().is<LexicalScope>()) {
Rooted<LexicalScope*> lexicalScope(cx, &ei.scope().as<LexicalScope>());
Rooted<LexicalEnvironmentObject*> env(cx,
LexicalEnvironmentObject::createHollowForDebug(cx, lexicalScope));
if (!env)
return nullptr;
debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
} else {
Rooted<VarScope*> varScope(cx, &ei.scope().as<VarScope>());
Rooted<VarEnvironmentObject*> env(cx,
VarEnvironmentObject::createHollowForDebug(cx, varScope));
if (!env)
return nullptr;
debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
}
if (!debugEnv)
return nullptr;
if (!DebugEnvironments::addDebugEnvironment(cx, ei, debugEnv))
return nullptr;
return debugEnv;
}
static JSObject*
GetDebugEnvironmentForNonEnvironmentObject(const EnvironmentIter& ei)
{
JSObject& enclosing = ei.enclosingEnvironment();
#ifdef DEBUG
JSObject* o = &enclosing;
while ((o = o->enclosingEnvironment()))
MOZ_ASSERT(!o->is<EnvironmentObject>());
#endif
return &enclosing;
}
static JSObject*
GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei)
{
JS_CHECK_RECURSION(cx, return nullptr);
if (ei.done())
return GetDebugEnvironmentForNonEnvironmentObject(ei);
if (ei.hasAnyEnvironmentObject())
return GetDebugEnvironmentForEnvironmentObject(cx, ei);
if (ei.scope().is<FunctionScope>() ||
ei.scope().is<LexicalScope>() ||
ei.scope().is<VarScope>())
{
return GetDebugEnvironmentForMissing(cx, ei);
}
EnvironmentIter copy(cx, ei);
return GetDebugEnvironment(cx, ++copy);
}
JSObject*
js::GetDebugEnvironmentForFunction(JSContext* cx, HandleFunction fun)
{
assertSameCompartment(cx, fun);
MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx));
if (!DebugEnvironments::updateLiveEnvironments(cx))
return nullptr;
JSScript* script = JSFunction::getOrCreateScript(cx, fun);
if (!script)
return nullptr;
EnvironmentIter ei(cx, fun->environment(), script->enclosingScope());
return GetDebugEnvironment(cx, ei);
}
JSObject*
js::GetDebugEnvironmentForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
assertSameCompartment(cx, frame);
if (CanUseDebugEnvironmentMaps(cx) && !DebugEnvironments::updateLiveEnvironments(cx))
return nullptr;
EnvironmentIter ei(cx, frame, pc);
return GetDebugEnvironment(cx, ei);
}
JSObject*
js::GetDebugEnvironmentForGlobalLexicalEnvironment(JSContext* cx)
{
EnvironmentIter ei(cx, &cx->global()->lexicalEnvironment(), &cx->global()->emptyGlobalScope());
return GetDebugEnvironment(cx, ei);
}
// See declaration and documentation in jsfriendapi.h
JS_FRIEND_API(JSObject*)
js::GetNearestEnclosingWithEnvironmentObjectForFunction(JSFunction* fun)
{
if (!fun->isInterpreted())
return &fun->global();
JSObject* env = fun->environment();
while (env && !env->is<WithEnvironmentObject>())
env = env->enclosingEnvironment();
if (!env)
return &fun->global();
return &env->as<WithEnvironmentObject>().object();
}
bool
js::CreateObjectsForEnvironmentChain(JSContext* cx, AutoObjectVector& chain,
HandleObject terminatingEnv, MutableHandleObject envObj)
{
#ifdef DEBUG
for (size_t i = 0; i < chain.length(); ++i) {
assertSameCompartment(cx, chain[i]);
MOZ_ASSERT(!chain[i]->is<GlobalObject>());
}
#endif
// Construct With object wrappers for the things on this environment chain
// and use the result as the thing to scope the function to.
Rooted<WithEnvironmentObject*> withEnv(cx);
RootedObject enclosingEnv(cx, terminatingEnv);
for (size_t i = chain.length(); i > 0; ) {
withEnv = WithEnvironmentObject::createNonSyntactic(cx, chain[--i], enclosingEnv);
if (!withEnv)
return false;
enclosingEnv = withEnv;
}
envObj.set(enclosingEnv);
return true;
}
JSObject&
WithEnvironmentObject::object() const
{
return getReservedSlot(OBJECT_SLOT).toObject();
}
JSObject*
WithEnvironmentObject::withThis() const
{
return &getReservedSlot(THIS_SLOT).toObject();
}
bool
WithEnvironmentObject::isSyntactic() const
{
Value v = getReservedSlot(SCOPE_SLOT);
MOZ_ASSERT(v.isPrivateGCThing() || v.isNull());
return v.isPrivateGCThing();
}
WithScope&
WithEnvironmentObject::scope() const
{
MOZ_ASSERT(isSyntactic());
return *static_cast<WithScope*>(getReservedSlot(SCOPE_SLOT).toGCThing());
}
ModuleEnvironmentObject*
js::GetModuleEnvironmentForScript(JSScript* script)
{
for (ScopeIter si(script); si; si++) {
if (si.kind() == ScopeKind::Module)
return si.scope()->as<ModuleScope>().module()->environment();
}
return nullptr;
}
bool
js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
MutableHandleValue res)
{
for (EnvironmentIter ei(cx, frame, pc); ei; ei++) {
if (ei.scope().kind() == ScopeKind::Module) {
res.setUndefined();
return true;
}
if (!ei.scope().is<FunctionScope>() ||
ei.scope().as<FunctionScope>().canonicalFunction()->hasLexicalThis())
{
continue;
}
RootedScript script(cx, ei.scope().as<FunctionScope>().script());
// Figure out if we executed JSOP_FUNCTIONTHIS and set it.
bool executedInitThisOp = false;
if (script->functionHasThisBinding()) {
for (jsbytecode* it = script->code(); it < script->codeEnd(); it = GetNextPc(it)) {
if (*it == JSOP_FUNCTIONTHIS) {
// The next op after JSOP_FUNCTIONTHIS always sets it.
executedInitThisOp = pc > GetNextPc(it);
break;
}
}
}
if (ei.withinInitialFrame() && !executedInitThisOp) {
// Either we're yet to initialize the this-binding
// (JSOP_FUNCTIONTHIS), or the script does not have a this-binding
// (because it doesn't use |this|).
// If our this-argument is an object, or we're in strict mode,
// the this-binding is always the same as our this-argument.
if (frame.thisArgument().isObject() || script->strict()) {
res.set(frame.thisArgument());
return true;
}
// We didn't initialize the this-binding yet. Determine the
// correct |this| value for this frame (box primitives if not
// in strict mode), and assign it to the this-argument slot so
// JSOP_FUNCTIONTHIS will use it and not box a second time.
if (!GetFunctionThis(cx, frame, res))
return false;
frame.thisArgument() = res;
return true;
}
if (!script->functionHasThisBinding()) {
res.setMagic(JS_OPTIMIZED_OUT);
return true;
}
for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
if (bi.name() != cx->names().dotThis)
continue;
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment) {
RootedObject callObj(cx, &ei.environment().as<CallObject>());
return GetProperty(cx, callObj, callObj, bi.name()->asPropertyName(), res);
}
if (loc.kind() == BindingLocation::Kind::Frame && ei.withinInitialFrame())
res.set(frame.unaliasedLocal(loc.slot()));
else
res.setMagic(JS_OPTIMIZED_OUT);
return true;
}
MOZ_CRASH("'this' binding must be found");
}
RootedObject scopeChain(cx, frame.environmentChain());
return GetNonSyntacticGlobalThis(cx, scopeChain, res);
}
bool
js::CheckLexicalNameConflict(JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
HandleObject varObj, HandlePropertyName name)
{
const char* redeclKind = nullptr;
RootedId id(cx, NameToId(name));
RootedShape shape(cx);
if (varObj->is<GlobalObject>() && varObj->compartment()->isInVarNames(name)) {
// ES 15.1.11 step 5.a
redeclKind = "var";
} else if ((shape = lexicalEnv->lookup(cx, name))) {
// ES 15.1.11 step 5.b
redeclKind = shape->writable() ? "let" : "const";
} else if (varObj->isNative() && (shape = varObj->as<NativeObject>().lookup(cx, name))) {
// Faster path for ES 15.1.11 step 5.c-d when the shape can be found
// without going through a resolve hook.
if (!shape->configurable())
redeclKind = "non-configurable global property";
} else {
// ES 15.1.11 step 5.c-d
Rooted<PropertyDescriptor> desc(cx);
if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc))
return false;
if (desc.object() && desc.hasConfigurable() && !desc.configurable())
redeclKind = "non-configurable global property";
}
if (redeclKind) {
ReportRuntimeRedeclaration(cx, name, redeclKind);
return false;
}
return true;
}
bool
js::CheckVarNameConflict(JSContext* cx, Handle<LexicalEnvironmentObject*> lexicalEnv,
HandlePropertyName name)
{
if (Shape* shape = lexicalEnv->lookup(cx, name)) {
ReportRuntimeRedeclaration(cx, name, shape->writable() ? "let" : "const");
return false;
}
return true;
}
static void
ReportCannotDeclareGlobalBinding(JSContext* cx, HandlePropertyName name, const char* reason)
{
JSAutoByteString printable;
if (AtomToPrintableString(cx, name, &printable)) {
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
JSMSG_CANT_DECLARE_GLOBAL_BINDING,
printable.ptr(), reason);
}
}
bool
js::CheckCanDeclareGlobalBinding(JSContext* cx, Handle<GlobalObject*> global,
HandlePropertyName name, bool isFunction)
{
RootedId id(cx, NameToId(name));
Rooted<PropertyDescriptor> desc(cx);
if (!GetOwnPropertyDescriptor(cx, global, id, &desc))
return false;
// ES 8.1.1.4.15 CanDeclareGlobalVar
// ES 8.1.1.4.16 CanDeclareGlobalFunction
// Step 4.
if (!desc.object()) {
// 8.1.14.15 step 6.
// 8.1.14.16 step 5.
if (global->nonProxyIsExtensible())
return true;
ReportCannotDeclareGlobalBinding(cx, name, "global is non-extensible");
return false;
}
// Global functions have additional restrictions.
if (isFunction) {
// 8.1.14.16 step 6.
if (desc.configurable())
return true;
// 8.1.14.16 step 7.
if (desc.isDataDescriptor() && desc.writable() && desc.enumerable())
return true;
ReportCannotDeclareGlobalBinding(cx, name,
"property must be configurable or "
"both writable and enumerable");
return false;
}
return true;
}
bool
js::CheckGlobalDeclarationConflicts(JSContext* cx, HandleScript script,
Handle<LexicalEnvironmentObject*> lexicalEnv,
HandleObject varObj)
{
// Due to the extensibility of the global lexical environment, we must
// check for redeclaring a binding.
//
// In the case of non-syntactic environment chains, we are checking
// redeclarations against the non-syntactic lexical environment and the
// variables object that the lexical environment corresponds to.
RootedPropertyName name(cx);
Rooted<BindingIter> bi(cx, BindingIter(script));
// ES 15.1.11 GlobalDeclarationInstantiation
// Step 6.
//
// Check 'var' declarations do not conflict with existing bindings in the
// global lexical environment.
for (; bi; bi++) {
if (bi.kind() != BindingKind::Var)
break;
name = bi.name()->asPropertyName();
if (!CheckVarNameConflict(cx, lexicalEnv, name))
return false;
// Step 10 and 12.
//
// Check that global functions and vars may be declared.
if (varObj->is<GlobalObject>()) {
Handle<GlobalObject*> global = varObj.as<GlobalObject>();
if (!CheckCanDeclareGlobalBinding(cx, global, name, bi.isTopLevelFunction()))
return false;
}
}
// Step 5.
//
// Check that lexical bindings do not conflict.
for (; bi; bi++) {
name = bi.name()->asPropertyName();
if (!CheckLexicalNameConflict(cx, lexicalEnv, varObj, name))
return false;
}
return true;
}
static bool
CheckVarNameConflictsInEnv(JSContext* cx, HandleScript script, HandleObject obj)
{
Rooted<LexicalEnvironmentObject*> env(cx);
if (obj->is<LexicalEnvironmentObject>()) {
env = &obj->as<LexicalEnvironmentObject>();
} else if (obj->is<DebugEnvironmentProxy>() &&
obj->as<DebugEnvironmentProxy>().environment().is<LexicalEnvironmentObject>())
{
env = &obj->as<DebugEnvironmentProxy>().environment().as<LexicalEnvironmentObject>();
} else {
// Environment cannot contain lexical bindings.
return true;
}
if (env->isSyntactic() && !env->isGlobal() && env->scope().kind() == ScopeKind::SimpleCatch) {
// Annex B.3.5 allows redeclaring simple (non-destructured) catch
// parameters with var declarations, except when it appears in a
// for-of. The for-of allowance is computed in
// Parser::isVarRedeclaredInEval.
return true;
}
RootedPropertyName name(cx);
for (BindingIter bi(script); bi; bi++) {
name = bi.name()->asPropertyName();
if (!CheckVarNameConflict(cx, env, name))
return false;
}
return true;
}
bool
js::CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script,
HandleObject scopeChain, HandleObject varObj)
{
if (!script->bodyScope()->as<EvalScope>().hasBindings())
return true;
RootedObject obj(cx, scopeChain);
// ES 18.2.1.3.
// Step 5.
//
// Check that a direct eval will not hoist 'var' bindings over lexical
// bindings with the same name.
while (obj != varObj) {
if (!CheckVarNameConflictsInEnv(cx, script, obj))
return false;
obj = obj->enclosingEnvironment();
}
// Step 8.
//
// Check that global functions may be declared.
if (varObj->is<GlobalObject>()) {
Handle<GlobalObject*> global = varObj.as<GlobalObject>();
RootedPropertyName name(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
name = bi.name()->asPropertyName();
if (!CheckCanDeclareGlobalBinding(cx, global, name, bi.isTopLevelFunction()))
return false;
}
}
return true;
}
bool
js::InitFunctionEnvironmentObjects(JSContext* cx, AbstractFramePtr frame)
{
MOZ_ASSERT(frame.isFunctionFrame());
MOZ_ASSERT(frame.callee()->needsSomeEnvironmentObject());
RootedFunction callee(cx, frame.callee());
// Named lambdas may have an environment that holds itself for recursion.
if (callee->needsNamedLambdaEnvironment()) {
NamedLambdaObject* declEnv;
if (callee->isAsync()) {
// Named async function needs special environment to return
// wrapped function for the binding.
RootedFunction fun(cx, GetWrappedAsyncFunction(callee));
declEnv = NamedLambdaObject::create(cx, frame, fun);
} else {
declEnv = NamedLambdaObject::create(cx, frame);
}
if (!declEnv)
return false;
frame.pushOnEnvironmentChain(*declEnv);
}
// If the function has parameter default expressions, there may be an
// extra environment to hold the parameters.
if (callee->needsCallObject()) {
CallObject* callObj = CallObject::create(cx, frame);
if (!callObj)
return false;
frame.pushOnEnvironmentChain(*callObj);
}
return true;
}
bool
js::PushVarEnvironmentObject(JSContext* cx, HandleScope scope, AbstractFramePtr frame)
{
VarEnvironmentObject* env = VarEnvironmentObject::create(cx, scope, frame);
if (!env)
return false;
frame.pushOnEnvironmentChain(*env);
return true;
}
#ifdef DEBUG
typedef HashSet<PropertyName*> PropertyNameSet;
static bool
RemoveReferencedNames(JSContext* cx, HandleScript script, PropertyNameSet& remainingNames)
{
// Remove from remainingNames --- the closure variables in some outer
// script --- any free variables in this script. This analysis isn't perfect:
//
// - It will not account for free variables in an inner script which are
// actually accessing some name in an intermediate script between the
// inner and outer scripts. This can cause remainingNames to be an
// underapproximation.
//
// - It will not account for new names introduced via eval. This can cause
// remainingNames to be an overapproximation. This would be easy to fix
// but is nice to have as the eval will probably not access these
// these names and putting eval in an inner script is bad news if you
// care about entraining variables unnecessarily.
for (jsbytecode* pc = script->code(); pc != script->codeEnd(); pc += GetBytecodeLength(pc)) {
PropertyName* name;
switch (JSOp(*pc)) {
case JSOP_GETNAME:
case JSOP_SETNAME:
case JSOP_STRICTSETNAME:
name = script->getName(pc);
break;
case JSOP_GETGNAME:
case JSOP_SETGNAME:
case JSOP_STRICTSETGNAME:
if (script->hasNonSyntacticScope())
name = script->getName(pc);
else
name = nullptr;
break;
case JSOP_GETALIASEDVAR:
case JSOP_SETALIASEDVAR:
name = EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, script, pc);
break;
default:
name = nullptr;
break;
}
if (name)
remainingNames.remove(name);
}
if (script->hasObjects()) {
ObjectArray* objects = script->objects();
RootedFunction fun(cx);
RootedScript innerScript(cx);
for (size_t i = 0; i < objects->length; i++) {
JSObject* obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
fun = &obj->as<JSFunction>();
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);
if (!remainingNames.init())
return false;
for (BindingIter bi(script); bi; bi++) {
if (bi.closedOver()) {
PropertyName* name = bi.name()->asPropertyName();
PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(name);
if (!p && !remainingNames.add(p, name))
return false;
}
}
if (!RemoveReferencedNames(cx, innerScript, remainingNames))
return false;
if (!remainingNames.empty()) {
Sprinter buf(cx);
if (!buf.init())
return false;
buf.printf("Script ");
if (JSAtom* name = script->functionNonDelazifying()->displayAtom()) {
buf.putString(name);
buf.printf(" ");
}
buf.printf("(%s:%" PRIuSIZE ") has variables entrained by ", script->filename(), script->lineno());
if (JSAtom* name = innerScript->functionNonDelazifying()->displayAtom()) {
buf.putString(name);
buf.printf(" ");
}
buf.printf("(%s:%" PRIuSIZE ") ::", innerScript->filename(), innerScript->lineno());
for (PropertyNameSet::Range r = remainingNames.all(); !r.empty(); r.popFront()) {
buf.printf(" ");
buf.putString(r.front());
}
printf("%s\n", buf.string());
}
if (innerScript->hasObjects()) {
ObjectArray* objects = innerScript->objects();
RootedFunction fun(cx);
RootedScript innerInnerScript(cx);
for (size_t i = 0; i < objects->length; i++) {
JSObject* obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
fun = &obj->as<JSFunction>();
innerInnerScript = JSFunction::getOrCreateScript(cx, fun);
if (!innerInnerScript ||
!AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript))
{
return false;
}
}
}
}
return true;
}
// Look for local variables in script or any other script inner to it, which are
// part of the script's call object and are unnecessarily entrained by their own
// inner scripts which do not refer to those variables. An example is:
//
// function foo() {
// var a, b;
// function bar() { return a; }
// function baz() { return b; }
// }
//
// |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|.
bool
js::AnalyzeEntrainedVariables(JSContext* cx, HandleScript script)
{
if (!script->hasObjects())
return true;
ObjectArray* objects = script->objects();
RootedFunction fun(cx);
RootedScript innerScript(cx);
for (size_t i = 0; i < objects->length; i++) {
JSObject* obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
fun = &obj->as<JSFunction>();
innerScript = JSFunction::getOrCreateScript(cx, fun);
if (!innerScript)
return false;
if (script->functionDelazifying() && script->functionDelazifying()->needsCallObject()) {
if (!AnalyzeEntrainedVariablesInScript(cx, script, innerScript))
return false;
}
if (!AnalyzeEntrainedVariables(cx, innerScript))
return false;
}
}
return true;
}
#endif