michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "vm/ScopeObject-inl.h" michael@0: michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include "jscompartment.h" michael@0: #include "jsiter.h" michael@0: michael@0: #include "vm/ArgumentsObject.h" michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/ProxyObject.h" michael@0: #include "vm/Shape.h" michael@0: #include "vm/Xdr.h" michael@0: michael@0: #include "jsatominlines.h" michael@0: #include "jsobjinlines.h" michael@0: #include "jsscriptinlines.h" michael@0: michael@0: #include "vm/Stack-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::types; michael@0: michael@0: using mozilla::PodZero; michael@0: michael@0: typedef Rooted RootedArgumentsObject; michael@0: typedef MutableHandle MutableHandleArgumentsObject; michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: static JSObject * michael@0: InnermostStaticScope(JSScript *script, jsbytecode *pc) michael@0: { michael@0: JS_ASSERT(script->containsPC(pc)); michael@0: JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD); michael@0: michael@0: NestedScopeObject *scope = script->getStaticScope(pc); michael@0: if (scope) michael@0: return scope; michael@0: return script->functionNonDelazifying(); michael@0: } michael@0: michael@0: Shape * michael@0: js::ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc) michael@0: { michael@0: StaticScopeIter ssi(InnermostStaticScope(script, pc)); michael@0: uint32_t hops = ScopeCoordinate(pc).hops(); michael@0: while (true) { michael@0: JS_ASSERT(!ssi.done()); michael@0: if (ssi.hasDynamicScopeObject()) { michael@0: if (!hops) michael@0: break; michael@0: hops--; michael@0: } michael@0: ssi++; michael@0: } michael@0: return ssi.scopeShape(); michael@0: } michael@0: michael@0: static const uint32_t SCOPE_COORDINATE_NAME_THRESHOLD = 20; michael@0: michael@0: void michael@0: ScopeCoordinateNameCache::purge() michael@0: { michael@0: shape = nullptr; michael@0: if (map.initialized()) michael@0: map.finish(); michael@0: } michael@0: michael@0: PropertyName * michael@0: js::ScopeCoordinateName(ScopeCoordinateNameCache &cache, JSScript *script, jsbytecode *pc) michael@0: { michael@0: Shape *shape = ScopeCoordinateToStaticScopeShape(script, pc); michael@0: if (shape != cache.shape && shape->slot() >= SCOPE_COORDINATE_NAME_THRESHOLD) { michael@0: cache.purge(); michael@0: if (cache.map.init(shape->slot())) { michael@0: cache.shape = shape; michael@0: Shape::Range r(shape); michael@0: while (!r.empty()) { michael@0: if (!cache.map.putNew(r.front().slot(), r.front().propid())) { michael@0: cache.purge(); michael@0: break; michael@0: } michael@0: r.popFront(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: jsid id; michael@0: ScopeCoordinate sc(pc); michael@0: if (shape == cache.shape) { michael@0: ScopeCoordinateNameCache::Map::Ptr p = cache.map.lookup(sc.slot()); michael@0: id = p->value(); michael@0: } else { michael@0: Shape::Range r(shape); michael@0: while (r.front().slot() != sc.slot()) michael@0: r.popFront(); michael@0: id = r.front().propidRaw(); michael@0: } michael@0: michael@0: /* Beware nameless destructuring formal. */ michael@0: if (!JSID_IS_ATOM(id)) michael@0: return script->runtimeFromAnyThread()->commonNames->empty; michael@0: return JSID_TO_ATOM(id)->asPropertyName(); michael@0: } michael@0: michael@0: JSScript * michael@0: js::ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc) michael@0: { michael@0: StaticScopeIter ssi(InnermostStaticScope(script, pc)); michael@0: uint32_t hops = ScopeCoordinate(pc).hops(); michael@0: while (true) { michael@0: if (ssi.hasDynamicScopeObject()) { michael@0: if (!hops) michael@0: break; michael@0: hops--; michael@0: } michael@0: ssi++; michael@0: } michael@0: if (ssi.type() != StaticScopeIter::FUNCTION) michael@0: return nullptr; michael@0: return ssi.funScript(); michael@0: } michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: void michael@0: ScopeObject::setEnclosingScope(HandleObject obj) michael@0: { michael@0: JS_ASSERT_IF(obj->is() || obj->is() || obj->is(), michael@0: obj->isDelegate()); michael@0: setFixedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*obj)); michael@0: } michael@0: michael@0: CallObject * michael@0: CallObject::create(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots) michael@0: { michael@0: MOZ_ASSERT(!type->singleton(), michael@0: "passed a singleton type to create() (use createSingleton() " michael@0: "instead)"); michael@0: gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); michael@0: MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_)); michael@0: kind = gc::GetBackgroundAllocKind(kind); michael@0: michael@0: JSObject *obj = JSObject::create(cx, kind, gc::DefaultHeap, shape, type, slots); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: CallObject * michael@0: CallObject::createSingleton(JSContext *cx, HandleShape shape, HeapSlot *slots) michael@0: { michael@0: gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); michael@0: MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_)); michael@0: kind = gc::GetBackgroundAllocKind(kind); michael@0: michael@0: RootedTypeObject type(cx, cx->getSingletonType(&class_, nullptr)); michael@0: if (!type) michael@0: return nullptr; michael@0: RootedObject obj(cx, JSObject::create(cx, kind, gc::TenuredHeap, shape, type, slots)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: MOZ_ASSERT(obj->hasSingletonType(), michael@0: "type created inline above must be a singleton"); michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: /* michael@0: * Create a CallObject for a JSScript that is not initialized to any particular michael@0: * callsite. This object can either be initialized (with an enclosing scope and michael@0: * callee) or used as a template for jit compilation. michael@0: */ michael@0: CallObject * michael@0: CallObject::createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap) michael@0: { michael@0: RootedShape shape(cx, script->bindings.callObjShape()); michael@0: JS_ASSERT(shape->getObjectClass() == &class_); michael@0: michael@0: RootedTypeObject type(cx, cx->getNewType(&class_, nullptr)); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); michael@0: JS_ASSERT(CanBeFinalizedInBackground(kind, &class_)); michael@0: kind = gc::GetBackgroundAllocKind(kind); michael@0: michael@0: JSObject *obj = JSObject::create(cx, kind, heap, shape, type); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: /* michael@0: * Construct a call object for the given bindings. If this is a call object michael@0: * for a function invocation, callee should be the function being called. michael@0: * Otherwise it must be a call object for eval of strict mode code, and callee michael@0: * must be null. michael@0: */ michael@0: CallObject * michael@0: CallObject::create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee) michael@0: { michael@0: gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap; michael@0: CallObject *callobj = CallObject::createTemplateObject(cx, script, heap); michael@0: if (!callobj) michael@0: return nullptr; michael@0: michael@0: callobj->as().setEnclosingScope(enclosing); michael@0: callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee)); michael@0: michael@0: if (script->treatAsRunOnce()) { michael@0: Rooted ncallobj(cx, callobj); michael@0: if (!JSObject::setSingletonType(cx, ncallobj)) michael@0: return nullptr; michael@0: return ncallobj; michael@0: } michael@0: michael@0: return callobj; michael@0: } michael@0: michael@0: CallObject * michael@0: CallObject::createForFunction(JSContext *cx, HandleObject enclosing, HandleFunction callee) michael@0: { michael@0: RootedObject scopeChain(cx, enclosing); michael@0: JS_ASSERT(scopeChain); michael@0: michael@0: /* michael@0: * For a named function expression Call's parent points to an environment michael@0: * object holding function's name. michael@0: */ michael@0: if (callee->isNamedLambda()) { michael@0: scopeChain = DeclEnvObject::create(cx, scopeChain, callee); michael@0: if (!scopeChain) michael@0: return nullptr; michael@0: } michael@0: michael@0: RootedScript script(cx, callee->nonLazyScript()); michael@0: return create(cx, script, scopeChain, callee); michael@0: } michael@0: michael@0: CallObject * michael@0: CallObject::createForFunction(JSContext *cx, AbstractFramePtr frame) michael@0: { michael@0: JS_ASSERT(frame.isNonEvalFunctionFrame()); michael@0: assertSameCompartment(cx, frame); michael@0: michael@0: RootedObject scopeChain(cx, frame.scopeChain()); michael@0: RootedFunction callee(cx, frame.callee()); michael@0: michael@0: CallObject *callobj = createForFunction(cx, scopeChain, callee); michael@0: if (!callobj) michael@0: return nullptr; michael@0: michael@0: /* Copy in the closed-over formal arguments. */ michael@0: for (AliasedFormalIter i(frame.script()); i; i++) { michael@0: callobj->setAliasedVar(cx, i, i->name(), michael@0: frame.unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING)); michael@0: } michael@0: michael@0: return callobj; michael@0: } michael@0: michael@0: CallObject * michael@0: CallObject::createForStrictEval(JSContext *cx, AbstractFramePtr frame) michael@0: { michael@0: JS_ASSERT(frame.isStrictEvalFrame()); michael@0: JS_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterFrame() == frame.asInterpreterFrame()); michael@0: JS_ASSERT_IF(frame.isInterpreterFrame(), cx->interpreterRegs().pc == frame.script()->code()); michael@0: michael@0: RootedFunction callee(cx); michael@0: RootedScript script(cx, frame.script()); michael@0: RootedObject scopeChain(cx, frame.scopeChain()); michael@0: return create(cx, script, scopeChain, callee); michael@0: } michael@0: michael@0: const Class CallObject::class_ = { michael@0: "Call", michael@0: JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: nullptr /* convert: Leave it nullptr so we notice if calls ever escape */ michael@0: }; michael@0: michael@0: const Class DeclEnvObject::class_ = { michael@0: js_Object_str, michael@0: JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) | michael@0: JSCLASS_HAS_CACHED_PROTO(JSProto_Object), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: /* michael@0: * Create a DeclEnvObject for a JSScript that is not initialized to any michael@0: * particular callsite. This object can either be initialized (with an enclosing michael@0: * scope and callee) or used as a template for jit compilation. michael@0: */ michael@0: DeclEnvObject * michael@0: DeclEnvObject::createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap) michael@0: { michael@0: JS_ASSERT(IsNurseryAllocable(FINALIZE_KIND)); michael@0: michael@0: RootedTypeObject type(cx, cx->getNewType(&class_, nullptr)); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: RootedShape emptyDeclEnvShape(cx); michael@0: emptyDeclEnvShape = EmptyShape::getInitialShape(cx, &class_, nullptr, michael@0: cx->global(), nullptr, FINALIZE_KIND, michael@0: BaseShape::DELEGATE); michael@0: if (!emptyDeclEnvShape) michael@0: return nullptr; michael@0: michael@0: RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, heap, emptyDeclEnvShape, type)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: // Assign a fixed slot to a property with the same name as the lambda. michael@0: Rooted id(cx, AtomToId(fun->atom())); michael@0: const Class *clasp = obj->getClass(); michael@0: unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY; michael@0: if (!JSObject::putProperty(cx, obj, id, clasp->getProperty, michael@0: clasp->setProperty, lambdaSlot(), attrs, 0)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: JS_ASSERT(!obj->hasDynamicSlots()); michael@0: return &obj->as(); michael@0: } michael@0: michael@0: DeclEnvObject * michael@0: DeclEnvObject::create(JSContext *cx, HandleObject enclosing, HandleFunction callee) michael@0: { michael@0: RootedObject obj(cx, createTemplateObject(cx, callee, gc::DefaultHeap)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: obj->as().setEnclosingScope(enclosing); michael@0: obj->setFixedSlot(lambdaSlot(), ObjectValue(*callee)); michael@0: return &obj->as(); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: js::XDRStaticWithObject(XDRState *xdr, HandleObject enclosingScope, StaticWithObject **objp) michael@0: { michael@0: if (mode == XDR_DECODE) { michael@0: JSContext *cx = xdr->cx(); michael@0: Rooted obj(cx, StaticWithObject::create(cx)); michael@0: if (!obj) michael@0: return false; michael@0: obj->initEnclosingNestedScope(enclosingScope); michael@0: *objp = obj; michael@0: } michael@0: // For encoding, there is nothing to do. The only information that is michael@0: // encoded by a StaticWithObject is its presence on the scope chain, and the michael@0: // script XDR handler already takes care of that. michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::XDRStaticWithObject(XDRState *, HandleObject, StaticWithObject **); michael@0: michael@0: template bool michael@0: js::XDRStaticWithObject(XDRState *, HandleObject, StaticWithObject **); michael@0: michael@0: StaticWithObject * michael@0: StaticWithObject::create(ExclusiveContext *cx) michael@0: { michael@0: RootedTypeObject type(cx, cx->getNewType(&class_, nullptr)); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr), michael@0: nullptr, nullptr, FINALIZE_KIND)); michael@0: if (!shape) michael@0: return nullptr; michael@0: michael@0: RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, type)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: static JSObject * michael@0: CloneStaticWithObject(JSContext *cx, HandleObject enclosingScope, Handle srcWith) michael@0: { michael@0: Rooted clone(cx, StaticWithObject::create(cx)); michael@0: if (!clone) michael@0: return nullptr; michael@0: michael@0: clone->initEnclosingNestedScope(enclosingScope); michael@0: michael@0: return clone; michael@0: } michael@0: michael@0: DynamicWithObject * michael@0: DynamicWithObject::create(JSContext *cx, HandleObject object, HandleObject enclosing, michael@0: HandleObject staticWith) michael@0: { michael@0: JS_ASSERT(staticWith->is()); michael@0: RootedTypeObject type(cx, cx->getNewType(&class_, staticWith.get())); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(staticWith), michael@0: &enclosing->global(), nullptr, michael@0: FINALIZE_KIND)); michael@0: if (!shape) michael@0: return nullptr; michael@0: michael@0: RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, type)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: JSObject *thisp = JSObject::thisObject(cx, object); michael@0: if (!thisp) michael@0: return nullptr; michael@0: michael@0: obj->as().setEnclosingScope(enclosing); michael@0: obj->setFixedSlot(OBJECT_SLOT, ObjectValue(*object)); michael@0: obj->setFixedSlot(THIS_SLOT, ObjectValue(*thisp)); michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: static bool michael@0: with_LookupGeneric(JSContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::lookupGeneric(cx, actual, id, objp, propp); michael@0: } michael@0: michael@0: static bool michael@0: with_LookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: Rooted id(cx, NameToId(name)); michael@0: return with_LookupGeneric(cx, obj, id, objp, propp); michael@0: } michael@0: michael@0: static bool michael@0: with_LookupElement(JSContext *cx, HandleObject obj, uint32_t index, michael@0: MutableHandleObject objp, MutableHandleShape propp) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return with_LookupGeneric(cx, obj, id, objp, propp); michael@0: } michael@0: michael@0: static bool michael@0: with_GetGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id, michael@0: MutableHandleValue vp) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::getGeneric(cx, actual, actual, id, vp); michael@0: } michael@0: michael@0: static bool michael@0: with_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name, michael@0: MutableHandleValue vp) michael@0: { michael@0: RootedId id(cx, NameToId(name)); michael@0: return with_GetGeneric(cx, obj, receiver, id, vp); michael@0: } michael@0: michael@0: static bool michael@0: with_GetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, michael@0: MutableHandleValue vp) michael@0: { michael@0: RootedId id(cx); michael@0: if (!IndexToId(cx, index, &id)) michael@0: return false; michael@0: return with_GetGeneric(cx, obj, receiver, id, vp); michael@0: } michael@0: michael@0: static bool michael@0: with_SetGeneric(JSContext *cx, HandleObject obj, HandleId id, michael@0: MutableHandleValue vp, bool strict) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::setGeneric(cx, actual, actual, id, vp, strict); michael@0: } michael@0: michael@0: static bool michael@0: with_SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, michael@0: MutableHandleValue vp, bool strict) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::setProperty(cx, actual, actual, name, vp, strict); michael@0: } michael@0: michael@0: static bool michael@0: with_SetElement(JSContext *cx, HandleObject obj, uint32_t index, michael@0: MutableHandleValue vp, bool strict) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::setElement(cx, actual, actual, index, vp, strict); michael@0: } michael@0: michael@0: static bool michael@0: with_GetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::getGenericAttributes(cx, actual, id, attrsp); michael@0: } michael@0: michael@0: static bool michael@0: with_SetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::setGenericAttributes(cx, actual, id, attrsp); michael@0: } michael@0: michael@0: static bool michael@0: with_DeleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, michael@0: bool *succeeded) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::deleteProperty(cx, actual, name, succeeded); michael@0: } michael@0: michael@0: static bool michael@0: with_DeleteElement(JSContext *cx, HandleObject obj, uint32_t index, michael@0: bool *succeeded) michael@0: { michael@0: RootedObject actual(cx, &obj->as().object()); michael@0: return JSObject::deleteElement(cx, actual, index, succeeded); michael@0: } michael@0: michael@0: static JSObject * michael@0: with_ThisObject(JSContext *cx, HandleObject obj) michael@0: { michael@0: return &obj->as().withThis(); michael@0: } michael@0: michael@0: const Class StaticWithObject::class_ = { michael@0: "WithTemplate", michael@0: JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(StaticWithObject::RESERVED_SLOTS) | michael@0: JSCLASS_IS_ANONYMOUS, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: const Class DynamicWithObject::class_ = { michael@0: "With", michael@0: JSCLASS_HAS_RESERVED_SLOTS(DynamicWithObject::RESERVED_SLOTS) | michael@0: JSCLASS_IS_ANONYMOUS, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: nullptr, /* finalize */ michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: nullptr, /* trace */ michael@0: JS_NULL_CLASS_SPEC, michael@0: JS_NULL_CLASS_EXT, michael@0: { michael@0: with_LookupGeneric, michael@0: with_LookupProperty, michael@0: with_LookupElement, michael@0: nullptr, /* defineGeneric */ michael@0: nullptr, /* defineProperty */ michael@0: nullptr, /* defineElement */ michael@0: with_GetGeneric, michael@0: with_GetProperty, michael@0: with_GetElement, michael@0: with_SetGeneric, michael@0: with_SetProperty, michael@0: with_SetElement, michael@0: with_GetGenericAttributes, michael@0: with_SetGenericAttributes, michael@0: with_DeleteProperty, michael@0: with_DeleteElement, michael@0: nullptr, nullptr, /* watch/unwatch */ michael@0: nullptr, /* slice */ michael@0: nullptr, /* enumerate (native enumeration of target doesn't work) */ michael@0: with_ThisObject, michael@0: } michael@0: }; michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: ClonedBlockObject * michael@0: ClonedBlockObject::create(JSContext *cx, Handle block, AbstractFramePtr frame) michael@0: { michael@0: assertSameCompartment(cx, frame); michael@0: JS_ASSERT(block->getClass() == &BlockObject::class_); michael@0: michael@0: RootedTypeObject type(cx, cx->getNewType(&BlockObject::class_, block.get())); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: RootedShape shape(cx, block->lastProperty()); michael@0: michael@0: RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, type)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: /* Set the parent if necessary, as for call objects. */ michael@0: if (&frame.scopeChain()->global() != obj->getParent()) { michael@0: JS_ASSERT(obj->getParent() == nullptr); michael@0: Rooted global(cx, &frame.scopeChain()->global()); michael@0: if (!JSObject::setParent(cx, obj, global)) michael@0: return nullptr; michael@0: } michael@0: michael@0: JS_ASSERT(!obj->inDictionaryMode()); michael@0: JS_ASSERT(obj->slotSpan() >= block->numVariables() + RESERVED_SLOTS); michael@0: michael@0: obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain())); michael@0: michael@0: /* michael@0: * Copy in the closed-over locals. Closed-over locals don't need michael@0: * any fixup since the initial value is 'undefined'. michael@0: */ michael@0: unsigned nvars = block->numVariables(); michael@0: for (unsigned i = 0; i < nvars; ++i) { michael@0: if (block->isAliased(i)) { michael@0: Value &val = frame.unaliasedLocal(block->blockIndexToLocalIndex(i)); michael@0: obj->as().setVar(i, val); michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(obj->isDelegate()); michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: void michael@0: ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame) michael@0: { michael@0: StaticBlockObject &block = staticBlock(); michael@0: for (unsigned i = 0; i < numVariables(); ++i) { michael@0: if (!block.isAliased(i)) { michael@0: Value &val = frame.unaliasedLocal(block.blockIndexToLocalIndex(i)); michael@0: setVar(i, val, DONT_CHECK_ALIASING); michael@0: } michael@0: } michael@0: } michael@0: michael@0: StaticBlockObject * michael@0: StaticBlockObject::create(ExclusiveContext *cx) michael@0: { michael@0: RootedTypeObject type(cx, cx->getNewType(&BlockObject::class_, nullptr)); michael@0: if (!type) michael@0: return nullptr; michael@0: michael@0: RootedShape emptyBlockShape(cx); michael@0: emptyBlockShape = EmptyShape::getInitialShape(cx, &BlockObject::class_, nullptr, nullptr, michael@0: nullptr, FINALIZE_KIND, BaseShape::DELEGATE); michael@0: if (!emptyBlockShape) michael@0: return nullptr; michael@0: michael@0: JSObject *obj = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, emptyBlockShape, type); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: return &obj->as(); michael@0: } michael@0: michael@0: /* static */ Shape * michael@0: StaticBlockObject::addVar(ExclusiveContext *cx, Handle block, HandleId id, michael@0: unsigned index, bool *redeclared) michael@0: { michael@0: JS_ASSERT(JSID_IS_ATOM(id)); michael@0: JS_ASSERT(index < LOCAL_INDEX_LIMIT); michael@0: michael@0: *redeclared = false; michael@0: michael@0: /* Inline JSObject::addProperty in order to trap the redefinition case. */ michael@0: Shape **spp; michael@0: if (Shape::search(cx, block->lastProperty(), id, &spp, true)) { michael@0: *redeclared = true; michael@0: return nullptr; michael@0: } michael@0: michael@0: /* michael@0: * Don't convert this object to dictionary mode so that we can clone the michael@0: * block's shape later. michael@0: */ michael@0: uint32_t slot = JSSLOT_FREE(&BlockObject::class_) + index; michael@0: return JSObject::addPropertyInternal(cx, block, id, michael@0: /* getter = */ nullptr, michael@0: /* setter = */ nullptr, michael@0: slot, michael@0: JSPROP_ENUMERATE | JSPROP_PERMANENT, michael@0: /* attrs = */ 0, michael@0: spp, michael@0: /* allowDictionary = */ false); michael@0: } michael@0: michael@0: const Class BlockObject::class_ = { michael@0: "Block", michael@0: JSCLASS_IMPLEMENTS_BARRIERS | michael@0: JSCLASS_HAS_RESERVED_SLOTS(BlockObject::RESERVED_SLOTS) | michael@0: JSCLASS_IS_ANONYMOUS, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: template michael@0: bool michael@0: js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, michael@0: StaticBlockObject **objp) michael@0: { michael@0: /* NB: Keep this in sync with CloneStaticBlockObject. */ michael@0: michael@0: JSContext *cx = xdr->cx(); michael@0: michael@0: Rooted obj(cx); michael@0: uint32_t count = 0, offset = 0; michael@0: michael@0: if (mode == XDR_ENCODE) { michael@0: obj = *objp; michael@0: count = obj->numVariables(); michael@0: offset = obj->localOffset(); michael@0: } michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: obj = StaticBlockObject::create(cx); michael@0: if (!obj) michael@0: return false; michael@0: obj->initEnclosingNestedScope(enclosingScope); michael@0: *objp = obj; michael@0: } michael@0: michael@0: if (!xdr->codeUint32(&count)) michael@0: return false; michael@0: if (!xdr->codeUint32(&offset)) michael@0: return false; michael@0: michael@0: /* michael@0: * XDR the block object's properties. We know that there are 'count' michael@0: * properties to XDR, stored as id/aliased pairs. (The empty string as michael@0: * id indicates an int id.) michael@0: */ michael@0: if (mode == XDR_DECODE) { michael@0: obj->setLocalOffset(offset); michael@0: michael@0: for (unsigned i = 0; i < count; i++) { michael@0: RootedAtom atom(cx); michael@0: if (!XDRAtom(xdr, &atom)) michael@0: return false; michael@0: michael@0: RootedId id(cx, atom != cx->runtime()->emptyString michael@0: ? AtomToId(atom) michael@0: : INT_TO_JSID(i)); michael@0: michael@0: bool redeclared; michael@0: if (!StaticBlockObject::addVar(cx, obj, id, i, &redeclared)) { michael@0: JS_ASSERT(!redeclared); michael@0: return false; michael@0: } michael@0: michael@0: uint32_t aliased; michael@0: if (!xdr->codeUint32(&aliased)) michael@0: return false; michael@0: michael@0: JS_ASSERT(aliased == 0 || aliased == 1); michael@0: obj->setAliased(i, !!aliased); michael@0: } michael@0: } else { michael@0: AutoShapeVector shapes(cx); michael@0: if (!shapes.growBy(count)) michael@0: return false; michael@0: michael@0: for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) michael@0: shapes[obj->shapeToIndex(r.front())] = &r.front(); michael@0: michael@0: RootedShape shape(cx); michael@0: RootedId propid(cx); michael@0: RootedAtom atom(cx); michael@0: for (unsigned i = 0; i < count; i++) { michael@0: shape = shapes[i]; michael@0: JS_ASSERT(shape->hasDefaultGetter()); michael@0: JS_ASSERT(obj->shapeToIndex(*shape) == i); michael@0: michael@0: propid = shape->propid(); michael@0: JS_ASSERT(JSID_IS_ATOM(propid) || JSID_IS_INT(propid)); michael@0: michael@0: atom = JSID_IS_ATOM(propid) michael@0: ? JSID_TO_ATOM(propid) michael@0: : cx->runtime()->emptyString; michael@0: if (!XDRAtom(xdr, &atom)) michael@0: return false; michael@0: michael@0: uint32_t aliased = obj->isAliased(i); michael@0: if (!xdr->codeUint32(&aliased)) michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::XDRStaticBlockObject(XDRState *, HandleObject, StaticBlockObject **); michael@0: michael@0: template bool michael@0: js::XDRStaticBlockObject(XDRState *, HandleObject, StaticBlockObject **); michael@0: michael@0: static JSObject * michael@0: CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle srcBlock) michael@0: { michael@0: /* NB: Keep this in sync with XDRStaticBlockObject. */ michael@0: michael@0: Rooted clone(cx, StaticBlockObject::create(cx)); michael@0: if (!clone) michael@0: return nullptr; michael@0: michael@0: clone->initEnclosingNestedScope(enclosingScope); michael@0: clone->setLocalOffset(srcBlock->localOffset()); michael@0: michael@0: /* Shape::Range is reverse order, so build a list in forward order. */ michael@0: AutoShapeVector shapes(cx); michael@0: if (!shapes.growBy(srcBlock->numVariables())) michael@0: return nullptr; michael@0: michael@0: for (Shape::Range r(srcBlock->lastProperty()); !r.empty(); r.popFront()) michael@0: shapes[srcBlock->shapeToIndex(r.front())] = &r.front(); michael@0: michael@0: for (Shape **p = shapes.begin(); p != shapes.end(); ++p) { michael@0: RootedId id(cx, (*p)->propid()); michael@0: unsigned i = srcBlock->shapeToIndex(**p); michael@0: michael@0: bool redeclared; michael@0: if (!StaticBlockObject::addVar(cx, clone, id, i, &redeclared)) { michael@0: JS_ASSERT(!redeclared); michael@0: return nullptr; michael@0: } michael@0: michael@0: clone->setAliased(i, srcBlock->isAliased(i)); michael@0: } michael@0: michael@0: return clone; michael@0: } michael@0: michael@0: JSObject * michael@0: js::CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle srcBlock) michael@0: { michael@0: if (srcBlock->is()) { michael@0: Rooted blockObj(cx, &srcBlock->as()); michael@0: return CloneStaticBlockObject(cx, enclosingScope, blockObj); michael@0: } else { michael@0: Rooted withObj(cx, &srcBlock->as()); michael@0: return CloneStaticWithObject(cx, enclosingScope, withObj); michael@0: } michael@0: } michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: // Any name atom for a function which will be added as a DeclEnv object to the michael@0: // scope chain above call objects for fun. michael@0: static inline JSAtom * michael@0: CallObjectLambdaName(JSFunction &fun) michael@0: { michael@0: return fun.isNamedLambda() ? fun.atom() : nullptr; michael@0: } michael@0: michael@0: ScopeIter::ScopeIter(const ScopeIter &si, JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) michael@0: : cx(cx), michael@0: frame_(si.frame_), michael@0: cur_(cx, si.cur_), michael@0: staticScope_(cx, si.staticScope_), michael@0: type_(si.type_), michael@0: hasScopeObject_(si.hasScopeObject_) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: michael@0: ScopeIter::ScopeIter(JSObject &enclosingScope, JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) michael@0: : cx(cx), michael@0: frame_(NullFramePtr()), michael@0: cur_(cx, &enclosingScope), michael@0: staticScope_(cx, nullptr), michael@0: type_(Type(-1)) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: michael@0: ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) michael@0: : cx(cx), michael@0: frame_(frame), michael@0: cur_(cx, frame.scopeChain()), michael@0: staticScope_(cx, frame.script()->getStaticScope(pc)) michael@0: { michael@0: assertSameCompartment(cx, frame); michael@0: settle(); michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: michael@0: ScopeIter::ScopeIter(const ScopeIterVal &val, JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) michael@0: : cx(cx), michael@0: frame_(val.frame_), michael@0: cur_(cx, val.cur_), michael@0: staticScope_(cx, val.staticScope_), michael@0: type_(val.type_), michael@0: hasScopeObject_(val.hasScopeObject_) michael@0: { michael@0: assertSameCompartment(cx, val.frame_); michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: michael@0: ScopeObject & michael@0: ScopeIter::scope() const michael@0: { michael@0: JS_ASSERT(hasScopeObject()); michael@0: return cur_->as(); michael@0: } michael@0: michael@0: ScopeIter & michael@0: ScopeIter::operator++() michael@0: { michael@0: JS_ASSERT(!done()); michael@0: switch (type_) { michael@0: case Call: michael@0: if (hasScopeObject_) { michael@0: cur_ = &cur_->as().enclosingScope(); michael@0: if (CallObjectLambdaName(*frame_.fun())) michael@0: cur_ = &cur_->as().enclosingScope(); michael@0: } michael@0: frame_ = NullFramePtr(); michael@0: break; michael@0: case Block: michael@0: JS_ASSERT(staticScope_ && staticScope_->is()); michael@0: staticScope_ = staticScope_->as().enclosingNestedScope(); michael@0: if (hasScopeObject_) michael@0: cur_ = &cur_->as().enclosingScope(); michael@0: settle(); michael@0: break; michael@0: case With: michael@0: JS_ASSERT(staticScope_ && staticScope_->is()); michael@0: JS_ASSERT(hasScopeObject_); michael@0: staticScope_ = staticScope_->as().enclosingNestedScope(); michael@0: cur_ = &cur_->as().enclosingScope(); michael@0: settle(); michael@0: break; michael@0: case StrictEvalScope: michael@0: if (hasScopeObject_) michael@0: cur_ = &cur_->as().enclosingScope(); michael@0: frame_ = NullFramePtr(); michael@0: break; michael@0: } michael@0: return *this; michael@0: } michael@0: michael@0: void michael@0: ScopeIter::settle() michael@0: { michael@0: /* michael@0: * Given an iterator state (cur_, staticScope_), figure out which (potentially michael@0: * optimized) scope the iterator should report. Thus, the result is a pair michael@0: * (type_, hasScopeObject_) where hasScopeObject_ indicates whether the michael@0: * scope object has been optimized away and does not exist on the scope michael@0: * chain. Beware: while ScopeIter iterates over the scopes of a single michael@0: * frame, the scope chain (pointed to by cur_) continues into the scopes of michael@0: * enclosing frames. Thus, it is important not to look at cur_ until it is michael@0: * certain that cur_ points to a scope object in the current frame. In michael@0: * particular, there are three tricky corner cases: michael@0: * - non-heavyweight functions; michael@0: * - non-strict direct eval. michael@0: * - heavyweight functions observed before the prologue has finished; michael@0: * In all cases, cur_ can already be pointing into an enclosing frame's michael@0: * scope chain. Furthermore, in the first two cases: even if cur_ points michael@0: * into an enclosing frame's scope chain, the current frame may still have michael@0: * uncloned blocks. In the last case, since we haven't entered the michael@0: * function, we simply return a ScopeIter where done() == true. michael@0: * michael@0: * Note: DebugScopeObject falls nicely into this plan: since they are only michael@0: * ever introduced as the *enclosing* scope of a frame, they should never michael@0: * show up in scope iteration and fall into the final non-scope case. michael@0: */ michael@0: if (frame_.isNonEvalFunctionFrame() && !frame_.fun()->isHeavyweight()) { michael@0: if (staticScope_) { michael@0: // If staticScope_ were a StaticWithObject, the function would be michael@0: // heavyweight. michael@0: JS_ASSERT(staticScope_->is()); michael@0: type_ = Block; michael@0: hasScopeObject_ = staticScope_->as().needsClone(); michael@0: } else { michael@0: type_ = Call; michael@0: hasScopeObject_ = false; michael@0: } michael@0: } else if (frame_.isNonStrictDirectEvalFrame() && cur_ == frame_.evalPrevScopeChain(cx)) { michael@0: if (staticScope_) { michael@0: JS_ASSERT(staticScope_->is()); michael@0: JS_ASSERT(!staticScope_->as().needsClone()); michael@0: type_ = Block; michael@0: hasScopeObject_ = false; michael@0: } else { michael@0: frame_ = NullFramePtr(); michael@0: } michael@0: } else if (frame_.isNonEvalFunctionFrame() && !frame_.hasCallObj()) { michael@0: JS_ASSERT(cur_ == frame_.fun()->environment()); michael@0: frame_ = NullFramePtr(); michael@0: } else if (frame_.isStrictEvalFrame() && !frame_.hasCallObj()) { michael@0: JS_ASSERT(cur_ == frame_.evalPrevScopeChain(cx)); michael@0: frame_ = NullFramePtr(); michael@0: } else if (staticScope_) { michael@0: if (staticScope_->is()) { michael@0: JS_ASSERT(cur_); michael@0: JS_ASSERT(cur_->as().staticScope() == staticScope_); michael@0: type_ = With; michael@0: hasScopeObject_ = true; michael@0: } else { michael@0: type_ = Block; michael@0: hasScopeObject_ = staticScope_->as().needsClone(); michael@0: JS_ASSERT_IF(hasScopeObject_, michael@0: cur_->as().staticBlock() == *staticScope_); michael@0: } michael@0: } else if (cur_->is()) { michael@0: CallObject &callobj = cur_->as(); michael@0: type_ = callobj.isForEval() ? StrictEvalScope : Call; michael@0: hasScopeObject_ = true; michael@0: JS_ASSERT_IF(type_ == Call, callobj.callee().nonLazyScript() == frame_.script()); michael@0: } else { michael@0: JS_ASSERT(!cur_->is()); michael@0: JS_ASSERT(frame_.isGlobalFrame() || frame_.isDebuggerFrame()); michael@0: frame_ = NullFramePtr(); michael@0: } michael@0: } michael@0: michael@0: /* static */ HashNumber michael@0: ScopeIterKey::hash(ScopeIterKey si) michael@0: { michael@0: /* hasScopeObject_ is determined by the other fields. */ michael@0: return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.staticScope_) ^ si.type_; michael@0: } michael@0: michael@0: /* static */ bool michael@0: ScopeIterKey::match(ScopeIterKey si1, ScopeIterKey si2) michael@0: { michael@0: /* hasScopeObject_ is determined by the other fields. */ michael@0: return si1.frame_ == si2.frame_ && michael@0: (!si1.frame_ || michael@0: (si1.cur_ == si2.cur_ && michael@0: si1.staticScope_ == si2.staticScope_ && michael@0: si1.type_ == si2.type_)); michael@0: } michael@0: michael@0: // Live ScopeIter values may be added to DebugScopes::liveScopes, as michael@0: // ScopeIterVal instances. They need to have write barriers when they are added michael@0: // to the hash table, but no barriers when rehashing inside GC. It's a nasty michael@0: // hack, but the important thing is that ScopeIterKey and ScopeIterVal need to michael@0: // alias each other. michael@0: void ScopeIterVal::staticAsserts() { michael@0: static_assert(sizeof(ScopeIterVal) == sizeof(ScopeIterKey), michael@0: "ScopeIterVal must be same size of ScopeIterKey"); michael@0: static_assert(offsetof(ScopeIterVal, cur_) == offsetof(ScopeIterKey, cur_), michael@0: "ScopeIterVal.cur_ must alias ScopeIterKey.cur_"); michael@0: static_assert(offsetof(ScopeIterVal, staticScope_) == offsetof(ScopeIterKey, staticScope_), michael@0: "ScopeIterVal.staticScope_ must alias ScopeIterKey.staticScope_"); michael@0: } michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: namespace { michael@0: michael@0: /* michael@0: * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a michael@0: * custom handler (rather than trying to reuse js::Wrapper) gives us several michael@0: * important abilities: michael@0: * - We want to pass the ScopeObject as the receiver to forwarded scope michael@0: * property ops on aliased variables so that Call/Block/With ops do not all michael@0: * require a 'normalization' step. michael@0: * - The debug scope proxy can directly manipulate the stack frame to allow michael@0: * the debugger to read/write args/locals that were otherwise unaliased. michael@0: * - The debug scope proxy can store unaliased variables after the stack frame michael@0: * is popped so that they may still be read/written by the debugger. michael@0: * - The engine has made certain assumptions about the possible reads/writes michael@0: * in a scope. DebugScopeProxy allows us to prevent the debugger from michael@0: * breaking those assumptions. michael@0: * - The engine makes optimizations that are observable to the debugger. The michael@0: * proxy can either hide these optimizations or make the situation more michael@0: * clear to the debugger. An example is 'arguments'. michael@0: */ michael@0: class DebugScopeProxy : public BaseProxyHandler michael@0: { michael@0: enum Action { SET, GET }; michael@0: michael@0: enum AccessResult { michael@0: ACCESS_UNALIASED, michael@0: ACCESS_GENERIC, michael@0: ACCESS_LOST michael@0: }; michael@0: michael@0: /* michael@0: * This function handles access to unaliased locals/formals. Since they are michael@0: * unaliased, the values of these variables are not stored in the slots of michael@0: * the normal Call/BlockObject scope objects and thus must be recovered michael@0: * from somewhere else: michael@0: * + if the invocation for which the scope was created is still executing, michael@0: * there is a JS frame live on the stack holding the values; michael@0: * + if the invocation for which the scope was created finished executing: michael@0: * - and there was a DebugScopeObject associated with scope, then the michael@0: * DebugScopes::onPop(Call|Block) handler copied out the unaliased michael@0: * variables: michael@0: * . for block scopes, the unaliased values were copied directly michael@0: * into the block object, since there is a slot allocated for every michael@0: * block binding, regardless of whether it is aliased; michael@0: * . for function scopes, a dense array is created in onPopCall to hold michael@0: * the unaliased values and attached to the DebugScopeObject; michael@0: * - and there was not a DebugScopeObject yet associated with the michael@0: * scope, then the unaliased values are lost and not recoverable. michael@0: * michael@0: * Callers should check accessResult for non-failure results: michael@0: * - ACCESS_UNALIASED if the access was unaliased and completed michael@0: * - ACCESS_GENERIC if the access was aliased or the property not found michael@0: * - ACCESS_LOST if the value has been lost to the debugger michael@0: */ michael@0: bool handleUnaliasedAccess(JSContext *cx, Handle debugScope, michael@0: Handle scope, jsid id, Action action, michael@0: MutableHandleValue vp, AccessResult *accessResult) michael@0: { michael@0: JS_ASSERT(&debugScope->scope() == scope); michael@0: *accessResult = ACCESS_GENERIC; michael@0: ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope); michael@0: michael@0: /* Handle unaliased formals, vars, and consts at function scope. */ michael@0: if (scope->is() && !scope->as().isForEval()) { michael@0: CallObject &callobj = scope->as(); michael@0: RootedScript script(cx, callobj.callee().nonLazyScript()); michael@0: if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx)) michael@0: return false; michael@0: michael@0: Bindings &bindings = script->bindings; michael@0: BindingIter bi(script); michael@0: while (bi && NameToId(bi->name()) != id) michael@0: bi++; michael@0: if (!bi) michael@0: return true; michael@0: michael@0: if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) { michael@0: uint32_t i = bi.frameIndex(); michael@0: if (script->varIsAliased(i)) michael@0: return true; michael@0: michael@0: if (maybeLiveScope) { michael@0: AbstractFramePtr frame = maybeLiveScope->frame(); michael@0: if (action == GET) michael@0: vp.set(frame.unaliasedVar(i)); michael@0: else michael@0: frame.unaliasedVar(i) = vp; michael@0: } else if (JSObject *snapshot = debugScope->maybeSnapshot()) { michael@0: if (action == GET) michael@0: vp.set(snapshot->getDenseElement(bindings.numArgs() + i)); michael@0: else michael@0: snapshot->setDenseElement(bindings.numArgs() + i, vp); michael@0: } else { michael@0: /* The unaliased value has been lost to the debugger. */ michael@0: if (action == GET) { michael@0: *accessResult = ACCESS_LOST; michael@0: return true; michael@0: } michael@0: } michael@0: } else { michael@0: JS_ASSERT(bi->kind() == Binding::ARGUMENT); michael@0: unsigned i = bi.frameIndex(); michael@0: if (script->formalIsAliased(i)) michael@0: return true; michael@0: michael@0: if (maybeLiveScope) { michael@0: AbstractFramePtr frame = maybeLiveScope->frame(); michael@0: if (script->argsObjAliasesFormals() && frame.hasArgsObj()) { michael@0: if (action == GET) michael@0: vp.set(frame.argsObj().arg(i)); michael@0: else michael@0: frame.argsObj().setArg(i, vp); michael@0: } else { michael@0: if (action == GET) michael@0: vp.set(frame.unaliasedFormal(i, DONT_CHECK_ALIASING)); michael@0: else michael@0: frame.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp; michael@0: } michael@0: } else if (JSObject *snapshot = debugScope->maybeSnapshot()) { michael@0: if (action == GET) michael@0: vp.set(snapshot->getDenseElement(i)); michael@0: else michael@0: snapshot->setDenseElement(i, vp); michael@0: } else { michael@0: /* The unaliased value has been lost to the debugger. */ michael@0: if (action == GET) { michael@0: *accessResult = ACCESS_LOST; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (action == SET) michael@0: TypeScript::SetArgument(cx, script, i, vp); michael@0: } michael@0: michael@0: *accessResult = ACCESS_UNALIASED; michael@0: return true; michael@0: } michael@0: michael@0: /* Handle unaliased let and catch bindings at block scope. */ michael@0: if (scope->is()) { michael@0: Rooted block(cx, &scope->as()); michael@0: Shape *shape = block->lastProperty()->search(cx, id); michael@0: if (!shape) michael@0: return true; michael@0: michael@0: unsigned i = block->staticBlock().shapeToIndex(*shape); michael@0: if (block->staticBlock().isAliased(i)) michael@0: return true; michael@0: michael@0: if (maybeLiveScope) { michael@0: AbstractFramePtr frame = maybeLiveScope->frame(); michael@0: uint32_t local = block->staticBlock().blockIndexToLocalIndex(i); michael@0: JS_ASSERT(local < frame.script()->nfixed()); michael@0: if (action == GET) michael@0: vp.set(frame.unaliasedLocal(local)); michael@0: else michael@0: frame.unaliasedLocal(local) = vp; michael@0: } else { michael@0: if (action == GET) michael@0: vp.set(block->var(i, DONT_CHECK_ALIASING)); michael@0: else michael@0: block->setVar(i, vp, DONT_CHECK_ALIASING); michael@0: } michael@0: michael@0: *accessResult = ACCESS_UNALIASED; michael@0: return true; michael@0: } michael@0: michael@0: /* The rest of the internal scopes do not have unaliased vars. */ michael@0: JS_ASSERT(scope->is() || scope->is() || michael@0: scope->as().isForEval()); michael@0: return true; michael@0: } michael@0: michael@0: static bool isArguments(JSContext *cx, jsid id) michael@0: { michael@0: return id == NameToId(cx->names().arguments); michael@0: } michael@0: michael@0: static bool isFunctionScope(ScopeObject &scope) michael@0: { michael@0: return scope.is() && !scope.as().isForEval(); michael@0: } michael@0: michael@0: /* michael@0: * In theory, every function scope contains an 'arguments' bindings. michael@0: * However, the engine only adds a binding if 'arguments' is used in the michael@0: * function body. Thus, from the debugger's perspective, 'arguments' may be michael@0: * missing from the list of bindings. michael@0: */ michael@0: static bool isMissingArgumentsBinding(ScopeObject &scope) michael@0: { michael@0: return isFunctionScope(scope) && michael@0: !scope.as().callee().nonLazyScript()->argumentsHasVarBinding(); michael@0: } michael@0: michael@0: /* michael@0: * This function checks if an arguments object needs to be created when michael@0: * the debugger requests 'arguments' for a function scope where the michael@0: * arguments object has been optimized away (either because the binding is michael@0: * missing altogether or because !ScriptAnalysis::needsArgsObj). michael@0: */ michael@0: static bool isMissingArguments(JSContext *cx, jsid id, ScopeObject &scope) michael@0: { michael@0: return isArguments(cx, id) && isFunctionScope(scope) && michael@0: !scope.as().callee().nonLazyScript()->needsArgsObj(); michael@0: } michael@0: michael@0: /* michael@0: * Create a missing arguments object. If the function returns true but michael@0: * argsObj is null, it means the scope is dead. michael@0: */ michael@0: static bool createMissingArguments(JSContext *cx, jsid id, ScopeObject &scope, michael@0: MutableHandleArgumentsObject argsObj) michael@0: { michael@0: MOZ_ASSERT(isMissingArguments(cx, id, scope)); michael@0: argsObj.set(nullptr); michael@0: michael@0: ScopeIterVal *maybeScope = DebugScopes::hasLiveScope(scope); michael@0: if (!maybeScope) michael@0: return true; michael@0: michael@0: argsObj.set(ArgumentsObject::createUnexpected(cx, maybeScope->frame())); michael@0: return !!argsObj; michael@0: } michael@0: michael@0: public: michael@0: static int family; michael@0: static DebugScopeProxy singleton; michael@0: michael@0: DebugScopeProxy() : BaseProxyHandler(&family) {} michael@0: michael@0: bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE michael@0: { michael@0: // always [[Extensible]], can't be made non-[[Extensible]], like most michael@0: // proxies michael@0: *extensible = true; michael@0: return true; michael@0: } michael@0: michael@0: bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE michael@0: { michael@0: // See above. michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CHANGE_EXTENSIBILITY); michael@0: return false; michael@0: } michael@0: michael@0: bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE michael@0: { michael@0: return getOwnPropertyDescriptor(cx, proxy, id, desc); michael@0: } michael@0: michael@0: bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE michael@0: { michael@0: Rooted debugScope(cx, &proxy->as()); michael@0: Rooted scope(cx, &debugScope->scope()); michael@0: michael@0: if (isMissingArguments(cx, id, *scope)) { michael@0: RootedArgumentsObject argsObj(cx); michael@0: if (!createMissingArguments(cx, id, *scope, &argsObj)) michael@0: return false; michael@0: michael@0: if (!argsObj) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, michael@0: "Debugger scope"); michael@0: return false; michael@0: } michael@0: michael@0: desc.object().set(debugScope); michael@0: desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); michael@0: desc.value().setObject(*argsObj); michael@0: desc.setGetter(nullptr); michael@0: desc.setSetter(nullptr); michael@0: return true; michael@0: } michael@0: michael@0: RootedValue v(cx); michael@0: AccessResult access; michael@0: if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, &v, &access)) michael@0: return false; michael@0: michael@0: switch (access) { michael@0: case ACCESS_UNALIASED: michael@0: desc.object().set(debugScope); michael@0: desc.setAttributes(JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); michael@0: desc.value().set(v); michael@0: desc.setGetter(nullptr); michael@0: desc.setSetter(nullptr); michael@0: return true; michael@0: case ACCESS_GENERIC: michael@0: return JS_GetOwnPropertyDescriptorById(cx, scope, id, desc); michael@0: case ACCESS_LOST: michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT); michael@0: return false; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad AccessResult"); michael@0: } michael@0: } michael@0: michael@0: bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, michael@0: MutableHandleValue vp) MOZ_OVERRIDE michael@0: { michael@0: Rooted debugScope(cx, &proxy->as()); michael@0: Rooted scope(cx, &proxy->as().scope()); michael@0: michael@0: if (isMissingArguments(cx, id, *scope)) { michael@0: RootedArgumentsObject argsObj(cx); michael@0: if (!createMissingArguments(cx, id, *scope, &argsObj)) michael@0: return false; michael@0: michael@0: if (!argsObj) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE, michael@0: "Debugger scope"); michael@0: return false; michael@0: } michael@0: michael@0: vp.setObject(*argsObj); michael@0: return true; michael@0: } michael@0: michael@0: AccessResult access; michael@0: if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access)) michael@0: return false; michael@0: michael@0: switch (access) { michael@0: case ACCESS_UNALIASED: michael@0: return true; michael@0: case ACCESS_GENERIC: michael@0: return JSObject::getGeneric(cx, scope, scope, id, vp); michael@0: case ACCESS_LOST: michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_OPTIMIZED_OUT); michael@0: return false; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad AccessResult"); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Like 'get', but returns sentinel values instead of throwing on michael@0: * exceptional cases. michael@0: */ michael@0: bool getMaybeSentinelValue(JSContext *cx, Handle debugScope, HandleId id, michael@0: MutableHandleValue vp) michael@0: { michael@0: Rooted scope(cx, &debugScope->scope()); michael@0: michael@0: if (isMissingArguments(cx, id, *scope)) { michael@0: RootedArgumentsObject argsObj(cx); michael@0: if (!createMissingArguments(cx, id, *scope, &argsObj)) michael@0: return false; michael@0: vp.set(argsObj ? ObjectValue(*argsObj) : MagicValue(JS_OPTIMIZED_ARGUMENTS)); michael@0: return true; michael@0: } michael@0: michael@0: AccessResult access; michael@0: if (!handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp, &access)) michael@0: return false; michael@0: michael@0: switch (access) { michael@0: case ACCESS_UNALIASED: michael@0: return true; michael@0: case ACCESS_GENERIC: michael@0: return JSObject::getGeneric(cx, scope, scope, id, vp); michael@0: case ACCESS_LOST: michael@0: vp.setMagic(JS_OPTIMIZED_OUT); michael@0: return true; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad AccessResult"); michael@0: } michael@0: } michael@0: michael@0: bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, bool strict, michael@0: MutableHandleValue vp) MOZ_OVERRIDE michael@0: { michael@0: Rooted debugScope(cx, &proxy->as()); michael@0: Rooted scope(cx, &proxy->as().scope()); michael@0: michael@0: AccessResult access; michael@0: if (!handleUnaliasedAccess(cx, debugScope, scope, id, SET, vp, &access)) michael@0: return false; michael@0: michael@0: switch (access) { michael@0: case ACCESS_UNALIASED: michael@0: return true; michael@0: case ACCESS_GENERIC: michael@0: return JSObject::setGeneric(cx, scope, scope, id, vp, strict); michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad AccessResult"); michael@0: } michael@0: } michael@0: michael@0: bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id, michael@0: MutableHandle desc) MOZ_OVERRIDE michael@0: { michael@0: Rooted scope(cx, &proxy->as().scope()); michael@0: michael@0: bool found; michael@0: if (!has(cx, proxy, id, &found)) michael@0: return false; michael@0: if (found) michael@0: return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP); michael@0: michael@0: return JS_DefinePropertyById(cx, scope, id, desc.value(), desc.getter(), desc.setter(), michael@0: desc.attributes()); michael@0: } michael@0: michael@0: bool getScopePropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props, michael@0: unsigned flags) michael@0: { michael@0: Rooted scope(cx, &proxy->as().scope()); michael@0: michael@0: if (isMissingArgumentsBinding(*scope)) { michael@0: if (!props.append(NameToId(cx->names().arguments))) michael@0: return false; michael@0: } michael@0: michael@0: // DynamicWithObject isn't a very good proxy. It doesn't have a michael@0: // JSNewEnumerateOp implementation, because if it just delegated to the michael@0: // target object, the object would indicate that native enumeration is michael@0: // the thing to do, but native enumeration over the DynamicWithObject michael@0: // wrapper yields no properties. So instead here we hack around the michael@0: // issue, and punch a hole through to the with object target. michael@0: Rooted target(cx, (scope->is() michael@0: ? &scope->as().object() : scope)); michael@0: if (!GetPropertyNames(cx, target, flags, &props)) michael@0: return false; michael@0: michael@0: /* michael@0: * Function scopes are optimized to not contain unaliased variables so michael@0: * they must be manually appended here. michael@0: */ michael@0: if (scope->is() && !scope->as().isForEval()) { michael@0: RootedScript script(cx, scope->as().callee().nonLazyScript()); michael@0: for (BindingIter bi(script); bi; bi++) { michael@0: if (!bi->aliased() && !props.append(NameToId(bi->name()))) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE michael@0: { michael@0: return getScopePropertyNames(cx, proxy, props, JSITER_OWNONLY); michael@0: } michael@0: michael@0: bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE michael@0: { michael@0: return getScopePropertyNames(cx, proxy, props, 0); michael@0: } michael@0: michael@0: bool has(JSContext *cx, HandleObject proxy, HandleId id_, bool *bp) MOZ_OVERRIDE michael@0: { michael@0: RootedId id(cx, id_); michael@0: ScopeObject &scopeObj = proxy->as().scope(); michael@0: michael@0: if (isArguments(cx, id) && isFunctionScope(scopeObj)) { michael@0: *bp = true; michael@0: return true; michael@0: } michael@0: michael@0: bool found; michael@0: RootedObject scope(cx, &scopeObj); michael@0: if (!JS_HasPropertyById(cx, scope, id, &found)) michael@0: return false; michael@0: michael@0: /* michael@0: * Function scopes are optimized to not contain unaliased variables so michael@0: * a manual search is necessary. michael@0: */ michael@0: if (!found && scope->is() && !scope->as().isForEval()) { michael@0: RootedScript script(cx, scope->as().callee().nonLazyScript()); michael@0: for (BindingIter bi(script); bi; bi++) { michael@0: if (!bi->aliased() && NameToId(bi->name()) == id) { michael@0: found = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: *bp = found; michael@0: return true; michael@0: } michael@0: michael@0: bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE michael@0: { michael@0: RootedValue idval(cx, IdToValue(id)); michael@0: return js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_CANT_DELETE, michael@0: JSDVG_IGNORE_STACK, idval, NullPtr(), nullptr, nullptr); michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: int DebugScopeProxy::family = 0; michael@0: DebugScopeProxy DebugScopeProxy::singleton; michael@0: michael@0: /* static */ DebugScopeObject * michael@0: DebugScopeObject::create(JSContext *cx, ScopeObject &scope, HandleObject enclosing) michael@0: { michael@0: JS_ASSERT(scope.compartment() == cx->compartment()); michael@0: RootedValue priv(cx, ObjectValue(scope)); michael@0: JSObject *obj = NewProxyObject(cx, &DebugScopeProxy::singleton, priv, michael@0: nullptr /* proto */, &scope.global()); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: JS_ASSERT(!enclosing->is()); michael@0: michael@0: DebugScopeObject *debugScope = &obj->as(); michael@0: debugScope->setExtra(ENCLOSING_EXTRA, ObjectValue(*enclosing)); michael@0: debugScope->setExtra(SNAPSHOT_EXTRA, NullValue()); michael@0: michael@0: return debugScope; michael@0: } michael@0: michael@0: ScopeObject & michael@0: DebugScopeObject::scope() const michael@0: { michael@0: return target()->as(); michael@0: } michael@0: michael@0: JSObject & michael@0: DebugScopeObject::enclosingScope() const michael@0: { michael@0: return extra(ENCLOSING_EXTRA).toObject(); michael@0: } michael@0: michael@0: JSObject * michael@0: DebugScopeObject::maybeSnapshot() const michael@0: { michael@0: JS_ASSERT(!scope().as().isForEval()); michael@0: return extra(SNAPSHOT_EXTRA).toObjectOrNull(); michael@0: } michael@0: michael@0: void michael@0: DebugScopeObject::initSnapshot(JSObject &o) michael@0: { michael@0: JS_ASSERT(maybeSnapshot() == nullptr); michael@0: setExtra(SNAPSHOT_EXTRA, ObjectValue(o)); michael@0: } michael@0: michael@0: bool michael@0: DebugScopeObject::isForDeclarative() const michael@0: { michael@0: ScopeObject &s = scope(); michael@0: return s.is() || s.is() || s.is(); michael@0: } michael@0: michael@0: bool michael@0: DebugScopeObject::getMaybeSentinelValue(JSContext *cx, HandleId id, MutableHandleValue vp) michael@0: { michael@0: Rooted self(cx, this); michael@0: return DebugScopeProxy::singleton.getMaybeSentinelValue(cx, self, id, vp); michael@0: } michael@0: michael@0: bool michael@0: js_IsDebugScopeSlow(ProxyObject *proxy) michael@0: { michael@0: JS_ASSERT(proxy->hasClass(&ProxyObject::uncallableClass_)); michael@0: return proxy->handler() == &DebugScopeProxy::singleton; michael@0: } michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE void michael@0: DebugScopes::proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map, michael@0: const EncapsulatedPtr &key) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: /* michael@0: * Strip the barriers from the type before inserting into the store buffer. michael@0: * This will automatically ensure that barriers do not fire during GC. michael@0: * michael@0: * Some compilers complain about instantiating the WeakMap class for michael@0: * unbarriered type arguments, so we cast to a HashMap instead. Because of michael@0: * WeakMap's multiple inheritace, We need to do this in two stages, first to michael@0: * the HashMap base class and then to the unbarriered version. michael@0: */ michael@0: ObjectWeakMap::Base *baseHashMap = static_cast(map); michael@0: michael@0: typedef HashMap UnbarrieredMap; michael@0: UnbarrieredMap *unbarrieredMap = reinterpret_cast(baseHashMap); michael@0: michael@0: typedef gc::HashKeyRef Ref; michael@0: if (key && IsInsideNursery(rt, key)) michael@0: rt->gcStoreBuffer.putGeneric(Ref(unbarrieredMap, key.get())); michael@0: #endif michael@0: } michael@0: michael@0: #ifdef JSGC_GENERATIONAL michael@0: class DebugScopes::MissingScopesRef : public gc::BufferableRef michael@0: { michael@0: MissingScopeMap *map; michael@0: ScopeIterKey key; michael@0: michael@0: public: michael@0: MissingScopesRef(MissingScopeMap *m, const ScopeIterKey &k) : map(m), key(k) {} michael@0: michael@0: void mark(JSTracer *trc) { michael@0: ScopeIterKey prior = key; michael@0: MissingScopeMap::Ptr p = map->lookup(key); michael@0: if (!p) michael@0: return; michael@0: trc->setTracingLocation(&const_cast(p->key()).enclosingScope()); michael@0: Mark(trc, &key.enclosingScope(), "MissingScopesRef"); michael@0: map->rekeyIfMoved(prior, key); michael@0: } michael@0: }; michael@0: #endif michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE void michael@0: DebugScopes::missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map, michael@0: const ScopeIterKey &key) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: if (key.enclosingScope() && IsInsideNursery(rt, key.enclosingScope())) michael@0: rt->gcStoreBuffer.putGeneric(MissingScopesRef(map, key)); michael@0: #endif michael@0: } michael@0: michael@0: /* static */ MOZ_ALWAYS_INLINE void michael@0: DebugScopes::liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map, ScopeObject *key) michael@0: { michael@0: #ifdef JSGC_GENERATIONAL michael@0: // As above. Otherwise, barriers could fire during GC when moving the michael@0: // value. michael@0: typedef HashMap, michael@0: RuntimeAllocPolicy> UnbarrieredLiveScopeMap; michael@0: typedef gc::HashKeyRef Ref; michael@0: if (key && IsInsideNursery(rt, key)) michael@0: rt->gcStoreBuffer.putGeneric(Ref(reinterpret_cast(map), key)); michael@0: #endif michael@0: } michael@0: michael@0: DebugScopes::DebugScopes(JSContext *cx) michael@0: : proxiedScopes(cx), michael@0: missingScopes(cx->runtime()), michael@0: liveScopes(cx->runtime()) michael@0: {} michael@0: michael@0: DebugScopes::~DebugScopes() michael@0: { michael@0: JS_ASSERT(missingScopes.empty()); michael@0: WeakMapBase::removeWeakMapFromList(&proxiedScopes); michael@0: } michael@0: michael@0: bool michael@0: DebugScopes::init() michael@0: { michael@0: if (!liveScopes.init() || michael@0: !proxiedScopes.init() || michael@0: !missingScopes.init()) michael@0: { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: DebugScopes::mark(JSTracer *trc) michael@0: { michael@0: proxiedScopes.trace(trc); michael@0: } michael@0: michael@0: void michael@0: DebugScopes::sweep(JSRuntime *rt) michael@0: { michael@0: /* michael@0: * missingScopes points to debug scopes weakly so that debug scopes can be michael@0: * released more eagerly. michael@0: */ michael@0: for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) { michael@0: DebugScopeObject **debugScope = e.front().value().unsafeGet(); michael@0: if (IsObjectAboutToBeFinalized(debugScope)) { michael@0: /* michael@0: * Note that onPopCall and onPopBlock rely on missingScopes to find michael@0: * scope objects that we synthesized for the debugger's sake, and michael@0: * clean up the synthetic scope objects' entries in liveScopes. So michael@0: * if we remove an entry frcom missingScopes here, we must also michael@0: * remove the corresponding liveScopes entry. michael@0: * michael@0: * Since the DebugScopeObject is the only thing using its scope michael@0: * object, and the DSO is about to be finalized, you might assume michael@0: * that the synthetic SO is also about to be finalized too, and thus michael@0: * the loop below will take care of things. But complex GC behavior michael@0: * means that marks are only conservative approximations of michael@0: * liveness; we should assume that anything could be marked. michael@0: * michael@0: * Thus, we must explicitly remove the entries from both liveScopes michael@0: * and missingScopes here. michael@0: */ michael@0: liveScopes.remove(&(*debugScope)->scope()); michael@0: e.removeFront(); michael@0: } michael@0: } michael@0: michael@0: for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) { michael@0: ScopeObject *scope = e.front().key(); michael@0: michael@0: /* michael@0: * Scopes can be finalized when a debugger-synthesized ScopeObject is michael@0: * no longer reachable via its DebugScopeObject. michael@0: */ michael@0: if (IsObjectAboutToBeFinalized(&scope)) { michael@0: e.removeFront(); michael@0: continue; michael@0: } michael@0: } michael@0: } michael@0: michael@0: #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) michael@0: void michael@0: DebugScopes::checkHashTablesAfterMovingGC(JSRuntime *runtime) michael@0: { michael@0: /* michael@0: * This is called at the end of StoreBuffer::mark() to check that our michael@0: * postbarriers have worked and that no hashtable keys (or values) are left michael@0: * pointing into the nursery. michael@0: */ michael@0: JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtime); michael@0: for (ObjectWeakMap::Range r = proxiedScopes.all(); !r.empty(); r.popFront()) { michael@0: JS_ASSERT(!IsInsideNursery(rt, r.front().key().get())); michael@0: JS_ASSERT(!IsInsideNursery(rt, r.front().value().get())); michael@0: } michael@0: for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) { michael@0: JS_ASSERT(!IsInsideNursery(rt, r.front().key().cur())); michael@0: JS_ASSERT(!IsInsideNursery(rt, r.front().key().staticScope())); michael@0: JS_ASSERT(!IsInsideNursery(rt, r.front().value().get())); michael@0: } michael@0: for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) { michael@0: JS_ASSERT(!IsInsideNursery(rt, r.front().key())); michael@0: JS_ASSERT(!IsInsideNursery(rt, r.front().value().cur_.get())); michael@0: JS_ASSERT(!IsInsideNursery(rt, r.front().value().staticScope_.get())); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: /* michael@0: * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode michael@0: * (in particular, JS_GetFrameScopeChain does not require debug mode). Since michael@0: * DebugScopes::onPop* are only called in debug mode, this means we cannot michael@0: * use any of the maps in DebugScopes. This will produce debug scope chains michael@0: * that do not obey the debugger invariants but that is just fine. michael@0: */ michael@0: static bool michael@0: CanUseDebugScopeMaps(JSContext *cx) michael@0: { michael@0: return cx->compartment()->debugMode(); michael@0: } michael@0: michael@0: DebugScopes * michael@0: DebugScopes::ensureCompartmentData(JSContext *cx) michael@0: { michael@0: JSCompartment *c = cx->compartment(); michael@0: if (c->debugScopes) michael@0: return c->debugScopes; michael@0: michael@0: c->debugScopes = cx->runtime()->new_(cx); michael@0: if (c->debugScopes && c->debugScopes->init()) michael@0: return c->debugScopes; michael@0: michael@0: js_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: michael@0: DebugScopeObject * michael@0: DebugScopes::hasDebugScope(JSContext *cx, ScopeObject &scope) michael@0: { michael@0: DebugScopes *scopes = scope.compartment()->debugScopes; michael@0: if (!scopes) michael@0: return nullptr; michael@0: michael@0: if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&scope)) { michael@0: JS_ASSERT(CanUseDebugScopeMaps(cx)); michael@0: return &p->value()->as(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: DebugScopes::addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope) michael@0: { michael@0: JS_ASSERT(cx->compartment() == scope.compartment()); michael@0: JS_ASSERT(cx->compartment() == debugScope.compartment()); michael@0: michael@0: if (!CanUseDebugScopeMaps(cx)) michael@0: return true; michael@0: michael@0: DebugScopes *scopes = ensureCompartmentData(cx); michael@0: if (!scopes) michael@0: return false; michael@0: michael@0: JS_ASSERT(!scopes->proxiedScopes.has(&scope)); michael@0: if (!scopes->proxiedScopes.put(&scope, &debugScope)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: proxiedScopesPostWriteBarrier(cx->runtime(), &scopes->proxiedScopes, &scope); michael@0: return true; michael@0: } michael@0: michael@0: DebugScopeObject * michael@0: DebugScopes::hasDebugScope(JSContext *cx, const ScopeIter &si) michael@0: { michael@0: JS_ASSERT(!si.hasScopeObject()); michael@0: michael@0: DebugScopes *scopes = cx->compartment()->debugScopes; michael@0: if (!scopes) michael@0: return nullptr; michael@0: michael@0: if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { michael@0: JS_ASSERT(CanUseDebugScopeMaps(cx)); michael@0: return p->value(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope) michael@0: { michael@0: JS_ASSERT(!si.hasScopeObject()); michael@0: JS_ASSERT(cx->compartment() == debugScope.compartment()); michael@0: JS_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator()); michael@0: michael@0: if (!CanUseDebugScopeMaps(cx)) michael@0: return true; michael@0: michael@0: DebugScopes *scopes = ensureCompartmentData(cx); michael@0: if (!scopes) michael@0: return false; michael@0: michael@0: JS_ASSERT(!scopes->missingScopes.has(si)); michael@0: if (!scopes->missingScopes.put(si, &debugScope)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: missingScopesPostWriteBarrier(cx->runtime(), &scopes->missingScopes, si); michael@0: michael@0: JS_ASSERT(!scopes->liveScopes.has(&debugScope.scope())); michael@0: if (!scopes->liveScopes.put(&debugScope.scope(), si)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &debugScope.scope()); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: DebugScopes::onPopCall(AbstractFramePtr frame, JSContext *cx) michael@0: { michael@0: JS_ASSERT(!frame.isYielding()); michael@0: assertSameCompartment(cx, frame); michael@0: michael@0: DebugScopes *scopes = cx->compartment()->debugScopes; michael@0: if (!scopes) michael@0: return; michael@0: michael@0: Rooted debugScope(cx, nullptr); michael@0: michael@0: if (frame.fun()->isHeavyweight()) { michael@0: /* michael@0: * The frame may be observed before the prologue has created the michael@0: * CallObject. See ScopeIter::settle. michael@0: */ michael@0: if (!frame.hasCallObj()) michael@0: return; michael@0: michael@0: CallObject &callobj = frame.scopeChain()->as(); michael@0: scopes->liveScopes.remove(&callobj); michael@0: if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj)) michael@0: debugScope = &p->value()->as(); michael@0: } else { michael@0: ScopeIter si(frame, frame.script()->main(), cx); michael@0: if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { michael@0: debugScope = p->value(); michael@0: scopes->liveScopes.remove(&debugScope->scope().as()); michael@0: scopes->missingScopes.remove(p); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * When the JS stack frame is popped, the values of unaliased variables michael@0: * are lost. If there is any debug scope referring to this scope, save a michael@0: * copy of the unaliased variables' values in an array for later debugger michael@0: * access via DebugScopeProxy::handleUnaliasedAccess. michael@0: * michael@0: * Note: since it is simplest for this function to be infallible, failure michael@0: * in this code will be silently ignored. This does not break any michael@0: * invariants since DebugScopeObject::maybeSnapshot can already be nullptr. michael@0: */ michael@0: if (debugScope) { michael@0: /* michael@0: * Copy all frame values into the snapshot, regardless of michael@0: * aliasing. This unnecessarily includes aliased variables michael@0: * but it simplifies later indexing logic. michael@0: */ michael@0: AutoValueVector vec(cx); michael@0: if (!frame.copyRawFrameSlots(&vec) || vec.length() == 0) michael@0: return; michael@0: michael@0: /* michael@0: * Copy in formals that are not aliased via the scope chain michael@0: * but are aliased via the arguments object. michael@0: */ michael@0: RootedScript script(cx, frame.script()); michael@0: if (script->analyzedArgsUsage() && script->needsArgsObj() && frame.hasArgsObj()) { michael@0: for (unsigned i = 0; i < frame.numFormalArgs(); ++i) { michael@0: if (script->formalLivesInArgumentsObject(i)) michael@0: vec[i] = frame.argsObj().arg(i); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Use a dense array as storage (since proxies do not have trace michael@0: * hooks). This array must not escape into the wild. michael@0: */ michael@0: RootedObject snapshot(cx, NewDenseCopiedArray(cx, vec.length(), vec.begin())); michael@0: if (!snapshot) { michael@0: cx->clearPendingException(); michael@0: return; michael@0: } michael@0: michael@0: debugScope->initSnapshot(*snapshot); michael@0: } michael@0: } michael@0: michael@0: void michael@0: DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc) michael@0: { michael@0: assertSameCompartment(cx, frame); michael@0: michael@0: DebugScopes *scopes = cx->compartment()->debugScopes; michael@0: if (!scopes) michael@0: return; michael@0: michael@0: ScopeIter si(frame, pc, cx); michael@0: onPopBlock(cx, si); michael@0: } michael@0: michael@0: void michael@0: DebugScopes::onPopBlock(JSContext *cx, const ScopeIter &si) michael@0: { michael@0: DebugScopes *scopes = cx->compartment()->debugScopes; michael@0: if (!scopes) michael@0: return; michael@0: michael@0: JS_ASSERT(si.type() == ScopeIter::Block); michael@0: michael@0: if (si.staticBlock().needsClone()) { michael@0: ClonedBlockObject &clone = si.scope().as(); michael@0: clone.copyUnaliasedValues(si.frame()); michael@0: scopes->liveScopes.remove(&clone); michael@0: } else { michael@0: if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { michael@0: ClonedBlockObject &clone = p->value()->scope().as(); michael@0: clone.copyUnaliasedValues(si.frame()); michael@0: scopes->liveScopes.remove(&clone); michael@0: scopes->missingScopes.remove(p); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: DebugScopes::onPopWith(AbstractFramePtr frame) michael@0: { michael@0: DebugScopes *scopes = frame.compartment()->debugScopes; michael@0: if (scopes) michael@0: scopes->liveScopes.remove(&frame.scopeChain()->as()); michael@0: } michael@0: michael@0: void michael@0: DebugScopes::onPopStrictEvalScope(AbstractFramePtr frame) michael@0: { michael@0: DebugScopes *scopes = frame.compartment()->debugScopes; michael@0: if (!scopes) michael@0: return; michael@0: michael@0: /* michael@0: * The stack frame may be observed before the prologue has created the michael@0: * CallObject. See ScopeIter::settle. michael@0: */ michael@0: if (frame.hasCallObj()) michael@0: scopes->liveScopes.remove(&frame.scopeChain()->as()); michael@0: } michael@0: michael@0: void michael@0: DebugScopes::onCompartmentLeaveDebugMode(JSCompartment *c) michael@0: { michael@0: DebugScopes *scopes = c->debugScopes; michael@0: if (scopes) { michael@0: scopes->proxiedScopes.clear(); michael@0: scopes->missingScopes.clear(); michael@0: scopes->liveScopes.clear(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: DebugScopes::updateLiveScopes(JSContext *cx) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: /* michael@0: * Note that we must always update the top frame's scope objects' entries michael@0: * in liveScopes because we can't be sure code hasn't run in that frame to michael@0: * change the scope chain since we were last called. The fp->prevUpToDate() michael@0: * flag indicates whether the scopes of frames older than fp are already michael@0: * included in liveScopes. It might seem simpler to have fp instead carry a michael@0: * flag indicating whether fp itself is accurately described, but then we michael@0: * would need to clear that flag whenever fp ran code. By storing the 'up michael@0: * to date' bit for fp->prev() in fp, simply popping fp effectively clears michael@0: * the flag for us, at exactly the time when execution resumes fp->prev(). michael@0: */ michael@0: for (AllFramesIter i(cx); !i.done(); ++i) { michael@0: if (!i.hasUsableAbstractFramePtr()) michael@0: continue; michael@0: michael@0: AbstractFramePtr frame = i.abstractFramePtr(); michael@0: if (frame.scopeChain()->compartment() != cx->compartment()) michael@0: continue; michael@0: michael@0: if (frame.isFunctionFrame() && frame.callee()->isGenerator()) michael@0: continue; michael@0: michael@0: for (ScopeIter si(frame, i.pc(), cx); !si.done(); ++si) { michael@0: if (si.hasScopeObject()) { michael@0: JS_ASSERT(si.scope().compartment() == cx->compartment()); michael@0: DebugScopes *scopes = ensureCompartmentData(cx); michael@0: if (!scopes) michael@0: return false; michael@0: if (!scopes->liveScopes.put(&si.scope(), si)) michael@0: return false; michael@0: liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope()); michael@0: } michael@0: } michael@0: michael@0: if (frame.prevUpToDate()) michael@0: return true; michael@0: JS_ASSERT(frame.scopeChain()->compartment()->debugMode()); michael@0: frame.setPrevUpToDate(); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: ScopeIterVal* michael@0: DebugScopes::hasLiveScope(ScopeObject &scope) michael@0: { michael@0: DebugScopes *scopes = scope.compartment()->debugScopes; michael@0: if (!scopes) michael@0: return nullptr; michael@0: michael@0: if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope)) michael@0: return &p->value(); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: /*****************************************************************************/ michael@0: michael@0: static JSObject * michael@0: GetDebugScope(JSContext *cx, const ScopeIter &si); michael@0: michael@0: static DebugScopeObject * michael@0: GetDebugScopeForScope(JSContext *cx, Handle scope, const ScopeIter &enclosing) michael@0: { michael@0: if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, *scope)) michael@0: return debugScope; michael@0: michael@0: RootedObject enclosingDebug(cx, GetDebugScope(cx, enclosing)); michael@0: if (!enclosingDebug) michael@0: return nullptr; michael@0: michael@0: JSObject &maybeDecl = scope->enclosingScope(); michael@0: if (maybeDecl.is()) { michael@0: JS_ASSERT(CallObjectLambdaName(scope->as().callee())); michael@0: enclosingDebug = DebugScopeObject::create(cx, maybeDecl.as(), enclosingDebug); michael@0: if (!enclosingDebug) michael@0: return nullptr; michael@0: } michael@0: michael@0: DebugScopeObject *debugScope = DebugScopeObject::create(cx, *scope, enclosingDebug); michael@0: if (!debugScope) michael@0: return nullptr; michael@0: michael@0: if (!DebugScopes::addDebugScope(cx, *scope, *debugScope)) michael@0: return nullptr; michael@0: michael@0: return debugScope; michael@0: } michael@0: michael@0: static DebugScopeObject * michael@0: GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si) michael@0: { michael@0: if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, si)) michael@0: return debugScope; michael@0: michael@0: ScopeIter copy(si, cx); michael@0: RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy)); michael@0: if (!enclosingDebug) michael@0: return nullptr; michael@0: michael@0: /* michael@0: * Create the missing scope object. For block objects, this takes care of michael@0: * storing variable values after the stack frame has been popped. For call michael@0: * objects, we only use the pretend call object to access callee, bindings michael@0: * and to receive dynamically added properties. Together, this provides the michael@0: * nice invariant that every DebugScopeObject has a ScopeObject. michael@0: * michael@0: * Note: to preserve scopeChain depth invariants, these lazily-reified michael@0: * scopes must not be put on the frame's scope chain; instead, they are michael@0: * maintained via DebugScopes hooks. michael@0: */ michael@0: DebugScopeObject *debugScope = nullptr; michael@0: switch (si.type()) { michael@0: case ScopeIter::Call: { michael@0: // Generators should always reify their scopes. michael@0: JS_ASSERT(!si.frame().callee()->isGenerator()); michael@0: Rooted callobj(cx, CallObject::createForFunction(cx, si.frame())); michael@0: if (!callobj) michael@0: return nullptr; michael@0: michael@0: if (callobj->enclosingScope().is()) { michael@0: JS_ASSERT(CallObjectLambdaName(callobj->callee())); michael@0: DeclEnvObject &declenv = callobj->enclosingScope().as(); michael@0: enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug); michael@0: if (!enclosingDebug) michael@0: return nullptr; michael@0: } michael@0: michael@0: debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug); michael@0: break; michael@0: } michael@0: case ScopeIter::Block: { michael@0: // Generators should always reify their scopes. michael@0: JS_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator()); michael@0: Rooted staticBlock(cx, &si.staticBlock()); michael@0: ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.frame()); michael@0: if (!block) michael@0: return nullptr; michael@0: michael@0: debugScope = DebugScopeObject::create(cx, *block, enclosingDebug); michael@0: break; michael@0: } michael@0: case ScopeIter::With: michael@0: case ScopeIter::StrictEvalScope: michael@0: MOZ_ASSUME_UNREACHABLE("should already have a scope"); michael@0: } michael@0: if (!debugScope) michael@0: return nullptr; michael@0: michael@0: if (!DebugScopes::addDebugScope(cx, si, *debugScope)) michael@0: return nullptr; michael@0: michael@0: return debugScope; michael@0: } michael@0: michael@0: static JSObject * michael@0: GetDebugScope(JSContext *cx, JSObject &obj) michael@0: { michael@0: /* michael@0: * As an engine invariant (maintained internally and asserted by Execute), michael@0: * ScopeObjects and non-ScopeObjects cannot be interleaved on the scope michael@0: * chain; every scope chain must start with zero or more ScopeObjects and michael@0: * terminate with one or more non-ScopeObjects (viz., GlobalObject). michael@0: */ michael@0: if (!obj.is()) { michael@0: #ifdef DEBUG michael@0: JSObject *o = &obj; michael@0: while ((o = o->enclosingScope())) michael@0: JS_ASSERT(!o->is()); michael@0: #endif michael@0: return &obj; michael@0: } michael@0: michael@0: Rooted scope(cx, &obj.as()); michael@0: if (ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope)) { michael@0: ScopeIter si(*maybeLiveScope, cx); michael@0: return GetDebugScope(cx, si); michael@0: } michael@0: ScopeIter si(scope->enclosingScope(), cx); michael@0: return GetDebugScopeForScope(cx, scope, si); michael@0: } michael@0: michael@0: static JSObject * michael@0: GetDebugScope(JSContext *cx, const ScopeIter &si) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return nullptr); michael@0: michael@0: if (si.done()) michael@0: return GetDebugScope(cx, si.enclosingScope()); michael@0: michael@0: if (!si.hasScopeObject()) michael@0: return GetDebugScopeForMissing(cx, si); michael@0: michael@0: Rooted scope(cx, &si.scope()); michael@0: michael@0: ScopeIter copy(si, cx); michael@0: return GetDebugScopeForScope(cx, scope, ++copy); michael@0: } michael@0: michael@0: JSObject * michael@0: js::GetDebugScopeForFunction(JSContext *cx, HandleFunction fun) michael@0: { michael@0: assertSameCompartment(cx, fun); michael@0: JS_ASSERT(cx->compartment()->debugMode()); michael@0: if (!DebugScopes::updateLiveScopes(cx)) michael@0: return nullptr; michael@0: return GetDebugScope(cx, *fun->environment()); michael@0: } michael@0: michael@0: JSObject * michael@0: js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc) michael@0: { michael@0: assertSameCompartment(cx, frame); michael@0: if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx)) michael@0: return nullptr; michael@0: ScopeIter si(frame, pc, cx); michael@0: return GetDebugScope(cx, si); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: michael@0: typedef HashSet PropertyNameSet; michael@0: michael@0: static bool michael@0: RemoveReferencedNames(JSContext *cx, HandleScript script, PropertyNameSet &remainingNames) michael@0: { michael@0: // Remove from remainingNames --- the closure variables in some outer michael@0: // script --- any free variables in this script. This analysis isn't perfect: michael@0: // michael@0: // - It will not account for free variables in an inner script which are michael@0: // actually accessing some name in an intermediate script between the michael@0: // inner and outer scripts. This can cause remainingNames to be an michael@0: // underapproximation. michael@0: // michael@0: // - It will not account for new names introduced via eval. This can cause michael@0: // remainingNames to be an overapproximation. This would be easy to fix michael@0: // but is nice to have as the eval will probably not access these michael@0: // these names and putting eval in an inner script is bad news if you michael@0: // care about entraining variables unnecessarily. michael@0: michael@0: for (jsbytecode *pc = script->code(); pc != script->codeEnd(); pc += GetBytecodeLength(pc)) { michael@0: PropertyName *name; michael@0: michael@0: switch (JSOp(*pc)) { michael@0: case JSOP_NAME: michael@0: case JSOP_SETNAME: michael@0: name = script->getName(pc); michael@0: break; michael@0: michael@0: case JSOP_GETALIASEDVAR: michael@0: case JSOP_SETALIASEDVAR: michael@0: name = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc); michael@0: break; michael@0: michael@0: default: michael@0: name = nullptr; michael@0: break; michael@0: } michael@0: michael@0: if (name) michael@0: remainingNames.remove(name); michael@0: } michael@0: michael@0: if (script->hasObjects()) { michael@0: ObjectArray *objects = script->objects(); michael@0: for (size_t i = 0; i < objects->length; i++) { michael@0: JSObject *obj = objects->vector[i]; michael@0: if (obj->is() && obj->as().isInterpreted()) { michael@0: JSFunction *fun = &obj->as(); michael@0: RootedScript innerScript(cx, fun->getOrCreateScript(cx)); michael@0: if (!innerScript) michael@0: return false; michael@0: michael@0: if (!RemoveReferencedNames(cx, innerScript, remainingNames)) michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: AnalyzeEntrainedVariablesInScript(JSContext *cx, HandleScript script, HandleScript innerScript) michael@0: { michael@0: PropertyNameSet remainingNames(cx); michael@0: if (!remainingNames.init()) michael@0: return false; michael@0: michael@0: for (BindingIter bi(script); bi; bi++) { michael@0: if (bi->aliased()) { michael@0: PropertyNameSet::AddPtr p = remainingNames.lookupForAdd(bi->name()); michael@0: if (!p && !remainingNames.add(p, bi->name())) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!RemoveReferencedNames(cx, innerScript, remainingNames)) michael@0: return false; michael@0: michael@0: if (!remainingNames.empty()) { michael@0: Sprinter buf(cx); michael@0: if (!buf.init()) michael@0: return false; michael@0: michael@0: buf.printf("Script "); michael@0: michael@0: if (JSAtom *name = script->functionNonDelazifying()->displayAtom()) { michael@0: buf.putString(name); michael@0: buf.printf(" "); michael@0: } michael@0: michael@0: buf.printf("(%s:%d) has variables entrained by ", script->filename(), script->lineno()); michael@0: michael@0: if (JSAtom *name = innerScript->functionNonDelazifying()->displayAtom()) { michael@0: buf.putString(name); michael@0: buf.printf(" "); michael@0: } michael@0: michael@0: buf.printf("(%s:%d) ::", innerScript->filename(), innerScript->lineno()); michael@0: michael@0: for (PropertyNameSet::Range r = remainingNames.all(); !r.empty(); r.popFront()) { michael@0: buf.printf(" "); michael@0: buf.putString(r.front()); michael@0: } michael@0: michael@0: printf("%s\n", buf.string()); michael@0: } michael@0: michael@0: if (innerScript->hasObjects()) { michael@0: ObjectArray *objects = innerScript->objects(); michael@0: for (size_t i = 0; i < objects->length; i++) { michael@0: JSObject *obj = objects->vector[i]; michael@0: if (obj->is() && obj->as().isInterpreted()) { michael@0: JSFunction *fun = &obj->as(); michael@0: RootedScript innerInnerScript(cx, fun->getOrCreateScript(cx)); michael@0: if (!innerInnerScript || michael@0: !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Look for local variables in script or any other script inner to it, which are michael@0: // part of the script's call object and are unnecessarily entrained by their own michael@0: // inner scripts which do not refer to those variables. An example is: michael@0: // michael@0: // function foo() { michael@0: // var a, b; michael@0: // function bar() { return a; } michael@0: // function baz() { return b; } michael@0: // } michael@0: // michael@0: // |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|. michael@0: bool michael@0: js::AnalyzeEntrainedVariables(JSContext *cx, HandleScript script) michael@0: { michael@0: if (!script->hasObjects()) michael@0: return true; michael@0: michael@0: ObjectArray *objects = script->objects(); michael@0: for (size_t i = 0; i < objects->length; i++) { michael@0: JSObject *obj = objects->vector[i]; michael@0: if (obj->is() && obj->as().isInterpreted()) { michael@0: JSFunction *fun = &obj->as(); michael@0: RootedScript innerScript(cx, fun->getOrCreateScript(cx)); michael@0: if (!innerScript) michael@0: return false; michael@0: michael@0: if (script->functionDelazifying() && script->functionDelazifying()->isHeavyweight()) { michael@0: if (!AnalyzeEntrainedVariablesInScript(cx, script, innerScript)) michael@0: return false; michael@0: } michael@0: michael@0: if (!AnalyzeEntrainedVariables(cx, innerScript)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: #endif