Bug 1420975 - Add a environment variable to record JS stack for leaks. r=froydnj,mrbkap
This patch adds a new environment variable XPCOM_MEM_LOG_JS_STACK that
changes XPCOM leakchecking to record a JS stack for all objects, in
addition to a C++ stack. This is useful when a C++ object is being
leaked due to JS. The JS stack will be printed if the object leaks, if
it is used in combination with XPCOM_MEM_BLOAT_LOG=1 and
XPCOM_MEM_LOG_CLASSES=nsFoo, if nsFoo is the class of interest.
This patch moves a few XPConnect functions for recording the stack
into xpcpublic.h so they can be called from nsTraceRefcnt.cpp.
MozReview-Commit-ID: FX2QVCSXz4f
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -2348,28 +2348,16 @@ extern JSObject*
xpc_NewIDObject(JSContext* cx, JS::HandleObject jsobj, const nsID& aID);
extern const nsID*
xpc_JSObjectToID(JSContext* cx, JSObject* obj);
extern bool
xpc_JSObjectIsID(JSContext* cx, JSObject* obj);
-/***************************************************************************/
-// in XPCDebug.cpp
-
-extern bool
-xpc_DumpJSStack(bool showArgs, bool showLocals, bool showThisProps);
-
-// Return a newly-allocated string containing a representation of the
-// current JS stack.
-extern JS::UniqueChars
-xpc_PrintJSStack(JSContext* cx, bool showArgs, bool showLocals,
- bool showThisProps);
-
/******************************************************************************
* Handles pre/post script processing.
*/
class MOZ_RAII AutoScriptEvaluate
{
public:
/**
* Saves the JSContext as well as initializing our state
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -249,16 +249,27 @@ xpc_MarkInCCGeneration(nsISupports* aVar
// If aWrappedJS is a JS wrapper, unmark its JSObject.
extern void
xpc_TryUnmarkWrappedGrayObject(nsISupports* aWrappedJS);
extern void
xpc_UnmarkSkippableJSHolders();
+// Defined in XPCDebug.cpp.
+extern bool
+xpc_DumpJSStack(bool showArgs, bool showLocals, bool showThisProps);
+
+// Return a newly-allocated string containing a representation of the
+// current JS stack. Defined in XPCDebug.cpp.
+extern JS::UniqueChars
+xpc_PrintJSStack(JSContext* cx, bool showArgs, bool showLocals,
+ bool showThisProps);
+
+
// readable string conversions, static methods and members only
class XPCStringConvert
{
public:
// If the string shares the readable's buffer, that buffer will
// get assigned to *sharedBuffer. Otherwise null will be
// assigned.
--- a/xpcom/base/nsTraceRefcnt.cpp
+++ b/xpcom/base/nsTraceRefcnt.cpp
@@ -29,16 +29,17 @@
#else
#include <unistd.h>
#endif
#include "mozilla/Atomics.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/BlockingResourceBase.h"
#include "mozilla/PoisonIOInterposer.h"
+#include "mozilla/UniquePtr.h"
#include <string>
#include <vector>
#ifdef HAVE_DLOPEN
#include <dlfcn.h>
#endif
@@ -78,16 +79,17 @@ struct MOZ_STACK_CLASS AutoTraceLogLock
};
static PLHashTable* gBloatView;
static PLHashTable* gTypesToLog;
static PLHashTable* gObjectsToLog;
static PLHashTable* gSerialNumbers;
static intptr_t gNextSerialNumber;
static bool gDumpedStatistics = false;
+static bool gLogJSStacks = false;
// By default, debug builds only do bloat logging. Bloat logging
// only tries to record when an object is created or destroyed, so we
// optimize the common case in NS_LogAddRef and NS_LogRelease where
// only bloat logging is enabled and no logging needs to be done.
enum LoggingType
{
NoLogging,
@@ -128,16 +130,37 @@ struct SerialNumberRecord
intptr_t serialNumber;
int32_t refCount;
int32_t COMPtrCount;
// We use std:: classes here rather than the XPCOM equivalents because the
// XPCOM equivalents do leak-checking, and if you try to leak-check while
// leak-checking, you're gonna have a bad time.
std::vector<void*> allocationStack;
+ mozilla::UniquePtr<char[]> jsStack;
+
+ void SaveJSStack() {
+ // If this thread isn't running JS, there's nothing to do.
+ if (!CycleCollectedJSContext::Get()) {
+ return;
+ }
+
+ JSContext* cx = nsContentUtils::GetCurrentJSContextForThread();
+ if (!cx) {
+ return;
+ }
+
+ JS::UniqueChars chars = xpc_PrintJSStack(cx,
+ /*showArgs=*/ false,
+ /*showLocals=*/ false,
+ /*showThisProps=*/ false);
+ size_t len = strlen(chars.get());
+ jsStack = MakeUnique<char[]>(len + 1);
+ memcpy(jsStack.get(), chars.get(), len + 1);
+ }
};
struct nsTraceRefcntStats
{
uint64_t mCreates;
uint64_t mDestroys;
bool HaveLeaks() const
@@ -467,16 +490,25 @@ DumpSerialNumbers(PLHashEntry* aHashEntr
for (size_t i = 0, length = record->allocationStack.size();
i < length;
++i) {
gCodeAddressService->GetLocation(i, record->allocationStack[i],
buf, bufLen);
fprintf(outputFile, "%s\n", buf);
}
}
+
+ if (gLogJSStacks) {
+ if (record->jsStack) {
+ fprintf(outputFile, "JS allocation stack:\n%s\n", record->jsStack.get());
+ } else {
+ fprintf(outputFile, "There is no JS context on the stack.\n");
+ }
+ }
+
return HT_ENUMERATE_NEXT;
}
template<>
class nsDefaultComparator<BloatEntry*, BloatEntry*>
{
public:
@@ -582,16 +614,19 @@ GetSerialNumber(void* aPtr, bool aCreate
if (!aCreate) {
return 0;
}
SerialNumberRecord* record = new SerialNumberRecord();
WalkTheStackSavingLocations(record->allocationStack);
PL_HashTableRawAdd(gSerialNumbers, hep, HashNumber(aPtr),
aPtr, static_cast<void*>(record));
+ if (gLogJSStacks) {
+ record->SaveJSStack();
+ }
return gNextSerialNumber;
}
static int32_t*
GetRefCount(void* aPtr)
{
PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers,
HashNumber(aPtr),
@@ -824,16 +859,20 @@ InitTraceLog()
}
*cm = ',';
cp = cm + 1;
}
fprintf(stdout, "\n");
}
}
+ if (getenv("XPCOM_MEM_LOG_JS_STACK")) {
+ fprintf(stdout, "### XPCOM_MEM_LOG_JS_STACK defined\n");
+ gLogJSStacks = true;
+ }
if (gBloatLog) {
gLogging = OnlyBloatLogging;
}
if (gRefcntsLog || gAllocLog || gCOMPtrLog) {
gLogging = FullLogging;
}