Bug 1134865 - Part 2: Add constructor name to the allocations log; r=djvj
authorNick Fitzgerald <fitzgen@gmail.com>
Mon, 27 Apr 2015 10:57:06 -0700
changeset 271171 626f0dd608b0a52e58e9fa9a655db2819f0c51a9
parent 271170 50dedbefd46e856541d39560d1c68725157621f6
child 271172 ada25868ffa642d6a5b0e4c0db2e1f769656e09f
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdjvj
bugs1134865
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1134865 - Part 2: Add constructor name to the allocations log; r=djvj
js/src/doc/Debugger/Debugger.Memory.md
js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/DebuggerMemory.cpp
--- a/js/src/doc/Debugger/Debugger.Memory.md
+++ b/js/src/doc/Debugger/Debugger.Memory.md
@@ -194,25 +194,36 @@ Function Properties of the `Debugger.Mem
     method effectively clears the log.
 
     Objects in the array are of the form:
 
     <pre class='language-js'><code>
     {
       "timestamp": <i>timestamp</i>,
       "frame": <i>allocationSite</i>,
-      "class": <i>className</i>
+      "class": <i>className</i>,
+      "constructor": <i>constructorName</i>
     }
     </code></pre>
 
-    Here <i>timestamp</i> is the [timestamp][timestamps] of the allocation
-    event, <i>allocationSite</i> is an allocation site (as a
-    [captured stack][saved-frame]), and <i>className</i> is the string name of
-    the allocated object's internal `[[Class]]` property.  <i>allocationSite</i>
-    is `null` for objects allocated with no JavaScript frames on the stack.
+    Where
+
+    * *timestamp* is the [timestamp][timestamps] of the allocation event.
+
+    * *allocationSite* is an allocation site (as a
+      [captured stack][saved-frame]). Note that this property can be null if the
+      object was allocated with no JavaScript frames on the stack.
+
+    * *className* is the string name of the allocated object's internal
+    `[[Class]]` property, for example "Array", "Date", "RegExp", or (most
+    commonly) "Object".
+
+    * *constructorName* is the constructor function's display name for objects
+      created by `new Ctor`. If that data is not available, or the object was
+      not created with a `new` expression, this property is `null`.
 
     When `trackingAllocationSites` is `false`, `drainAllocationsLog()` throws an
     `Error`.
 
 <code id='take-census'>takeCensus()</code>
 :   Carry out a census of the debuggee compartments' contents. A *census* is a
     complete traversal of the graph of all reachable memory items belonging to a
     particular `Debugger`'s debuggees. The census produces a count of those
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Memory-drainAllocationsLog-16.js
@@ -0,0 +1,47 @@
+// Test drainAllocationsLog() and constructor names.
+
+const root = newGlobal();
+const dbg = new Debugger();
+const wrappedRoot = dbg.addDebuggee(root);
+
+root.eval(
+  `
+  function Ctor() {}
+
+  var nested = {};
+  nested.Ctor = function () {};
+
+  function makeInstance() {
+    let LexicalCtor = function () {};
+    return new LexicalCtor;
+  }
+
+  function makeObject() {
+    let object = {};
+    return object;
+  }
+
+  this.tests = [
+    { name: "Ctor",                     fn: () => new Ctor             },
+    { name: "nested.Ctor",              fn: () => new nested.Ctor      },
+    { name: "makeInstance/LexicalCtor", fn: () => makeInstance()       },
+    { name: null,                       fn: () => ({})                 },
+    { name: null,                       fn: () => (nested.object = {}) },
+    { name: null,                       fn: () => makeObject()         },
+  ];
+  `
+);
+
+for (let { name, fn } of root.tests) {
+  print(name);
+
+  dbg.memory.trackingAllocationSites = true;
+
+  fn();
+
+  let entries = dbg.memory.drainAllocationsLog();
+  let ctors = entries.map(e => e.constructor);
+  assertEq(ctors.some(ctor => ctor === name), true);
+
+  dbg.memory.trackingAllocationSites = false;
+}
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1650,26 +1650,48 @@ Debugger::slowPathOnLogAllocationSite(JS
 
 bool
 Debugger::isDebuggee(const JSCompartment* compartment) const
 {
     MOZ_ASSERT(compartment);
     return compartment->isDebuggee() && debuggees.has(compartment->maybeGlobal());
 }
 
+/* static */ Debugger::AllocationSite*
+Debugger::AllocationSite::create(JSContext* cx, HandleObject frame, int64_t when, HandleObject obj)
+{
+    assertSameCompartment(cx, frame);
+
+    RootedAtom ctorName(cx);
+    {
+        AutoCompartment ac(cx, obj);
+        if (!obj->constructorDisplayAtom(cx, &ctorName))
+            return nullptr;
+    }
+
+    AllocationSite* allocSite = cx->new_<AllocationSite>(frame, when);
+    if (!allocSite)
+        return nullptr;
+
+    allocSite->className = obj->getClass()->name;
+    allocSite->ctorName = ctorName.get();
+    return allocSite;
+}
+
+
 bool
 Debugger::appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
                                int64_t when)
 {
     AutoCompartment ac(cx, object);
     RootedObject wrappedFrame(cx, frame);
     if (!cx->compartment()->wrap(cx, &wrappedFrame))
         return false;
 
-    AllocationSite* allocSite = cx->new_<AllocationSite>(wrappedFrame, when, obj->getClass()->name);
+    AllocationSite* allocSite = AllocationSite::create(cx, wrappedFrame, when, obj);
     if (!allocSite)
         return false;
 
     allocationsLog.insertBack(allocSite);
 
     if (allocationsLogLength >= maxAllocationsLogLength) {
         js_delete(allocationsLog.getFirst());
         allocationsLogOverflowed = true;
@@ -2319,16 +2341,18 @@ Debugger::trace(JSTracer* trc)
     }
 
     /*
      * Mark every allocation site in our allocation log.
      */
     for (AllocationSite* s = allocationsLog.getFirst(); s; s = s->getNext()) {
         if (s->frame)
             TraceEdge(trc, &s->frame, "allocation log SavedFrame");
+        if (s->ctorName)
+            TraceEdge(trc, &s->ctorName, "allocation log constructor name");
     }
 
     /* Trace the weak map from JSScript instances to Debugger.Script objects. */
     scripts.trace(trc);
 
     /* Trace the referent ->Debugger.Source weak map */
     sources.trace(trc);
 
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -266,27 +266,32 @@ class Debugger : private mozilla::Linked
     JSCList breakpoints;                /* Circular list of all js::Breakpoints in this debugger */
 
     // The set of GC numbers for which one or more of this Debugger's observed
     // debuggees participated in.
     js::HashSet<uint64_t> observedGCs;
 
     struct AllocationSite : public mozilla::LinkedListElement<AllocationSite>
     {
-        AllocationSite(HandleObject frame, int64_t when, const char* className)
+        AllocationSite(HandleObject frame, int64_t when)
             : frame(frame),
               when(when),
-              className(className)
+              className(nullptr),
+              ctorName(nullptr)
         {
             MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());
         };
 
+        static AllocationSite* create(JSContext* cx, HandleObject frame, int64_t when,
+                                      HandleObject obj);
+
         RelocatablePtrObject frame;
         int64_t when;
         const char* className;
+        RelocatablePtrAtom ctorName;
     };
     typedef mozilla::LinkedList<AllocationSite> AllocationSiteList;
 
     bool allowUnobservedAsmJS;
     bool trackingAllocationSites;
     double allocationSamplingProbability;
     AllocationSiteList allocationsLog;
     size_t allocationsLogLength;
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -211,16 +211,22 @@ DebuggerMemory::drainAllocationsLog(JSCo
 
         RootedString className(cx, Atomize(cx, allocSite->className, strlen(allocSite->className)));
         if (!className)
             return false;
         RootedValue classNameValue(cx, StringValue(className));
         if (!DefineProperty(cx, obj, cx->names().class_, classNameValue))
             return false;
 
+        RootedValue ctorName(cx, NullValue());
+        if (allocSite->ctorName)
+            ctorName.setString(allocSite->ctorName);
+        if (!DefineProperty(cx, obj, cx->names().constructor, ctorName))
+            return false;
+
         result->setDenseElement(i, ObjectValue(*obj));
 
         // Pop the front queue entry, and delete it immediately, so that
         // the GC sees the AllocationSite's RelocatablePtr barriers run
         // atomically with the change to the graph (the queue link).
         MOZ_ALWAYS_TRUE(dbg->allocationsLog.popFirst() == allocSite);
         js_delete(allocSite);
     }