Bug 1919083 - [ci] Enable os-integration variant for more suites, r=jmaher
[gecko.git] / xpcom / base / nsTraceRefcnt.cpp
blobd359c9e98295a2a33a77bad6740b986cf07ee9cb
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_DLFCN_H
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 MOZ_RUNINIT 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 StringBuffer was used to
357 // store a char16_t string.
358 auto* buffer = static_cast<const mozilla::StringBuffer*>(aHashEntry.Key());
359 nsDependentCString bufferString(static_cast<char*>(buffer->Data()));
360 fprintf(
361 outputFile,
362 "Contents of leaked mozilla::StringBuffer with storage size %d as a "
363 "char*: %s\n",
364 buffer->StorageSize(), bufferString.get());
367 if (!record->allocationStack.empty()) {
368 static const size_t bufLen = 1024;
369 char buf[bufLen];
370 fprintf(outputFile, "allocation stack:\n");
371 for (size_t i = 0, length = record->allocationStack.size(); i < length;
372 ++i) {
373 gCodeAddressService->GetLocation(i, record->allocationStack[i], buf,
374 bufLen);
375 fprintf(outputFile, "%s\n", buf);
379 if (gLogJSStacks) {
380 if (record->jsStack) {
381 fprintf(outputFile, "JS allocation stack:\n%s\n", record->jsStack.get());
382 } else {
383 fprintf(outputFile, "There is no JS context on the stack.\n");
388 template <>
389 class nsDefaultComparator<BloatEntry*, BloatEntry*> {
390 public:
391 bool Equals(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const {
392 return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) == 0;
394 bool LessThan(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const {
395 return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) < 0;
399 nsresult nsTraceRefcnt::DumpStatistics() {
400 if (!gBloatLog || !gBloatView) {
401 return NS_ERROR_FAILURE;
404 AutoTraceLogLock lock(gTraceLog);
406 MOZ_ASSERT(!gDumpedStatistics,
407 "Calling DumpStatistics more than once may result in "
408 "bogus positive or negative leaks being reported");
409 gDumpedStatistics = true;
411 // Don't try to log while we hold the lock, we'd deadlock.
412 AutoRestore<LoggingType> saveLogging(gLogging);
413 gLogging = NoLogging;
415 BloatEntry total("TOTAL", 0);
416 for (const auto& data : gBloatView->Values()) {
417 if (nsCRT::strcmp(data->GetClassName(), "TOTAL") != 0) {
418 data->Total(&total);
422 const char* msg;
423 if (gLogLeaksOnly) {
424 msg = "ALL (cumulative) LEAK STATISTICS";
425 } else {
426 msg = "ALL (cumulative) LEAK AND BLOAT STATISTICS";
428 const bool leaked = total.PrintDumpHeader(gBloatLog, msg);
430 nsTArray<BloatEntry*> entries(gBloatView->Count());
431 for (const auto& data : gBloatView->Values()) {
432 entries.AppendElement(data.get());
435 const uint32_t count = entries.Length();
437 if (!gLogLeaksOnly || leaked) {
438 // Sort the entries alphabetically by classname.
439 entries.Sort();
441 for (uint32_t i = 0; i < count; ++i) {
442 BloatEntry* entry = entries[i];
443 entry->Dump(i, gBloatLog);
446 fprintf(gBloatLog, "\n");
449 fprintf(gBloatLog, "nsTraceRefcnt::DumpStatistics: %d entries\n", count);
451 if (gSerialNumbers) {
452 bool onlyLoggingStringBuffers = gTypesToLog && gTypesToLog->Count() == 1 &&
453 gTypesToLog->Contains("StringBuffer");
455 fprintf(gBloatLog, "\nSerial Numbers of Leaked Objects:\n");
456 for (auto iter = gSerialNumbers->ConstIter(); !iter.Done(); iter.Next()) {
457 DumpSerialNumbers(iter, gBloatLog, onlyLoggingStringBuffers);
461 return NS_OK;
464 void nsTraceRefcnt::ResetStatistics() {
465 AutoTraceLogLock lock(gTraceLog);
466 gBloatView = nullptr;
469 static intptr_t GetSerialNumber(void* aPtr, bool aCreate, void* aFirstFramePC) {
470 if (!aCreate) {
471 auto record = gSerialNumbers->Get(aPtr);
472 return record ? record->serialNumber : 0;
475 gSerialNumbers->WithEntryHandle(aPtr, [aFirstFramePC](auto&& entry) {
476 if (entry) {
477 MOZ_CRASH(
478 "If an object already has a serial number, we should be destroying "
479 "it.");
482 auto& record = entry.Insert(mozilla::MakeUnique<SerialNumberRecord>());
483 WalkTheStackSavingLocations(record->allocationStack, aFirstFramePC);
484 if (gLogJSStacks) {
485 record->SaveJSStack();
488 return gNextSerialNumber;
491 static void RecycleSerialNumberPtr(void* aPtr) { gSerialNumbers->Remove(aPtr); }
493 static bool LogThisObj(intptr_t aSerialNumber) {
494 return gObjectsToLog->Contains(aSerialNumber);
497 using EnvCharType = mozilla::filesystem::Path::value_type;
499 static bool InitLog(const EnvCharType* aEnvVar, const char* aMsg,
500 FILE** aResult, const char* aProcType) {
501 #ifdef XP_WIN
502 // This is gross, I know.
503 const wchar_t* envvar = reinterpret_cast<const wchar_t*>(aEnvVar);
504 const char16_t* value = reinterpret_cast<const char16_t*>(::_wgetenv(envvar));
505 # define ENVVAR_PRINTF "%S"
506 #else
507 const char* envvar = aEnvVar;
508 const char* value = ::getenv(aEnvVar);
509 # define ENVVAR_PRINTF "%s"
510 #endif
512 if (value) {
513 nsTDependentString<EnvCharType> fname(value);
514 if (fname.EqualsLiteral("1")) {
515 *aResult = stdout;
516 fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stdout\n",
517 envvar, aMsg);
518 return true;
520 if (fname.EqualsLiteral("2")) {
521 *aResult = stderr;
522 fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stderr\n",
523 envvar, aMsg);
524 return true;
526 if (!XRE_IsParentProcess()) {
527 nsTString<EnvCharType> extension;
528 extension.AssignLiteral(".log");
529 bool hasLogExtension = StringEndsWith(fname, extension);
530 if (hasLogExtension) {
531 fname.Cut(fname.Length() - 4, 4);
533 fname.Append('_');
534 fname.AppendASCII(aProcType);
535 fname.AppendLiteral("_pid");
536 fname.AppendInt((uint32_t)getpid());
537 if (hasLogExtension) {
538 fname.AppendLiteral(".log");
541 #ifdef XP_WIN
542 FILE* stream = ::_wfopen(fname.get(), L"wN");
543 const wchar_t* fp = (const wchar_t*)fname.get();
544 #else
545 FILE* stream = ::fopen(fname.get(), "w");
546 const char* fp = fname.get();
547 #endif
548 if (stream) {
549 MozillaRegisterDebugFD(fileno(stream));
550 *aResult = stream;
551 fprintf(stderr,
552 "### " ENVVAR_PRINTF " defined -- logging %s to " ENVVAR_PRINTF
553 "\n",
554 envvar, aMsg, fp);
556 return true;
559 fprintf(stderr,
560 "### " ENVVAR_PRINTF
561 " defined -- unable to log %s to " ENVVAR_PRINTF "\n",
562 envvar, aMsg, fp);
563 MOZ_ASSERT(false, "Tried and failed to create an XPCOM log");
565 #undef ENVVAR_PRINTF
567 return false;
570 static void maybeUnregisterAndCloseFile(FILE*& aFile) {
571 if (!aFile) {
572 return;
575 MozillaUnRegisterDebugFILE(aFile);
576 fclose(aFile);
577 aFile = nullptr;
580 static void DoInitTraceLog(const char* aProcType) {
581 #ifdef XP_WIN
582 # define ENVVAR(x) u"" x
583 #else
584 # define ENVVAR(x) x
585 #endif
587 bool defined = InitLog(ENVVAR("XPCOM_MEM_BLOAT_LOG"), "bloat/leaks",
588 &gBloatLog, aProcType);
589 if (!defined) {
590 gLogLeaksOnly =
591 InitLog(ENVVAR("XPCOM_MEM_LEAK_LOG"), "leaks", &gBloatLog, aProcType);
593 if (defined || gLogLeaksOnly) {
594 // Use the same bloat view, if there is one, to keep it consistent
595 // between the fork server and content processes.
596 EnsureBloatView();
597 } else if (gBloatView) {
598 nsTraceRefcnt::ResetStatistics();
601 InitLog(ENVVAR("XPCOM_MEM_REFCNT_LOG"), "refcounts", &gRefcntsLog, aProcType);
603 InitLog(ENVVAR("XPCOM_MEM_ALLOC_LOG"), "new/delete", &gAllocLog, aProcType);
605 const char* classes = getenv("XPCOM_MEM_LOG_CLASSES");
607 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
608 if (classes) {
609 InitLog(ENVVAR("XPCOM_MEM_COMPTR_LOG"), "nsCOMPtr", &gCOMPtrLog, aProcType);
610 } else {
611 if (getenv("XPCOM_MEM_COMPTR_LOG")) {
612 fprintf(stdout,
613 "### XPCOM_MEM_COMPTR_LOG defined -- "
614 "but XPCOM_MEM_LOG_CLASSES is not defined\n");
617 #else
618 const char* comptr_log = getenv("XPCOM_MEM_COMPTR_LOG");
619 if (comptr_log) {
620 fprintf(stdout,
621 "### XPCOM_MEM_COMPTR_LOG defined -- "
622 "but it will not work without dynamic_cast\n");
624 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
626 #undef ENVVAR
628 if (classes) {
629 // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted
630 // as a list of class names to track
632 // Use the same |gTypesToLog| and |gSerialNumbers| to keep them
633 // consistent through the fork server and content processes.
634 // Without this, counters will be incorrect.
635 if (!gTypesToLog) {
636 gTypesToLog = new CharPtrSet(256);
639 fprintf(stdout,
640 "### XPCOM_MEM_LOG_CLASSES defined -- "
641 "only logging these classes: ");
642 const char* cp = classes;
643 for (;;) {
644 char* cm = (char*)strchr(cp, ',');
645 if (cm) {
646 *cm = '\0';
648 if (!gTypesToLog->Contains(cp)) {
649 gTypesToLog->PutEntry(cp);
651 fprintf(stdout, "%s ", cp);
652 if (!cm) {
653 break;
655 *cm = ',';
656 cp = cm + 1;
658 fprintf(stdout, "\n");
660 if (!gSerialNumbers) {
661 gSerialNumbers = new SerialHash(256);
663 } else {
664 gTypesToLog = nullptr;
665 gSerialNumbers = nullptr;
668 const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS");
669 if (objects) {
670 gObjectsToLog = new IntPtrSet(256);
672 if (!(gRefcntsLog || gAllocLog || gCOMPtrLog)) {
673 fprintf(stdout,
674 "### XPCOM_MEM_LOG_OBJECTS defined -- "
675 "but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n");
676 } else {
677 fprintf(stdout,
678 "### XPCOM_MEM_LOG_OBJECTS defined -- "
679 "only logging these objects: ");
680 const char* cp = objects;
681 for (;;) {
682 char* cm = (char*)strchr(cp, ',');
683 if (cm) {
684 *cm = '\0';
686 intptr_t top = 0;
687 intptr_t bottom = 0;
688 while (*cp) {
689 if (*cp == '-') {
690 bottom = top;
691 top = 0;
692 ++cp;
694 top *= 10;
695 top += *cp - '0';
696 ++cp;
698 if (!bottom) {
699 bottom = top;
701 for (intptr_t serialno = bottom; serialno <= top; serialno++) {
702 gObjectsToLog->PutEntry(serialno);
703 fprintf(stdout, "%" PRIdPTR " ", serialno);
705 if (!cm) {
706 break;
708 *cm = ',';
709 cp = cm + 1;
711 fprintf(stdout, "\n");
715 if (getenv("XPCOM_MEM_LOG_JS_STACK")) {
716 fprintf(stdout, "### XPCOM_MEM_LOG_JS_STACK defined\n");
717 gLogJSStacks = true;
720 if (gBloatLog) {
721 gLogging = OnlyBloatLogging;
724 if (gRefcntsLog || gAllocLog || gCOMPtrLog) {
725 gLogging = FullLogging;
729 static void InitTraceLog() {
730 if (gInitialized) {
731 return;
733 gInitialized = true;
735 DoInitTraceLog(XRE_GetProcessTypeString());
738 extern "C" {
740 static void EnsureWrite(FILE* aStream, const char* aBuf, size_t aLen) {
741 #ifdef XP_WIN
742 int fd = _fileno(aStream);
743 #else
744 int fd = fileno(aStream);
745 #endif
746 while (aLen > 0) {
747 #ifdef XP_WIN
748 auto written = _write(fd, aBuf, aLen);
749 #else
750 auto written = write(fd, aBuf, aLen);
751 #endif
752 if (written <= 0 || size_t(written) > aLen) {
753 break;
755 aBuf += written;
756 aLen -= written;
760 static void PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP,
761 void* aClosure) {
762 auto stream = static_cast<FILE*>(aClosure);
763 static const int buflen = 1024;
764 char buf[buflen + 5] = " "; // 5 for leading " " and trailing '\n'
765 int len =
766 gCodeAddressService->GetLocation(aFrameNumber, aPC, buf + 4, buflen);
767 len = std::min(len, buflen + 1 - 2) + 4;
768 buf[len++] = '\n';
769 buf[len] = '\0';
770 fflush(stream);
771 EnsureWrite(stream, buf, len);
774 static void RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC,
775 void* /*aSP*/, void* aClosure) {
776 auto locations = static_cast<std::vector<void*>*>(aClosure);
777 locations->push_back(aPC);
782 * This is a variant of |MozWalkTheStack| that uses |CodeAddressService| to
783 * cache the results of |NS_DescribeCodeAddress|. If |WalkTheStackCached| is
784 * being called frequently, it will be a few orders of magnitude faster than
785 * |MozWalkTheStack|. However, the cache uses a lot of memory, which can cause
786 * OOM crashes. Therefore, this should only be used for things like refcount
787 * logging which walk the stack extremely frequently.
789 static void WalkTheStackCached(FILE* aStream, const void* aFirstFramePC) {
790 if (!gCodeAddressService) {
791 gCodeAddressService = new CodeAddressService<>();
793 MozStackWalk(PrintStackFrameCached, aFirstFramePC, /* maxFrames */ 0,
794 aStream);
797 static void WalkTheStackSavingLocations(std::vector<void*>& aLocations,
798 const void* aFirstFramePC) {
799 if (!gCodeAddressService) {
800 gCodeAddressService = new CodeAddressService<>();
802 MozStackWalk(RecordStackFrame, aFirstFramePC, /* maxFrames */ 0, &aLocations);
805 //----------------------------------------------------------------------
807 EXPORT_XPCOM_API(void)
808 NS_LogInit() {
809 NS_SetMainThread();
811 #if defined(NS_BUILD_REFCNT_LOGGING)
812 mozilla::detail::RefCountLogger::SetLeakCheckingFunctions(NS_LogAddRef,
813 NS_LogRelease);
814 #endif
816 // FIXME: This is called multiple times, we should probably not allow that.
817 if (++gInitCount) {
818 nsTraceRefcnt::SetActivityIsLegal(true);
822 EXPORT_XPCOM_API(void)
823 NS_LogTerm() { mozilla::LogTerm(); }
825 #ifdef MOZ_DMD
826 // If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file.
827 // The value of this environment variable is used as the prefix
828 // of the file name, so you probably want something like "/tmp/".
829 // By default, this is run in all processes, but you can record a
830 // log only for a specific process type by setting MOZ_DMD_LOG_PROCESS
831 // to the process type you want to log, such as "default" or "tab".
832 // This method can't use the higher level XPCOM file utilities
833 // because it is run very late in shutdown to avoid recording
834 // information about refcount logging entries.
835 static void LogDMDFile() {
836 const char* dmdFilePrefix = PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG");
837 if (!dmdFilePrefix) {
838 return;
841 const char* logProcessEnv = PR_GetEnv("MOZ_DMD_LOG_PROCESS");
842 if (logProcessEnv && !!strcmp(logProcessEnv, XRE_GetProcessTypeString())) {
843 return;
846 nsPrintfCString fileName("%sdmd-%" PRIPID ".log.gz", dmdFilePrefix,
847 base::GetCurrentProcId());
848 FILE* logFile = fopen(fileName.get(), "w");
849 if (NS_WARN_IF(!logFile)) {
850 return;
853 nsMemoryInfoDumper::DumpDMDToFile(logFile);
855 #endif // MOZ_DMD
857 namespace mozilla {
858 void LogTerm() {
859 NS_ASSERTION(gInitCount > 0, "NS_LogTerm without matching NS_LogInit");
861 if (--gInitCount == 0) {
862 #ifdef DEBUG
863 /* FIXME bug 491977: This is only going to operate on the
864 * BlockingResourceBase which is compiled into
865 * libxul/libxpcom_core.so. Anyone using external linkage will
866 * have their own copy of BlockingResourceBase statics which will
867 * not be freed by this method.
869 * It sounds like what we really want is to be able to register a
870 * callback function to call at XPCOM shutdown. Note that with
871 * this solution, however, we need to guarantee that
872 * BlockingResourceBase::Shutdown() runs after all other shutdown
873 * functions.
875 BlockingResourceBase::Shutdown();
876 #endif
878 if (gInitialized) {
879 nsTraceRefcnt::DumpStatistics();
880 nsTraceRefcnt::ResetStatistics();
882 nsTraceRefcnt::Shutdown();
883 nsTraceRefcnt::SetActivityIsLegal(false);
884 gActivityTLS = BAD_TLS_INDEX;
886 #ifdef MOZ_DMD
887 LogDMDFile();
888 #endif
892 } // namespace mozilla
894 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
895 NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt, const char* aClass,
896 uint32_t aClassSize) {
897 ASSERT_ACTIVITY_IS_LEGAL(aClass, "addrefed");
898 if (!gInitialized) {
899 InitTraceLog();
901 if (gLogging == NoLogging) {
902 return;
904 if (aRefcnt == 1 || gLogging == FullLogging) {
905 AutoTraceLogLock lock(gTraceLog);
907 if (aRefcnt == 1 && gBloatLog) {
908 BloatEntry* entry = GetBloatEntry(aClass, aClassSize);
909 if (entry) {
910 entry->Ctor();
914 // Here's the case where MOZ_COUNT_CTOR was not used,
915 // yet we still want to see creation information:
917 bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass));
918 intptr_t serialno = 0;
919 if (gSerialNumbers && loggingThisType) {
920 serialno = GetSerialNumber(aPtr, aRefcnt == 1, CallerPC());
921 MOZ_ASSERT(serialno != 0,
922 "Serial number requested for unrecognized pointer! "
923 "Are you memmoving a refcounted object?");
924 auto record = gSerialNumbers->Get(aPtr);
925 if (record) {
926 ++record->refCount;
930 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
931 if (aRefcnt == 1 && gAllocLog && loggingThisType && loggingThisObject) {
932 fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Create [thread %p]\n", aClass,
933 aPtr, serialno, PR_GetCurrentThread());
934 WalkTheStackCached(gAllocLog, CallerPC());
937 if (gRefcntsLog && loggingThisType && loggingThisObject) {
938 // Can't use MOZ_LOG(), b/c it truncates the line
939 fprintf(gRefcntsLog,
940 "\n<%s> %p %" PRIuPTR " AddRef %" PRIuPTR " [thread %p]\n",
941 aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread());
942 WalkTheStackCached(gRefcntsLog, CallerPC());
943 fflush(gRefcntsLog);
948 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
949 NS_LogRelease(void* aPtr, nsrefcnt aRefcnt, const char* aClass) {
950 ASSERT_ACTIVITY_IS_LEGAL(aClass, "released");
951 if (!gInitialized) {
952 InitTraceLog();
954 if (gLogging == NoLogging) {
955 return;
957 if (aRefcnt == 0 || gLogging == FullLogging) {
958 AutoTraceLogLock lock(gTraceLog);
960 if (aRefcnt == 0 && gBloatLog) {
961 BloatEntry* entry = GetBloatEntry(aClass, 0);
962 if (entry) {
963 entry->Dtor();
967 bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass));
968 intptr_t serialno = 0;
969 if (gSerialNumbers && loggingThisType) {
970 serialno = GetSerialNumber(aPtr, false, CallerPC());
971 MOZ_ASSERT(serialno != 0,
972 "Serial number requested for unrecognized pointer! "
973 "Are you memmoving a refcounted object?");
974 auto record = gSerialNumbers->Get(aPtr);
975 if (record) {
976 --record->refCount;
980 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
981 if (gRefcntsLog && loggingThisType && loggingThisObject) {
982 // Can't use MOZ_LOG(), b/c it truncates the line
983 fprintf(gRefcntsLog,
984 "\n<%s> %p %" PRIuPTR " Release %" PRIuPTR " [thread %p]\n",
985 aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread());
986 WalkTheStackCached(gRefcntsLog, CallerPC());
987 fflush(gRefcntsLog);
990 // Here's the case where MOZ_COUNT_DTOR was not used,
991 // yet we still want to see deletion information:
993 if (aRefcnt == 0 && gAllocLog && loggingThisType && loggingThisObject) {
994 fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Destroy [thread %p]\n", aClass,
995 aPtr, serialno, PR_GetCurrentThread());
996 WalkTheStackCached(gAllocLog, CallerPC());
999 if (aRefcnt == 0 && gSerialNumbers && loggingThisType) {
1000 RecycleSerialNumberPtr(aPtr);
1005 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
1006 NS_LogCtor(void* aPtr, const char* aType, uint32_t aInstanceSize) {
1007 ASSERT_ACTIVITY_IS_LEGAL(aType, "constructed");
1008 if (!gInitialized) {
1009 InitTraceLog();
1012 if (gLogging == NoLogging) {
1013 return;
1016 AutoTraceLogLock lock(gTraceLog);
1018 if (gBloatLog) {
1019 BloatEntry* entry = GetBloatEntry(aType, aInstanceSize);
1020 if (entry) {
1021 entry->Ctor();
1025 bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType));
1026 intptr_t serialno = 0;
1027 if (gSerialNumbers && loggingThisType) {
1028 serialno = GetSerialNumber(aPtr, true, CallerPC());
1029 MOZ_ASSERT(serialno != 0,
1030 "GetSerialNumber should never return 0 when passed true");
1033 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
1034 if (gAllocLog && loggingThisType && loggingThisObject) {
1035 fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Ctor (%d)\n", aType, aPtr,
1036 serialno, aInstanceSize);
1037 WalkTheStackCached(gAllocLog, CallerPC());
1041 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
1042 NS_LogDtor(void* aPtr, const char* aType, uint32_t aInstanceSize) {
1043 ASSERT_ACTIVITY_IS_LEGAL(aType, "destroyed");
1044 if (!gInitialized) {
1045 InitTraceLog();
1048 if (gLogging == NoLogging) {
1049 return;
1052 AutoTraceLogLock lock(gTraceLog);
1054 if (gBloatLog) {
1055 BloatEntry* entry = GetBloatEntry(aType, aInstanceSize);
1056 if (entry) {
1057 entry->Dtor();
1061 bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType));
1062 intptr_t serialno = 0;
1063 if (gSerialNumbers && loggingThisType) {
1064 serialno = GetSerialNumber(aPtr, false, CallerPC());
1065 MOZ_ASSERT(serialno != 0,
1066 "Serial number requested for unrecognized pointer! "
1067 "Are you memmoving a MOZ_COUNT_CTOR-tracked object?");
1068 RecycleSerialNumberPtr(aPtr);
1071 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
1073 // (If we're on a losing architecture, don't do this because we'll be
1074 // using LogDeleteXPCOM instead to get file and line numbers.)
1075 if (gAllocLog && loggingThisType && loggingThisObject) {
1076 fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Dtor (%d)\n", aType, aPtr,
1077 serialno, aInstanceSize);
1078 WalkTheStackCached(gAllocLog, CallerPC());
1082 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
1083 NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject) {
1084 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1085 // Get the most-derived object.
1086 void* object = dynamic_cast<void*>(aObject);
1088 // This is a very indirect way of finding out what the class is
1089 // of the object being logged. If we're logging a specific type,
1090 // then
1091 if (!gTypesToLog || !gSerialNumbers) {
1092 return;
1094 if (!gInitialized) {
1095 InitTraceLog();
1097 if (gLogging == FullLogging) {
1098 AutoTraceLogLock lock(gTraceLog);
1100 intptr_t serialno = GetSerialNumber(object, false, CallerPC());
1101 if (serialno == 0) {
1102 return;
1105 auto record = gSerialNumbers->Get(object);
1106 int32_t count = record ? ++record->COMPtrCount : -1;
1107 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
1109 if (gCOMPtrLog && loggingThisObject) {
1110 fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrAddRef %d %p\n",
1111 object, serialno, count, aCOMPtr);
1112 WalkTheStackCached(gCOMPtrLog, CallerPC());
1115 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1118 EXPORT_XPCOM_API(MOZ_NEVER_INLINE void)
1119 NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject) {
1120 #ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1121 // Get the most-derived object.
1122 void* object = dynamic_cast<void*>(aObject);
1124 // This is a very indirect way of finding out what the class is
1125 // of the object being logged. If we're logging a specific type,
1126 // then
1127 if (!gTypesToLog || !gSerialNumbers) {
1128 return;
1130 if (!gInitialized) {
1131 InitTraceLog();
1133 if (gLogging == FullLogging) {
1134 AutoTraceLogLock lock(gTraceLog);
1136 intptr_t serialno = GetSerialNumber(object, false, CallerPC());
1137 if (serialno == 0) {
1138 return;
1141 auto record = gSerialNumbers->Get(object);
1142 int32_t count = record ? --record->COMPtrCount : -1;
1143 bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
1145 if (gCOMPtrLog && loggingThisObject) {
1146 fprintf(gCOMPtrLog, "\n<?> %p %" PRIdPTR " nsCOMPtrRelease %d %p\n",
1147 object, serialno, count, aCOMPtr);
1148 WalkTheStackCached(gCOMPtrLog, CallerPC());
1151 #endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
1154 static void ClearLogs(bool aKeepCounters) {
1155 gCodeAddressService = nullptr;
1156 // These counters from the fork server process will be preserved
1157 // for the content processes to keep them consistent.
1158 if (!aKeepCounters) {
1159 gBloatView = nullptr;
1160 gTypesToLog = nullptr;
1161 gSerialNumbers = nullptr;
1163 gObjectsToLog = nullptr;
1164 gLogJSStacks = false;
1165 gLogLeaksOnly = false;
1166 maybeUnregisterAndCloseFile(gBloatLog);
1167 maybeUnregisterAndCloseFile(gRefcntsLog);
1168 maybeUnregisterAndCloseFile(gAllocLog);
1169 maybeUnregisterAndCloseFile(gCOMPtrLog);
1172 void nsTraceRefcnt::Shutdown() { ClearLogs(false); }
1174 void nsTraceRefcnt::SetActivityIsLegal(bool aLegal) {
1175 if (gActivityTLS == BAD_TLS_INDEX) {
1176 PR_NewThreadPrivateIndex(&gActivityTLS, nullptr);
1179 PR_SetThreadPrivate(gActivityTLS, reinterpret_cast<void*>(!aLegal));
1182 void nsTraceRefcnt::StartLoggingClass(const char* aClass) {
1183 gLogging = FullLogging;
1185 if (!gTypesToLog) {
1186 gTypesToLog = new CharPtrSet(256);
1189 fprintf(stdout, "### StartLoggingClass %s\n", aClass);
1190 if (!gTypesToLog->Contains(aClass)) {
1191 gTypesToLog->PutEntry(aClass);
1194 // We are deliberately not initializing gSerialNumbers here, because
1195 // it would cause assertions. gObjectsToLog can't be used because it
1196 // relies on serial numbers.
1198 #ifdef XP_WIN
1199 # define ENVVAR(x) u"" x
1200 #else
1201 # define ENVVAR(x) x
1202 #endif
1204 if (!gRefcntsLog) {
1205 InitLog(ENVVAR("XPCOM_MEM_LATE_REFCNT_LOG"), "refcounts", &gRefcntsLog,
1206 XRE_GetProcessTypeString());
1209 #undef ENVVAR
1212 #ifdef MOZ_ENABLE_FORKSERVER
1213 void nsTraceRefcnt::CloseLogFilesAfterFork() {
1214 // Disable logging, and close our log files. We can't have open file
1215 // descriptors on the fork server during the FileDescriptorShuffle
1216 // (bug 1909125).
1217 gLogging = NoLogging;
1219 ClearLogs(true);
1222 void nsTraceRefcnt::ReopenLogFilesAfterFork(const char* aProcType) {
1223 // Create log files with the correct process type in the name.
1224 // This will re-initialize gLogging after it was cleared in
1225 // CloseLogFilesAfterFork.
1226 DoInitTraceLog(aProcType);
1228 #endif