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