mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-23 02:05:42 +00:00
Bug 1551176: Add GENERATOR_INFO_SLOT to js::DebuggerFrame. r=jorendorff
Debugger.Frame objects referring to generator or async calls need to be able to find the call's generator object, even when the call is suspended. This patch adds a reserved slot to js::DebuggerFrame objects that points to a new GeneratorInfo class that holds a cross-compartment wrapper to the generator object. Differential Revision: https://phabricator.services.mozilla.com/D32270 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
d990c31e1d
commit
2402708a44
@ -117,10 +117,17 @@ class CrossCompartmentKey {
|
||||
: Debuggee(debugger, referent) {}
|
||||
};
|
||||
|
||||
using WrappedType =
|
||||
mozilla::Variant<JSObject*, JSString*, DebuggeeObject, DebuggeeJSScript,
|
||||
DebuggeeWasmScript, DebuggeeLazyScript,
|
||||
DebuggeeEnvironment, DebuggeeSource>;
|
||||
// Key under which we find debugger's Debugger.Frame for the generator call
|
||||
// whose AbstractGeneratorObject is referent.
|
||||
struct DebuggeeFrameGenerator : Debuggee<NativeObject> {
|
||||
DebuggeeFrameGenerator(NativeObject* debugger, NativeObject* referent)
|
||||
: Debuggee(debugger, referent) {}
|
||||
};
|
||||
|
||||
using WrappedType = mozilla::Variant<JSObject*, JSString*, DebuggeeObject,
|
||||
DebuggeeJSScript, DebuggeeWasmScript,
|
||||
DebuggeeLazyScript, DebuggeeEnvironment,
|
||||
DebuggeeSource, DebuggeeFrameGenerator>;
|
||||
|
||||
explicit CrossCompartmentKey(JSObject* obj) : wrapped(obj) {
|
||||
MOZ_RELEASE_ASSERT(obj);
|
||||
@ -143,6 +150,8 @@ class CrossCompartmentKey {
|
||||
: wrapped(std::move(key)) {}
|
||||
explicit CrossCompartmentKey(DebuggeeWasmScript&& key)
|
||||
: wrapped(std::move(key)) {}
|
||||
explicit CrossCompartmentKey(DebuggeeFrameGenerator&& key)
|
||||
: wrapped(std::move(key)) {}
|
||||
explicit CrossCompartmentKey(NativeObject* debugger, JSScript* referent)
|
||||
: wrapped(DebuggeeJSScript(debugger, referent)) {}
|
||||
explicit CrossCompartmentKey(NativeObject* debugger, LazyScript* referent)
|
||||
|
@ -136,4 +136,15 @@ inline js::PromiseObject* js::DebuggerObject::promise() const {
|
||||
return &referent->as<PromiseObject>();
|
||||
}
|
||||
|
||||
inline bool js::DebuggerFrame::hasGenerator() const {
|
||||
return !getReservedSlot(GENERATOR_INFO_SLOT).isUndefined();
|
||||
}
|
||||
|
||||
inline js::DebuggerFrame::GeneratorInfo* js::DebuggerFrame::generatorInfo()
|
||||
const {
|
||||
MOZ_ASSERT(hasGenerator());
|
||||
return static_cast<GeneratorInfo*>(
|
||||
getReservedSlot(GENERATOR_INFO_SLOT).toPrivate());
|
||||
}
|
||||
|
||||
#endif /* vm_Debugger_inl_h */
|
||||
|
@ -107,7 +107,9 @@ const ClassOps DebuggerFrame::classOps_ = {
|
||||
const Class DebuggerFrame::class_ = {
|
||||
"Frame",
|
||||
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
|
||||
JSCLASS_BACKGROUND_FINALIZE,
|
||||
// We require foreground finalization so we can destruct GeneratorInfo's
|
||||
// HeapPtrs.
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&DebuggerFrame::classOps_};
|
||||
|
||||
enum { JSSLOT_DEBUGARGUMENTS_FRAME, JSSLOT_DEBUGARGUMENTS_COUNT };
|
||||
@ -715,6 +717,7 @@ bool Debugger::getFrame(JSContext* cx, const FrameIter& iter,
|
||||
gp = generatorFrames.lookupForAdd(genObj);
|
||||
if (gp) {
|
||||
frame = &gp->value()->as<DebuggerFrame>();
|
||||
MOZ_ASSERT(&frame->unwrappedGenerator() == genObj);
|
||||
|
||||
// We have found an existing Debugger.Frame object. But
|
||||
// since it was previously popped (see comment above), it
|
||||
@ -749,8 +752,13 @@ bool Debugger::getFrame(JSContext* cx, const FrameIter& iter,
|
||||
}
|
||||
|
||||
if (genObj) {
|
||||
if (!frame->setGenerator(cx, genObj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DebuggerFrame* frameObj = frame;
|
||||
if (!generatorFrames.relookupOrAdd(gp, genObj, frameObj)) {
|
||||
frame->clearGenerator(cx->runtime()->defaultFreeOp());
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
@ -761,6 +769,7 @@ bool Debugger::getFrame(JSContext* cx, const FrameIter& iter,
|
||||
NukeDebuggerWrapper(frame);
|
||||
if (genObj) {
|
||||
generatorFrames.remove(genObj);
|
||||
frame->clearGenerator(cx->runtime()->defaultFreeOp());
|
||||
}
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
@ -778,7 +787,14 @@ bool Debugger::addGeneratorFrame(JSContext* cx,
|
||||
if (p) {
|
||||
MOZ_ASSERT(p->value() == frameObj);
|
||||
} else {
|
||||
{
|
||||
AutoRealm ar(cx, frameObj);
|
||||
if (!frameObj->setGenerator(cx, genObj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!generatorFrames.relookupOrAdd(p, genObj, frameObj)) {
|
||||
frameObj->clearGenerator(cx->runtime()->defaultFreeOp());
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
@ -912,6 +928,7 @@ ResumeMode Debugger::slowPathOnResumeFrame(JSContext* cx,
|
||||
for (Debugger* dbg : *debuggers) {
|
||||
if (GeneratorWeakMap::Ptr entry = dbg->generatorFrames.lookup(genObj)) {
|
||||
DebuggerFrame* frameObj = &entry->value()->as<DebuggerFrame>();
|
||||
MOZ_ASSERT(&frameObj->unwrappedGenerator() == genObj);
|
||||
if (!dbg->frames.putNew(frame, frameObj)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return ResumeMode::Throw;
|
||||
@ -2401,6 +2418,7 @@ ResumeMode Debugger::onSingleStep(JSContext* cx, MutableHandleValue vp) {
|
||||
AbstractGeneratorObject& genObj =
|
||||
r.front().key()->as<AbstractGeneratorObject>();
|
||||
DebuggerFrame& frameObj = r.front().value()->as<DebuggerFrame>();
|
||||
MOZ_ASSERT(&frameObj.unwrappedGenerator() == &genObj);
|
||||
|
||||
// Live Debugger.Frames were already counted in dbg->frames loop.
|
||||
if (frameObj.isLive()) {
|
||||
@ -4499,9 +4517,14 @@ void Debugger::removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
|
||||
// live generators would keep the global alive and we wouldn't be here. GC
|
||||
// will sweep dead keys from the weakmap.
|
||||
if (!global->zone()->isGCSweeping()) {
|
||||
generatorFrames.removeIf([global](JSObject* key) {
|
||||
generatorFrames.removeIf([global, fop](JSObject* key, JSObject* value) {
|
||||
auto& genObj = key->as<AbstractGeneratorObject>();
|
||||
return genObj.isClosed() || &genObj.callee().global() == global;
|
||||
auto& frameObj = value->as<DebuggerFrame>();
|
||||
if (genObj.isClosed() || &genObj.callee().global() == global) {
|
||||
frameObj.clearGenerator(fop);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@ -7806,10 +7829,19 @@ void Debugger::removeFromFrameMapsAndClearBreakpointsIn(JSContext* cx,
|
||||
dbg->frames.remove(frame);
|
||||
|
||||
if (!suspending && frame.isGeneratorFrame()) {
|
||||
// Terminally exiting a generator.
|
||||
// Terminally exiting a generator. Note that, since
|
||||
// GetGeneratorObjectForFrame cannot find a generator frame's generator
|
||||
// object between the GENERATOR opcode and the SETALIASEDVAR opcode, it
|
||||
// may return nullptr even for frames that do have generator objects.
|
||||
auto* genObj = GetGeneratorObjectForFrame(cx, frame);
|
||||
if (GeneratorWeakMap::Ptr p = dbg->generatorFrames.lookup(genObj)) {
|
||||
MOZ_ASSERT(p->value() == frameobj);
|
||||
dbg->generatorFrames.remove(p);
|
||||
|
||||
// Maintain the invariant that a DebuggerFrame's GENERATOR_INFO_SLOT is
|
||||
// populated if and only if there is a corresponding entry in
|
||||
// generatorFrames.
|
||||
frameobj->clearGenerator(fop);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -8990,6 +9022,88 @@ DebuggerFrame* DebuggerFrame::create(JSContext* cx, HandleObject proto,
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information held by a DebuggerFrame about a generator/async call. A
|
||||
* Debugger.Frame's GENERATOR_INFO_SLOT, if set, holds a PrivateValue pointing
|
||||
* to one of these.
|
||||
*
|
||||
* This is created and attached as soon as a generator object is created for a
|
||||
* debuggee generator/async frame, retained across suspensions and resumptions,
|
||||
* and cleared when the generator call ends permanently.
|
||||
*
|
||||
* It may seem like this information might belong in ordinary reserved slots on
|
||||
* the DebuggerFrame object. But that isn't possible:
|
||||
*
|
||||
* 1) Slots cannot contain cross-compartment references directly.
|
||||
* 2) Ordinary cross-compartment wrappers aren't good enough, because the
|
||||
* debugger must create its own magic entries in the wrapper table for the GC
|
||||
* to get zone collection groups right.
|
||||
* 3) Even if we make debugger wrapper table entries by hand, hiding
|
||||
* cross-compartment edges as PrivateValues doesn't call post-barriers, and
|
||||
* the generational GC won't update our pointer when the generator object
|
||||
* gets tenured.
|
||||
*
|
||||
* Yes, officer, I definitely knew all this in advance and designed it this way
|
||||
* the first time.
|
||||
*/
|
||||
class DebuggerFrame::GeneratorInfo {
|
||||
// An unwrapped cross-compartment reference to the generator object.
|
||||
//
|
||||
// Always an object.
|
||||
//
|
||||
// This cannot be GCPtr because we are not always destructed during sweeping;
|
||||
// a Debugger.Frame's generator is also cleared when the generator returns
|
||||
// permanently.
|
||||
HeapPtr<Value> unwrappedGenerator_;
|
||||
|
||||
public:
|
||||
explicit GeneratorInfo(Handle<AbstractGeneratorObject*> unwrappedGenerator)
|
||||
: unwrappedGenerator_(ObjectValue(*unwrappedGenerator)) {}
|
||||
|
||||
void trace(JSTracer* tracer, DebuggerFrame& frameObj) {
|
||||
TraceCrossCompartmentEdge(tracer, &frameObj, &unwrappedGenerator_,
|
||||
"Debugger.Frame generator object");
|
||||
}
|
||||
|
||||
AbstractGeneratorObject& unwrappedGenerator() const {
|
||||
return unwrappedGenerator_.toObject().as<AbstractGeneratorObject>();
|
||||
}
|
||||
};
|
||||
|
||||
bool DebuggerFrame::setGenerator(
|
||||
JSContext* cx, Handle<AbstractGeneratorObject*> unwrappedGenObj) {
|
||||
cx->check(this);
|
||||
|
||||
RootedNativeObject owner(cx, this->owner()->toJSObject());
|
||||
Rooted<CrossCompartmentKey> key(
|
||||
cx, CrossCompartmentKey::DebuggeeFrameGenerator(owner, unwrappedGenObj));
|
||||
if (!compartment()->putWrapper(cx, key, ObjectValue(*this))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* info = cx->new_<GeneratorInfo>(unwrappedGenObj);
|
||||
if (!info) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
setReservedSlot(GENERATOR_INFO_SLOT, PrivateValue(info));
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebuggerFrame::clearGenerator(FreeOp* fop) {
|
||||
if (!hasGenerator()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fop->delete_(generatorInfo());
|
||||
setReservedSlot(GENERATOR_INFO_SLOT, UndefinedValue());
|
||||
}
|
||||
|
||||
js::AbstractGeneratorObject& js::DebuggerFrame::unwrappedGenerator() const {
|
||||
return generatorInfo()->unwrappedGenerator();
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool DebuggerFrame::getCallee(JSContext* cx, HandleDebuggerFrame frame,
|
||||
MutableHandleDebuggerObject result) {
|
||||
@ -9584,7 +9698,7 @@ void DebuggerFrame::maybeDecrementFrameScriptStepperCount(
|
||||
|
||||
/* static */
|
||||
void DebuggerFrame::finalize(FreeOp* fop, JSObject* obj) {
|
||||
MOZ_ASSERT(fop->maybeOnHelperThread());
|
||||
MOZ_ASSERT(fop->onMainThread());
|
||||
DebuggerFrame& frameobj = obj->as<DebuggerFrame>();
|
||||
frameobj.freeFrameIterData(fop);
|
||||
OnStepHandler* onStepHandler = frameobj.onStepHandler();
|
||||
@ -9595,6 +9709,7 @@ void DebuggerFrame::finalize(FreeOp* fop, JSObject* obj) {
|
||||
if (onPopHandler) {
|
||||
onPopHandler->drop();
|
||||
}
|
||||
frameobj.clearGenerator(fop);
|
||||
}
|
||||
|
||||
/* static */
|
||||
@ -9607,6 +9722,11 @@ void DebuggerFrame::trace(JSTracer* trc, JSObject* obj) {
|
||||
if (onPopHandler) {
|
||||
onPopHandler->trace(trc);
|
||||
}
|
||||
|
||||
DebuggerFrame& frameObj = obj->as<DebuggerFrame>();
|
||||
if (frameObj.hasGenerator()) {
|
||||
frameObj.generatorInfo()->trace(trc, frameObj);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
@ -175,12 +175,13 @@ class DebuggerWeakMap
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Remove entries whose keys satisfy the given predicate.
|
||||
// Remove all entries for which test(key, value) returns true.
|
||||
template <typename Predicate>
|
||||
void removeIf(Predicate test) {
|
||||
for (Enum e(*static_cast<Base*>(this)); !e.empty(); e.popFront()) {
|
||||
JSObject* key = e.front().key();
|
||||
if (test(key)) {
|
||||
JSObject* value = e.front().value();
|
||||
if (test(key, value)) {
|
||||
e.removeFront();
|
||||
}
|
||||
}
|
||||
@ -519,6 +520,9 @@ class Debugger : private mozilla::LinkedListElement<Debugger> {
|
||||
* regardless of whether the frame is currently suspended. (This list is
|
||||
* meant to explain why we update the table in the particular places where
|
||||
* we do so.)
|
||||
*
|
||||
* An entry in this table exists if and only if the Debugger.Frame's
|
||||
* GENERATOR_INFO_SLOT is set.
|
||||
*/
|
||||
typedef DebuggerWeakMap<JSObject*> GeneratorWeakMap;
|
||||
GeneratorWeakMap generatorFrames;
|
||||
@ -1445,6 +1449,20 @@ class DebuggerFrame : public NativeObject {
|
||||
ARGUMENTS_SLOT,
|
||||
ONSTEP_HANDLER_SLOT,
|
||||
ONPOP_HANDLER_SLOT,
|
||||
|
||||
// If this is a frame for a generator call, and the generator object has
|
||||
// been created (which doesn't happen until after default argument
|
||||
// evaluation and destructuring), then this is a PrivateValue pointing to a
|
||||
// GeneratorInfo struct that points to the call's AbstractGeneratorObject.
|
||||
// This allows us to implement Debugger.Frame methods even while the call is
|
||||
// suspended, and we have no FrameIter::Data.
|
||||
//
|
||||
// While Debugger::generatorFrames maps an AbstractGeneratorObject to its
|
||||
// Debugger.Frame, this link represents the reverse relation, from a
|
||||
// Debugger.Frame to its generator object. This slot is set if and only if
|
||||
// there is a corresponding entry in generatorFrames.
|
||||
GENERATOR_INFO_SLOT,
|
||||
|
||||
RESERVED_SLOTS,
|
||||
};
|
||||
|
||||
@ -1501,6 +1519,15 @@ class DebuggerFrame : public NativeObject {
|
||||
OnPopHandler* onPopHandler() const;
|
||||
void setOnPopHandler(OnPopHandler* handler);
|
||||
|
||||
bool setGenerator(JSContext* cx,
|
||||
Handle<AbstractGeneratorObject*> unwrappedGenObj);
|
||||
bool hasGenerator() const;
|
||||
void clearGenerator(FreeOp* fop);
|
||||
|
||||
// If hasGenerator(), return an direct cross-compartment reference to this
|
||||
// Debugger.Frame's generator object.
|
||||
AbstractGeneratorObject& unwrappedGenerator() const;
|
||||
|
||||
/*
|
||||
* Called after a generator/async frame is resumed, before exposing this
|
||||
* Debugger.Frame object to any hooks.
|
||||
@ -1562,6 +1589,10 @@ class DebuggerFrame : public NativeObject {
|
||||
void freeFrameIterData(FreeOp* fop);
|
||||
void maybeDecrementFrameScriptStepperCount(FreeOp* fop,
|
||||
AbstractFramePtr frame);
|
||||
|
||||
private:
|
||||
class GeneratorInfo;
|
||||
inline GeneratorInfo* generatorInfo() const;
|
||||
};
|
||||
|
||||
class DebuggerObject : public NativeObject {
|
||||
|
Loading…
Reference in New Issue
Block a user