nsTraceRefcntImpl.cpp revision 677833bc953b6cb418c701facbdcf4aa18d6c44e
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* L. David Baron <dbaron@dbaron.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsTraceRefcntImpl.h"
#include "nscore.h"
#include "nsISupports.h"
#include "nsVoidArray.h"
#include "prprf.h"
#include "prlog.h"
#include "plstr.h"
#include <stdlib.h>
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include <math.h>
#if defined(_WIN32)
#include <windows.h>
#elif defined(linux) && defined(__GLIBC__) && (defined(__i386) || defined(PPC))
#include <setjmp.h>
//
// On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed
// if __USE_GNU is defined. I suppose its some kind of standards
// adherence thing.
//
#if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU)
#define __USE_GNU
#endif
#include <dlfcn.h>
#endif
#ifdef HAVE_LIBDL
#include <dlfcn.h>
#endif
#if defined(XP_MAC) && !TARGET_CARBON
#include "macstdlibextras.h"
#endif
////////////////////////////////////////////////////////////////////////////////
NS_COM void
NS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues,
double *meanResult, double *stdDevResult)
{
double mean = 0.0, var = 0.0, stdDev = 0.0;
if (n > 0.0 && sumOfValues >= 0) {
mean = sumOfValues / n;
double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues);
if (temp < 0.0 || n <= 1)
var = 0.0;
else
var = temp / (n * (n - 1));
// for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this:
stdDev = var != 0.0 ? sqrt(var) : 0.0;
}
*meanResult = mean;
*stdDevResult = stdDev;
}
////////////////////////////////////////////////////////////////////////////////
#ifdef NS_BUILD_REFCNT_LOGGING
#include "plhash.h"
#include "prmem.h"
#include "prlock.h"
static PRLock* gTraceLock;
#define LOCK_TRACELOG() PR_Lock(gTraceLock)
#define UNLOCK_TRACELOG() PR_Unlock(gTraceLock)
static PLHashTable* gBloatView;
static PLHashTable* gTypesToLog;
static PLHashTable* gObjectsToLog;
static PLHashTable* gSerialNumbers;
static PRInt32 gNextSerialNumber;
static PRBool gLogging;
static PRBool gLogToLeaky;
static PRBool gLogLeaksOnly;
static void (*leakyLogAddRef)(void* p, int oldrc, int newrc);
static void (*leakyLogRelease)(void* p, int oldrc, int newrc);
static PRBool gInitialized = PR_FALSE;
static FILE *gBloatLog = nsnull;
static FILE *gRefcntsLog = nsnull;
static FILE *gAllocLog = nsnull;
static FILE *gLeakyLog = nsnull;
static FILE *gCOMPtrLog = nsnull;
static PRBool gActivityIsLegal = PR_FALSE;
struct serialNumberRecord {
PRInt32 serialNumber;
PRInt32 refCount;
PRInt32 COMPtrCount;
};
struct nsTraceRefcntStats {
nsrefcnt mAddRefs;
nsrefcnt mReleases;
nsrefcnt mCreates;
nsrefcnt mDestroys;
double mRefsOutstandingTotal;
double mRefsOutstandingSquared;
double mObjsOutstandingTotal;
double mObjsOutstandingSquared;
};
#ifdef DEBUG_dbaron_off
// I hope to turn this on for everybody once we hit it a little less.
#define ASSERT_ACTIVITY_IS_LEGAL \
NS_WARN_IF_FALSE(gActivityIsLegal, \
"XPCOM objects created/destroyed from static ctor/dtor")
#else
#define ASSERT_ACTIVITY_IS_LEGAL
#endif
// These functions are copied from nsprpub/lib/ds/plhash.c, with changes
// to the functions not called Default* to free the serialNumberRecord or
// the BloatEntry.
static void * PR_CALLBACK
DefaultAllocTable(void *pool, PRSize size)
{
#if defined(XP_MAC)
#pragma unused (pool)
#endif
return PR_MALLOC(size);
}
static void PR_CALLBACK
DefaultFreeTable(void *pool, void *item)
{
#if defined(XP_MAC)
#pragma unused (pool)
#endif
PR_Free(item);
}
static PLHashEntry * PR_CALLBACK
DefaultAllocEntry(void *pool, const void *key)
{
#if defined(XP_MAC)
#pragma unused (pool,key)
#endif
return PR_NEW(PLHashEntry);
}
static void PR_CALLBACK
SerialNumberFreeEntry(void *pool, PLHashEntry *he, PRUintn flag)
{
#if defined(XP_MAC)
#pragma unused (pool)
#endif
if (flag == HT_FREE_ENTRY) {
PR_Free(NS_REINTERPRET_CAST(serialNumberRecord*,he->value));
PR_Free(he);
}
}
static void PR_CALLBACK
TypesToLogFreeEntry(void *pool, PLHashEntry *he, PRUintn flag)
{
#if defined(XP_MAC)
#pragma unused (pool)
#endif
if (flag == HT_FREE_ENTRY) {
nsCRT::free(NS_CONST_CAST(char*,
NS_REINTERPRET_CAST(const char*, he->key)));
PR_Free(he);
}
}
static const PLHashAllocOps serialNumberHashAllocOps = {
DefaultAllocTable, DefaultFreeTable,
DefaultAllocEntry, SerialNumberFreeEntry
};
static const PLHashAllocOps typesToLogHashAllocOps = {
DefaultAllocTable, DefaultFreeTable,
DefaultAllocEntry, TypesToLogFreeEntry
};
////////////////////////////////////////////////////////////////////////////////
class BloatEntry {
public:
BloatEntry(const char* className, PRUint32 classSize)
: mClassSize(classSize) {
mClassName = PL_strdup(className);
Clear(&mNewStats);
Clear(&mAllStats);
mTotalLeaked = 0;
}
~BloatEntry() {
PL_strfree(mClassName);
}
PRUint32 GetClassSize() { return (PRUint32)mClassSize; }
const char* GetClassName() { return mClassName; }
static void Clear(nsTraceRefcntStats* stats) {
stats->mAddRefs = 0;
stats->mReleases = 0;
stats->mCreates = 0;
stats->mDestroys = 0;
stats->mRefsOutstandingTotal = 0;
stats->mRefsOutstandingSquared = 0;
stats->mObjsOutstandingTotal = 0;
stats->mObjsOutstandingSquared = 0;
}
void Accumulate() {
mAllStats.mAddRefs += mNewStats.mAddRefs;
mAllStats.mReleases += mNewStats.mReleases;
mAllStats.mCreates += mNewStats.mCreates;
mAllStats.mDestroys += mNewStats.mDestroys;
mAllStats.mRefsOutstandingTotal += mNewStats.mRefsOutstandingTotal;
mAllStats.mRefsOutstandingSquared += mNewStats.mRefsOutstandingSquared;
mAllStats.mObjsOutstandingTotal += mNewStats.mObjsOutstandingTotal;
mAllStats.mObjsOutstandingSquared += mNewStats.mObjsOutstandingSquared;
Clear(&mNewStats);
}
void AddRef(nsrefcnt refcnt) {
mNewStats.mAddRefs++;
if (refcnt == 1) {
Ctor();
}
AccountRefs();
}
void Release(nsrefcnt refcnt) {
mNewStats.mReleases++;
if (refcnt == 0) {
Dtor();
}
AccountRefs();
}
void Ctor() {
mNewStats.mCreates++;
AccountObjs();
}
void Dtor() {
mNewStats.mDestroys++;
AccountObjs();
}
void AccountRefs() {
PRInt32 cnt = (mNewStats.mAddRefs - mNewStats.mReleases);
mNewStats.mRefsOutstandingTotal += cnt;
mNewStats.mRefsOutstandingSquared += cnt * cnt;
}
void AccountObjs() {
PRInt32 cnt = (mNewStats.mCreates - mNewStats.mDestroys);
mNewStats.mObjsOutstandingTotal += cnt;
mNewStats.mObjsOutstandingSquared += cnt * cnt;
}
static PRIntn PR_CALLBACK DumpEntry(PLHashEntry *he, PRIntn i, void *arg) {
BloatEntry* entry = (BloatEntry*)he->value;
if (entry) {
entry->Accumulate();
NS_STATIC_CAST(nsVoidArray*, arg)->AppendElement(entry);
}
return HT_ENUMERATE_NEXT;
}
static PRIntn PR_CALLBACK TotalEntries(PLHashEntry *he, PRIntn i, void *arg) {
BloatEntry* entry = (BloatEntry*)he->value;
if (entry && nsCRT::strcmp(entry->mClassName, "TOTAL") != 0) {
entry->Total((BloatEntry*)arg);
}
return HT_ENUMERATE_NEXT;
}
void Total(BloatEntry* total) {
total->mAllStats.mAddRefs += mNewStats.mAddRefs + mAllStats.mAddRefs;
total->mAllStats.mReleases += mNewStats.mReleases + mAllStats.mReleases;
total->mAllStats.mCreates += mNewStats.mCreates + mAllStats.mCreates;
total->mAllStats.mDestroys += mNewStats.mDestroys + mAllStats.mDestroys;
total->mAllStats.mRefsOutstandingTotal += mNewStats.mRefsOutstandingTotal + mAllStats.mRefsOutstandingTotal;
total->mAllStats.mRefsOutstandingSquared += mNewStats.mRefsOutstandingSquared + mAllStats.mRefsOutstandingSquared;
total->mAllStats.mObjsOutstandingTotal += mNewStats.mObjsOutstandingTotal + mAllStats.mObjsOutstandingTotal;
total->mAllStats.mObjsOutstandingSquared += mNewStats.mObjsOutstandingSquared + mAllStats.mObjsOutstandingSquared;
PRInt32 count = (mNewStats.mCreates + mAllStats.mCreates);
total->mClassSize += mClassSize * count; // adjust for average in DumpTotal
total->mTotalLeaked += (PRInt32)(mClassSize *
((mNewStats.mCreates + mAllStats.mCreates)
-(mNewStats.mDestroys + mAllStats.mDestroys)));
}
nsresult DumpTotal(PRUint32 nClasses, FILE* out) {
mClassSize /= mAllStats.mCreates;
return Dump(-1, out, nsTraceRefcntImpl::ALL_STATS);
}
static PRBool HaveLeaks(nsTraceRefcntStats* stats) {
return ((stats->mAddRefs != stats->mReleases) ||
(stats->mCreates != stats->mDestroys));
}
static nsresult PrintDumpHeader(FILE* out, const char* msg) {
fprintf(out, "\n== BloatView: %s\n\n", msg);
fprintf(out,
" |<----------------Class--------------->|<-----Bytes------>|<----------------Objects---------------->|<--------------References-------------->|\n");
fprintf(out,
" Per-Inst Leaked Total Rem Mean StdDev Total Rem Mean StdDev\n");
return NS_OK;
}
nsresult Dump(PRIntn i, FILE* out, nsTraceRefcntImpl::StatisticsType type) {
nsTraceRefcntStats* stats = (type == nsTraceRefcntImpl::NEW_STATS) ? &mNewStats : &mAllStats;
if (gLogLeaksOnly && !HaveLeaks(stats)) {
return NS_OK;
}
double meanRefs, stddevRefs;
NS_MeanAndStdDev(stats->mAddRefs + stats->mReleases,
stats->mRefsOutstandingTotal,
stats->mRefsOutstandingSquared,
&meanRefs, &stddevRefs);
double meanObjs, stddevObjs;
NS_MeanAndStdDev(stats->mCreates + stats->mDestroys,
stats->mObjsOutstandingTotal,
stats->mObjsOutstandingSquared,
&meanObjs, &stddevObjs);
if ((stats->mAddRefs - stats->mReleases) != 0 ||
stats->mAddRefs != 0 ||
meanRefs != 0 ||
stddevRefs != 0 ||
(stats->mCreates - stats->mDestroys) != 0 ||
stats->mCreates != 0 ||
meanObjs != 0 ||
stddevObjs != 0) {
fprintf(out, "%4d %-40.40s %8d %8d %8d %8d (%8.2f +/- %8.2f) %8d %8d (%8.2f +/- %8.2f)\n",
i+1, mClassName,
(PRInt32)mClassSize,
(nsCRT::strcmp(mClassName, "TOTAL"))
?(PRInt32)((stats->mCreates - stats->mDestroys) * mClassSize)
:mTotalLeaked,
stats->mCreates,
(stats->mCreates - stats->mDestroys),
meanObjs,
stddevObjs,
stats->mAddRefs,
(stats->mAddRefs - stats->mReleases),
meanRefs,
stddevRefs);
}
return NS_OK;
}
protected:
char* mClassName;
double mClassSize; // this is stored as a double because of the way we compute the avg class size for total bloat
PRInt32 mTotalLeaked; // used only for TOTAL entry
nsTraceRefcntStats mNewStats;
nsTraceRefcntStats mAllStats;
};
static void PR_CALLBACK
BloatViewFreeEntry(void *pool, PLHashEntry *he, PRUintn flag)
{
#if defined(XP_MAC)
#pragma unused (pool)
#endif
if (flag == HT_FREE_ENTRY) {
BloatEntry* entry = NS_REINTERPRET_CAST(BloatEntry*,he->value);
delete entry;
PR_Free(he);
}
}
const static PLHashAllocOps bloatViewHashAllocOps = {
DefaultAllocTable, DefaultFreeTable,
DefaultAllocEntry, BloatViewFreeEntry
};
static void
RecreateBloatView()
{
gBloatView = PL_NewHashTable(256,
PL_HashString,
PL_CompareStrings,
PL_CompareValues,
&bloatViewHashAllocOps, NULL);
}
static BloatEntry*
GetBloatEntry(const char* aTypeName, PRUint32 aInstanceSize)
{
if (!gBloatView) {
RecreateBloatView();
}
BloatEntry* entry = NULL;
if (gBloatView) {
entry = (BloatEntry*)PL_HashTableLookup(gBloatView, aTypeName);
if (entry == NULL && aInstanceSize > 0) {
entry = new BloatEntry(aTypeName, aInstanceSize);
PLHashEntry* e = PL_HashTableAdd(gBloatView, aTypeName, entry);
if (e == NULL) {
delete entry;
entry = NULL;
}
} else {
NS_ASSERTION(aInstanceSize == 0 ||
entry->GetClassSize() == aInstanceSize,
"bad size recorded");
}
}
return entry;
}
static PRIntn PR_CALLBACK DumpSerialNumbers(PLHashEntry* aHashEntry, PRIntn aIndex, void* aClosure)
{
serialNumberRecord* record = NS_REINTERPRET_CAST(serialNumberRecord *,aHashEntry->value);
#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
fprintf((FILE*) aClosure, "%d (%d references; %d from COMPtrs)\n",
record->serialNumber,
record->refCount,
record->COMPtrCount);
#else
fprintf((FILE*) aClosure, "%d (%d references)\n",
record->serialNumber,
record->refCount);
#endif
return HT_ENUMERATE_NEXT;
}
#endif /* NS_BUILD_REFCNT_LOGGING */
nsresult
nsTraceRefcntImpl::DumpStatistics(StatisticsType type, FILE* out)
{
nsresult rv = NS_OK;
#ifdef NS_BUILD_REFCNT_LOGGING
if (gBloatLog == nsnull || gBloatView == nsnull) {
return NS_ERROR_FAILURE;
}
if (out == nsnull) {
out = gBloatLog;
}
LOCK_TRACELOG();
PRBool wasLogging = gLogging;
gLogging = PR_FALSE; // turn off logging for this method
const char* msg;
if (type == NEW_STATS) {
if (gLogLeaksOnly)
msg = "NEW (incremental) LEAK STATISTICS";
else
msg = "NEW (incremental) LEAK AND BLOAT STATISTICS";
}
else {
if (gLogLeaksOnly)
msg = "ALL (cumulative) LEAK STATISTICS";
else
msg = "ALL (cumulative) LEAK AND BLOAT STATISTICS";
}
rv = BloatEntry::PrintDumpHeader(out, msg);
if (NS_FAILED(rv)) goto done;
{
BloatEntry total("TOTAL", 0);
PL_HashTableEnumerateEntries(gBloatView, BloatEntry::TotalEntries, &total);
total.DumpTotal(gBloatView->nentries, out);
nsVoidArray entries;
PL_HashTableEnumerateEntries(gBloatView, BloatEntry::DumpEntry, &entries);
fprintf(stdout, "nsTraceRefcntImpl::DumpStatistics: %d entries\n",
entries.Count());
// Sort the entries alphabetically by classname.
PRInt32 i, j;
for (i = entries.Count() - 1; i >= 1; --i) {
for (j = i - 1; j >= 0; --j) {
BloatEntry* left = NS_STATIC_CAST(BloatEntry*, entries[i]);
BloatEntry* right = NS_STATIC_CAST(BloatEntry*, entries[j]);
if (PL_strcmp(left->GetClassName(), right->GetClassName()) < 0) {
entries.ReplaceElementAt(right, i);
entries.ReplaceElementAt(left, j);
}
}
}
// Enumerate from back-to-front, so things come out in alpha order
for (i = 0; i < entries.Count(); ++i) {
BloatEntry* entry = NS_STATIC_CAST(BloatEntry*, entries[i]);
entry->Dump(i, out, type);
}
}
if (gSerialNumbers) {
fprintf(out, "\n\nSerial Numbers of Leaked Objects:\n");
PL_HashTableEnumerateEntries(gSerialNumbers, DumpSerialNumbers, out);
}
done:
gLogging = wasLogging;
UNLOCK_TRACELOG();
#endif
return rv;
}
void
nsTraceRefcntImpl::ResetStatistics()
{
#ifdef NS_BUILD_REFCNT_LOGGING
LOCK_TRACELOG();
if (gBloatView) {
PL_HashTableDestroy(gBloatView);
gBloatView = nsnull;
}
UNLOCK_TRACELOG();
#endif
}
#ifdef NS_BUILD_REFCNT_LOGGING
static PRBool LogThisType(const char* aTypeName)
{
void* he = PL_HashTableLookup(gTypesToLog, aTypeName);
return nsnull != he;
}
static PRInt32 GetSerialNumber(void* aPtr, PRBool aCreate)
{
#ifdef GC_LEAK_DETECTOR
// need to disguise this pointer, so the table won't keep the object alive.
aPtr = (void*) ~PLHashNumber(aPtr);
#endif
PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, PLHashNumber(NS_PTR_TO_INT32(aPtr)), aPtr);
if (hep && *hep) {
return PRInt32((NS_REINTERPRET_CAST(serialNumberRecord*,(*hep)->value))->serialNumber);
}
else if (aCreate) {
serialNumberRecord *record = PR_NEW(serialNumberRecord);
record->serialNumber = ++gNextSerialNumber;
record->refCount = 0;
record->COMPtrCount = 0;
PL_HashTableRawAdd(gSerialNumbers, hep, PLHashNumber(NS_PTR_TO_INT32(aPtr)), aPtr, NS_REINTERPRET_CAST(void*,record));
return gNextSerialNumber;
}
else {
return 0;
}
}
static PRInt32* GetRefCount(void* aPtr)
{
#ifdef GC_LEAK_DETECTOR
// need to disguise this pointer, so the table won't keep the object alive.
aPtr = (void*) ~PLHashNumber(aPtr);
#endif
PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, PLHashNumber(NS_PTR_TO_INT32(aPtr)), aPtr);
if (hep && *hep) {
return &((NS_REINTERPRET_CAST(serialNumberRecord*,(*hep)->value))->refCount);
} else {
return nsnull;
}
}
static PRInt32* GetCOMPtrCount(void* aPtr)
{
#ifdef GC_LEAK_DETECTOR
// need to disguise this pointer, so the table won't keep the object alive.
aPtr = (void*) ~PLHashNumber(aPtr);
#endif
PLHashEntry** hep = PL_HashTableRawLookup(gSerialNumbers, PLHashNumber(NS_PTR_TO_INT32(aPtr)), aPtr);
if (hep && *hep) {
return &((NS_REINTERPRET_CAST(serialNumberRecord*,(*hep)->value))->COMPtrCount);
} else {
return nsnull;
}
}
static void RecycleSerialNumberPtr(void* aPtr)
{
#ifdef GC_LEAK_DETECTOR
// need to disguise this pointer, so the table won't keep the object alive.
aPtr = (void*) ~PLHashNumber(aPtr);
#endif
PL_HashTableRemove(gSerialNumbers, aPtr);
}
static PRBool LogThisObj(PRInt32 aSerialNumber)
{
return nsnull != PL_HashTableLookup(gObjectsToLog, (const void*)(aSerialNumber));
}
static PRBool InitLog(const char* envVar, const char* msg, FILE* *result)
{
const char* value = getenv(envVar);
if (value) {
if (nsCRT::strcmp(value, "1") == 0) {
*result = stdout;
fprintf(stdout, "### %s defined -- logging %s to stdout\n",
envVar, msg);
return PR_TRUE;
}
else if (nsCRT::strcmp(value, "2") == 0) {
*result = stderr;
fprintf(stdout, "### %s defined -- logging %s to stderr\n",
envVar, msg);
return PR_TRUE;
}
else {
FILE *stream = ::fopen(value, "w");
if (stream != NULL) {
*result = stream;
fprintf(stdout, "### %s defined -- logging %s to %s\n",
envVar, msg, value);
return PR_TRUE;
}
else {
fprintf(stdout, "### %s defined -- unable to log %s to %s\n",
envVar, msg, value);
return PR_FALSE;
}
}
}
return PR_FALSE;
}
static PLHashNumber PR_CALLBACK HashNumber(const void* aKey)
{
return PLHashNumber(NS_PTR_TO_INT32(aKey));
}
static void InitTraceLog(void)
{
if (gInitialized) return;
gInitialized = PR_TRUE;
#if defined(XP_MAC) && !TARGET_CARBON
// this can get called before Toolbox has been initialized.
InitializeMacToolbox();
#endif
PRBool defined;
defined = InitLog("XPCOM_MEM_BLOAT_LOG", "bloat/leaks", &gBloatLog);
if (!defined)
gLogLeaksOnly = InitLog("XPCOM_MEM_LEAK_LOG", "leaks", &gBloatLog);
if (defined || gLogLeaksOnly) {
RecreateBloatView();
if (!gBloatView) {
NS_WARNING("out of memory");
gBloatLog = nsnull;
gLogLeaksOnly = PR_FALSE;
}
}
(void)InitLog("XPCOM_MEM_REFCNT_LOG", "refcounts", &gRefcntsLog);
(void)InitLog("XPCOM_MEM_ALLOC_LOG", "new/delete", &gAllocLog);
defined = InitLog("XPCOM_MEM_LEAKY_LOG", "for leaky", &gLeakyLog);
if (defined) {
gLogToLeaky = PR_TRUE;
void* p = nsnull;
void* q = nsnull;
#ifdef HAVE_LIBDL
p = dlsym(0, "__log_addref");
q = dlsym(0, "__log_release");
#endif
if (p && q) {
leakyLogAddRef = (void (*)(void*,int,int)) p;
leakyLogRelease = (void (*)(void*,int,int)) q;
}
else {
gLogToLeaky = PR_FALSE;
fprintf(stdout, "### ERROR: XPCOM_MEM_LEAKY_LOG defined, but can't locate __log_addref and __log_release symbols\n");
fflush(stdout);
}
}
const char* classes = getenv("XPCOM_MEM_LOG_CLASSES");
#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR
if (classes) {
(void)InitLog("XPCOM_MEM_COMPTR_LOG", "nsCOMPtr", &gCOMPtrLog);
} else {
if (getenv("XPCOM_MEM_COMPTR_LOG")) {
fprintf(stdout, "### XPCOM_MEM_COMPTR_LOG defined -- but XPCOM_MEM_LOG_CLASSES is not defined\n");
}
}
#else
const char* comptr_log = getenv("XPCOM_MEM_COMPTR_LOG");
if (comptr_log) {
fprintf(stdout, "### XPCOM_MEM_COMPTR_LOG defined -- but it will not work without dynamic_cast\n");
}
#endif
if (classes) {
// if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted
// as a list of class names to track
gTypesToLog = PL_NewHashTable(256,
PL_HashString,
PL_CompareStrings,
PL_CompareValues,
&typesToLogHashAllocOps, NULL);
if (!gTypesToLog) {
NS_WARNING("out of memory");
fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- unable to log specific classes\n");
}
else {
fprintf(stdout, "### XPCOM_MEM_LOG_CLASSES defined -- only logging these classes: ");
const char* cp = classes;
for (;;) {
char* cm = (char*) strchr(cp, ',');
if (cm) {
*cm = '\0';
}
PL_HashTableAdd(gTypesToLog, nsCRT::strdup(cp), (void*)1);
fprintf(stdout, "%s ", cp);
if (!cm) break;
*cm = ',';
cp = cm + 1;
}
fprintf(stdout, "\n");
}
gSerialNumbers = PL_NewHashTable(256,
HashNumber,
PL_CompareValues,
PL_CompareValues,
&serialNumberHashAllocOps, NULL);
}
const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS");
if (objects) {
gObjectsToLog = PL_NewHashTable(256,
HashNumber,
PL_CompareValues,
PL_CompareValues,
NULL, NULL);
if (!gObjectsToLog) {
NS_WARNING("out of memory");
fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- unable to log specific objects\n");
}
else if (! (gRefcntsLog || gAllocLog || gCOMPtrLog)) {
fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n");
}
else {
fprintf(stdout, "### XPCOM_MEM_LOG_OBJECTS defined -- only logging these objects: ");
const char* cp = objects;
for (;;) {
char* cm = (char*) strchr(cp, ',');
if (cm) {
*cm = '\0';
}
PRInt32 top = 0;
PRInt32 bottom = 0;
while (*cp) {
if (*cp == '-') {
bottom = top;
top = 0;
++cp;
}
top *= 10;
top += *cp - '0';
++cp;
}
if (!bottom) {
bottom = top;
}
for(PRInt32 serialno = bottom; serialno <= top; serialno++) {
PL_HashTableAdd(gObjectsToLog, (const void*)serialno, (void*)1);
fprintf(stdout, "%d ", serialno);
}
if (!cm) break;
*cm = ',';
cp = cm + 1;
}
fprintf(stdout, "\n");
}
}
if (gBloatLog || gRefcntsLog || gAllocLog || gLeakyLog || gCOMPtrLog) {
gLogging = PR_TRUE;
}
gTraceLock = PR_NewLock();
}
#endif
#if defined(_WIN32) && defined(_M_IX86) // WIN32 x86 stack walking code
#include "nsStackFrameWin.h"
void
nsTraceRefcntImpl::WalkTheStack(FILE* aStream)
{
DumpStackToFile(aStream);
}
// WIN32 x86 stack walking code
// i386 or PPC Linux stackwalking code or Solaris
#elif (defined(linux) && defined(__GLIBC__) && (defined(__i386) || defined(PPC))) || (defined(__sun) && (defined(__sparc) || defined(sparc) || defined(__i386) || defined(i386)))
#include "nsStackFrameUnix.h"
void
nsTraceRefcntImpl::WalkTheStack(FILE* aStream)
{
DumpStackToFile(aStream);
}
#elif defined(XP_MAC)
/**
* Stack walking code for the Mac OS.
*/
#include "gc_fragments.h"
#include <typeinfo>
extern "C" {
void MWUnmangle(const char *mangled_name, char *unmangled_name, size_t buffersize);
}
struct traceback_table {
long zero;
long magic;
long reserved;
long codeSize;
short nameLength;
char name[2];
};
static char* pc2name(long* pc, char name[], long size)
{
name[0] = '\0';
// make sure pc is instruction aligned (at least).
if (UInt32(pc) == (UInt32(pc) & 0xFFFFFFFC)) {
long instructionsToLook = 4096;
long* instruction = (long*)pc;
// look for the traceback table.
while (instructionsToLook--) {
if (instruction[0] == 0x4E800020 && instruction[1] == 0x00000000) {
traceback_table* tb = (traceback_table*)&instruction[1];
memcpy(name, tb->name + 1, --nameLength);
name[nameLength] = '\0';
break;
}
++instruction;
}
}
return name;
}
struct stack_frame {
stack_frame* next; // savedSP
void* savedCR;
void* savedLR;
void* reserved0;
void* reserved1;
void* savedTOC;
};
static asm stack_frame* getStackFrame()
{
mr r3, sp
blr
}
NS_COM void
nsTraceRefcntImpl::WalkTheStack(FILE* aStream)
{
stack_frame* currentFrame = getStackFrame(); // WalkTheStack's frame.
currentFrame = currentFrame->next; // WalkTheStack's caller's frame.
currentFrame = currentFrame->next; // WalkTheStack's caller's caller's frame.
while (true) {
// LR saved at 8(SP) in each frame. subtract 4 to get address of calling instruction.
void* pc = currentFrame->savedLR;
// convert PC to name, unmangle it, and generate source location, if possible.
static char symbol_name[1024], unmangled_name[1024], file_name[256]; UInt32 file_offset;
if (GC_address_to_source((char*)pc, symbol_name, file_name, &file_offset)) {
MWUnmangle(symbol_name, unmangled_name, sizeof(unmangled_name));
fprintf(aStream, "%s[%s,%ld]\n", unmangled_name, file_name, file_offset);
} else {
pc2name((long*)pc, symbol_name, sizeof(symbol_name));
MWUnmangle(symbol_name, unmangled_name, sizeof(unmangled_name));
fprintf(aStream, "%s(0x%08X)\n", unmangled_name, pc);
}
currentFrame = currentFrame->next;
// the bottom-most frame is marked as pointing to NULL, or is ODD if a 68K transition frame.
if (currentFrame == NULL || UInt32(currentFrame) & 0x1)
break;
}
}
#else // unsupported platform.
void
nsTraceRefcntImpl::WalkTheStack(FILE* aStream)
{
fprintf(aStream, "write me, dammit!\n");
}
#endif
//----------------------------------------------------------------------
// This thing is exported by libstdc++
// Yes, this is a gcc only hack
#if defined(MOZ_DEMANGLE_SYMBOLS)
#include <cxxabi.h>
#include <stdlib.h> // for free()
#endif // MOZ_DEMANGLE_SYMBOLS
NS_COM void
nsTraceRefcntImpl::DemangleSymbol(const char * aSymbol,
char * aBuffer,
int aBufLen)
{
NS_ASSERTION(nsnull != aSymbol,"null symbol");
NS_ASSERTION(nsnull != aBuffer,"null buffer");
NS_ASSERTION(aBufLen >= 32 ,"pulled 32 out of you know where");
aBuffer[0] = '\0';
#if defined(MOZ_DEMANGLE_SYMBOLS)
/* See demangle.h in the gcc source for the voodoo */
char * demangled = abi::__cxa_demangle(aSymbol,0,0,0);
if (demangled)
{
strncpy(aBuffer,demangled,aBufLen);
free(demangled);
}
#endif // MOZ_DEMANGLE_SYMBOLS
}
//----------------------------------------------------------------------
NS_COM void
nsTraceRefcntImpl::LoadLibrarySymbols(const char* aLibraryName,
void* aLibrayHandle)
{
#ifdef NS_BUILD_REFCNT_LOGGING
#if defined(_WIN32) && defined(_M_IX86) /* Win32 x86 only */
if (!gInitialized)
InitTraceLog();
if (gAllocLog || gRefcntsLog) {
fprintf(stdout, "### Loading symbols for %s\n", aLibraryName);
fflush(stdout);
HANDLE myProcess = ::GetCurrentProcess();
BOOL ok = EnsureSymInitialized();
if (ok) {
const char* baseName = aLibraryName;
// just get the base name of the library if a full path was given:
PRInt32 len = strlen(aLibraryName);
for (PRInt32 i = len - 1; i >= 0; i--) {
if (aLibraryName[i] == '\\') {
baseName = &aLibraryName[i + 1];
break;
}
}
DWORD baseAddr = _SymLoadModule(myProcess,
NULL,
(char*)baseName,
(char*)baseName,
0,
0);
ok = (baseAddr != nsnull);
}
if (!ok) {
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);
fprintf(stdout, "### ERROR: LoadLibrarySymbols for %s: %s\n",
aLibraryName, lpMsgBuf);
fflush(stdout);
LocalFree( lpMsgBuf );
}
}
#endif
#endif
}
//----------------------------------------------------------------------
// don't use the logging ones. :-)
NS_IMETHODIMP_(nsrefcnt) nsTraceRefcntImpl::AddRef(void)
{
NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "illegal refcnt");
++mRefCnt;
return mRefCnt;
}
NS_IMETHODIMP_(nsrefcnt) nsTraceRefcntImpl::Release(void)
{
NS_PRECONDITION(0 != mRefCnt, "dup release");
--mRefCnt;
if (mRefCnt == 0) {
mRefCnt = 1; /* stabilize */
delete this;
return 0;
}
return mRefCnt;
}
NS_IMPL_QUERY_INTERFACE1(nsTraceRefcntImpl, nsITraceRefcnt)
nsTraceRefcntImpl::nsTraceRefcntImpl()
{
/* member initializers and constructor code */
}
NS_IMETHODIMP
nsTraceRefcntImpl::LogAddRef(void* aPtr,
nsrefcnt aRefcnt,
const char* aClazz,
PRUint32 classSize)
{
#ifdef NS_BUILD_REFCNT_LOGGING
ASSERT_ACTIVITY_IS_LEGAL;
if (!gInitialized)
InitTraceLog();
if (gLogging) {
LOCK_TRACELOG();
if (gBloatLog) {
BloatEntry* entry = GetBloatEntry(aClazz, classSize);
if (entry) {
entry->AddRef(aRefcnt);
}
}
// Here's the case where neither NS_NEWXPCOM nor MOZ_COUNT_CTOR were used,
// yet we still want to see creation information:
PRBool loggingThisType = (!gTypesToLog || LogThisType(aClazz));
PRInt32 serialno = 0;
if (gSerialNumbers && loggingThisType) {
serialno = GetSerialNumber(aPtr, aRefcnt == 1);
PRInt32* count = GetRefCount(aPtr);
if(count)
(*count)++;
}
PRBool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
if (aRefcnt == 1 && gAllocLog && loggingThisType && loggingThisObject) {
fprintf(gAllocLog, "\n<%s> 0x%08X %d Create\n",
aClazz, NS_PTR_TO_INT32(aPtr), serialno);
WalkTheStack(gAllocLog);
}
if (gRefcntsLog && loggingThisType && loggingThisObject) {
if (gLogToLeaky) {
(*leakyLogAddRef)(aPtr, aRefcnt - 1, aRefcnt);
}
else {
// Can't use PR_LOG(), b/c it truncates the line
fprintf(gRefcntsLog,
"\n<%s> 0x%08X %d AddRef %d\n", aClazz, NS_PTR_TO_INT32(aPtr), serialno, aRefcnt);
WalkTheStack(gRefcntsLog);
fflush(gRefcntsLog);
}
}
UNLOCK_TRACELOG();
}
#endif
return NS_OK;
}
NS_IMETHODIMP
nsTraceRefcntImpl::LogRelease(void* aPtr,
nsrefcnt aRefcnt,
const char* aClazz)
{
#ifdef NS_BUILD_REFCNT_LOGGING
ASSERT_ACTIVITY_IS_LEGAL;
if (!gInitialized)
InitTraceLog();
if (gLogging) {
LOCK_TRACELOG();
if (gBloatLog) {
BloatEntry* entry = GetBloatEntry(aClazz, 0);
if (entry) {
entry->Release(aRefcnt);
}
}
PRBool loggingThisType = (!gTypesToLog || LogThisType(aClazz));
PRInt32 serialno = 0;
if (gSerialNumbers && loggingThisType) {
serialno = GetSerialNumber(aPtr, PR_FALSE);
PRInt32* count = GetRefCount(aPtr);
if(count)
(*count)--;
}
PRBool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
if (gRefcntsLog && loggingThisType && loggingThisObject) {
if (gLogToLeaky) {
(*leakyLogRelease)(aPtr, aRefcnt + 1, aRefcnt);
}
else {
// Can't use PR_LOG(), b/c it truncates the line
fprintf(gRefcntsLog,
"\n<%s> 0x%08X %d Release %d\n", aClazz, NS_PTR_TO_INT32(aPtr), serialno, aRefcnt);
WalkTheStack(gRefcntsLog);
fflush(gRefcntsLog);
}
}
// Here's the case where neither NS_DELETEXPCOM nor MOZ_COUNT_DTOR were used,
// yet we still want to see deletion information:
if (aRefcnt == 0 && gAllocLog && loggingThisType && loggingThisObject) {
fprintf(gAllocLog,
"\n<%s> 0x%08X %d Destroy\n",
aClazz, NS_PTR_TO_INT32(aPtr), serialno);
WalkTheStack(gAllocLog);
}
if (aRefcnt == 0 && gSerialNumbers && loggingThisType) {
RecycleSerialNumberPtr(aPtr);
}
UNLOCK_TRACELOG();
}
#endif
return NS_OK;
}
NS_IMETHODIMP
nsTraceRefcntImpl::LogCtor(void* aPtr,
const char* aType,
PRUint32 aInstanceSize)
{
#ifdef NS_BUILD_REFCNT_LOGGING
ASSERT_ACTIVITY_IS_LEGAL;
if (!gInitialized)
InitTraceLog();
if (gLogging) {
LOCK_TRACELOG();
if (gBloatLog) {
BloatEntry* entry = GetBloatEntry(aType, aInstanceSize);
if (entry) {
entry->Ctor();
}
}
PRBool loggingThisType = (!gTypesToLog || LogThisType(aType));
PRInt32 serialno = 0;
if (gSerialNumbers && loggingThisType) {
serialno = GetSerialNumber(aPtr, PR_TRUE);
}
PRBool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
if (gAllocLog && loggingThisType && loggingThisObject) {
fprintf(gAllocLog, "\n<%s> 0x%08X %d Ctor (%d)\n",
aType, NS_PTR_TO_INT32(aPtr), serialno, aInstanceSize);
WalkTheStack(gAllocLog);
}
UNLOCK_TRACELOG();
}
#endif
return NS_OK;
}
NS_IMETHODIMP
nsTraceRefcntImpl::LogDtor(void* aPtr,
const char* aType,
PRUint32 aInstanceSize)
{
#ifdef NS_BUILD_REFCNT_LOGGING
ASSERT_ACTIVITY_IS_LEGAL;
if (!gInitialized)
InitTraceLog();
if (gLogging) {
LOCK_TRACELOG();
if (gBloatLog) {
BloatEntry* entry = GetBloatEntry(aType, aInstanceSize);
if (entry) {
entry->Dtor();
}
}
PRBool loggingThisType = (!gTypesToLog || LogThisType(aType));
PRInt32 serialno = 0;
if (gSerialNumbers && loggingThisType) {
serialno = GetSerialNumber(aPtr, PR_FALSE);
RecycleSerialNumberPtr(aPtr);
}
PRBool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
// (If we're on a losing architecture, don't do this because we'll be
// using LogDeleteXPCOM instead to get file and line numbers.)
if (gAllocLog && loggingThisType && loggingThisObject) {
fprintf(gAllocLog, "\n<%s> 0x%08X %d Dtor (%d)\n",
aType, NS_PTR_TO_INT32(aPtr), serialno, aInstanceSize);
WalkTheStack(gAllocLog);
}
UNLOCK_TRACELOG();
}
#endif
return NS_OK;
}
NS_IMETHODIMP
nsTraceRefcntImpl::LogAddCOMPtr(void* aCOMPtr,
nsISupports* aObject)
{
#if defined(NS_BUILD_REFCNT_LOGGING) && defined(HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR)
// Get the most-derived object.
void *object = dynamic_cast<void *>(aObject);
// This is a very indirect way of finding out what the class is
// of the object being logged. If we're logging a specific type,
// then
if (!gTypesToLog || !gSerialNumbers) {
return NS_OK;
}
PRInt32 serialno = GetSerialNumber(object, PR_FALSE);
if (serialno == 0) {
return NS_OK;
}
if (!gInitialized)
InitTraceLog();
if (gLogging) {
LOCK_TRACELOG();
PRInt32* count = GetCOMPtrCount(object);
if(count)
(*count)++;
PRBool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
if (gCOMPtrLog && loggingThisObject) {
fprintf(gCOMPtrLog, "\n<?> 0x%08X %d nsCOMPtrAddRef %d 0x%08X\n",
NS_PTR_TO_INT32(object), serialno, count?(*count):-1, NS_PTR_TO_INT32(aCOMPtr));
WalkTheStack(gCOMPtrLog);
}
UNLOCK_TRACELOG();
}
#endif
return NS_OK;
}
NS_IMETHODIMP
nsTraceRefcntImpl::LogReleaseCOMPtr(void* aCOMPtr,
nsISupports* aObject)
{
#if defined(NS_BUILD_REFCNT_LOGGING) && defined(HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR)
// Get the most-derived object.
void *object = dynamic_cast<void *>(aObject);
// This is a very indirect way of finding out what the class is
// of the object being logged. If we're logging a specific type,
// then
if (!gTypesToLog || !gSerialNumbers) {
return NS_OK;
}
PRInt32 serialno = GetSerialNumber(object, PR_FALSE);
if (serialno == 0) {
return NS_OK;
}
if (!gInitialized)
InitTraceLog();
if (gLogging) {
LOCK_TRACELOG();
PRInt32* count = GetCOMPtrCount(object);
if(count)
(*count)--;
PRBool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno));
if (gCOMPtrLog && loggingThisObject) {
fprintf(gCOMPtrLog, "\n<?> 0x%08X %d nsCOMPtrRelease %d 0x%08X\n",
NS_PTR_TO_INT32(object), serialno, count?(*count):-1, NS_PTR_TO_INT32(aCOMPtr));
WalkTheStack(gCOMPtrLog);
}
UNLOCK_TRACELOG();
}
#endif
return NS_OK;
}
NS_COM void
nsTraceRefcntImpl::Startup()
{
#ifdef NS_BUILD_REFCNT_LOGGING
SetActivityIsLegal(PR_TRUE);
#endif
}
NS_COM void
nsTraceRefcntImpl::Shutdown()
{
#ifdef NS_BUILD_REFCNT_LOGGING
if (gBloatView) {
PL_HashTableDestroy(gBloatView);
gBloatView = nsnull;
}
if (gTypesToLog) {
PL_HashTableDestroy(gTypesToLog);
gTypesToLog = nsnull;
}
if (gObjectsToLog) {
PL_HashTableDestroy(gObjectsToLog);
gObjectsToLog = nsnull;
}
if (gSerialNumbers) {
PL_HashTableDestroy(gSerialNumbers);
gSerialNumbers = nsnull;
}
SetActivityIsLegal(PR_FALSE);
#endif
}
NS_COM void
nsTraceRefcntImpl::SetActivityIsLegal(PRBool aLegal)
{
#ifdef NS_BUILD_REFCNT_LOGGING
gActivityIsLegal = aLegal;
#endif
}
NS_METHOD
nsTraceRefcntImpl::Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr)
{
*aInstancePtr = nsnull;
nsITraceRefcnt* tracer = new nsTraceRefcntImpl();
if (!tracer)
return NS_ERROR_OUT_OF_MEMORY;
nsresult rv = tracer->QueryInterface(aIID, aInstancePtr);
if (NS_FAILED(rv)) {
delete tracer;
}
return rv;
}