Bug 378261: Replacing GC_MARK_DEBUG by DumpHeap. r=brendan

This commit is contained in:
igor@mir2.org 2007-04-25 06:43:18 -07:00
parent 1d9fcd63fd
commit 68d7b74a96
10 changed files with 676 additions and 482 deletions

View File

@ -271,6 +271,13 @@ interface jsdIDebuggerService : nsISupports
*/
void GC();
/**
* Output dump of JS heap.
*
* @param fileName Filename to dump the heap into.
*/
void DumpHeap(in string fileName);
/**
* Clear profile data for all scripts.
*/

View File

@ -2701,30 +2701,40 @@ jsdService::EnumerateScripts (jsdIScriptEnumerator *enumerator)
return rv;
}
#ifdef GC_MARK_DEBUG
JS_BEGIN_EXTERN_C
JS_FRIEND_DATA(FILE *) js_DumpGCHeap;
JS_END_EXTERN_C
#endif
NS_IMETHODIMP
jsdService::GC (void)
{
ASSERT_VALID_CONTEXT;
JSContext *cx = JSD_GetDefaultJSContext (mCx);
#ifdef GC_MARK_DEBUG
FILE *file = fopen("jsds-roots.txt", "w");
js_DumpGCHeap = file;
#endif
JS_GC(cx);
#ifdef GC_MARK_DEBUG
if (file)
fclose (file);
js_DumpGCHeap = NULL;
#endif
return NS_OK;
}
NS_IMETHODIMP
jsdService::DumpHeap(const char* fileName)
{
ASSERT_VALID_CONTEXT;
#ifndef DEBUG
return NS_ERROR_NOT_IMPLEMENTED;
#else
nsresult rv = NS_OK;
FILE *file = fileName ? fopen(fileName, "w") : stdout;
if (!file) {
rv = NS_ERROR_FAILURE;
} else {
JSContext *cx = JSD_GetDefaultJSContext (mCx);
if (!JS_DumpHeap(cx, NULL, 0, NULL, (size_t)-1, NULL,
NS_REINTERPRET_CAST(JSPrintfFormater, &fprintf),
file)) {
rv = NS_ERROR_FAILURE;
}
if (file != stdout)
fclose(file);
}
return rv;
#endif
}
NS_IMETHODIMP
jsdService::ClearProfileData ()
{

View File

@ -1352,45 +1352,56 @@ DumpStats(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
static JSBool
DumpHeap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
jsval v = JSVAL_NULL;
char *fileName = NULL;
size_t maxRecursionLevel = (size_t)-1;
void* startThing = NULL;
uint32 startTraceKind = 0;
void *thingToFind = NULL;
size_t maxDepth = (size_t)-1;
void *thingToIgnore = NULL;
jsval *vp;
FILE *dumpFile;
JSBool ok;
JSTracer *trc;
if (argc != 0 && argv[0] != JSVAL_NULL && argv[0] != JSVAL_VOID) {
v = argv[0];
if (!JSVAL_IS_TRACEABLE(v)) {
fprintf(gErrFile,
"dumpHeap: the first argument is not null or "
"a heap-allocated thing\n");
return JS_FALSE;
}
}
if (argc > 1 && argv[1] != JSVAL_NULL && argv[1] != JSVAL_VOID) {
vp = &argv[0];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
JSString *str;
str = JS_ValueToString(cx, argv[1]);
str = JS_ValueToString(cx, *vp);
if (!str)
return JS_FALSE;
argv[1] = STRING_TO_JSVAL(str);
*vp = STRING_TO_JSVAL(str);
fileName = JS_GetStringBytes(str);
}
if (argc > 2 && argv[2] != JSVAL_NULL && argv[2] != JSVAL_VOID) {
uint32 depth;
if (!JS_ValueToECMAUint32(cx, argv[2], &depth))
return JS_FALSE;
maxRecursionLevel = depth;
vp = &argv[1];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
if (!JSVAL_IS_TRACEABLE(*vp))
goto not_traceable_arg;
startThing = JSVAL_TO_TRACEABLE(*vp);
startTraceKind = JSVAL_TRACE_KIND(*vp);
}
if (argc > 3 && argv[3] != JSVAL_NULL && argv[3] != JSVAL_VOID) {
if (JSVAL_IS_GCTHING(argv[3]))
thingToIgnore = JSVAL_TO_GCTHING(argv[3]);
vp = &argv[2];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
if (!JSVAL_IS_TRACEABLE(*vp))
goto not_traceable_arg;
thingToFind = JSVAL_TO_TRACEABLE(*vp);
}
vp = &argv[3];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
uint32 depth;
if (!JS_ValueToECMAUint32(cx, *vp, &depth))
return JS_FALSE;
maxDepth = depth;
}
vp = &argv[4];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
if (!JSVAL_IS_TRACEABLE(*vp))
goto not_traceable_arg;
thingToIgnore = JSVAL_TO_TRACEABLE(*vp);
}
if (!fileName) {
@ -1404,23 +1415,18 @@ DumpHeap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
}
}
trc = js_NewGCHeapDumper(cx, NULL, dumpFile, maxRecursionLevel,
thingToIgnore);
if (!trc) {
ok = JS_FALSE;
} else {
if (v == JSVAL_NULL) {
js_TraceRuntime(trc);
} else {
JS_TraceChildren(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v));
}
ok = js_FreeGCHeapDumper(trc);
}
ok = JS_DumpHeap(cx, startThing, startTraceKind, thingToFind,
maxDepth, thingToIgnore,
(JSPrintfFormater)fprintf, dumpFile);
if (dumpFile != stdout)
fclose(dumpFile);
return ok;
not_traceable_arg:
fprintf(gErrFile,
"dumpHeap: argument %u is not null or a heap-allocated thing\n",
(unsigned)(vp - argv));
return JS_FALSE;
}
#endif /* DEBUG */
@ -2203,7 +2209,7 @@ static JSFunctionSpec shell_functions[] = {
#ifdef DEBUG
{"dis", Disassemble, 1,0,0},
{"dissrc", DisassWithSrc, 1,0,0},
{"dumpHeap", DumpHeap, 3,0,0},
{"dumpHeap", DumpHeap, 5,0,0},
{"notes", Notes, 1,0,0},
{"tracing", Tracing, 0,0,0},
{"stats", DumpStats, 1,0,0},
@ -2247,7 +2253,8 @@ static char *shell_help_messages[] = {
#ifdef DEBUG
"dis([fun]) Disassemble functions into bytecodes",
"dissrc([fun]) Disassemble functions with source lines",
"dumpHeap([obj]) Display reachable objects",
"dumpHeap([fileName], [start], [toFind], [maxDepth], [toIgnore])\n"
" Interface to JS_DumpHeap with output sent to file",
"notes([fun]) Show source notes for functions",
"tracing([toggle]) Turn tracing on or off",
"stats([string ...]) Dump 'arena', 'atom', 'global' stats",

View File

@ -1863,6 +1863,464 @@ JS_UnlockGCThingRT(JSRuntime *rt, void *thing)
return js_UnlockGCThingRT(rt, thing);
}
JS_PUBLIC_API(void)
JS_TraceRuntime(JSTracer *trc)
{
JSBool allAtoms = trc->context->runtime->gcKeepAtoms != 0;
js_TraceRuntime(trc, allAtoms);
}
#ifdef DEBUG
#ifdef HAVE_XPCONNECT
#include "dump_xpc.h"
#endif
JS_PUBLIC_API(void)
JS_PrintTraceThingInfo(char *buf, size_t bufsize, JSTracer *trc,
void *thing, uint32 kind, JSBool details)
{
const char *name;
size_t n;
if (bufsize == 0)
return;
switch (kind) {
case JSTRACE_OBJECT:
{
JSObject *obj = (JSObject *)thing;
JSClass *clasp = STOBJ_GET_CLASS(obj);
name = clasp->name;
#ifdef HAVE_XPCONNECT
if (clasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) {
jsval privateValue = STOBJ_GET_SLOT(obj, JSSLOT_PRIVATE);
JS_ASSERT(clasp->flags & JSCLASS_HAS_PRIVATE);
if (!JSVAL_IS_VOID(privateValue)) {
void *privateThing = JSVAL_TO_PRIVATE(privateValue);
const char *xpcClassName = GetXPCObjectClassName(privateThing);
if (xpcClassName)
name = xpcClassName;
}
}
#endif
break;
}
case JSTRACE_STRING:
name = JSSTRING_IS_DEPENDENT((JSString *)thing)
? "substring"
: "string";
break;
case JSTRACE_DOUBLE:
name = "double";
break;
case JSTRACE_FUNCTION:
name = "function";
break;
case JSTRACE_ATOM:
name = "atom";
break;
#if JS_HAS_XML_SUPPORT
case JSTRACE_NAMESPACE:
name = "namespace";
break;
case JSTRACE_QNAME:
name = "qname";
break;
case JSTRACE_XML:
name = "xml";
break;
#endif
default:
JS_ASSERT(0);
return;
break;
}
n = strlen(name);
if (n > bufsize - 1)
n = bufsize - 1;
memcpy(buf, name, n + 1);
buf += n;
bufsize -= n;
if (details && bufsize > 2) {
*buf++ = ' ';
bufsize--;
switch (kind) {
case JSTRACE_OBJECT:
{
JSObject *obj = (JSObject *)thing;
jsval privateValue = STOBJ_GET_SLOT(obj, JSSLOT_PRIVATE);
void *privateThing = JSVAL_IS_VOID(privateValue)
? NULL
: JSVAL_TO_PRIVATE(privateValue);
JS_snprintf(buf, bufsize, "%p", privateThing);
break;
}
case JSTRACE_STRING:
js_PutEscapedString(buf, bufsize, (JSString *)thing, 0);
break;
case JSTRACE_DOUBLE:
JS_snprintf(buf, bufsize, "%g", *(jsdouble *)thing);
break;
case JSTRACE_FUNCTION:
{
JSFunction *fun = (JSFunction *)thing;
if (fun->atom && ATOM_IS_STRING(fun->atom))
js_PutEscapedString(buf, bufsize, ATOM_TO_STRING(fun->atom), 0);
break;
}
case JSTRACE_ATOM:
{
JSAtom *atom = (JSAtom *)thing;
if (ATOM_IS_INT(atom))
JS_snprintf(buf, bufsize, "%d", ATOM_TO_INT(atom));
else if (ATOM_IS_STRING(atom))
js_PutEscapedString(buf, bufsize, ATOM_TO_STRING(atom), 0);
else
JS_snprintf(buf, bufsize, "object");
break;
}
#if JS_HAS_XML_SUPPORT
case GCX_NAMESPACE:
{
JSXMLNamespace *ns = (JSXMLNamespace *)thing;
if (ns->prefix) {
n = js_PutEscapedString(buf, bufsize, ns->prefix, 0);
buf += n;
bufsize -= n;
}
if (bufsize > 2) {
*buf++ = ':';
bufsize--;
js_PutEscapedString(buf, bufsize, ns->uri, 0);
}
break;
}
case GCX_QNAME:
{
JSXMLQName *qn = (JSXMLQName *)thing;
if (qn->prefix) {
n = js_PutEscapedString(buf, bufsize, qn->prefix, 0);
buf += n;
bufsize -= n;
}
if (bufsize > 2) {
*buf++ = '(';
bufsize--;
n = js_PutEscapedString(buf, bufsize, qn->uri, 0);
buf += n;
bufsize -= n;
if (bufsize > 3) {
*buf++ = ')';
*buf++ = ':';
bufsize -= 2;
js_PutEscapedString(buf, bufsize, qn->localName, 0);
}
}
break;
}
case GCX_XML:
{
extern const char *js_xml_class_str[];
JSXML *xml = (JSXML *)thing;
JS_snprintf(buf, bufsize, "%s", js_xml_class_str[xml->xml_class]);
break;
}
#endif
default:
JS_ASSERT(0);
break;
}
}
buf[bufsize - 1] = '\0';
}
typedef struct JSHeapDumpNode JSHeapDumpNode;
struct JSHeapDumpNode {
void *thing;
uint32 kind;
JSHeapDumpNode *next; /* next sibling */
JSHeapDumpNode *parent; /* node with the thing that refer to thing
from this node */
char edgeName[1]; /* name of the edge from parent->thing
into thing */
};
typedef struct JSDumpingTracer {
JSTracer base;
JSDHashTable visited;
JSBool ok;
void *startThing;
void *thingToFind;
void *thingToIgnore;
JSHeapDumpNode *parentNode;
JSHeapDumpNode **lastNodep;
char buffer[200];
} JSDumpingTracer;
static void
DumpNotify(JSTracer *trc, void *thing, uint32 kind)
{
JSDumpingTracer *dtrc;
JSContext *cx;
JSDHashEntryStub *entry;
JSHeapDumpNode *node;
const char *edgeName;
size_t edgeNameSize;
JS_ASSERT(trc->callback == DumpNotify);
dtrc = (JSDumpingTracer *)trc;
if (!dtrc->ok || thing == dtrc->thingToIgnore)
return;
cx = trc->context;
/*
* Check if we have already seen thing unless it is thingToFind to include
* it to the graph each time we reach it and print all live things that
* refer to thingToFind.
*
* This does not print all possible paths leading to thingToFind since
* when a thing A refers directly or indirectly to thingToFind and A is
* present several times in the graph, we will print only the first path
* leading to A and thingToFind, other ways to reach A will be ignored.
*/
if (dtrc->thingToFind != thing) {
/*
* The startThing check allows to avoid putting startThing into the
* hash table before tracing startThing in JS_DumpHeap.
*/
if (thing == dtrc->startThing)
return;
entry = (JSDHashEntryStub *)
JS_DHashTableOperate(&dtrc->visited, thing, JS_DHASH_ADD);
if (!entry) {
JS_ReportOutOfMemory(cx);
dtrc->ok = JS_FALSE;
return;
}
if (entry->key)
return;
entry->key = thing;
}
if (dtrc->base.debugPrinter) {
dtrc->base.debugPrinter(trc, dtrc->buffer, sizeof(dtrc->buffer));
edgeName = dtrc->buffer;
} else if (dtrc->base.debugPrintIndex != (size_t)-1) {
JS_snprintf(dtrc->buffer, sizeof(dtrc->buffer), "%s[%lu]",
(const char *)dtrc->base.debugPrintArg,
dtrc->base.debugPrintIndex);
edgeName = dtrc->buffer;
} else {
edgeName = (const char*)dtrc->base.debugPrintArg;
}
edgeNameSize = strlen(edgeName) + 1;
node = (JSHeapDumpNode *)
JS_malloc(cx, offsetof(JSHeapDumpNode, edgeName) + edgeNameSize);
if (!node) {
dtrc->ok = JS_FALSE;
return;
}
node->thing = thing;
node->kind = kind;
node->next = NULL;
node->parent = dtrc->parentNode;
memcpy(node->edgeName, edgeName, edgeNameSize);
JS_ASSERT(!*dtrc->lastNodep);
*dtrc->lastNodep = node;
dtrc->lastNodep = &node->next;
}
/* Dump node and the chain that leads to thing it contains. */
static JSBool
DumpNode(JSDumpingTracer *dtrc, JSHeapDumpNode *node,
JSPrintfFormater format, void *closure)
{
JSHeapDumpNode *prev, *following;
size_t chainLimit;
JSBool ok;
enum { MAX_PARENTS_TO_PRINT = 10 };
JS_PrintTraceThingInfo(dtrc->buffer, sizeof dtrc->buffer,
&dtrc->base, node->thing, node->kind, JS_TRUE);
if (format(closure, "%p %-22s via ", node->thing, dtrc->buffer) < 0)
return JS_FALSE;
/*
* We need to print the parent chain in the reverse order. To do it in
* O(N) time where N is the chain length we first reverse the chain while
* searching for the top and then print each node while restoring the
* chain order.
*/
chainLimit = MAX_PARENTS_TO_PRINT;
prev = NULL;
for (;;) {
following = node->parent;
node->parent = prev;
prev = node;
node = following;
if (!node)
break;
if (chainLimit == 0) {
if (format(closure, "...") < 0)
return JS_FALSE;
break;
}
--chainLimit;
}
node = prev;
prev = following;
ok = JS_TRUE;
do {
/* Loop must continue even when !ok to restore the parent chain. */
if (ok) {
if (!prev) {
/* Print edge from some runtime root or startThing. */
if (format(closure, "%s", node->edgeName) < 0)
ok = JS_FALSE;
} else {
JS_PrintTraceThingInfo(dtrc->buffer, sizeof dtrc->buffer,
&dtrc->base, prev->thing, prev->kind,
JS_FALSE);
if (format(closure, "(%p %s).%s",
prev->thing, dtrc->buffer, node->edgeName) < 0) {
ok = JS_FALSE;
}
}
}
following = node->parent;
node->parent = prev;
prev = node;
node = following;
} while (node);
return ok && format(closure, "\n") >= 0;
}
JS_PUBLIC_API(JSBool)
JS_DumpHeap(JSContext *cx, void* startThing, uint32 startKind,
void *thingToFind, size_t maxDepth, void *thingToIgnore,
JSPrintfFormater format, void *closure)
{
JSDumpingTracer dtrc;
JSHeapDumpNode *node, *children, *next, *parent;
size_t depth;
JSBool thingToFindWasTraced;
if (maxDepth == 0)
return JS_TRUE;
JS_TRACER_INIT(&dtrc.base, cx, DumpNotify);
if (!JS_DHashTableInit(&dtrc.visited, JS_DHashGetStubOps(),
NULL, sizeof(JSDHashEntryStub),
JS_DHASH_DEFAULT_CAPACITY(100))) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
dtrc.ok = JS_TRUE;
dtrc.startThing = startThing;
dtrc.thingToFind = thingToFind;
dtrc.thingToIgnore = thingToIgnore;
dtrc.parentNode = NULL;
node = NULL;
dtrc.lastNodep = &node;
if (!startThing) {
JS_ASSERT(startKind == 0);
JS_TraceRuntime(&dtrc.base);
} else {
JS_TraceChildren(&dtrc.base, startThing, startKind);
}
depth = 1;
if (!node)
goto dump_out;
thingToFindWasTraced = thingToFind && thingToFind == startThing;
for (;;) {
/*
* Loop must continue even when !dtrc.ok to free all nodes allocated
* so far.
*/
if (dtrc.ok) {
if (thingToFind == NULL || thingToFind == node->thing)
dtrc.ok = DumpNode(&dtrc, node, format, closure);
/* Descend into children. */
if (dtrc.ok &&
depth < maxDepth &&
(thingToFind != node->thing || !thingToFindWasTraced)) {
dtrc.parentNode = node;
children = NULL;
dtrc.lastNodep = &children;
JS_TraceChildren(&dtrc.base, node->thing, node->kind);
if (thingToFind == node->thing)
thingToFindWasTraced = JS_TRUE;
if (children != NULL) {
++depth;
node = children;
continue;
}
}
}
/* Move to next or parents next and free the node. */
for (;;) {
next = node->next;
parent = node->parent;
JS_free(cx, node);
node = next;
if (node)
break;
if (!parent)
goto dump_out;
JS_ASSERT(depth > 1);
--depth;
node = parent;
}
}
dump_out:
JS_ASSERT(depth == 1);
JS_DHashTableFinish(&dtrc.visited);
return dtrc.ok;
}
#endif /* DEBUG */
JS_PUBLIC_API(void)
JS_MarkGCThing(JSContext *cx, void *thing, const char *name, void *arg)
{

View File

@ -910,7 +910,6 @@ struct JSTracer {
extern JS_PUBLIC_API(void)
JS_CallTracer(JSTracer *trc, void *thing, uint32 kind);
/*
* Set debugging information about a reference to a traceable thing to prepare
* for the following call to JS_CallTracer.
@ -1009,10 +1008,33 @@ JS_CallTracer(JSTracer *trc, void *thing, uint32 kind);
extern JS_PUBLIC_API(void)
JS_TraceChildren(JSTracer *trc, void *thing, uint32 kind);
extern JS_PUBLIC_API(void)
JS_TraceRuntime(JSTracer *trc);
#ifdef DEBUG
extern JS_PUBLIC_API(void)
JS_PrintTraceThingInfo(char *buf, size_t bufsize, JSTracer *trc,
void *thing, uint32 kind, JSBool includeDetails);
/*
* DEBUG-only method to dump an object graph of heap-allocated things.
*
* start: when non-null, dump only things reachable from start thing. Otherwise
* dump all things rechable from runtime roots.
* startKind: trace kind of start if start is not null. Must be 0 when start
* is null.
* thingToFind: dump only paths in the object graph leading to thingToFind
* when non-null.
* maxDepth: the upper bound on the number of edges to descend from the graph
* roots.
* thingToIgnore: thing to ignore during graph traversal when non-null.
* format: callback to format the dump output.
* closure: an argument to pass to formater.
*/
extern JS_PUBLIC_API(JSBool)
JS_DumpHeap(JSContext *cx, void* startThing, uint32 startKind,
void *thingToFind, size_t maxDepth, void *thingToIgnore,
JSPrintfFormater format, void *closure);
#endif
/*

View File

@ -449,12 +449,14 @@ js_locked_atom_tracer(JSHashEntry *he, intN i, void *arg)
atom = (JSAtom *)he;
args = (TraceArgs *)arg;
if ((atom->flags & (ATOM_PINNED | ATOM_INTERNED)) || args->allAtoms) {
JS_CALL_TRACER(args->trc, atom, JSTRACE_ATOM,
(atom->flags & ATOM_PINNED)
? "pinned_atom"
: (atom->flags & ATOM_INTERNED)
? "interned_atom"
: "locked_atom");
JS_SET_TRACING_INDEX(args->trc,
(atom->flags & ATOM_PINNED)
? "pinned_atom"
: (atom->flags & ATOM_INTERNED)
? "interned_atom"
: "locked_atom",
(size_t)i);
JS_CallTracer(args->trc, atom, JSTRACE_ATOM);
}
return HT_ENUMERATE_NEXT;
}

View File

@ -1794,378 +1794,6 @@ out:
return JS_TRUE;
}
#ifdef DEBUG
#include <stdio.h>
#include "jsprf.h"
#ifdef HAVE_XPCONNECT
#include "dump_xpc.h"
#endif
JS_PUBLIC_API(void)
JS_PrintTraceThingInfo(char *buf, size_t bufsize, JSTracer *trc,
void *thing, uint32 kind, JSBool details)
{
const char *name;
size_t n;
if (bufsize == 0)
return;
switch (kind) {
case JSTRACE_OBJECT:
{
JSObject *obj = (JSObject *)thing;
JSClass *clasp = STOBJ_GET_CLASS(obj);
name = clasp->name;
#ifdef HAVE_XPCONNECT
if (clasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) {
jsval privateValue = STOBJ_GET_SLOT(obj, JSSLOT_PRIVATE);
JS_ASSERT(clasp->flags & JSCLASS_HAS_PRIVATE);
if (!JSVAL_IS_VOID(privateValue)) {
void *privateThing = JSVAL_TO_PRIVATE(privateValue);
const char *xpcClassName = GetXPCObjectClassName(privateThing);
if (xpcClassName)
name = xpcClassName;
}
}
#endif
break;
}
case JSTRACE_STRING:
name = JSSTRING_IS_DEPENDENT((JSString *)thing)
? "substring"
: "string";
break;
case JSTRACE_DOUBLE:
name = "double";
break;
case JSTRACE_FUNCTION:
name = "function";
break;
case JSTRACE_ATOM:
name = "atom";
break;
#if JS_HAS_XML_SUPPORT
case JSTRACE_NAMESPACE:
name = "namespace";
break;
case JSTRACE_QNAME:
name = "qname";
break;
case JSTRACE_XML:
name = "xml";
break;
#endif
default:
JS_ASSERT(0);
return;
break;
}
n = strlen(name);
if (n > bufsize - 1)
n = bufsize - 1;
memcpy(buf, name, n + 1);
buf += n;
bufsize -= n;
if (details && bufsize > 2) {
*buf++ = ' ';
bufsize--;
switch (kind) {
case JSTRACE_OBJECT:
{
JSObject *obj = (JSObject *)thing;
jsval privateValue = STOBJ_GET_SLOT(obj, JSSLOT_PRIVATE);
void *privateThing = JSVAL_IS_VOID(privateValue)
? NULL
: JSVAL_TO_PRIVATE(privateValue);
JS_snprintf(buf, bufsize, "%8p", privateThing);
break;
}
case JSTRACE_STRING:
js_PutEscapedString(buf, bufsize, (JSString *)thing, 0);
break;
case JSTRACE_DOUBLE:
JS_snprintf(buf, bufsize, "%g", *(jsdouble *)thing);
break;
case JSTRACE_FUNCTION:
{
JSFunction *fun = (JSFunction *)thing;
if (fun->atom && ATOM_IS_STRING(fun->atom))
js_PutEscapedString(buf, bufsize, ATOM_TO_STRING(fun->atom), 0);
break;
}
case JSTRACE_ATOM:
{
JSAtom *atom = (JSAtom *)thing;
if (ATOM_IS_INT(atom))
JS_snprintf(buf, bufsize, "%d", ATOM_TO_INT(atom));
else if (ATOM_IS_STRING(atom))
js_PutEscapedString(buf, bufsize, ATOM_TO_STRING(atom), 0);
else
JS_snprintf(buf, bufsize, "object");
break;
}
#if JS_HAS_XML_SUPPORT
case GCX_NAMESPACE:
{
JSXMLNamespace *ns = (JSXMLNamespace *)thing;
if (ns->prefix) {
n = js_PutEscapedString(buf, bufsize, ns->prefix, 0);
buf += n;
bufsize -= n;
}
if (bufsize > 2) {
*buf++ = ':';
bufsize--;
js_PutEscapedString(buf, bufsize, ns->uri, 0);
}
break;
}
case GCX_QNAME:
{
JSXMLQName *qn = (JSXMLQName *)thing;
if (qn->prefix) {
n = js_PutEscapedString(buf, bufsize, qn->prefix, 0);
buf += n;
bufsize -= n;
}
if (bufsize > 2) {
*buf++ = '(';
bufsize--;
n = js_PutEscapedString(buf, bufsize, qn->uri, 0);
buf += n;
bufsize -= n;
if (bufsize > 3) {
*buf++ = ')';
*buf++ = ':';
bufsize -= 2;
js_PutEscapedString(buf, bufsize, qn->localName, 0);
}
}
break;
}
case GCX_XML:
{
extern const char *js_xml_class_str[];
JSXML *xml = (JSXML *)thing;
JS_snprintf(buf, bufsize, "%s", js_xml_class_str[xml->xml_class]);
break;
}
#endif
default:
JS_ASSERT(0);
break;
}
}
buf[bufsize - 1] = '\0';
}
typedef struct JSGCDumpNode JSGCDumpNode;
struct JSGCDumpNode {
void *thing;
uint32 kind;
char *name;
JSGCDumpNode *next;
};
typedef struct JSDumpingTracer
{
JSTracer base;
JSDHashTable visited;
JSBool ok;
FILE *file;
void *thingToFind;
size_t maxRecursionDepth;
void *thingToIgnore;
size_t recursionDepth;
JSGCDumpNode *top, **bottomp;
char buffer[200];
} JSDumpingTracer;
static void
DumpThing(JSDumpingTracer *dtrc, JSGCDumpNode *node)
{
FILE *fp;
fp = dtrc->file;
fprintf(fp, "%p ", node->thing);
JS_PrintTraceThingInfo(dtrc->buffer, sizeof(dtrc->buffer),
&dtrc->base, node->thing, node->kind, JS_TRUE);
fputs(dtrc->buffer, fp);
/* Print path to this node from a traversal root. */
node = dtrc->top;
if (node->next) {
fputs("\tvia ", fp);
do {
if (node != dtrc->top)
fputc('.', fp);
fprintf(fp, "%s(%p ", node->name, node->thing);
JS_PrintTraceThingInfo(dtrc->buffer, sizeof(dtrc->buffer),
&dtrc->base, node->thing, node->kind,
JS_FALSE);
fputs(dtrc->buffer, fp);
node = node->next;
} while (node->next);
}
fputc('\n', fp);
}
static void
DumpNotify(JSTracer *trc, void *thing, uint32 kind)
{
JSDumpingTracer *dtrc;
JSContext *cx;
JSDHashEntryStub *entry;
JSGCDumpNode node, **bottomp;
const char *name;
JS_ASSERT(trc->callback == DumpNotify);
dtrc = (JSDumpingTracer *)trc;
if (!dtrc->ok)
return;
if (dtrc->thingToIgnore == thing)
return;
cx = trc->context;
entry = (JSDHashEntryStub *)
JS_DHashTableOperate(&dtrc->visited, thing, JS_DHASH_ADD);
if (!entry) {
JS_ReportOutOfMemory(cx);
dtrc->ok = JS_FALSE;
return;
}
if (entry->key)
return;
entry->key = thing;
node.thing = thing;
node.kind = kind;
if (dtrc->base.debugPrinter) {
dtrc->base.debugPrinter(trc, dtrc->buffer, sizeof(dtrc->buffer));
name = dtrc->buffer;
} else if (dtrc->base.debugPrintIndex != (size_t)-1) {
JS_snprintf(dtrc->buffer, sizeof(dtrc->buffer), "%s[%lu]",
(const char *)dtrc->base.debugPrintArg,
dtrc->base.debugPrintIndex);
name = dtrc->buffer;
} else {
name = (const char*)dtrc->base.debugPrintArg;
}
node.name = JS_strdup(cx, name);
if (!node.name) {
dtrc->ok = JS_FALSE;
return;
}
node.next = NULL;
bottomp = dtrc->bottomp;
JS_ASSERT(!*bottomp);
*bottomp = &node;
dtrc->bottomp = &node.next;
/*
* Dump thingToFind each time we reach it during the tracing to print all
* live references to the thing.
*/
if (!dtrc->thingToFind || dtrc->thingToFind == thing)
DumpThing(dtrc, &node);
if (dtrc->recursionDepth < dtrc->maxRecursionDepth) {
dtrc->recursionDepth++;
JS_TraceChildren(&dtrc->base, thing, kind);
dtrc->recursionDepth--;
}
*bottomp = NULL;
dtrc->bottomp = bottomp;
JS_free(cx, node.name);
}
JS_FRIEND_API(JSTracer *)
js_NewGCHeapDumper(JSContext *cx, void *thingToFind, FILE *fp,
size_t maxRecursionDepth, void *thingToIgnore)
{
JSDumpingTracer *dtrc;
dtrc = (JSDumpingTracer *)JS_malloc(cx, sizeof(*dtrc));
if (!dtrc)
return NULL;
JS_TRACER_INIT(&dtrc->base, cx, DumpNotify);
if (!JS_DHashTableInit(&dtrc->visited, JS_DHashGetStubOps(),
NULL, sizeof(JSDHashEntryStub),
JS_DHASH_DEFAULT_CAPACITY(100))) {
JS_ReportOutOfMemory(cx);
JS_free(cx, dtrc);
return NULL;
}
dtrc->ok = JS_TRUE;
dtrc->file = fp ? fp : stderr;
dtrc->thingToFind = thingToFind;
dtrc->maxRecursionDepth = maxRecursionDepth;
dtrc->thingToIgnore = thingToIgnore;
dtrc->recursionDepth = 0;
dtrc->top = NULL;
dtrc->bottomp = &dtrc->top;
return &dtrc->base;
}
JS_FRIEND_API(JSBool)
js_FreeGCHeapDumper(JSTracer *trc)
{
JSDumpingTracer *dtrc;
JSBool ok;
JSContext *cx;
JS_ASSERT(trc->callback == DumpNotify);
dtrc = (JSDumpingTracer *)trc;
JS_ASSERT(!dtrc->top);
JS_ASSERT(dtrc->bottomp == &dtrc->top);
JS_DHashTableFinish(&dtrc->visited);
ok = dtrc->ok;
cx = dtrc->base.context;
JS_free(cx, dtrc);
return ok;
}
#endif /* DEBUG */
JS_PUBLIC_API(void)
JS_TraceChildren(JSTracer *trc, void *thing, uint32 kind)
{
@ -2689,11 +2317,11 @@ js_TraceStackFrame(JSTracer *trc, JSStackFrame *fp)
{
uintN depth, nslots;
if (fp->callobj)
JS_CALL_OBJECT_TRACER(trc, fp->callobj, "call object");
JS_CALL_OBJECT_TRACER(trc, fp->callobj, "call");
if (fp->argsobj)
JS_CALL_OBJECT_TRACER(trc, fp->argsobj, "arguments object");
JS_CALL_OBJECT_TRACER(trc, fp->argsobj, "arguments");
if (fp->varobj)
JS_CALL_OBJECT_TRACER(trc, fp->varobj, "variables object");
JS_CALL_OBJECT_TRACER(trc, fp->varobj, "variables");
if (fp->script) {
js_TraceScript(trc, fp->script);
if (fp->spbase) {
@ -2862,8 +2490,8 @@ js_TraceContext(JSTracer *trc, JSContext *acx)
js_TraceSharpMap(trc, &acx->sharpObjectMap);
}
static void
TraceRuntime(JSTracer *trc, JSBool allAtoms)
void
js_TraceRuntime(JSTracer *trc, JSBool allAtoms)
{
JSRuntime *rt = trc->context->runtime;
JSContext *iter, *acx;
@ -2884,12 +2512,6 @@ TraceRuntime(JSTracer *trc, JSBool allAtoms)
js_TraceContext(trc, acx);
}
JS_FRIEND_API(void)
js_TraceRuntime(JSTracer *trc)
{
TraceRuntime(trc, JS_FALSE);
}
/*
* When gckind is GC_LAST_DITCH, it indicates a call from js_NewGCThing with
* rt->gcLock already held and when the lock should be kept on return.
@ -3110,7 +2732,7 @@ restart:
JS_TRACER_INIT(&trc, cx, NULL);
rt->gcMarkingTracer = &trc;
JS_ASSERT(IS_GC_MARKING_TRACER(&trc));
TraceRuntime(&trc, keepAtoms);
js_TraceRuntime(&trc, keepAtoms);
js_MarkScriptFilenames(rt, keepAtoms);
/*

View File

@ -207,17 +207,6 @@ js_IsAboutToBeFinalized(JSContext *cx, void *thing);
*/
#define IS_GC_MARKING_TRACER(trc) ((trc)->callback == NULL)
#ifdef DEBUG
extern JS_FRIEND_API(JSTracer *)
js_NewGCHeapDumper(JSContext *cx, void *thingToFind, FILE *fp,
size_t maxRecursionDepth, void *thingToIgnore);
extern JS_FRIEND_API(JSBool)
js_FreeGCHeapDumper(JSTracer *trc);
#endif
JS_STATIC_ASSERT(JSTRACE_STRING == 2);
#define JSTRACE_FUNCTION 3
@ -243,8 +232,8 @@ js_CallValueTracerIfGCThing(JSTracer *trc, jsval v);
extern void
js_TraceStackFrame(JSTracer *trc, JSStackFrame *fp);
extern JS_FRIEND_API(void)
js_TraceRuntime(JSTracer *trc);
extern void
js_TraceRuntime(JSTracer *trc, JSBool allAtoms);
extern JS_FRIEND_API(void)
js_TraceContext(JSTracer *trc, JSContext *acx);

View File

@ -715,6 +715,18 @@ typedef JSBool
typedef JSPrincipals *
(* JS_DLL_CALLBACK JSObjectPrincipalsFinder)(JSContext *cx, JSObject *obj);
/*
* Output formated arguments as specified by format string. See fprintf/sprintf
* documentation for specification of format. Return the number of characters
* printed or -1 if an error occur.
*/
typedef int
(* JS_DLL_CALLBACK JSPrintfFormater)(void *closure, const char *format, ...)
#if defined __GNUC__
__attribute__ ((format (printf, 2, 3)))
#endif
;
JS_END_EXTERN_C
#endif /* jspubtd_h___ */

View File

@ -294,10 +294,6 @@ DumpXPC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
/* XXX needed only by GC() */
#include "jscntxt.h"
#ifdef GC_MARK_DEBUG
extern "C" JS_FRIEND_DATA(FILE *) js_DumpGCHeap;
#endif
JS_STATIC_DLL_CALLBACK(JSBool)
GC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
@ -306,25 +302,7 @@ GC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
rt = cx->runtime;
preBytes = rt->gcBytes;
#ifdef GC_MARK_DEBUG
if (argc && JSVAL_IS_STRING(argv[0])) {
char *name = JS_GetStringBytes(JSVAL_TO_STRING(argv[0]));
FILE *file = fopen(name, "w");
if (!file) {
fprintf(gErrFile, "gc: can't open %s: %s\n", strerror(errno));
return JS_FALSE;
}
js_DumpGCHeap = file;
} else {
js_DumpGCHeap = stdout;
}
#endif
JS_GC(cx);
#ifdef GC_MARK_DEBUG
if (js_DumpGCHeap != stdout)
fclose(js_DumpGCHeap);
js_DumpGCHeap = NULL;
#endif
fprintf(gOutFile, "before %lu, after %lu, break %08lx\n",
(unsigned long)preBytes, (unsigned long)rt->gcBytes,
#ifdef XP_UNIX
@ -339,6 +317,90 @@ GC(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
return JS_TRUE;
}
#ifdef DEBUG
static JSBool
DumpHeap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
char *fileName = NULL;
void* startThing = NULL;
uint32 startTraceKind = 0;
void *thingToFind = NULL;
size_t maxDepth = (size_t)-1;
void *thingToIgnore = NULL;
jsval *vp;
FILE *dumpFile;
JSBool ok;
vp = &argv[0];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
JSString *str;
str = JS_ValueToString(cx, *vp);
if (!str)
return JS_FALSE;
*vp = STRING_TO_JSVAL(str);
fileName = JS_GetStringBytes(str);
}
vp = &argv[1];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
if (!JSVAL_IS_TRACEABLE(*vp))
goto not_traceable_arg;
startThing = JSVAL_TO_TRACEABLE(*vp);
startTraceKind = JSVAL_TRACE_KIND(*vp);
}
vp = &argv[2];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
if (!JSVAL_IS_TRACEABLE(*vp))
goto not_traceable_arg;
thingToFind = JSVAL_TO_TRACEABLE(*vp);
}
vp = &argv[3];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
uint32 depth;
if (!JS_ValueToECMAUint32(cx, *vp, &depth))
return JS_FALSE;
maxDepth = depth;
}
vp = &argv[4];
if (*vp != JSVAL_NULL && *vp != JSVAL_VOID) {
if (!JSVAL_IS_TRACEABLE(*vp))
goto not_traceable_arg;
thingToIgnore = JSVAL_TO_TRACEABLE(*vp);
}
if (!fileName) {
dumpFile = gOutFile;
} else {
dumpFile = fopen(fileName, "w");
if (!dumpFile) {
fprintf(gErrFile, "dumpHeap: can't open %s: %s\n",
fileName, strerror(errno));
return JS_FALSE;
}
}
ok = JS_DumpHeap(cx, startThing, startTraceKind, thingToFind,
maxDepth, thingToIgnore,
(JSPrintfFormater)fprintf, dumpFile);
if (dumpFile != gOutFile)
fclose(dumpFile);
return ok;
not_traceable_arg:
fprintf(gErrFile,
"dumpHeap: argument %u is not null or a heap-allocated thing\n",
(unsigned)(vp - argv));
return JS_FALSE;
}
#endif /* DEBUG */
JS_STATIC_DLL_CALLBACK(JSBool)
Clear(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
@ -361,6 +423,9 @@ static JSFunctionSpec glob_functions[] = {
{"dump", Dump, 1,0,0},
{"gc", GC, 0,0,0},
{"clear", Clear, 1,0,0},
#ifdef DEBUG
{"dumpHeap", DumpHeap, 5,0,0},
#endif
{nsnull,nsnull,0,0,0}
};