Bug 739681 - Allow DumpHeapComplete to print unreachable objects (r=mccr8 a=leo+)
authorBill McCloskey <wmccloskey@mozilla.com>
Fri, 02 Nov 2012 12:24:19 -0700
changeset 119829 a2a9b89ef5ee008db097e57f24e401e1706b2746
parent 119828 ba71c64705e5dd14f90b4b7e133409213acd98ff
child 119830 43a00244c61e09f634393796ef9cbc9233396637
push id1016
push useramccreight@mozilla.com
push dateSun, 04 Aug 2013 16:59:25 +0000
reviewersmccr8, leo
bugs739681
milestone18.1
Bug 739681 - Allow DumpHeapComplete to print unreachable objects (r=mccr8 a=leo+)
js/src/builtin/TestingFunctions.cpp
js/src/jsfriendapi.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -682,16 +682,50 @@ MakeFinalizeObserver(JSContext *cx, unsi
 
 static JSBool
 FinalizeCount(JSContext *cx, unsigned argc, jsval *vp)
 {
     *vp = INT_TO_JSVAL(finalizeCount);
     return true;
 }
 
+static JSBool
+DumpHeapComplete(JSContext *cx, unsigned argc, jsval *vp)
+{
+    const char *fileName = NULL;
+    JSAutoByteString fileNameBytes;
+    if (argc > 0) {
+        Value v = JS_ARGV(cx, vp)[0];
+        if (v.isString()) {
+            JSString *str = v.toString();
+            if (!fileNameBytes.encode(cx, str))
+                return false;
+            fileName = fileNameBytes.ptr();
+        }
+    }
+
+    FILE *dumpFile;
+    if (!fileName) {
+        dumpFile = stdout;
+    } else {
+        dumpFile = fopen(fileName, "w");
+        if (!dumpFile) {
+            JS_ReportError(cx, "can't open %s", fileName);
+            return false;
+        }
+    }
+
+    js::DumpHeapComplete(JS_GetRuntime(cx), dumpFile);
+
+    fclose(dumpFile);
+
+    JS_SET_RVAL(cx, vp, JSVAL_VOID);
+    return true;
+}
+
 JSBool
 MJitChunkLimit(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (argc != 1) {
         RootedObject callee(cx, &args.callee());
         ReportUsageError(cx, callee, "Wrong number of arguments");
@@ -855,16 +889,20 @@ static JSFunctionSpecWithHelp TestingFun
 "internalConst(name)",
 "  Query an internal constant for the engine. See InternalConst source for\n"
 "  the list of constant names."),
 
     JS_FN_HELP("isProxy", IsProxy, 1, 0,
 "isProxy(obj)",
 "  If true, obj is a proxy of some sort"),
 
+    JS_FN_HELP("dumpHeapComplete", DumpHeapComplete, 1, 0,
+"dumpHeapComplete([filename])",
+"  Dump reachable and unreachable objects to a file."),
+
     JS_FN_HELP("mjitChunkLimit", MJitChunkLimit, 1, 0,
 "mjitChunkLimit(N)",
 "  Specify limit on compiled chunk size during mjit compilation."),
 
     JS_FN_HELP("terminate", Terminate, 0, 0,
 "terminate()",
 "  Terminate JavaScript execution, as if we had run out of\n"
 "  memory or been terminated by the slow script dialog."),
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -604,112 +604,102 @@ js_DumpChars(const jschar *s, size_t n)
 JS_FRIEND_API(void)
 js_DumpObject(JSObject *obj)
 {
     obj->dump();
 }
 
 #endif
 
-struct DumpingChildInfo {
-    void *node;
-    JSGCTraceKind kind;
-
-    DumpingChildInfo (void *n, JSGCTraceKind k)
-        : node(n), kind(k)
-    {}
-};
-
-typedef HashSet<void *, DefaultHasher<void *>, SystemAllocPolicy> PtrSet;
-
-struct JSDumpHeapTracer : public JSTracer {
-    PtrSet visited;
+struct JSDumpHeapTracer : public JSTracer
+{
     FILE   *output;
-    Vector<DumpingChildInfo, 0, SystemAllocPolicy> nodes;
-    char   buffer[1024 * 32];
-    bool   rootTracing;
 
     JSDumpHeapTracer(FILE *fp)
       : output(fp)
     {}
 };
 
-static void
-DumpHeapVisitChild(JSTracer *trc, void **thingp, JSGCTraceKind kind);
-
 static char
 MarkDescriptor(void *thing)
 {
     gc::Cell *cell = static_cast<gc::Cell*>(thing);
     if (cell->isMarked(gc::BLACK))
         return cell->isMarked(gc::GRAY) ? 'G' : 'B';
     else
         return cell->isMarked(gc::GRAY) ? 'X' : 'W';
 }
 
 static void
-DumpHeapPushIfNew(JSTracer *trc, void **thingp, JSGCTraceKind kind)
+DumpHeapVisitCompartment(JSRuntime *rt, void *data, JSCompartment *comp)
 {
-    JS_ASSERT(trc->callback == DumpHeapPushIfNew ||
-              trc->callback == DumpHeapVisitChild);
-    void *thing = *thingp;
-    JSDumpHeapTracer *dtrc = static_cast<JSDumpHeapTracer *>(trc);
+    char name[1024];
+    if (rt->compartmentNameCallback)
+        (*rt->compartmentNameCallback)(rt, comp, name, sizeof(name));
+    else
+        strcpy(name, "<unknown>");
+
+    JSDumpHeapTracer *dtrc = static_cast<JSDumpHeapTracer *>(data);
+    fprintf(dtrc->output, "# compartment %s\n", name);
+}
 
-    /*
-     * If we're tracing roots, print root information.  Do this even if we've
-     * already seen thing, for complete root information.
-     */
-    if (dtrc->rootTracing) {
-        fprintf(dtrc->output, "%p %c %s\n", thing, MarkDescriptor(thing),
-                JS_GetTraceEdgeName(dtrc, dtrc->buffer, sizeof(dtrc->buffer)));
-    }
+static void
+DumpHeapVisitArena(JSRuntime *rt, void *data, gc::Arena *arena,
+                   JSGCTraceKind traceKind, size_t thingSize)
+{
+    JSDumpHeapTracer *dtrc = static_cast<JSDumpHeapTracer *>(data);
+    fprintf(dtrc->output, "# arena allockind=%u size=%u\n",
+            unsigned(arena->aheader.getAllocKind()), unsigned(thingSize));
+}
 
-    PtrSet::AddPtr ptrEntry = dtrc->visited.lookupForAdd(thing);
-    if (ptrEntry || !dtrc->visited.add(ptrEntry, thing))
-        return;
-
-    dtrc->nodes.append(DumpingChildInfo(thing, kind));
+static void
+DumpHeapVisitCell(JSRuntime *rt, void *data, void *thing,
+                  JSGCTraceKind traceKind, size_t thingSize)
+{
+    JSDumpHeapTracer *dtrc = static_cast<JSDumpHeapTracer *>(data);
+    char cellDesc[1024];
+    JS_GetTraceThingInfo(cellDesc, sizeof(cellDesc), dtrc, thing, traceKind, true);
+    fprintf(dtrc->output, "%p %c %s\n", thing, MarkDescriptor(thing), cellDesc);
+    JS_TraceChildren(dtrc, thing, traceKind);
 }
 
 static void
 DumpHeapVisitChild(JSTracer *trc, void **thingp, JSGCTraceKind kind)
 {
-    JS_ASSERT(trc->callback == DumpHeapVisitChild);
     JSDumpHeapTracer *dtrc = static_cast<JSDumpHeapTracer *>(trc);
-    const char *edgeName = JS_GetTraceEdgeName(dtrc, dtrc->buffer, sizeof(dtrc->buffer));
-    fprintf(dtrc->output, "> %p %c %s\n", *thingp, MarkDescriptor(*thingp), edgeName);
-    DumpHeapPushIfNew(dtrc, thingp, kind);
+    char buffer[1024];
+    fprintf(dtrc->output, "> %p %c %s\n", *thingp, MarkDescriptor(*thingp),
+            JS_GetTraceEdgeName(dtrc, buffer, sizeof(buffer)));
+}
+
+static void
+DumpHeapVisitRoot(JSTracer *trc, void **thingp, JSGCTraceKind kind)
+{
+    JSDumpHeapTracer *dtrc = static_cast<JSDumpHeapTracer *>(trc);
+    char buffer[1024];
+    fprintf(dtrc->output, "%p %c %s\n", *thingp, MarkDescriptor(*thingp),
+            JS_GetTraceEdgeName(dtrc, buffer, sizeof(buffer)));
 }
 
 void
 js::DumpHeapComplete(JSRuntime *rt, FILE *fp)
 {
     JSDumpHeapTracer dtrc(fp);
-    JS_TracerInit(&dtrc, rt, DumpHeapPushIfNew);
-    if (!dtrc.visited.init(10000))
-        return;
 
-    /* Store and log the root information. */
-    dtrc.rootTracing = true;
+    JS_TracerInit(&dtrc, rt, DumpHeapVisitRoot);
     TraceRuntime(&dtrc);
+
     fprintf(dtrc.output, "==========\n");
 
-    /* Log the graph. */
-    dtrc.rootTracing = false;
-    dtrc.callback = DumpHeapVisitChild;
+    JS_TracerInit(&dtrc, rt, DumpHeapVisitChild);
+    IterateCompartmentsArenasCells(rt, &dtrc,
+                                   DumpHeapVisitCompartment,
+                                   DumpHeapVisitArena,
+                                   DumpHeapVisitCell);
 
-    while (!dtrc.nodes.empty()) {
-        DumpingChildInfo dci = dtrc.nodes.popCopy();
-        JS_GetTraceThingInfo(dtrc.buffer, sizeof(dtrc.buffer),
-                             &dtrc, dci.node, dci.kind, JS_TRUE);
-        fprintf(fp, "%p %c %s\n", dci.node, MarkDescriptor(dci.node), dtrc.buffer);
-        JS_TraceChildren(&dtrc, dci.node, dci.kind);
-    }
-
-    dtrc.visited.finish();
     fflush(dtrc.output);
 }
 
 namespace js {
 
 JS_FRIEND_API(const JSStructuredCloneCallbacks *)
 GetContextStructuredCloneCallbacks(JSContext *cx)
 {