/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/EnvironmentObject-inl.h" #include "mozilla/Maybe.h" #include "builtin/ModuleObject.h" #include "js/Exception.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_HasProperty, JS_HasPropertyById #include "vm/ArgumentsObject.h" #include "vm/BytecodeIterator.h" #include "vm/BytecodeLocation.h" #include "vm/GeneratorObject.h" // js::GetGeneratorObjectForEnvironment #include "vm/GlobalObject.h" #include "vm/JSObject.h" #include "vm/ProxyObject.h" #include "vm/Realm.h" #include "vm/Scope.h" #include "vm/Shape.h" #include "wasm/WasmDebug.h" #include "wasm/WasmDebugFrame.h" #include "wasm/WasmInstance.h" #include "gc/Marking-inl.h" #include "vm/BytecodeIterator-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Stack-inl.h" using namespace js; using RootedArgumentsObject = Rooted; using MutableHandleArgumentsObject = MutableHandle; /*****************************************************************************/ SharedShape* js::EnvironmentCoordinateToEnvironmentShape(JSScript* script, jsbytecode* pc) { MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD); ScopeIter si(script->innermostScope(pc)); uint32_t hops = EnvironmentCoordinate(pc).hops(); while (true) { MOZ_ASSERT(!si.done()); if (si.hasSyntacticEnvironment()) { if (!hops) { break; } hops--; } si++; } return si.environmentShape(); } PropertyName* js::EnvironmentCoordinateNameSlow(JSScript* script, jsbytecode* pc) { SharedShape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc); EnvironmentCoordinate ec(pc); SharedShapePropertyIter iter(shape); while (iter->slot() != ec.slot()) { iter++; } jsid id = iter->key(); /* Beware nameless destructuring formal. */ if (!id.isAtom()) { return script->runtimeFromAnyThread()->commonNames->empty; } return id.toAtom()->asPropertyName(); } /*****************************************************************************/ template static T* CreateEnvironmentObject(JSContext* cx, Handle shape, gc::InitialHeap heap) { static_assert(std::is_base_of_v, "T must be an EnvironmentObject"); // All environment objects can be background-finalized. gc::AllocKind allocKind = gc::GetGCObjectKind(shape->numFixedSlots()); MOZ_ASSERT(CanChangeToBackgroundAllocKind(allocKind, &T::class_)); allocKind = gc::ForegroundToBackgroundAllocKind(allocKind); JSObject* obj = NativeObject::create(cx, allocKind, heap, shape); if (!obj) { return nullptr; } return &obj->as(); } // Helper function for simple environment objects that don't need the overloads // above. template static T* CreateEnvironmentObject(JSContext* cx, Handle shape, NewObjectKind newKind = GenericObject) { gc::InitialHeap heap = GetInitialHeap(newKind, &T::class_); return CreateEnvironmentObject(cx, shape, heap); } CallObject* CallObject::createWithShape(JSContext* cx, Handle shape) { return CreateEnvironmentObject(cx, shape); } /* * 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::create(JSContext* cx, HandleScript script, HandleObject enclosing, gc::InitialHeap heap) { Rooted scope(cx, &script->bodyScope()->as()); Rooted shape(cx, scope->environmentShape()); MOZ_ASSERT(shape->getObjectClass() == &class_); // The JITs assume the result is nursery allocated unless we collected the // nursery, so don't change |heap| here. auto* callObj = CreateEnvironmentObject(cx, shape, heap); if (!callObj) { return nullptr; } if (enclosing) { 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; } CallObject* CallObject::createTemplateObject(JSContext* cx, HandleScript script, HandleObject enclosing) { return create(cx, script, enclosing, gc::TenuredHeap); } CallObject* CallObject::create(JSContext* cx, AbstractFramePtr frame) { MOZ_ASSERT(frame.isFunctionFrame()); cx->check(frame); RootedObject envChain(cx, frame.environmentChain()); RootedFunction callee(cx, frame.callee()); RootedScript script(cx, callee->nonLazyScript()); gc::InitialHeap heap = gc::DefaultHeap; CallObject* callobj = create(cx, script, envChain, heap); if (!callobj) { return nullptr; } callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee)); if (!frame.script()->bodyScope()->as().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( fi, frame.unaliasedFormal(fi.argumentSlot(), DONT_CHECK_ALIASING)); } } return callobj; } template EnvT* FindEnclosingEnv(JSObject* env) { for (;;) { if (env->is()) { break; } else if (env->is()) { env = &env->as().enclosingEnvironment(); } else if (env->is()) { EnvironmentObject& unwrapped = env->as().environment(); if (unwrapped.is()) { env = &unwrapped; break; } env = &env->as().enclosingEnvironment(); } else { MOZ_ASSERT(env->is()); return nullptr; } } return &env->as(); } CallObject* CallObject::find(JSObject* env) { return FindEnclosingEnv(env); } ModuleEnvironmentObject* ModuleEnvironmentObject::find(JSObject* env) { return FindEnclosingEnv(env); } CallObject* CallObject::createHollowForDebug(JSContext* cx, HandleFunction callee) { MOZ_ASSERT(!callee->needsCallObject()); RootedScript script(cx, callee->nonLazyScript()); Rooted scope(cx, &script->bodyScope()->as()); Rooted shape(cx, EmptyEnvironmentShape(cx)); if (!shape) { return nullptr; } Rooted callobj(cx, createWithShape(cx, shape)); 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 bi(cx, BindingIter(script)); bi; bi++) { id = NameToId(bi.name()->asPropertyName()); if (!SetProperty(cx, callobj, id, optimizedOut)) { return nullptr; } } return callobj; } const JSClass CallObject::class_ = { "Call", JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)}; /*****************************************************************************/ /* static */ VarEnvironmentObject* VarEnvironmentObject::createInternal( JSContext* cx, Handle shape, HandleObject enclosing, gc::InitialHeap heap) { MOZ_ASSERT(shape->getObjectClass() == &class_); auto* env = CreateEnvironmentObject(cx, shape, heap); if (!env) { return nullptr; } MOZ_ASSERT(!env->inDictionaryMode()); if (enclosing) { env->initEnclosingEnvironment(enclosing); } return env; } /* static */ VarEnvironmentObject* VarEnvironmentObject::create(JSContext* cx, Handle scope, HandleObject enclosing, gc::InitialHeap heap) { MOZ_ASSERT(scope->is() || scope->is()); Rooted shape(cx, scope->environmentShape()); auto* env = createInternal(cx, shape, enclosing, heap); if (!env) { return nullptr; } env->initScope(scope); return env; } /* static */ VarEnvironmentObject* VarEnvironmentObject::createForFrame( JSContext* cx, Handle scope, AbstractFramePtr frame) { #ifdef DEBUG if (frame.isEvalFrame()) { MOZ_ASSERT(scope->is() && 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().callee() == frame.callee()); } #endif RootedObject envChain(cx, frame.environmentChain()); return create(cx, scope, envChain, gc::DefaultHeap); } /* static */ VarEnvironmentObject* VarEnvironmentObject::createHollowForDebug( JSContext* cx, Handle scope) { MOZ_ASSERT(scope->is() || scope->kind() == ScopeKind::StrictEval); MOZ_ASSERT(!scope->hasEnvironment()); Rooted shape(cx, EmptyEnvironmentShape(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 env( cx, createInternal(cx, shape, enclosingEnv, gc::DefaultHeap)); if (!env) { return nullptr; } RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT)); RootedId id(cx); for (Rooted bi(cx, BindingIter(scope)); bi; bi++) { id = NameToId(bi.name()->asPropertyName()); if (!SetProperty(cx, env, id, optimizedOut)) { return nullptr; } } env->initScope(scope); return env; } /* static */ VarEnvironmentObject* VarEnvironmentObject::createTemplateObject( JSContext* cx, Handle scope) { return create(cx, scope, nullptr, gc::TenuredHeap); } /* static */ VarEnvironmentObject* VarEnvironmentObject::createWithoutEnclosing( JSContext* cx, Handle scope) { return create(cx, scope, nullptr, gc::DefaultHeap); } const JSClass VarEnvironmentObject::class_ = { "Var", JSCLASS_HAS_RESERVED_SLOTS(VarEnvironmentObject::RESERVED_SLOTS)}; /*****************************************************************************/ const ObjectOps ModuleEnvironmentObject::objectOps_ = { ModuleEnvironmentObject::lookupProperty, // lookupProperty nullptr, // defineProperty ModuleEnvironmentObject::hasProperty, // hasProperty ModuleEnvironmentObject::getProperty, // getProperty ModuleEnvironmentObject::setProperty, // setProperty ModuleEnvironmentObject:: getOwnPropertyDescriptor, // getOwnPropertyDescriptor ModuleEnvironmentObject::deleteProperty, // deleteProperty nullptr, // getElements nullptr, // funToString }; const JSClassOps ModuleEnvironmentObject::classOps_ = { nullptr, // addProperty nullptr, // delProperty nullptr, // enumerate ModuleEnvironmentObject::newEnumerate, // newEnumerate nullptr, // resolve nullptr, // mayResolve nullptr, // finalize nullptr, // call nullptr, // construct nullptr, // trace }; const JSClass ModuleEnvironmentObject::class_ = { "ModuleEnvironmentObject", JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS), &ModuleEnvironmentObject::classOps_, JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, &ModuleEnvironmentObject::objectOps_}; /* static */ ModuleEnvironmentObject* ModuleEnvironmentObject::create( JSContext* cx, Handle module) { RootedScript script(cx, module->script()); Rooted shape( cx, script->bodyScope()->as().environmentShape()); MOZ_ASSERT(shape->getObjectClass() == &class_); Rooted env( cx, CreateEnvironmentObject(cx, shape, TenuredObject)); if (!env) { return nullptr; } env->initReservedSlot(MODULE_SLOT, ObjectValue(*module)); // Initialize this early so that we can manipulate the env object without // causing assertions. env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment()); // Initialize all lexical bindings and imports as uninitialized. Imports // get uninitialized because they have a special TDZ for cyclic imports. for (BindingIter bi(script); bi; bi++) { BindingLocation loc = bi.location(); if (loc.kind() == BindingLocation::Kind::Environment && BindingKindIsLexical(bi.kind())) { env->initSlot(loc.slot(), MagicValue(JS_UNINITIALIZED_LEXICAL)); } } // It is not be possible to add or remove bindings from a module environment // after this point as module code is always strict. #ifdef DEBUG for (ShapePropertyIter iter(env->shape()); !iter.done(); iter++) { MOZ_ASSERT(!iter->configurable()); } MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible)); MOZ_ASSERT(!env->inDictionaryMode()); #endif return env; } ModuleObject& ModuleEnvironmentObject::module() const { return getReservedSlot(MODULE_SLOT).toObject().as(); } IndirectBindingMap& ModuleEnvironmentObject::importBindings() const { return module().importBindings(); } bool ModuleEnvironmentObject::createImportBinding(JSContext* cx, Handle importName, Handle module, Handle localName) { RootedId importNameId(cx, AtomToId(importName)); RootedId localNameId(cx, AtomToId(localName)); Rooted env(cx, &module->initialEnvironment()); if (!importBindings().put(cx, importNameId, env, localNameId)) { return false; } return true; } bool ModuleEnvironmentObject::hasImportBinding(Handle name) { return importBindings().has(NameToId(name)); } bool ModuleEnvironmentObject::lookupImport( jsid name, ModuleEnvironmentObject** envOut, mozilla::Maybe* propOut) { return importBindings().lookup(name, envOut, propOut); } /* static */ bool ModuleEnvironmentObject::lookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, PropertyResult* propp) { const IndirectBindingMap& bindings = obj->as().importBindings(); mozilla::Maybe propInfo; ModuleEnvironmentObject* env; if (bindings.lookup(id, &env, &propInfo)) { objp.set(env); propp->setNativeProperty(*propInfo); return true; } Rooted target(cx, &obj->as()); if (!NativeLookupOwnProperty(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().importBindings().has(id)) { *foundp = true; return true; } Rooted self(cx, &obj->as()); 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().importBindings(); mozilla::Maybe prop; ModuleEnvironmentObject* env; if (bindings.lookup(id, &env, &prop)) { vp.set(env->getSlot(prop->slot())); return true; } Rooted self(cx, &obj->as()); return NativeGetProperty(cx, self, receiver, id, vp); } /* static */ bool ModuleEnvironmentObject::setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, JS::ObjectOpResult& result) { Rooted self(cx, &obj->as()); if (self->importBindings().has(id)) { return result.failReadOnly(); } return NativeSetProperty(cx, self, id, v, receiver, result); } /* static */ bool ModuleEnvironmentObject::getOwnPropertyDescriptor( JSContext* cx, HandleObject obj, HandleId id, MutableHandle> desc) { const IndirectBindingMap& bindings = obj->as().importBindings(); mozilla::Maybe prop; ModuleEnvironmentObject* env; if (bindings.lookup(id, &env, &prop)) { desc.set(mozilla::Some(PropertyDescriptor::Data( env->getSlot(prop->slot()), {JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable}))); return true; } Rooted self(cx, &obj->as()); return NativeGetOwnPropertyDescriptor(cx, self, id, desc); } /* static */ bool ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { return result.failCantDelete(); } /* static */ bool ModuleEnvironmentObject::newEnumerate(JSContext* cx, HandleObject obj, MutableHandleIdVector properties, bool enumerableOnly) { Rooted self(cx, &obj->as()); 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 (ShapePropertyIter iter(self->shape()); !iter.done(); iter++) { properties.infallibleAppend(iter->key()); } MOZ_ASSERT(properties.length() == count); return true; } /*****************************************************************************/ const JSClass WasmInstanceEnvironmentObject::class_ = { "WasmInstance", JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceEnvironmentObject::RESERVED_SLOTS)}; /* static */ WasmInstanceEnvironmentObject* WasmInstanceEnvironmentObject::createHollowForDebug( JSContext* cx, Handle scope) { Rooted shape( cx, EmptyEnvironmentShape(cx)); if (!shape) { return nullptr; } auto* env = CreateEnvironmentObject(cx, shape); if (!env) { return nullptr; } env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment()); env->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope)); return env; } /*****************************************************************************/ const JSClass WasmFunctionCallObject::class_ = { "WasmCall", JSCLASS_HAS_RESERVED_SLOTS(WasmFunctionCallObject::RESERVED_SLOTS)}; /* static */ WasmFunctionCallObject* WasmFunctionCallObject::createHollowForDebug( JSContext* cx, HandleObject enclosing, Handle scope) { Rooted shape(cx, EmptyEnvironmentShape(cx)); if (!shape) { return nullptr; } auto* callobj = CreateEnvironmentObject(cx, shape); if (!callobj) { return nullptr; } callobj->initEnclosingEnvironment(enclosing); callobj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope)); return callobj; } /*****************************************************************************/ WithEnvironmentObject* WithEnvironmentObject::create(JSContext* cx, HandleObject object, HandleObject enclosing, Handle scope) { Rooted shape(cx, EmptyEnvironmentShape(cx)); if (!shape) { return nullptr; } auto* obj = CreateEnvironmentObject(cx, shape); if (!obj) { return nullptr; } JSObject* thisObj = GetThisObject(object); obj->initEnclosingEnvironment(enclosing); obj->initReservedSlot(OBJECT_SLOT, ObjectValue(*object)); obj->initReservedSlot(THIS_SLOT, ObjectValue(*thisObj)); if (scope) { obj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope)); } else { obj->initReservedSlot(SCOPE_SLOT, NullValue()); } return obj; } WithEnvironmentObject* WithEnvironmentObject::createNonSyntactic( JSContext* cx, HandleObject object, HandleObject enclosing) { return create(cx, object, enclosing, nullptr); } static inline bool IsUnscopableDotName(JSContext* cx, HandleId id) { return id.isAtom(cx->names().dotThis) || id.isAtom(cx->names().dotNewTarget); } #ifdef DEBUG static bool IsInternalDotName(JSContext* cx, HandleId id) { return id.isAtom(cx->names().dotThis) || id.isAtom(cx->names().dotGenerator) || id.isAtom(cx->names().dotInitializers) || id.isAtom(cx->names().dotFieldKeys) || id.isAtom(cx->names().dotStaticInitializers) || id.isAtom(cx->names().dotStaticFieldKeys) || id.isAtom(cx->names().dotArgs) || id.isAtom(cx->names().dotNewTarget) || id.isAtom(cx->names().starNamespaceStar); } #endif /* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */ static bool CheckUnscopables(JSContext* cx, HandleObject obj, HandleId id, bool* scopable) { RootedId unscopablesId( cx, PropertyKey::Symbol(cx->wellKnownSymbols().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, PropertyResult* propp) { // SpiderMonkey-specific: consider the internal '.this' and '.newTarget' names // to be unscopable. if (IsUnscopableDotName(cx, id)) { objp.set(nullptr); propp->setNotFound(); return true; } // Other internal dot-names shouldn't even end up in with-environments. MOZ_ASSERT(!IsInternalDotName(cx, id)); RootedObject actual(cx, &obj->as().object()); if (!LookupProperty(cx, actual, id, objp, propp)) { return false; } if (propp->isFound()) { bool scopable; if (!CheckUnscopables(cx, actual, id, &scopable)) { return false; } if (!scopable) { objp.set(nullptr); propp->setNotFound(); } } return true; } static bool with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle desc, ObjectOpResult& result) { MOZ_ASSERT(!IsInternalDotName(cx, id)); RootedObject actual(cx, &obj->as().object()); return DefineProperty(cx, actual, id, desc, result); } static bool with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) { MOZ_ASSERT(!IsInternalDotName(cx, id)); RootedObject actual(cx, &obj->as().object()); // ES 8.1.1.2.1 step 3-5. if (!HasProperty(cx, actual, id, foundp)) { return false; } if (!*foundp) { return true; } // Steps 7-10. (Step 6 is a no-op.) return CheckUnscopables(cx, actual, id, foundp); } static bool with_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp) { MOZ_ASSERT(!IsInternalDotName(cx, id)); RootedObject actual(cx, &obj->as().object()); RootedValue actualReceiver(cx, receiver); if (receiver.isObject() && &receiver.toObject() == obj) { actualReceiver.setObject(*actual); } return GetProperty(cx, actual, actualReceiver, id, vp); } static bool with_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) { MOZ_ASSERT(!IsInternalDotName(cx, id)); RootedObject actual(cx, &obj->as().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> desc) { MOZ_ASSERT(!IsInternalDotName(cx, id)); RootedObject actual(cx, &obj->as().object()); return GetOwnPropertyDescriptor(cx, actual, id, desc); } static bool with_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { MOZ_ASSERT(!IsInternalDotName(cx, id)); RootedObject actual(cx, &obj->as().object()); return DeleteProperty(cx, actual, id, result); } static const ObjectOps WithEnvironmentObjectOps = { with_LookupProperty, // lookupProperty with_DefineProperty, // defineProperty with_HasProperty, // hasProperty with_GetProperty, // getProperty with_SetProperty, // setProperty with_GetOwnPropertyDescriptor, // getOwnPropertyDescriptor with_DeleteProperty, // deleteProperty nullptr, // getElements nullptr, // funToString }; const JSClass WithEnvironmentObject::class_ = { "With", JSCLASS_HAS_RESERVED_SLOTS(WithEnvironmentObject::RESERVED_SLOTS), JS_NULL_CLASS_OPS, JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, &WithEnvironmentObjectOps}; /* static */ NonSyntacticVariablesObject* NonSyntacticVariablesObject::create( JSContext* cx) { Rooted shape( cx, EmptyEnvironmentShape(cx)); if (!shape) { return nullptr; } Rooted obj( cx, CreateEnvironmentObject(cx, shape, TenuredObject)); if (!obj) { return nullptr; } MOZ_ASSERT(obj->isUnqualifiedVarObj()); if (!JSObject::setQualifiedVarObj(cx, obj)) { return nullptr; } obj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment()); return obj; } const JSClass NonSyntacticVariablesObject::class_ = { "NonSyntacticVariablesObject", JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS)}; bool js::CreateNonSyntacticEnvironmentChain(JSContext* cx, HandleObjectVector envChain, MutableHandleObject env) { // Callers are responsible for segregating the NonSyntactic case from simple // compilation cases. MOZ_RELEASE_ASSERT(!envChain.empty()); RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment()); if (!CreateObjectsForEnvironmentChain(cx, envChain, globalLexical, env)) { return false; } // The XPConnect subscript loader, which may pass in its own // environments to load scripts in, expects the environment chain to // be the holder of "var" declarations. In SpiderMonkey, such objects // are called "qualified varobjs", the "qualified" part meaning the // declaration was qualified by "var". There is only sadness. // // See JSObject::isQualifiedVarObj. if (!JSObject::setQualifiedVarObj(cx, env)) { return false; } // Also get a non-syntactic lexical environment to capture 'let' and // 'const' bindings. To persist lexical bindings, we have a 1-1 // mapping with the final unwrapped environment object (the // environment that stores the 'var' bindings) and the lexical // environment. // // TODOshu: disallow the subscript loader from using non-distinguished // objects as dynamic scopes. env.set( ObjectRealm::get(env).getOrCreateNonSyntacticLexicalEnvironment(cx, env)); return !!env; } /*****************************************************************************/ const JSClass LexicalEnvironmentObject::class_ = { "LexicalEnvironment", JSCLASS_HAS_RESERVED_SLOTS(LexicalEnvironmentObject::RESERVED_SLOTS), JS_NULL_CLASS_OPS, JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, JS_NULL_OBJECT_OPS}; /* static */ LexicalEnvironmentObject* LexicalEnvironmentObject::create( JSContext* cx, Handle shape, HandleObject enclosing, gc::InitialHeap heap) { MOZ_ASSERT(shape->getObjectClass() == &LexicalEnvironmentObject::class_); // The JITs assume the result is nursery allocated unless we collected the // nursery, so don't change |heap| here. auto* env = CreateEnvironmentObject(cx, shape, heap); if (!env) { return nullptr; } MOZ_ASSERT(!env->inDictionaryMode()); if (enclosing) { env->initEnclosingEnvironment(enclosing); } return env; } bool LexicalEnvironmentObject::isExtensible() const { return NativeObject::isExtensible(); } /* static */ BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::create( JSContext* cx, Handle scope, HandleObject enclosing, gc::InitialHeap heap) { cx->check(enclosing); MOZ_ASSERT(scope->hasEnvironment()); Rooted shape(cx, scope->environmentShape()); auto* env = static_cast( LexicalEnvironmentObject::create(cx, shape, enclosing, heap)); if (!env) { return nullptr; } // All lexical bindings start off uninitialized for TDZ. uint32_t lastSlot = env->getLastProperty().slot(); for (uint32_t slot = JSSLOT_FREE(&class_); slot <= lastSlot; slot++) { env->initSlot(slot, MagicValue(JS_UNINITIALIZED_LEXICAL)); } env->initScope(scope); return env; } /* static */ BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::createForFrame( JSContext* cx, Handle scope, AbstractFramePtr frame) { RootedObject enclosing(cx, frame.environmentChain()); return create(cx, scope, enclosing, gc::DefaultHeap); } /* static */ BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::createHollowForDebug( JSContext* cx, Handle scope) { MOZ_ASSERT(!scope->hasEnvironment()); Rooted 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 env( cx, LexicalEnvironmentObject::create(cx, shape, enclosingEnv, gc::TenuredHeap)); if (!env) { return nullptr; } RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT)); RootedId id(cx); for (Rooted bi(cx, BindingIter(scope)); bi; bi++) { id = NameToId(bi.name()->asPropertyName()); if (!SetProperty(cx, env, id, optimizedOut)) { return nullptr; } } if (!JSObject::setFlag(cx, env, ObjectFlag::NotExtensible)) { return nullptr; } env->as().initScope(scope); return &env->as(); } /* static */ BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::createTemplateObject( JSContext* cx, Handle scope) { return create(cx, scope, nullptr, gc::TenuredHeap); } /* static */ BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::createWithoutEnclosing( JSContext* cx, Handle scope) { return create(cx, scope, nullptr, gc::DefaultHeap); } /* static */ BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::clone( JSContext* cx, Handle env) { Rooted scope(cx, &env->scope()); RootedObject enclosing(cx, &env->enclosingEnvironment()); Rooted copy( cx, create(cx, scope, enclosing, gc::DefaultHeap)); if (!copy) { return nullptr; } MOZ_ASSERT(env->shape() == copy->shape()); for (uint32_t i = JSSLOT_FREE(&class_); i < copy->slotSpan(); i++) { copy->setSlot(i, env->getSlot(i)); } return copy; } /* static */ BlockLexicalEnvironmentObject* BlockLexicalEnvironmentObject::recreate( JSContext* cx, Handle env) { Rooted scope(cx, &env->scope()); RootedObject enclosing(cx, &env->enclosingEnvironment()); return create(cx, scope, enclosing, gc::DefaultHeap); } /* static */ NamedLambdaObject* NamedLambdaObject::create(JSContext* cx, HandleFunction callee, HandleObject enclosing, gc::InitialHeap heap) { MOZ_ASSERT(callee->isNamedLambda()); Rooted scope(cx, callee->nonLazyScript()->maybeNamedLambdaScope()); MOZ_ASSERT(scope && scope->environmentShape()); #ifdef DEBUG { // Named lambda objects have one (non-writable) property. SharedShapePropertyIter iter(scope->environmentShape()); MOZ_ASSERT(iter->slot() == lambdaSlot()); MOZ_ASSERT(!iter->writable()); iter++; MOZ_ASSERT(iter.done()); // There should be exactly one binding in the named lambda scope. BindingIter bi(scope); bi++; MOZ_ASSERT(bi.done()); } #endif BlockLexicalEnvironmentObject* obj = BlockLexicalEnvironmentObject::create( cx, scope.as(), enclosing, heap); if (!obj) { return nullptr; } obj->initFixedSlot(lambdaSlot(), ObjectValue(*callee)); return static_cast(obj); } /* static */ NamedLambdaObject* NamedLambdaObject::createTemplateObject( JSContext* cx, HandleFunction callee) { return create(cx, callee, nullptr, gc::TenuredHeap); } /* static */ NamedLambdaObject* NamedLambdaObject::createWithoutEnclosing( JSContext* cx, HandleFunction callee) { return create(cx, callee, nullptr, gc::DefaultHeap); } /* static */ NamedLambdaObject* NamedLambdaObject::create(JSContext* cx, AbstractFramePtr frame) { RootedFunction fun(cx, frame.callee()); RootedObject enclosing(cx, frame.environmentChain()); return create(cx, fun, enclosing, gc::DefaultHeap); } /* static */ size_t NamedLambdaObject::lambdaSlot() { // Named lambda environments have exactly one name. return JSSLOT_FREE(&LexicalEnvironmentObject::class_); } /* static */ ClassBodyLexicalEnvironmentObject* ClassBodyLexicalEnvironmentObject::create( JSContext* cx, Handle scope, HandleObject enclosing, gc::InitialHeap heap) { cx->check(enclosing); MOZ_ASSERT(scope->hasEnvironment()); Rooted shape(cx, scope->environmentShape()); auto* env = static_cast( LexicalEnvironmentObject::create(cx, shape, enclosing, heap)); if (!env) { return nullptr; } env->initScope(scope); return env; } /* static */ ClassBodyLexicalEnvironmentObject* ClassBodyLexicalEnvironmentObject::createForFrame(JSContext* cx, Handle scope, AbstractFramePtr frame) { RootedObject enclosing(cx, frame.environmentChain()); return create(cx, scope, enclosing, gc::DefaultHeap); } /* static */ ClassBodyLexicalEnvironmentObject* ClassBodyLexicalEnvironmentObject::createTemplateObject( JSContext* cx, Handle scope) { return create(cx, scope, nullptr, gc::TenuredHeap); } /* static */ ClassBodyLexicalEnvironmentObject* ClassBodyLexicalEnvironmentObject::createWithoutEnclosing( JSContext* cx, Handle scope) { return create(cx, scope, nullptr, gc::DefaultHeap); } JSObject* ExtensibleLexicalEnvironmentObject::thisObject() const { JSObject* obj = &getReservedSlot(THIS_VALUE_OR_SCOPE_SLOT).toObject(); // Windows must never be exposed to script. setWindowProxyThisValue should // have set this to the WindowProxy. MOZ_ASSERT(!IsWindow(obj)); // WarpBuilder relies on the return value not being nursery-allocated for the // global lexical environment. MOZ_ASSERT_IF(isGlobal(), obj->isTenured()); return obj; } /* static */ ExtensibleLexicalEnvironmentObject* ExtensibleLexicalEnvironmentObject::forVarEnvironment(JSObject* obj) { ExtensibleLexicalEnvironmentObject* lexical = nullptr; if (obj->is()) { lexical = &obj->as().lexicalEnvironment(); } else { lexical = ObjectRealm::get(obj).getNonSyntacticLexicalEnvironment(obj); } MOZ_ASSERT(lexical); return lexical; } /* static */ GlobalLexicalEnvironmentObject* GlobalLexicalEnvironmentObject::create( JSContext* cx, Handle global) { MOZ_ASSERT(global); Rooted shape( cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx)); if (!shape) { return nullptr; } auto* env = static_cast( LexicalEnvironmentObject::create(cx, shape, global, gc::TenuredHeap)); if (!env) { return nullptr; } env->initThisObject(global); return env; } void GlobalLexicalEnvironmentObject::setWindowProxyThisObject(JSObject* obj) { MOZ_ASSERT(IsWindowProxy(obj)); setReservedSlot(THIS_VALUE_OR_SCOPE_SLOT, ObjectValue(*obj)); } /* static */ NonSyntacticLexicalEnvironmentObject* NonSyntacticLexicalEnvironmentObject::create(JSContext* cx, HandleObject enclosing, HandleObject thisv) { MOZ_ASSERT(enclosing); MOZ_ASSERT(!IsSyntacticEnvironment(enclosing)); Rooted shape( cx, LexicalScope::getEmptyExtensibleEnvironmentShape(cx)); if (!shape) { return nullptr; } auto* env = static_cast( LexicalEnvironmentObject::create(cx, shape, enclosing, gc::TenuredHeap)); if (!env) { return nullptr; } env->initThisObject(thisv); return env; } /* static */ RuntimeLexicalErrorObject* RuntimeLexicalErrorObject::create( JSContext* cx, HandleObject enclosing, unsigned errorNumber) { Rooted shape( cx, EmptyEnvironmentShape(cx, &class_, JSSLOT_FREE(&class_), ObjectFlags())); if (!shape) { return nullptr; } auto* obj = CreateEnvironmentObject(cx, shape); if (!obj) { return nullptr; } obj->initEnclosingEnvironment(enclosing); obj->initReservedSlot(ERROR_SLOT, Int32Value(int32_t(errorNumber))); return obj; } static void ReportRuntimeLexicalErrorId(JSContext* cx, unsigned errorNumber, HandleId id) { if (id.isAtom()) { Rooted name(cx, id.toAtom()->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, PropertyResult* propp) { ReportRuntimeLexicalErrorId( cx, obj->as().errorNumber(), id); return false; } static bool lexicalError_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) { ReportRuntimeLexicalErrorId( cx, obj->as().errorNumber(), id); return false; } static bool lexicalError_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp) { ReportRuntimeLexicalErrorId( cx, obj->as().errorNumber(), id); return false; } static bool lexicalError_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) { ReportRuntimeLexicalErrorId( cx, obj->as().errorNumber(), id); return false; } static bool lexicalError_GetOwnPropertyDescriptor( JSContext* cx, HandleObject obj, HandleId id, MutableHandle> desc) { ReportRuntimeLexicalErrorId( cx, obj->as().errorNumber(), id); return false; } static bool lexicalError_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { ReportRuntimeLexicalErrorId( cx, obj->as().errorNumber(), id); return false; } static const ObjectOps RuntimeLexicalErrorObjectObjectOps = { lexicalError_LookupProperty, // lookupProperty nullptr, // defineProperty lexicalError_HasProperty, // hasProperty lexicalError_GetProperty, // getProperty lexicalError_SetProperty, // setProperty lexicalError_GetOwnPropertyDescriptor, // getOwnPropertyDescriptor lexicalError_DeleteProperty, // deleteProperty nullptr, // getElements nullptr, // funToString }; const JSClass RuntimeLexicalErrorObject::class_ = { "RuntimeLexicalError", JSCLASS_HAS_RESERVED_SLOTS(RuntimeLexicalErrorObject::RESERVED_SLOTS), JS_NULL_CLASS_OPS, JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, &RuntimeLexicalErrorObjectObjectOps}; /*****************************************************************************/ EnvironmentIter::EnvironmentIter(JSContext* cx, const EnvironmentIter& ei) : si_(cx, ei.si_.get()), env_(cx, ei.env_), frame_(ei.frame_) {} EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope) : si_(cx, ScopeIter(scope)), env_(cx, env), frame_(NullFramePtr()) { settle(); } EnvironmentIter::EnvironmentIter(JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc) : si_(cx, frame.script()->innermostScope(pc)), env_(cx, frame.environmentChain()), frame_(frame) { cx->check(frame); settle(); } EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope, AbstractFramePtr frame) : si_(cx, ScopeIter(scope)), env_(cx, env), frame_(frame) { cx->check(frame); settle(); } void EnvironmentIter::incrementScopeIter() { if (si_.scope()->is()) { // 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()) { si_++; } } else { si_++; } } void EnvironmentIter::settle() { // Check for trying to iterate a function or eval frame before the prologue // has created the CallObject, in which case we have to skip. if (frame_ && frame_.hasScript() && frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment()) { // Skip until we're at the enclosing scope of the script. while (si_.scope() != frame_.script()->enclosingScope()) { if (env_->is() && &env_->as().scope() == si_.scope()) { MOZ_ASSERT(si_.kind() == ScopeKind::NamedLambda || si_.kind() == ScopeKind::StrictNamedLambda); env_ = &env_->as().enclosingEnvironment(); } incrementScopeIter(); } } // Check if we have left the extent of the initial frame after we've // settled on a static scope. if (frame_ && (!si_ || (frame_.hasScript() && si_.scope() == frame_.script()->enclosingScope()) || (frame_.isWasmDebugFrame() && !si_.scope()->is()))) { frame_ = NullFramePtr(); } #ifdef DEBUG if (si_) { if (hasSyntacticEnvironment()) { Scope* scope = si_.scope(); if (scope->is()) { MOZ_ASSERT(scope == &env_->as().scope()); } else if (scope->is()) { MOZ_ASSERT(scope->as().script() == env_->as() .callee() .maybeCanonicalFunction() ->baseScript()); } else if (scope->is()) { MOZ_ASSERT(scope == &env_->as().scope()); } else if (scope->is()) { MOZ_ASSERT(scope == &env_->as().scope()); } else if (scope->is()) { MOZ_ASSERT(scope == &env_->as().scope()); } else if (scope->is()) { MOZ_ASSERT(env_->is() || IsGlobalLexicalEnvironment(env_)); } } else if (hasNonSyntacticEnvironmentObject()) { if (env_->is()) { // The global lexical environment still encloses non-syntactic // environment objects. MOZ_ASSERT(env_->is() || env_->is()); } else if (env_->is()) { MOZ_ASSERT(!env_->as().isSyntactic()); } else { MOZ_ASSERT(env_->is()); } } } #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()); 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 NonSyntacticLexicalEnvironmentObject. if (si_.kind() == ScopeKind::NonSyntactic) { MOZ_ASSERT_IF(env_->is(), !env_->as().isSyntactic()); return env_->is(); } 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::traceWeak(JSTracer* trc) { return TraceWeakEdge(trc, &scope_, "LiveEnvironmentVal::scope_"); } // Live EnvironmentIter values may be added to DebugEnvironments::liveEnvs, as // LiveEnvironmentVal instances. They need to have write barriers when they are // added to the hash table, but no barriers when rehashing inside GC. It's a // nasty hack, but the important thing is that LiveEnvironmentVal and // MissingEnvironmentKey need to alias each other. void LiveEnvironmentVal::staticAsserts() { static_assert( sizeof(LiveEnvironmentVal) == sizeof(MissingEnvironmentKey), "LiveEnvironmentVal must be same size of MissingEnvironmentKey"); static_assert( offsetof(LiveEnvironmentVal, scope_) == offsetof(MissingEnvironmentKey, scope_), "LiveEnvironmentVal.scope_ must alias MissingEnvironmentKey.scope_"); } /*****************************************************************************/ namespace { /* * DebugEnvironmentProxy is the handler for DebugEnvironmentProxy proxy * objects. Having a custom handler (rather than trying to reuse js::Wrapper) * gives us several important abilities: * - We want to pass the EnvironmentObject as the receiver to forwarded scope * property ops on aliased variables so that Call/Block/With ops do not all * require a 'normalization' step. * - The debug scope proxy can directly manipulate the stack frame to allow * the debugger to read/write args/locals that were otherwise unaliased. * - The debug scope proxy can store unaliased variables after the stack frame * is popped so that they may still be read/written by the debugger. * - The engine has made certain assumptions about the possible reads/writes * in a scope. DebugEnvironmentProxy allows us to prevent the debugger from * breaking those assumptions. * - The engine makes optimizations that are observable to the debugger. The * proxy can either hide these optimizations or make the situation more * clear to the debugger. An example is 'arguments'. */ class DebugEnvironmentProxyHandler : public BaseProxyHandler { enum Action { SET, GET }; enum AccessResult { ACCESS_UNALIASED, ACCESS_GENERIC, ACCESS_LOST }; /* * This function handles access to unaliased locals/formals. Since they * are unaliased, the values of these variables are not stored in the * slots of the normal CallObject and BlockLexicalEnvironmentObject * 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 debugEnv, Handle env, HandleId id, Action action, MutableHandleValue vp, AccessResult* accessResult) const { MOZ_ASSERT(&debugEnv->environment() == env); MOZ_ASSERT_IF(action == SET, !debugEnv->isOptimizedOut()); *accessResult = ACCESS_GENERIC; LiveEnvironmentVal* maybeLiveEnv = DebugEnvironments::hasLiveEnvironment(*env); // Handle unaliased formals, vars, lets, and consts at function or module // scope. if (env->is() || env->is()) { RootedScript script(cx); if (env->is()) { CallObject& callobj = env->as(); RootedFunction fun(cx, &callobj.callee()); script = JSFunction::getOrCreateScript(cx, fun); } else { script = env->as().module().maybeScript(); if (!script) { return true; } } BindingIter bi(script); while (bi && NameToId(bi.name()->asPropertyName()) != id) { bi++; } if (!bi) { return true; } if (action == SET && bi.kind() == BindingKind::Const) { ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, id); return false; } if (bi.location().kind() == BindingLocation::Kind::Import) { return true; } if (!bi.hasArgumentSlot()) { if (bi.closedOver()) { return true; } uint32_t i = bi.location().slot(); if (maybeLiveEnv) { AbstractFramePtr frame = maybeLiveEnv->frame(); if (action == GET) { vp.set(frame.unaliasedLocal(i)); } else { frame.unaliasedLocal(i) = vp; } } else if (AbstractGeneratorObject* genObj = GetGeneratorObjectForEnvironment(cx, env); genObj && genObj->isSuspended() && genObj->hasStackStorage()) { if (action == GET) { vp.set(genObj->getUnaliasedLocal(i)); } else { genObj->setUnaliasedLocal(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; } } } // 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() || env->is()) { // Currently consider all global and non-syntactic top-level lexical // bindings to be aliased. if (env->is() && env->as().isExtensible()) { MOZ_ASSERT(IsGlobalLexicalEnvironment(env) || !IsSyntacticEnvironment(env)); return true; } // Currently all vars inside non-strict eval var environments are aliased. if (env->is() && env->as().isForNonStrictEval()) { return true; } Rooted scope(cx, getEnvironmentScope(*env)); uint32_t firstFrameSlot = scope->firstFrameSlot(); BindingIter bi(scope); while (bi && NameToId(bi.name()->asPropertyName()) != id) { bi++; } if (!bi) { return true; } if (action == SET && bi.kind() == BindingKind::Const) { ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, id); return false; } 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 (AbstractGeneratorObject* genObj = GetGeneratorObjectForEnvironment(cx, debugEnv); genObj && genObj->isSuspended() && genObj->hasStackStorage()) { if (action == GET) { vp.set(genObj->getUnaliasedLocal(loc.slot())); } else { genObj->setUnaliasedLocal(loc.slot(), 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; } if (env->is()) { if (maybeLiveEnv) { Rooted scope(cx, getEnvironmentScope(*env)); uint32_t index = 0; for (BindingIter bi(scope); bi; bi++) { if (id.isAtom(bi.name())) { break; } MOZ_ASSERT(!bi.isLast()); index++; } AbstractFramePtr frame = maybeLiveEnv->frame(); MOZ_ASSERT(frame.isWasmDebugFrame()); wasm::DebugFrame* wasmFrame = frame.asWasmDebugFrame(); if (action == GET) { if (!wasmFrame->getLocal(index, vp)) { ReportOutOfMemory(cx); return false; } *accessResult = ACCESS_UNALIASED; } else { // if (action == SET) // TODO } } else { *accessResult = ACCESS_LOST; } return true; } if (env->is()) { Rooted scope(cx, getEnvironmentScope(*env)); MOZ_ASSERT(scope->is()); uint32_t index = 0; for (BindingIter bi(scope); bi; bi++) { if (id.isAtom(bi.name())) { break; } MOZ_ASSERT(!bi.isLast()); index++; } Rooted instanceScope(cx, &scope->as()); wasm::Instance& instance = instanceScope->instance()->instance(); if (action == GET) { if (instanceScope->memoriesStart() <= index && index < instanceScope->globalsStart()) { MOZ_ASSERT(instanceScope->memoriesStart() + 1 == instanceScope->globalsStart()); vp.set(ObjectValue(*instance.memory())); } if (instanceScope->globalsStart() <= index) { MOZ_ASSERT(index < instanceScope->namesCount()); if (!instance.debug().getGlobal( instance, index - instanceScope->globalsStart(), vp)) { ReportOutOfMemory(cx); return false; } } *accessResult = ACCESS_UNALIASED; } else { // if (action == SET) // TODO } return true; } /* The rest of the internal scopes do not have unaliased vars. */ MOZ_ASSERT(!IsSyntacticEnvironment(env) || env->is()); 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(); } static bool isNonExtensibleLexicalEnvironment(const JSObject& env) { return env.is(); } static Scope* getEnvironmentScope(const JSObject& env) { if (isFunctionEnvironment(env)) { return env.as().callee().nonLazyScript()->bodyScope(); } if (env.is()) { JSScript* script = env.as().module().maybeScript(); return script ? script->bodyScope() : nullptr; } if (isNonExtensibleLexicalEnvironment(env)) { return &env.as().scope(); } if (env.is()) { return &env.as().scope(); } if (env.is()) { return &env.as().scope(); } if (env.is()) { return &env.as().scope(); } return nullptr; } friend Scope* js::GetEnvironmentScope(const JSObject& env); /* * 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().callee().baseScript()->needsArgsObj(); } /* * 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() .callee() .baseScript() ->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 was not otherwise needed. */ static bool isMissingArguments(JSContext* cx, jsid id, EnvironmentObject& env) { return isArguments(cx, id) && isMissingArgumentsBinding(env); } static bool isMissingThis(JSContext* cx, jsid id, EnvironmentObject& env) { return isThis(cx, id) && isMissingThisBinding(env); } /* * 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; } static void reportOptimizedOut(JSContext* cx, HandleId id) { if (isThis(cx, id)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT, "this"); return; } if (UniqueChars printable = IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT, printable.get()); } } public: static const char family; static const DebugEnvironmentProxyHandler singleton; constexpr DebugEnvironmentProxyHandler() : BaseProxyHandler(&family) {} static bool isFunctionEnvironmentWithThis(const JSObject& env) { // All functions except arrows should have their own this binding. return isFunctionEnvironment(env) && !env.as().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 getMissingArgumentsPropertyDescriptor( JSContext* cx, Handle debugEnv, EnvironmentObject& env, MutableHandle> desc) const { RootedArgumentsObject argsObj(cx); if (!createMissingArguments(cx, env, &argsObj)) { return false; } if (!argsObj) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_ON_STACK, "Debugger scope"); return false; } desc.set(mozilla::Some(PropertyDescriptor::Data( ObjectValue(*argsObj), {JS::PropertyAttribute::Enumerable}))); return true; } bool getMissingThisPropertyDescriptor( JSContext* cx, Handle debugEnv, EnvironmentObject& env, MutableHandle> 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_ON_STACK, "Debugger scope"); return false; } desc.set(mozilla::Some( PropertyDescriptor::Data(thisv, {JS::PropertyAttribute::Enumerable}))); return true; } bool getOwnPropertyDescriptor( JSContext* cx, HandleObject proxy, HandleId id, MutableHandle> desc) const override { Rooted debugEnv( cx, &proxy->as()); Rooted 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: { desc.set(mozilla::Some( PropertyDescriptor::Data(v, {JS::PropertyAttribute::Enumerable}))); return true; } case ACCESS_GENERIC: return GetOwnPropertyDescriptor(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_ON_STACK, "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_ON_STACK, "Debugger env"); return false; } vp.set(thisv); return true; } bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, MutableHandleValue vp) const override { Rooted debugEnv( cx, &proxy->as()); Rooted env( cx, &proxy->as().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 (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_MISSING_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 debugEnv, HandleId id, MutableHandleValue vp) const { Rooted 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 (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 debugEnv( cx, &proxy->as()); Rooted env( cx, &proxy->as().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 desc, ObjectOpResult& result) const override { Rooted env( cx, &proxy->as().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, MutableHandleIdVector props) const override { Rooted env( cx, &proxy->as().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(); if (isWith) { target = &env->as().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 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().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 */ Scope* js::GetEnvironmentScope(const JSObject& env) { return DebugEnvironmentProxyHandler::getEnvironmentScope(env); } template <> bool JSObject::is() 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.realm() == cx->realm()); MOZ_ASSERT(!enclosing->is()); RootedValue priv(cx, ObjectValue(env)); JSObject* obj = NewProxyObject(cx, &DebugEnvironmentProxyHandler::singleton, priv, nullptr /* proto */); if (!obj) { return nullptr; } DebugEnvironmentProxy* debugEnv = &obj->as(); debugEnv->setReservedSlot(ENCLOSING_SLOT, ObjectValue(*enclosing)); debugEnv->setReservedSlot(SNAPSHOT_SLOT, NullValue()); return debugEnv; } EnvironmentObject& DebugEnvironmentProxy::environment() const { return target()->as(); } JSObject& DebugEnvironmentProxy::enclosingEnvironment() const { return reservedSlot(ENCLOSING_SLOT).toObject(); } ArrayObject* DebugEnvironmentProxy::maybeSnapshot() const { JSObject* obj = reservedSlot(SNAPSHOT_SLOT).toObjectOrNull(); return obj ? &obj->as() : nullptr; } void DebugEnvironmentProxy::initSnapshot(ArrayObject& o) { #ifdef DEBUG if (maybeSnapshot()) { auto* callObj = CallObject::find(&environment()); if (callObj) { MOZ_ASSERT(callObj->callee().isGeneratorOrAsync()); } else { auto* moduleEnv = ModuleEnvironmentObject::find(&environment()); MOZ_ASSERT(moduleEnv); MOZ_ASSERT(moduleEnv->module().hasTopLevelAwait()); } } #endif setReservedSlot(SNAPSHOT_SLOT, ObjectValue(o)); } bool DebugEnvironmentProxy::isForDeclarative() const { EnvironmentObject& e = environment(); return e.is() || e.is() || e.is() || e.is() || e.is() || e.is(); } /* static */ bool DebugEnvironmentProxy::getMaybeSentinelValue( JSContext* cx, Handle env, HandleId id, MutableHandleValue vp) { return DebugEnvironmentProxyHandler::singleton.getMaybeSentinelValue(cx, env, id, vp); } bool DebugEnvironmentProxy::isFunctionEnvironmentWithThis() { return DebugEnvironmentProxyHandler::isFunctionEnvironmentWithThis( environment()); } bool DebugEnvironmentProxy::isOptimizedOut() const { EnvironmentObject& e = environment(); if (DebugEnvironments::hasLiveEnvironment(e)) { return false; } if (e.is()) { return e.is() && !e.as().scope().hasEnvironment(); } if (e.is()) { return !e.as().callee().needsCallObject() && !maybeSnapshot(); } return false; } /*****************************************************************************/ DebugEnvironments::DebugEnvironments(JSContext* cx, Zone* zone) : zone_(zone), proxiedEnvs(cx), missingEnvs(cx->zone()), liveEnvs(cx->zone()) {} DebugEnvironments::~DebugEnvironments() { MOZ_ASSERT(missingEnvs.empty()); } void DebugEnvironments::trace(JSTracer* trc) { proxiedEnvs.trace(trc); } void DebugEnvironments::traceWeak(JSTracer* trc) { /* * missingEnvs points to debug envs weakly so that debug envs can be * released more eagerly. */ for (MissingEnvironmentMap::Enum e(missingEnvs); !e.empty(); e.popFront()) { auto result = TraceWeakEdge(trc, &e.front().value(), "MissingEnvironmentMap value"); if (result.isDead()) { /* * 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(&result.initialTarget()->environment()); e.removeFront(); } else { MissingEnvironmentKey key = e.front().key(); Scope* scope = key.scope(); MOZ_ALWAYS_TRUE(TraceManuallyBarrieredWeakEdge( trc, &scope, "MissingEnvironmentKey scope")); if (scope != key.scope()) { key.updateScope(scope); e.rekeyFront(key); } } } /* * Scopes can be finalized when a debugger-synthesized EnvironmentObject is * no longer reachable via its DebugEnvironmentProxy. */ liveEnvs.traceWeak(trc); } void DebugEnvironments::finish() { proxiedEnvs.clear(); } #ifdef JSGC_HASH_TABLE_CHECKS void DebugEnvironments::checkHashTablesAfterMovingGC() { /* * 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()); // Use unbarrieredGet() to prevent triggering read barrier while collecting. CheckGCThingAfterMovingGC(r.front().value().unbarrieredGet()); } 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->realm()->isDebuggee(); } DebugEnvironments* DebugEnvironments::ensureRealmData(JSContext* cx) { Realm* realm = cx->realm(); if (auto* debugEnvs = realm->debugEnvs()) { return debugEnvs; } auto debugEnvs = cx->make_unique(cx, cx->zone()); if (!debugEnvs) { return nullptr; } realm->debugEnvsRef() = std::move(debugEnvs); return realm->debugEnvs(); } /* static */ DebugEnvironmentProxy* DebugEnvironments::hasDebugEnvironment( JSContext* cx, EnvironmentObject& env) { DebugEnvironments* envs = env.realm()->debugEnvs(); if (!envs) { return nullptr; } if (JSObject* obj = envs->proxiedEnvs.lookup(&env)) { MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx)); return &obj->as(); } return nullptr; } /* static */ bool DebugEnvironments::addDebugEnvironment( JSContext* cx, Handle env, Handle debugEnv) { MOZ_ASSERT(cx->realm() == env->realm()); MOZ_ASSERT(cx->realm() == debugEnv->nonCCWRealm()); if (!CanUseDebugEnvironmentMaps(cx)) { return true; } DebugEnvironments* envs = ensureRealmData(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->realm()->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 debugEnv) { MOZ_ASSERT(!ei.hasSyntacticEnvironment()); MOZ_ASSERT(cx->realm() == debugEnv->nonCCWRealm()); if (!CanUseDebugEnvironmentMaps(cx)) { return true; } DebugEnvironments* envs = ensureRealmData(cx); if (!envs) { return false; } MissingEnvironmentKey key(ei); MOZ_ASSERT(!envs->missingEnvs.has(key)); if (!envs->missingEnvs.put(key, WeakHeapPtr(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 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. */ // Because this can be called during exception unwinding, save the exception // state and restore it when we're done. JS::AutoSaveExceptionState ases(cx); JSScript* script = frame.script(); // Act like no snapshot was taken if we run OOM while taking the snapshot. Rooted> vec(cx, GCVector(cx)); if (debugEnv->environment().is()) { FunctionScope* scope = &script->bodyScope()->as(); 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->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()) { LexicalScope* scope = &debugEnv->environment().as().scope(); frameSlotStart = scope->firstFrameSlot(); frameSlotEnd = scope->nextFrameSlot(); } else if (debugEnv->environment() .is()) { ClassBodyScope* scope = &debugEnv->environment() .as() .scope(); frameSlotStart = scope->firstFrameSlot(); frameSlotEnd = scope->nextFrameSlot(); } else if (debugEnv->environment().is()) { VarEnvironmentObject* env = &debugEnv->environment().as(); if (frame.isFunctionFrame()) { VarScope* scope = &env->scope().as(); frameSlotStart = scope->firstFrameSlot(); frameSlotEnd = scope->nextFrameSlot(); } else { EvalScope* scope = &env->scope().as(); MOZ_ASSERT(scope == script->bodyScope()); frameSlotStart = 0; frameSlotEnd = scope->nextFrameSlot(); } } else { MOZ_ASSERT(&debugEnv->environment().as() == script->module()->environment()); ModuleScope* scope = &script->bodyScope()->as(); frameSlotStart = 0; frameSlotEnd = scope->nextFrameSlot(); } uint32_t frameSlotCount = frameSlotEnd - frameSlotStart; MOZ_ASSERT(frameSlotCount <= 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. */ Rooted snapshot( cx, NewDenseCopiedArray(cx, vec.length(), vec.begin())); if (!snapshot) { MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed()); cx->clearPendingException(); return; } debugEnv->initSnapshot(*snapshot); } /* static */ void DebugEnvironments::onPopCall(JSContext* cx, AbstractFramePtr frame) { cx->check(frame); DebugEnvironments* envs = cx->realm()->debugEnvs(); if (!envs) { return; } Rooted debugEnv(cx, nullptr); FunctionScope* funScope = &frame.script()->bodyScope()->as(); 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()) { return; } CallObject& callobj = frame.environmentChain()->as(); envs->liveEnvs.remove(&callobj); if (JSObject* obj = envs->proxiedEnvs.lookup(&callobj)) { debugEnv = &obj->as(); } } else { MissingEnvironmentKey key(frame, funScope); if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(key)) { debugEnv = p->value(); envs->liveEnvs.remove(&debugEnv->environment().as()); envs->missingEnvs.remove(p); } } if (debugEnv) { DebugEnvironments::takeFrameSnapshot(cx, debugEnv, frame); } } void DebugEnvironments::onPopLexical(JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc) { cx->check(frame); DebugEnvironments* envs = cx->realm()->debugEnvs(); if (!envs) { return; } EnvironmentIter ei(cx, frame, pc); onPopLexical(cx, ei); } template void DebugEnvironments::onPopGeneric(JSContext* cx, const EnvironmentIter& ei) { DebugEnvironments* envs = cx->realm()->debugEnvs(); if (!envs) { return; } MOZ_ASSERT(ei.withinInitialFrame()); MOZ_ASSERT(ei.scope().is()); Rooted env(cx); if (MissingEnvironmentMap::Ptr p = envs->missingEnvs.lookup(MissingEnvironmentKey(ei))) { env = &p->value()->environment().as(); envs->missingEnvs.remove(p); } else if (ei.hasSyntacticEnvironment()) { env = &ei.environment().as(); } if (env) { envs->liveEnvs.remove(env); if (JSObject* obj = envs->proxiedEnvs.lookup(env)) { Rooted debugEnv( cx, &obj->as()); DebugEnvironments::takeFrameSnapshot(cx, debugEnv, ei.initialFrame()); } } } void DebugEnvironments::onPopLexical(JSContext* cx, const EnvironmentIter& ei) { if (ei.scope().is()) { onPopGeneric(cx, ei); } else { onPopGeneric(cx, ei); } } void DebugEnvironments::onPopVar(JSContext* cx, const EnvironmentIter& ei) { if (ei.scope().is()) { onPopGeneric(cx, ei); } else { onPopGeneric(cx, ei); } } void DebugEnvironments::onPopWith(AbstractFramePtr frame) { Realm* realm = frame.realm(); if (DebugEnvironments* envs = realm->debugEnvs()) { envs->liveEnvs.remove( &frame.environmentChain()->as()); } } void DebugEnvironments::onPopModule(JSContext* cx, const EnvironmentIter& ei) { onPopGeneric(cx, ei); } void DebugEnvironments::onRealmUnsetIsDebuggee(Realm* realm) { if (DebugEnvironments* envs = realm->debugEnvs()) { envs->proxiedEnvs.clear(); envs->missingEnvs.clear(); envs->liveEnvs.clear(); } } bool DebugEnvironments::updateLiveEnvironments(JSContext* cx) { AutoCheckRecursionLimit recursion(cx); if (!recursion.check(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.realm() != cx->realm()) { continue; } if (!frame.isDebuggee()) { continue; } RootedObject env(cx); Rooted scope(cx); if (!GetFrameEnvironmentAndScope(cx, frame, i.pc(), &env, &scope)) { return false; } for (EnvironmentIter ei(cx, env, scope, frame); ei.withinInitialFrame(); ei++) { if (ei.hasSyntacticEnvironment() && !ei.scope().is()) { MOZ_ASSERT(ei.environment().realm() == cx->realm()); DebugEnvironments* envs = ensureRealmData(cx); if (!envs) { return false; } if (!envs->liveEnvs.put(&ei.environment(), LiveEnvironmentVal(ei))) { ReportOutOfMemory(cx); return false; } } } if (frame.prevUpToDate()) { return true; } MOZ_ASSERT(frame.realm()->isDebuggee()); frame.setPrevUpToDate(); } return true; } LiveEnvironmentVal* DebugEnvironments::hasLiveEnvironment( EnvironmentObject& env) { DebugEnvironments* envs = env.realm()->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.realm() != cx->realm()) { continue; } frame.unsetPrevUpToDate(); } } /* static */ void DebugEnvironments::forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to) { DebugEnvironments* envs = cx->realm()->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::traceLiveFrame(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 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 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() || ei.scope().is() || ei.scope().is() || ei.scope().is() || ei.scope().is() || ei.scope().kind() == ScopeKind::StrictEval)); 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 debugEnv(cx); if (ei.scope().is()) { RootedFunction callee(cx, ei.scope().as().canonicalFunction()); JS::ExposeObjectToActiveJS(callee); Rooted callobj(cx, CallObject::createHollowForDebug(cx, callee)); if (!callobj) { return nullptr; } debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug); } else if (ei.scope().is()) { Rooted lexicalScope(cx, &ei.scope().as()); Rooted env( cx, BlockLexicalEnvironmentObject::createHollowForDebug(cx, lexicalScope)); if (!env) { return nullptr; } debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug); } else if (ei.scope().is()) { Rooted wasmInstanceScope( cx, &ei.scope().as()); Rooted env( cx, WasmInstanceEnvironmentObject::createHollowForDebug( cx, wasmInstanceScope)); if (!env) { return nullptr; } debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug); } else if (ei.scope().is()) { Rooted wasmFunctionScope( cx, &ei.scope().as()); RootedObject enclosing( cx, &enclosingDebug->as().environment()); Rooted callobj( cx, WasmFunctionCallObject::createHollowForDebug(cx, enclosing, wasmFunctionScope)); if (!callobj) { return nullptr; } debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug); } else { Rooted scope(cx, &ei.scope()); MOZ_ASSERT(scope->is() || scope->kind() == ScopeKind::StrictEval); Rooted env( cx, VarEnvironmentObject::createHollowForDebug(cx, scope)); 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()); } #endif return &enclosing; } static JSObject* GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei) { AutoCheckRecursionLimit recursion(cx); if (!recursion.check(cx)) { return nullptr; } if (ei.done()) { return GetDebugEnvironmentForNonEnvironmentObject(ei); } if (ei.hasAnyEnvironmentObject()) { return GetDebugEnvironmentForEnvironmentObject(cx, ei); } if (ei.scope().is() || ei.scope().is() || ei.scope().is() || ei.scope().is() || ei.scope().is() || ei.scope().kind() == ScopeKind::StrictEval) { return GetDebugEnvironmentForMissing(cx, ei); } EnvironmentIter copy(cx, ei); return GetDebugEnvironment(cx, ++copy); } JSObject* js::GetDebugEnvironmentForFunction(JSContext* cx, HandleFunction fun) { cx->check(fun); MOZ_ASSERT(CanUseDebugEnvironmentMaps(cx)); if (!DebugEnvironments::updateLiveEnvironments(cx)) { return nullptr; } JSScript* script = JSFunction::getOrCreateScript(cx, fun); if (!script) { return nullptr; } EnvironmentIter ei(cx, fun->environment(), script->enclosingScope()); return GetDebugEnvironment(cx, ei); } JSObject* js::GetDebugEnvironmentForSuspendedGenerator( JSContext* cx, JSScript* script, AbstractGeneratorObject& genObj) { RootedObject env(cx); Rooted scope(cx); GetSuspendedGeneratorEnvironmentAndScope(genObj, script, &env, &scope); EnvironmentIter ei(cx, env, scope); return GetDebugEnvironment(cx, ei); } JSObject* js::GetDebugEnvironmentForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc) { cx->check(frame); if (CanUseDebugEnvironmentMaps(cx) && !DebugEnvironments::updateLiveEnvironments(cx)) { return nullptr; } RootedObject env(cx); Rooted scope(cx); if (!GetFrameEnvironmentAndScope(cx, frame, pc, &env, &scope)) { return nullptr; } EnvironmentIter ei(cx, env, scope, frame); return GetDebugEnvironment(cx, ei); } JSObject* js::GetDebugEnvironmentForGlobalLexicalEnvironment(JSContext* cx) { EnvironmentIter ei(cx, &cx->global()->lexicalEnvironment(), &cx->global()->emptyGlobalScope()); return GetDebugEnvironment(cx, ei); } bool js::CreateObjectsForEnvironmentChain(JSContext* cx, HandleObjectVector chain, HandleObject terminatingEnv, MutableHandleObject envObj) { #ifdef DEBUG for (size_t i = 0; i < chain.length(); ++i) { cx->check(chain[i]); MOZ_ASSERT(!chain[i]->is() && !chain[i]->is()); } #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 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(getReservedSlot(SCOPE_SLOT).toGCThing()); } ModuleEnvironmentObject* js::GetModuleEnvironmentForScript(JSScript* script) { ModuleObject* module = GetModuleObjectForScript(script); if (!module) { return nullptr; } return module->environment(); } ModuleObject* js::GetModuleObjectForScript(JSScript* script) { for (ScopeIter si(script); si; si++) { if (si.kind() == ScopeKind::Module) { return si.scope()->as().module(); } } return nullptr; } static bool GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut( JSContext* cx, const EnvironmentIter& originalIter, HandleObject scopeChain, const jsbytecode* pc, MutableHandleValue res) { for (EnvironmentIter ei(cx, originalIter); ei; ei++) { if (ei.scope().kind() == ScopeKind::Module) { res.setUndefined(); return true; } if (!ei.scope().is() || ei.scope().as().canonicalFunction()->hasLexicalThis()) { continue; } RootedScript script(cx, ei.scope().as().script()); if (ei.withinInitialFrame()) { MOZ_ASSERT(pc, "must have PC if there is an initial frame"); // Figure out if we executed JSOp::FunctionThis and set it. bool executedInitThisOp = false; if (script->functionHasThisBinding()) { for (const BytecodeLocation& loc : js::AllBytecodesIterable(script)) { if (loc.getOp() == JSOp::FunctionThis) { // The next op after JSOp::FunctionThis always sets it. executedInitThisOp = pc > GetNextPc(loc.toRawBytecode()); break; } } } if (!executedInitThisOp) { AbstractFramePtr initialFrame = ei.initialFrame(); // 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 (initialFrame.thisArgument().isObject() || script->strict()) { res.set(initialFrame.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, initialFrame, res)) { return false; } initialFrame.thisArgument() = res; return true; } } if (!script->functionHasThisBinding()) { res.setMagic(JS_OPTIMIZED_OUT); return true; } for (Rooted 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()); return GetProperty(cx, callObj, callObj, bi.name()->asPropertyName(), res); } if (loc.kind() == BindingLocation::Kind::Frame) { if (ei.withinInitialFrame()) { res.set(ei.initialFrame().unaliasedLocal(loc.slot())); return true; } if (ei.hasAnyEnvironmentObject()) { RootedObject env(cx, &ei.environment()); AbstractGeneratorObject* genObj = GetGeneratorObjectForEnvironment(cx, env); if (genObj && genObj->isSuspended() && genObj->hasStackStorage()) { res.set(genObj->getUnaliasedLocal(loc.slot())); return true; } } } res.setMagic(JS_OPTIMIZED_OUT); return true; } MOZ_CRASH("'this' binding must be found"); } GetNonSyntacticGlobalThis(cx, scopeChain, res); return true; } bool js::GetThisValueForDebuggerFrameMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc, MutableHandleValue res) { RootedObject scopeChain(cx); Rooted scope(cx); if (!GetFrameEnvironmentAndScope(cx, frame, pc, &scopeChain, &scope)) { return false; } EnvironmentIter ei(cx, scopeChain, scope, frame); return GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut( cx, ei, scopeChain, pc, res); } bool js::GetThisValueForDebuggerSuspendedGeneratorMaybeOptimizedOut( JSContext* cx, AbstractGeneratorObject& genObj, JSScript* script, MutableHandleValue res) { RootedObject scopeChain(cx); Rooted scope(cx); GetSuspendedGeneratorEnvironmentAndScope(genObj, script, &scopeChain, &scope); EnvironmentIter ei(cx, scopeChain, scope); return GetThisValueForDebuggerEnvironmentIterMaybeOptimizedOut( cx, ei, scopeChain, nullptr, res); } bool js::CheckLexicalNameConflict( JSContext* cx, Handle lexicalEnv, HandleObject varObj, Handle name) { const char* redeclKind = nullptr; RootedId id(cx, NameToId(name)); mozilla::Maybe prop; if (varObj->is() && varObj->as().isInVarNames(name)) { // ES 15.1.11 step 5.a redeclKind = "var"; } else if ((prop = lexicalEnv->lookup(cx, name))) { // ES 15.1.11 step 5.b redeclKind = prop->writable() ? "let" : "const"; } else if (varObj->is() && (prop = varObj->as().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 (!prop->configurable()) { redeclKind = "non-configurable global property"; } } else { // ES 15.1.11 step 5.c-d Rooted> desc(cx); if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc)) { return false; } if (desc.isSome() && !desc->configurable()) { redeclKind = "non-configurable global property"; } } if (redeclKind) { ReportRuntimeRedeclaration(cx, name, redeclKind); return false; } return true; } [[nodiscard]] static bool CheckVarNameConflict( JSContext* cx, Handle lexicalEnv, Handle name) { mozilla::Maybe prop = lexicalEnv->lookup(cx, name); if (prop.isSome()) { ReportRuntimeRedeclaration(cx, name, prop->writable() ? "let" : "const"); return false; } return true; } static void ReportCannotDeclareGlobalBinding(JSContext* cx, Handle name, const char* reason) { if (UniqueChars printable = AtomToPrintableString(cx, name)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_DECLARE_GLOBAL_BINDING, printable.get(), reason); } } bool js::CheckCanDeclareGlobalBinding(JSContext* cx, Handle global, Handle name, bool isFunction) { RootedId id(cx, NameToId(name)); Rooted> 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.isNothing()) { // 8.1.14.15 step 6. // 8.1.14.16 step 5. if (global->isExtensible()) { 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; } // Add the var/let/const bindings to the variables environment of a global or // sloppy-eval script. The redeclaration checks should already have been // performed. static bool InitGlobalOrEvalDeclarations( JSContext* cx, HandleScript script, Handle lexicalEnv, HandleObject varObj) { Rooted bi(cx, BindingIter(script)); for (; bi; bi++) { if (bi.isTopLevelFunction()) { continue; } Rooted name(cx, bi.name()->asPropertyName()); unsigned attrs = script->isForEval() ? JSPROP_ENUMERATE : JSPROP_ENUMERATE | JSPROP_PERMANENT; switch (bi.kind()) { case BindingKind::Var: { PropertyResult prop; RootedObject obj2(cx); if (!LookupProperty(cx, varObj, name, &obj2, &prop)) { return false; } if (prop.isNotFound() || (obj2 != varObj && varObj->is())) { if (!DefineDataProperty(cx, varObj, name, UndefinedHandleValue, attrs)) { return false; } } if (varObj->is()) { if (!varObj->as().addToVarNames(cx, name)) { return false; } } break; } case BindingKind::Const: attrs |= JSPROP_READONLY; [[fallthrough]]; case BindingKind::Let: { RootedId id(cx, NameToId(name)); RootedValue uninitialized(cx, MagicValue(JS_UNINITIALIZED_LEXICAL)); if (!NativeDefineDataProperty(cx, lexicalEnv, id, uninitialized, attrs)) { return false; } break; } default: MOZ_CRASH("Expected binding kind"); return false; } } return true; } // Define the hoisted top-level functions on the variables environment of a // global or sloppy-eval script. Redeclaration checks must already have been // performed. static bool InitHoistedFunctionDeclarations(JSContext* cx, HandleScript script, HandleObject envChain, HandleObject varObj, GCThingIndex lastFun) { // The inner-functions up to `lastFun` are the hoisted function declarations // of the script. We must clone and bind them now. for (size_t i = 0; i <= lastFun; ++i) { JS::GCCellPtr thing = script->gcthings()[i]; // Skip the initial scopes. In practice, there is at most one variables and // one lexical scope. if (thing.is()) { MOZ_ASSERT(i < 2); continue; } RootedFunction fun(cx, &thing.as().as()); Rooted name(cx, fun->explicitName()->asPropertyName()); // Clone the function before exposing to script as a binding. JSObject* clone = Lambda(cx, fun, envChain); if (!clone) { return false; } RootedValue rval(cx, ObjectValue(*clone)); PropertyResult prop; RootedObject pobj(cx); if (!LookupProperty(cx, varObj, name, &pobj, &prop)) { return false; } // ECMA requires functions defined when entering Eval code to be // impermanent. unsigned attrs = script->isForEval() ? JSPROP_ENUMERATE : JSPROP_ENUMERATE | JSPROP_PERMANENT; if (prop.isNotFound() || pobj != varObj) { if (!DefineDataProperty(cx, varObj, name, rval, attrs)) { return false; } if (varObj->is()) { if (!varObj->as().addToVarNames(cx, name)) { return false; } } // Done processing this function. continue; } /* * A DebugEnvironmentProxy is okay here, and sometimes necessary. If * Debugger.Frame.prototype.eval defines a function with the same name as an * extant variable in the frame, the DebugEnvironmentProxy takes care of * storing the function in the stack frame (for non-aliased variables) or on * the scope object (for aliased). */ MOZ_ASSERT(varObj->is() || varObj->is()); if (varObj->is()) { PropertyInfo propInfo = prop.propertyInfo(); if (propInfo.configurable()) { if (!DefineDataProperty(cx, varObj, name, rval, attrs)) { return false; } } else { MOZ_ASSERT(propInfo.isDataProperty()); MOZ_ASSERT(propInfo.writable()); MOZ_ASSERT(propInfo.enumerable()); } // Careful: the presence of a shape, even one appearing to derive from // a variable declaration, doesn't mean it's in [[VarNames]]. if (!varObj->as().addToVarNames(cx, name)) { return false; } } /* * Non-global properties, and global properties which we aren't simply * redefining, must be set. First, this preserves their attributes. * Second, this will produce warnings and/or errors as necessary if the * specified Call object property is not writable (const). */ RootedId id(cx, NameToId(name)); if (!PutProperty(cx, varObj, id, rval, script->strict())) { return false; } } return true; } bool js::CheckGlobalDeclarationConflicts( JSContext* cx, HandleScript script, Handle 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. Rooted name(cx); Rooted 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()) { Handle global = varObj.as(); 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; } [[nodiscard]] static bool CheckVarNameConflictsInEnv(JSContext* cx, HandleScript script, HandleObject obj) { Rooted env(cx); if (obj->is()) { env = &obj->as(); } else if (obj->is() && obj->as() .environment() .is()) { env = &obj->as() .environment() .as(); } else { // Environment cannot contain lexical bindings. return true; } if (env->is() && env->as().scope().kind() == ScopeKind::SimpleCatch) { // Annex B.3.5 allows redeclaring simple (non-destructured) catch parameters // with var declarations. return true; } Rooted name(cx); for (BindingIter bi(script); bi; bi++) { name = bi.name()->asPropertyName(); if (!CheckVarNameConflict(cx, env, name)) { return false; } } return true; } static bool CheckArgumentsRedeclaration(JSContext* cx, HandleScript script) { for (BindingIter bi(script); bi; bi++) { if (bi.name() == cx->names().arguments) { ReportRuntimeRedeclaration(cx, cx->names().arguments, "let"); return false; } } return true; } static bool CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script, HandleObject scopeChain, HandleObject varObj) { // Strict eval has its own call objects and we shouldn't end up here. // // Non-strict eval may introduce 'var' bindings that conflict with lexical // bindings in an enclosing lexical scope. MOZ_ASSERT(!script->bodyScope()->hasEnvironment()); MOZ_ASSERT(!script->strict()); MOZ_ASSERT(script->bodyScope()->as().hasBindings()); 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(); } // Check for redeclared "arguments" in function parameter expressions. // // Direct eval in function parameter expressions isn't allowed to redeclare // the implicit "arguments" bindings: // function f(a = eval("var arguments;")) {} // // |varObj| isn't a CallObject when the direct eval occurs in the function // body and the extra function body var scope is present. The extra var scope // is present iff the function has parameter expressions. So when we test // that |varObj| is a CallObject and function parameter expressions are // present, we can pinpoint the direct eval location to be in a function // parameter expression. Additionally we must ensure the function isn't an // arrow function, because arrow functions don't have an implicit "arguments" // binding. if (script->isDirectEvalInFunction() && varObj->is()) { JSFunction* fun = &varObj->as().callee(); JSScript* funScript = fun->nonLazyScript(); if (funScript->functionHasParameterExprs() && !fun->isArrow()) { if (!CheckArgumentsRedeclaration(cx, script)) { return false; } } } // Step 8. // // Check that global functions may be declared. if (varObj->is()) { Handle global = varObj.as(); Rooted name(cx); for (Rooted bi(cx, BindingIter(script)); bi; bi++) { name = bi.name()->asPropertyName(); if (!CheckCanDeclareGlobalBinding(cx, global, name, bi.isTopLevelFunction())) { return false; } } } return true; } bool js::GlobalOrEvalDeclInstantiation(JSContext* cx, HandleObject envChain, HandleScript script, GCThingIndex lastFun) { MOZ_ASSERT(script->isGlobalCode() || script->isForEval()); MOZ_ASSERT(!script->selfHosted()); RootedObject varObj(cx, &GetVariablesObject(envChain)); Rooted lexicalEnv(cx); if (script->isForEval()) { if (!CheckEvalDeclarationConflicts(cx, script, envChain, varObj)) { return false; } } else { lexicalEnv = &NearestEnclosingExtensibleLexicalEnvironment(envChain); if (!CheckGlobalDeclarationConflicts(cx, script, lexicalEnv, varObj)) { return false; } } if (!InitGlobalOrEvalDeclarations(cx, script, lexicalEnv, varObj)) { return false; } return InitHoistedFunctionDeclarations(cx, script, envChain, varObj, lastFun); } 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 = 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, Handle scope, AbstractFramePtr frame) { auto* env = VarEnvironmentObject::createForFrame(cx, scope, frame); if (!env) { return false; } frame.pushOnEnvironmentChain(*env); return true; } bool js::GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, const jsbytecode* pc, MutableHandleObject env, MutableHandle scope) { env.set(frame.environmentChain()); if (frame.isWasmDebugFrame()) { Rooted instance(cx, frame.wasmInstance()->object()); uint32_t funcIndex = frame.asWasmDebugFrame()->funcIndex(); scope.set(WasmInstanceObject::getFunctionScope(cx, instance, funcIndex)); if (!scope) { return false; } } else { scope.set(frame.script()->innermostScope(pc)); } return true; } void js::GetSuspendedGeneratorEnvironmentAndScope( AbstractGeneratorObject& genObj, JSScript* script, MutableHandleObject env, MutableHandle scope) { env.set(&genObj.environmentChain()); jsbytecode* pc = script->offsetToPC(script->resumeOffsets()[genObj.resumeIndex()]); scope.set(script->innermostScope(pc)); } #ifdef DEBUG typedef HashSet 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. AllBytecodesIterable iter(script); for (BytecodeLocation loc : iter) { PropertyName* name; switch (loc.getOp()) { case JSOp::GetName: case JSOp::SetName: case JSOp::StrictSetName: name = script->getName(loc.toRawBytecode()); break; case JSOp::GetAliasedVar: case JSOp::SetAliasedVar: name = EnvironmentCoordinateNameSlow(script, loc.toRawBytecode()); break; default: name = nullptr; break; } if (name) { remainingNames.remove(name); } } RootedFunction fun(cx); RootedScript innerScript(cx); for (JS::GCCellPtr gcThing : script->gcthings()) { if (!gcThing.is()) { continue; } JSObject* obj = &gcThing.as(); if (!obj->is()) { continue; } fun = &obj->as(); if (!fun->isInterpreted()) { continue; } innerScript = JSFunction::getOrCreateScript(cx, fun); if (!innerScript) { return false; } if (!RemoveReferencedNames(cx, innerScript, remainingNames)) { return false; } } return true; } static bool AnalyzeEntrainedVariablesInScript(JSContext* cx, HandleScript script, HandleScript innerScript) { PropertyNameSet remainingNames(cx); 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->function()->displayAtom()) { buf.putString(name); buf.printf(" "); } buf.printf("(%s:%u) has variables entrained by ", script->filename(), script->lineno()); if (JSAtom* name = innerScript->function()->displayAtom()) { buf.putString(name); buf.printf(" "); } buf.printf("(%s:%u) ::", 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()); } RootedFunction fun(cx); RootedScript innerInnerScript(cx); for (JS::GCCellPtr gcThing : script->gcthings()) { if (!gcThing.is()) { continue; } JSObject* obj = &gcThing.as(); if (!obj->is()) { continue; } fun = &obj->as(); if (!fun->isInterpreted()) { continue; } innerInnerScript = JSFunction::getOrCreateScript(cx, fun); if (!innerInnerScript) { return false; } if (!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) { RootedFunction fun(cx); RootedScript innerScript(cx); for (JS::GCCellPtr gcThing : script->gcthings()) { if (!gcThing.is()) { continue; } JSObject* obj = &gcThing.as(); if (!obj->is()) { continue; } fun = &obj->as(); if (!fun->isInterpreted()) { continue; } innerScript = JSFunction::getOrCreateScript(cx, fun); if (!innerScript) { return false; } if (fun->needsCallObject()) { if (!AnalyzeEntrainedVariablesInScript(cx, script, innerScript)) { return false; } } if (!AnalyzeEntrainedVariables(cx, innerScript)) { return false; } } return true; } #endif JSObject* js::MaybeOptimizeBindGlobalName(JSContext* cx, Handle global, Handle name) { // We can bind name to the global lexical scope if the binding already // exists, is initialized, and is writable (i.e., an initialized // 'let') at compile time. Rooted env(cx, &global->lexicalEnvironment()); mozilla::Maybe prop = env->lookup(cx, name); if (prop.isSome()) { if (prop->writable() && !env->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) { return env; } return nullptr; } prop = global->lookup(cx, name); if (prop.isSome()) { // If the property does not currently exist on the global lexical // scope, we can bind name to the global object if the property // exists on the global and is non-configurable, as then it cannot // be shadowed. if (!prop->configurable()) { return global; } } return nullptr; } const char* EnvironmentObject::typeString() const { if (is()) { return "CallObject"; } if (is()) { return "VarEnvironmentObject"; } if (is()) { return "ModuleEnvironmentObject"; } if (is()) { return "WasmInstanceEnvironmentObject"; } if (is()) { return "WasmFunctionCallObject"; } if (is()) { if (is()) { if (is()) { if (is()) { return "NamedLambdaObject"; } return "BlockLexicalEnvironmentObject"; } if (is()) { return "ClassBodyLexicalEnvironmentObject"; } return "ScopedLexicalEnvironmentObject"; } if (is()) { if (is()) { return "GlobalLexicalEnvironmentObject"; } if (is()) { return "NonSyntacticLexicalEnvironmentObject"; } return "ExtensibleLexicalEnvironmentObject"; } return "LexicalEnvironmentObject"; } if (is()) { return "NonSyntacticVariablesObject"; } if (is()) { return "WithEnvironmentObject"; } if (is()) { return "RuntimeLexicalErrorObject"; } return "EnvironmentObject"; } #if defined(DEBUG) || defined(JS_JITSPEW) static void DumpEnvironmentObject(JSObject* unrootedEnvObj) { JSContext* cx = TlsContext.get(); if (!cx) { fprintf(stderr, "*** can't get JSContext for current thread\n"); return; } Rooted envObj(cx, unrootedEnvObj); while (envObj) { Rooted env(cx); if (envObj->is()) { env = &envObj->as(); } else if (envObj->is()) { fprintf(stderr, "[DebugProxy] "); env = &envObj->as().environment(); } else { MOZ_ASSERT(envObj->is()); fprintf(stderr, "global\n"); break; } fprintf(stderr, "%s", env->typeString()); Rooted scope(cx); if (env->is()) { scope = &env->as().scope(); } if (env->is()) { scope = &env->as().scope(); } else if (env->is()) { scope = &env->as().scope(); } else if (env->is()) { scope = &env->as().scope(); } if (scope) { fprintf(stderr, " {\n"); for (Rooted bi(cx, BindingIter(scope)); bi; bi++) { if (bi.location().kind() == BindingLocation::Kind::Environment) { UniqueChars bytes = AtomToPrintableString(cx, bi.name()); if (!bytes) { fprintf(stderr, " *** out of memory\n"); return; } fprintf(stderr, " %u: %s %s\n", bi.location().slot(), BindingKindString(bi.kind()), bytes.get()); } } fprintf(stderr, "}"); } fprintf(stderr, "\n"); if (envObj->is()) { envObj = &envObj->as().enclosingEnvironment(); } else { envObj = &env->enclosingEnvironment(); } if (envObj) { fprintf(stderr, "-> "); } } } void EnvironmentObject::dump() { DumpEnvironmentObject(this); } void DebugEnvironmentProxy::dump() { DumpEnvironmentObject(this); } #endif /* defined(DEBUG) || defined(JS_JITSPEW) */