| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "mozilla/PodOperations.h" |
| |
| #include "jscompartment.h" |
| #include "jsiter.h" |
| |
| #include "GlobalObject.h" |
| #include "ScopeObject.h" |
| #include "Shape.h" |
| #include "Xdr.h" |
| |
| #include "jsatominlines.h" |
| #include "jsobjinlines.h" |
| |
| #include "gc/Barrier-inl.h" |
| #include "vm/ScopeObject-inl.h" |
| |
| using namespace js; |
| using namespace js::types; |
| |
| using mozilla::PodZero; |
| |
| typedef Rooted<ArgumentsObject *> RootedArgumentsObject; |
| |
| /*****************************************************************************/ |
| |
| StaticScopeIter::StaticScopeIter(JSContext *cx, JSObject *objArg) |
| : obj(cx, objArg), onNamedLambda(false) |
| { |
| JS_ASSERT_IF(obj, obj->is<StaticBlockObject>() || obj->is<JSFunction>()); |
| } |
| |
| bool |
| StaticScopeIter::done() const |
| { |
| return !obj; |
| } |
| |
| void |
| StaticScopeIter::operator++(int) |
| { |
| if (obj->is<StaticBlockObject>()) { |
| obj = obj->as<StaticBlockObject>().enclosingStaticScope(); |
| } else if (onNamedLambda || !obj->as<JSFunction>().isNamedLambda()) { |
| onNamedLambda = false; |
| obj = obj->as<JSFunction>().nonLazyScript()->enclosingStaticScope(); |
| } else { |
| onNamedLambda = true; |
| } |
| JS_ASSERT_IF(obj, obj->is<StaticBlockObject>() || obj->is<JSFunction>()); |
| JS_ASSERT_IF(onNamedLambda, obj->is<JSFunction>()); |
| } |
| |
| bool |
| StaticScopeIter::hasDynamicScopeObject() const |
| { |
| return obj->is<StaticBlockObject>() |
| ? obj->as<StaticBlockObject>().needsClone() |
| : obj->as<JSFunction>().isHeavyweight(); |
| } |
| |
| Shape * |
| StaticScopeIter::scopeShape() const |
| { |
| JS_ASSERT(hasDynamicScopeObject()); |
| JS_ASSERT(type() != NAMED_LAMBDA); |
| return type() == BLOCK |
| ? block().lastProperty() |
| : funScript()->bindings.callObjShape(); |
| } |
| |
| StaticScopeIter::Type |
| StaticScopeIter::type() const |
| { |
| if (onNamedLambda) |
| return NAMED_LAMBDA; |
| return obj->is<StaticBlockObject>() ? BLOCK : FUNCTION; |
| } |
| |
| StaticBlockObject & |
| StaticScopeIter::block() const |
| { |
| JS_ASSERT(type() == BLOCK); |
| return obj->as<StaticBlockObject>(); |
| } |
| |
| JSScript * |
| StaticScopeIter::funScript() const |
| { |
| JS_ASSERT(type() == FUNCTION); |
| return obj->as<JSFunction>().nonLazyScript(); |
| } |
| |
| /*****************************************************************************/ |
| |
| static JSObject * |
| InnermostStaticScope(JSScript *script, jsbytecode *pc) |
| { |
| JS_ASSERT(pc >= script->code && pc < script->code + script->length); |
| JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD); |
| |
| uint32_t blockIndex = GET_UINT32_INDEX(pc + 2 * sizeof(uint16_t)); |
| |
| if (blockIndex == UINT32_MAX) |
| return script->function(); |
| return &script->getObject(blockIndex)->as<StaticBlockObject>(); |
| } |
| |
| Shape * |
| js::ScopeCoordinateToStaticScopeShape(JSContext *cx, JSScript *script, jsbytecode *pc) |
| { |
| StaticScopeIter ssi(cx, InnermostStaticScope(script, pc)); |
| ScopeCoordinate sc(pc); |
| while (true) { |
| if (ssi.hasDynamicScopeObject()) { |
| if (!sc.hops) |
| break; |
| sc.hops--; |
| } |
| ssi++; |
| } |
| return ssi.scopeShape(); |
| } |
| |
| PropertyName * |
| js::ScopeCoordinateName(JSContext *cx, JSScript *script, jsbytecode *pc) |
| { |
| Shape::Range<NoGC> r(ScopeCoordinateToStaticScopeShape(cx, script, pc)); |
| ScopeCoordinate sc(pc); |
| while (r.front().slot() != sc.slot) |
| r.popFront(); |
| jsid id = r.front().propid(); |
| |
| /* Beware nameless destructuring formal. */ |
| if (!JSID_IS_ATOM(id)) |
| return cx->runtime()->atomState.empty; |
| return JSID_TO_ATOM(id)->asPropertyName(); |
| } |
| |
| JSScript * |
| js::ScopeCoordinateFunctionScript(JSContext *cx, JSScript *script, jsbytecode *pc) |
| { |
| StaticScopeIter ssi(cx, InnermostStaticScope(script, pc)); |
| ScopeCoordinate sc(pc); |
| while (true) { |
| if (ssi.hasDynamicScopeObject()) { |
| if (!sc.hops) |
| break; |
| sc.hops--; |
| } |
| ssi++; |
| } |
| if (ssi.type() != StaticScopeIter::FUNCTION) |
| return NULL; |
| return ssi.funScript(); |
| } |
| |
| /*****************************************************************************/ |
| |
| /* |
| * Construct a bare-bones call object given a shape, type, and slots pointer. |
| * The call object must be further initialized to be usable. |
| */ |
| CallObject * |
| CallObject::create(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type, HeapSlot *slots) |
| { |
| gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); |
| JS_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_)); |
| kind = gc::GetBackgroundAllocKind(kind); |
| |
| gc::InitialHeap heap = script->treatAsRunOnce ? gc::TenuredHeap : gc::DefaultHeap; |
| JSObject *obj = JSObject::create(cx, kind, heap, shape, type, slots); |
| if (!obj) |
| return NULL; |
| |
| if (script->treatAsRunOnce) { |
| RootedObject nobj(cx, obj); |
| if (!JSObject::setSingletonType(cx, nobj)) |
| return NULL; |
| return &nobj->as<CallObject>(); |
| } |
| |
| return &obj->as<CallObject>(); |
| } |
| |
| /* |
| * Create a CallObject for a JSScript that is not initialized to any particular |
| * callsite. This object can either be initialized (with an enclosing scope and |
| * callee) or used as a template for jit compilation. |
| */ |
| CallObject * |
| CallObject::createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap) |
| { |
| RootedShape shape(cx, script->bindings.callObjShape()); |
| JS_ASSERT(shape->getObjectClass() == &class_); |
| |
| RootedTypeObject type(cx, cx->compartment()->getNewType(cx, &class_, NULL)); |
| if (!type) |
| return NULL; |
| |
| gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); |
| JS_ASSERT(CanBeFinalizedInBackground(kind, &class_)); |
| kind = gc::GetBackgroundAllocKind(kind); |
| |
| JSObject *obj = JSObject::create(cx, kind, heap, shape, type); |
| if (!obj) |
| return NULL; |
| |
| return &obj->as<CallObject>(); |
| } |
| |
| /* |
| * Construct a call object for the given bindings. If this is a call object |
| * for a function invocation, callee should be the function being called. |
| * Otherwise it must be a call object for eval of strict mode code, and callee |
| * must be null. |
| */ |
| CallObject * |
| CallObject::create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee) |
| { |
| gc::InitialHeap heap = script->treatAsRunOnce ? gc::TenuredHeap : gc::DefaultHeap; |
| CallObject *callobj = CallObject::createTemplateObject(cx, script, heap); |
| if (!callobj) |
| return NULL; |
| |
| callobj->as<ScopeObject>().setEnclosingScope(enclosing); |
| callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee)); |
| |
| if (script->treatAsRunOnce) { |
| Rooted<CallObject*> ncallobj(cx, callobj); |
| if (!JSObject::setSingletonType(cx, ncallobj)) |
| return NULL; |
| return ncallobj; |
| } |
| |
| return callobj; |
| } |
| |
| CallObject * |
| CallObject::createForFunction(JSContext *cx, HandleObject enclosing, HandleFunction callee) |
| { |
| RootedObject scopeChain(cx, enclosing); |
| JS_ASSERT(scopeChain); |
| |
| /* |
| * For a named function expression Call's parent points to an environment |
| * object holding function's name. |
| */ |
| if (callee->isNamedLambda()) { |
| scopeChain = DeclEnvObject::create(cx, scopeChain, callee); |
| if (!scopeChain) |
| return NULL; |
| } |
| |
| RootedScript script(cx, callee->nonLazyScript()); |
| return create(cx, script, scopeChain, callee); |
| } |
| |
| CallObject * |
| CallObject::createForFunction(JSContext *cx, AbstractFramePtr frame) |
| { |
| JS_ASSERT(frame.isNonEvalFunctionFrame()); |
| assertSameCompartment(cx, frame); |
| |
| RootedObject scopeChain(cx, frame.scopeChain()); |
| RootedFunction callee(cx, frame.callee()); |
| |
| CallObject *callobj = createForFunction(cx, scopeChain, callee); |
| if (!callobj) |
| return NULL; |
| |
| /* Copy in the closed-over formal arguments. */ |
| for (AliasedFormalIter i(frame.script()); i; i++) { |
| callobj->setAliasedVar(cx, i, i->name(), |
| frame.unaliasedFormal(i.frameIndex(), DONT_CHECK_ALIASING)); |
| } |
| |
| return callobj; |
| } |
| |
| CallObject * |
| CallObject::createForStrictEval(JSContext *cx, AbstractFramePtr frame) |
| { |
| JS_ASSERT(frame.isStrictEvalFrame()); |
| JS_ASSERT_IF(frame.isStackFrame(), cx->interpreterFrame() == frame.asStackFrame()); |
| JS_ASSERT_IF(frame.isStackFrame(), cx->interpreterRegs().pc == frame.script()->code); |
| |
| RootedFunction callee(cx); |
| RootedScript script(cx, frame.script()); |
| RootedObject scopeChain(cx, frame.scopeChain()); |
| return create(cx, script, scopeChain, callee); |
| } |
| |
| Class CallObject::class_ = { |
| "Call", |
| JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS), |
| JS_PropertyStub, /* addProperty */ |
| JS_DeletePropertyStub, /* delProperty */ |
| JS_PropertyStub, /* getProperty */ |
| JS_StrictPropertyStub, /* setProperty */ |
| JS_EnumerateStub, |
| JS_ResolveStub, |
| NULL /* convert: Leave it NULL so we notice if calls ever escape */ |
| }; |
| |
| Class DeclEnvObject::class_ = { |
| js_Object_str, |
| JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) | |
| JSCLASS_HAS_CACHED_PROTO(JSProto_Object), |
| JS_PropertyStub, /* addProperty */ |
| JS_DeletePropertyStub, /* delProperty */ |
| JS_PropertyStub, /* getProperty */ |
| JS_StrictPropertyStub, /* setProperty */ |
| JS_EnumerateStub, |
| JS_ResolveStub, |
| JS_ConvertStub |
| }; |
| |
| /* |
| * Create a DeclEnvObject for a JSScript that is not initialized to any |
| * particular callsite. This object can either be initialized (with an enclosing |
| * scope and callee) or used as a template for jit compilation. |
| */ |
| DeclEnvObject * |
| DeclEnvObject::createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap) |
| { |
| RootedTypeObject type(cx, cx->compartment()->getNewType(cx, &class_, NULL)); |
| if (!type) |
| return NULL; |
| |
| RootedShape emptyDeclEnvShape(cx); |
| emptyDeclEnvShape = EmptyShape::getInitialShape(cx, &class_, NULL, |
| cx->global(), NULL, FINALIZE_KIND, |
| BaseShape::DELEGATE); |
| if (!emptyDeclEnvShape) |
| return NULL; |
| |
| RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, heap, emptyDeclEnvShape, type)); |
| if (!obj) |
| return NULL; |
| |
| // Assign a fixed slot to a property with the same name as the lambda. |
| Rooted<jsid> id(cx, AtomToId(fun->atom())); |
| Class *clasp = obj->getClass(); |
| unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY; |
| if (!JSObject::putProperty(cx, obj, id, clasp->getProperty, clasp->setProperty, |
| lambdaSlot(), attrs, 0, 0)) |
| { |
| return NULL; |
| } |
| |
| JS_ASSERT(!obj->hasDynamicSlots()); |
| return &obj->as<DeclEnvObject>(); |
| } |
| |
| DeclEnvObject * |
| DeclEnvObject::create(JSContext *cx, HandleObject enclosing, HandleFunction callee) |
| { |
| RootedObject obj(cx, createTemplateObject(cx, callee, gc::DefaultHeap)); |
| if (!obj) |
| return NULL; |
| |
| obj->as<ScopeObject>().setEnclosingScope(enclosing); |
| obj->setFixedSlot(lambdaSlot(), ObjectValue(*callee)); |
| return &obj->as<DeclEnvObject>(); |
| } |
| |
| WithObject * |
| WithObject::create(JSContext *cx, HandleObject proto, HandleObject enclosing, uint32_t depth) |
| { |
| RootedTypeObject type(cx, proto->getNewType(cx, &class_)); |
| if (!type) |
| return NULL; |
| |
| RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(proto), |
| &enclosing->global(), NULL, FINALIZE_KIND)); |
| if (!shape) |
| return NULL; |
| |
| RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, type)); |
| if (!obj) |
| return NULL; |
| |
| obj->as<ScopeObject>().setEnclosingScope(enclosing); |
| obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth)); |
| |
| JSObject *thisp = JSObject::thisObject(cx, proto); |
| if (!thisp) |
| return NULL; |
| |
| obj->setFixedSlot(THIS_SLOT, ObjectValue(*thisp)); |
| |
| return &obj->as<WithObject>(); |
| } |
| |
| static JSBool |
| with_LookupGeneric(JSContext *cx, HandleObject obj, HandleId id, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::lookupGeneric(cx, actual, id, objp, propp); |
| } |
| |
| static JSBool |
| with_LookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return with_LookupGeneric(cx, obj, id, objp, propp); |
| } |
| |
| static JSBool |
| with_LookupElement(JSContext *cx, HandleObject obj, uint32_t index, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| RootedId id(cx); |
| if (!IndexToId(cx, index, &id)) |
| return false; |
| return with_LookupGeneric(cx, obj, id, objp, propp); |
| } |
| |
| static JSBool |
| with_LookupSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return with_LookupGeneric(cx, obj, id, objp, propp); |
| } |
| |
| static JSBool |
| with_GetGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id, |
| MutableHandleValue vp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::getGeneric(cx, actual, actual, id, vp); |
| } |
| |
| static JSBool |
| with_GetProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name, |
| MutableHandleValue vp) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return with_GetGeneric(cx, obj, receiver, id, vp); |
| } |
| |
| static JSBool |
| with_GetElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, |
| MutableHandleValue vp) |
| { |
| RootedId id(cx); |
| if (!IndexToId(cx, index, &id)) |
| return false; |
| return with_GetGeneric(cx, obj, receiver, id, vp); |
| } |
| |
| static JSBool |
| with_GetSpecial(JSContext *cx, HandleObject obj, HandleObject receiver, HandleSpecialId sid, |
| MutableHandleValue vp) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return with_GetGeneric(cx, obj, receiver, id, vp); |
| } |
| |
| static JSBool |
| with_SetGeneric(JSContext *cx, HandleObject obj, HandleId id, |
| MutableHandleValue vp, JSBool strict) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::setGeneric(cx, actual, actual, id, vp, strict); |
| } |
| |
| static JSBool |
| with_SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| MutableHandleValue vp, JSBool strict) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::setProperty(cx, actual, actual, name, vp, strict); |
| } |
| |
| static JSBool |
| with_SetElement(JSContext *cx, HandleObject obj, uint32_t index, |
| MutableHandleValue vp, JSBool strict) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::setElement(cx, actual, actual, index, vp, strict); |
| } |
| |
| static JSBool |
| with_SetSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, |
| MutableHandleValue vp, JSBool strict) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::setSpecial(cx, actual, actual, sid, vp, strict); |
| } |
| |
| static JSBool |
| with_GetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::getGenericAttributes(cx, actual, id, attrsp); |
| } |
| |
| static JSBool |
| with_GetPropertyAttributes(JSContext *cx, HandleObject obj, HandlePropertyName name, unsigned *attrsp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::getPropertyAttributes(cx, actual, name, attrsp); |
| } |
| |
| static JSBool |
| with_GetElementAttributes(JSContext *cx, HandleObject obj, uint32_t index, unsigned *attrsp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::getElementAttributes(cx, actual, index, attrsp); |
| } |
| |
| static JSBool |
| with_GetSpecialAttributes(JSContext *cx, HandleObject obj, HandleSpecialId sid, unsigned *attrsp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::getSpecialAttributes(cx, actual, sid, attrsp); |
| } |
| |
| static JSBool |
| with_SetGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::setGenericAttributes(cx, actual, id, attrsp); |
| } |
| |
| static JSBool |
| with_SetPropertyAttributes(JSContext *cx, HandleObject obj, HandlePropertyName name, unsigned *attrsp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::setPropertyAttributes(cx, actual, name, attrsp); |
| } |
| |
| static JSBool |
| with_SetElementAttributes(JSContext *cx, HandleObject obj, uint32_t index, unsigned *attrsp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::setElementAttributes(cx, actual, index, attrsp); |
| } |
| |
| static JSBool |
| with_SetSpecialAttributes(JSContext *cx, HandleObject obj, HandleSpecialId sid, unsigned *attrsp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::setSpecialAttributes(cx, actual, sid, attrsp); |
| } |
| |
| static JSBool |
| with_DeleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| JSBool *succeeded) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::deleteProperty(cx, actual, name, succeeded); |
| } |
| |
| static JSBool |
| with_DeleteElement(JSContext *cx, HandleObject obj, uint32_t index, |
| JSBool *succeeded) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::deleteElement(cx, actual, index, succeeded); |
| } |
| |
| static JSBool |
| with_DeleteSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, |
| JSBool *succeeded) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::deleteSpecial(cx, actual, sid, succeeded); |
| } |
| |
| static JSBool |
| with_Enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op, |
| MutableHandleValue statep, MutableHandleId idp) |
| { |
| RootedObject actual(cx, &obj->as<WithObject>().object()); |
| return JSObject::enumerate(cx, actual, enum_op, statep, idp); |
| } |
| |
| static JSObject * |
| with_ThisObject(JSContext *cx, HandleObject obj) |
| { |
| return &obj->as<WithObject>().withThis(); |
| } |
| |
| Class WithObject::class_ = { |
| "With", |
| JSCLASS_HAS_RESERVED_SLOTS(WithObject::RESERVED_SLOTS) | |
| JSCLASS_IS_ANONYMOUS, |
| JS_PropertyStub, /* addProperty */ |
| JS_DeletePropertyStub, /* delProperty */ |
| JS_PropertyStub, /* getProperty */ |
| JS_StrictPropertyStub, /* setProperty */ |
| JS_EnumerateStub, |
| JS_ResolveStub, |
| JS_ConvertStub, |
| NULL, /* finalize */ |
| NULL, /* checkAccess */ |
| NULL, /* call */ |
| NULL, /* construct */ |
| NULL, /* hasInstance */ |
| NULL, /* trace */ |
| JS_NULL_CLASS_EXT, |
| { |
| with_LookupGeneric, |
| with_LookupProperty, |
| with_LookupElement, |
| with_LookupSpecial, |
| NULL, /* defineGeneric */ |
| NULL, /* defineProperty */ |
| NULL, /* defineElement */ |
| NULL, /* defineSpecial */ |
| with_GetGeneric, |
| with_GetProperty, |
| with_GetElement, |
| NULL, /* getElementIfPresent */ |
| with_GetSpecial, |
| with_SetGeneric, |
| with_SetProperty, |
| with_SetElement, |
| with_SetSpecial, |
| with_GetGenericAttributes, |
| with_GetPropertyAttributes, |
| with_GetElementAttributes, |
| with_GetSpecialAttributes, |
| with_SetGenericAttributes, |
| with_SetPropertyAttributes, |
| with_SetElementAttributes, |
| with_SetSpecialAttributes, |
| with_DeleteProperty, |
| with_DeleteElement, |
| with_DeleteSpecial, |
| with_Enumerate, |
| with_ThisObject, |
| } |
| }; |
| |
| /*****************************************************************************/ |
| |
| ClonedBlockObject * |
| ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, AbstractFramePtr frame) |
| { |
| assertSameCompartment(cx, frame); |
| JS_ASSERT(block->getClass() == &BlockObject::class_); |
| |
| RootedTypeObject type(cx, block->getNewType(cx, &BlockObject::class_)); |
| if (!type) |
| return NULL; |
| |
| RootedShape shape(cx, block->lastProperty()); |
| |
| RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, type)); |
| if (!obj) |
| return NULL; |
| |
| /* Set the parent if necessary, as for call objects. */ |
| if (&frame.scopeChain()->global() != obj->getParent()) { |
| JS_ASSERT(obj->getParent() == NULL); |
| Rooted<GlobalObject*> global(cx, &frame.scopeChain()->global()); |
| if (!JSObject::setParent(cx, obj, global)) |
| return NULL; |
| } |
| |
| JS_ASSERT(!obj->inDictionaryMode()); |
| JS_ASSERT(obj->slotSpan() >= block->slotCount() + RESERVED_SLOTS); |
| |
| obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain())); |
| obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(block->stackDepth())); |
| |
| /* |
| * Copy in the closed-over locals. Closed-over locals don't need |
| * any fixup since the initial value is 'undefined'. |
| */ |
| unsigned nslots = block->slotCount(); |
| unsigned base = frame.script()->nfixed + block->stackDepth(); |
| for (unsigned i = 0; i < nslots; ++i) { |
| if (block->isAliased(i)) |
| obj->as<ClonedBlockObject>().setVar(i, frame.unaliasedLocal(base + i)); |
| } |
| |
| JS_ASSERT(obj->isDelegate()); |
| |
| return &obj->as<ClonedBlockObject>(); |
| } |
| |
| void |
| ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame) |
| { |
| StaticBlockObject &block = staticBlock(); |
| unsigned base = frame.script()->nfixed + block.stackDepth(); |
| for (unsigned i = 0; i < slotCount(); ++i) { |
| if (!block.isAliased(i)) |
| setVar(i, frame.unaliasedLocal(base + i), DONT_CHECK_ALIASING); |
| } |
| } |
| |
| StaticBlockObject * |
| StaticBlockObject::create(JSContext *cx) |
| { |
| RootedTypeObject type(cx, cx->compartment()->getNewType(cx, &BlockObject::class_, NULL)); |
| if (!type) |
| return NULL; |
| |
| RootedShape emptyBlockShape(cx); |
| emptyBlockShape = EmptyShape::getInitialShape(cx, &BlockObject::class_, NULL, NULL, NULL, |
| FINALIZE_KIND, BaseShape::DELEGATE); |
| if (!emptyBlockShape) |
| return NULL; |
| |
| JSObject *obj = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, emptyBlockShape, type); |
| if (!obj) |
| return NULL; |
| |
| return &obj->as<StaticBlockObject>(); |
| } |
| |
| /* static */ Shape * |
| StaticBlockObject::addVar(JSContext *cx, Handle<StaticBlockObject*> block, HandleId id, |
| int index, bool *redeclared) |
| { |
| JS_ASSERT(JSID_IS_ATOM(id) || (JSID_IS_INT(id) && JSID_TO_INT(id) == index)); |
| |
| *redeclared = false; |
| |
| /* Inline JSObject::addProperty in order to trap the redefinition case. */ |
| Shape **spp; |
| if (Shape::search(cx, block->lastProperty(), id, &spp, true)) { |
| *redeclared = true; |
| return NULL; |
| } |
| |
| /* |
| * Don't convert this object to dictionary mode so that we can clone the |
| * block's shape later. |
| */ |
| uint32_t slot = JSSLOT_FREE(&BlockObject::class_) + index; |
| return JSObject::addPropertyInternal(cx, block, id, /* getter = */ NULL, /* setter = */ NULL, |
| slot, JSPROP_ENUMERATE | JSPROP_PERMANENT, |
| Shape::HAS_SHORTID, index, spp, |
| /* allowDictionary = */ false); |
| } |
| |
| Class BlockObject::class_ = { |
| "Block", |
| JSCLASS_IMPLEMENTS_BARRIERS | |
| JSCLASS_HAS_RESERVED_SLOTS(BlockObject::RESERVED_SLOTS) | |
| JSCLASS_IS_ANONYMOUS, |
| JS_PropertyStub, /* addProperty */ |
| JS_DeletePropertyStub, /* delProperty */ |
| JS_PropertyStub, /* getProperty */ |
| JS_StrictPropertyStub, /* setProperty */ |
| JS_EnumerateStub, |
| JS_ResolveStub, |
| JS_ConvertStub |
| }; |
| |
| template<XDRMode mode> |
| bool |
| js::XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript script, |
| StaticBlockObject **objp) |
| { |
| /* NB: Keep this in sync with CloneStaticBlockObject. */ |
| |
| JSContext *cx = xdr->cx(); |
| |
| Rooted<StaticBlockObject*> obj(cx); |
| uint32_t count = 0; |
| uint32_t depthAndCount = 0; |
| |
| if (mode == XDR_ENCODE) { |
| obj = *objp; |
| uint32_t depth = obj->stackDepth(); |
| JS_ASSERT(depth <= UINT16_MAX); |
| count = obj->slotCount(); |
| JS_ASSERT(count <= UINT16_MAX); |
| depthAndCount = (depth << 16) | uint16_t(count); |
| } |
| |
| if (mode == XDR_DECODE) { |
| obj = StaticBlockObject::create(cx); |
| if (!obj) |
| return false; |
| obj->initEnclosingStaticScope(enclosingScope); |
| *objp = obj; |
| } |
| |
| if (!xdr->codeUint32(&depthAndCount)) |
| return false; |
| |
| if (mode == XDR_DECODE) { |
| uint32_t depth = uint16_t(depthAndCount >> 16); |
| count = uint16_t(depthAndCount); |
| obj->setStackDepth(depth); |
| |
| /* |
| * XDR the block object's properties. We know that there are 'count' |
| * properties to XDR, stored as id/shortid pairs. |
| */ |
| for (unsigned i = 0; i < count; i++) { |
| RootedAtom atom(cx); |
| if (!XDRAtom(xdr, &atom)) |
| return false; |
| |
| /* The empty string indicates an int id. */ |
| RootedId id(cx, atom != cx->runtime()->emptyString |
| ? AtomToId(atom) |
| : INT_TO_JSID(i)); |
| |
| bool redeclared; |
| if (!StaticBlockObject::addVar(cx, obj, id, i, &redeclared)) { |
| JS_ASSERT(!redeclared); |
| return false; |
| } |
| |
| uint32_t aliased; |
| if (!xdr->codeUint32(&aliased)) |
| return false; |
| |
| JS_ASSERT(aliased == 0 || aliased == 1); |
| obj->setAliased(i, !!aliased); |
| } |
| } else { |
| AutoShapeVector shapes(cx); |
| if (!shapes.growBy(count)) |
| return false; |
| |
| for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) { |
| Shape *shape = &r.front(); |
| shapes[shape->shortid()] = shape; |
| } |
| |
| /* |
| * XDR the block object's properties. We know that there are 'count' |
| * properties to XDR, stored as id/shortid pairs. |
| */ |
| RootedShape shape(cx); |
| RootedId propid(cx); |
| RootedAtom atom(cx); |
| for (unsigned i = 0; i < count; i++) { |
| shape = shapes[i]; |
| JS_ASSERT(shape->hasDefaultGetter()); |
| JS_ASSERT(unsigned(shape->shortid()) == i); |
| |
| propid = shape->propid(); |
| JS_ASSERT(JSID_IS_ATOM(propid) || JSID_IS_INT(propid)); |
| |
| /* The empty string indicates an int id. */ |
| atom = JSID_IS_ATOM(propid) |
| ? JSID_TO_ATOM(propid) |
| : cx->runtime()->emptyString; |
| if (!XDRAtom(xdr, &atom)) |
| return false; |
| |
| uint32_t aliased = obj->isAliased(i); |
| if (!xdr->codeUint32(&aliased)) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template bool |
| js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *, HandleObject, HandleScript, StaticBlockObject **); |
| |
| template bool |
| js::XDRStaticBlockObject(XDRState<XDR_DECODE> *, HandleObject, HandleScript, StaticBlockObject **); |
| |
| JSObject * |
| js::CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock) |
| { |
| /* NB: Keep this in sync with XDRStaticBlockObject. */ |
| |
| Rooted<StaticBlockObject*> clone(cx, StaticBlockObject::create(cx)); |
| if (!clone) |
| return NULL; |
| |
| clone->initEnclosingStaticScope(enclosingScope); |
| clone->setStackDepth(srcBlock->stackDepth()); |
| |
| /* Shape::Range is reverse order, so build a list in forward order. */ |
| AutoShapeVector shapes(cx); |
| if (!shapes.growBy(srcBlock->slotCount())) |
| return NULL; |
| for (Shape::Range<NoGC> r(srcBlock->lastProperty()); !r.empty(); r.popFront()) |
| shapes[r.front().shortid()] = &r.front(); |
| |
| for (Shape **p = shapes.begin(); p != shapes.end(); ++p) { |
| RootedId id(cx, (*p)->propid()); |
| unsigned i = (*p)->shortid(); |
| |
| bool redeclared; |
| if (!StaticBlockObject::addVar(cx, clone, id, i, &redeclared)) { |
| JS_ASSERT(!redeclared); |
| return NULL; |
| } |
| |
| clone->setAliased(i, srcBlock->isAliased(i)); |
| } |
| |
| return clone; |
| } |
| |
| /*****************************************************************************/ |
| |
| ScopeIter::ScopeIter(JSContext *cx |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| : cx(cx), |
| frame_(NullFramePtr()), |
| cur_(cx, reinterpret_cast<JSObject *>(-1)), |
| block_(cx, reinterpret_cast<StaticBlockObject *>(-1)), |
| type_(Type(-1)) |
| { |
| MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| } |
| |
| ScopeIter::ScopeIter(const ScopeIter &si, JSContext *cx |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| : cx(cx), |
| frame_(si.frame_), |
| cur_(cx, si.cur_), |
| block_(cx, si.block_), |
| type_(si.type_), |
| hasScopeObject_(si.hasScopeObject_) |
| { |
| MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| } |
| |
| ScopeIter::ScopeIter(JSObject &enclosingScope, JSContext *cx |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| : cx(cx), |
| frame_(NullFramePtr()), |
| cur_(cx, &enclosingScope), |
| block_(cx, reinterpret_cast<StaticBlockObject *>(-1)), |
| type_(Type(-1)) |
| { |
| MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| } |
| |
| ScopeIter::ScopeIter(AbstractFramePtr frame, JSContext *cx |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| : cx(cx), |
| frame_(frame), |
| cur_(cx, frame.scopeChain()), |
| block_(cx, frame.maybeBlockChain()) |
| { |
| assertSameCompartment(cx, frame); |
| settle(); |
| MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| } |
| |
| ScopeIter::ScopeIter(const ScopeIter &si, AbstractFramePtr frame, JSContext *cx |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| : cx(si.cx), |
| frame_(frame), |
| cur_(cx, si.cur_), |
| block_(cx, si.block_), |
| type_(si.type_), |
| hasScopeObject_(si.hasScopeObject_) |
| { |
| MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| } |
| |
| ScopeIter::ScopeIter(AbstractFramePtr frame, ScopeObject &scope, JSContext *cx |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
| : cx(cx), |
| frame_(frame), |
| cur_(cx, &scope), |
| block_(cx) |
| { |
| /* |
| * Find the appropriate static block for this iterator, given 'scope'. We |
| * know that 'scope' is a (non-optimized) scope on fp's scope chain. We do |
| * not, however, know whether fp->maybeScopeChain() encloses 'scope'. E.g.: |
| * |
| * let (x = 1) { |
| * g = function() { eval('debugger') }; |
| * let (y = 1) g(); |
| * } |
| * |
| * g will have x's block in its enclosing scope but not y's. However, at |
| * the debugger statement, both the x's and y's blocks will be on |
| * fp->blockChain. Fortunately, we can compare scope object stack depths to |
| * determine the block (if any) that encloses 'scope'. |
| */ |
| if (cur_->is<NestedScopeObject>()) { |
| block_ = frame.maybeBlockChain(); |
| while (block_) { |
| if (block_->stackDepth() <= cur_->as<NestedScopeObject>().stackDepth()) |
| break; |
| block_ = block_->enclosingBlock(); |
| } |
| JS_ASSERT_IF(cur_->is<ClonedBlockObject>(), |
| cur_->as<ClonedBlockObject>().staticBlock() == *block_); |
| } else { |
| block_ = NULL; |
| } |
| settle(); |
| MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| } |
| |
| ScopeObject & |
| ScopeIter::scope() const |
| { |
| JS_ASSERT(hasScopeObject()); |
| return cur_->as<ScopeObject>(); |
| } |
| |
| ScopeIter & |
| ScopeIter::operator++() |
| { |
| JS_ASSERT(!done()); |
| switch (type_) { |
| case Call: |
| if (hasScopeObject_) { |
| cur_ = &cur_->as<CallObject>().enclosingScope(); |
| if (CallObjectLambdaName(*frame_.fun())) |
| cur_ = &cur_->as<DeclEnvObject>().enclosingScope(); |
| } |
| frame_ = NullFramePtr(); |
| break; |
| case Block: |
| block_ = block_->enclosingBlock(); |
| if (hasScopeObject_) |
| cur_ = &cur_->as<ClonedBlockObject>().enclosingScope(); |
| settle(); |
| break; |
| case With: |
| JS_ASSERT(hasScopeObject_); |
| cur_ = &cur_->as<WithObject>().enclosingScope(); |
| settle(); |
| break; |
| case StrictEvalScope: |
| if (hasScopeObject_) |
| cur_ = &cur_->as<CallObject>().enclosingScope(); |
| frame_ = NullFramePtr(); |
| break; |
| } |
| return *this; |
| } |
| |
| void |
| ScopeIter::settle() |
| { |
| /* |
| * Given an iterator state (cur_, block_), figure out which (potentially |
| * optimized) scope the iterator should report. Thus, the result is a pair |
| * (type_, hasScopeObject_) where hasScopeObject_ indicates whether the |
| * scope object has been optimized away and does not exist on the scope |
| * chain. Beware: while ScopeIter iterates over the scopes of a single |
| * frame, the scope chain (pointed to by cur_) continues into the scopes of |
| * enclosing frames. Thus, it is important not to look at cur_ until it is |
| * certain that cur_ points to a scope object in the current frame. In |
| * particular, there are three tricky corner cases: |
| * - non-heavyweight functions; |
| * - non-strict direct eval. |
| * - heavyweight functions observed before the prologue has finished; |
| * In all cases, cur_ can already be pointing into an enclosing frame's |
| * scope chain. Furthermore, in the first two cases: even if cur_ points |
| * into an enclosing frame's scope chain, the current frame may still have |
| * uncloned blocks. In the last case, since we haven't entered the |
| * function, we simply return a ScopeIter where done() == true. |
| * |
| * Note: DebugScopeObject falls nicely into this plan: since they are only |
| * ever introduced as the *enclosing* scope of a frame, they should never |
| * show up in scope iteration and fall into the final non-scope case. |
| */ |
| if (frame_.isNonEvalFunctionFrame() && !frame_.fun()->isHeavyweight()) { |
| if (block_) { |
| type_ = Block; |
| hasScopeObject_ = block_->needsClone(); |
| } else { |
| type_ = Call; |
| hasScopeObject_ = false; |
| } |
| } else if (frame_.isNonStrictDirectEvalFrame() && cur_ == frame_.evalPrevScopeChain(cx)) { |
| if (block_) { |
| JS_ASSERT(!block_->needsClone()); |
| type_ = Block; |
| hasScopeObject_ = false; |
| } else { |
| frame_ = NullFramePtr(); |
| } |
| } else if (frame_.isNonEvalFunctionFrame() && !frame_.hasCallObj()) { |
| JS_ASSERT(cur_ == frame_.fun()->environment()); |
| frame_ = NullFramePtr(); |
| } else if (frame_.isStrictEvalFrame() && !frame_.hasCallObj()) { |
| JS_ASSERT(cur_ == frame_.evalPrevScopeChain(cx)); |
| frame_ = NullFramePtr(); |
| } else if (cur_->is<WithObject>()) { |
| JS_ASSERT_IF(frame_.isFunctionFrame(), frame_.fun()->isHeavyweight()); |
| JS_ASSERT_IF(block_, block_->needsClone()); |
| JS_ASSERT_IF(block_, block_->stackDepth() < cur_->as<WithObject>().stackDepth()); |
| type_ = With; |
| hasScopeObject_ = true; |
| } else if (block_) { |
| type_ = Block; |
| hasScopeObject_ = block_->needsClone(); |
| JS_ASSERT_IF(hasScopeObject_, cur_->as<ClonedBlockObject>().staticBlock() == *block_); |
| } else if (cur_->is<CallObject>()) { |
| CallObject &callobj = cur_->as<CallObject>(); |
| type_ = callobj.isForEval() ? StrictEvalScope : Call; |
| hasScopeObject_ = true; |
| JS_ASSERT_IF(type_ == Call, callobj.callee().nonLazyScript() == frame_.script()); |
| } else { |
| JS_ASSERT(!cur_->is<ScopeObject>()); |
| JS_ASSERT(frame_.isGlobalFrame() || frame_.isDebuggerFrame()); |
| frame_ = NullFramePtr(); |
| } |
| } |
| |
| /* static */ HashNumber |
| ScopeIterKey::hash(ScopeIterKey si) |
| { |
| /* hasScopeObject_ is determined by the other fields. */ |
| return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.block_) ^ si.type_; |
| } |
| |
| /* static */ bool |
| ScopeIterKey::match(ScopeIterKey si1, ScopeIterKey si2) |
| { |
| /* hasScopeObject_ is determined by the other fields. */ |
| return si1.frame_ == si2.frame_ && |
| (!si1.frame_ || |
| (si1.cur_ == si2.cur_ && |
| si1.block_ == si2.block_ && |
| si1.type_ == si2.type_)); |
| } |
| |
| /*****************************************************************************/ |
| |
| /* |
| * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a |
| * custom handler (rather than trying to reuse js::Wrapper) gives us several |
| * important abilities: |
| * - We want to pass the ScopeObject as the receiver to forwarded scope |
| * property ops on aliased variables so that Call/Block/With ops do not all |
| * require a 'normalization' step. |
| * - The debug scope proxy can directly manipulate the stack frame to allow |
| * the debugger to read/write args/locals that were otherwise unaliased. |
| * - The debug scope proxy can store unaliased variables after the stack frame |
| * is popped so that they may still be read/written by the debugger. |
| * - The engine has made certain assumptions about the possible reads/writes |
| * in a scope. DebugScopeProxy allows us to prevent the debugger from |
| * breaking those assumptions. |
| * - The engine makes optimizations that are observable to the debugger. The |
| * proxy can either hide these optimizations or make the situation more |
| * clear to the debugger. An example is 'arguments'. |
| */ |
| class DebugScopeProxy : public BaseProxyHandler |
| { |
| enum Action { SET, GET }; |
| |
| /* |
| * This function handles access to unaliased locals/formals. Since they are |
| * unaliased, the values of these variables are not stored in the slots of |
| * the normal Call/BlockObject scope objects and thus must be recovered |
| * from somewhere else: |
| * + if the invocation for which the scope was created is still executing, |
| * there is a StackFrame (either live on the stack or floating in a |
| * generator object) holding the values; |
| * + if the invocation for which the scope was created finished executing: |
| * - and there was a DebugScopeObject associated with scope, then the |
| * DebugScopes::onPop(Call|Block) handler copied out the unaliased |
| * variables: |
| * . for block scopes, the unaliased values were copied directly |
| * into the block object, since there is a slot allocated for every |
| * block binding, regardless of whether it is aliased; |
| * . for function scopes, a dense array is created in onPopCall to hold |
| * the unaliased values and attached to the DebugScopeObject; |
| * - and there was not a DebugScopeObject yet associated with the |
| * scope, then the unaliased values are lost and not recoverable. |
| * |
| * handleUnaliasedAccess returns 'true' if the access was unaliased and |
| * completed by handleUnaliasedAccess. |
| */ |
| bool handleUnaliasedAccess(JSContext *cx, Handle<DebugScopeObject*> debugScope, Handle<ScopeObject*> scope, |
| jsid id, Action action, MutableHandleValue vp) |
| { |
| JS_ASSERT(&debugScope->scope() == scope); |
| AbstractFramePtr maybeframe = DebugScopes::hasLiveFrame(*scope); |
| |
| /* Handle unaliased formals, vars, and consts at function scope. */ |
| if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { |
| CallObject &callobj = scope->as<CallObject>(); |
| RootedScript script(cx, callobj.callee().nonLazyScript()); |
| if (!script->ensureHasTypes(cx)) |
| return false; |
| |
| Bindings &bindings = script->bindings; |
| BindingIter bi(script); |
| while (bi && NameToId(bi->name()) != id) |
| bi++; |
| if (!bi) |
| return false; |
| |
| if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) { |
| unsigned i = bi.frameIndex(); |
| if (script->varIsAliased(i)) |
| return false; |
| |
| if (maybeframe) { |
| if (action == GET) |
| vp.set(maybeframe.unaliasedVar(i)); |
| else |
| maybeframe.unaliasedVar(i) = vp; |
| } else if (JSObject *snapshot = debugScope->maybeSnapshot()) { |
| if (action == GET) |
| vp.set(snapshot->getDenseElement(bindings.numArgs() + i)); |
| else |
| snapshot->setDenseElement(bindings.numArgs() + i, vp); |
| } else { |
| /* The unaliased value has been lost to the debugger. */ |
| if (action == GET) |
| vp.set(UndefinedValue()); |
| } |
| } else { |
| JS_ASSERT(bi->kind() == ARGUMENT); |
| unsigned i = bi.frameIndex(); |
| if (script->formalIsAliased(i)) |
| return false; |
| |
| if (maybeframe) { |
| if (script->argsObjAliasesFormals() && maybeframe.hasArgsObj()) { |
| if (action == GET) |
| vp.set(maybeframe.argsObj().arg(i)); |
| else |
| maybeframe.argsObj().setArg(i, vp); |
| } else { |
| if (action == GET) |
| vp.set(maybeframe.unaliasedFormal(i, DONT_CHECK_ALIASING)); |
| else |
| maybeframe.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp; |
| } |
| } else if (JSObject *snapshot = debugScope->maybeSnapshot()) { |
| if (action == GET) |
| vp.set(snapshot->getDenseElement(i)); |
| else |
| snapshot->setDenseElement(i, vp); |
| } else { |
| /* The unaliased value has been lost to the debugger. */ |
| if (action == GET) |
| vp.set(UndefinedValue()); |
| } |
| |
| if (action == SET) |
| TypeScript::SetArgument(cx, script, i, vp); |
| } |
| |
| return true; |
| } |
| |
| /* Handle unaliased let and catch bindings at block scope. */ |
| if (scope->is<ClonedBlockObject>()) { |
| Rooted<ClonedBlockObject *> block(cx, &scope->as<ClonedBlockObject>()); |
| Shape *shape = block->lastProperty()->search(cx, id); |
| if (!shape) |
| return false; |
| |
| unsigned i = shape->shortid(); |
| if (block->staticBlock().isAliased(i)) |
| return false; |
| |
| if (maybeframe) { |
| JSScript *script = maybeframe.script(); |
| unsigned local = block->slotToLocalIndex(script->bindings, shape->slot()); |
| if (action == GET) |
| vp.set(maybeframe.unaliasedLocal(local)); |
| else |
| maybeframe.unaliasedLocal(local) = vp; |
| JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script)); |
| } else { |
| if (action == GET) |
| vp.set(block->var(i, DONT_CHECK_ALIASING)); |
| else |
| block->setVar(i, vp, DONT_CHECK_ALIASING); |
| } |
| |
| return true; |
| } |
| |
| /* The rest of the internal scopes do not have unaliased vars. */ |
| JS_ASSERT(scope->is<DeclEnvObject>() || scope->is<WithObject>() || |
| scope->as<CallObject>().isForEval()); |
| return false; |
| } |
| |
| static bool isArguments(JSContext *cx, jsid id) |
| { |
| return id == NameToId(cx->names().arguments); |
| } |
| |
| static bool isFunctionScope(ScopeObject &scope) |
| { |
| return scope.is<CallObject>() && !scope.as<CallObject>().isForEval(); |
| } |
| |
| /* |
| * In theory, every function scope contains an 'arguments' bindings. |
| * However, the engine only adds a binding if 'arguments' is used in the |
| * function body. Thus, from the debugger's perspective, 'arguments' may be |
| * missing from the list of bindings. |
| */ |
| static bool isMissingArgumentsBinding(ScopeObject &scope) |
| { |
| return isFunctionScope(scope) && |
| !scope.as<CallObject>().callee().nonLazyScript()->argumentsHasVarBinding(); |
| } |
| |
| /* |
| * This function creates an arguments object when the debugger requests |
| * 'arguments' for a function scope where the arguments object has been |
| * optimized away (either because the binding is missing altogether or |
| * because !ScriptAnalysis::needsArgsObj). |
| */ |
| static bool checkForMissingArguments(JSContext *cx, jsid id, ScopeObject &scope, |
| ArgumentsObject **maybeArgsObj) |
| { |
| *maybeArgsObj = NULL; |
| |
| if (!isArguments(cx, id) || !isFunctionScope(scope)) |
| return true; |
| |
| if (scope.as<CallObject>().callee().nonLazyScript()->needsArgsObj()) |
| return true; |
| |
| AbstractFramePtr maybeframe = DebugScopes::hasLiveFrame(scope); |
| if (!maybeframe) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEBUG_NOT_LIVE, |
| "Debugger scope"); |
| return false; |
| } |
| |
| *maybeArgsObj = ArgumentsObject::createUnexpected(cx, maybeframe); |
| return true; |
| } |
| |
| public: |
| static int family; |
| static DebugScopeProxy singleton; |
| |
| DebugScopeProxy() : BaseProxyHandler(&family) {} |
| |
| bool isExtensible(JSObject *proxy) MOZ_OVERRIDE |
| { |
| // always [[Extensible]], can't be made non-[[Extensible]], like most |
| // proxies |
| return true; |
| } |
| |
| bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE |
| { |
| // See above. |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY); |
| return false; |
| } |
| |
| bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, PropertyDescriptor *desc, |
| unsigned flags) MOZ_OVERRIDE |
| { |
| return getOwnPropertyDescriptor(cx, proxy, id, desc, flags); |
| } |
| |
| bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, |
| PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE |
| { |
| Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>()); |
| Rooted<ScopeObject*> scope(cx, &debugScope->scope()); |
| |
| RootedArgumentsObject maybeArgsObj(cx); |
| if (!checkForMissingArguments(cx, id, *scope, maybeArgsObj.address())) |
| return false; |
| |
| if (maybeArgsObj) { |
| PodZero(desc); |
| desc->obj = debugScope; |
| desc->attrs = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT; |
| desc->value = ObjectValue(*maybeArgsObj); |
| return true; |
| } |
| |
| RootedValue v(cx); |
| if (handleUnaliasedAccess(cx, debugScope, scope, id, GET, &v)) { |
| PodZero(desc); |
| desc->obj = debugScope; |
| desc->attrs = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT; |
| desc->value = v; |
| return true; |
| } |
| |
| return JS_GetPropertyDescriptorById(cx, scope, id, 0, desc); |
| } |
| |
| bool get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, |
| MutableHandleValue vp) MOZ_OVERRIDE |
| { |
| Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>()); |
| Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope()); |
| |
| RootedArgumentsObject maybeArgsObj(cx); |
| if (!checkForMissingArguments(cx, id, *scope, maybeArgsObj.address())) |
| return false; |
| |
| if (maybeArgsObj) { |
| vp.set(ObjectValue(*maybeArgsObj)); |
| return true; |
| } |
| |
| if (handleUnaliasedAccess(cx, debugScope, scope, id, GET, vp)) |
| return true; |
| |
| return JSObject::getGeneric(cx, scope, scope, id, vp); |
| } |
| |
| bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, bool strict, |
| MutableHandleValue vp) MOZ_OVERRIDE |
| { |
| Rooted<DebugScopeObject*> debugScope(cx, &proxy->as<DebugScopeObject>()); |
| Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope()); |
| if (handleUnaliasedAccess(cx, debugScope, scope, id, SET, vp)) |
| return true; |
| return JSObject::setGeneric(cx, scope, scope, id, vp, strict); |
| } |
| |
| bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id, |
| PropertyDescriptor *desc) MOZ_OVERRIDE |
| { |
| Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope()); |
| |
| bool found; |
| if (!has(cx, proxy, id, &found)) |
| return false; |
| if (found) |
| return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP); |
| |
| return JS_DefinePropertyById(cx, scope, id, desc->value, desc->getter, desc->setter, |
| desc->attrs); |
| } |
| |
| bool getScopePropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props, |
| unsigned flags) |
| { |
| Rooted<ScopeObject*> scope(cx, &proxy->as<DebugScopeObject>().scope()); |
| |
| if (isMissingArgumentsBinding(*scope)) { |
| if (!props.append(NameToId(cx->names().arguments))) |
| return false; |
| } |
| |
| if (!GetPropertyNames(cx, scope, flags, &props)) |
| return false; |
| |
| /* |
| * Function scopes are optimized to not contain unaliased variables so |
| * they must be manually appended here. |
| */ |
| if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { |
| RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript()); |
| for (BindingIter bi(script); bi; bi++) { |
| if (!bi->aliased() && !props.append(NameToId(bi->name()))) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE |
| { |
| return getScopePropertyNames(cx, proxy, props, JSITER_OWNONLY); |
| } |
| |
| bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE |
| { |
| return getScopePropertyNames(cx, proxy, props, 0); |
| } |
| |
| bool has(JSContext *cx, HandleObject proxy, HandleId id_, bool *bp) MOZ_OVERRIDE |
| { |
| RootedId id(cx, id_); |
| ScopeObject &scopeObj = proxy->as<DebugScopeObject>().scope(); |
| |
| if (isArguments(cx, id) && isFunctionScope(scopeObj)) { |
| *bp = true; |
| return true; |
| } |
| |
| JSBool found; |
| RootedObject scope(cx, &scopeObj); |
| if (!JS_HasPropertyById(cx, scope, id, &found)) |
| return false; |
| |
| /* |
| * Function scopes are optimized to not contain unaliased variables so |
| * a manual search is necessary. |
| */ |
| if (!found && scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) { |
| RootedScript script(cx, scope->as<CallObject>().callee().nonLazyScript()); |
| for (BindingIter bi(script); bi; bi++) { |
| if (!bi->aliased() && NameToId(bi->name()) == id) { |
| found = true; |
| break; |
| } |
| } |
| } |
| |
| *bp = found; |
| return true; |
| } |
| |
| bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE |
| { |
| RootedValue idval(cx, IdToValue(id)); |
| return js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_CANT_DELETE, |
| JSDVG_IGNORE_STACK, idval, NullPtr(), NULL, NULL); |
| } |
| }; |
| |
| int DebugScopeProxy::family = 0; |
| DebugScopeProxy DebugScopeProxy::singleton; |
| |
| /* static */ DebugScopeObject * |
| DebugScopeObject::create(JSContext *cx, ScopeObject &scope, HandleObject enclosing) |
| { |
| JS_ASSERT(scope.compartment() == cx->compartment()); |
| RootedValue priv(cx, ObjectValue(scope)); |
| JSObject *obj = NewProxyObject(cx, &DebugScopeProxy::singleton, priv, |
| NULL /* proto */, &scope.global(), ProxyNotCallable); |
| if (!obj) |
| return NULL; |
| |
| JS_ASSERT(!enclosing->is<ScopeObject>()); |
| SetProxyExtra(obj, ENCLOSING_EXTRA, ObjectValue(*enclosing)); |
| SetProxyExtra(obj, SNAPSHOT_EXTRA, NullValue()); |
| |
| return &obj->as<DebugScopeObject>(); |
| } |
| |
| ScopeObject & |
| DebugScopeObject::scope() const |
| { |
| return GetProxyTargetObject(const_cast<DebugScopeObject*>(this))->as<ScopeObject>(); |
| } |
| |
| JSObject & |
| DebugScopeObject::enclosingScope() const |
| { |
| return GetProxyExtra(const_cast<DebugScopeObject*>(this), ENCLOSING_EXTRA).toObject(); |
| } |
| |
| JSObject * |
| DebugScopeObject::maybeSnapshot() const |
| { |
| JS_ASSERT(!scope().as<CallObject>().isForEval()); |
| return GetProxyExtra(const_cast<DebugScopeObject*>(this), SNAPSHOT_EXTRA).toObjectOrNull(); |
| } |
| |
| void |
| DebugScopeObject::initSnapshot(JSObject &o) |
| { |
| JS_ASSERT(maybeSnapshot() == NULL); |
| SetProxyExtra(this, SNAPSHOT_EXTRA, ObjectValue(o)); |
| } |
| |
| bool |
| DebugScopeObject::isForDeclarative() const |
| { |
| ScopeObject &s = scope(); |
| return s.is<CallObject>() || s.is<BlockObject>() || s.is<DeclEnvObject>(); |
| } |
| |
| bool |
| js_IsDebugScopeSlow(JSObject *obj) |
| { |
| return obj->getClass() == &ObjectProxyClass && |
| GetProxyHandler(obj) == &DebugScopeProxy::singleton; |
| } |
| |
| /*****************************************************************************/ |
| |
| DebugScopes::DebugScopes(JSContext *cx) |
| : proxiedScopes(cx), |
| missingScopes(cx), |
| liveScopes(cx) |
| {} |
| |
| DebugScopes::~DebugScopes() |
| { |
| JS_ASSERT(missingScopes.empty()); |
| WeakMapBase::removeWeakMapFromList(&proxiedScopes); |
| } |
| |
| bool |
| DebugScopes::init() |
| { |
| if (!liveScopes.init() || |
| !proxiedScopes.init() || |
| !missingScopes.init()) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| void |
| DebugScopes::mark(JSTracer *trc) |
| { |
| proxiedScopes.trace(trc); |
| } |
| |
| void |
| DebugScopes::sweep(JSRuntime *rt) |
| { |
| /* |
| * Note: missingScopes points to debug scopes weakly not just so that debug |
| * scopes can be released more eagerly, but, more importantly, to avoid |
| * creating an uncollectable cycle with suspended generator frames. |
| */ |
| for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) { |
| if (IsObjectAboutToBeFinalized(e.front().value.unsafeGet())) |
| e.removeFront(); |
| } |
| |
| for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) { |
| ScopeObject *scope = e.front().key; |
| AbstractFramePtr frame = e.front().value; |
| |
| /* |
| * Scopes can be finalized when a debugger-synthesized ScopeObject is |
| * no longer reachable via its DebugScopeObject. |
| */ |
| if (IsObjectAboutToBeFinalized(&scope)) { |
| e.removeFront(); |
| continue; |
| } |
| |
| /* |
| * As explained in onGeneratorFrameChange, liveScopes includes |
| * suspended generator frames. Since a generator can be finalized while |
| * its scope is live, we must explicitly detect finalized generators. |
| */ |
| if (JSGenerator *gen = frame.maybeSuspendedGenerator(rt)) { |
| JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN); |
| if (IsObjectAboutToBeFinalized(&gen->obj)) { |
| e.removeFront(); |
| continue; |
| } |
| } |
| } |
| } |
| |
| /* |
| * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode |
| * (in particular, JS_GetFrameScopeChain does not require debug mode). Since |
| * DebugScopes::onPop* are only called in debug mode, this means we cannot |
| * use any of the maps in DebugScopes. This will produce debug scope chains |
| * that do not obey the debugger invariants but that is just fine. |
| */ |
| static bool |
| CanUseDebugScopeMaps(JSContext *cx) |
| { |
| return cx->compartment()->debugMode(); |
| } |
| |
| DebugScopes * |
| DebugScopes::ensureCompartmentData(JSContext *cx) |
| { |
| JSCompartment *c = cx->compartment(); |
| if (c->debugScopes) |
| return c->debugScopes; |
| |
| c->debugScopes = c->rt->new_<DebugScopes>(cx); |
| if (c->debugScopes && c->debugScopes->init()) |
| return c->debugScopes; |
| |
| js_ReportOutOfMemory(cx); |
| return NULL; |
| } |
| |
| DebugScopeObject * |
| DebugScopes::hasDebugScope(JSContext *cx, ScopeObject &scope) |
| { |
| DebugScopes *scopes = scope.compartment()->debugScopes; |
| if (!scopes) |
| return NULL; |
| |
| if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&scope)) { |
| JS_ASSERT(CanUseDebugScopeMaps(cx)); |
| return &p->value->as<DebugScopeObject>(); |
| } |
| |
| return NULL; |
| } |
| |
| bool |
| DebugScopes::addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope) |
| { |
| JS_ASSERT(cx->compartment() == scope.compartment()); |
| JS_ASSERT(cx->compartment() == debugScope.compartment()); |
| |
| if (!CanUseDebugScopeMaps(cx)) |
| return true; |
| |
| DebugScopes *scopes = ensureCompartmentData(cx); |
| if (!scopes) |
| return false; |
| |
| JS_ASSERT(!scopes->proxiedScopes.has(&scope)); |
| if (!scopes->proxiedScopes.put(&scope, &debugScope)) { |
| js_ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| HashTableWriteBarrierPost(cx->runtime(), &scopes->proxiedScopes, &scope); |
| return true; |
| } |
| |
| DebugScopeObject * |
| DebugScopes::hasDebugScope(JSContext *cx, const ScopeIter &si) |
| { |
| JS_ASSERT(!si.hasScopeObject()); |
| |
| DebugScopes *scopes = cx->compartment()->debugScopes; |
| if (!scopes) |
| return NULL; |
| |
| if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { |
| JS_ASSERT(CanUseDebugScopeMaps(cx)); |
| return p->value; |
| } |
| return NULL; |
| } |
| |
| bool |
| DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope) |
| { |
| JS_ASSERT(!si.hasScopeObject()); |
| JS_ASSERT(cx->compartment() == debugScope.compartment()); |
| |
| if (!CanUseDebugScopeMaps(cx)) |
| return true; |
| |
| DebugScopes *scopes = ensureCompartmentData(cx); |
| if (!scopes) |
| return false; |
| |
| JS_ASSERT(!scopes->missingScopes.has(si)); |
| if (!scopes->missingScopes.put(si, &debugScope)) { |
| js_ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| JS_ASSERT(!scopes->liveScopes.has(&debugScope.scope())); |
| if (!scopes->liveScopes.put(&debugScope.scope(), si.frame())) { |
| js_ReportOutOfMemory(cx); |
| return false; |
| } |
| HashTableWriteBarrierPost(cx->runtime(), &scopes->liveScopes, &debugScope.scope()); |
| |
| return true; |
| } |
| |
| void |
| DebugScopes::onPopCall(AbstractFramePtr frame, JSContext *cx) |
| { |
| JS_ASSERT(!frame.isYielding()); |
| assertSameCompartment(cx, frame); |
| |
| DebugScopes *scopes = cx->compartment()->debugScopes; |
| if (!scopes) |
| return; |
| |
| Rooted<DebugScopeObject*> debugScope(cx, NULL); |
| |
| if (frame.fun()->isHeavyweight()) { |
| /* |
| * The StackFrame may be observed before the prologue has created the |
| * CallObject. See ScopeIter::settle. |
| */ |
| if (!frame.hasCallObj()) |
| return; |
| |
| CallObject &callobj = frame.scopeChain()->as<CallObject>(); |
| scopes->liveScopes.remove(&callobj); |
| if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj)) |
| debugScope = &p->value->as<DebugScopeObject>(); |
| } else { |
| ScopeIter si(frame, cx); |
| if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { |
| debugScope = p->value; |
| scopes->liveScopes.remove(&debugScope->scope().as<CallObject>()); |
| scopes->missingScopes.remove(p); |
| } |
| } |
| |
| /* |
| * When the StackFrame is popped, the values of unaliased variables |
| * are lost. If there is any debug scope referring to this scope, save a |
| * copy of the unaliased variables' values in an array for later debugger |
| * access via DebugScopeProxy::handleUnaliasedAccess. |
| * |
| * Note: since it is simplest for this function to be infallible, failure |
| * in this code will be silently ignored. This does not break any |
| * invariants since DebugScopeObject::maybeSnapshot can already be NULL. |
| */ |
| if (debugScope) { |
| /* |
| * Copy all frame values into the snapshot, regardless of |
| * aliasing. This unnecessarily includes aliased variables |
| * but it simplifies later indexing logic. |
| */ |
| AutoValueVector vec(cx); |
| if (!frame.copyRawFrameSlots(&vec) || vec.length() == 0) |
| return; |
| |
| /* |
| * Copy in formals that are not aliased via the scope chain |
| * but are aliased via the arguments object. |
| */ |
| RootedScript script(cx, frame.script()); |
| if (script->needsArgsObj() && frame.hasArgsObj()) { |
| for (unsigned i = 0; i < frame.numFormalArgs(); ++i) { |
| if (script->formalLivesInArgumentsObject(i)) |
| vec[i] = frame.argsObj().arg(i); |
| } |
| } |
| |
| /* |
| * Use a dense array as storage (since proxies do not have trace |
| * hooks). This array must not escape into the wild. |
| */ |
| RootedObject snapshot(cx, NewDenseCopiedArray(cx, vec.length(), vec.begin())); |
| if (!snapshot) { |
| cx->clearPendingException(); |
| return; |
| } |
| |
| debugScope->initSnapshot(*snapshot); |
| } |
| } |
| |
| void |
| DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame) |
| { |
| assertSameCompartment(cx, frame); |
| |
| DebugScopes *scopes = cx->compartment()->debugScopes; |
| if (!scopes) |
| return; |
| |
| StaticBlockObject &staticBlock = *frame.maybeBlockChain(); |
| if (staticBlock.needsClone()) { |
| ClonedBlockObject &clone = frame.scopeChain()->as<ClonedBlockObject>(); |
| clone.copyUnaliasedValues(frame); |
| scopes->liveScopes.remove(&clone); |
| } else { |
| ScopeIter si(frame, cx); |
| if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { |
| ClonedBlockObject &clone = p->value->scope().as<ClonedBlockObject>(); |
| clone.copyUnaliasedValues(frame); |
| scopes->liveScopes.remove(&clone); |
| scopes->missingScopes.remove(p); |
| } |
| } |
| } |
| |
| void |
| DebugScopes::onPopWith(AbstractFramePtr frame) |
| { |
| DebugScopes *scopes = frame.compartment()->debugScopes; |
| if (scopes) |
| scopes->liveScopes.remove(&frame.scopeChain()->as<WithObject>()); |
| } |
| |
| void |
| DebugScopes::onPopStrictEvalScope(AbstractFramePtr frame) |
| { |
| DebugScopes *scopes = frame.compartment()->debugScopes; |
| if (!scopes) |
| return; |
| |
| /* |
| * The StackFrame may be observed before the prologue has created the |
| * CallObject. See ScopeIter::settle. |
| */ |
| if (frame.hasCallObj()) |
| scopes->liveScopes.remove(&frame.scopeChain()->as<CallObject>()); |
| } |
| |
| void |
| DebugScopes::onGeneratorFrameChange(AbstractFramePtr from, AbstractFramePtr to, JSContext *cx) |
| { |
| for (ScopeIter toIter(to, cx); !toIter.done(); ++toIter) { |
| DebugScopes *scopes = ensureCompartmentData(cx); |
| if (!scopes) |
| return; |
| |
| if (toIter.hasScopeObject()) { |
| /* |
| * Not only must we correctly replace mappings [scope -> from] with |
| * mappings [scope -> to], but we must add [scope -> to] if it |
| * doesn't already exist so that if we need to proxy a generator's |
| * scope while it is suspended, we can find its frame (which would |
| * otherwise not be found by AllFramesIter). |
| */ |
| JS_ASSERT(toIter.scope().compartment() == cx->compartment()); |
| LiveScopeMap::AddPtr livePtr = scopes->liveScopes.lookupForAdd(&toIter.scope()); |
| if (livePtr) { |
| livePtr->value = to; |
| } else { |
| scopes->liveScopes.add(livePtr, &toIter.scope(), to); // OOM here? |
| HashTableWriteBarrierPost(cx->runtime(), &scopes->liveScopes, &toIter.scope()); |
| } |
| } else { |
| ScopeIter si(toIter, from, cx); |
| JS_ASSERT(si.frame().scopeChain()->compartment() == cx->compartment()); |
| if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) { |
| DebugScopeObject &debugScope = *p->value; |
| scopes->liveScopes.lookup(&debugScope.scope())->value = to; |
| scopes->missingScopes.remove(p); |
| scopes->missingScopes.put(toIter, &debugScope); // OOM here? |
| } |
| } |
| } |
| } |
| |
| void |
| DebugScopes::onCompartmentLeaveDebugMode(JSCompartment *c) |
| { |
| DebugScopes *scopes = c->debugScopes; |
| if (scopes) { |
| scopes->proxiedScopes.clear(); |
| scopes->missingScopes.clear(); |
| scopes->liveScopes.clear(); |
| } |
| } |
| |
| bool |
| DebugScopes::updateLiveScopes(JSContext *cx) |
| { |
| JS_CHECK_RECURSION(cx, return false); |
| |
| /* |
| * Note that we must always update the top frame's scope objects' entries |
| * in liveScopes because we can't be sure code hasn't run in that frame to |
| * change the scope chain since we were last called. The fp->prevUpToDate() |
| * flag indicates whether the scopes of frames older than fp are already |
| * included in liveScopes. It might seem simpler to have fp instead carry a |
| * flag indicating whether fp itself is accurately described, but then we |
| * would need to clear that flag whenever fp ran code. By storing the 'up |
| * to date' bit for fp->prev() in fp, simply popping fp effectively clears |
| * the flag for us, at exactly the time when execution resumes fp->prev(). |
| */ |
| for (AllFramesIter i(cx); !i.done(); ++i) { |
| /* |
| * Debug-mode currently disables Ion compilation in the compartment of |
| * the debuggee. |
| */ |
| if (i.isIon()) |
| continue; |
| |
| AbstractFramePtr frame = i.abstractFramePtr(); |
| if (frame.scopeChain()->compartment() != cx->compartment()) |
| continue; |
| |
| for (ScopeIter si(frame, cx); !si.done(); ++si) { |
| if (si.hasScopeObject()) { |
| JS_ASSERT(si.scope().compartment() == cx->compartment()); |
| DebugScopes *scopes = ensureCompartmentData(cx); |
| if (!scopes) |
| return false; |
| if (!scopes->liveScopes.put(&si.scope(), frame)) |
| return false; |
| HashTableWriteBarrierPost(cx->runtime(), &scopes->liveScopes, &si.scope()); |
| } |
| } |
| |
| if (frame.prevUpToDate()) |
| return true; |
| JS_ASSERT(frame.scopeChain()->compartment()->debugMode()); |
| frame.setPrevUpToDate(); |
| } |
| |
| return true; |
| } |
| |
| AbstractFramePtr |
| DebugScopes::hasLiveFrame(ScopeObject &scope) |
| { |
| DebugScopes *scopes = scope.compartment()->debugScopes; |
| if (!scopes) |
| return NullFramePtr(); |
| |
| if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope)) { |
| AbstractFramePtr frame = p->value; |
| |
| /* |
| * Since liveScopes is effectively a weak pointer, we need a read |
| * barrier. The scenario where this is necessary is: |
| * 1. GC starts, a suspended generator is not live |
| * 2. hasLiveFrame returns a StackFrame* to the (soon to be dead) |
| * suspended generator |
| * 3. stack frame values (which will neve be marked) are read from the |
| * StackFrame |
| * 4. GC completes, live objects may now point to values that weren't |
| * marked and thus may point to swept GC things |
| */ |
| if (JSGenerator *gen = frame.maybeSuspendedGenerator(scope.compartment()->rt)) |
| JSObject::readBarrier(gen->obj); |
| |
| return frame; |
| } |
| return NullFramePtr(); |
| } |
| |
| /*****************************************************************************/ |
| |
| static JSObject * |
| GetDebugScope(JSContext *cx, const ScopeIter &si); |
| |
| static DebugScopeObject * |
| GetDebugScopeForScope(JSContext *cx, Handle<ScopeObject*> scope, const ScopeIter &enclosing) |
| { |
| if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, *scope)) |
| return debugScope; |
| |
| RootedObject enclosingDebug(cx, GetDebugScope(cx, enclosing)); |
| if (!enclosingDebug) |
| return NULL; |
| |
| JSObject &maybeDecl = scope->enclosingScope(); |
| if (maybeDecl.is<DeclEnvObject>()) { |
| JS_ASSERT(CallObjectLambdaName(scope->as<CallObject>().callee())); |
| enclosingDebug = DebugScopeObject::create(cx, maybeDecl.as<DeclEnvObject>(), enclosingDebug); |
| if (!enclosingDebug) |
| return NULL; |
| } |
| |
| DebugScopeObject *debugScope = DebugScopeObject::create(cx, *scope, enclosingDebug); |
| if (!debugScope) |
| return NULL; |
| |
| if (!DebugScopes::addDebugScope(cx, *scope, *debugScope)) |
| return NULL; |
| |
| return debugScope; |
| } |
| |
| static DebugScopeObject * |
| GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si) |
| { |
| if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, si)) |
| return debugScope; |
| |
| ScopeIter copy(si, cx); |
| RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy)); |
| if (!enclosingDebug) |
| return NULL; |
| |
| /* |
| * Create the missing scope object. For block objects, this takes care of |
| * storing variable values after the StackFrame has been popped. For call |
| * objects, we only use the pretend call object to access callee, bindings |
| * and to receive dynamically added properties. Together, this provides the |
| * nice invariant that every DebugScopeObject has a ScopeObject. |
| * |
| * Note: to preserve scopeChain depth invariants, these lazily-reified |
| * scopes must not be put on the frame's scope chain; instead, they are |
| * maintained via DebugScopes hooks. |
| */ |
| DebugScopeObject *debugScope = NULL; |
| switch (si.type()) { |
| case ScopeIter::Call: { |
| Rooted<CallObject*> callobj(cx, CallObject::createForFunction(cx, si.frame())); |
| if (!callobj) |
| return NULL; |
| |
| if (callobj->enclosingScope().is<DeclEnvObject>()) { |
| JS_ASSERT(CallObjectLambdaName(callobj->callee())); |
| DeclEnvObject &declenv = callobj->enclosingScope().as<DeclEnvObject>(); |
| enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug); |
| if (!enclosingDebug) |
| return NULL; |
| } |
| |
| debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug); |
| break; |
| } |
| case ScopeIter::Block: { |
| Rooted<StaticBlockObject *> staticBlock(cx, &si.staticBlock()); |
| ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.frame()); |
| if (!block) |
| return NULL; |
| |
| debugScope = DebugScopeObject::create(cx, *block, enclosingDebug); |
| break; |
| } |
| case ScopeIter::With: |
| case ScopeIter::StrictEvalScope: |
| JS_NOT_REACHED("should already have a scope"); |
| } |
| if (!debugScope) |
| return NULL; |
| |
| if (!DebugScopes::addDebugScope(cx, si, *debugScope)) |
| return NULL; |
| |
| return debugScope; |
| } |
| |
| static JSObject * |
| GetDebugScope(JSContext *cx, JSObject &obj) |
| { |
| /* |
| * As an engine invariant (maintained internally and asserted by Execute), |
| * ScopeObjects and non-ScopeObjects cannot be interleaved on the scope |
| * chain; every scope chain must start with zero or more ScopeObjects and |
| * terminate with one or more non-ScopeObjects (viz., GlobalObject). |
| */ |
| if (!obj.is<ScopeObject>()) { |
| #ifdef DEBUG |
| JSObject *o = &obj; |
| while ((o = o->enclosingScope())) |
| JS_ASSERT(!o->is<ScopeObject>()); |
| #endif |
| return &obj; |
| } |
| |
| Rooted<ScopeObject*> scope(cx, &obj.as<ScopeObject>()); |
| if (AbstractFramePtr frame = DebugScopes::hasLiveFrame(*scope)) { |
| ScopeIter si(frame, *scope, cx); |
| return GetDebugScope(cx, si); |
| } |
| ScopeIter si(scope->enclosingScope(), cx); |
| return GetDebugScopeForScope(cx, scope, si); |
| } |
| |
| static JSObject * |
| GetDebugScope(JSContext *cx, const ScopeIter &si) |
| { |
| JS_CHECK_RECURSION(cx, return NULL); |
| |
| if (si.done()) |
| return GetDebugScope(cx, si.enclosingScope()); |
| |
| if (!si.hasScopeObject()) |
| return GetDebugScopeForMissing(cx, si); |
| |
| Rooted<ScopeObject*> scope(cx, &si.scope()); |
| |
| ScopeIter copy(si, cx); |
| return GetDebugScopeForScope(cx, scope, ++copy); |
| } |
| |
| JSObject * |
| js::GetDebugScopeForFunction(JSContext *cx, HandleFunction fun) |
| { |
| assertSameCompartment(cx, fun); |
| JS_ASSERT(cx->compartment()->debugMode()); |
| if (!DebugScopes::updateLiveScopes(cx)) |
| return NULL; |
| return GetDebugScope(cx, *fun->environment()); |
| } |
| |
| JSObject * |
| js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame) |
| { |
| assertSameCompartment(cx, frame); |
| if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx)) |
| return NULL; |
| ScopeIter si(frame, cx); |
| return GetDebugScope(cx, si); |
| } |