Bug 1053932: Don't let Debugger.Memory.prototype.takeCensus's result be influenced by the hash table's ordering (ByUbinodeType version). r=terrence
authorJim Blandy <jimb@mozilla.com>
Tue, 19 Aug 2014 14:21:34 -0700
changeset 214792 a131d5ac6e339655a38c2dacc16ead9312baa6d3
parent 214791 a602365f5041783cde230d72fd27bedfacfc9701
child 214793 7d959a7a6f54bb6eb1001f8f3c50af065b429f20
push idunknown
push userunknown
push dateunknown
reviewersterrence
bugs1053932
milestone34.0a1
Bug 1053932: Don't let Debugger.Memory.prototype.takeCensus's result be influenced by the hash table's ordering (ByUbinodeType version). r=terrence
js/src/vm/DebuggerMemory.cpp
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -495,17 +495,17 @@ class ByObjectClass {
         // more interesting, and a little less non-deterministic.
         mozilla::Vector<Entry *> entries;
         if (!entries.reserve(table.count()))
             return false;
         for (typename Table::Range r = table.all(); !r.empty(); r.popFront())
             entries.infallibleAppend(&r.front());
         qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries);
 
-        // Now build the result by iterating over the vector, not the hash table.
+        // Now build the result by iterating over the sorted vector.
         RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
         if (!obj)
             return false;
         for (Entry **entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) {
             Entry &entry = **entryPtr;
             EachClass &assorter = entry.value();
             RootedValue assorterReport(cx);
             if (!assorter.report(census, &assorterReport))
@@ -546,16 +546,17 @@ class ByObjectClass {
 template<typename EachType = Tally>
 class ByUbinodeType {
     size_t total_;
 
     // Note that, because ubi::Node::typeName promises to return a specific
     // pointer, not just any string whose contents are correct, we can use their
     // addresses as hash table keys.
     typedef HashMap<const jschar *, EachType, DefaultHasher<const jschar *>, SystemAllocPolicy> Table;
+    typedef typename Table::Entry Entry;
     Table table;
 
   public:
     ByUbinodeType(Census &census) : total_(0) { }
     ByUbinodeType(ByUbinodeType &&rhs) : total_(rhs.total_), table(Move(rhs.table)) { }
     ByUbinodeType &operator=(ByUbinodeType &&rhs) {
         MOZ_ASSERT(&rhs != this);
         this->~ByUbinodeType();
@@ -575,37 +576,62 @@ class ByUbinodeType {
             if (!p->value().init(census))
                 return false;
         }
         return p->value().count(census, node);
     }
 
     size_t total() const { return total_; }
 
+    static int compareEntries(const void *lhsVoid, const void *rhsVoid) {
+        size_t lhs = (*static_cast<const Entry * const *>(lhsVoid))->value().total();
+        size_t rhs = (*static_cast<const Entry * const *>(rhsVoid))->value().total();
+
+        // qsort sorts in "ascending" order, so we should describe entries with
+        // smaller counts as being "greater than" entries with larger counts. We
+        // don't want to just subtract the counts, as they're unsigned.
+        if (lhs < rhs)
+            return 1;
+        if (lhs > rhs)
+            return -1;
+        return 0;
+    }
+
     bool report(Census &census, MutableHandleValue report) {
         JSContext *cx = census.cx;
 
+        // Build a vector of pointers to entries; sort by total; and then use
+        // that to build the result object. This makes the ordering of entries
+        // more interesting, and a little less non-deterministic.
+        mozilla::Vector<Entry *> entries;
+        if (!entries.reserve(table.count()))
+            return false;
+        for (typename Table::Range r = table.all(); !r.empty(); r.popFront())
+            entries.infallibleAppend(&r.front());
+        qsort(entries.begin(), entries.length(), sizeof(*entries.begin()), compareEntries);
+
+        // Now build the result by iterating over the sorted vector.
         RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
         if (!obj)
             return false;
-
-        for (typename Table::Range r = table.all(); !r.empty(); r.popFront()) {
-            EachType &entry = r.front().value();
-            RootedValue entryReport(cx);
-            if (!entry.report(census, &entryReport))
+        for (Entry **entryPtr = entries.begin(); entryPtr < entries.end(); entryPtr++) {
+            Entry &entry = **entryPtr;
+            EachType &assorter = entry.value();
+            RootedValue assorterReport(cx);
+            if (!assorter.report(census, &assorterReport))
                 return false;
 
-            const jschar *name = r.front().key();
+            const jschar *name = entry.key();
             MOZ_ASSERT(name);
             JSAtom *atom = AtomizeChars(cx, name, js_strlen(name));
             if (!atom)
                 return false;
             RootedId entryId(cx, AtomToId(atom));
 
-            if (!JSObject::defineGeneric(cx, obj, entryId, entryReport))
+            if (!JSObject::defineGeneric(cx, obj, entryId, assorterReport))
                 return false;
         }
 
         report.setObject(*obj);
         return true;
     }
 };