blob: 19e13c0da2c66d4e6f03651e426dfb811edfaae8 [file] [log] [blame]
/* -*- 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/ScopeObject-inl.h"
#include "mozilla/PodOperations.h"
#include "mozilla/SizePrintfMacros.h"
#include "jscompartment.h"
#include "jsiter.h"
#include "builtin/ModuleObject.h"
#include "frontend/ParseNode.h"
#include "vm/ArgumentsObject.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"
// Unified leak fix:
#include "jspubtd.h"
using namespace js;
using namespace js::gc;
using mozilla::PodZero;
typedef Rooted<ArgumentsObject*> RootedArgumentsObject;
typedef MutableHandle<ArgumentsObject*> MutableHandleArgumentsObject;
/*****************************************************************************/
Shape*
js::ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc)
{
MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
uint32_t hops = ScopeCoordinate(pc).hops();
while (true) {
MOZ_ASSERT(!ssi.done());
if (ssi.hasSyntacticDynamicScopeObject()) {
if (!hops)
break;
hops--;
}
ssi++;
}
return ssi.scopeShape();
}
static const uint32_t SCOPE_COORDINATE_NAME_THRESHOLD = 20;
void
ScopeCoordinateNameCache::purge()
{
shape = nullptr;
if (map.initialized())
map.finish();
}
PropertyName*
js::ScopeCoordinateName(ScopeCoordinateNameCache& cache, JSScript* script, jsbytecode* pc)
{
Shape* shape = ScopeCoordinateToStaticScopeShape(script, pc);
if (shape != cache.shape && shape->slot() >= SCOPE_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;
ScopeCoordinate sc(pc);
if (shape == cache.shape) {
ScopeCoordinateNameCache::Map::Ptr p = cache.map.lookup(sc.slot());
id = p->value();
} else {
Shape::Range<NoGC> r(shape);
while (r.front().slot() != sc.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::ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc)
{
MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
uint32_t hops = ScopeCoordinate(pc).hops();
while (true) {
if (ssi.hasSyntacticDynamicScopeObject()) {
if (!hops)
break;
hops--;
}
ssi++;
}
if (ssi.type() != StaticScopeIter<NoGC>::Function)
return nullptr;
return ssi.funScript();
}
/*****************************************************************************/
void
ScopeObject::setEnclosingScope(HandleObject obj)
{
MOZ_ASSERT_IF(obj->is<LexicalScopeBase>() || obj->is<DeclEnvObject>() || obj->is<BlockObject>(),
obj->isDelegate());
setFixedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*obj));
}
CallObject*
CallObject::create(JSContext* cx, HandleShape shape, HandleObjectGroup group, uint32_t lexicalBegin)
{
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;
obj->as<CallObject>().initRemainingSlotsToUninitializedLexicals(lexicalBegin);
return &obj->as<CallObject>();
}
CallObject*
CallObject::createSingleton(JSContext* cx, HandleShape shape, uint32_t lexicalBegin)
{
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");
obj->as<CallObject>().initRemainingSlotsToUninitializedLexicals(lexicalBegin);
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, gc::InitialHeap heap)
{
RootedShape shape(cx, script->bindings.callObjShape());
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;
// Set uninitialized lexicals even on template objects, as Ion will copy
// over the template object's slot values in the fast path.
obj->as<CallObject>().initAliasedLexicalsToThrowOnTouch(script);
return &obj->as<CallObject>();
}
/*
* 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, HandleScript script, HandleObject enclosing, HandleFunction callee)
{
gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap;
CallObject* callobj = CallObject::createTemplateObject(cx, script, heap);
if (!callobj)
return nullptr;
callobj->setEnclosingScope(enclosing);
callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee));
if (script->treatAsRunOnce()) {
Rooted<CallObject*> ncallobj(cx, callobj);
if (!JSObject::setSingleton(cx, ncallobj))
return nullptr;
return ncallobj;
}
return callobj;
}
CallObject*
CallObject::createForFunction(JSContext* cx, HandleObject enclosing, HandleFunction callee)
{
RootedObject scopeChain(cx, enclosing);
MOZ_ASSERT(scopeChain);
/*
* For a named function expression Call's parent points to an environment
* object holding function's name.
*/
if (callee->isNamedLambda()) {
scopeChain = DeclEnvObject::create(cx, scopeChain, callee);
if (!scopeChain)
return nullptr;
}
RootedScript script(cx, callee->nonLazyScript());
return create(cx, script, scopeChain, callee);
}
CallObject*
CallObject::createForFunction(JSContext* cx, AbstractFramePtr frame)
{
MOZ_ASSERT(frame.isNonEvalFunctionFrame());
assertSameCompartment(cx, frame);
RootedObject scopeChain(cx, frame.scopeChain());
RootedFunction callee(cx, frame.callee());
CallObject* callobj = createForFunction(cx, scopeChain, callee);
if (!callobj)
return nullptr;
/* Copy in the closed-over formal arguments. */
for (AliasedFormalIter i(frame.script()); i; i++) {
callobj->setAliasedVar(cx, i, i->name(),
frame.unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING));
}
return callobj;
}
CallObject*
CallObject::createForStrictEval(JSContext* cx, AbstractFramePtr frame)
{
MOZ_ASSERT(frame.isStrictEvalFrame());
MOZ_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterFrame() == frame.asInterpreterFrame());
MOZ_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterRegs().pc == frame.script()->code());
RootedFunction callee(cx);
RootedScript script(cx, frame.script());
RootedObject scopeChain(cx, frame.scopeChain());
return create(cx, script, scopeChain, callee);
}
CallObject*
CallObject::createHollowForDebug(JSContext* cx, HandleFunction callee)
{
MOZ_ASSERT(!callee->needsCallObject());
// This scope's parent link is never used: the DebugScopeObject that
// refers to this scope carries its own parent link, which is what
// Debugger uses to construct the tree of Debugger.Environment objects. So
// just parent this scope directly to the global lexical scope.
Rooted<GlobalObject*> global(cx, &callee->global());
RootedObject globalLexical(cx, &global->lexicalScope());
Rooted<CallObject*> callobj(cx, createForFunction(cx, globalLexical, callee));
if (!callobj)
return nullptr;
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
RootedScript script(cx, callee->nonLazyScript());
for (BindingIter bi(script); !bi.done(); bi++) {
id = NameToId(bi->name());
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)
};
/*****************************************************************************/
const Class ModuleEnvironmentObject::class_ = {
"ModuleEnvironmentObject",
JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS,
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
nullptr, /* trace */
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
{
ModuleEnvironmentObject::lookupProperty,
nullptr, /* defineProperty */
ModuleEnvironmentObject::hasProperty,
ModuleEnvironmentObject::getProperty,
ModuleEnvironmentObject::setProperty,
ModuleEnvironmentObject::getOwnPropertyDescriptor,
ModuleEnvironmentObject::deleteProperty,
nullptr, nullptr, /* watch/unwatch */
nullptr, /* getElements */
ModuleEnvironmentObject::enumerate,
nullptr
}
};
/* static */ ModuleEnvironmentObject*
ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module)
{
RootedScript script(cx, module->script());
RootedShape shape(cx, script->bindings.callObjShape());
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 scope(cx, &obj->as<ModuleEnvironmentObject>());
// Set uninitialized lexicals even on template objects, as Ion will use
// copy over the template object's slot values in the fast path.
scope->initAliasedLexicalsToThrowOnTouch(script);
scope->initFixedSlot(MODULE_SLOT, ObjectValue(*module));
if (!JSObject::setSingleton(cx, scope))
return nullptr;
// Initialize this early so that we can manipulate the scope object without
// causing assertions.
RootedObject globalLexical(cx, &cx->global()->lexicalScope());
scope->setEnclosingScope(globalLexical);
// 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(scope->lastProperty()); !r.empty(); r.popFront())
MOZ_ASSERT(!r.front().configurable());
MOZ_ASSERT(scope->lastProperty()->getObjectFlags() & BaseShape::NOT_EXTENSIBLE);
MOZ_ASSERT(!scope->inDictionaryMode());
#endif
return scope;
}
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->environment());
if (!importBindings().putNew(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);
}
/* 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<JSPropertyDescriptor> desc)
{
// We never call this hook on scope objects.
MOZ_CRASH();
}
/* 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;
}
/*****************************************************************************/
const Class DeclEnvObject::class_ = {
js_Object_str,
JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) |
JSCLASS_HAS_CACHED_PROTO(JSProto_Object)
};
/*
* Create a DeclEnvObject 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.
*/
DeclEnvObject*
DeclEnvObject::createTemplateObject(JSContext* cx, HandleFunction fun, NewObjectKind newKind)
{
Rooted<DeclEnvObject*> obj(cx);
obj = NewObjectWithNullTaggedProto<DeclEnvObject>(cx, newKind, BaseShape::DELEGATE);
if (!obj)
return nullptr;
// Assign a fixed slot to a property with the same name as the lambda.
Rooted<jsid> id(cx, AtomToId(fun->atom()));
const Class* clasp = obj->getClass();
unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;
JSGetterOp getter = clasp->getProperty;
JSSetterOp setter = clasp->setProperty;
MOZ_ASSERT(getter != JS_PropertyStub);
MOZ_ASSERT(setter != JS_StrictPropertyStub);
if (!NativeObject::putProperty(cx, obj, id, getter, setter, lambdaSlot(), attrs, 0))
return nullptr;
MOZ_ASSERT(!obj->hasDynamicSlots());
return obj;
}
DeclEnvObject*
DeclEnvObject::create(JSContext* cx, HandleObject enclosing, HandleFunction callee)
{
Rooted<DeclEnvObject*> obj(cx, createTemplateObject(cx, callee, GenericObject));
if (!obj)
return nullptr;
obj->setEnclosingScope(enclosing);
obj->setFixedSlot(lambdaSlot(), ObjectValue(*callee));
return obj;
}
template<XDRMode mode>
bool
js::XDRStaticWithObject(XDRState<mode>* xdr, HandleObject enclosingScope,
MutableHandle<StaticWithObject*> objp)
{
if (mode == XDR_DECODE) {
JSContext* cx = xdr->cx();
Rooted<StaticWithObject*> obj(cx, StaticWithObject::create(cx));
if (!obj)
return false;
obj->initEnclosingScope(enclosingScope);
objp.set(obj);
}
// For encoding, there is nothing to do. The only information that is
// encoded by a StaticWithObject is its presence on the scope chain, and the
// script XDR handler already takes care of that.
return true;
}
template bool
js::XDRStaticWithObject(XDRState<XDR_ENCODE>*, HandleObject, MutableHandle<StaticWithObject*>);
template bool
js::XDRStaticWithObject(XDRState<XDR_DECODE>*, HandleObject, MutableHandle<StaticWithObject*>);
StaticWithObject*
StaticWithObject::create(ExclusiveContext* cx)
{
return NewObjectWithNullTaggedProto<StaticWithObject>(cx, TenuredObject, BaseShape::DELEGATE);
}
static JSObject*
CloneStaticWithObject(JSContext* cx, HandleObject enclosingScope, Handle<StaticWithObject*> srcWith)
{
Rooted<StaticWithObject*> clone(cx, StaticWithObject::create(cx));
if (!clone)
return nullptr;
clone->initEnclosingScope(enclosingScope);
return clone;
}
DynamicWithObject*
DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
HandleObject staticWith, WithKind kind)
{
MOZ_ASSERT(staticWith->is<StaticWithObject>());
Rooted<TaggedProto> proto(cx, TaggedProto(staticWith));
Rooted<DynamicWithObject*> obj(cx);
obj = NewObjectWithGivenTaggedProto<DynamicWithObject>(cx, proto, GenericObject,
BaseShape::DELEGATE);
if (!obj)
return nullptr;
Value thisv = GetThisValue(object);
obj->setEnclosingScope(enclosing);
obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object));
obj->setFixedSlot(THIS_SLOT, thisv);
obj->setFixedSlot(KIND_SLOT, Int32Value(kind));
return obj;
}
static bool
with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject objp, MutableHandleShape propp)
{
if (JSID_IS_ATOM(id, cx->names().dotThis)) {
objp.set(nullptr);
propp.set(nullptr);
return true;
}
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
return LookupProperty(cx, actual, id, objp, propp);
}
static bool
with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle<PropertyDescriptor> desc,
ObjectOpResult& result)
{
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
return DefineProperty(cx, actual, id, desc, result);
}
static bool
with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
return HasProperty(cx, actual, id, foundp);
}
static bool
with_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
MutableHandleValue vp)
{
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
RootedObject actual(cx, &obj->as<DynamicWithObject>().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(!JSID_IS_ATOM(id, cx->names().dotThis));
RootedObject actual(cx, &obj->as<DynamicWithObject>().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<JSPropertyDescriptor> desc)
{
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
return GetOwnPropertyDescriptor(cx, actual, id, desc);
}
static bool
with_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result)
{
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
return DeleteProperty(cx, actual, id, result);
}
const Class StaticWithObject::class_ = {
"WithTemplate",
JSCLASS_HAS_RESERVED_SLOTS(StaticWithObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS
};
const Class DynamicWithObject::class_ = {
"With",
JSCLASS_HAS_RESERVED_SLOTS(DynamicWithObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS,
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
nullptr, /* trace */
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
{
with_LookupProperty,
with_DefineProperty,
with_HasProperty,
with_GetProperty,
with_SetProperty,
with_GetOwnPropertyDescriptor,
with_DeleteProperty,
nullptr, nullptr, /* watch/unwatch */
nullptr, /* getElements */
nullptr, /* enumerate (native enumeration of target doesn't work) */
nullptr,
}
};
/* static */ StaticEvalObject*
StaticEvalObject::create(JSContext* cx, HandleObject enclosing)
{
StaticEvalObject* obj =
NewObjectWithNullTaggedProto<StaticEvalObject>(cx, TenuredObject, BaseShape::DELEGATE);
if (!obj)
return nullptr;
obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(enclosing));
obj->setReservedSlot(STRICT_SLOT, BooleanValue(false));
return obj;
}
const Class StaticEvalObject::class_ = {
"StaticEval",
JSCLASS_HAS_RESERVED_SLOTS(StaticEvalObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS
};
/* static */ StaticNonSyntacticScopeObjects*
StaticNonSyntacticScopeObjects::create(JSContext*cx, HandleObject enclosing)
{
StaticNonSyntacticScopeObjects* obj =
NewObjectWithNullTaggedProto<StaticNonSyntacticScopeObjects>(cx, TenuredObject,
BaseShape::DELEGATE);
if (!obj)
return nullptr;
obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(enclosing));
return obj;
}
const Class StaticNonSyntacticScopeObjects::class_ = {
"StaticNonSyntacticScopeObjects",
JSCLASS_HAS_RESERVED_SLOTS(StaticNonSyntacticScopeObjects::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS
};
/* static */ NonSyntacticVariablesObject*
NonSyntacticVariablesObject::create(JSContext* cx, Handle<ClonedBlockObject*> globalLexical)
{
MOZ_ASSERT(globalLexical->isGlobal());
Rooted<NonSyntacticVariablesObject*> obj(cx,
NewObjectWithNullTaggedProto<NonSyntacticVariablesObject>(cx, TenuredObject,
BaseShape::DELEGATE));
if (!obj)
return nullptr;
MOZ_ASSERT(obj->isUnqualifiedVarObj());
if (!obj->setQualifiedVarObj(cx))
return nullptr;
obj->setEnclosingScope(globalLexical);
return obj;
}
const Class NonSyntacticVariablesObject::class_ = {
"NonSyntacticVariablesObject",
JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS
};
/*****************************************************************************/
bool
BlockObject::isExtensible() const
{
return nonProxyIsExtensible();
}
/* static */ ClonedBlockObject*
ClonedBlockObject::create(JSContext* cx, Handle<StaticBlockObject*> block, HandleObject enclosing)
{
MOZ_ASSERT(block->getClass() == &BlockObject::class_);
RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &BlockObject::class_,
TaggedProto(block.get())));
if (!group)
return nullptr;
RootedShape shape(cx, block->lastProperty());
gc::AllocKind allocKind = gc::GetGCObjectKind(&BlockObject::class_);
if (CanBeFinalizedInBackground(allocKind, &BlockObject::class_))
allocKind = GetBackgroundAllocKind(allocKind);
RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, allocKind,
gc::TenuredHeap, shape, group)));
if (!obj)
return nullptr;
MOZ_ASSERT(!obj->inDictionaryMode());
MOZ_ASSERT(obj->slotSpan() >= block->numVariables() + RESERVED_SLOTS);
obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*enclosing));
MOZ_ASSERT(obj->isDelegate());
ClonedBlockObject* res = &obj->as<ClonedBlockObject>();
if (res->isGlobal() || !res->isSyntactic())
res->setReservedSlot(THIS_VALUE_SLOT, GetThisValue(enclosing));
return res;
}
/* static */ ClonedBlockObject*
ClonedBlockObject::create(JSContext* cx, Handle<StaticBlockObject*> block, AbstractFramePtr frame)
{
assertSameCompartment(cx, frame);
RootedObject enclosing(cx, frame.scopeChain());
return create(cx, block, enclosing);
}
/* static */ ClonedBlockObject*
ClonedBlockObject::createGlobal(JSContext* cx, Handle<GlobalObject*> global)
{
Rooted<StaticBlockObject*> staticLexical(cx, StaticBlockObject::create(cx));
if (!staticLexical)
return nullptr;
// Currently the global lexical scope cannot have any bindings with frame
// slots.
staticLexical->setLocalOffset(UINT32_MAX);
staticLexical->initEnclosingScope(nullptr);
Rooted<ClonedBlockObject*> lexical(cx, ClonedBlockObject::create(cx, staticLexical, global));
if (!lexical)
return nullptr;
if (!JSObject::setSingleton(cx, lexical))
return nullptr;
return lexical;
}
/* static */ ClonedBlockObject*
ClonedBlockObject::createNonSyntactic(JSContext* cx, HandleObject enclosingStatic,
HandleObject enclosingScope)
{
MOZ_ASSERT(enclosingStatic->is<StaticNonSyntacticScopeObjects>());
MOZ_ASSERT(!IsSyntacticScope(enclosingScope));
Rooted<StaticBlockObject*> staticLexical(cx, StaticBlockObject::create(cx));
if (!staticLexical)
return nullptr;
staticLexical->setLocalOffset(UINT32_MAX);
staticLexical->initEnclosingScope(enclosingStatic);
Rooted<ClonedBlockObject*> lexical(cx, ClonedBlockObject::create(cx, staticLexical,
enclosingScope));
if (!lexical)
return nullptr;
return lexical;
}
/* static */ ClonedBlockObject*
ClonedBlockObject::createHollowForDebug(JSContext* cx, Handle<StaticBlockObject*> block)
{
MOZ_ASSERT(!block->needsClone());
// This scope's parent link is never used: the DebugScopeObject that
// refers to this scope carries its own parent link, which is what
// Debugger uses to construct the tree of Debugger.Environment objects. So
// just parent this scope directly to the global lexical scope.
Rooted<GlobalObject*> global(cx, &block->global());
RootedObject globalLexical(cx, &global->lexicalScope());
Rooted<ClonedBlockObject*> obj(cx, create(cx, block, globalLexical));
if (!obj)
return nullptr;
for (unsigned i = 0; i < block->numVariables(); i++)
obj->setVar(i, MagicValue(JS_OPTIMIZED_OUT), DONT_CHECK_ALIASING);
return obj;
}
void
ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame)
{
StaticBlockObject& block = staticBlock();
for (unsigned i = 0; i < numVariables(); ++i) {
if (!block.isAliased(i)) {
Value& val = frame.unaliasedLocal(block.blockIndexToLocalIndex(i));
setVar(i, val, DONT_CHECK_ALIASING);
}
}
}
/* static */ ClonedBlockObject*
ClonedBlockObject::clone(JSContext* cx, Handle<ClonedBlockObject*> clonedBlock)
{
Rooted<StaticBlockObject*> staticBlock(cx, &clonedBlock->staticBlock());
MOZ_ASSERT(!staticBlock->isExtensible());
RootedObject enclosing(cx, &clonedBlock->enclosingScope());
Rooted<ClonedBlockObject*> copy(cx, create(cx, staticBlock, enclosing));
if (!copy)
return nullptr;
for (uint32_t i = 0, count = staticBlock->numVariables(); i < count; i++)
copy->setVar(i, clonedBlock->var(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING);
return copy;
}
StaticBlockObject*
StaticBlockObject::create(ExclusiveContext* cx)
{
return NewObjectWithNullTaggedProto<StaticBlockObject>(cx, TenuredObject, BaseShape::DELEGATE);
}
Shape*
StaticBlockObject::lookupAliasedName(PropertyName* name)
{
Shape::Range<NoGC> r(lastProperty());
while (!r.empty()) {
jsid id = r.front().propidRaw();
if (JSID_TO_ATOM(id)->asPropertyName() == name && isAliased(shapeToIndex(r.front())))
return &r.front();
r.popFront();
}
return nullptr;
}
bool
StaticBlockObject::makeNonExtensible(ExclusiveContext* cx)
{
// Do not do all the work of js::PreventExtensions, as BlockObjects are
// known to be NativeObjects, have no lazy properties, and no dense
// elements. Indeed, we do not have a JSContext as parsing may happen
// off-thread.
if (!isExtensible())
return true;
return setFlags(cx, BaseShape::NOT_EXTENSIBLE, JSObject::GENERATE_SHAPE);
}
/* static */ Shape*
StaticBlockObject::addVar(ExclusiveContext* cx, Handle<StaticBlockObject*> block, HandleId id,
bool constant, unsigned index, bool* redeclared)
{
MOZ_ASSERT(JSID_IS_ATOM(id));
MOZ_ASSERT(index < LOCAL_INDEX_LIMIT);
*redeclared = false;
/* Inline NativeObject::addProperty in order to trap the redefinition case. */
ShapeTable::Entry* entry;
if (Shape::search<true>(cx, block->lastProperty(), id, &entry)) {
*redeclared = true;
return nullptr;
}
/*
* Don't convert this object to dictionary mode so that we can clone the
* block's shape later.
*/
uint32_t slot = JSSLOT_FREE(&BlockObject::class_) + index;
uint32_t readonly = constant ? JSPROP_READONLY : 0;
uint32_t propFlags = readonly | JSPROP_ENUMERATE | JSPROP_PERMANENT;
return NativeObject::addPropertyInternal(cx, block, id,
/* getter = */ nullptr,
/* setter = */ nullptr,
slot,
propFlags,
/* attrs = */ 0,
entry,
/* allowDictionary = */ false);
}
Value
ClonedBlockObject::thisValue() const
{
MOZ_ASSERT(isGlobal() || !isSyntactic());
Value v = getReservedSlot(THIS_VALUE_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_SLOT, but it's possible the WindowProxy was
// attached to the global *after* we set THIS_VALUE_SLOT.
return ObjectValue(*ToWindowProxyIfWindow(&v.toObject()));
}
return v;
}
const Class BlockObject::class_ = {
"Block",
JSCLASS_HAS_RESERVED_SLOTS(BlockObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS,
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
nullptr, /* trace */
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
{
nullptr, /* lookupProperty */
nullptr, /* defineProperty */
nullptr, /* hasProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* getOwnPropertyDescriptor */
nullptr, /* deleteProperty */
nullptr, nullptr, /* watch/unwatch */
nullptr, /* getElements */
nullptr, /* enumerate (native enumeration of target doesn't work) */
nullptr,
}
};
template<XDRMode mode>
bool
js::XDRStaticBlockObject(XDRState<mode>* xdr, HandleObject enclosingScope,
MutableHandle<StaticBlockObject*> objp)
{
/* NB: Keep this in sync with CloneStaticBlockObject. */
JSContext* cx = xdr->cx();
Rooted<StaticBlockObject*> obj(cx);
uint32_t count = 0, offset = 0;
uint8_t extensible = 0;
if (mode == XDR_ENCODE) {
obj = objp;
count = obj->numVariables();
offset = obj->localOffset();
extensible = obj->isExtensible() ? 1 : 0;
}
if (mode == XDR_DECODE) {
obj = StaticBlockObject::create(cx);
if (!obj)
return false;
obj->initEnclosingScope(enclosingScope);
objp.set(obj);
}
if (!xdr->codeUint32(&count))
return false;
if (!xdr->codeUint32(&offset))
return false;
if (!xdr->codeUint8(&extensible))
return false;
/*
* XDR the block object's properties. We know that there are 'count'
* properties to XDR, stored as id/aliased pairs. (The empty string as
* id indicates an int id.)
*/
if (mode == XDR_DECODE) {
obj->setLocalOffset(offset);
for (unsigned i = 0; i < count; i++) {
RootedAtom atom(cx);
if (!XDRAtom(xdr, &atom))
return false;
RootedId id(cx, atom != cx->runtime()->emptyString
? AtomToId(atom)
: INT_TO_JSID(i));
uint32_t propFlags;
if (!xdr->codeUint32(&propFlags))
return false;
bool readonly = !!(propFlags & 1);
bool redeclared;
if (!StaticBlockObject::addVar(cx, obj, id, readonly, i, &redeclared)) {
MOZ_ASSERT(!redeclared);
return false;
}
bool aliased = !!(propFlags >> 1);
obj->setAliased(i, aliased);
}
if (!extensible) {
if (!obj->makeNonExtensible(cx))
return false;
}
} else {
Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
if (!shapes.growBy(count))
return false;
for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront())
shapes[obj->shapeToIndex(r.front())].set(&r.front());
RootedShape shape(cx);
RootedId propid(cx);
RootedAtom atom(cx);
for (unsigned i = 0; i < count; i++) {
shape = shapes[i];
MOZ_ASSERT(shape->hasDefaultGetter());
MOZ_ASSERT(obj->shapeToIndex(*shape) == i);
propid = shape->propid();
MOZ_ASSERT(JSID_IS_ATOM(propid) || JSID_IS_INT(propid));
atom = JSID_IS_ATOM(propid)
? JSID_TO_ATOM(propid)
: cx->runtime()->emptyString;
if (!XDRAtom(xdr, &atom))
return false;
bool aliased = obj->isAliased(i);
bool readonly = !shape->writable();
uint32_t propFlags = (aliased << 1) | readonly;
if (!xdr->codeUint32(&propFlags))
return false;
}
}
return true;
}
template bool
js::XDRStaticBlockObject(XDRState<XDR_ENCODE>*, HandleObject, MutableHandle<StaticBlockObject*>);
template bool
js::XDRStaticBlockObject(XDRState<XDR_DECODE>*, HandleObject, MutableHandle<StaticBlockObject*>);
static JSObject*
CloneStaticBlockObject(JSContext* cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock)
{
/* NB: Keep this in sync with XDRStaticBlockObject. */
Rooted<StaticBlockObject*> clone(cx, StaticBlockObject::create(cx));
if (!clone)
return nullptr;
clone->initEnclosingScope(enclosingScope);
clone->setLocalOffset(srcBlock->localOffset());
/* Shape::Range is reverse order, so build a list in forward order. */
Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
if (!shapes.growBy(srcBlock->numVariables()))
return nullptr;
for (Shape::Range<NoGC> r(srcBlock->lastProperty()); !r.empty(); r.popFront())
shapes[srcBlock->shapeToIndex(r.front())].set(&r.front());
RootedId id(cx);
for (Shape* shape : shapes) {
id = shape->propid();
unsigned i = srcBlock->shapeToIndex(*shape);
bool redeclared;
if (!StaticBlockObject::addVar(cx, clone, id, !shape->writable(), i, &redeclared)) {
MOZ_ASSERT(!redeclared);
return nullptr;
}
clone->setAliased(i, srcBlock->isAliased(i));
}
if (!srcBlock->isExtensible()) {
if (!clone->makeNonExtensible(cx))
return nullptr;
}
return clone;
}
JSObject*
js::CloneNestedScopeObject(JSContext* cx, HandleObject enclosingScope, Handle<NestedScopeObject*> srcBlock)
{
if (srcBlock->is<StaticBlockObject>()) {
Rooted<StaticBlockObject*> blockObj(cx, &srcBlock->as<StaticBlockObject>());
return CloneStaticBlockObject(cx, enclosingScope, blockObj);
} else {
Rooted<StaticWithObject*> withObj(cx, &srcBlock->as<StaticWithObject>());
return CloneStaticWithObject(cx, enclosingScope, withObj);
}
}
/* static */ RuntimeLexicalErrorObject*
RuntimeLexicalErrorObject::create(JSContext* cx, HandleObject enclosing, unsigned errorNumber)
{
RuntimeLexicalErrorObject* obj =
NewObjectWithNullTaggedProto<RuntimeLexicalErrorObject>(cx, GenericObject,
BaseShape::DELEGATE);
if (!obj)
return nullptr;
obj->setEnclosingScope(enclosing);
obj->setReservedSlot(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<JSPropertyDescriptor> 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;
}
const Class RuntimeLexicalErrorObject::class_ = {
"RuntimeLexicalError",
JSCLASS_HAS_RESERVED_SLOTS(RuntimeLexicalErrorObject::RESERVED_SLOTS) |
JSCLASS_IS_ANONYMOUS,
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
nullptr, /* trace */
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
{
lexicalError_LookupProperty,
nullptr, /* defineProperty */
lexicalError_HasProperty,
lexicalError_GetProperty,
lexicalError_SetProperty,
lexicalError_GetOwnPropertyDescriptor,
lexicalError_DeleteProperty,
nullptr, nullptr, /* watch/unwatch */
nullptr, /* getElements */
nullptr, /* enumerate (native enumeration of target doesn't work) */
nullptr, /* this */
}
};
/*****************************************************************************/
// Any name atom for a function which will be added as a DeclEnv object to the
// scope chain above call objects for fun.
static inline JSAtom*
CallObjectLambdaName(JSFunction& fun)
{
return fun.isNamedLambda() ? fun.atom() : nullptr;
}
ScopeIter::ScopeIter(JSContext* cx, const ScopeIter& si
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: ssi_(cx, si.ssi_),
scope_(cx, si.scope_),
frame_(si.frame_)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
ScopeIter::ScopeIter(JSContext* cx, JSObject* scope, JSObject* staticScope
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: ssi_(cx, staticScope),
scope_(cx, scope),
frame_(NullFramePtr())
{
settle();
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
ScopeIter::ScopeIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: ssi_(cx, frame.script()->innermostStaticScope(pc)),
scope_(cx, frame.scopeChain()),
frame_(frame)
{
assertSameCompartment(cx, frame);
settle();
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
void
ScopeIter::incrementStaticScopeIter()
{
// If settled on a non-syntactic static scope, only increment ssi_ once
// we've iterated through all the non-syntactic dynamic ScopeObjects.
if (ssi_.type() == StaticScopeIter<CanGC>::NonSyntactic) {
if (!hasNonSyntacticScopeObject())
ssi_++;
} else {
ssi_++;
}
// For named lambdas, DeclEnvObject scopes are always attached to their
// CallObjects. Skip it here, as they are special cased in users of
// ScopeIter.
if (!ssi_.done() && ssi_.type() == StaticScopeIter<CanGC>::NamedLambda)
ssi_++;
}
void
ScopeIter::settle()
{
// Check for trying to iterate a function frame before the prologue has
// created the CallObject, in which case we have to skip.
if (frame_ && frame_.isNonEvalFunctionFrame() && frame_.fun()->needsCallObject() &&
!frame_.hasCallObj())
{
MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Function);
incrementStaticScopeIter();
}
// Check for trying to iterate a strict eval frame before the prologue has
// created the CallObject.
if (frame_ && frame_.isStrictEvalFrame() && !frame_.hasCallObj() && !ssi_.done()) {
MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Block);
incrementStaticScopeIter();
MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Eval);
MOZ_ASSERT(maybeStaticScope() == frame_.script()->enclosingStaticScope());
incrementStaticScopeIter();
frame_ = NullFramePtr();
}
// Check if we have left the extent of the initial frame after we've
// settled on a static scope.
if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope()))
frame_ = NullFramePtr();
#ifdef DEBUG
if (!ssi_.done() && hasAnyScopeObject()) {
switch (ssi_.type()) {
case StaticScopeIter<CanGC>::Module:
MOZ_ASSERT(scope_->as<ModuleEnvironmentObject>().module() == ssi_.module());
break;
case StaticScopeIter<CanGC>::Function:
MOZ_ASSERT(scope_->as<CallObject>().callee().nonLazyScript() == ssi_.funScript());
break;
case StaticScopeIter<CanGC>::Block:
MOZ_ASSERT(scope_->as<ClonedBlockObject>().staticBlock() == staticBlock());
break;
case StaticScopeIter<CanGC>::With:
MOZ_ASSERT(scope_->as<DynamicWithObject>().staticScope() == &staticWith());
break;
case StaticScopeIter<CanGC>::Eval:
MOZ_ASSERT(scope_->as<CallObject>().isForEval());
break;
case StaticScopeIter<CanGC>::NonSyntactic:
MOZ_ASSERT(!IsSyntacticScope(scope_));
break;
case StaticScopeIter<CanGC>::NamedLambda:
MOZ_CRASH("named lambda static scopes should have been skipped");
}
}
#endif
}
ScopeIter&
ScopeIter::operator++()
{
if (hasAnyScopeObject()) {
scope_ = &scope_->as<ScopeObject>().enclosingScope();
if (scope_->is<DeclEnvObject>())
scope_ = &scope_->as<DeclEnvObject>().enclosingScope();
}
incrementStaticScopeIter();
settle();
return *this;
}
ScopeIter::Type
ScopeIter::type() const
{
MOZ_ASSERT(!done());
switch (ssi_.type()) {
case StaticScopeIter<CanGC>::Module:
return Module;
case StaticScopeIter<CanGC>::Function:
return Call;
case StaticScopeIter<CanGC>::Block:
return Block;
case StaticScopeIter<CanGC>::With:
return With;
case StaticScopeIter<CanGC>::Eval:
return Eval;
case StaticScopeIter<CanGC>::NonSyntactic:
return NonSyntactic;
case StaticScopeIter<CanGC>::NamedLambda:
MOZ_CRASH("named lambda static scopes should have been skipped");
default:
MOZ_CRASH("bad SSI type");
}
}
ScopeObject&
ScopeIter::scope() const
{
MOZ_ASSERT(hasAnyScopeObject());
return scope_->as<ScopeObject>();
}
JSObject*
ScopeIter::maybeStaticScope() const
{
if (ssi_.done())
return nullptr;
switch (ssi_.type()) {
case StaticScopeIter<CanGC>::Function:
return &fun();
case StaticScopeIter<CanGC>::Module:
return &module();
case StaticScopeIter<CanGC>::Block:
return &staticBlock();
case StaticScopeIter<CanGC>::With:
return &staticWith();
case StaticScopeIter<CanGC>::Eval:
return &staticEval();
case StaticScopeIter<CanGC>::NonSyntactic:
return &staticNonSyntactic();
case StaticScopeIter<CanGC>::NamedLambda:
MOZ_CRASH("named lambda static scopes should have been skipped");
default:
MOZ_CRASH("bad SSI type");
}
}
/* static */ HashNumber
MissingScopeKey::hash(MissingScopeKey sk)
{
return size_t(sk.frame_.raw()) ^ size_t(sk.staticScope_);
}
/* static */ bool
MissingScopeKey::match(MissingScopeKey sk1, MissingScopeKey sk2)
{
return sk1.frame_ == sk2.frame_ && sk1.staticScope_ == sk2.staticScope_;
}
bool
LiveScopeVal::needsSweep()
{
if (staticScope_)
MOZ_ALWAYS_FALSE(IsAboutToBeFinalized(&staticScope_));
return false;
}
// Live ScopeIter values may be added to DebugScopes::liveScopes, as
// LiveScopeVal 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 LiveScopeVal and MissingScopeKey need to
// alias each other.
void
LiveScopeVal::staticAsserts()
{
static_assert(sizeof(LiveScopeVal) == sizeof(MissingScopeKey),
"LiveScopeVal must be same size of MissingScopeKey");
static_assert(offsetof(LiveScopeVal, staticScope_) == offsetof(MissingScopeKey, staticScope_),
"LiveScopeVal.staticScope_ must alias MissingScopeKey.staticScope_");
}
/*****************************************************************************/
namespace {
/*
* DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a
* custom handler (rather than trying to reuse js::Wrapper) gives us several
* important abilities:
* - We want to pass the ScopeObject 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. DebugScopeProxy 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 DebugScopeProxy : 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 Call/BlockObject scope objects and thus must be recovered
* from somewhere else:
* + if the invocation for which the scope was created is still executing,
* there is a JS frame live on the stack holding the values;
* + if the invocation for which the scope was created finished executing:
* - and there was a DebugScopeObject associated with scope, then the
* DebugScopes::onPop(Call|Block) handler copied out the unaliased
* variables:
* . for block scopes, the unaliased values were copied directly
* into the block object, since there is a slot allocated for every
* block binding, regardless of whether it is aliased;
* . for function scopes, a dense array is created in onPopCall to hold
* the unaliased values and attached to the DebugScopeObject;
* - and there was not a DebugScopeObject 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
*/
bool handleUnaliasedAccess(JSContext* cx, Handle<DebugScopeObject*> debugScope,
Handle<ScopeObject*> scope, HandleId id, Action action,
MutableHandleValue vp, AccessResult* accessResult) const
{
MOZ_ASSERT(&debugScope->scope() == scope);
MOZ_ASSERT_IF(action == SET, !debugScope->isOptimizedOut());
*accessResult = ACCESS_GENERIC;
LiveScopeVal* maybeLiveScope = DebugScopes::hasLiveScope(*scope);
if (scope->is<ModuleEnvironmentObject>()) {
/* Everything is aliased and stored in the environment object. */
return true;
}
/* Handle unaliased formals, vars, lets, and consts at function scope. */
if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
CallObject& callobj = scope->as<CallObject>();
RootedScript script(cx, callobj.callee().getOrCreateScript(cx));
if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx))
return false;
Bindings& bindings = script->bindings;
BindingIter bi(script);
while (bi && NameToId(bi->name()) != id)
bi++;
if (!bi)
return true;
if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) {
if (script->bindingIsAliased(bi))
return true;
uint32_t i = bi.frameIndex();
if (maybeLiveScope) {
AbstractFramePtr frame = maybeLiveScope->frame();
if (action == GET)
vp.set(frame.unaliasedLocal(i));
else
frame.unaliasedLocal(i) = vp;
} else if (NativeObject* snapshot = debugScope->maybeSnapshot()) {
if (action == GET)
vp.set(snapshot->getDenseElement(bindings.numArgs() + i));
else
snapshot->setDenseElement(bindings.numArgs() + i, vp);
} else {
/* The unaliased value has been lost to the debugger. */
if (action == GET) {
*accessResult = ACCESS_LOST;
return true;
}
}
} else {
MOZ_ASSERT(bi->kind() == Binding::ARGUMENT);
unsigned i = bi.argIndex();
if (script->formalIsAliased(i))
return true;
if (maybeLiveScope) {
AbstractFramePtr frame = maybeLiveScope->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 = debugScope->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);
}
*accessResult = ACCESS_UNALIASED;
return true;
}
/* Handle unaliased let and catch bindings at block scope. */
if (scope->is<ClonedBlockObject>()) {
Rooted<ClonedBlockObject*> block(cx, &scope->as<ClonedBlockObject>());
Shape* shape = block->lastProperty()->search(cx, id);
if (!shape)
return true;
// Currently consider all global and non-syntactic top-level lexical
// bindings to be aliased.
if (block->isExtensible()) {
MOZ_ASSERT(IsGlobalLexicalScope(block) || !IsSyntacticScope(block));
return true;
}
unsigned i = block->staticBlock().shapeToIndex(*shape);
if (block->staticBlock().isAliased(i))
return true;
if (maybeLiveScope) {
AbstractFramePtr frame = maybeLiveScope->frame();
uint32_t local = block->staticBlock().blockIndexToLocalIndex(i);
MOZ_ASSERT(local < frame.script()->nfixed());
if (action == GET)
vp.set(frame.unaliasedLocal(local));
else
frame.unaliasedLocal(local) = vp;
} else {
if (action == GET) {
// A ClonedBlockObject whose static block does not need
// cloning is a "hollow" block object reflected for
// missing block scopes. Their slot values are lost.
if (!block->staticBlock().needsClone()) {
*accessResult = ACCESS_LOST;
return true;
}
vp.set(block->var(i, DONT_CHECK_ALIASING));
} else {
block->setVar(i, vp, DONT_CHECK_ALIASING);
}
}
*accessResult = ACCESS_UNALIASED;
return true;
}
/* The rest of the internal scopes do not have unaliased vars. */
MOZ_ASSERT(scope->is<DeclEnvObject>() || scope->is<DynamicWithObject>() ||
scope->as<CallObject>().isForEval());
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 isFunctionScope(const JSObject& scope)
{
return scope.is<CallObject>() && !scope.as<CallObject>().isForEval();
}
/*
* In theory, every 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(ScopeObject& scope)
{
return isFunctionScope(scope) &&
!scope.as<CallObject>().callee().nonLazyScript()->argumentsHasVarBinding();
}
/*
* Similar to 'arguments' above, we don't add a 'this' binding to functions
* if it's not used.
*/
static bool isMissingThisBinding(ScopeObject& scope)
{
return isFunctionScopeWithThis(scope) &&
!scope.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, ScopeObject& scope)
{
return isArguments(cx, id) && isFunctionScope(scope) &&
!scope.as<CallObject>().callee().nonLazyScript()->needsArgsObj();
}
static bool isMissingThis(JSContext* cx, jsid id, ScopeObject& scope)
{
return isThis(cx, id) && isMissingThisBinding(scope);
}
/*
* 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, ScopeObject& scope, HandleValue v)
{
bool isMagic = v.isMagic() && v.whyMagic() == JS_OPTIMIZED_ARGUMENTS;
MOZ_ASSERT_IF(isMagic,
isFunctionScope(scope) &&
scope.as<CallObject>().callee().nonLazyScript()->argumentsHasVarBinding());
return isMagic;
}
/*
* Create a missing arguments object. If the function returns true but
* argsObj is null, it means the scope is dead.
*/
static bool createMissingArguments(JSContext* cx, ScopeObject& scope,
MutableHandleArgumentsObject argsObj)
{
argsObj.set(nullptr);
LiveScopeVal* maybeScope = DebugScopes::hasLiveScope(scope);
if (!maybeScope)
return true;
argsObj.set(ArgumentsObject::createUnexpected(cx, maybeScope->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, ScopeObject& scope,
MutableHandleValue thisv, bool* success)
{
*success = false;
LiveScopeVal* maybeScope = DebugScopes::hasLiveScope(scope);
if (!maybeScope)
return true;
if (!GetFunctionThis(cx, maybeScope->frame(), thisv))
return false;
*success = true;
return true;
}
public:
static const char family;
static const DebugScopeProxy singleton;
MOZ_CONSTEXPR DebugScopeProxy() : BaseProxyHandler(&family) {}
static bool isFunctionScopeWithThis(const JSObject& scope)
{
// All functions except arrows and generator expression lambdas should
// have their own this binding.
return isFunctionScope(scope) && !scope.as<CallObject>().callee().hasLexicalThis();
}
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<DebugScopeObject*> debugScope,
ScopeObject& scope,
MutableHandle<PropertyDescriptor> desc) const
{
RootedArgumentsObject argsObj(cx);
if (!createMissingArguments(cx, scope, &argsObj))
return false;
if (!argsObj) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger scope");
return false;
}
desc.object().set(debugScope);
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<DebugScopeObject*> debugScope,
ScopeObject& scope,
MutableHandle<PropertyDescriptor> desc) const
{
RootedValue thisv(cx);
bool success;
if (!createMissingThis(cx, scope, &thisv, &success))
return false;
if (!success) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger scope");
return false;
}
desc.object().set(debugScope);
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<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
Rooted<ScopeObject*> scope(cx, &debugScope->scope());
if (isMissingArguments(cx, id, *scope))
return getMissingArgumentsPropertyDescriptor(cx, debugScope, *scope, desc);
if (isMissingThis(cx, id, *scope))
return getMissingThisPropertyDescriptor(cx, debugScope, *scope, desc);
RootedValue v(cx);
AccessResult access;
if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, &v, &access))
return false;
switch (access) {
case ACCESS_UNALIASED:
if (isMagicMissingArgumentsValue(cx, *scope, v))
return getMissingArgumentsPropertyDescriptor(cx, debugScope, *scope, desc);
desc.object().set(debugScope);
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, scope, id, desc);
case ACCESS_LOST:
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT);
return false;
default:
MOZ_CRASH("bad AccessResult");
}
}
bool getMissingArguments(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const
{
RootedArgumentsObject argsObj(cx);
if (!createMissingArguments(cx, scope, &argsObj))
return false;
if (!argsObj) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger scope");
return false;
}
vp.setObject(*argsObj);
return true;
}
bool getMissingThis(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const
{
RootedValue thisv(cx);
bool success;
if (!createMissingThis(cx, scope, &thisv, &success))
return false;
if (!success) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger scope");
return false;
}
vp.set(thisv);
return true;
}
bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,
MutableHandleValue vp) const override
{
Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());
if (isMissingArguments(cx, id, *scope))
return getMissingArguments(cx, *scope, vp);
if (isMissingThis(cx, id, *scope))
return getMissingThis(cx, *scope, vp);
AccessResult access;
if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access))
return false;
switch (access) {
case ACCESS_UNALIASED:
if (isMagicMissingArgumentsValue(cx, *scope, vp))
return getMissingArguments(cx, *scope, vp);
return true;
case ACCESS_GENERIC:
return GetProperty(cx, scope, scope, id, vp);
case ACCESS_LOST:
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT);
return false;
default:
MOZ_CRASH("bad AccessResult");
}
}
bool getMissingArgumentsMaybeSentinelValue(JSContext* cx, ScopeObject& scope,
MutableHandleValue vp) const
{
RootedArgumentsObject argsObj(cx);
if (!createMissingArguments(cx, scope, &argsObj))
return false;
vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_OPTIMIZED_ARGUMENTS));
return true;
}
bool getMissingThisMaybeSentinelValue(JSContext* cx, ScopeObject& scope,
MutableHandleValue vp) const
{
RootedValue thisv(cx);
bool success;
if (!createMissingThis(cx, scope, &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<DebugScopeObject*> debugScope, HandleId id,
MutableHandleValue vp) const
{
Rooted<ScopeObject*> scope(cx, &debugScope->scope());
if (isMissingArguments(cx, id, *scope))
return getMissingArgumentsMaybeSentinelValue(cx, *scope, vp);
if (isMissingThis(cx, id, *scope))
return getMissingThisMaybeSentinelValue(cx, *scope, vp);
AccessResult access;
if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access))
return false;
switch (access) {
case ACCESS_UNALIASED:
if (isMagicMissingArgumentsValue(cx, *scope, vp))
return getMissingArgumentsMaybeSentinelValue(cx, *scope, vp);
return true;
case ACCESS_GENERIC:
return GetProperty(cx, scope, scope, id, vp);
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<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>());
Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());
if (debugScope->isOptimizedOut())
return Throw(cx, id, JSMSG_DEBUG_CANT_SET_OPT_ENV);
AccessResult access;
RootedValue valCopy(cx, v);
if (!handleUnaliasedAccess(cx, debugScope, scope, id, SET, &valCopy, &access))
return false;
switch (access) {
case ACCESS_UNALIASED:
return result.succeed();
case ACCESS_GENERIC:
{
RootedValue scopeVal(cx, ObjectValue(*scope));
return SetProperty(cx, scope, id, v, scopeVal, result);
}
default:
MOZ_CRASH("bad AccessResult");
}
}
bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) const override
{
Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());
bool found;
if (!has(cx, proxy, id, &found))
return false;
if (found)
return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP);
return JS_DefinePropertyById(cx, scope, id, desc, result);
}
bool ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const override
{
Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope());
if (isMissingArgumentsBinding(*scope)) {
if (!props.append(NameToId(cx->names().arguments)))
return false;
}
if (isMissingThisBinding(*scope)) {
if (!props.append(NameToId(cx->names().dotThis)))
return false;
}
// DynamicWithObject 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 DynamicWithObject
// wrapper yields no properties. So instead here we hack around the
// issue, and punch a hole through to the with object target.
Rooted<JSObject*> target(cx, (scope->is<DynamicWithObject>()
? &scope->as<DynamicWithObject>().object() : scope));
if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props))
return false;
/*
* Function scopes are optimized to not contain unaliased variables so
* they must be manually appended here.
*/
if (isFunctionScope(*scope)) {
RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript());
for (BindingIter bi(script); bi; bi++) {
if (!bi->aliased() && !props.append(NameToId(bi->name())))
return false;
}
}
return true;
}
bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const override
{
return BaseProxyHandler::enumerate(cx, proxy, objp);
}
bool has(JSContext* cx, HandleObject proxy, HandleId id_, bool* bp) const override
{
RootedId id(cx, id_);
ScopeObject& scopeObj = proxy->as<DebugScopeObject>().scope();
if (isArguments(cx, id) && isFunctionScope(scopeObj)) {
*bp = true;
return true;
}
if (isThis(cx, id) && isFunctionScopeWithThis(scopeObj)) {
*bp = true;
return true;
}
bool found;
RootedObject scope(cx, &scopeObj);
if (!JS_HasPropertyById(cx, scope, id, &found))
return false;
/*
* Function scopes are optimized to not contain unaliased variables so
* a manual search is necessary.
*/
if (!found && isFunctionScope(*scope)) {
RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript());
for (BindingIter bi(script); bi; bi++) {
if (!bi->aliased() && NameToId(bi->name()) == 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::DebugScopeObject>() const
{
return IsDerivedProxyObject(this, &DebugScopeProxy::singleton);
}
const char DebugScopeProxy::family = 0;
const DebugScopeProxy DebugScopeProxy::singleton;
/* static */ DebugScopeObject*
DebugScopeObject::create(JSContext* cx, ScopeObject& scope, HandleObject enclosing)
{
MOZ_ASSERT(scope.compartment() == cx->compartment());
MOZ_ASSERT(!enclosing->is<ScopeObject>());
RootedValue priv(cx, ObjectValue(scope));
JSObject* obj = NewProxyObject(cx, &DebugScopeProxy::singleton, priv,
nullptr /* proto */);
if (!obj)
return nullptr;
DebugScopeObject* debugScope = &obj->as<DebugScopeObject>();
debugScope->setExtra(ENCLOSING_EXTRA, ObjectValue(*enclosing));
debugScope->setExtra(SNAPSHOT_EXTRA, NullValue());
return debugScope;
}
ScopeObject&
DebugScopeObject::scope() const
{
return target()->as<ScopeObject>();
}
JSObject&
DebugScopeObject::enclosingScope() const
{
return extra(ENCLOSING_EXTRA).toObject();
}
ArrayObject*
DebugScopeObject::maybeSnapshot() const
{
MOZ_ASSERT(!scope().as<CallObject>().isForEval());
JSObject* obj = extra(SNAPSHOT_EXTRA).toObjectOrNull();
return obj ? &obj->as<ArrayObject>() : nullptr;
}
void
DebugScopeObject::initSnapshot(ArrayObject& o)
{
MOZ_ASSERT(maybeSnapshot() == nullptr);
setExtra(SNAPSHOT_EXTRA, ObjectValue(o));
}
bool
DebugScopeObject::isForDeclarative() const
{
ScopeObject& s = scope();
return s.is<LexicalScopeBase>() || s.is<BlockObject>() || s.is<DeclEnvObject>();
}
bool
DebugScopeObject::getMaybeSentinelValue(JSContext* cx, HandleId id, MutableHandleValue vp)
{
Rooted<DebugScopeObject*> self(cx, this);
return DebugScopeProxy::singleton.getMaybeSentinelValue(cx, self, id, vp);
}
bool
DebugScopeObject::isFunctionScopeWithThis()
{
return DebugScopeProxy::isFunctionScopeWithThis(scope());
}
bool
DebugScopeObject::isOptimizedOut() const
{
ScopeObject& s = scope();
if (DebugScopes::hasLiveScope(s))
return false;
if (s.is<ClonedBlockObject>())
return !s.as<ClonedBlockObject>().staticBlock().needsClone();
if (s.is<CallObject>()) {
return !s.as<CallObject>().isForEval() &&
!s.as<CallObject>().callee().needsCallObject() &&
!maybeSnapshot();
}
return false;
}
/*****************************************************************************/
DebugScopes::DebugScopes(JSContext* cx)
: proxiedScopes(cx),
missingScopes(cx->runtime()),
liveScopes(cx->runtime())
{}
DebugScopes::~DebugScopes()
{
MOZ_ASSERT(missingScopes.empty());
}
bool
DebugScopes::init()
{
return proxiedScopes.init() && missingScopes.init() && liveScopes.init();
}
void
DebugScopes::mark(JSTracer* trc)
{
proxiedScopes.trace(trc);
}
void
DebugScopes::sweep(JSRuntime* rt)
{
/*
* missingScopes points to debug scopes weakly so that debug scopes can be
* released more eagerly.
*/
for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) {
if (IsAboutToBeFinalized(&e.front().value())) {
/*
* Note that onPopCall and onPopBlock rely on missingScopes to find
* scope objects that we synthesized for the debugger's sake, and
* clean up the synthetic scope objects' entries in liveScopes. So
* if we remove an entry frcom missingScopes here, we must also
* remove the corresponding liveScopes entry.
*
* Since the DebugScopeObject is the only thing using its scope
* 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 liveScopes
* and missingScopes here.
*/
liveScopes.remove(&e.front().value().unbarrieredGet()->scope());
e.removeFront();
} else {
MissingScopeKey key = e.front().key();
if (IsForwarded(key.staticScope())) {
key.updateStaticScope(Forwarded(key.staticScope()));
e.rekeyFront(key);
}
}
}
/*
* Scopes can be finalized when a debugger-synthesized ScopeObject is
* no longer reachable via its DebugScopeObject.
*/
liveScopes.sweep();
}
#ifdef JSGC_HASH_TABLE_CHECKS
void
DebugScopes::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.
*/
proxiedScopes.checkAfterMovingGC();
for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) {
CheckGCThingAfterMovingGC(r.front().key().staticScope());
CheckGCThingAfterMovingGC(r.front().value().get());
}
for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) {
CheckGCThingAfterMovingGC(r.front().key());
CheckGCThingAfterMovingGC(r.front().value().staticScope_.get());
}
}
#endif
/*
* Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode
* (in particular, JS_GetFrameScopeChain does not require debug mode). Since
* DebugScopes::onPop* are only called in debuggee frames, this means we
* cannot use any of the maps in DebugScopes. This will produce debug scope
* chains that do not obey the debugger invariants but that is just fine.
*/
static bool
CanUseDebugScopeMaps(JSContext* cx)
{
return cx->compartment()->isDebuggee();
}
DebugScopes*
DebugScopes::ensureCompartmentData(JSContext* cx)
{
JSCompartment* c = cx->compartment();
if (c->debugScopes)
return c->debugScopes;
c->debugScopes = cx->runtime()->new_<DebugScopes>(cx);
if (c->debugScopes && c->debugScopes->init())
return c->debugScopes;
if (c->debugScopes)
js_delete<DebugScopes>(c->debugScopes);
c->debugScopes = nullptr;
ReportOutOfMemory(cx);
return nullptr;
}
DebugScopeObject*
DebugScopes::hasDebugScope(JSContext* cx, ScopeObject& scope)
{
DebugScopes* scopes = scope.compartment()->debugScopes;
if (!scopes)
return nullptr;
if (JSObject* obj = scopes->proxiedScopes.lookup(&scope)) {
MOZ_ASSERT(CanUseDebugScopeMaps(cx));
return &obj->as<DebugScopeObject>();
}
return nullptr;
}
bool
DebugScopes::addDebugScope(JSContext* cx, ScopeObject& scope, DebugScopeObject& debugScope)
{
MOZ_ASSERT(cx->compartment() == scope.compartment());
MOZ_ASSERT(cx->compartment() == debugScope.compartment());
if (!CanUseDebugScopeMaps(cx))
return true;
DebugScopes* scopes = ensureCompartmentData(cx);
if (!scopes)
return false;
return scopes->proxiedScopes.add(cx, &scope, &debugScope);
}
DebugScopeObject*
DebugScopes::hasDebugScope(JSContext* cx, const ScopeIter& si)
{
MOZ_ASSERT(!si.hasSyntacticScopeObject());
DebugScopes* scopes = cx->compartment()->debugScopes;
if (!scopes)
return nullptr;
if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
MOZ_ASSERT(CanUseDebugScopeMaps(cx));
return p->value();
}
return nullptr;
}
bool
DebugScopes::addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope)
{
MOZ_ASSERT(!si.hasSyntacticScopeObject());
MOZ_ASSERT(cx->compartment() == debugScope.compartment());
// Generators should always reify their scopes.
MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().isGenerator());
if (!CanUseDebugScopeMaps(cx))
return true;
DebugScopes* scopes = ensureCompartmentData(cx);
if (!scopes)
return false;
MissingScopeKey key(si);
MOZ_ASSERT(!scopes->missingScopes.has(key));
if (!scopes->missingScopes.put(key, ReadBarriered<DebugScopeObject*>(&debugScope))) {
ReportOutOfMemory(cx);
return false;
}
// Only add to liveScopes if we synthesized the debug scope on a live
// frame.
if (si.withinInitialFrame()) {
MOZ_ASSERT(!scopes->liveScopes.has(&debugScope.scope()));
if (!scopes->liveScopes.put(&debugScope.scope(), LiveScopeVal(si))) {
ReportOutOfMemory(cx);
return false;
}
}
return true;
}
void
DebugScopes::onPopCall(AbstractFramePtr frame, JSContext* cx)
{
assertSameCompartment(cx, frame);
DebugScopes* scopes = cx->compartment()->debugScopes;
if (!scopes)
return;
Rooted<DebugScopeObject*> debugScope(cx, nullptr);
if (frame.fun()->needsCallObject()) {
/*
* The frame may be observed before the prologue has created the
* CallObject. See ScopeIter::settle.
*/
if (!frame.hasCallObj())
return;
if (frame.fun()->isGenerator())
return;
CallObject& callobj = frame.scopeChain()->as<CallObject>();
scopes->liveScopes.remove(&callobj);
if (JSObject* obj = scopes->proxiedScopes.lookup(&callobj))
debugScope = &obj->as<DebugScopeObject>();
} else {
ScopeIter si(cx, frame, frame.script()->main());
if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
debugScope = p->value();
scopes->liveScopes.remove(&debugScope->scope().as<CallObject>());
scopes->missingScopes.remove(p);
}
}
/*
* When the JS stack frame is popped, the values of unaliased variables
* are lost. If there is any debug scope referring to this scope, save a
* copy of the unaliased variables' values in an array for later debugger
* access via DebugScopeProxy::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 DebugScopeObject::maybeSnapshot can already be nullptr.
*/
if (debugScope) {
/*
* Copy all frame values into the snapshot, regardless of
* aliasing. This unnecessarily includes aliased variables
* but it simplifies later indexing logic.
*/
AutoValueVector vec(cx);
if (!frame.copyRawFrameSlots(&vec) || vec.length() == 0)
return;
/*
* Copy in formals that are not aliased via the scope chain
* but are aliased via the arguments object.
*/
RootedScript script(cx, frame.script());
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));
}
}
/*
* 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->clearPendingException();
return;
}
debugScope->initSnapshot(*snapshot);
}
}
void
DebugScopes::onPopBlock(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
assertSameCompartment(cx, frame);
DebugScopes* scopes = cx->compartment()->debugScopes;
if (!scopes)
return;
ScopeIter si(cx, frame, pc);
onPopBlock(cx, si);
}
void
DebugScopes::onPopBlock(JSContext* cx, const ScopeIter& si)
{
DebugScopes* scopes = cx->compartment()->debugScopes;
if (!scopes)
return;
MOZ_ASSERT(si.withinInitialFrame());
MOZ_ASSERT(si.type() == ScopeIter::Block);
if (si.staticBlock().needsClone()) {
ClonedBlockObject& clone = si.scope().as<ClonedBlockObject>();
clone.copyUnaliasedValues(si.initialFrame());
scopes->liveScopes.remove(&clone);
} else {
if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
ClonedBlockObject& clone = p->value()->scope().as<ClonedBlockObject>();
clone.copyUnaliasedValues(si.initialFrame());
scopes->liveScopes.remove(&clone);
scopes->missingScopes.remove(p);
}
}
}
void
DebugScopes::onPopWith(AbstractFramePtr frame)
{
DebugScopes* scopes = frame.compartment()->debugScopes;
if (scopes)
scopes->liveScopes.remove(&frame.scopeChain()->as<DynamicWithObject>());
}
void
DebugScopes::onPopStrictEvalScope(AbstractFramePtr frame)
{
DebugScopes* scopes = frame.compartment()->debugScopes;
if (!scopes)
return;
/*
* The stack frame may be observed before the prologue has created the
* CallObject. See ScopeIter::settle.
*/
if (frame.hasCallObj())
scopes->liveScopes.remove(&frame.scopeChain()->as<CallObject>());
}
void
DebugScopes::onCompartmentUnsetIsDebuggee(JSCompartment* c)
{
DebugScopes* scopes = c->debugScopes;
if (scopes) {
scopes->proxiedScopes.clear();
scopes->missingScopes.clear();
scopes->liveScopes.clear();
}
}
bool
DebugScopes::updateLiveScopes(JSContext* cx)
{
JS_CHECK_RECURSION(cx, return false);
/*
* Note that we must always update the top frame's scope objects' entries
* in liveScopes because we can't be sure code hasn't run in that frame to
* change the scope chain since we were last called. The fp->prevUpToDate()
* flag indicates whether the scopes of frames older than fp are already
* included in liveScopes. 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.scopeChain()->compartment() != cx->compartment())
continue;
if (frame.isFunctionFrame() && frame.callee()->isGenerator())
continue;
if (!frame.isDebuggee())
continue;
for (ScopeIter si(cx, frame, i.pc()); si.withinInitialFrame(); ++si) {
if (si.hasSyntacticScopeObject()) {
MOZ_ASSERT(si.scope().compartment() == cx->compartment());
DebugScopes* scopes = ensureCompartmentData(cx);
if (!scopes)
return false;
if (!scopes->liveScopes.put(&si.scope(), LiveScopeVal(si)))
return false;
}
}
if (frame.prevUpToDate())
return true;
MOZ_ASSERT(frame.scopeChain()->compartment()->isDebuggee());
frame.setPrevUpToDate();
}
return true;
}
LiveScopeVal*
DebugScopes::hasLiveScope(ScopeObject& scope)
{
DebugScopes* scopes = scope.compartment()->debugScopes;
if (!scopes)
return nullptr;
if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope))
return &p->value();
return nullptr;
}
/* static */ void
DebugScopes::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 updateLiveScopes. 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.scopeChain()->compartment() != cx->compartment())
continue;
frame.unsetPrevUpToDate();
}
}
/* static */ void
DebugScopes::forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to)
{
DebugScopes* scopes = cx->compartment()->debugScopes;
if (!scopes)
return;
for (MissingScopeMap::Enum e(scopes->missingScopes); !e.empty(); e.popFront()) {
MissingScopeKey key = e.front().key();
if (key.frame() == from) {
key.updateFrame(to);
e.rekeyFront(key);
}
}
for (LiveScopeMap::Enum e(scopes->liveScopes); !e.empty(); e.popFront()) {
LiveScopeVal& val = e.front().value();
if (val.frame() == from)
val.updateFrame(to);
}
}
/*****************************************************************************/
static JSObject*
GetDebugScope(JSContext* cx, const ScopeIter& si);
static DebugScopeObject*
GetDebugScopeForScope(JSContext* cx, const ScopeIter& si)
{
Rooted<ScopeObject*> scope(cx, &si.scope());
if (DebugScopeObject* debugScope = DebugScopes::hasDebugScope(cx, *scope))
return debugScope;
ScopeIter copy(cx, si);
RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy));
if (!enclosingDebug)
return nullptr;
JSObject& maybeDecl = scope->enclosingScope();
if (maybeDecl.is<DeclEnvObject>()) {
MOZ_ASSERT(CallObjectLambdaName(scope->as<CallObject>().callee()));
enclosingDebug = DebugScopeObject::create(cx, maybeDecl.as<DeclEnvObject>(), enclosingDebug);
if (!enclosingDebug)
return nullptr;
}
DebugScopeObject* debugScope = DebugScopeObject::create(cx, *scope, enclosingDebug);
if (!debugScope)
return nullptr;
if (!DebugScopes::addDebugScope(cx, *scope, *debugScope))
return nullptr;
return debugScope;
}
static DebugScopeObject*
GetDebugScopeForMissing(JSContext* cx, const ScopeIter& si)
{
MOZ_ASSERT(!si.hasSyntacticScopeObject() && si.canHaveSyntacticScopeObject());
if (DebugScopeObject* debugScope = DebugScopes::hasDebugScope(cx, si))
return debugScope;
ScopeIter copy(cx, si);
RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy));
if (!enclosingDebug)
return nullptr;
/*
* Create the missing scope object. For block 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 DebugScopeObject has a ScopeObject.
*
* Note: to preserve scopeChain depth invariants, these lazily-reified
* scopes must not be put on the frame's scope chain; instead, they are
* maintained via DebugScopes hooks.
*/
DebugScopeObject* debugScope = nullptr;
switch (si.type()) {
case ScopeIter::Module:
MOZ_CRASH(); // TODO: Implement debug scopes for modules.
break;
case ScopeIter::Call: {
RootedFunction callee(cx, &si.fun());
// Generators should always reify their scopes.
MOZ_ASSERT(!callee->isGenerator());
Rooted<CallObject*> callobj(cx);
if (si.withinInitialFrame())
callobj = CallObject::createForFunction(cx, si.initialFrame());
else
callobj = CallObject::createHollowForDebug(cx, callee);
if (!callobj)
return nullptr;
if (callobj->enclosingScope().is<DeclEnvObject>()) {
MOZ_ASSERT(CallObjectLambdaName(callobj->callee()));
DeclEnvObject& declenv = callobj->enclosingScope().as<DeclEnvObject>();
enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug);
if (!enclosingDebug)
return nullptr;
}
debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug);
break;
}
case ScopeIter::Block: {
// Generators should always reify their scopes, except in this one
// weird case of deprecated let expressions where we can create a
// 0-variable StaticBlockObject inside a generator that does not need
// cloning.
//
// For example, |let ({} = "") { yield evalInFrame("foo"); }|.
MOZ_ASSERT_IF(si.staticBlock().numVariables() > 0 &&
si.withinInitialFrame() &&
si.initialFrame().isFunctionFrame(),
!si.initialFrame().callee()->isGenerator());
Rooted<StaticBlockObject*> staticBlock(cx, &si.staticBlock());
ClonedBlockObject* block;
if (si.withinInitialFrame())
block = ClonedBlockObject::create(cx, staticBlock, si.initialFrame());
else
block = ClonedBlockObject::createHollowForDebug(cx, staticBlock);
if (!block)
return nullptr;
debugScope = DebugScopeObject::create(cx, *block, enclosingDebug);
break;
}
case ScopeIter::With:
case ScopeIter::Eval:
MOZ_CRASH("should already have a scope");
case ScopeIter::NonSyntactic:
MOZ_CRASH("non-syntactic scopes cannot be synthesized");
}
if (!debugScope)
return nullptr;
if (!DebugScopes::addDebugScope(cx, si, *debugScope))
return nullptr;
return debugScope;
}
static JSObject*
GetDebugScopeForNonScopeObject(const ScopeIter& si)
{
JSObject& enclosing = si.enclosingScope();
MOZ_ASSERT(!enclosing.is<ScopeObject>());
#ifdef DEBUG
JSObject* o = &enclosing;
while ((o = o->enclosingScope()))
MOZ_ASSERT(!o->is<ScopeObject>());
#endif
return &enclosing;
}
static JSObject*
GetDebugScope(JSContext* cx, const ScopeIter& si)
{
JS_CHECK_RECURSION(cx, return nullptr);
if (si.done())
return GetDebugScopeForNonScopeObject(si);
if (si.hasAnyScopeObject())
return GetDebugScopeForScope(cx, si);
if (si.canHaveSyntacticScopeObject())
return GetDebugScopeForMissing(cx, si);
ScopeIter copy(cx, si);
return GetDebugScope(cx, ++copy);
}
JSObject*
js::GetDebugScopeForFunction(JSContext* cx, HandleFunction fun)
{
assertSameCompartment(cx, fun);
MOZ_ASSERT(CanUseDebugScopeMaps(cx));
if (!DebugScopes::updateLiveScopes(cx))
return nullptr;
JSScript* script = fun->getOrCreateScript(cx);
if (!script)
return nullptr;
ScopeIter si(cx, fun->environment(), script->enclosingStaticScope());
return GetDebugScope(cx, si);
}
JSObject*
js::GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc)
{
assertSameCompartment(cx, frame);
if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx))
return nullptr;
ScopeIter si(cx, frame, pc);
return GetDebugScope(cx, si);
}
JSObject*
js::GetDebugScopeForGlobalLexicalScope(JSContext* cx)
{
ScopeIter si(cx, &cx->global()->lexicalScope(), &cx->global()->lexicalScope().staticBlock());
return GetDebugScope(cx, si);
}
// See declaration and documentation in jsfriendapi.h
JS_FRIEND_API(JSObject*)
js::GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun)
{
if (!fun->isInterpreted())
return &fun->global();
JSObject* env = fun->environment();
while (env && !env->is<DynamicWithObject>())
env = env->enclosingScope();
if (!env)
return &fun->global();
return &env->as<DynamicWithObject>().object();
}
bool
js::CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain,
HandleObject dynamicTerminatingScope,
MutableHandleObject dynamicScopeObj)
{
#ifdef DEBUG
for (size_t i = 0; i < scopeChain.length(); ++i) {
assertSameCompartment(cx, scopeChain[i]);
MOZ_ASSERT(!scopeChain[i]->is<GlobalObject>());
}
#endif
// Construct With object wrappers for the things on this scope
// chain and use the result as the thing to scope the function to.
Rooted<StaticWithObject*> staticWith(cx);
RootedObject staticEnclosingScope(cx);
Rooted<DynamicWithObject*> dynamicWith(cx);
RootedObject dynamicEnclosingScope(cx, dynamicTerminatingScope);
for (size_t i = scopeChain.length(); i > 0; ) {
staticWith = StaticWithObject::create(cx);
if (!staticWith)
return false;
staticWith->initEnclosingScope(staticEnclosingScope);
staticEnclosingScope = staticWith;
dynamicWith = DynamicWithObject::create(cx, scopeChain[--i], dynamicEnclosingScope,
staticWith, DynamicWithObject::NonSyntacticWith);
if (!dynamicWith)
return false;
dynamicEnclosingScope = dynamicWith;
}
dynamicScopeObj.set(dynamicEnclosingScope);
return true;
}
bool
js::HasNonSyntacticStaticScopeChain(JSObject* staticScope)
{
for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++) {
// If we hit a function scope, we can short circuit the logic, as
// scripts cache whether they are under a non-syntactic scope.
if (ssi.type() == StaticScopeIter<NoGC>::Function)
return ssi.funScript()->hasNonSyntacticScope();
if (ssi.type() == StaticScopeIter<NoGC>::NonSyntactic)
return true;
}
return false;
}
uint32_t
js::StaticScopeChainLength(JSObject* staticScope)
{
uint32_t length = 0;
for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++)
length++;
return length;
}
ModuleEnvironmentObject*
js::GetModuleEnvironmentForScript(JSScript* script)
{
StaticScopeIter<NoGC> ssi(script->enclosingStaticScope());
while (!ssi.done() && ssi.type() != StaticScopeIter<NoGC>::Module)
ssi++;
if (ssi.done())
return nullptr;
return ssi.module().environment();
}
bool
js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
MutableHandleValue res)
{
for (ScopeIter si(cx, frame, pc); !si.done(); ++si) {
if (si.type() == ScopeIter::Module) {
res.setUndefined();
return true;
}
if (si.type() != ScopeIter::Call || si.fun().hasLexicalThis())
continue;
RootedScript script(cx, si.fun().nonLazyScript());
if (!script->functionHasThisBinding()) {
MOZ_ASSERT(!script->isDerivedClassConstructor(),
"Derived class constructors always have a this-binding");
// If we're still inside `frame`, we can use the this-value passed
// to it, if it does not require boxing.
if (si.withinInitialFrame() && (frame.thisArgument().isObject() || script->strict()))
res.set(frame.thisArgument());
else
res.setMagic(JS_OPTIMIZED_OUT);
return true;
}
BindingIter bi = Bindings::thisBinding(cx, script);
if (script->bindingIsAliased(bi)) {
RootedObject callObj(cx, &si.scope().as<CallObject>());
return GetProperty(cx, callObj, callObj, cx->names().dotThis, res);
}
if (si.withinInitialFrame())
res.set(frame.unaliasedLocal(bi.frameIndex()));
else
res.setMagic(JS_OPTIMIZED_OUT);
return true;
}
RootedObject scopeChain(cx, frame.scopeChain());
return GetNonSyntacticGlobalThis(cx, scopeChain, res);
}
bool
js::CheckLexicalNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
HandleObject varObj, HandlePropertyName name)
{
mozilla::Maybe<frontend::Definition::Kind> redeclKind;
RootedId id(cx, NameToId(name));
RootedShape shape(cx);
if ((shape = lexicalScope->lookup(cx, name))) {
redeclKind = mozilla::Some(shape->writable() ? frontend::Definition::LET
: frontend::Definition::CONSTANT);
} else if (varObj->isNative() && (shape = varObj->as<NativeObject>().lookup(cx, name))) {
if (!shape->configurable())
redeclKind = mozilla::Some(frontend::Definition::VAR);
} else {
Rooted<PropertyDescriptor> desc(cx);
if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc))
return false;
if (desc.object() && desc.hasConfigurable() && !desc.configurable())
redeclKind = mozilla::Some(frontend::Definition::VAR);
}
if (redeclKind.isSome()) {
ReportRuntimeRedeclaration(cx, name, *redeclKind);
return false;
}
return true;
}
bool
js::CheckVarNameConflict(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
HandlePropertyName name)
{
if (Shape* shape = lexicalScope->lookup(cx, name)) {
ReportRuntimeRedeclaration(cx, name, shape->writable() ? frontend::Definition::LET
: frontend::Definition::CONSTANT);
return false;
}
return true;
}
static bool
CheckVarNameConflict(JSContext* cx, Handle<CallObject*> callObj, HandlePropertyName name)
{
RootedFunction fun(cx, &callObj->callee());
RootedScript script(cx, fun->nonLazyScript());
uint32_t bodyLevelLexicalsStart = script->bindings.numVars();
for (BindingIter bi(script); !bi.done(); bi++) {
if (name == bi->name() &&
bi.isBodyLevelLexical() &&
bi.localIndex() >= bodyLevelLexicalsStart)
{
ReportRuntimeRedeclaration(cx, name,
bi->kind() == Binding::CONSTANT
? frontend::Definition::CONSTANT
: frontend::Definition::LET);
return false;
}
}
return true;
}
bool
js::CheckGlobalDeclarationConflicts(JSContext* cx, HandleScript script,
Handle<ClonedBlockObject*> lexicalScope,
HandleObject varObj)
{
// Due to the extensibility of the global lexical scope, we must check for
// redeclaring a binding.
//
// In the case of non-syntactic scope chains, we are checking
// redeclarations against the non-syntactic lexical scope and the
// variables object that the lexical scope corresponds to.
RootedPropertyName name(cx);
BindingIter bi(script);
for (uint32_t i = 0; i < script->bindings.numVars(); i++, bi++) {
name = bi->name();
if (!CheckVarNameConflict(cx, lexicalScope, name))
return false;
}
for (uint32_t i = 0; i < script->bindings.numBodyLevelLexicals(); i++, bi++) {
name = bi->name();
if (!CheckLexicalNameConflict(cx, lexicalScope, varObj, name))
return false;
}
return true;
}
template <class ScopeT>
static bool
CheckVarNameConflictsInScope(JSContext* cx, HandleScript script, HandleObject obj)
{
Rooted<ScopeT*> scope(cx);
// We return true when the scope object is not ScopeT below, because
// ScopeT is either ClonedBlockObject or CallObject. No other scope
// objects can contain lexical bindings, and there are no other overloads
// for CheckVarNameConflict.
if (obj->is<ScopeT>())
scope = &obj->as<ScopeT>();
else if (obj->is<DebugScopeObject>() && obj->as<DebugScopeObject>().scope().is<ScopeT>())
scope = &obj->as<DebugScopeObject>().scope().as<ScopeT>();
else
return true;
RootedPropertyName name(cx);
for (BindingIter bi(script); !bi.done(); bi++) {
name = bi->name();
if (!CheckVarNameConflict(cx, scope, name))
return false;
}
return true;
}
bool
js::CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script,
HandleObject scopeChain, HandleObject varObj)
{
// We don't need to check body-level lexical bindings for conflict. Eval
// scripts always execute under their own lexical scope.
if (script->bindings.numVars() == 0)
return true;
RootedObject obj(cx, scopeChain);
// ES6 18.2.1.2 step d
//
// Check that a direct eval will not hoist 'var' bindings over lexical
// bindings with the same name.
while (obj != varObj) {
if (!CheckVarNameConflictsInScope<ClonedBlockObject>(cx, script, obj))
return false;
obj = obj->enclosingScope();
}
return CheckVarNameConflictsInScope<CallObject>(cx, script, varObj);
}
#ifdef DEBUG
void
js::DumpStaticScopeChain(JSScript* script)
{
DumpStaticScopeChain(script->enclosingStaticScope());
}
void
js::DumpStaticScopeChain(JSObject* staticScope)
{
for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++) {
switch (ssi.type()) {
case StaticScopeIter<NoGC>::Module:
fprintf(stdout, "module [%p]", &ssi.module());
break;
case StaticScopeIter<NoGC>::Function:
if (ssi.fun().isBeingParsed())
fprintf(stdout, "funbox [%p fun=%p]", ssi.maybeFunctionBox(), &ssi.fun());
else
fprintf(stdout, "function [%p]", &ssi.fun());
break;
case StaticScopeIter<NoGC>::Block:
fprintf(stdout, "block [%p]", &ssi.block());
break;
case StaticScopeIter<NoGC>::With:
fprintf(stdout, "with [%p]", &ssi.staticWith());
break;
case StaticScopeIter<NoGC>::NamedLambda:
fprintf(stdout, "named lambda");
break;
case StaticScopeIter<NoGC>::Eval:
fprintf(stdout, "eval [%p]", &ssi.eval());
break;
case StaticScopeIter<NoGC>::NonSyntactic:
fprintf(stdout, "non-syntactic [%p]", &ssi.nonSyntactic());
break;
}
fprintf(stdout, " -> ");
}
fprintf(stdout, "global\n");
}
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 = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
break;
default:
name = nullptr;
break;
}
if (name)
remainingNames.remove(name);
}
if (script->hasObjects()) {
ObjectArray* objects = script->objects();
for (size_t i = 0; i < objects->length; i++) {
JSObject* obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
JSFunction* fun = &obj->as<JSFunction>();
RootedScript innerScript(cx, fun->getOrCreateScript(cx));
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->aliased()) {
PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(bi->name());
if (!p && !remainingNames.add(p, bi->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();
for (size_t i = 0; i < objects->length; i++) {
JSObject* obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
JSFunction* fun = &obj->as<JSFunction>();
RootedScript innerInnerScript(cx, fun->getOrCreateScript(cx));
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();
for (size_t i = 0; i < objects->length; i++) {
JSObject* obj = objects->vector[i];
if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
JSFunction* fun = &obj->as<JSFunction>();
RootedScript innerScript(cx, fun->getOrCreateScript(cx));
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