/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/ScopeObject-inl.h" #include "mozilla/PodOperations.h" #include "mozilla/SizePrintfMacros.h" #include "jscompartment.h" #include "jsiter.h" #include "builtin/ModuleObject.h" #include "frontend/ParseNode.h" #include "vm/ArgumentsObject.h" #include "vm/GlobalObject.h" #include "vm/ProxyObject.h" #include "vm/Shape.h" #include "vm/Xdr.h" #include "jsatominlines.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" #include "vm/Stack-inl.h" using namespace js; using namespace js::gc; using mozilla::PodZero; typedef Rooted RootedArgumentsObject; typedef MutableHandle MutableHandleArgumentsObject; /*****************************************************************************/ Shape* js::ScopeCoordinateToStaticScopeShape(JSScript* script, jsbytecode* pc) { MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD); StaticScopeIter ssi(script->innermostStaticScopeInScript(pc)); uint32_t hops = ScopeCoordinate(pc).hops(); while (true) { MOZ_ASSERT(!ssi.done()); if (ssi.hasSyntacticDynamicScopeObject()) { if (!hops) break; hops--; } ssi++; } return ssi.scopeShape(); } static const uint32_t SCOPE_COORDINATE_NAME_THRESHOLD = 20; void ScopeCoordinateNameCache::purge() { shape = nullptr; if (map.initialized()) map.finish(); } PropertyName* js::ScopeCoordinateName(ScopeCoordinateNameCache& cache, JSScript* script, jsbytecode* pc) { Shape* shape = ScopeCoordinateToStaticScopeShape(script, pc); if (shape != cache.shape && shape->slot() >= SCOPE_COORDINATE_NAME_THRESHOLD) { cache.purge(); if (cache.map.init(shape->slot())) { cache.shape = shape; Shape::Range r(shape); while (!r.empty()) { if (!cache.map.putNew(r.front().slot(), r.front().propid())) { cache.purge(); break; } r.popFront(); } } } jsid id; ScopeCoordinate sc(pc); if (shape == cache.shape) { ScopeCoordinateNameCache::Map::Ptr p = cache.map.lookup(sc.slot()); id = p->value(); } else { Shape::Range r(shape); while (r.front().slot() != sc.slot()) r.popFront(); id = r.front().propidRaw(); } /* Beware nameless destructuring formal. */ if (!JSID_IS_ATOM(id)) return script->runtimeFromAnyThread()->commonNames->empty; return JSID_TO_ATOM(id)->asPropertyName(); } JSScript* js::ScopeCoordinateFunctionScript(JSScript* script, jsbytecode* pc) { MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD); StaticScopeIter ssi(script->innermostStaticScopeInScript(pc)); uint32_t hops = ScopeCoordinate(pc).hops(); while (true) { if (ssi.hasSyntacticDynamicScopeObject()) { if (!hops) break; hops--; } ssi++; } if (ssi.type() != StaticScopeIter::Function) return nullptr; return ssi.funScript(); } /*****************************************************************************/ void ScopeObject::setEnclosingScope(HandleObject obj) { MOZ_ASSERT_IF(obj->is() || obj->is() || obj->is(), obj->isDelegate()); setFixedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*obj)); } CallObject* CallObject::create(JSContext* cx, HandleShape shape, HandleObjectGroup group, uint32_t lexicalBegin) { MOZ_ASSERT(!group->singleton(), "passed a singleton group to create() (use createSingleton() " "instead)"); gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_)); kind = gc::GetBackgroundAllocKind(kind); JSObject* obj = JSObject::create(cx, kind, gc::DefaultHeap, shape, group); if (!obj) return nullptr; obj->as().initRemainingSlotsToUninitializedLexicals(lexicalBegin); return &obj->as(); } CallObject* CallObject::createSingleton(JSContext* cx, HandleShape shape, uint32_t lexicalBegin) { gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_)); kind = gc::GetBackgroundAllocKind(kind); RootedObjectGroup group(cx, ObjectGroup::lazySingletonGroup(cx, &class_, TaggedProto(nullptr))); if (!group) return nullptr; RootedObject obj(cx, JSObject::create(cx, kind, gc::TenuredHeap, shape, group)); if (!obj) return nullptr; MOZ_ASSERT(obj->isSingleton(), "group created inline above must be a singleton"); obj->as().initRemainingSlotsToUninitializedLexicals(lexicalBegin); return &obj->as(); } /* * Create a CallObject for a JSScript that is not initialized to any particular * callsite. This object can either be initialized (with an enclosing scope and * callee) or used as a template for jit compilation. */ CallObject* CallObject::createTemplateObject(JSContext* cx, HandleScript script, gc::InitialHeap heap) { RootedShape shape(cx, script->bindings.callObjShape()); MOZ_ASSERT(shape->getObjectClass() == &class_); RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr))); if (!group) return nullptr; gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_)); kind = gc::GetBackgroundAllocKind(kind); JSObject* obj = JSObject::create(cx, kind, heap, shape, group); if (!obj) return nullptr; // Set uninitialized lexicals even on template objects, as Ion will copy // over the template object's slot values in the fast path. obj->as().initAliasedLexicalsToThrowOnTouch(script); return &obj->as(); } /* * Construct a call object for the given bindings. If this is a call object * for a function invocation, callee should be the function being called. * Otherwise it must be a call object for eval of strict mode code, and callee * must be null. */ CallObject* CallObject::create(JSContext* cx, HandleScript script, HandleObject enclosing, HandleFunction callee) { gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap; CallObject* callobj = CallObject::createTemplateObject(cx, script, heap); if (!callobj) return nullptr; callobj->setEnclosingScope(enclosing); callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee)); if (script->treatAsRunOnce()) { Rooted ncallobj(cx, callobj); if (!JSObject::setSingleton(cx, ncallobj)) return nullptr; return ncallobj; } return callobj; } CallObject* CallObject::createForFunction(JSContext* cx, HandleObject enclosing, HandleFunction callee) { RootedObject scopeChain(cx, enclosing); MOZ_ASSERT(scopeChain); /* * For a named function expression Call's parent points to an environment * object holding function's name. */ if (callee->isNamedLambda()) { scopeChain = DeclEnvObject::create(cx, scopeChain, callee); if (!scopeChain) return nullptr; } RootedScript script(cx, callee->nonLazyScript()); return create(cx, script, scopeChain, callee); } CallObject* CallObject::createForFunction(JSContext* cx, AbstractFramePtr frame) { MOZ_ASSERT(frame.isNonEvalFunctionFrame()); assertSameCompartment(cx, frame); RootedObject scopeChain(cx, frame.scopeChain()); RootedFunction callee(cx, frame.callee()); CallObject* callobj = createForFunction(cx, scopeChain, callee); if (!callobj) return nullptr; /* Copy in the closed-over formal arguments. */ for (AliasedFormalIter i(frame.script()); i; i++) { callobj->setAliasedVar(cx, i, i->name(), frame.unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING)); } return callobj; } CallObject* CallObject::createForStrictEval(JSContext* cx, AbstractFramePtr frame) { MOZ_ASSERT(frame.isStrictEvalFrame()); MOZ_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterFrame() == frame.asInterpreterFrame()); MOZ_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterRegs().pc == frame.script()->code()); RootedFunction callee(cx); RootedScript script(cx, frame.script()); RootedObject scopeChain(cx, frame.scopeChain()); return create(cx, script, scopeChain, callee); } CallObject* CallObject::createHollowForDebug(JSContext* cx, HandleFunction callee) { MOZ_ASSERT(!callee->needsCallObject()); // This scope's parent link is never used: the DebugScopeObject that // refers to this scope carries its own parent link, which is what // Debugger uses to construct the tree of Debugger.Environment objects. So // just parent this scope directly to the global lexical scope. Rooted global(cx, &callee->global()); RootedObject globalLexical(cx, &global->lexicalScope()); Rooted callobj(cx, createForFunction(cx, globalLexical, callee)); if (!callobj) return nullptr; RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT)); RootedId id(cx); RootedScript script(cx, callee->nonLazyScript()); for (BindingIter bi(script); !bi.done(); bi++) { id = NameToId(bi->name()); if (!SetProperty(cx, callobj, id, optimizedOut)) return nullptr; } return callobj; } const Class CallObject::class_ = { "Call", JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS) }; /*****************************************************************************/ const Class ModuleEnvironmentObject::class_ = { "ModuleEnvironmentObject", JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS, nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ nullptr, /* trace */ JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, { ModuleEnvironmentObject::lookupProperty, nullptr, /* defineProperty */ ModuleEnvironmentObject::hasProperty, ModuleEnvironmentObject::getProperty, ModuleEnvironmentObject::setProperty, ModuleEnvironmentObject::getOwnPropertyDescriptor, ModuleEnvironmentObject::deleteProperty, nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ ModuleEnvironmentObject::enumerate, nullptr } }; /* static */ ModuleEnvironmentObject* ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module) { RootedScript script(cx, module->script()); RootedShape shape(cx, script->bindings.callObjShape()); MOZ_ASSERT(shape->getObjectClass() == &class_); RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr))); if (!group) return nullptr; gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_)); kind = gc::GetBackgroundAllocKind(kind); JSObject* obj = JSObject::create(cx, kind, TenuredHeap, shape, group); if (!obj) return nullptr; RootedModuleEnvironmentObject scope(cx, &obj->as()); // Set uninitialized lexicals even on template objects, as Ion will use // copy over the template object's slot values in the fast path. scope->initAliasedLexicalsToThrowOnTouch(script); scope->initFixedSlot(MODULE_SLOT, ObjectValue(*module)); if (!JSObject::setSingleton(cx, scope)) return nullptr; // Initialize this early so that we can manipulate the scope object without // causing assertions. RootedObject globalLexical(cx, &cx->global()->lexicalScope()); scope->setEnclosingScope(globalLexical); // It is not be possible to add or remove bindings from a module environment // after this point as module code is always strict. #ifdef DEBUG for (Shape::Range r(scope->lastProperty()); !r.empty(); r.popFront()) MOZ_ASSERT(!r.front().configurable()); MOZ_ASSERT(scope->lastProperty()->getObjectFlags() & BaseShape::NOT_EXTENSIBLE); MOZ_ASSERT(!scope->inDictionaryMode()); #endif return scope; } ModuleObject& ModuleEnvironmentObject::module() { return getReservedSlot(MODULE_SLOT).toObject().as(); } IndirectBindingMap& ModuleEnvironmentObject::importBindings() { return module().importBindings(); } bool ModuleEnvironmentObject::createImportBinding(JSContext* cx, HandleAtom importName, HandleModuleObject module, HandleAtom localName) { RootedId importNameId(cx, AtomToId(importName)); RootedId localNameId(cx, AtomToId(localName)); RootedModuleEnvironmentObject env(cx, module->environment()); if (!importBindings().putNew(cx, importNameId, env, localNameId)) { ReportOutOfMemory(cx); return false; } return true; } bool ModuleEnvironmentObject::hasImportBinding(HandlePropertyName name) { return importBindings().has(NameToId(name)); } bool ModuleEnvironmentObject::lookupImport(jsid name, ModuleEnvironmentObject** envOut, Shape** shapeOut) { return importBindings().lookup(name, envOut, shapeOut); } /* static */ bool ModuleEnvironmentObject::lookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp) { const IndirectBindingMap& bindings = obj->as().importBindings(); Shape* shape; ModuleEnvironmentObject* env; if (bindings.lookup(id, &env, &shape)) { objp.set(env); propp.set(shape); return true; } RootedNativeObject 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; } RootedNativeObject 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(); Shape* shape; ModuleEnvironmentObject* env; if (bindings.lookup(id, &env, &shape)) { vp.set(env->getSlot(shape->slot())); return true; } RootedNativeObject 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) { RootedModuleEnvironmentObject self(cx, &obj->as()); if (self->importBindings().has(id)) return result.failReadOnly(); return NativeSetProperty(cx, self, id, v, receiver, Qualified, result); } /* static */ bool ModuleEnvironmentObject::getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, MutableHandle desc) { // We never call this hook on scope objects. MOZ_CRASH(); } /* static */ bool ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { return result.failCantDelete(); } /* static */ bool ModuleEnvironmentObject::enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, bool enumerableOnly) { RootedModuleEnvironmentObject self(cx, &obj->as()); const IndirectBindingMap& bs(self->importBindings()); MOZ_ASSERT(properties.length() == 0); size_t count = bs.count() + self->slotSpan() - RESERVED_SLOTS; if (!properties.reserve(count)) { ReportOutOfMemory(cx); return false; } bs.forEachExportedName([&] (jsid name) { properties.infallibleAppend(name); }); for (Shape::Range r(self->lastProperty()); !r.empty(); r.popFront()) properties.infallibleAppend(r.front().propid()); MOZ_ASSERT(properties.length() == count); return true; } /*****************************************************************************/ const Class DeclEnvObject::class_ = { js_Object_str, JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Object) }; /* * Create a DeclEnvObject for a JSScript that is not initialized to any * particular callsite. This object can either be initialized (with an enclosing * scope and callee) or used as a template for jit compilation. */ DeclEnvObject* DeclEnvObject::createTemplateObject(JSContext* cx, HandleFunction fun, NewObjectKind newKind) { Rooted obj(cx); obj = NewObjectWithNullTaggedProto(cx, newKind, BaseShape::DELEGATE); if (!obj) return nullptr; // Assign a fixed slot to a property with the same name as the lambda. Rooted id(cx, AtomToId(fun->atom())); const Class* clasp = obj->getClass(); unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY; JSGetterOp getter = clasp->getProperty; JSSetterOp setter = clasp->setProperty; MOZ_ASSERT(getter != JS_PropertyStub); MOZ_ASSERT(setter != JS_StrictPropertyStub); if (!NativeObject::putProperty(cx, obj, id, getter, setter, lambdaSlot(), attrs, 0)) return nullptr; MOZ_ASSERT(!obj->hasDynamicSlots()); return obj; } DeclEnvObject* DeclEnvObject::create(JSContext* cx, HandleObject enclosing, HandleFunction callee) { Rooted obj(cx, createTemplateObject(cx, callee, GenericObject)); if (!obj) return nullptr; obj->setEnclosingScope(enclosing); obj->setFixedSlot(lambdaSlot(), ObjectValue(*callee)); return obj; } template bool js::XDRStaticWithObject(XDRState* xdr, HandleObject enclosingScope, MutableHandle objp) { if (mode == XDR_DECODE) { JSContext* cx = xdr->cx(); Rooted obj(cx, StaticWithObject::create(cx)); if (!obj) return false; obj->initEnclosingScope(enclosingScope); objp.set(obj); } // For encoding, there is nothing to do. The only information that is // encoded by a StaticWithObject is its presence on the scope chain, and the // script XDR handler already takes care of that. return true; } template bool js::XDRStaticWithObject(XDRState*, HandleObject, MutableHandle); template bool js::XDRStaticWithObject(XDRState*, HandleObject, MutableHandle); StaticWithObject* StaticWithObject::create(ExclusiveContext* cx) { return NewObjectWithNullTaggedProto(cx, TenuredObject, BaseShape::DELEGATE); } static JSObject* CloneStaticWithObject(JSContext* cx, HandleObject enclosingScope, Handle srcWith) { Rooted clone(cx, StaticWithObject::create(cx)); if (!clone) return nullptr; clone->initEnclosingScope(enclosingScope); return clone; } DynamicWithObject* DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclosing, HandleObject staticWith, WithKind kind) { MOZ_ASSERT(staticWith->is()); Rooted proto(cx, TaggedProto(staticWith)); Rooted obj(cx); obj = NewObjectWithGivenTaggedProto(cx, proto, GenericObject, BaseShape::DELEGATE); if (!obj) return nullptr; Value thisv = GetThisValue(object); obj->setEnclosingScope(enclosing); obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object)); obj->setFixedSlot(THIS_SLOT, thisv); obj->setFixedSlot(KIND_SLOT, Int32Value(kind)); return obj; } static bool with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp) { if (JSID_IS_ATOM(id, cx->names().dotThis)) { objp.set(nullptr); propp.set(nullptr); return true; } RootedObject actual(cx, &obj->as().object()); return LookupProperty(cx, actual, id, objp, propp); } static bool with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle desc, ObjectOpResult& result) { MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis)); 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(!JSID_IS_ATOM(id, cx->names().dotThis)); RootedObject actual(cx, &obj->as().object()); return HasProperty(cx, actual, id, foundp); } static bool with_GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp) { MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis)); RootedObject actual(cx, &obj->as().object()); RootedValue actualReceiver(cx, receiver); if (receiver.isObject() && &receiver.toObject() == obj) actualReceiver.setObject(*actual); return GetProperty(cx, actual, actualReceiver, id, vp); } static bool with_SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) { MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis)); RootedObject actual(cx, &obj->as().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(!JSID_IS_ATOM(id, cx->names().dotThis)); 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(!JSID_IS_ATOM(id, cx->names().dotThis)); RootedObject actual(cx, &obj->as().object()); return DeleteProperty(cx, actual, id, result); } const Class StaticWithObject::class_ = { "WithTemplate", JSCLASS_HAS_RESERVED_SLOTS(StaticWithObject::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS }; const Class DynamicWithObject::class_ = { "With", JSCLASS_HAS_RESERVED_SLOTS(DynamicWithObject::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS, nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ nullptr, /* trace */ JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, { with_LookupProperty, with_DefineProperty, with_HasProperty, with_GetProperty, with_SetProperty, with_GetOwnPropertyDescriptor, with_DeleteProperty, nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ nullptr, /* enumerate (native enumeration of target doesn't work) */ nullptr, } }; /* static */ StaticEvalObject* StaticEvalObject::create(JSContext* cx, HandleObject enclosing) { StaticEvalObject* obj = NewObjectWithNullTaggedProto(cx, TenuredObject, BaseShape::DELEGATE); if (!obj) return nullptr; obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(enclosing)); obj->setReservedSlot(STRICT_SLOT, BooleanValue(false)); return obj; } const Class StaticEvalObject::class_ = { "StaticEval", JSCLASS_HAS_RESERVED_SLOTS(StaticEvalObject::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS }; /* static */ StaticNonSyntacticScopeObjects* StaticNonSyntacticScopeObjects::create(JSContext*cx, HandleObject enclosing) { StaticNonSyntacticScopeObjects* obj = NewObjectWithNullTaggedProto(cx, TenuredObject, BaseShape::DELEGATE); if (!obj) return nullptr; obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(enclosing)); return obj; } const Class StaticNonSyntacticScopeObjects::class_ = { "StaticNonSyntacticScopeObjects", JSCLASS_HAS_RESERVED_SLOTS(StaticNonSyntacticScopeObjects::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS }; /* static */ NonSyntacticVariablesObject* NonSyntacticVariablesObject::create(JSContext* cx, Handle globalLexical) { MOZ_ASSERT(globalLexical->isGlobal()); Rooted obj(cx, NewObjectWithNullTaggedProto(cx, TenuredObject, BaseShape::DELEGATE)); if (!obj) return nullptr; MOZ_ASSERT(obj->isUnqualifiedVarObj()); if (!obj->setQualifiedVarObj(cx)) return nullptr; obj->setEnclosingScope(globalLexical); return obj; } const Class NonSyntacticVariablesObject::class_ = { "NonSyntacticVariablesObject", JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS }; /*****************************************************************************/ bool BlockObject::isExtensible() const { return nonProxyIsExtensible(); } /* static */ ClonedBlockObject* ClonedBlockObject::create(JSContext* cx, Handle block, HandleObject enclosing) { MOZ_ASSERT(block->getClass() == &BlockObject::class_); RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &BlockObject::class_, TaggedProto(block.get()))); if (!group) return nullptr; RootedShape shape(cx, block->lastProperty()); gc::AllocKind allocKind = gc::GetGCObjectKind(&BlockObject::class_); if (CanBeFinalizedInBackground(allocKind, &BlockObject::class_)) allocKind = GetBackgroundAllocKind(allocKind); RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, allocKind, gc::TenuredHeap, shape, group))); if (!obj) return nullptr; MOZ_ASSERT(!obj->inDictionaryMode()); MOZ_ASSERT(obj->slotSpan() >= block->numVariables() + RESERVED_SLOTS); obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*enclosing)); MOZ_ASSERT(obj->isDelegate()); ClonedBlockObject* res = &obj->as(); if (res->isGlobal() || !res->isSyntactic()) res->setReservedSlot(THIS_VALUE_SLOT, GetThisValue(enclosing)); return res; } /* static */ ClonedBlockObject* ClonedBlockObject::create(JSContext* cx, Handle block, AbstractFramePtr frame) { assertSameCompartment(cx, frame); RootedObject enclosing(cx, frame.scopeChain()); return create(cx, block, enclosing); } /* static */ ClonedBlockObject* ClonedBlockObject::createGlobal(JSContext* cx, Handle global) { Rooted staticLexical(cx, StaticBlockObject::create(cx)); if (!staticLexical) return nullptr; // Currently the global lexical scope cannot have any bindings with frame // slots. staticLexical->setLocalOffset(UINT32_MAX); staticLexical->initEnclosingScope(nullptr); Rooted lexical(cx, ClonedBlockObject::create(cx, staticLexical, global)); if (!lexical) return nullptr; if (!JSObject::setSingleton(cx, lexical)) return nullptr; return lexical; } /* static */ ClonedBlockObject* ClonedBlockObject::createNonSyntactic(JSContext* cx, HandleObject enclosingStatic, HandleObject enclosingScope) { MOZ_ASSERT(enclosingStatic->is()); MOZ_ASSERT(!IsSyntacticScope(enclosingScope)); Rooted staticLexical(cx, StaticBlockObject::create(cx)); if (!staticLexical) return nullptr; staticLexical->setLocalOffset(UINT32_MAX); staticLexical->initEnclosingScope(enclosingStatic); Rooted lexical(cx, ClonedBlockObject::create(cx, staticLexical, enclosingScope)); if (!lexical) return nullptr; return lexical; } /* static */ ClonedBlockObject* ClonedBlockObject::createHollowForDebug(JSContext* cx, Handle block) { MOZ_ASSERT(!block->needsClone()); // This scope's parent link is never used: the DebugScopeObject that // refers to this scope carries its own parent link, which is what // Debugger uses to construct the tree of Debugger.Environment objects. So // just parent this scope directly to the global lexical scope. Rooted global(cx, &block->global()); RootedObject globalLexical(cx, &global->lexicalScope()); Rooted obj(cx, create(cx, block, globalLexical)); if (!obj) return nullptr; for (unsigned i = 0; i < block->numVariables(); i++) obj->setVar(i, MagicValue(JS_OPTIMIZED_OUT), DONT_CHECK_ALIASING); return obj; } void ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame) { StaticBlockObject& block = staticBlock(); for (unsigned i = 0; i < numVariables(); ++i) { if (!block.isAliased(i)) { Value& val = frame.unaliasedLocal(block.blockIndexToLocalIndex(i)); setVar(i, val, DONT_CHECK_ALIASING); } } } /* static */ ClonedBlockObject* ClonedBlockObject::clone(JSContext* cx, Handle clonedBlock) { Rooted staticBlock(cx, &clonedBlock->staticBlock()); MOZ_ASSERT(!staticBlock->isExtensible()); RootedObject enclosing(cx, &clonedBlock->enclosingScope()); Rooted copy(cx, create(cx, staticBlock, enclosing)); if (!copy) return nullptr; for (uint32_t i = 0, count = staticBlock->numVariables(); i < count; i++) copy->setVar(i, clonedBlock->var(i, DONT_CHECK_ALIASING), DONT_CHECK_ALIASING); return copy; } StaticBlockObject* StaticBlockObject::create(ExclusiveContext* cx) { return NewObjectWithNullTaggedProto(cx, TenuredObject, BaseShape::DELEGATE); } Shape* StaticBlockObject::lookupAliasedName(PropertyName* name) { Shape::Range r(lastProperty()); while (!r.empty()) { jsid id = r.front().propidRaw(); if (JSID_TO_ATOM(id)->asPropertyName() == name && isAliased(shapeToIndex(r.front()))) return &r.front(); r.popFront(); } return nullptr; } bool StaticBlockObject::makeNonExtensible(ExclusiveContext* cx) { // Do not do all the work of js::PreventExtensions, as BlockObjects are // known to be NativeObjects, have no lazy properties, and no dense // elements. Indeed, we do not have a JSContext as parsing may happen // off-thread. if (!isExtensible()) return true; return setFlags(cx, BaseShape::NOT_EXTENSIBLE, JSObject::GENERATE_SHAPE); } /* static */ Shape* StaticBlockObject::addVar(ExclusiveContext* cx, Handle block, HandleId id, bool constant, unsigned index, bool* redeclared) { MOZ_ASSERT(JSID_IS_ATOM(id)); MOZ_ASSERT(index < LOCAL_INDEX_LIMIT); *redeclared = false; /* Inline NativeObject::addProperty in order to trap the redefinition case. */ ShapeTable::Entry* entry; if (Shape::search(cx, block->lastProperty(), id, &entry, true)) { *redeclared = true; return nullptr; } /* * Don't convert this object to dictionary mode so that we can clone the * block's shape later. */ uint32_t slot = JSSLOT_FREE(&BlockObject::class_) + index; uint32_t readonly = constant ? JSPROP_READONLY : 0; uint32_t propFlags = readonly | JSPROP_ENUMERATE | JSPROP_PERMANENT; return NativeObject::addPropertyInternal(cx, block, id, /* getter = */ nullptr, /* setter = */ nullptr, slot, propFlags, /* attrs = */ 0, entry, /* allowDictionary = */ false); } Value ClonedBlockObject::thisValue() const { MOZ_ASSERT(isGlobal() || !isSyntactic()); Value v = getReservedSlot(THIS_VALUE_SLOT); if (v.isObject()) { // If `v` is a Window, return the WindowProxy instead. We called // GetThisValue (which also does ToWindowProxyIfWindow) when storing // the value in THIS_VALUE_SLOT, but it's possible the WindowProxy was // attached to the global *after* we set THIS_VALUE_SLOT. return ObjectValue(*ToWindowProxyIfWindow(&v.toObject())); } return v; } const Class BlockObject::class_ = { "Block", JSCLASS_HAS_RESERVED_SLOTS(BlockObject::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS, nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ nullptr, /* trace */ JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, { nullptr, /* lookupProperty */ nullptr, /* defineProperty */ nullptr, /* hasProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* getOwnPropertyDescriptor */ nullptr, /* deleteProperty */ nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ nullptr, /* enumerate (native enumeration of target doesn't work) */ nullptr, } }; template bool js::XDRStaticBlockObject(XDRState* xdr, HandleObject enclosingScope, MutableHandle objp) { /* NB: Keep this in sync with CloneStaticBlockObject. */ JSContext* cx = xdr->cx(); Rooted obj(cx); uint32_t count = 0, offset = 0; uint8_t extensible = 0; if (mode == XDR_ENCODE) { obj = objp; count = obj->numVariables(); offset = obj->localOffset(); extensible = obj->isExtensible() ? 1 : 0; } if (mode == XDR_DECODE) { obj = StaticBlockObject::create(cx); if (!obj) return false; obj->initEnclosingScope(enclosingScope); objp.set(obj); } if (!xdr->codeUint32(&count)) return false; if (!xdr->codeUint32(&offset)) return false; if (!xdr->codeUint8(&extensible)) return false; /* * XDR the block object's properties. We know that there are 'count' * properties to XDR, stored as id/aliased pairs. (The empty string as * id indicates an int id.) */ if (mode == XDR_DECODE) { obj->setLocalOffset(offset); for (unsigned i = 0; i < count; i++) { RootedAtom atom(cx); if (!XDRAtom(xdr, &atom)) return false; RootedId id(cx, atom != cx->runtime()->emptyString ? AtomToId(atom) : INT_TO_JSID(i)); uint32_t propFlags; if (!xdr->codeUint32(&propFlags)) return false; bool readonly = !!(propFlags & 1); bool redeclared; if (!StaticBlockObject::addVar(cx, obj, id, readonly, i, &redeclared)) { MOZ_ASSERT(!redeclared); return false; } bool aliased = !!(propFlags >> 1); obj->setAliased(i, aliased); } if (!extensible) { if (!obj->makeNonExtensible(cx)) return false; } } else { Rooted shapes(cx, ShapeVector(cx)); if (!shapes.growBy(count)) return false; for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) shapes[obj->shapeToIndex(r.front())].set(&r.front()); RootedShape shape(cx); RootedId propid(cx); RootedAtom atom(cx); for (unsigned i = 0; i < count; i++) { shape = shapes[i]; MOZ_ASSERT(shape->hasDefaultGetter()); MOZ_ASSERT(obj->shapeToIndex(*shape) == i); propid = shape->propid(); MOZ_ASSERT(JSID_IS_ATOM(propid) || JSID_IS_INT(propid)); atom = JSID_IS_ATOM(propid) ? JSID_TO_ATOM(propid) : cx->runtime()->emptyString; if (!XDRAtom(xdr, &atom)) return false; bool aliased = obj->isAliased(i); bool readonly = !shape->writable(); uint32_t propFlags = (aliased << 1) | readonly; if (!xdr->codeUint32(&propFlags)) return false; } } return true; } template bool js::XDRStaticBlockObject(XDRState*, HandleObject, MutableHandle); template bool js::XDRStaticBlockObject(XDRState*, HandleObject, MutableHandle); static JSObject* CloneStaticBlockObject(JSContext* cx, HandleObject enclosingScope, Handle srcBlock) { /* NB: Keep this in sync with XDRStaticBlockObject. */ Rooted clone(cx, StaticBlockObject::create(cx)); if (!clone) return nullptr; clone->initEnclosingScope(enclosingScope); clone->setLocalOffset(srcBlock->localOffset()); /* Shape::Range is reverse order, so build a list in forward order. */ Rooted shapes(cx, ShapeVector(cx)); if (!shapes.growBy(srcBlock->numVariables())) return nullptr; for (Shape::Range r(srcBlock->lastProperty()); !r.empty(); r.popFront()) shapes[srcBlock->shapeToIndex(r.front())].set(&r.front()); RootedId id(cx); for (Shape* shape : shapes) { id = shape->propid(); unsigned i = srcBlock->shapeToIndex(*shape); bool redeclared; if (!StaticBlockObject::addVar(cx, clone, id, !shape->writable(), i, &redeclared)) { MOZ_ASSERT(!redeclared); return nullptr; } clone->setAliased(i, srcBlock->isAliased(i)); } if (!srcBlock->isExtensible()) { if (!clone->makeNonExtensible(cx)) return nullptr; } return clone; } JSObject* js::CloneNestedScopeObject(JSContext* cx, HandleObject enclosingScope, Handle srcBlock) { if (srcBlock->is()) { Rooted blockObj(cx, &srcBlock->as()); return CloneStaticBlockObject(cx, enclosingScope, blockObj); } else { Rooted withObj(cx, &srcBlock->as()); return CloneStaticWithObject(cx, enclosingScope, withObj); } } /* static */ RuntimeLexicalErrorObject* RuntimeLexicalErrorObject::create(JSContext* cx, HandleObject enclosing, unsigned errorNumber) { RuntimeLexicalErrorObject* obj = NewObjectWithNullTaggedProto(cx, GenericObject, BaseShape::DELEGATE); if (!obj) return nullptr; obj->setEnclosingScope(enclosing); obj->setReservedSlot(ERROR_SLOT, Int32Value(int32_t(errorNumber))); return obj; } static void ReportRuntimeLexicalErrorId(JSContext* cx, unsigned errorNumber, HandleId id) { if (JSID_IS_ATOM(id)) { RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName()); ReportRuntimeLexicalError(cx, errorNumber, name); return; } MOZ_CRASH("RuntimeLexicalErrorObject should only be used with property names"); } static bool lexicalError_LookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp) { ReportRuntimeLexicalErrorId(cx, obj->as().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; } const Class RuntimeLexicalErrorObject::class_ = { "RuntimeLexicalError", JSCLASS_HAS_RESERVED_SLOTS(RuntimeLexicalErrorObject::RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS, nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ nullptr, /* trace */ JS_NULL_CLASS_SPEC, JS_NULL_CLASS_EXT, { lexicalError_LookupProperty, nullptr, /* defineProperty */ lexicalError_HasProperty, lexicalError_GetProperty, lexicalError_SetProperty, lexicalError_GetOwnPropertyDescriptor, lexicalError_DeleteProperty, nullptr, nullptr, /* watch/unwatch */ nullptr, /* getElements */ nullptr, /* enumerate (native enumeration of target doesn't work) */ nullptr, /* this */ } }; /*****************************************************************************/ // Any name atom for a function which will be added as a DeclEnv object to the // scope chain above call objects for fun. static inline JSAtom* CallObjectLambdaName(JSFunction& fun) { return fun.isNamedLambda() ? fun.atom() : nullptr; } ScopeIter::ScopeIter(JSContext* cx, const ScopeIter& si MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) : ssi_(cx, si.ssi_), scope_(cx, si.scope_), frame_(si.frame_) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } ScopeIter::ScopeIter(JSContext* cx, JSObject* scope, JSObject* staticScope MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) : ssi_(cx, staticScope), scope_(cx, scope), frame_(NullFramePtr()) { settle(); MOZ_GUARD_OBJECT_NOTIFIER_INIT; } ScopeIter::ScopeIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) : ssi_(cx, frame.script()->innermostStaticScope(pc)), scope_(cx, frame.scopeChain()), frame_(frame) { assertSameCompartment(cx, frame); settle(); MOZ_GUARD_OBJECT_NOTIFIER_INIT; } void ScopeIter::incrementStaticScopeIter() { // If settled on a non-syntactic static scope, only increment ssi_ once // we've iterated through all the non-syntactic dynamic ScopeObjects. if (ssi_.type() == StaticScopeIter::NonSyntactic) { if (!hasNonSyntacticScopeObject()) ssi_++; } else { ssi_++; } // For named lambdas, DeclEnvObject scopes are always attached to their // CallObjects. Skip it here, as they are special cased in users of // ScopeIter. if (!ssi_.done() && ssi_.type() == StaticScopeIter::NamedLambda) ssi_++; } void ScopeIter::settle() { // Check for trying to iterate a function frame before the prologue has // created the CallObject, in which case we have to skip. if (frame_ && frame_.isNonEvalFunctionFrame() && frame_.fun()->needsCallObject() && !frame_.hasCallObj()) { MOZ_ASSERT(ssi_.type() == StaticScopeIter::Function); incrementStaticScopeIter(); } // Check for trying to iterate a strict eval frame before the prologue has // created the CallObject. if (frame_ && frame_.isStrictEvalFrame() && !frame_.hasCallObj() && !ssi_.done()) { MOZ_ASSERT(ssi_.type() == StaticScopeIter::Block); incrementStaticScopeIter(); MOZ_ASSERT(ssi_.type() == StaticScopeIter::Eval); MOZ_ASSERT(maybeStaticScope() == frame_.script()->enclosingStaticScope()); incrementStaticScopeIter(); frame_ = NullFramePtr(); } // Check if we have left the extent of the initial frame after we've // settled on a static scope. if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope())) frame_ = NullFramePtr(); #ifdef DEBUG if (!ssi_.done() && hasAnyScopeObject()) { switch (ssi_.type()) { case StaticScopeIter::Module: MOZ_ASSERT(scope_->as().module() == ssi_.module()); break; case StaticScopeIter::Function: MOZ_ASSERT(scope_->as().callee().nonLazyScript() == ssi_.funScript()); break; case StaticScopeIter::Block: MOZ_ASSERT(scope_->as().staticBlock() == staticBlock()); break; case StaticScopeIter::With: MOZ_ASSERT(scope_->as().staticScope() == &staticWith()); break; case StaticScopeIter::Eval: MOZ_ASSERT(scope_->as().isForEval()); break; case StaticScopeIter::NonSyntactic: MOZ_ASSERT(!IsSyntacticScope(scope_)); break; case StaticScopeIter::NamedLambda: MOZ_CRASH("named lambda static scopes should have been skipped"); } } #endif } ScopeIter& ScopeIter::operator++() { if (hasAnyScopeObject()) { scope_ = &scope_->as().enclosingScope(); if (scope_->is()) scope_ = &scope_->as().enclosingScope(); } incrementStaticScopeIter(); settle(); return *this; } ScopeIter::Type ScopeIter::type() const { MOZ_ASSERT(!done()); switch (ssi_.type()) { case StaticScopeIter::Module: return Module; case StaticScopeIter::Function: return Call; case StaticScopeIter::Block: return Block; case StaticScopeIter::With: return With; case StaticScopeIter::Eval: return Eval; case StaticScopeIter::NonSyntactic: return NonSyntactic; case StaticScopeIter::NamedLambda: MOZ_CRASH("named lambda static scopes should have been skipped"); default: MOZ_CRASH("bad SSI type"); } } ScopeObject& ScopeIter::scope() const { MOZ_ASSERT(hasAnyScopeObject()); return scope_->as(); } JSObject* ScopeIter::maybeStaticScope() const { if (ssi_.done()) return nullptr; switch (ssi_.type()) { case StaticScopeIter::Function: return &fun(); case StaticScopeIter::Module: return &module(); case StaticScopeIter::Block: return &staticBlock(); case StaticScopeIter::With: return &staticWith(); case StaticScopeIter::Eval: return &staticEval(); case StaticScopeIter::NonSyntactic: return &staticNonSyntactic(); case StaticScopeIter::NamedLambda: MOZ_CRASH("named lambda static scopes should have been skipped"); default: MOZ_CRASH("bad SSI type"); } } /* static */ HashNumber MissingScopeKey::hash(MissingScopeKey sk) { return size_t(sk.frame_.raw()) ^ size_t(sk.staticScope_); } /* static */ bool MissingScopeKey::match(MissingScopeKey sk1, MissingScopeKey sk2) { return sk1.frame_ == sk2.frame_ && sk1.staticScope_ == sk2.staticScope_; } bool LiveScopeVal::needsSweep() { if (staticScope_) MOZ_ALWAYS_FALSE(IsAboutToBeFinalized(&staticScope_)); return false; } // Live ScopeIter values may be added to DebugScopes::liveScopes, as // LiveScopeVal instances. They need to have write barriers when they are added // to the hash table, but no barriers when rehashing inside GC. It's a nasty // hack, but the important thing is that LiveScopeVal and MissingScopeKey need to // alias each other. void LiveScopeVal::staticAsserts() { static_assert(sizeof(LiveScopeVal) == sizeof(MissingScopeKey), "LiveScopeVal must be same size of MissingScopeKey"); static_assert(offsetof(LiveScopeVal, staticScope_) == offsetof(MissingScopeKey, staticScope_), "LiveScopeVal.staticScope_ must alias MissingScopeKey.staticScope_"); } /*****************************************************************************/ namespace { /* * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a * custom handler (rather than trying to reuse js::Wrapper) gives us several * important abilities: * - We want to pass the ScopeObject as the receiver to forwarded scope * property ops on aliased variables so that Call/Block/With ops do not all * require a 'normalization' step. * - The debug scope proxy can directly manipulate the stack frame to allow * the debugger to read/write args/locals that were otherwise unaliased. * - The debug scope proxy can store unaliased variables after the stack frame * is popped so that they may still be read/written by the debugger. * - The engine has made certain assumptions about the possible reads/writes * in a scope. DebugScopeProxy allows us to prevent the debugger from * breaking those assumptions. * - The engine makes optimizations that are observable to the debugger. The * proxy can either hide these optimizations or make the situation more * clear to the debugger. An example is 'arguments'. */ class DebugScopeProxy : public BaseProxyHandler { enum Action { SET, GET }; enum AccessResult { ACCESS_UNALIASED, ACCESS_GENERIC, ACCESS_LOST }; /* * This function handles access to unaliased locals/formals. Since they are * unaliased, the values of these variables are not stored in the slots of * the normal Call/BlockObject scope objects and thus must be recovered * from somewhere else: * + if the invocation for which the scope was created is still executing, * there is a JS frame live on the stack holding the values; * + if the invocation for which the scope was created finished executing: * - and there was a DebugScopeObject associated with scope, then the * DebugScopes::onPop(Call|Block) handler copied out the unaliased * variables: * . for block scopes, the unaliased values were copied directly * into the block object, since there is a slot allocated for every * block binding, regardless of whether it is aliased; * . for function scopes, a dense array is created in onPopCall to hold * the unaliased values and attached to the DebugScopeObject; * - and there was not a DebugScopeObject yet associated with the * scope, then the unaliased values are lost and not recoverable. * * Callers should check accessResult for non-failure results: * - ACCESS_UNALIASED if the access was unaliased and completed * - ACCESS_GENERIC if the access was aliased or the property not found * - ACCESS_LOST if the value has been lost to the debugger */ bool handleUnaliasedAccess(JSContext* cx, Handle debugScope, Handle scope, HandleId id, Action action, MutableHandleValue vp, AccessResult* accessResult) const { MOZ_ASSERT(&debugScope->scope() == scope); MOZ_ASSERT_IF(action == SET, !debugScope->isOptimizedOut()); *accessResult = ACCESS_GENERIC; LiveScopeVal* maybeLiveScope = DebugScopes::hasLiveScope(*scope); if (scope->is()) { /* Everything is aliased and stored in the environment object. */ return true; } /* Handle unaliased formals, vars, lets, and consts at function scope. */ if (scope->is() && !scope->as().isForEval()) { CallObject& callobj = scope->as(); RootedScript script(cx, callobj.callee().getOrCreateScript(cx)); if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx)) return false; Bindings& bindings = script->bindings; BindingIter bi(script); while (bi && NameToId(bi->name()) != id) bi++; if (!bi) return true; if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) { if (script->bindingIsAliased(bi)) return true; uint32_t i = bi.frameIndex(); if (maybeLiveScope) { AbstractFramePtr frame = maybeLiveScope->frame(); if (action == GET) vp.set(frame.unaliasedLocal(i)); else frame.unaliasedLocal(i) = vp; } else if (NativeObject* snapshot = debugScope->maybeSnapshot()) { if (action == GET) vp.set(snapshot->getDenseElement(bindings.numArgs() + i)); else snapshot->setDenseElement(bindings.numArgs() + i, vp); } else { /* The unaliased value has been lost to the debugger. */ if (action == GET) { *accessResult = ACCESS_LOST; return true; } } } else { MOZ_ASSERT(bi->kind() == Binding::ARGUMENT); unsigned i = bi.argIndex(); if (script->formalIsAliased(i)) return true; if (maybeLiveScope) { AbstractFramePtr frame = maybeLiveScope->frame(); if (script->argsObjAliasesFormals() && frame.hasArgsObj()) { if (action == GET) vp.set(frame.argsObj().arg(i)); else frame.argsObj().setArg(i, vp); } else { if (action == GET) vp.set(frame.unaliasedFormal(i, DONT_CHECK_ALIASING)); else frame.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp; } } else if (NativeObject* snapshot = debugScope->maybeSnapshot()) { if (action == GET) vp.set(snapshot->getDenseElement(i)); else snapshot->setDenseElement(i, vp); } else { /* The unaliased value has been lost to the debugger. */ if (action == GET) { *accessResult = ACCESS_LOST; return true; } } if (action == SET) TypeScript::SetArgument(cx, script, i, vp); } *accessResult = ACCESS_UNALIASED; return true; } /* Handle unaliased let and catch bindings at block scope. */ if (scope->is()) { Rooted block(cx, &scope->as()); Shape* shape = block->lastProperty()->search(cx, id); if (!shape) return true; // Currently consider all global and non-syntactic top-level lexical // bindings to be aliased. if (block->isExtensible()) { MOZ_ASSERT(IsGlobalLexicalScope(block) || !IsSyntacticScope(block)); return true; } unsigned i = block->staticBlock().shapeToIndex(*shape); if (block->staticBlock().isAliased(i)) return true; if (maybeLiveScope) { AbstractFramePtr frame = maybeLiveScope->frame(); uint32_t local = block->staticBlock().blockIndexToLocalIndex(i); MOZ_ASSERT(local < frame.script()->nfixed()); if (action == GET) vp.set(frame.unaliasedLocal(local)); else frame.unaliasedLocal(local) = vp; } else { if (action == GET) { // A ClonedBlockObject whose static block does not need // cloning is a "hollow" block object reflected for // missing block scopes. Their slot values are lost. if (!block->staticBlock().needsClone()) { *accessResult = ACCESS_LOST; return true; } vp.set(block->var(i, DONT_CHECK_ALIASING)); } else { block->setVar(i, vp, DONT_CHECK_ALIASING); } } *accessResult = ACCESS_UNALIASED; return true; } /* The rest of the internal scopes do not have unaliased vars. */ MOZ_ASSERT(scope->is() || scope->is() || scope->as().isForEval()); return true; } static bool isArguments(JSContext* cx, jsid id) { return id == NameToId(cx->names().arguments); } static bool isThis(JSContext* cx, jsid id) { return id == NameToId(cx->names().dotThis); } static bool isFunctionScope(const JSObject& scope) { return scope.is() && !scope.as().isForEval(); } /* * In theory, every function scope contains an 'arguments' bindings. * However, the engine only adds a binding if 'arguments' is used in the * function body. Thus, from the debugger's perspective, 'arguments' may be * missing from the list of bindings. */ static bool isMissingArgumentsBinding(ScopeObject& scope) { return isFunctionScope(scope) && !scope.as().callee().nonLazyScript()->argumentsHasVarBinding(); } /* * Similar to 'arguments' above, we don't add a 'this' binding to functions * if it's not used. */ static bool isMissingThisBinding(ScopeObject& scope) { return isFunctionScopeWithThis(scope) && !scope.as().callee().nonLazyScript()->functionHasThisBinding(); } /* * This function checks if an arguments object needs to be created when * the debugger requests 'arguments' for a function scope where the * arguments object has been optimized away (either because the binding is * missing altogether or because !ScriptAnalysis::needsArgsObj). */ static bool isMissingArguments(JSContext* cx, jsid id, ScopeObject& scope) { return isArguments(cx, id) && isFunctionScope(scope) && !scope.as().callee().nonLazyScript()->needsArgsObj(); } static bool isMissingThis(JSContext* cx, jsid id, ScopeObject& scope) { return isThis(cx, id) && isMissingThisBinding(scope); } /* * Check if the value is the magic value JS_OPTIMIZED_ARGUMENTS. The * arguments analysis may have optimized out the 'arguments', and this * magic value could have propagated to other local slots. e.g., * * function f() { var a = arguments; h(); } * function h() { evalInFrame(1, "a.push(0)"); } * * where evalInFrame(N, str) means to evaluate str N frames up. * * In this case we don't know we need to recover a missing arguments * object until after we've performed the property get. */ static bool isMagicMissingArgumentsValue(JSContext* cx, ScopeObject& scope, HandleValue v) { bool isMagic = v.isMagic() && v.whyMagic() == JS_OPTIMIZED_ARGUMENTS; MOZ_ASSERT_IF(isMagic, isFunctionScope(scope) && scope.as().callee().nonLazyScript()->argumentsHasVarBinding()); return isMagic; } /* * Create a missing arguments object. If the function returns true but * argsObj is null, it means the scope is dead. */ static bool createMissingArguments(JSContext* cx, ScopeObject& scope, MutableHandleArgumentsObject argsObj) { argsObj.set(nullptr); LiveScopeVal* maybeScope = DebugScopes::hasLiveScope(scope); if (!maybeScope) return true; argsObj.set(ArgumentsObject::createUnexpected(cx, maybeScope->frame())); return !!argsObj; } /* * Create a missing this Value. If the function returns true but * *success is false, it means the scope is dead. */ static bool createMissingThis(JSContext* cx, ScopeObject& scope, MutableHandleValue thisv, bool* success) { *success = false; LiveScopeVal* maybeScope = DebugScopes::hasLiveScope(scope); if (!maybeScope) return true; if (!GetFunctionThis(cx, maybeScope->frame(), thisv)) return false; *success = true; return true; } public: static const char family; static const DebugScopeProxy singleton; MOZ_CONSTEXPR DebugScopeProxy() : BaseProxyHandler(&family) {} static bool isFunctionScopeWithThis(const JSObject& scope) { // All functions except arrows and generator expression lambdas should // have their own this binding. return isFunctionScope(scope) && !scope.as().callee().hasLexicalThis(); } bool preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const override { // always [[Extensible]], can't be made non-[[Extensible]], like most // proxies return result.fail(JSMSG_CANT_CHANGE_EXTENSIBILITY); } bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override { // See above. *extensible = true; return true; } bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) const override { return getOwnPropertyDescriptor(cx, proxy, id, desc); } bool getMissingArgumentsPropertyDescriptor(JSContext* cx, Handle debugScope, ScopeObject& scope, MutableHandle desc) const { RootedArgumentsObject argsObj(cx); if (!createMissingArguments(cx, scope, &argsObj)) return false; if (!argsObj) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, "Debugger scope"); return false; } desc.object().set(debugScope); desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); desc.value().setObject(*argsObj); desc.setGetter(nullptr); desc.setSetter(nullptr); return true; } bool getMissingThisPropertyDescriptor(JSContext* cx, Handle debugScope, ScopeObject& scope, MutableHandle desc) const { RootedValue thisv(cx); bool success; if (!createMissingThis(cx, scope, &thisv, &success)) return false; if (!success) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, "Debugger scope"); return false; } desc.object().set(debugScope); desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); desc.value().set(thisv); desc.setGetter(nullptr); desc.setSetter(nullptr); return true; } bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle desc) const override { Rooted debugScope(cx, &proxy->as()); Rooted scope(cx, &debugScope->scope()); if (isMissingArguments(cx, id, *scope)) return getMissingArgumentsPropertyDescriptor(cx, debugScope, *scope, desc); if (isMissingThis(cx, id, *scope)) return getMissingThisPropertyDescriptor(cx, debugScope, *scope, desc); RootedValue v(cx); AccessResult access; if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, &v, &access)) return false; switch (access) { case ACCESS_UNALIASED: if (isMagicMissingArgumentsValue(cx, *scope, v)) return getMissingArgumentsPropertyDescriptor(cx, debugScope, *scope, desc); desc.object().set(debugScope); desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); desc.value().set(v); desc.setGetter(nullptr); desc.setSetter(nullptr); return true; case ACCESS_GENERIC: return JS_GetOwnPropertyDescriptorById(cx, scope, id, desc); case ACCESS_LOST: JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT); return false; default: MOZ_CRASH("bad AccessResult"); } } bool getMissingArguments(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const { RootedArgumentsObject argsObj(cx); if (!createMissingArguments(cx, scope, &argsObj)) return false; if (!argsObj) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, "Debugger scope"); return false; } vp.setObject(*argsObj); return true; } bool getMissingThis(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const { RootedValue thisv(cx); bool success; if (!createMissingThis(cx, scope, &thisv, &success)) return false; if (!success) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, "Debugger scope"); return false; } vp.set(thisv); return true; } bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, MutableHandleValue vp) const override { Rooted debugScope(cx, &proxy->as()); Rooted scope(cx, &proxy->as().scope()); if (isMissingArguments(cx, id, *scope)) return getMissingArguments(cx, *scope, vp); if (isMissingThis(cx, id, *scope)) return getMissingThis(cx, *scope, vp); AccessResult access; if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access)) return false; switch (access) { case ACCESS_UNALIASED: if (isMagicMissingArgumentsValue(cx, *scope, vp)) return getMissingArguments(cx, *scope, vp); return true; case ACCESS_GENERIC: return GetProperty(cx, scope, scope, id, vp); case ACCESS_LOST: JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT); return false; default: MOZ_CRASH("bad AccessResult"); } } bool getMissingArgumentsMaybeSentinelValue(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const { RootedArgumentsObject argsObj(cx); if (!createMissingArguments(cx, scope, &argsObj)) return false; vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_OPTIMIZED_ARGUMENTS)); return true; } bool getMissingThisMaybeSentinelValue(JSContext* cx, ScopeObject& scope, MutableHandleValue vp) const { RootedValue thisv(cx); bool success; if (!createMissingThis(cx, scope, &thisv, &success)) return false; vp.set(success ? thisv : MagicValue(JS_OPTIMIZED_OUT)); return true; } /* * Like 'get', but returns sentinel values instead of throwing on * exceptional cases. */ bool getMaybeSentinelValue(JSContext* cx, Handle debugScope, HandleId id, MutableHandleValue vp) const { Rooted scope(cx, &debugScope->scope()); if (isMissingArguments(cx, id, *scope)) return getMissingArgumentsMaybeSentinelValue(cx, *scope, vp); if (isMissingThis(cx, id, *scope)) return getMissingThisMaybeSentinelValue(cx, *scope, vp); AccessResult access; if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access)) return false; switch (access) { case ACCESS_UNALIASED: if (isMagicMissingArgumentsValue(cx, *scope, vp)) return getMissingArgumentsMaybeSentinelValue(cx, *scope, vp); return true; case ACCESS_GENERIC: return GetProperty(cx, scope, scope, id, vp); case ACCESS_LOST: vp.setMagic(JS_OPTIMIZED_OUT); return true; default: MOZ_CRASH("bad AccessResult"); } } bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const override { Rooted debugScope(cx, &proxy->as()); Rooted scope(cx, &proxy->as().scope()); if (debugScope->isOptimizedOut()) return Throw(cx, id, JSMSG_DEBUG_CANT_SET_OPT_ENV); AccessResult access; RootedValue valCopy(cx, v); if (!handleUnaliasedAccess(cx, debugScope, scope, id, SET, &valCopy, &access)) return false; switch (access) { case ACCESS_UNALIASED: return result.succeed(); case ACCESS_GENERIC: { RootedValue scopeVal(cx, ObjectValue(*scope)); return SetProperty(cx, scope, id, v, scopeVal, result); } default: MOZ_CRASH("bad AccessResult"); } } bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle desc, ObjectOpResult& result) const override { Rooted scope(cx, &proxy->as().scope()); bool found; if (!has(cx, proxy, id, &found)) return false; if (found) return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP); return JS_DefinePropertyById(cx, scope, id, desc, result); } bool ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props) const override { Rooted scope(cx, &proxy->as().scope()); if (isMissingArgumentsBinding(*scope)) { if (!props.append(NameToId(cx->names().arguments))) return false; } if (isMissingThisBinding(*scope)) { if (!props.append(NameToId(cx->names().dotThis))) return false; } // DynamicWithObject isn't a very good proxy. It doesn't have a // JSNewEnumerateOp implementation, because if it just delegated to the // target object, the object would indicate that native enumeration is // the thing to do, but native enumeration over the DynamicWithObject // wrapper yields no properties. So instead here we hack around the // issue, and punch a hole through to the with object target. Rooted target(cx, (scope->is() ? &scope->as().object() : scope)); if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props)) return false; /* * Function scopes are optimized to not contain unaliased variables so * they must be manually appended here. */ if (isFunctionScope(*scope)) { RootedScript script(cx, scope->as().callee().nonLazyScript()); for (BindingIter bi(script); bi; bi++) { if (!bi->aliased() && !props.append(NameToId(bi->name()))) return false; } } return true; } bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const override { return BaseProxyHandler::enumerate(cx, proxy, objp); } bool has(JSContext* cx, HandleObject proxy, HandleId id_, bool* bp) const override { RootedId id(cx, id_); ScopeObject& scopeObj = proxy->as().scope(); if (isArguments(cx, id) && isFunctionScope(scopeObj)) { *bp = true; return true; } if (isThis(cx, id) && isFunctionScopeWithThis(scopeObj)) { *bp = true; return true; } bool found; RootedObject scope(cx, &scopeObj); if (!JS_HasPropertyById(cx, scope, id, &found)) return false; /* * Function scopes are optimized to not contain unaliased variables so * a manual search is necessary. */ if (!found && isFunctionScope(*scope)) { RootedScript script(cx, scope->as().callee().nonLazyScript()); for (BindingIter bi(script); bi; bi++) { if (!bi->aliased() && NameToId(bi->name()) == id) { found = true; break; } } } *bp = found; return true; } bool delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result) const override { return result.fail(JSMSG_CANT_DELETE); } }; } /* anonymous namespace */ template<> bool JSObject::is() const { return IsDerivedProxyObject(this, &DebugScopeProxy::singleton); } const char DebugScopeProxy::family = 0; const DebugScopeProxy DebugScopeProxy::singleton; /* static */ DebugScopeObject* DebugScopeObject::create(JSContext* cx, ScopeObject& scope, HandleObject enclosing) { MOZ_ASSERT(scope.compartment() == cx->compartment()); MOZ_ASSERT(!enclosing->is()); RootedValue priv(cx, ObjectValue(scope)); JSObject* obj = NewProxyObject(cx, &DebugScopeProxy::singleton, priv, nullptr /* proto */); if (!obj) return nullptr; DebugScopeObject* debugScope = &obj->as(); debugScope->setExtra(ENCLOSING_EXTRA, ObjectValue(*enclosing)); debugScope->setExtra(SNAPSHOT_EXTRA, NullValue()); return debugScope; } ScopeObject& DebugScopeObject::scope() const { return target()->as(); } JSObject& DebugScopeObject::enclosingScope() const { return extra(ENCLOSING_EXTRA).toObject(); } ArrayObject* DebugScopeObject::maybeSnapshot() const { MOZ_ASSERT(!scope().as().isForEval()); JSObject* obj = extra(SNAPSHOT_EXTRA).toObjectOrNull(); return obj ? &obj->as() : nullptr; } void DebugScopeObject::initSnapshot(ArrayObject& o) { MOZ_ASSERT(maybeSnapshot() == nullptr); setExtra(SNAPSHOT_EXTRA, ObjectValue(o)); } bool DebugScopeObject::isForDeclarative() const { ScopeObject& s = scope(); return s.is() || s.is() || s.is(); } bool DebugScopeObject::getMaybeSentinelValue(JSContext* cx, HandleId id, MutableHandleValue vp) { Rooted self(cx, this); return DebugScopeProxy::singleton.getMaybeSentinelValue(cx, self, id, vp); } bool DebugScopeObject::isFunctionScopeWithThis() { return DebugScopeProxy::isFunctionScopeWithThis(scope()); } bool DebugScopeObject::isOptimizedOut() const { ScopeObject& s = scope(); if (DebugScopes::hasLiveScope(s)) return false; if (s.is()) return !s.as().staticBlock().needsClone(); if (s.is()) { return !s.as().isForEval() && !s.as().callee().needsCallObject() && !maybeSnapshot(); } return false; } /*****************************************************************************/ DebugScopes::DebugScopes(JSContext* cx) : proxiedScopes(cx), missingScopes(cx->runtime()), liveScopes(cx->runtime()) {} DebugScopes::~DebugScopes() { MOZ_ASSERT(missingScopes.empty()); } bool DebugScopes::init() { return proxiedScopes.init() && missingScopes.init() && liveScopes.init(); } void DebugScopes::mark(JSTracer* trc) { proxiedScopes.trace(trc); } void DebugScopes::sweep(JSRuntime* rt) { /* * missingScopes points to debug scopes weakly so that debug scopes can be * released more eagerly. */ for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) { if (IsAboutToBeFinalized(&e.front().value())) { /* * Note that onPopCall and onPopBlock rely on missingScopes to find * scope objects that we synthesized for the debugger's sake, and * clean up the synthetic scope objects' entries in liveScopes. So * if we remove an entry frcom missingScopes here, we must also * remove the corresponding liveScopes entry. * * Since the DebugScopeObject is the only thing using its scope * object, and the DSO is about to be finalized, you might assume * that the synthetic SO is also about to be finalized too, and thus * the loop below will take care of things. But complex GC behavior * means that marks are only conservative approximations of * liveness; we should assume that anything could be marked. * * Thus, we must explicitly remove the entries from both liveScopes * and missingScopes here. */ liveScopes.remove(&e.front().value().unbarrieredGet()->scope()); e.removeFront(); } else { MissingScopeKey key = e.front().key(); if (IsForwarded(key.staticScope())) { key.updateStaticScope(Forwarded(key.staticScope())); e.rekeyFront(key); } } } /* * Scopes can be finalized when a debugger-synthesized ScopeObject is * no longer reachable via its DebugScopeObject. */ liveScopes.sweep(); } #ifdef JSGC_HASH_TABLE_CHECKS void DebugScopes::checkHashTablesAfterMovingGC(JSRuntime* runtime) { /* * This is called at the end of StoreBuffer::mark() to check that our * postbarriers have worked and that no hashtable keys (or values) are left * pointing into the nursery. */ proxiedScopes.checkAfterMovingGC(); for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) { CheckGCThingAfterMovingGC(r.front().key().staticScope()); CheckGCThingAfterMovingGC(r.front().value().get()); } for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) { CheckGCThingAfterMovingGC(r.front().key()); CheckGCThingAfterMovingGC(r.front().value().staticScope_.get()); } } #endif /* * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode * (in particular, JS_GetFrameScopeChain does not require debug mode). Since * DebugScopes::onPop* are only called in debuggee frames, this means we * cannot use any of the maps in DebugScopes. This will produce debug scope * chains that do not obey the debugger invariants but that is just fine. */ static bool CanUseDebugScopeMaps(JSContext* cx) { return cx->compartment()->isDebuggee(); } DebugScopes* DebugScopes::ensureCompartmentData(JSContext* cx) { JSCompartment* c = cx->compartment(); if (c->debugScopes) return c->debugScopes; auto debugScopes = cx->make_unique(cx); if (!debugScopes || !debugScopes->init()) { ReportOutOfMemory(cx); return nullptr; } c->debugScopes = debugScopes.release(); return c->debugScopes; } DebugScopeObject* DebugScopes::hasDebugScope(JSContext* cx, ScopeObject& scope) { DebugScopes* scopes = scope.compartment()->debugScopes; if (!scopes) return nullptr; if (JSObject* obj = scopes->proxiedScopes.lookup(&scope)) { MOZ_ASSERT(CanUseDebugScopeMaps(cx)); return &obj->as(); } return nullptr; } bool DebugScopes::addDebugScope(JSContext* cx, ScopeObject& scope, DebugScopeObject& debugScope) { MOZ_ASSERT(cx->compartment() == scope.compartment()); MOZ_ASSERT(cx->compartment() == debugScope.compartment()); if (!CanUseDebugScopeMaps(cx)) return true; DebugScopes* scopes = ensureCompartmentData(cx); if (!scopes) return false; return scopes->proxiedScopes.add(cx, &scope, &debugScope); } DebugScopeObject* DebugScopes::hasDebugScope(JSContext* cx, const ScopeIter& si) { MOZ_ASSERT(!si.hasSyntacticScopeObject()); DebugScopes* scopes = cx->compartment()->debugScopes; if (!scopes) return nullptr; if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) { MOZ_ASSERT(CanUseDebugScopeMaps(cx)); return p->value(); } return nullptr; } bool DebugScopes::addDebugScope(JSContext* cx, const ScopeIter& si, DebugScopeObject& debugScope) { MOZ_ASSERT(!si.hasSyntacticScopeObject()); MOZ_ASSERT(cx->compartment() == debugScope.compartment()); // Generators should always reify their scopes. MOZ_ASSERT_IF(si.type() == ScopeIter::Call, !si.fun().isGenerator()); if (!CanUseDebugScopeMaps(cx)) return true; DebugScopes* scopes = ensureCompartmentData(cx); if (!scopes) return false; MissingScopeKey key(si); MOZ_ASSERT(!scopes->missingScopes.has(key)); if (!scopes->missingScopes.put(key, ReadBarriered(&debugScope))) { ReportOutOfMemory(cx); return false; } // Only add to liveScopes if we synthesized the debug scope on a live // frame. if (si.withinInitialFrame()) { MOZ_ASSERT(!scopes->liveScopes.has(&debugScope.scope())); if (!scopes->liveScopes.put(&debugScope.scope(), LiveScopeVal(si))) { ReportOutOfMemory(cx); return false; } } return true; } void DebugScopes::onPopCall(AbstractFramePtr frame, JSContext* cx) { assertSameCompartment(cx, frame); DebugScopes* scopes = cx->compartment()->debugScopes; if (!scopes) return; Rooted debugScope(cx, nullptr); if (frame.fun()->needsCallObject()) { /* * The frame may be observed before the prologue has created the * CallObject. See ScopeIter::settle. */ if (!frame.hasCallObj()) return; if (frame.fun()->isGenerator()) return; CallObject& callobj = frame.scopeChain()->as(); scopes->liveScopes.remove(&callobj); if (JSObject* obj = scopes->proxiedScopes.lookup(&callobj)) debugScope = &obj->as(); } else { ScopeIter si(cx, frame, frame.script()->main()); if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) { debugScope = p->value(); scopes->liveScopes.remove(&debugScope->scope().as()); scopes->missingScopes.remove(p); } } /* * When the JS stack frame is popped, the values of unaliased variables * are lost. If there is any debug scope referring to this scope, save a * copy of the unaliased variables' values in an array for later debugger * access via DebugScopeProxy::handleUnaliasedAccess. * * Note: since it is simplest for this function to be infallible, failure * in this code will be silently ignored. This does not break any * invariants since DebugScopeObject::maybeSnapshot can already be nullptr. */ if (debugScope) { /* * Copy all frame values into the snapshot, regardless of * aliasing. This unnecessarily includes aliased variables * but it simplifies later indexing logic. */ AutoValueVector vec(cx); if (!frame.copyRawFrameSlots(&vec) || vec.length() == 0) return; /* * Copy in formals that are not aliased via the scope chain * but are aliased via the arguments object. */ RootedScript script(cx, frame.script()); if (script->analyzedArgsUsage() && script->needsArgsObj() && frame.hasArgsObj()) { for (unsigned i = 0; i < frame.numFormalArgs(); ++i) { if (script->formalLivesInArgumentsObject(i)) vec[i].set(frame.argsObj().arg(i)); } } /* * Use a dense array as storage (since proxies do not have trace * hooks). This array must not escape into the wild. */ RootedArrayObject snapshot(cx, NewDenseCopiedArray(cx, vec.length(), vec.begin())); if (!snapshot) { cx->clearPendingException(); return; } debugScope->initSnapshot(*snapshot); } } void DebugScopes::onPopBlock(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc) { assertSameCompartment(cx, frame); DebugScopes* scopes = cx->compartment()->debugScopes; if (!scopes) return; ScopeIter si(cx, frame, pc); onPopBlock(cx, si); } void DebugScopes::onPopBlock(JSContext* cx, const ScopeIter& si) { DebugScopes* scopes = cx->compartment()->debugScopes; if (!scopes) return; MOZ_ASSERT(si.withinInitialFrame()); MOZ_ASSERT(si.type() == ScopeIter::Block); if (si.staticBlock().needsClone()) { ClonedBlockObject& clone = si.scope().as(); clone.copyUnaliasedValues(si.initialFrame()); scopes->liveScopes.remove(&clone); } else { if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) { ClonedBlockObject& clone = p->value()->scope().as(); clone.copyUnaliasedValues(si.initialFrame()); scopes->liveScopes.remove(&clone); scopes->missingScopes.remove(p); } } } void DebugScopes::onPopWith(AbstractFramePtr frame) { DebugScopes* scopes = frame.compartment()->debugScopes; if (scopes) scopes->liveScopes.remove(&frame.scopeChain()->as()); } void DebugScopes::onPopStrictEvalScope(AbstractFramePtr frame) { DebugScopes* scopes = frame.compartment()->debugScopes; if (!scopes) return; /* * The stack frame may be observed before the prologue has created the * CallObject. See ScopeIter::settle. */ if (frame.hasCallObj()) scopes->liveScopes.remove(&frame.scopeChain()->as()); } void DebugScopes::onCompartmentUnsetIsDebuggee(JSCompartment* c) { DebugScopes* scopes = c->debugScopes; if (scopes) { scopes->proxiedScopes.clear(); scopes->missingScopes.clear(); scopes->liveScopes.clear(); } } bool DebugScopes::updateLiveScopes(JSContext* cx) { JS_CHECK_RECURSION(cx, return false); /* * Note that we must always update the top frame's scope objects' entries * in liveScopes because we can't be sure code hasn't run in that frame to * change the scope chain since we were last called. The fp->prevUpToDate() * flag indicates whether the scopes of frames older than fp are already * included in liveScopes. It might seem simpler to have fp instead carry a * flag indicating whether fp itself is accurately described, but then we * would need to clear that flag whenever fp ran code. By storing the 'up * to date' bit for fp->prev() in fp, simply popping fp effectively clears * the flag for us, at exactly the time when execution resumes fp->prev(). */ for (AllFramesIter i(cx); !i.done(); ++i) { if (!i.hasUsableAbstractFramePtr()) continue; AbstractFramePtr frame = i.abstractFramePtr(); if (frame.scopeChain()->compartment() != cx->compartment()) continue; if (frame.isFunctionFrame() && frame.callee()->isGenerator()) continue; if (!frame.isDebuggee()) continue; for (ScopeIter si(cx, frame, i.pc()); si.withinInitialFrame(); ++si) { if (si.hasSyntacticScopeObject()) { MOZ_ASSERT(si.scope().compartment() == cx->compartment()); DebugScopes* scopes = ensureCompartmentData(cx); if (!scopes) return false; if (!scopes->liveScopes.put(&si.scope(), LiveScopeVal(si))) return false; } } if (frame.prevUpToDate()) return true; MOZ_ASSERT(frame.scopeChain()->compartment()->isDebuggee()); frame.setPrevUpToDate(); } return true; } LiveScopeVal* DebugScopes::hasLiveScope(ScopeObject& scope) { DebugScopes* scopes = scope.compartment()->debugScopes; if (!scopes) return nullptr; if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope)) return &p->value(); return nullptr; } /* static */ void DebugScopes::unsetPrevUpToDateUntil(JSContext* cx, AbstractFramePtr until) { // This are two exceptions where fp->prevUpToDate() is cleared without // popping the frame. When a frame is rematerialized or has its // debuggeeness toggled off->on, all frames younger than the frame must // have their prevUpToDate set to false. This is because unrematerialized // Ion frames and non-debuggee frames are skipped by updateLiveScopes. If // in the future a frame suddenly gains a usable AbstractFramePtr via // rematerialization or becomes a debuggee, the prevUpToDate invariant // will no longer hold for older frames on its stack. for (AllFramesIter i(cx); !i.done(); ++i) { if (!i.hasUsableAbstractFramePtr()) continue; AbstractFramePtr frame = i.abstractFramePtr(); if (frame == until) return; if (frame.scopeChain()->compartment() != cx->compartment()) continue; frame.unsetPrevUpToDate(); } } /* static */ void DebugScopes::forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to) { DebugScopes* scopes = cx->compartment()->debugScopes; if (!scopes) return; for (MissingScopeMap::Enum e(scopes->missingScopes); !e.empty(); e.popFront()) { MissingScopeKey key = e.front().key(); if (key.frame() == from) { key.updateFrame(to); e.rekeyFront(key); } } for (LiveScopeMap::Enum e(scopes->liveScopes); !e.empty(); e.popFront()) { LiveScopeVal& val = e.front().value(); if (val.frame() == from) val.updateFrame(to); } } /*****************************************************************************/ static JSObject* GetDebugScope(JSContext* cx, const ScopeIter& si); static DebugScopeObject* GetDebugScopeForScope(JSContext* cx, const ScopeIter& si) { Rooted scope(cx, &si.scope()); if (DebugScopeObject* debugScope = DebugScopes::hasDebugScope(cx, *scope)) return debugScope; ScopeIter copy(cx, si); RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy)); if (!enclosingDebug) return nullptr; JSObject& maybeDecl = scope->enclosingScope(); if (maybeDecl.is()) { MOZ_ASSERT(CallObjectLambdaName(scope->as().callee())); enclosingDebug = DebugScopeObject::create(cx, maybeDecl.as(), enclosingDebug); if (!enclosingDebug) return nullptr; } DebugScopeObject* debugScope = DebugScopeObject::create(cx, *scope, enclosingDebug); if (!debugScope) return nullptr; if (!DebugScopes::addDebugScope(cx, *scope, *debugScope)) return nullptr; return debugScope; } static DebugScopeObject* GetDebugScopeForMissing(JSContext* cx, const ScopeIter& si) { MOZ_ASSERT(!si.hasSyntacticScopeObject() && si.canHaveSyntacticScopeObject()); if (DebugScopeObject* debugScope = DebugScopes::hasDebugScope(cx, si)) return debugScope; ScopeIter copy(cx, si); RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy)); if (!enclosingDebug) return nullptr; /* * Create the missing scope object. For block objects, this takes care of * storing variable values after the stack frame has been popped. For call * objects, we only use the pretend call object to access callee, bindings * and to receive dynamically added properties. Together, this provides the * nice invariant that every DebugScopeObject has a ScopeObject. * * Note: to preserve scopeChain depth invariants, these lazily-reified * scopes must not be put on the frame's scope chain; instead, they are * maintained via DebugScopes hooks. */ DebugScopeObject* debugScope = nullptr; switch (si.type()) { case ScopeIter::Module: MOZ_CRASH(); // TODO: Implement debug scopes for modules. break; case ScopeIter::Call: { RootedFunction callee(cx, &si.fun()); // Generators should always reify their scopes. MOZ_ASSERT(!callee->isGenerator()); Rooted callobj(cx); if (si.withinInitialFrame()) callobj = CallObject::createForFunction(cx, si.initialFrame()); else callobj = CallObject::createHollowForDebug(cx, callee); if (!callobj) return nullptr; if (callobj->enclosingScope().is()) { MOZ_ASSERT(CallObjectLambdaName(callobj->callee())); DeclEnvObject& declenv = callobj->enclosingScope().as(); enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug); if (!enclosingDebug) return nullptr; } debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug); break; } case ScopeIter::Block: { // Generators should always reify their scopes, except in this one // weird case of deprecated let expressions where we can create a // 0-variable StaticBlockObject inside a generator that does not need // cloning. // // For example, |let ({} = "") { yield evalInFrame("foo"); }|. MOZ_ASSERT_IF(si.staticBlock().numVariables() > 0 && si.withinInitialFrame() && si.initialFrame().isFunctionFrame(), !si.initialFrame().callee()->isGenerator()); Rooted staticBlock(cx, &si.staticBlock()); ClonedBlockObject* block; if (si.withinInitialFrame()) block = ClonedBlockObject::create(cx, staticBlock, si.initialFrame()); else block = ClonedBlockObject::createHollowForDebug(cx, staticBlock); if (!block) return nullptr; debugScope = DebugScopeObject::create(cx, *block, enclosingDebug); break; } case ScopeIter::With: case ScopeIter::Eval: MOZ_CRASH("should already have a scope"); case ScopeIter::NonSyntactic: MOZ_CRASH("non-syntactic scopes cannot be synthesized"); } if (!debugScope) return nullptr; if (!DebugScopes::addDebugScope(cx, si, *debugScope)) return nullptr; return debugScope; } static JSObject* GetDebugScopeForNonScopeObject(const ScopeIter& si) { JSObject& enclosing = si.enclosingScope(); MOZ_ASSERT(!enclosing.is()); #ifdef DEBUG JSObject* o = &enclosing; while ((o = o->enclosingScope())) MOZ_ASSERT(!o->is()); #endif return &enclosing; } static JSObject* GetDebugScope(JSContext* cx, const ScopeIter& si) { JS_CHECK_RECURSION(cx, return nullptr); if (si.done()) return GetDebugScopeForNonScopeObject(si); if (si.hasAnyScopeObject()) return GetDebugScopeForScope(cx, si); if (si.canHaveSyntacticScopeObject()) return GetDebugScopeForMissing(cx, si); ScopeIter copy(cx, si); return GetDebugScope(cx, ++copy); } JSObject* js::GetDebugScopeForFunction(JSContext* cx, HandleFunction fun) { assertSameCompartment(cx, fun); MOZ_ASSERT(CanUseDebugScopeMaps(cx)); if (!DebugScopes::updateLiveScopes(cx)) return nullptr; JSScript* script = fun->getOrCreateScript(cx); if (!script) return nullptr; ScopeIter si(cx, fun->environment(), script->enclosingStaticScope()); return GetDebugScope(cx, si); } JSObject* js::GetDebugScopeForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc) { assertSameCompartment(cx, frame); if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx)) return nullptr; ScopeIter si(cx, frame, pc); return GetDebugScope(cx, si); } JSObject* js::GetDebugScopeForGlobalLexicalScope(JSContext* cx) { ScopeIter si(cx, &cx->global()->lexicalScope(), &cx->global()->lexicalScope().staticBlock()); return GetDebugScope(cx, si); } // See declaration and documentation in jsfriendapi.h JS_FRIEND_API(JSObject*) js::GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun) { if (!fun->isInterpreted()) return &fun->global(); JSObject* env = fun->environment(); while (env && !env->is()) env = env->enclosingScope(); if (!env) return &fun->global(); return &env->as().object(); } bool js::CreateScopeObjectsForScopeChain(JSContext* cx, AutoObjectVector& scopeChain, HandleObject dynamicTerminatingScope, MutableHandleObject dynamicScopeObj) { #ifdef DEBUG for (size_t i = 0; i < scopeChain.length(); ++i) { assertSameCompartment(cx, scopeChain[i]); MOZ_ASSERT(!scopeChain[i]->is()); } #endif // Construct With object wrappers for the things on this scope // chain and use the result as the thing to scope the function to. Rooted staticWith(cx); RootedObject staticEnclosingScope(cx); Rooted dynamicWith(cx); RootedObject dynamicEnclosingScope(cx, dynamicTerminatingScope); for (size_t i = scopeChain.length(); i > 0; ) { staticWith = StaticWithObject::create(cx); if (!staticWith) return false; staticWith->initEnclosingScope(staticEnclosingScope); staticEnclosingScope = staticWith; dynamicWith = DynamicWithObject::create(cx, scopeChain[--i], dynamicEnclosingScope, staticWith, DynamicWithObject::NonSyntacticWith); if (!dynamicWith) return false; dynamicEnclosingScope = dynamicWith; } dynamicScopeObj.set(dynamicEnclosingScope); return true; } bool js::HasNonSyntacticStaticScopeChain(JSObject* staticScope) { for (StaticScopeIter ssi(staticScope); !ssi.done(); ssi++) { // If we hit a function scope, we can short circuit the logic, as // scripts cache whether they are under a non-syntactic scope. if (ssi.type() == StaticScopeIter::Function) return ssi.funScript()->hasNonSyntacticScope(); if (ssi.type() == StaticScopeIter::NonSyntactic) return true; } return false; } uint32_t js::StaticScopeChainLength(JSObject* staticScope) { uint32_t length = 0; for (StaticScopeIter ssi(staticScope); !ssi.done(); ssi++) length++; return length; } ModuleEnvironmentObject* js::GetModuleEnvironmentForScript(JSScript* script) { StaticScopeIter ssi(script->enclosingStaticScope()); while (!ssi.done() && ssi.type() != StaticScopeIter::Module) ssi++; if (ssi.done()) return nullptr; return ssi.module().environment(); } bool js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, MutableHandleValue res) { for (ScopeIter si(cx, frame, pc); !si.done(); ++si) { if (si.type() == ScopeIter::Module) { res.setUndefined(); return true; } if (si.type() != ScopeIter::Call || si.fun().hasLexicalThis()) continue; RootedScript script(cx, si.fun().nonLazyScript()); if (!script->functionHasThisBinding()) { MOZ_ASSERT(!script->isDerivedClassConstructor(), "Derived class constructors always have a this-binding"); // If we're still inside `frame`, we can use the this-value passed // to it, if it does not require boxing. if (si.withinInitialFrame() && (frame.thisArgument().isObject() || script->strict())) res.set(frame.thisArgument()); else res.setMagic(JS_OPTIMIZED_OUT); return true; } BindingIter bi = Bindings::thisBinding(cx, script); if (script->bindingIsAliased(bi)) { RootedObject callObj(cx, &si.scope().as()); return GetProperty(cx, callObj, callObj, cx->names().dotThis, res); } if (si.withinInitialFrame()) res.set(frame.unaliasedLocal(bi.frameIndex())); else res.setMagic(JS_OPTIMIZED_OUT); return true; } RootedObject scopeChain(cx, frame.scopeChain()); return GetNonSyntacticGlobalThis(cx, scopeChain, res); } bool js::CheckLexicalNameConflict(JSContext* cx, Handle lexicalScope, HandleObject varObj, HandlePropertyName name) { mozilla::Maybe redeclKind; RootedId id(cx, NameToId(name)); RootedShape shape(cx); if ((shape = lexicalScope->lookup(cx, name))) { redeclKind = mozilla::Some(shape->writable() ? frontend::Definition::LET : frontend::Definition::CONSTANT); } else if (varObj->isNative() && (shape = varObj->as().lookup(cx, name))) { if (!shape->configurable()) redeclKind = mozilla::Some(frontend::Definition::VAR); } else { Rooted desc(cx); if (!GetOwnPropertyDescriptor(cx, varObj, id, &desc)) return false; if (desc.object() && desc.hasConfigurable() && !desc.configurable()) redeclKind = mozilla::Some(frontend::Definition::VAR); } if (redeclKind.isSome()) { ReportRuntimeRedeclaration(cx, name, *redeclKind); return false; } return true; } bool js::CheckVarNameConflict(JSContext* cx, Handle lexicalScope, HandlePropertyName name) { if (Shape* shape = lexicalScope->lookup(cx, name)) { ReportRuntimeRedeclaration(cx, name, shape->writable() ? frontend::Definition::LET : frontend::Definition::CONSTANT); return false; } return true; } static bool CheckVarNameConflict(JSContext* cx, Handle callObj, HandlePropertyName name) { RootedFunction fun(cx, &callObj->callee()); RootedScript script(cx, fun->nonLazyScript()); uint32_t bodyLevelLexicalsStart = script->bindings.numVars(); for (BindingIter bi(script); !bi.done(); bi++) { if (name == bi->name() && bi.isBodyLevelLexical() && bi.localIndex() >= bodyLevelLexicalsStart) { ReportRuntimeRedeclaration(cx, name, bi->kind() == Binding::CONSTANT ? frontend::Definition::CONSTANT : frontend::Definition::LET); return false; } } return true; } bool js::CheckGlobalDeclarationConflicts(JSContext* cx, HandleScript script, Handle lexicalScope, HandleObject varObj) { // Due to the extensibility of the global lexical scope, we must check for // redeclaring a binding. // // In the case of non-syntactic scope chains, we are checking // redeclarations against the non-syntactic lexical scope and the // variables object that the lexical scope corresponds to. RootedPropertyName name(cx); BindingIter bi(script); for (uint32_t i = 0; i < script->bindings.numVars(); i++, bi++) { name = bi->name(); if (!CheckVarNameConflict(cx, lexicalScope, name)) return false; } for (uint32_t i = 0; i < script->bindings.numBodyLevelLexicals(); i++, bi++) { name = bi->name(); if (!CheckLexicalNameConflict(cx, lexicalScope, varObj, name)) return false; } return true; } template static bool CheckVarNameConflictsInScope(JSContext* cx, HandleScript script, HandleObject obj) { Rooted scope(cx); // We return true when the scope object is not ScopeT below, because // ScopeT is either ClonedBlockObject or CallObject. No other scope // objects can contain lexical bindings, and there are no other overloads // for CheckVarNameConflict. if (obj->is()) scope = &obj->as(); else if (obj->is() && obj->as().scope().is()) scope = &obj->as().scope().as(); else return true; RootedPropertyName name(cx); for (BindingIter bi(script); !bi.done(); bi++) { name = bi->name(); if (!CheckVarNameConflict(cx, scope, name)) return false; } return true; } bool js::CheckEvalDeclarationConflicts(JSContext* cx, HandleScript script, HandleObject scopeChain, HandleObject varObj) { // We don't need to check body-level lexical bindings for conflict. Eval // scripts always execute under their own lexical scope. if (script->bindings.numVars() == 0) return true; RootedObject obj(cx, scopeChain); // ES6 18.2.1.2 step d // // Check that a direct eval will not hoist 'var' bindings over lexical // bindings with the same name. while (obj != varObj) { if (!CheckVarNameConflictsInScope(cx, script, obj)) return false; obj = obj->enclosingScope(); } return CheckVarNameConflictsInScope(cx, script, varObj); } #ifdef DEBUG void js::DumpStaticScopeChain(JSScript* script) { DumpStaticScopeChain(script->enclosingStaticScope()); } void js::DumpStaticScopeChain(JSObject* staticScope) { for (StaticScopeIter ssi(staticScope); !ssi.done(); ssi++) { switch (ssi.type()) { case StaticScopeIter::Module: fprintf(stdout, "module [%p]", &ssi.module()); break; case StaticScopeIter::Function: if (ssi.fun().isBeingParsed()) fprintf(stdout, "funbox [%p fun=%p]", ssi.maybeFunctionBox(), &ssi.fun()); else fprintf(stdout, "function [%p]", &ssi.fun()); break; case StaticScopeIter::Block: fprintf(stdout, "block [%p]", &ssi.block()); break; case StaticScopeIter::With: fprintf(stdout, "with [%p]", &ssi.staticWith()); break; case StaticScopeIter::NamedLambda: fprintf(stdout, "named lambda"); break; case StaticScopeIter::Eval: fprintf(stdout, "eval [%p]", &ssi.eval()); break; case StaticScopeIter::NonSyntactic: fprintf(stdout, "non-syntactic [%p]", &ssi.nonSyntactic()); break; } fprintf(stdout, " -> "); } fprintf(stdout, "global\n"); } 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. for (jsbytecode* pc = script->code(); pc != script->codeEnd(); pc += GetBytecodeLength(pc)) { PropertyName* name; switch (JSOp(*pc)) { case JSOP_GETNAME: case JSOP_SETNAME: case JSOP_STRICTSETNAME: name = script->getName(pc); break; case JSOP_GETGNAME: case JSOP_SETGNAME: case JSOP_STRICTSETGNAME: if (script->hasNonSyntacticScope()) name = script->getName(pc); else name = nullptr; break; case JSOP_GETALIASEDVAR: case JSOP_SETALIASEDVAR: name = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc); break; default: name = nullptr; break; } if (name) remainingNames.remove(name); } if (script->hasObjects()) { ObjectArray* objects = script->objects(); for (size_t i = 0; i < objects->length; i++) { JSObject* obj = objects->vector[i]; if (obj->is() && obj->as().isInterpreted()) { JSFunction* fun = &obj->as(); RootedScript innerScript(cx, fun->getOrCreateScript(cx)); if (!innerScript) return false; if (!RemoveReferencedNames(cx, innerScript, remainingNames)) return false; } } } return true; } static bool AnalyzeEntrainedVariablesInScript(JSContext* cx, HandleScript script, HandleScript innerScript) { PropertyNameSet remainingNames(cx); if (!remainingNames.init()) return false; for (BindingIter bi(script); bi; bi++) { if (bi->aliased()) { PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(bi->name()); if (!p && !remainingNames.add(p, bi->name())) return false; } } if (!RemoveReferencedNames(cx, innerScript, remainingNames)) return false; if (!remainingNames.empty()) { Sprinter buf(cx); if (!buf.init()) return false; buf.printf("Script "); if (JSAtom* name = script->functionNonDelazifying()->displayAtom()) { buf.putString(name); buf.printf(" "); } buf.printf("(%s:%" PRIuSIZE ") has variables entrained by ", script->filename(), script->lineno()); if (JSAtom* name = innerScript->functionNonDelazifying()->displayAtom()) { buf.putString(name); buf.printf(" "); } buf.printf("(%s:%" PRIuSIZE ") ::", innerScript->filename(), innerScript->lineno()); for (PropertyNameSet::Range r = remainingNames.all(); !r.empty(); r.popFront()) { buf.printf(" "); buf.putString(r.front()); } printf("%s\n", buf.string()); } if (innerScript->hasObjects()) { ObjectArray* objects = innerScript->objects(); for (size_t i = 0; i < objects->length; i++) { JSObject* obj = objects->vector[i]; if (obj->is() && obj->as().isInterpreted()) { JSFunction* fun = &obj->as(); RootedScript innerInnerScript(cx, fun->getOrCreateScript(cx)); if (!innerInnerScript || !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) { return false; } } } } return true; } // Look for local variables in script or any other script inner to it, which are // part of the script's call object and are unnecessarily entrained by their own // inner scripts which do not refer to those variables. An example is: // // function foo() { // var a, b; // function bar() { return a; } // function baz() { return b; } // } // // |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|. bool js::AnalyzeEntrainedVariables(JSContext* cx, HandleScript script) { if (!script->hasObjects()) return true; ObjectArray* objects = script->objects(); for (size_t i = 0; i < objects->length; i++) { JSObject* obj = objects->vector[i]; if (obj->is() && obj->as().isInterpreted()) { JSFunction* fun = &obj->as(); RootedScript innerScript(cx, fun->getOrCreateScript(cx)); if (!innerScript) return false; if (script->functionDelazifying() && script->functionDelazifying()->needsCallObject()) { if (!AnalyzeEntrainedVariablesInScript(cx, script, innerScript)) return false; } if (!AnalyzeEntrainedVariables(cx, innerScript)) return false; } } return true; } #endif