Bug 1420975 - Add a environment variable to record JS stack for leaks. r=froydnj,mrbkap draft
authorAndrew McCreight <continuation@gmail.com>
Wed, 10 Jan 2018 14:02:45 -0800
changeset 748918 d771eea22118f5cfb9b02a2e8838b969bed1a188
parent 748899 fd995039d89708923b5673ecebc652967d40bd4e
push id97275
push userbmo:continuation@gmail.com
push dateTue, 30 Jan 2018 18:01:03 +0000
reviewersfroydnj, mrbkap
bugs1420975
milestone60.0a1
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
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
xpcom/base/nsTraceRefcnt.cpp
--- 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;
   }