summaryrefslogtreecommitdiffstats
path: root/js/src/vm/EnvironmentObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/EnvironmentObject.cpp')
-rw-r--r--js/src/vm/EnvironmentObject.cpp3594
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