зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1150253 - Part 1: SpiderMonkey should call an embedder-provided callback
instead of running the onGarbageCollection hook immediately; r=sfink
This commit is contained in:
Родитель
ce84bb0e7e
Коммит
1bbdc65eb9
|
@ -12,10 +12,11 @@
|
|||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include "jspubtd.h"
|
||||
|
||||
#include "js/GCAPI.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "js/TypeDecls.h"
|
||||
|
||||
|
@ -24,6 +25,9 @@ class Debugger;
|
|||
}
|
||||
|
||||
namespace JS {
|
||||
|
||||
using mozilla::UniquePtr;
|
||||
|
||||
namespace dbg {
|
||||
|
||||
// Helping embedding code build objects for Debugger
|
||||
|
@ -261,6 +265,24 @@ class BuilderOrigin : public Builder {
|
|||
void SetDebuggerMallocSizeOf(JSRuntime* runtime, mozilla::MallocSizeOf mallocSizeOf);
|
||||
|
||||
|
||||
|
||||
// Debugger and Garbage Collection Events
|
||||
// --------------------------------------
|
||||
//
|
||||
// The Debugger wants to report about its debuggees' GC cycles, however entering
|
||||
// JS after a GC is troublesome since SpiderMonkey will often do something like
|
||||
// force a GC and then rely on the nursery being empty. If we call into some
|
||||
// Debugger's hook after the GC, then JS runs and the nursery won't be
|
||||
// empty. Instead, we rely on embedders to call back into SpiderMonkey after a
|
||||
// GC and notify Debuggers to call their onGarbageCollection hook.
|
||||
|
||||
|
||||
// For each Debugger that observed a debuggee involved in the given GC event,
|
||||
// call its `onGarbageCollection` hook.
|
||||
JS_PUBLIC_API(bool)
|
||||
FireOnGarbageCollectionHook(JSContext* cx, GarbageCollectionEvent::Ptr&& data);
|
||||
|
||||
|
||||
|
||||
// Handlers for observing Promises
|
||||
// -------------------------------
|
||||
|
|
|
@ -7,12 +7,18 @@
|
|||
#ifndef js_GCAPI_h
|
||||
#define js_GCAPI_h
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include "js/HeapAPI.h"
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
class GCRuntime;
|
||||
}
|
||||
namespace gcstats {
|
||||
struct Statistics;
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum JSGCMode {
|
||||
|
@ -42,6 +48,8 @@ typedef enum JSGCInvocationKind {
|
|||
|
||||
namespace JS {
|
||||
|
||||
using mozilla::UniquePtr;
|
||||
|
||||
#define GCREASONS(D) \
|
||||
/* Reasons internal to the JS engine */ \
|
||||
D(API) \
|
||||
|
@ -254,6 +262,56 @@ FinishIncrementalGC(JSRuntime* rt, gcreason::Reason reason);
|
|||
extern JS_PUBLIC_API(void)
|
||||
AbortIncrementalGC(JSRuntime* rt);
|
||||
|
||||
namespace dbg {
|
||||
|
||||
// The `JS::dbg::GarbageCollectionEvent` class is essentially a view of the
|
||||
// `js::gcstats::Statistics` data without the uber implementation-specific bits.
|
||||
// It should generally be palatable for web developers.
|
||||
class GarbageCollectionEvent
|
||||
{
|
||||
// The major GC number of the GC cycle this data pertains to.
|
||||
uint64_t majorGCNumber_;
|
||||
|
||||
// Reference to a non-owned, statically allocated C string. This is a very
|
||||
// short reason explaining why a GC was triggered.
|
||||
const char* reason;
|
||||
|
||||
// Reference to a nullable, non-owned, statically allocated C string. If the
|
||||
// collection was forced to be non-incremental, this is a short reason of
|
||||
// why the GC could not perform an incremental collection.
|
||||
const char* nonincrementalReason;
|
||||
|
||||
// Represents a single slice of a possibly multi-slice incremental garbage
|
||||
// collection.
|
||||
struct Collection {
|
||||
int64_t startTimestamp;
|
||||
int64_t endTimestamp;
|
||||
};
|
||||
|
||||
// The set of garbage collection slices that made up this GC cycle.
|
||||
mozilla::Vector<Collection> collections;
|
||||
|
||||
GarbageCollectionEvent(const GarbageCollectionEvent& rhs) = delete;
|
||||
GarbageCollectionEvent& operator=(const GarbageCollectionEvent& rhs) = delete;
|
||||
|
||||
public:
|
||||
explicit GarbageCollectionEvent(uint64_t majorGCNum)
|
||||
: majorGCNumber_(majorGCNum)
|
||||
, reason(nullptr)
|
||||
, nonincrementalReason(nullptr)
|
||||
, collections()
|
||||
{ }
|
||||
|
||||
using Ptr = UniquePtr<GarbageCollectionEvent, DeletePolicy<GarbageCollectionEvent>>;
|
||||
static Ptr Create(JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t majorGCNumber);
|
||||
|
||||
JSObject* toJSObject(JSContext* cx) const;
|
||||
|
||||
uint64_t majorGCNumber() const { return majorGCNumber_; }
|
||||
};
|
||||
|
||||
} // namespace dbg
|
||||
|
||||
enum GCProgress {
|
||||
/*
|
||||
* During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END
|
||||
|
@ -280,6 +338,8 @@ struct JS_PUBLIC_API(GCDescription) {
|
|||
|
||||
char16_t* formatMessage(JSRuntime* rt) const;
|
||||
char16_t* formatJSON(JSRuntime* rt, uint64_t timestamp) const;
|
||||
|
||||
JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSRuntime* rt) const;
|
||||
};
|
||||
|
||||
typedef void
|
||||
|
|
|
@ -974,9 +974,6 @@ Statistics::endGC()
|
|||
if (fp)
|
||||
printStats();
|
||||
|
||||
if (!aborted)
|
||||
Debugger::onGarbageCollection(runtime, *this);
|
||||
|
||||
// Clear the timers at the end of a GC because we accumulate time in
|
||||
// between GCs for some (which come before PHASE_GC_BEGIN in the list.)
|
||||
PodZero(&phaseStartTimes[PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN);
|
||||
|
|
|
@ -264,7 +264,8 @@ void
|
|||
Zone::notifyObservingDebuggers()
|
||||
{
|
||||
for (CompartmentsInZoneIter comps(this); !comps.done(); comps.next()) {
|
||||
RootedGlobalObject global(runtimeFromAnyThread(), comps->maybeGlobal());
|
||||
JSRuntime* rt = runtimeFromAnyThread();
|
||||
RootedGlobalObject global(rt, comps->maybeGlobal());
|
||||
if (!global)
|
||||
continue;
|
||||
|
||||
|
@ -272,8 +273,16 @@ Zone::notifyObservingDebuggers()
|
|||
if (!dbgs)
|
||||
continue;
|
||||
|
||||
for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); r.popFront())
|
||||
r.front()->debuggeeIsBeingCollected();
|
||||
for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); r.popFront()) {
|
||||
if (!r.front()->debuggeeIsBeingCollected(rt->gc.majorGCCount())) {
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr,
|
||||
"OOM while notifying observing Debuggers of a GC: The onGarbageCollection\n"
|
||||
"hook will not be fired for this GC for some Debuggers!\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7029,6 +7029,12 @@ JS::GCDescription::formatMessage(JSRuntime* rt) const
|
|||
return rt->gc.stats.formatMessage();
|
||||
}
|
||||
|
||||
JS::dbg::GarbageCollectionEvent::Ptr
|
||||
JS::GCDescription::toGCEvent(JSRuntime* rt) const
|
||||
{
|
||||
return JS::dbg::GarbageCollectionEvent::Create(rt, rt->gc.stats, rt->gc.majorGCCount());
|
||||
}
|
||||
|
||||
char16_t*
|
||||
JS::GCDescription::formatJSON(JSRuntime* rt, uint64_t timestamp) const
|
||||
{
|
||||
|
|
|
@ -44,6 +44,7 @@ using js::frontend::IsIdentifier;
|
|||
using mozilla::ArrayLength;
|
||||
using mozilla::DebugOnly;
|
||||
using mozilla::Maybe;
|
||||
using mozilla::UniquePtr;
|
||||
|
||||
|
||||
/*** Forward declarations ************************************************************************/
|
||||
|
@ -358,9 +359,8 @@ Debugger::Debugger(JSContext* cx, NativeObject* dbg)
|
|||
: object(dbg),
|
||||
uncaughtExceptionHook(nullptr),
|
||||
enabled(true),
|
||||
observedGCs(cx),
|
||||
allowUnobservedAsmJS(false),
|
||||
debuggeeWasCollected(false),
|
||||
inOnGCHook(false),
|
||||
trackingAllocationSites(false),
|
||||
allocationSamplingProbability(1.0),
|
||||
allocationsLogLength(0),
|
||||
|
@ -408,6 +408,7 @@ Debugger::init(JSContext* cx)
|
|||
scripts.init() &&
|
||||
sources.init() &&
|
||||
objects.init() &&
|
||||
observedGCs.init() &&
|
||||
environments.init();
|
||||
if (!ok)
|
||||
ReportOutOfMemory(cx);
|
||||
|
@ -878,71 +879,6 @@ Debugger::wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
Debugger::translateGCStatistics(JSContext* cx, const gcstats::Statistics& stats)
|
||||
{
|
||||
// If this functions triggers a GC then the statistics object will change
|
||||
// underneath us.
|
||||
gc::AutoSuppressGC suppressGC(cx);
|
||||
|
||||
RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
const char* nonincrementalReason = stats.nonincrementalReason();
|
||||
RootedValue nonincrementalReasonValue(cx, NullValue());
|
||||
if (nonincrementalReason) {
|
||||
JSAtom* atomized = Atomize(cx, nonincrementalReason, strlen(nonincrementalReason));
|
||||
if (!atomized)
|
||||
return nullptr;
|
||||
nonincrementalReasonValue.setString(atomized);
|
||||
}
|
||||
|
||||
if (!DefineProperty(cx, obj, cx->names().nonincrementalReason, nonincrementalReasonValue))
|
||||
return nullptr;
|
||||
|
||||
RootedArrayObject slicesArray(cx, NewDenseEmptyArray(cx));
|
||||
if (!slicesArray)
|
||||
return nullptr;
|
||||
|
||||
size_t idx = 0;
|
||||
for (auto range = stats.sliceRange(); !range.empty(); range.popFront()) {
|
||||
if (idx == 0) {
|
||||
// There is only one GC reason for the whole cycle, but for legacy
|
||||
// reasons, this data is stored and replicated on each slice.
|
||||
const char* reason = gcstats::ExplainReason(range.front().reason);
|
||||
JSAtom* atomized = Atomize(cx, reason, strlen(reason));
|
||||
if (!atomized)
|
||||
return nullptr;
|
||||
RootedValue reasonVal(cx, StringValue(atomized));
|
||||
if (!DefineProperty(cx, obj, cx->names().reason, reasonVal))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedPlainObject collectionObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!collectionObj)
|
||||
return nullptr;
|
||||
|
||||
RootedValue start(cx, NumberValue(range.front().start));
|
||||
RootedValue end(cx, NumberValue(range.front().end));
|
||||
if (!DefineProperty(cx, collectionObj, cx->names().startTimestamp, start) ||
|
||||
!DefineProperty(cx, collectionObj, cx->names().endTimestamp, end))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue collectionVal(cx, ObjectValue(*collectionObj));
|
||||
if (!DefineElement(cx, slicesArray, idx++, collectionVal))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue slicesValue(cx, ObjectValue(*slicesArray));
|
||||
if (!DefineProperty(cx, obj, cx->names().collections, slicesValue))
|
||||
return nullptr;
|
||||
|
||||
return obj.get();
|
||||
}
|
||||
|
||||
bool
|
||||
Debugger::unwrapDebuggeeObject(JSContext* cx, MutableHandleObject obj)
|
||||
{
|
||||
|
@ -1334,18 +1270,11 @@ Debugger::fireNewScript(JSContext* cx, HandleScript script)
|
|||
}
|
||||
|
||||
void
|
||||
Debugger::fireOnGarbageCollectionHook(JSRuntime* rt, const gcstats::Statistics& stats)
|
||||
Debugger::fireOnGarbageCollectionHook(JSContext* cx,
|
||||
const JS::dbg::GarbageCollectionEvent::Ptr& gcData)
|
||||
{
|
||||
if (inOnGCHook)
|
||||
return;
|
||||
|
||||
AutoOnGCHookReentrancyGuard guard(*this);
|
||||
|
||||
MOZ_ASSERT(debuggeeWasCollected);
|
||||
debuggeeWasCollected = false;
|
||||
|
||||
JSContext* cx = DefaultJSContext(rt);
|
||||
MOZ_ASSERT(cx);
|
||||
MOZ_ASSERT(observedGC(gcData->majorGCNumber()));
|
||||
observedGCs.remove(gcData->majorGCNumber());
|
||||
|
||||
RootedObject hook(cx, getHook(OnGarbageCollection));
|
||||
MOZ_ASSERT(hook);
|
||||
|
@ -1354,15 +1283,15 @@ Debugger::fireOnGarbageCollectionHook(JSRuntime* rt, const gcstats::Statistics&
|
|||
Maybe<AutoCompartment> ac;
|
||||
ac.emplace(cx, object);
|
||||
|
||||
JSObject* statsObj = translateGCStatistics(cx, stats);
|
||||
if (!statsObj) {
|
||||
JSObject* dataObj = gcData->toJSObject(cx);
|
||||
if (!dataObj) {
|
||||
handleUncaughtException(ac, false);
|
||||
return;
|
||||
}
|
||||
|
||||
RootedValue statsVal(cx, ObjectValue(*statsObj));
|
||||
RootedValue dataVal(cx, ObjectValue(*dataObj));
|
||||
RootedValue rv(cx);
|
||||
if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, statsVal.address(), &rv))
|
||||
if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, dataVal.address(), &rv))
|
||||
handleUncaughtException(ac, true);
|
||||
}
|
||||
|
||||
|
@ -7865,3 +7794,129 @@ JS::dbg::IsDebugger(JS::Value val)
|
|||
|
||||
return js::Debugger::fromJSObject(&obj) != nullptr;
|
||||
}
|
||||
|
||||
|
||||
/*** JS::dbg::GarbageCollectionEvent **************************************************************/
|
||||
|
||||
namespace JS {
|
||||
namespace dbg {
|
||||
|
||||
/* static */ GarbageCollectionEvent::Ptr
|
||||
GarbageCollectionEvent::Create(JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t gcNumber)
|
||||
{
|
||||
auto data = rt->make_unique<GarbageCollectionEvent>(gcNumber);
|
||||
if (!data)
|
||||
return nullptr;
|
||||
|
||||
data->nonincrementalReason = stats.nonincrementalReason();
|
||||
|
||||
for (auto range = stats.sliceRange(); !range.empty(); range.popFront()) {
|
||||
if (!data->reason) {
|
||||
// There is only one GC reason for the whole cycle, but for legacy
|
||||
// reasons this data is stored and replicated on each slice. Each
|
||||
// slice used to have its own GCReason, but now they are all the
|
||||
// same.
|
||||
data->reason = gcstats::ExplainReason(range.front().reason);
|
||||
MOZ_ASSERT(data->reason);
|
||||
}
|
||||
|
||||
if (!data->collections.growBy(1))
|
||||
return nullptr;
|
||||
|
||||
data->collections.back().startTimestamp = range.front().start;
|
||||
data->collections.back().endTimestamp = range.front().end;
|
||||
}
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool
|
||||
DefineStringProperty(JSContext* cx, HandleObject obj, PropertyName* propName, const char* strVal)
|
||||
{
|
||||
RootedValue val(cx, UndefinedValue());
|
||||
if (strVal) {
|
||||
JSAtom* atomized = Atomize(cx, strVal, strlen(strVal));
|
||||
if (!atomized)
|
||||
return false;
|
||||
val = StringValue(atomized);
|
||||
}
|
||||
return DefineProperty(cx, obj, propName, val);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
GarbageCollectionEvent::toJSObject(JSContext* cx) const
|
||||
{
|
||||
RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!obj ||
|
||||
!DefineStringProperty(cx, obj, cx->names().nonincrementalReason, nonincrementalReason) ||
|
||||
!DefineStringProperty(cx, obj, cx->names().reason, reason))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedArrayObject slicesArray(cx, NewDenseEmptyArray(cx));
|
||||
if (!slicesArray)
|
||||
return nullptr;
|
||||
|
||||
size_t idx = 0;
|
||||
for (auto range = collections.all(); !range.empty(); range.popFront()) {
|
||||
RootedPlainObject collectionObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
|
||||
if (!collectionObj)
|
||||
return nullptr;
|
||||
|
||||
RootedValue start(cx, NumberValue(range.front().startTimestamp));
|
||||
RootedValue end(cx, NumberValue(range.front().endTimestamp));
|
||||
if (!DefineProperty(cx, collectionObj, cx->names().startTimestamp, start) ||
|
||||
!DefineProperty(cx, collectionObj, cx->names().endTimestamp, end))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue collectionVal(cx, ObjectValue(*collectionObj));
|
||||
if (!DefineElement(cx, slicesArray, idx++, collectionVal))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue slicesValue(cx, ObjectValue(*slicesArray));
|
||||
if (!DefineProperty(cx, obj, cx->names().collections, slicesValue))
|
||||
return nullptr;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
FireOnGarbageCollectionHook(JSContext* cx, JS::dbg::GarbageCollectionEvent::Ptr&& data)
|
||||
{
|
||||
AutoObjectVector triggered(cx);
|
||||
|
||||
{
|
||||
// We had better not GC (and potentially get a dangling Debugger
|
||||
// pointer) while finding all Debuggers observing a debuggee that
|
||||
// participated in this GC.
|
||||
AutoCheckCannotGC noGC;
|
||||
|
||||
for (Debugger* dbg = cx->runtime()->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) {
|
||||
if (dbg->enabled &&
|
||||
dbg->observedGC(data->majorGCNumber()) &&
|
||||
dbg->getHook(Debugger::OnGarbageCollection))
|
||||
{
|
||||
if (!triggered.append(dbg->object)) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( ; !triggered.empty(); triggered.popBack()) {
|
||||
Debugger* dbg = Debugger::fromJSObject(triggered.back());
|
||||
dbg->fireOnGarbageCollectionHook(cx, data);
|
||||
MOZ_ASSERT(!cx->isExceptionPending());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dbg
|
||||
} // namespace JS
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "mozilla/GuardObjects.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/Range.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include "jsclist.h"
|
||||
#include "jscntxt.h"
|
||||
|
@ -190,6 +192,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
friend JSObject* SavedStacksMetadataCallback(JSContext* cx);
|
||||
friend void JS::dbg::onNewPromise(JSContext* cx, HandleObject promise);
|
||||
friend void JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise);
|
||||
friend bool JS::dbg::FireOnGarbageCollectionHook(JSContext* cx,
|
||||
JS::dbg::GarbageCollectionEvent::Ptr&& data);
|
||||
|
||||
public:
|
||||
enum Hook {
|
||||
|
@ -242,9 +246,17 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
// false otherwise.
|
||||
bool isDebuggee(const JSCompartment* compartment) const;
|
||||
|
||||
// Notify this Debugger that one of its debuggee compartments' zones is
|
||||
// being collected.
|
||||
void debuggeeIsBeingCollected() { debuggeeWasCollected = true; }
|
||||
// Return true if this Debugger observed a debuggee that participated in the
|
||||
// GC identified by the given GC number. Return false otherwise.
|
||||
bool observedGC(uint64_t majorGCNumber) const {
|
||||
return observedGCs.has(majorGCNumber);
|
||||
}
|
||||
|
||||
// Notify this Debugger that one or more of its debuggees is participating
|
||||
// in the GC identified by the given GC number.
|
||||
bool debuggeeIsBeingCollected(uint64_t majorGCNumber) {
|
||||
return observedGCs.put(majorGCNumber);
|
||||
}
|
||||
|
||||
private:
|
||||
HeapPtrNativeObject object; /* The Debugger object. Strong reference. */
|
||||
|
@ -253,6 +265,10 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
bool enabled;
|
||||
JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */
|
||||
|
||||
// The set of GC numbers for which one or more of this Debugger's observed
|
||||
// debuggees participated in.
|
||||
js::HashSet<uint64_t> observedGCs;
|
||||
|
||||
struct AllocationSite : public mozilla::LinkedListElement<AllocationSite>
|
||||
{
|
||||
AllocationSite(HandleObject frame, int64_t when, const char* className)
|
||||
|
@ -270,38 +286,6 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
typedef mozilla::LinkedList<AllocationSite> AllocationSiteList;
|
||||
|
||||
bool allowUnobservedAsmJS;
|
||||
|
||||
// During a GC cycle, this is true if one of this Debugger's debuggees was
|
||||
// collected. When the GC cycle completes, this flag is reset.
|
||||
bool debuggeeWasCollected;
|
||||
|
||||
// True while we are executing the onGarbageCollection hook, and therefore
|
||||
// should not fire the hook for this Debugger instance again if there is a
|
||||
// GC while we are executing the hook. See also
|
||||
// `AutoOnGCHookReentrancyGuard` below.
|
||||
bool inOnGCHook;
|
||||
|
||||
// RAII class to automatically guard against reentrancy into the
|
||||
// OnGarbageCollection hook.
|
||||
class MOZ_STACK_CLASS AutoOnGCHookReentrancyGuard {
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
|
||||
Debugger& dbg;
|
||||
|
||||
public:
|
||||
explicit AutoOnGCHookReentrancyGuard(Debugger& dbg MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: dbg(dbg)
|
||||
{
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
MOZ_ASSERT(!dbg.inOnGCHook);
|
||||
dbg.inOnGCHook = true;
|
||||
}
|
||||
|
||||
~AutoOnGCHookReentrancyGuard() {
|
||||
MOZ_ASSERT(dbg.inOnGCHook);
|
||||
dbg.inOnGCHook = false;
|
||||
}
|
||||
};
|
||||
|
||||
bool trackingAllocationSites;
|
||||
double allocationSamplingProbability;
|
||||
AllocationSiteList allocationsLog;
|
||||
|
@ -554,9 +538,10 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
|
||||
/*
|
||||
* Receive a "garbage collection" event from the engine. A GC cycle with the
|
||||
* given statistics was just completed.
|
||||
* given data was recently completed.
|
||||
*/
|
||||
void fireOnGarbageCollectionHook(JSRuntime* rt, const gcstats::Statistics& stats);
|
||||
void fireOnGarbageCollectionHook(JSContext* cx,
|
||||
const JS::dbg::GarbageCollectionEvent::Ptr& gcData);
|
||||
|
||||
/*
|
||||
* Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
|
||||
|
@ -675,7 +660,6 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
static inline void onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
|
||||
static inline bool onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame,
|
||||
int64_t when);
|
||||
static inline void onGarbageCollection(JSRuntime* rt, const gcstats::Statistics& stats);
|
||||
static JSTrapStatus onTrap(JSContext* cx, MutableHandleValue vp);
|
||||
static JSTrapStatus onSingleStep(JSContext* cx, MutableHandleValue vp);
|
||||
static bool handleBaselineOsr(JSContext* cx, InterpreterFrame* from, jit::BaselineFrame* to);
|
||||
|
@ -724,13 +708,6 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
*/
|
||||
bool wrapDebuggeeValue(JSContext* cx, MutableHandleValue vp);
|
||||
|
||||
/*
|
||||
* Converts an implementor level of detail gcstats::Statistics object into a
|
||||
* JSObject that web developers should be able to make sense of. Returns
|
||||
* nullptr on failure.
|
||||
*/
|
||||
JSObject* translateGCStatistics(JSContext* cx, const gcstats::Statistics& stats);
|
||||
|
||||
/*
|
||||
* Unwrap a Debug.Object, without rewrapping it for any particular debuggee
|
||||
* compartment.
|
||||
|
@ -992,18 +969,9 @@ Debugger::onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame fra
|
|||
return Debugger::slowPathOnLogAllocationSite(cx, hobj, frame, when, *dbgs);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Debugger::onGarbageCollection(JSRuntime* rt, const gcstats::Statistics& stats)
|
||||
{
|
||||
for (Debugger* dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) {
|
||||
if (dbg->debuggeeWasCollected && dbg->getHook(OnGarbageCollection)) {
|
||||
dbg->fireOnGarbageCollectionHook(rt, stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ReportObjectRequired(JSContext* cx);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
|
||||
#endif /* vm_Debugger_h */
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "gc/GCRuntime.h"
|
||||
#include "gc/Tracer.h"
|
||||
#include "irregexp/RegExpStack.h"
|
||||
#include "js/Debug.h"
|
||||
#include "js/HashTable.h"
|
||||
#ifdef DEBUG
|
||||
# include "js/Proxy.h" // For AutoEnterPolicy
|
||||
|
|
Загрузка…
Ссылка в новой задаче