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