Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / base / nsTraceRefcnt.cpp
blob41de68eb31d32f608db4bc430e052bf1eb60dd05
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsTraceRefcnt.h"
9 #include "base/process_util.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/AutoRestore.h"
12 #include "mozilla/CycleCollectedJSContext.h"
13 #include "mozilla/IntegerPrintfMacros.h"
14 #include "mozilla/Path.h"
15 #include "mozilla/Sprintf.h"
16 #include "mozilla/StaticPtr.h"
17 #include "nsXPCOMPrivate.h"
18 #include "nscore.h"
19 #include "nsClassHashtable.h"
20 #include "nsContentUtils.h"
21 #include "nsISupports.h"
22 #include "nsHashKeys.h"
23 #include "nsPrintfCString.h"
24 #include "nsTArray.h"
25 #include "nsTHashtable.h"
26 #include "prenv.h"
27 #include "prlink.h"
28 #include "nsCRT.h"
29 #include <math.h>
30 #include "nsHashKeys.h"
31 #include "mozilla/StackWalk.h"
32 #include "nsThreadUtils.h"
33 #include "CodeAddressService.h"
35 #include "nsXULAppAPI.h"
36 #ifdef XP_WIN
37 # include <io.h>
38 # include <process.h>
39 # define getpid _getpid
40 #else
41 # include <unistd.h>
42 #endif
44 #include "mozilla/Atomics.h"
45 #include "mozilla/AutoRestore.h"
46 #include "mozilla/BlockingResourceBase.h"
47 #include "mozilla/PoisonIOInterposer.h"
48 #include "mozilla/UniquePtr.h"
50 #include <string>
51 #include <vector>
53 #ifdef HAVE_DLOPEN
54 # include <dlfcn.h>
55 #endif
57 #ifdef MOZ_DMD
58 # include "nsMemoryInfoDumper.h"
59 #endif
61 // dynamic_cast<void*> is not supported on Windows without RTTI.
62 #ifndef _WIN32
63 # define HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
64 #endif
66 ////////////////////////////////////////////////////////////////////////////////
68 #include "prthread.h"
70 class MOZ_CAPABILITY("mutex") TraceLogMutex
71 : private mozilla::detail::MutexImpl {
72 public:
73 explicit TraceLogMutex() = default;
75 private:
76 friend class AutoTraceLogLock;
78 void Lock() MOZ_CAPABILITY_ACQUIRE() { ::mozilla::detail::MutexImpl::lock(); }
79 void Unlock() MOZ_CAPABILITY_RELEASE() {
80 ::mozilla::detail::MutexImpl::unlock();
84 static TraceLogMutex gTraceLog;
86 class MOZ_RAII AutoTraceLogLock {
87 public:
88 explicit AutoTraceLogLock(TraceLogMutex& aMutex) : mMutex(aMutex) {
89 mMutex.Lock();
92 AutoTraceLogLock(const AutoTraceLogLock&) = delete;
93 AutoTraceLogLock& operator=(const AutoTraceLogLock&) = delete;
94 AutoTraceLogLock(AutoTraceLogLock&&) = delete;
95 AutoTraceLogLock& operator=(AutoTraceLogLock&&) = delete;
97 ~AutoTraceLogLock() { mMutex.Unlock(); }
99 private:
100 TraceLogMutex& mMutex;
103 class BloatEntry;
104 struct SerialNumberRecord;
106 using mozilla::AutoRestore;
107 using mozilla::CodeAddressService;
108 using mozilla::CycleCollectedJSContext;
109 using mozilla::StaticAutoPtr;
111 using BloatHash = nsClassHashtable<nsDepCharHashKey, BloatEntry>;
112 using CharPtrSet = nsTHashtable<nsCharPtrHashKey>;
113 using IntPtrSet = nsTHashtable<IntPtrHashKey>;
114 using SerialHash = nsClassHashtable<nsVoidPtrHashKey, SerialNumberRecord>;
116 static StaticAutoPtr<BloatHash> gBloatView;
117 static StaticAutoPtr<CharPtrSet> gTypesToLog;
118 static StaticAutoPtr<IntPtrSet> gObjectsToLog;
119 static StaticAutoPtr<SerialHash> gSerialNumbers;
121 static intptr_t gNextSerialNumber;
122 static bool gDumpedStatistics = false;
123 static bool gLogJSStacks = false;
125 // By default, debug builds only do bloat logging. Bloat logging
126 // only tries to record when an object is created or destroyed, so we
127 // optimize the common case in NS_LogAddRef and NS_LogRelease where
128 // only bloat logging is enabled and no logging needs to be done.
129 enum LoggingType { NoLogging, OnlyBloatLogging, FullLogging };
131 static LoggingType gLogging;
133 static bool gLogLeaksOnly;
135 #define BAD_TLS_INDEX ((unsigned)-1)
137 // if gActivityTLS == BAD_TLS_INDEX, then we're
138 // unitialized... otherwise this points to a NSPR TLS thread index
139 // indicating whether addref activity is legal. If the PTR_TO_INT32 is 0 then
140 // activity is ok, otherwise not!
141 static unsigned gActivityTLS = BAD_TLS_INDEX;
143 static bool gInitialized;
144 static nsrefcnt gInitCount;
146 static FILE* gBloatLog = nullptr;
147 static FILE* gRefcntsLog = nullptr;
148 static FILE* gAllocLog = nullptr;
149 static FILE* gCOMPtrLog = nullptr;
151 static void WalkTheStackSavingLocations(std::vector<void*>& aLocations,
152 const void* aFirstFramePC);
154 struct SerialNumberRecord {
155 SerialNumberRecord()
156 : serialNumber(++gNextSerialNumber), refCount(0), COMPtrCount(0) {}
158 intptr_t serialNumber;
159 int32_t refCount;
160 int32_t COMPtrCount;
161 // We use std:: classes here rather than the XPCOM equivalents because the
162 // XPCOM equivalents do leak-checking, and if you try to leak-check while
163 // leak-checking, you're gonna have a bad time.
164 std::vector<void*> allocationStack;
165 mozilla::UniquePtr<char[]> jsStack;
167 void SaveJSStack() {
168 // If this thread isn't running JS, there's nothing to do.
169 if (!CycleCollectedJSContext::Get()) {
170 return;
173 if (!nsContentUtils::IsInitialized()) {
174 return;
177 JSContext* cx = nsContentUtils::GetCurrentJSContext();
178 if (!cx) {
179 return;
182 JS::UniqueChars chars = xpc_PrintJSStack(cx,
183 /*showArgs=*/false,
184 /*showLocals=*/false,
185 /*showThisProps=*/false);
186 size_t len = strlen(chars.get());
187 jsStack = mozilla::MakeUnique<char[]>(len + 1);
188 memcpy(jsStack.get(), chars.get(), len + 1);
192 struct nsTraceRefcntStats {
193 uint64_t mCreates;
194 uint64_t mDestroys;
196 bool HaveLeaks() const { return mCreates != mDestroys; }
198 void Clear() {
199 mCreates = 0;
200 mDestroys = 0;
203 int64_t NumLeaked() const { return (int64_t)(mCreates - mDestroys); }
206 #ifdef DEBUG
207 static void AssertActivityIsLegal(const char* aType, const char* aAction) {
208 if (gActivityTLS == BAD_TLS_INDEX || PR_GetThreadPrivate(gActivityTLS)) {
209 char buf[1024];
210 SprintfLiteral(buf, "XPCOM object %s %s from static ctor/dtor", aType,
211 aAction);
213 if (PR_GetEnv("MOZ_FATAL_STATIC_XPCOM_CTORS_DTORS")) {
214 MOZ_CRASH_UNSAFE_PRINTF("%s", buf);
215 } else {
216 NS_WARNING(buf);
220 # define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \
221 do { \
222 AssertActivityIsLegal(type_, action_); \
223 } while (0)
224 #else
225 # define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \
226 do { \
227 } while (0)
228 #endif // DEBUG
230 ////////////////////////////////////////////////////////////////////////////////
232 mozilla::StaticAutoPtr<CodeAddressService<>> gCodeAddressService;
234 ////////////////////////////////////////////////////////////////////////////////
236 class BloatEntry {
237 public:
238 BloatEntry(const char* aClassName, uint32_t aClassSize)
239 : mClassSize(aClassSize), mStats() {
240 MOZ_ASSERT(strlen(aClassName) > 0, "BloatEntry name must be non-empty");
241 mClassName = aClassName;
242 mStats.Clear();
243 mTotalLeaked = 0;
246 ~BloatEntry() = default;
248 uint32_t GetClassSize() { return (uint32_t)mClassSize; }
249 const char* GetClassName() { return mClassName; }
251 void Ctor() { mStats.mCreates++; }
253 void Dtor() { mStats.mDestroys++; }
255 void Total(BloatEntry* aTotal) {
256 aTotal->mStats.mCreates += mStats.mCreates;
257 aTotal->mStats.mDestroys += mStats.mDestroys;
258 aTotal->mClassSize +=
259 mClassSize * mStats.mCreates; // adjust for average in DumpTotal
260 aTotal->mTotalLeaked += mClassSize * mStats.NumLeaked();
263 void DumpTotal(FILE* aOut) {
264 mClassSize /= mStats.mCreates;
265 Dump(-1, aOut);
268 bool PrintDumpHeader(FILE* aOut, const char* aMsg) {
269 fprintf(aOut, "\n== BloatView: %s, %s process %d\n", aMsg,
270 XRE_GetProcessTypeString(), getpid());
271 if (gLogLeaksOnly && !mStats.HaveLeaks()) {
272 return false;
275 // clang-format off
276 fprintf(aOut,
277 "\n" \
278 " |<----------------Class--------------->|<-----Bytes------>|<----Objects---->|\n" \
279 " | | Per-Inst Leaked| Total Rem|\n");
280 // clang-format on
282 this->DumpTotal(aOut);
284 return true;
287 void Dump(int aIndex, FILE* aOut) {
288 if (gLogLeaksOnly && !mStats.HaveLeaks()) {
289 return;
292 if (mStats.HaveLeaks() || mStats.mCreates != 0) {
293 fprintf(aOut,
294 "%4d |%-38.38s| %8d %8" PRId64 "|%8" PRIu64 " %8" PRId64 "|\n",
295 aIndex + 1, mClassName, GetClassSize(),
296 nsCRT::strcmp(mClassName, "TOTAL")
297 ? (mStats.NumLeaked() * GetClassSize())
298 : mTotalLeaked,
299 mStats.mCreates, mStats.NumLeaked());
303 protected:
304 const char* mClassName;
305 // mClassSize is stored as a double because of the way we compute the avg
306 // class size for total bloat.
307 double mClassSize;
308 // mTotalLeaked is only used for the TOTAL entry.
309 int64_t mTotalLeaked;
310 nsTraceRefcntStats mStats;
313 static void EnsureBloatView() {
314 if (!gBloatView) {
315 gBloatView = new BloatHash(256);
319 static BloatEntry* GetBloatEntry(const char* aTypeName,
320 uint32_t aInstanceSize) {
321 EnsureBloatView();
322 BloatEntry* entry = gBloatView->Get(aTypeName);
323 if (!entry && aInstanceSize > 0) {
324 entry = gBloatView
325 ->InsertOrUpdate(aTypeName, mozilla::MakeUnique<BloatEntry>(
326 aTypeName, aInstanceSize))
327 .get();
328 } else {
329 MOZ_ASSERT(
330 aInstanceSize == 0 || entry->GetClassSize() == aInstanceSize,
331 "Mismatched sizes were recorded in the memory leak logging table. "
332 "The usual cause of this is having a templated class that uses "
333 "MOZ_COUNT_{C,D}TOR in the constructor or destructor, respectively. "
334 "As a workaround, the MOZ_COUNT_{C,D}TOR calls can be moved to a "
335 "non-templated base class. Another possible cause is a runnable with "
336 "an mName that matches another refcounted class, or two refcounted "
337 "classes with the same class name in different C++ namespaces.");
339 return entry;
342 static void DumpSerialNumbers(const SerialHash::ConstIterator& aHashEntry,
343 FILE* aFd, bool aDumpAsStringBuffer) {
344 SerialNumberRecord* record = aHashEntry.UserData();
345 auto* outputFile = aFd;
346 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
347 fprintf(outputFile, "%" PRIdPTR " @%p (%d references; %d from COMPtrs)\n",
348 record->serialNumber, aHashEntry.Key(), record->refCount,
349 record->COMPtrCount);
350 #else
351 fprintf(outputFile, "%" PRIdPTR " @%p (%d references)\n",
352 record->serialNumber, aHashEntry.Key(), record->refCount);
353 #endif
355 if (aDumpAsStringBuffer) {
356 // This output will be wrong if the nsStringBuffer was used to
357 // store a char16_t string.
358 auto* buffer = static_cast<const nsStringBuffer*>(aHashEntry.Key());
359 nsDependentCString bufferString(static_cast<char*>(buffer->Data()));
360 fprintf(outputFile,
361 "Contents of leaked nsStringBuffer with storage size %d as a "
362 "char*: %s\n",
363 buffer->StorageSize(), bufferString.get());
366 if (!record->allocationStack.empty()) {
367 static const size_t bufLen = 1024;
368 char buf[bufLen];
369 fprintf(outputFile, "allocation stack:\n");
370 for (size_t i = 0, length = record->allocationStack.size(); i < length;
371 ++i) {
372 gCodeAddressService->GetLocation(i, record->allocationStack[i], buf,
373 bufLen);
374 fprintf(outputFile, "%s\n", buf);
378 if (gLogJSStacks) {
379 if (record->jsStack) {
380 fprintf(outputFile, "JS allocation stack:\n%s\n", record->jsStack.get());
381 } else {
382 fprintf(outputFile, "There is no JS context on the stack.\n");
387 template <>
388 class nsDefaultComparator<BloatEntry*, BloatEntry*> {
389 public:
390 bool Equals(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const {
391 return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) == 0;
393 bool LessThan(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const {
394 return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) < 0;
398 nsresult nsTraceRefcnt::DumpStatistics() {
399 if (!gBloatLog || !gBloatView) {
400 return NS_ERROR_FAILURE;
403 AutoTraceLogLock lock(gTraceLog);
405 MOZ_ASSERT(!gDumpedStatistics,
406 "Calling DumpStatistics more than once may result in "
407 "bogus positive or negative leaks being reported");
408 gDumpedStatistics = true;
410 // Don't try to log while we hold the lock, we'd deadlock.
411 AutoRestore<LoggingType> saveLogging(gLogging);
412 gLogging = NoLogging;
414 BloatEntry total("TOTAL", 0);
415 for (const auto& data : gBloatView->Values()) {
416 if (nsCRT::strcmp(data->GetClassName(), "TOTAL") != 0) {
417 data->Total(&total);
421 const char* msg;
422 if (gLogLeaksOnly) {
423 msg = "ALL (cumulative) LEAK STATISTICS";
424 } else {
425 msg = "ALL (cumulative) LEAK AND BLOAT STATISTICS";
427 const bool leaked = total.PrintDumpHeader(gBloatLog, msg);
429 nsTArray<BloatEntry*> entries(gBloatView->Count());
430 for (const auto& data : gBloatView->Values()) {
431 entries.AppendElement(data.get());
434 const uint32_t count = entries.Length();
436 if (!gLogLeaksOnly || leaked) {
437 // Sort the entries alphabetically by classname.
438 entries.Sort();
440 for (uint32_t i = 0; i < count; ++i) {
441 BloatEntry* entry = entries[i];
442 entry->Dump(i, gBloatLog);
445 fprintf(gBloatLog, "\n");
448 fprintf(gBloatLog, "nsTraceRefcnt::DumpStatistics: %d entries\n", count);
450 if (gSerialNumbers) {
451 bool onlyLoggingStringBuffers = gTypesToLog && gTypesToLog->Count() == 1 &&
452 gTypesToLog->Contains("nsStringBuffer");
454 fprintf(gBloatLog, "\nSerial Numbers of Leaked Objects:\n");
455 for (auto iter = gSerialNumbers->ConstIter(); !iter.Done(); iter.Next()) {
456 DumpSerialNumbers(iter, gBloatLog, onlyLoggingStringBuffers);
460 return NS_OK;
463 void nsTraceRefcnt::ResetStatistics() {
464 AutoTraceLogLock lock(gTraceLog);
465 gBloatView = nullptr;
468 static intptr_t GetSerialNumber(void* aPtr, bool aCreate, void* aFirstFramePC) {
469 if (!aCreate) {
470 auto record = gSerialNumbers->Get(aPtr);
471 return record ? record->serialNumber : 0;
474 gSerialNumbers->WithEntryHandle(aPtr, [aFirstFramePC](auto&& entry) {
475 if (entry) {
476 MOZ_CRASH(
477 "If an object already has a serial number, we should be destroying "
478 "it.");
481 auto& record = entry.Insert(mozilla::MakeUnique<SerialNumberRecord>());
482 WalkTheStackSavingLocations(record->allocationStack, aFirstFramePC);
483 if (gLogJSStacks) {
484 record->SaveJSStack();
487 return gNextSerialNumber;
490 static void RecycleSerialNumberPtr(void* aPtr) { gSerialNumbers->Remove(aPtr); }
492 static bool LogThisObj(intptr_t aSerialNumber) {
493 return gObjectsToLog->Contains(aSerialNumber);
496 using EnvCharType = mozilla::filesystem::Path::value_type;
498 static bool InitLog(const EnvCharType* aEnvVar, const char* aMsg,
499 FILE** aResult, const char* aProcType) {
500 #ifdef XP_WIN
501 // This is gross, I know.
502 const wchar_t* envvar = reinterpret_cast<const wchar_t*>(aEnvVar);
503 const char16_t* value = reinterpret_cast<const char16_t*>(::_wgetenv(envvar));
504 # define ENVVAR_PRINTF "%S"
505 #else
506 const char* envvar = aEnvVar;
507 const char* value = ::getenv(aEnvVar);
508 # define ENVVAR_PRINTF "%s"
509 #endif
511 if (value) {
512 nsTDependentString<EnvCharType> fname(value);
513 if (fname.EqualsLiteral("1")) {
514 *aResult = stdout;
515 fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stdout\n",
516 envvar, aMsg);
517 return true;
519 if (fname.EqualsLiteral("2")) {
520 *aResult = stderr;
521 fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stderr\n",
522 envvar, aMsg);
523 return true;
525 if (!XRE_IsParentProcess()) {
526 nsTString<EnvCharType> extension;
527 extension.AssignLiteral(".log");
528 bool hasLogExtension = StringEndsWith(fname, extension);
529 if (hasLogExtension) {
530 fname.Cut(fname.Length() - 4, 4);
532 fname.Append('_');
533 fname.AppendASCII(aProcType);
534 fname.AppendLiteral("_pid");
535 fname.AppendInt((uint32_t)getpid());
536 if (hasLogExtension) {
537 fname.AppendLiteral(".log");
540 #ifdef XP_WIN
541 FILE* stream = ::_wfopen(fname.get(), L"wN");
542 const wchar_t* fp = (const wchar_t*)fname.get();
543 #else
544 FILE* stream = ::fopen(fname.get(), "w");
545 const char* fp = fname.get();
546 #endif
547 if (stream) {
548 MozillaRegisterDebugFD(fileno(stream));
549 #ifdef MOZ_ENABLE_FORKSERVER
550 base::RegisterForkServerNoCloseFD(fileno(stream));
551 #endif
552 *aResult = stream;
553 fprintf(stderr,
554 "### " ENVVAR_PRINTF " defined -- logging %s to " ENVVAR_PRINTF
555 "\n",
556 envvar, aMsg, fp);
558 return true;
561 fprintf(stderr,
562 "### " ENVVAR_PRINTF
563 " defined -- unable to log %s to " ENVVAR_PRINTF "\n",
564 envvar, aMsg, fp);
565 MOZ_ASSERT(false, "Tried and failed to create an XPCOM log");
567 #undef ENVVAR_PRINTF
569 return false;
572 static void maybeUnregisterAndCloseFile(FILE*& aFile) {
573 if (!aFile) {
574 return;
577 MozillaUnRegisterDebugFILE(aFile);
578 fclose(aFile);
579 aFile = nullptr;
582 static void DoInitTraceLog(const char* aProcType) {
583 #ifdef XP_WIN
584 # define ENVVAR(x) u"" x
585 #else
586 # define ENVVAR(x) x
587 #endif
589 bool defined = InitLog(ENVVAR("XPCOM_MEM_BLOAT_LOG"), "bloat/leaks",
590 &gBloatLog, aProcType);
591 if (!defined) {
592 gLogLeaksOnly =
593 InitLog(ENVVAR("XPCOM_MEM_LEAK_LOG"), "leaks", &gBloatLog, aProcType);
595 if (defined || gLogLeaksOnly) {
596 // Use the same bloat view, if there is one, to keep it consistent
597 // between the fork server and content processes.
598 EnsureBloatView();
599 } else if (gBloatView) {
600 nsTraceRefcnt::ResetStatistics();
603 InitLog(ENVVAR("XPCOM_MEM_REFCNT_LOG"), "refcounts", &gRefcntsLog, aProcType);
605 InitLog(ENVVAR("XPCOM_MEM_ALLOC_LOG"), "new/delete", &gAllocLog, aProcType);
607 const char* classes = getenv("XPCOM_MEM_LOG_CLASSES");
609 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
610 if (classes) {
611 InitLog(ENVVAR("XPCOM_MEM_COMPTR_LOG"), "nsCOMPtr", &gCOMPtrLog, aProcType);
612 } else {
613 if (getenv("XPCOM_MEM_COMPTR_LOG")) {
614 fprintf(stdout,
615 "### XPCOM_MEM_COMPTR_LOG defined -- "
616 "but XPCOM_MEM_LOG_CLASSES is not defined\n");
619 #else
620 const char* comptr_log = getenv("XPCOM_MEM_COMPTR_LOG");
621 if (comptr_log) {
622 fprintf(stdout,
623 "### XPCOM_MEM_COMPTR_LOG defined -- "
624 "but it will not work without dynamic_cast\n");
626 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
628 #undef ENVVAR
630 if (classes) {
631 // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted
632 // as a list of class names to track
634 // Use the same |gTypesToLog| and |gSerialNumbers| to keep them
635 // consistent through the fork server and content processes.
636 // Without this, counters will be incorrect.
637 if (!gTypesToLog) {
638 gTypesToLog = new CharPtrSet(256);
641 fprintf(stdout,
642 "### XPCOM_MEM_LOG_CLASSES defined -- "
643 "only logging these classes: ");
644 const char* cp = classes;
645 for (;;) {
646 char* cm = (char*)strchr(cp, ',');
647 if (cm) {
648 *cm = '\0';
650 if (!gTypesToLog->Contains(cp)) {
651 gTypesToLog->PutEntry(cp);
653 fprintf(stdout, "%s ", cp);
654 if (!cm) {
655 break;
657 *cm = ',';
658 cp = cm + 1;
660 fprintf(stdout, "\n");
662 if (!gSerialNumbers) {
663 gSerialNumbers = new SerialHash(256);
665 } else {
666 gTypesToLog = nullptr;
667 gSerialNumbers = nullptr;
670 const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS");
671 if (objects) {
672 gObjectsToLog = new IntPtrSet(256);
674 if (!(gRefcntsLog || gAllocLog || gCOMPtrLog)) {
675 fprintf(stdout,
676 "### XPCOM_MEM_LOG_OBJECTS defined -- "
677 "but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n");
678 } else {
679 fprintf(stdout,
680 "### XPCOM_MEM_LOG_OBJECTS defined -- "
681 "only logging these objects: ");
682 const char* cp = objects;
683 for (;;) {
684 char* cm = (char*)strchr(cp, ',');
685 if (cm) {
686 *cm = '\0';
688 intptr_t top = 0;
689 intptr_t bottom = 0;
690 while (*cp) {
691 if (*cp == '-') {
692 bottom = top;
693 top = 0;
694 ++cp;
696 top *= 10;
697 top += *cp - '0';
698 ++cp;
700 if (!bottom) {
701 bottom = top;
703 for (intptr_t serialno = bottom; serialno <= top; serialno++) {
704 gObjectsToLog->PutEntry(serialno);
705 fprintf(stdout, "%" PRIdPTR " ", serialno);
707 if (!cm) {
708 break;
710 *cm = ',';
711 cp = cm + 1;
713 fprintf(stdout, "\n");
717 if (getenv("XPCOM_MEM_LOG_JS_STACK")) {
718 fprintf(stdout, "### XPCOM_MEM_LOG_JS_STACK defined\n");
719 gLogJSStacks = true;
722 if (gBloatLog) {
723 gLogging = OnlyBloatLogging;
726 if (gRefcntsLog || gAllocLog || gCOMPtrLog) {
727 gLogging = FullLogging;
731 static void InitTraceLog() {
732 if (gInitialized) {
733 return;
735 gInitialized = true;
737 DoInitTraceLog(XRE_GetProcessTypeString());
740 extern "C" {
742 static void EnsureWrite(FILE* aStream, const char* aBuf, size_t aLen) {
743 #ifdef XP_WIN
744 int fd = _fileno(aStream);
745 #else
746 int fd = fileno(aStream);
747 #endif
748 while (aLen > 0) {
749 #ifdef XP_WIN
750 auto written = _write(fd, aBuf, aLen);
751 #else
752 auto written = write(fd, aBuf, aLen);
753 #endif
754 if (written <= 0 || size_t(written) > aLen) {
755 break;
757 aBuf += written;
758 aLen -= written;
762 static void PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP,
763 void* aClosure) {
764 auto stream = static_cast<FILE*>(aClosure);
765 static const int buflen = 1024;
766 char buf[buflen + 5] = " "; // 5 for leading " " and trailing '\n'
767 int len =
768 gCodeAddressService->GetLocation(aFrameNumber, aPC, buf + 4, buflen);
769 len = std::min(len, buflen + 1 - 2) + 4;
770 buf[len++] = '\n';
771 buf[len] = '\0';
772 fflush(stream);
773 EnsureWrite(stream, buf, len);
776 static void RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC,
777 void* /*aSP*/, void* aClosure) {
778 auto locations = static_cast<std::vector<void*>*>(aClosure);
779 locations->push_back(aPC);
784 * This is a variant of |MozWalkTheStack| that uses |CodeAddressService| to
785 * cache the results of |NS_DescribeCodeAddress|. If |WalkTheStackCached| is
786 * being called frequently, it will be a few orders of magnitude faster than
787 * |MozWalkTheStack|. However, the cache uses a lot of memory, which can cause
788 * OOM crashes. Therefore, this should only be used for things like refcount
789 * logging which walk the stack extremely frequently.
791 static void WalkTheStackCached(FILE* aStream, const void* aFirstFramePC) {
792 if (!gCodeAddressService) {
793 gCodeAddressService = new CodeAddressService<>();
795 MozStackWalk(PrintStackFrameCached, aFirstFramePC, /* maxFrames */ 0,
796 aStream);
799 static void WalkTheStackSavingLocations(std::vector<void*>& aLocations,
800 const void* aFirstFramePC) {
801 if (!gCodeAddressService) {
802 gCodeAddressService = new CodeAddressService<>();
804 MozStackWalk(RecordStackFrame, aFirstFramePC, /* maxFrames */ 0, &aLocations);
807 //----------------------------------------------------------------------
809 EXPORT_XPCOM_API(void)
810 NS_LogInit() {
811 NS_SetMainThread();
813 // FIXME: This is called multiple times, we should probably not allow that.
814 if (++gInitCount) {
815 nsTraceRefcnt::SetActivityIsLegal(true);
819 EXPORT_XPCOM_API(void)
820 NS_LogTerm() { mozilla::LogTerm(); }
822 #ifdef MOZ_DMD
823 // If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file.
824 // The value of this environment variable is used as the prefix
825 // of the file name, so you probably want something like "/tmp/".
826 // By default, this is run in all processes, but you can record a
827 // log only for a specific process type by setting MOZ_DMD_LOG_PROCESS
828 // to the process type you want to log, such as "default" or "tab".
829 // This method can't use the higher level XPCOM file utilities
830 // because it is run very late in shutdown to avoid recording
831 // information about refcount logging entries.
832 static void LogDMDFile() {
833 const char* dmdFilePrefix = PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG");
834 if (!dmdFilePrefix) {
835 return;
838 const char* logProcessEnv = PR_GetEnv("MOZ_DMD_LOG_PROCESS");
839 if (logProcessEnv && !!strcmp(logProcessEnv, XRE_GetProcessTypeString())) {
840 return;
843 nsPrintfCString fileName("%sdmd-%" PRIPID ".log.gz", dmdFilePrefix,
844 base::GetCurrentProcId());
845 FILE* logFile = fopen(fileName.get(), "w");
846 if (NS_WARN_IF(!logFile)) {
847 return;
850 nsMemoryInfoDumper::DumpDMDToFile(logFile);
852 #endif // MOZ_DMD
854 namespace mozilla {
855 void LogTerm() {
856 NS_ASSERTION(gInitCount > 0, "NS_LogTerm without matching NS_LogInit");
858 if (--gInitCount == 0) {
859 #ifdef DEBUG
860 /* FIXME bug 491977: This is only going to operate on the
861 * BlockingResourceBase which is compiled into
862 * libxul/libxpcom_core.so. Anyone using external linkage will
863 * have their own copy of BlockingResourceBase statics which will
864 * not be freed by this method.
866 * It sounds like what we really want is to be able to register a
867 * callback function to call at XPCOM shutdown. Note that with
868 * this solution, however, we need to guarantee that
869 * BlockingResourceBase::Shutdown() runs after all other shutdown
870 * functions.
872 BlockingResourceBase::Shutdown();
873 #endif
875 if (gInitialized) {
876 nsTraceRefcnt::DumpStatistics();
877 nsTraceRefcnt::ResetStatistics();
879 nsTraceRefcnt::Shutdown();
880 nsTraceRefcnt::SetActivityIsLegal(false);
881 gActivityTLS = BAD_TLS_INDEX;
883 #ifdef MOZ_DMD
884 LogDMDFile();
885 #endif
889 } // namespace mozilla
891 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
892 NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt, const char* aClass,
893 uint32_t aClassSize) {
894 ASSERT_ACTIVITY_IS_LEGAL(aClass, "addrefed");
895 if (!gInitialized) {
896 InitTraceLog();
898 if (gLogging == NoLogging) {
899 return;
901 if (aRefcnt == 1 || gLogging == FullLogging) {
902 AutoTraceLogLock lock(gTraceLog);
904 if (aRefcnt == 1 && gBloatLog) {
905 BloatEntry* entry = GetBloatEntry(aClass, aClassSize);
906 if (entry) {
907 entry->Ctor();
911 // Here's the case where MOZ_COUNT_CTOR was not used,
912 // yet we still want to see creation information:
914 bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass));
915 intptr_t serialno = 0;
916 if (gSerialNumbers && loggingThisType) {
917 serialno = GetSerialNumber(aPtr, aRefcnt == 1, CallerPC());
918 MOZ_ASSERT(serialno != 0,
919 "Serial number requested for unrecognized pointer! "
920 "Are you memmoving a refcounted object?");
921 auto record = gSerialNumbers->Get(aPtr);
922 if (record) {
923 ++record->refCount;
927 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
928 if (aRefcnt == 1 && gAllocLog && loggingThisType && loggingThisObject) {
929 fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Create [thread %p]\n", aClass,
930 aPtr, serialno, PR_GetCurrentThread());
931 WalkTheStackCached(gAllocLog, CallerPC());
934 if (gRefcntsLog && loggingThisType && loggingThisObject) {
935 // Can't use MOZ_LOG(), b/c it truncates the line
936 fprintf(gRefcntsLog,
937 "\n<%s> %p %" PRIuPTR " AddRef %" PRIuPTR " [thread %p]\n",
938 aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread());
939 WalkTheStackCached(gRefcntsLog, CallerPC());
940 fflush(gRefcntsLog);
945 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
946 NS_LogRelease(void* aPtr, nsrefcnt aRefcnt, const char* aClass) {
947 ASSERT_ACTIVITY_IS_LEGAL(aClass, "released");
948 if (!gInitialized) {
949 InitTraceLog();
951 if (gLogging == NoLogging) {
952 return;
954 if (aRefcnt == 0 || gLogging == FullLogging) {
955 AutoTraceLogLock lock(gTraceLog);
957 if (aRefcnt == 0 && gBloatLog) {
958 BloatEntry* entry = GetBloatEntry(aClass, 0);
959 if (entry) {
960 entry->Dtor();
964 bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass));
965 intptr_t serialno = 0;
966 if (gSerialNumbers && loggingThisType) {
967 serialno = GetSerialNumber(aPtr, false, CallerPC());
968 MOZ_ASSERT(serialno != 0,
969 "Serial number requested for unrecognized pointer! "
970 "Are you memmoving a refcounted object?");
971 auto record = gSerialNumbers->Get(aPtr);
972 if (record) {
973 --record->refCount;
977 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
978 if (gRefcntsLog && loggingThisType && loggingThisObject) {
979 // Can't use MOZ_LOG(), b/c it truncates the line
980 fprintf(gRefcntsLog,
981 "\n<%s> %p %" PRIuPTR " Release %" PRIuPTR " [thread %p]\n",
982 aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread());
983 WalkTheStackCached(gRefcntsLog, CallerPC());
984 fflush(gRefcntsLog);
987 // Here's the case where MOZ_COUNT_DTOR was not used,
988 // yet we still want to see deletion information:
990 if (aRefcnt == 0 && gAllocLog && loggingThisType && loggingThisObject) {
991 fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Destroy [thread %p]\n", aClass,
992 aPtr, serialno, PR_GetCurrentThread());
993 WalkTheStackCached(gAllocLog, CallerPC());
996 if (aRefcnt == 0 && gSerialNumbers && loggingThisType) {
997 RecycleSerialNumberPtr(aPtr);
1002 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
1003 NS_LogCtor(void* aPtr, const char* aType, uint32_t aInstanceSize) {
1004 ASSERT_ACTIVITY_IS_LEGAL(aType, "constructed");
1005 if (!gInitialized) {
1006 InitTraceLog();
1009 if (gLogging == NoLogging) {
1010 return;
1013 AutoTraceLogLock lock(gTraceLog);
1015 if (gBloatLog) {
1016 BloatEntry* entry = GetBloatEntry(aType, aInstanceSize);
1017 if (entry) {
1018 entry->Ctor();
1022 bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType));
1023 intptr_t serialno = 0;
1024 if (gSerialNumbers && loggingThisType) {
1025 serialno = GetSerialNumber(aPtr, true, CallerPC());
1026 MOZ_ASSERT(serialno != 0,
1027 "GetSerialNumber should never return 0 when passed true");
1030 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
1031 if (gAllocLog && loggingThisType && loggingThisObject) {
1032 fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Ctor (%d)\n", aType, aPtr,
1033 serialno, aInstanceSize);
1034 WalkTheStackCached(gAllocLog, CallerPC());
1038 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
1039 NS_LogDtor(void* aPtr, const char* aType, uint32_t aInstanceSize) {
1040 ASSERT_ACTIVITY_IS_LEGAL(aType, "destroyed");
1041 if (!gInitialized) {
1042 InitTraceLog();
1045 if (gLogging == NoLogging) {
1046 return;
1049 AutoTraceLogLock lock(gTraceLog);
1051 if (gBloatLog) {
1052 BloatEntry* entry = GetBloatEntry(aType, aInstanceSize);
1053 if (entry) {
1054 entry->Dtor();
1058 bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType));
1059 intptr_t serialno = 0;
1060 if (gSerialNumbers && loggingThisType) {
1061 serialno = GetSerialNumber(aPtr, false, CallerPC());
1062 MOZ_ASSERT(serialno != 0,
1063 "Serial number requested for unrecognized pointer! "
1064 "Are you memmoving a MOZ_COUNT_CTOR-tracked object?");
1065 RecycleSerialNumberPtr(aPtr);
1068 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
1070 // (If we're on a losing architecture, don't do this because we'll be
1071 // using LogDeleteXPCOM instead to get file and line numbers.)
1072 if (gAllocLog && loggingThisType && loggingThisObject) {
1073 fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Dtor (%d)\n", aType, aPtr,
1074 serialno, aInstanceSize);
1075 WalkTheStackCached(gAllocLog, CallerPC());
1079 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
1080 NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject) {
1081 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1082 // Get the most-derived object.
1083 void* object = dynamic_cast<void*>(aObject);
1085 // This is a very indirect way of finding out what the class is
1086 // of the object being logged. If we're logging a specific type,
1087 // then
1088 if (!gTypesToLog || !gSerialNumbers) {
1089 return;
1091 if (!gInitialized) {
1092 InitTraceLog();
1094 if (gLogging == FullLogging) {
1095 AutoTraceLogLock lock(gTraceLog);
1097 intptr_t serialno = GetSerialNumber(object, false, CallerPC());
1098 if (serialno == 0) {
1099 return;
1102 auto record = gSerialNumbers->Get(object);
1103 int32_t count = record ? ++record->COMPtrCount : -1;
1104 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
1106 if (gCOMPtrLog && loggingThisObject) {
1107 fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrAddRef %d %p\n",
1108 object, serialno, count, aCOMPtr);
1109 WalkTheStackCached(gCOMPtrLog, CallerPC());
1112 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1115 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
1116 NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject) {
1117 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1118 // Get the most-derived object.
1119 void* object = dynamic_cast<void*>(aObject);
1121 // This is a very indirect way of finding out what the class is
1122 // of the object being logged. If we're logging a specific type,
1123 // then
1124 if (!gTypesToLog || !gSerialNumbers) {
1125 return;
1127 if (!gInitialized) {
1128 InitTraceLog();
1130 if (gLogging == FullLogging) {
1131 AutoTraceLogLock lock(gTraceLog);
1133 intptr_t serialno = GetSerialNumber(object, false, CallerPC());
1134 if (serialno == 0) {
1135 return;
1138 auto record = gSerialNumbers->Get(object);
1139 int32_t count = record ? --record->COMPtrCount : -1;
1140 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
1142 if (gCOMPtrLog && loggingThisObject) {
1143 fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrRelease %d %p\n",
1144 object, serialno, count, aCOMPtr);
1145 WalkTheStackCached(gCOMPtrLog, CallerPC());
1148 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1151 static void ClearLogs(bool aKeepCounters) {
1152 gCodeAddressService = nullptr;
1153 // These counters from the fork server process will be preserved
1154 // for the content processes to keep them consistent.
1155 if (!aKeepCounters) {
1156 gBloatView = nullptr;
1157 gTypesToLog = nullptr;
1158 gSerialNumbers = nullptr;
1160 gObjectsToLog = nullptr;
1161 gLogJSStacks = false;
1162 gLogLeaksOnly = false;
1163 maybeUnregisterAndCloseFile(gBloatLog);
1164 maybeUnregisterAndCloseFile(gRefcntsLog);
1165 maybeUnregisterAndCloseFile(gAllocLog);
1166 maybeUnregisterAndCloseFile(gCOMPtrLog);
1169 void nsTraceRefcnt::Shutdown() { ClearLogs(false); }
1171 void nsTraceRefcnt::SetActivityIsLegal(bool aLegal) {
1172 if (gActivityTLS == BAD_TLS_INDEX) {
1173 PR_NewThreadPrivateIndex(&gActivityTLS, nullptr);
1176 PR_SetThreadPrivate(gActivityTLS, reinterpret_cast<void*>(!aLegal));
1179 void nsTraceRefcnt::StartLoggingClass(const char* aClass) {
1180 gLogging = FullLogging;
1182 if (!gTypesToLog) {
1183 gTypesToLog = new CharPtrSet(256);
1186 fprintf(stdout, "### StartLoggingClass %s\n", aClass);
1187 if (!gTypesToLog->Contains(aClass)) {
1188 gTypesToLog->PutEntry(aClass);
1191 // We are deliberately not initializing gSerialNumbers here, because
1192 // it would cause assertions. gObjectsToLog can't be used because it
1193 // relies on serial numbers.
1195 #ifdef XP_WIN
1196 # define ENVVAR(x) u"" x
1197 #else
1198 # define ENVVAR(x) x
1199 #endif
1201 if (!gRefcntsLog) {
1202 InitLog(ENVVAR("XPCOM_MEM_LATE_REFCNT_LOG"), "refcounts", &gRefcntsLog,
1203 XRE_GetProcessTypeString());
1206 #undef ENVVAR
1209 #ifdef MOZ_ENABLE_FORKSERVER
1210 void nsTraceRefcnt::ResetLogFiles(const char* aProcType) {
1211 AutoRestore<LoggingType> saveLogging(gLogging);
1212 gLogging = NoLogging;
1214 ClearLogs(true);
1216 // Create log files with the correct process type in the name.
1217 DoInitTraceLog(aProcType);
1219 #endif