tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

UbiNodeCensus.cpp (41234B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "js/UbiNodeCensus.h"
      8 
      9 #include "mozilla/ScopeExit.h"
     10 
     11 #include "builtin/MapObject.h"
     12 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     13 #include "js/Printer.h"
     14 #include "util/Text.h"
     15 #include "vm/Compartment.h"
     16 #include "vm/JSContext.h"
     17 #include "vm/PlainObject.h"  // js::PlainObject
     18 
     19 #include "vm/NativeObject-inl.h"
     20 
     21 using namespace js;
     22 
     23 namespace JS {
     24 namespace ubi {
     25 
     26 JS_PUBLIC_API void CountDeleter::operator()(CountBase* ptr) {
     27  if (!ptr) {
     28    return;
     29  }
     30 
     31  // Downcast to our true type and destruct, as guided by our CountType
     32  // pointer.
     33  ptr->destruct();
     34  js_free(ptr);
     35 }
     36 
     37 /*** Count Types ************************************************************/
     38 
     39 // The simplest type: just count everything.
     40 class SimpleCount : public CountType {
     41  struct Count : CountBase {
     42    size_t totalBytes_;
     43 
     44    explicit Count(SimpleCount& count) : CountBase(count), totalBytes_(0) {}
     45  };
     46 
     47  UniqueTwoByteChars label;
     48  bool reportCount : 1;
     49  bool reportBytes : 1;
     50 
     51 public:
     52  explicit SimpleCount(UniqueTwoByteChars& label, bool reportCount = true,
     53                       bool reportBytes = true)
     54      : label(std::move(label)),
     55        reportCount(reportCount),
     56        reportBytes(reportBytes) {}
     57 
     58  explicit SimpleCount()
     59      : label(nullptr), reportCount(true), reportBytes(true) {}
     60 
     61  void destructCount(CountBase& countBase) override {
     62    Count& count = static_cast<Count&>(countBase);
     63    count.~Count();
     64  }
     65 
     66  CountBasePtr makeCount() override {
     67    return CountBasePtr(js_new<Count>(*this));
     68  }
     69  void traceCount(CountBase& countBase, JSTracer* trc) override {}
     70  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
     71             const Node& node) override;
     72  bool report(JSContext* cx, CountBase& countBase,
     73              MutableHandleValue report) override;
     74 };
     75 
     76 bool SimpleCount::count(CountBase& countBase,
     77                        mozilla::MallocSizeOf mallocSizeOf, const Node& node) {
     78  Count& count = static_cast<Count&>(countBase);
     79  if (reportBytes) {
     80    count.totalBytes_ += node.size(mallocSizeOf);
     81  }
     82  return true;
     83 }
     84 
     85 bool SimpleCount::report(JSContext* cx, CountBase& countBase,
     86                         MutableHandleValue report) {
     87  Count& count = static_cast<Count&>(countBase);
     88 
     89  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
     90  if (!obj) {
     91    return false;
     92  }
     93 
     94  RootedValue countValue(cx, NumberValue(count.total_));
     95  if (reportCount &&
     96      !DefineDataProperty(cx, obj, cx->names().count, countValue)) {
     97    return false;
     98  }
     99 
    100  RootedValue bytesValue(cx, NumberValue(count.totalBytes_));
    101  if (reportBytes &&
    102      !DefineDataProperty(cx, obj, cx->names().bytes, bytesValue)) {
    103    return false;
    104  }
    105 
    106  if (label) {
    107    JSString* labelString = JS_NewUCStringCopyZ(cx, label.get());
    108    if (!labelString) {
    109      return false;
    110    }
    111    RootedValue labelValue(cx, StringValue(labelString));
    112    if (!DefineDataProperty(cx, obj, cx->names().label, labelValue)) {
    113      return false;
    114    }
    115  }
    116 
    117  report.setObject(*obj);
    118  return true;
    119 }
    120 
    121 // A count type that collects all matching nodes in a bucket.
    122 class BucketCount : public CountType {
    123  struct Count : CountBase {
    124    JS::ubi::Vector<JS::ubi::Node::Id> ids_;
    125 
    126    explicit Count(BucketCount& count) : CountBase(count) {}
    127  };
    128 
    129 public:
    130  explicit BucketCount() = default;
    131 
    132  void destructCount(CountBase& countBase) override {
    133    Count& count = static_cast<Count&>(countBase);
    134    count.~Count();
    135  }
    136 
    137  CountBasePtr makeCount() override {
    138    return CountBasePtr(js_new<Count>(*this));
    139  }
    140  void traceCount(CountBase& countBase, JSTracer* trc) final {}
    141  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
    142             const Node& node) override;
    143  bool report(JSContext* cx, CountBase& countBase,
    144              MutableHandleValue report) override;
    145 };
    146 
    147 bool BucketCount::count(CountBase& countBase,
    148                        mozilla::MallocSizeOf mallocSizeOf, const Node& node) {
    149  Count& count = static_cast<Count&>(countBase);
    150  return count.ids_.append(node.identifier());
    151 }
    152 
    153 bool BucketCount::report(JSContext* cx, CountBase& countBase,
    154                         MutableHandleValue report) {
    155  Count& count = static_cast<Count&>(countBase);
    156 
    157  size_t length = count.ids_.length();
    158  ArrayObject* arr = NewDenseFullyAllocatedArray(cx, length);
    159  if (!arr) {
    160    return false;
    161  }
    162  arr->ensureDenseInitializedLength(0, length);
    163 
    164  for (size_t i = 0; i < length; i++) {
    165    arr->setDenseElement(i, NumberValue(count.ids_[i]));
    166  }
    167 
    168  report.setObject(*arr);
    169  return true;
    170 }
    171 
    172 // A type that categorizes nodes by their JavaScript type -- 'objects',
    173 // 'strings', 'scripts', 'domNode', and 'other' -- and then passes the nodes to
    174 // child types.
    175 //
    176 // Implementation details of scripts like jitted code are counted under
    177 // 'scripts'.
    178 class ByCoarseType : public CountType {
    179  CountTypePtr objects;
    180  CountTypePtr scripts;
    181  CountTypePtr strings;
    182  CountTypePtr other;
    183  CountTypePtr domNode;
    184 
    185  struct Count : CountBase {
    186    Count(CountType& type, CountBasePtr& objects, CountBasePtr& scripts,
    187          CountBasePtr& strings, CountBasePtr& other, CountBasePtr& domNode)
    188        : CountBase(type),
    189          objects(std::move(objects)),
    190          scripts(std::move(scripts)),
    191          strings(std::move(strings)),
    192          other(std::move(other)),
    193          domNode(std::move(domNode)) {}
    194 
    195    CountBasePtr objects;
    196    CountBasePtr scripts;
    197    CountBasePtr strings;
    198    CountBasePtr other;
    199    CountBasePtr domNode;
    200  };
    201 
    202 public:
    203  ByCoarseType(CountTypePtr& objects, CountTypePtr& scripts,
    204               CountTypePtr& strings, CountTypePtr& other,
    205               CountTypePtr& domNode)
    206      : objects(std::move(objects)),
    207        scripts(std::move(scripts)),
    208        strings(std::move(strings)),
    209        other(std::move(other)),
    210        domNode(std::move(domNode)) {}
    211 
    212  void destructCount(CountBase& countBase) override {
    213    Count& count = static_cast<Count&>(countBase);
    214    count.~Count();
    215  }
    216 
    217  CountBasePtr makeCount() override;
    218  void traceCount(CountBase& countBase, JSTracer* trc) override;
    219  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
    220             const Node& node) override;
    221  bool report(JSContext* cx, CountBase& countBase,
    222              MutableHandleValue report) override;
    223 };
    224 
    225 CountBasePtr ByCoarseType::makeCount() {
    226  CountBasePtr objectsCount(objects->makeCount());
    227  CountBasePtr scriptsCount(scripts->makeCount());
    228  CountBasePtr stringsCount(strings->makeCount());
    229  CountBasePtr otherCount(other->makeCount());
    230  CountBasePtr domNodeCount(domNode->makeCount());
    231 
    232  if (!objectsCount || !scriptsCount || !stringsCount || !otherCount ||
    233      !domNodeCount) {
    234    return CountBasePtr(nullptr);
    235  }
    236 
    237  return CountBasePtr(js_new<Count>(*this, objectsCount, scriptsCount,
    238                                    stringsCount, otherCount, domNodeCount));
    239 }
    240 
    241 void ByCoarseType::traceCount(CountBase& countBase, JSTracer* trc) {
    242  Count& count = static_cast<Count&>(countBase);
    243  count.objects->trace(trc);
    244  count.scripts->trace(trc);
    245  count.strings->trace(trc);
    246  count.other->trace(trc);
    247  count.domNode->trace(trc);
    248 }
    249 
    250 bool ByCoarseType::count(CountBase& countBase,
    251                         mozilla::MallocSizeOf mallocSizeOf, const Node& node) {
    252  Count& count = static_cast<Count&>(countBase);
    253 
    254  switch (node.coarseType()) {
    255    case JS::ubi::CoarseType::Object:
    256      return count.objects->count(mallocSizeOf, node);
    257    case JS::ubi::CoarseType::Script:
    258      return count.scripts->count(mallocSizeOf, node);
    259    case JS::ubi::CoarseType::String:
    260      return count.strings->count(mallocSizeOf, node);
    261    case JS::ubi::CoarseType::Other:
    262      return count.other->count(mallocSizeOf, node);
    263    case JS::ubi::CoarseType::DOMNode:
    264      return count.domNode->count(mallocSizeOf, node);
    265    default:
    266      MOZ_CRASH("bad JS::ubi::CoarseType in JS::ubi::ByCoarseType::count");
    267      return false;
    268  }
    269 }
    270 
    271 bool ByCoarseType::report(JSContext* cx, CountBase& countBase,
    272                          MutableHandleValue report) {
    273  Count& count = static_cast<Count&>(countBase);
    274 
    275  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
    276  if (!obj) {
    277    return false;
    278  }
    279 
    280  RootedValue objectsReport(cx);
    281  if (!count.objects->report(cx, &objectsReport) ||
    282      !DefineDataProperty(cx, obj, cx->names().objects, objectsReport))
    283    return false;
    284 
    285  RootedValue scriptsReport(cx);
    286  if (!count.scripts->report(cx, &scriptsReport) ||
    287      !DefineDataProperty(cx, obj, cx->names().scripts, scriptsReport))
    288    return false;
    289 
    290  RootedValue stringsReport(cx);
    291  if (!count.strings->report(cx, &stringsReport) ||
    292      !DefineDataProperty(cx, obj, cx->names().strings, stringsReport))
    293    return false;
    294 
    295  RootedValue otherReport(cx);
    296  if (!count.other->report(cx, &otherReport) ||
    297      !DefineDataProperty(cx, obj, cx->names().other, otherReport))
    298    return false;
    299  RootedValue domReport(cx);
    300  if (!count.domNode->report(cx, &domReport) ||
    301      !DefineDataProperty(cx, obj, cx->names().domNode, domReport))
    302    return false;
    303 
    304  report.setObject(*obj);
    305  return true;
    306 }
    307 
    308 // Comparison function for sorting hash table entries by the smallest node ID
    309 // they counted. Node IDs are stable and unique, which ensures ordering of
    310 // results never depends on hash table placement or sort algorithm vagaries. The
    311 // arguments are doubly indirect: they're pointers to elements in an array of
    312 // pointers to table entries.
    313 template <typename Entry>
    314 static int compareEntries(const void* lhsVoid, const void* rhsVoid) {
    315  auto lhs = (*static_cast<const Entry* const*>(lhsVoid))
    316                 ->value()
    317                 ->smallestNodeIdCounted_;
    318  auto rhs = (*static_cast<const Entry* const*>(rhsVoid))
    319                 ->value()
    320                 ->smallestNodeIdCounted_;
    321 
    322  // We don't want to just subtract the values, as they're unsigned.
    323  if (lhs < rhs) {
    324    return 1;
    325  }
    326  if (lhs > rhs) {
    327    return -1;
    328  }
    329  return 0;
    330 }
    331 
    332 // A hash map mapping from C strings to counts.
    333 using CStringCountMap = HashMap<const char*, CountBasePtr,
    334                                mozilla::CStringHasher, SystemAllocPolicy>;
    335 
    336 // Convert a HashMap into an object with each key one of the entries from the
    337 // map and each value the associated count's report. For use during census
    338 // reporting.
    339 //
    340 // `Map` must be a `HashMap` from some key type to a `CountBasePtr`.
    341 //
    342 // `GetName` must be a callable type which takes `const Map::Key&` and returns
    343 // `JSAtom*`.
    344 template <class Map, class GetName>
    345 static PlainObject* countMapToObject(JSContext* cx, Map& map, GetName getName) {
    346  // Build a vector of pointers to entries; sort by total; and then use
    347  // that to build the result object. This makes the ordering of entries
    348  // more interesting, and a little less non-deterministic.
    349 
    350  JS::ubi::Vector<typename Map::Entry*> entries;
    351  if (!entries.reserve(map.count())) {
    352    ReportOutOfMemory(cx);
    353    return nullptr;
    354  }
    355 
    356  for (auto r = map.all(); !r.empty(); r.popFront()) {
    357    entries.infallibleAppend(&r.front());
    358  }
    359 
    360  if (entries.length()) {
    361    qsort(entries.begin(), entries.length(), sizeof(*entries.begin()),
    362          compareEntries<typename Map::Entry>);
    363  }
    364 
    365  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
    366  if (!obj) {
    367    return nullptr;
    368  }
    369 
    370  for (auto& entry : entries) {
    371    CountBasePtr& thenCount = entry->value();
    372    RootedValue thenReport(cx);
    373    if (!thenCount->report(cx, &thenReport)) {
    374      return nullptr;
    375    }
    376 
    377    JSAtom* atom = getName(entry->key());
    378    if (!atom) {
    379      return nullptr;
    380    }
    381 
    382    RootedId entryId(cx, AtomToId(atom));
    383    if (!DefineDataProperty(cx, obj, entryId, thenReport)) {
    384      return nullptr;
    385    }
    386  }
    387 
    388  return obj;
    389 }
    390 
    391 // A type that categorizes nodes that are JSObjects by their class name,
    392 // and places all other nodes in an 'other' category.
    393 class ByObjectClass : public CountType {
    394  // A table mapping class names to their counts. Note that we treat js::Class
    395  // instances with the same name as equal keys. If you have several
    396  // js::Classes with equal names (and we do; as of this writing there were
    397  // six named "Object"), you will get several different js::Classes being
    398  // counted in the same table entry.
    399  using Table = CStringCountMap;
    400  using Entry = Table::Entry;
    401 
    402  struct Count : public CountBase {
    403    Table table;
    404    CountBasePtr other;
    405 
    406    Count(CountType& type, CountBasePtr& other)
    407        : CountBase(type), other(std::move(other)) {}
    408  };
    409 
    410  CountTypePtr classesType;
    411  CountTypePtr otherType;
    412 
    413 public:
    414  ByObjectClass(CountTypePtr& classesType, CountTypePtr& otherType)
    415      : classesType(std::move(classesType)), otherType(std::move(otherType)) {}
    416 
    417  void destructCount(CountBase& countBase) override {
    418    Count& count = static_cast<Count&>(countBase);
    419    count.~Count();
    420  }
    421 
    422  CountBasePtr makeCount() override;
    423  void traceCount(CountBase& countBase, JSTracer* trc) override;
    424  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
    425             const Node& node) override;
    426  bool report(JSContext* cx, CountBase& countBase,
    427              MutableHandleValue report) override;
    428 };
    429 
    430 CountBasePtr ByObjectClass::makeCount() {
    431  CountBasePtr otherCount(otherType->makeCount());
    432  if (!otherCount) {
    433    return nullptr;
    434  }
    435 
    436  auto count = js::MakeUnique<Count>(*this, otherCount);
    437  if (!count) {
    438    return nullptr;
    439  }
    440 
    441  return CountBasePtr(count.release());
    442 }
    443 
    444 void ByObjectClass::traceCount(CountBase& countBase, JSTracer* trc) {
    445  Count& count = static_cast<Count&>(countBase);
    446  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    447    r.front().value()->trace(trc);
    448  }
    449  count.other->trace(trc);
    450 }
    451 
    452 bool ByObjectClass::count(CountBase& countBase,
    453                          mozilla::MallocSizeOf mallocSizeOf,
    454                          const Node& node) {
    455  Count& count = static_cast<Count&>(countBase);
    456 
    457  const char* className = node.jsObjectClassName();
    458  if (!className) {
    459    return count.other->count(mallocSizeOf, node);
    460  }
    461 
    462  Table::AddPtr p = count.table.lookupForAdd(className);
    463  if (!p) {
    464    CountBasePtr classCount(classesType->makeCount());
    465    if (!classCount || !count.table.add(p, className, std::move(classCount))) {
    466      return false;
    467    }
    468  }
    469  return p->value()->count(mallocSizeOf, node);
    470 }
    471 
    472 bool ByObjectClass::report(JSContext* cx, CountBase& countBase,
    473                           MutableHandleValue report) {
    474  Count& count = static_cast<Count&>(countBase);
    475 
    476  Rooted<PlainObject*> obj(
    477      cx, countMapToObject(cx, count.table, [cx](const char* key) {
    478        MOZ_ASSERT(key);
    479        return Atomize(cx, key, strlen(key));
    480      }));
    481  if (!obj) {
    482    return false;
    483  }
    484 
    485  RootedValue otherReport(cx);
    486  if (!count.other->report(cx, &otherReport) ||
    487      !DefineDataProperty(cx, obj, cx->names().other, otherReport))
    488    return false;
    489 
    490  report.setObject(*obj);
    491  return true;
    492 }
    493 
    494 class ByDomObjectClass : public CountType {
    495  // A table mapping descriptive names to their counts.
    496  using UniqueC16String = JS::UniqueTwoByteChars;
    497 
    498  struct UniqueC16StringHasher {
    499    using Lookup = UniqueC16String;
    500 
    501    static js::HashNumber hash(const Lookup& lookup) {
    502      return mozilla::HashString(lookup.get());
    503    }
    504 
    505    static bool match(const UniqueC16String& key, const Lookup& lookup) {
    506      return CompareChars(key.get(), js_strlen(key.get()), lookup.get(),
    507                          js_strlen(lookup.get())) == 0;
    508    }
    509  };
    510 
    511  using Table = HashMap<UniqueC16String, CountBasePtr, UniqueC16StringHasher,
    512                        SystemAllocPolicy>;
    513  using Entry = Table::Entry;
    514 
    515  struct Count : public CountBase {
    516    Table table;
    517 
    518    explicit Count(CountType& type) : CountBase(type) {}
    519  };
    520 
    521  CountTypePtr classesType;
    522 
    523 public:
    524  explicit ByDomObjectClass(CountTypePtr& classesType)
    525      : classesType(std::move(classesType)) {}
    526 
    527  void destructCount(CountBase& countBase) override {
    528    Count& count = static_cast<Count&>(countBase);
    529    count.~Count();
    530  }
    531 
    532  CountBasePtr makeCount() override;
    533  void traceCount(CountBase& countBase, JSTracer* trc) override;
    534  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
    535             const Node& node) override;
    536  bool report(JSContext* cx, CountBase& countBase,
    537              MutableHandleValue report) override;
    538 };
    539 
    540 CountBasePtr ByDomObjectClass::makeCount() {
    541  auto count = js::MakeUnique<Count>(*this);
    542  if (!count) {
    543    return nullptr;
    544  }
    545 
    546  return CountBasePtr(count.release());
    547 }
    548 
    549 void ByDomObjectClass::traceCount(CountBase& countBase, JSTracer* trc) {
    550  Count& count = static_cast<Count&>(countBase);
    551  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    552    r.front().value()->trace(trc);
    553  }
    554 }
    555 
    556 bool ByDomObjectClass::count(CountBase& countBase,
    557                             mozilla::MallocSizeOf mallocSizeOf,
    558                             const Node& node) {
    559  Count& count = static_cast<Count&>(countBase);
    560 
    561  const char16_t* nodeName = node.descriptiveTypeName();
    562  if (!nodeName) {
    563    return false;
    564  }
    565 
    566  UniqueC16String name = DuplicateString(nodeName);
    567  if (!name) {
    568    return false;
    569  }
    570 
    571  Table::AddPtr p = count.table.lookupForAdd(name);
    572  if (!p) {
    573    CountBasePtr classesCount(classesType->makeCount());
    574    if (!classesCount ||
    575        !count.table.add(p, std::move(name), std::move(classesCount))) {
    576      return false;
    577    }
    578  }
    579  return p->value()->count(mallocSizeOf, node);
    580 }
    581 
    582 bool ByDomObjectClass::report(JSContext* cx, CountBase& countBase,
    583                              MutableHandleValue report) {
    584  Count& count = static_cast<Count&>(countBase);
    585 
    586  PlainObject* obj =
    587      countMapToObject(cx, count.table, [cx](const UniqueC16String& key) {
    588        const char16_t* chars = key.get();
    589        MOZ_ASSERT(chars);
    590        return AtomizeChars(cx, chars, js_strlen(chars));
    591      });
    592  if (!obj) {
    593    return false;
    594  }
    595 
    596  report.setObject(*obj);
    597  return true;
    598 }
    599 
    600 // A count type that categorizes nodes by their ubi::Node::typeName.
    601 class ByUbinodeType : public CountType {
    602  // Note that, because ubi::Node::typeName promises to return a specific
    603  // pointer, not just any string whose contents are correct, we can use their
    604  // addresses as hash table keys.
    605  using Table = HashMap<const char16_t*, CountBasePtr,
    606                        DefaultHasher<const char16_t*>, SystemAllocPolicy>;
    607  using Entry = Table::Entry;
    608 
    609  struct Count : public CountBase {
    610    Table table;
    611 
    612    explicit Count(CountType& type) : CountBase(type) {}
    613  };
    614 
    615  CountTypePtr entryType;
    616 
    617 public:
    618  explicit ByUbinodeType(CountTypePtr& entryType)
    619      : entryType(std::move(entryType)) {}
    620 
    621  void destructCount(CountBase& countBase) override {
    622    Count& count = static_cast<Count&>(countBase);
    623    count.~Count();
    624  }
    625 
    626  CountBasePtr makeCount() override;
    627  void traceCount(CountBase& countBase, JSTracer* trc) override;
    628  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
    629             const Node& node) override;
    630  bool report(JSContext* cx, CountBase& countBase,
    631              MutableHandleValue report) override;
    632 };
    633 
    634 CountBasePtr ByUbinodeType::makeCount() {
    635  auto count = js::MakeUnique<Count>(*this);
    636  if (!count) {
    637    return nullptr;
    638  }
    639 
    640  return CountBasePtr(count.release());
    641 }
    642 
    643 void ByUbinodeType::traceCount(CountBase& countBase, JSTracer* trc) {
    644  Count& count = static_cast<Count&>(countBase);
    645  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    646    r.front().value()->trace(trc);
    647  }
    648 }
    649 
    650 bool ByUbinodeType::count(CountBase& countBase,
    651                          mozilla::MallocSizeOf mallocSizeOf,
    652                          const Node& node) {
    653  Count& count = static_cast<Count&>(countBase);
    654 
    655  const char16_t* key = node.typeName();
    656  MOZ_ASSERT(key);
    657  Table::AddPtr p = count.table.lookupForAdd(key);
    658  if (!p) {
    659    CountBasePtr typesCount(entryType->makeCount());
    660    if (!typesCount || !count.table.add(p, key, std::move(typesCount))) {
    661      return false;
    662    }
    663  }
    664  return p->value()->count(mallocSizeOf, node);
    665 }
    666 
    667 bool ByUbinodeType::report(JSContext* cx, CountBase& countBase,
    668                           MutableHandleValue report) {
    669  Count& count = static_cast<Count&>(countBase);
    670 
    671  // Build a vector of pointers to entries; sort by total; and then use
    672  // that to build the result object. This makes the ordering of entries
    673  // more interesting, and a little less non-deterministic.
    674  JS::ubi::Vector<Entry*> entries;
    675  if (!entries.reserve(count.table.count())) {
    676    return false;
    677  }
    678  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    679    entries.infallibleAppend(&r.front());
    680  }
    681  if (entries.length()) {
    682    qsort(entries.begin(), entries.length(), sizeof(*entries.begin()),
    683          compareEntries<Entry>);
    684  }
    685 
    686  // Now build the result by iterating over the sorted vector.
    687  Rooted<PlainObject*> obj(cx, NewPlainObject(cx));
    688  if (!obj) {
    689    return false;
    690  }
    691  for (Entry** entryPtr = entries.begin(); entryPtr < entries.end();
    692       entryPtr++) {
    693    Entry& entry = **entryPtr;
    694    CountBasePtr& typeCount = entry.value();
    695    RootedValue typeReport(cx);
    696    if (!typeCount->report(cx, &typeReport)) {
    697      return false;
    698    }
    699 
    700    const char16_t* name = entry.key();
    701    MOZ_ASSERT(name);
    702    JSAtom* atom = AtomizeChars(cx, name, js_strlen(name));
    703    if (!atom) {
    704      return false;
    705    }
    706    RootedId entryId(cx, AtomToId(atom));
    707 
    708    if (!DefineDataProperty(cx, obj, entryId, typeReport)) {
    709      return false;
    710    }
    711  }
    712 
    713  report.setObject(*obj);
    714  return true;
    715 }
    716 
    717 // A count type that categorizes nodes by the JS stack under which they were
    718 // allocated.
    719 class ByAllocationStack : public CountType {
    720  using Table = HashMap<StackFrame, CountBasePtr, DefaultHasher<StackFrame>,
    721                        SystemAllocPolicy>;
    722  using Entry = Table::Entry;
    723 
    724  struct Count : public CountBase {
    725    // NOTE: You may look up entries in this table by JS::ubi::StackFrame
    726    // key only during traversal, NOT ONCE TRAVERSAL IS COMPLETE. Once
    727    // traversal is complete, you may only iterate over it.
    728    //
    729    // In this hash table, keys are JSObjects (with some indirection), and
    730    // we use JSObject identity (that is, address identity) as key
    731    // identity. The normal way to support such a table is to make the trace
    732    // function notice keys that have moved and re-key them in the
    733    // table. However, our trace function does *not* rehash; the first GC
    734    // may render the hash table unsearchable.
    735    //
    736    // This is as it should be:
    737    //
    738    // First, the heap traversal phase needs lookups by key to work. But no
    739    // GC may ever occur during a traversal; this is enforced by the
    740    // JS::ubi::BreadthFirst template. So the traceCount function doesn't
    741    // need to do anything to help traversal; it never even runs then.
    742    //
    743    // Second, the report phase needs iteration over the table to work, but
    744    // never looks up entries by key. GC may well occur during this phase:
    745    // we allocate a Map object, and probably cross-compartment wrappers for
    746    // SavedFrame instances as well. If a GC were to occur, it would call
    747    // our traceCount function; if traceCount were to re-key, that would
    748    // ruin the traversal in progress.
    749    //
    750    // So depending on the phase, we either don't need re-keying, or
    751    // can't abide it.
    752    Table table;
    753    CountBasePtr noStack;
    754 
    755    Count(CountType& type, CountBasePtr& noStack)
    756        : CountBase(type), noStack(std::move(noStack)) {}
    757  };
    758 
    759  CountTypePtr entryType;
    760  CountTypePtr noStackType;
    761 
    762 public:
    763  ByAllocationStack(CountTypePtr& entryType, CountTypePtr& noStackType)
    764      : entryType(std::move(entryType)), noStackType(std::move(noStackType)) {}
    765 
    766  void destructCount(CountBase& countBase) override {
    767    Count& count = static_cast<Count&>(countBase);
    768    count.~Count();
    769  }
    770 
    771  CountBasePtr makeCount() override;
    772  void traceCount(CountBase& countBase, JSTracer* trc) override;
    773  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
    774             const Node& node) override;
    775  bool report(JSContext* cx, CountBase& countBase,
    776              MutableHandleValue report) override;
    777 };
    778 
    779 CountBasePtr ByAllocationStack::makeCount() {
    780  CountBasePtr noStackCount(noStackType->makeCount());
    781  if (!noStackCount) {
    782    return nullptr;
    783  }
    784 
    785  auto count = js::MakeUnique<Count>(*this, noStackCount);
    786  if (!count) {
    787    return nullptr;
    788  }
    789  return CountBasePtr(count.release());
    790 }
    791 
    792 void ByAllocationStack::traceCount(CountBase& countBase, JSTracer* trc) {
    793  Count& count = static_cast<Count&>(countBase);
    794  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    795    // Trace our child Counts.
    796    r.front().value()->trace(trc);
    797 
    798    // Trace the StackFrame that is this entry's key. Do not re-key if
    799    // it has moved; see comments for ByAllocationStack::Count::table.
    800    const StackFrame* key = &r.front().key();
    801    auto& k = *const_cast<StackFrame*>(key);
    802    k.trace(trc);
    803  }
    804  count.noStack->trace(trc);
    805 }
    806 
    807 bool ByAllocationStack::count(CountBase& countBase,
    808                              mozilla::MallocSizeOf mallocSizeOf,
    809                              const Node& node) {
    810  Count& count = static_cast<Count&>(countBase);
    811 
    812  // If we do have an allocation stack for this node, include it in the
    813  // count for that stack.
    814  if (node.hasAllocationStack()) {
    815    auto allocationStack = node.allocationStack();
    816    auto p = count.table.lookupForAdd(allocationStack);
    817    if (!p) {
    818      CountBasePtr stackCount(entryType->makeCount());
    819      if (!stackCount ||
    820          !count.table.add(p, allocationStack, std::move(stackCount))) {
    821        return false;
    822      }
    823    }
    824    MOZ_ASSERT(p);
    825    return p->value()->count(mallocSizeOf, node);
    826  }
    827 
    828  // Otherwise, count it in the "no stack" category.
    829  return count.noStack->count(mallocSizeOf, node);
    830 }
    831 
    832 bool ByAllocationStack::report(JSContext* cx, CountBase& countBase,
    833                               MutableHandleValue report) {
    834  Count& count = static_cast<Count&>(countBase);
    835 
    836 #ifdef DEBUG
    837  // Check that nothing rehashes our table while we hold pointers into it.
    838  mozilla::Generation generation = count.table.generation();
    839 #endif
    840 
    841  // Build a vector of pointers to entries; sort by total; and then use
    842  // that to build the result object. This makes the ordering of entries
    843  // more interesting, and a little less non-deterministic.
    844  JS::ubi::Vector<Entry*> entries;
    845  if (!entries.reserve(count.table.count())) {
    846    return false;
    847  }
    848  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    849    entries.infallibleAppend(&r.front());
    850  }
    851  if (entries.length()) {
    852    qsort(entries.begin(), entries.length(), sizeof(*entries.begin()),
    853          compareEntries<Entry>);
    854  }
    855 
    856  // Now build the result by iterating over the sorted vector.
    857  Rooted<MapObject*> map(cx, MapObject::create(cx));
    858  if (!map) {
    859    return false;
    860  }
    861  for (Entry** entryPtr = entries.begin(); entryPtr < entries.end();
    862       entryPtr++) {
    863    Entry& entry = **entryPtr;
    864    MOZ_ASSERT(entry.key());
    865 
    866    RootedObject stack(cx);
    867    if (!entry.key().constructSavedFrameStack(cx, &stack) ||
    868        !cx->compartment()->wrap(cx, &stack)) {
    869      return false;
    870    }
    871    RootedValue stackVal(cx, ObjectValue(*stack));
    872 
    873    CountBasePtr& stackCount = entry.value();
    874    RootedValue stackReport(cx);
    875    if (!stackCount->report(cx, &stackReport)) {
    876      return false;
    877    }
    878 
    879    if (!map->set(cx, stackVal, stackReport)) {
    880      return false;
    881    }
    882  }
    883 
    884  if (count.noStack->total_ > 0) {
    885    RootedValue noStackReport(cx);
    886    if (!count.noStack->report(cx, &noStackReport)) {
    887      return false;
    888    }
    889    RootedValue noStack(cx, StringValue(cx->names().noStack));
    890    if (!map->set(cx, noStack, noStackReport)) {
    891      return false;
    892    }
    893  }
    894 
    895  MOZ_ASSERT(generation == count.table.generation());
    896 
    897  report.setObject(*map);
    898  return true;
    899 }
    900 
    901 // A count type that categorizes nodes by their script's filename.
    902 class ByFilename : public CountType {
    903  using UniqueCString = JS::UniqueChars;
    904 
    905  struct UniqueCStringHasher {
    906    using Lookup = UniqueCString;
    907 
    908    static js::HashNumber hash(const Lookup& lookup) {
    909      return mozilla::CStringHasher::hash(lookup.get());
    910    }
    911 
    912    static bool match(const UniqueCString& key, const Lookup& lookup) {
    913      return mozilla::CStringHasher::match(key.get(), lookup.get());
    914    }
    915  };
    916 
    917  // A table mapping filenames to their counts. Note that we treat scripts
    918  // with the same filename as equivalent. If you have several sources with
    919  // the same filename, then all their scripts will get bucketed together.
    920  using Table = HashMap<UniqueCString, CountBasePtr, UniqueCStringHasher,
    921                        SystemAllocPolicy>;
    922  using Entry = Table::Entry;
    923 
    924  struct Count : public CountBase {
    925    Table table;
    926    CountBasePtr then;
    927    CountBasePtr noFilename;
    928 
    929    Count(CountType& type, CountBasePtr&& then, CountBasePtr&& noFilename)
    930        : CountBase(type),
    931          then(std::move(then)),
    932          noFilename(std::move(noFilename)) {}
    933  };
    934 
    935  CountTypePtr thenType;
    936  CountTypePtr noFilenameType;
    937 
    938 public:
    939  ByFilename(CountTypePtr&& thenType, CountTypePtr&& noFilenameType)
    940      : thenType(std::move(thenType)),
    941        noFilenameType(std::move(noFilenameType)) {}
    942 
    943  void destructCount(CountBase& countBase) override {
    944    Count& count = static_cast<Count&>(countBase);
    945    count.~Count();
    946  }
    947 
    948  CountBasePtr makeCount() override;
    949  void traceCount(CountBase& countBase, JSTracer* trc) override;
    950  bool count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
    951             const Node& node) override;
    952  bool report(JSContext* cx, CountBase& countBase,
    953              MutableHandleValue report) override;
    954 };
    955 
    956 CountBasePtr ByFilename::makeCount() {
    957  CountBasePtr thenCount(thenType->makeCount());
    958  if (!thenCount) {
    959    return nullptr;
    960  }
    961 
    962  CountBasePtr noFilenameCount(noFilenameType->makeCount());
    963  if (!noFilenameCount) {
    964    return nullptr;
    965  }
    966 
    967  auto count = js::MakeUnique<Count>(*this, std::move(thenCount),
    968                                     std::move(noFilenameCount));
    969  if (!count) {
    970    return nullptr;
    971  }
    972 
    973  return CountBasePtr(count.release());
    974 }
    975 
    976 void ByFilename::traceCount(CountBase& countBase, JSTracer* trc) {
    977  Count& count = static_cast<Count&>(countBase);
    978  for (Table::Range r = count.table.all(); !r.empty(); r.popFront()) {
    979    r.front().value()->trace(trc);
    980  }
    981  count.noFilename->trace(trc);
    982 }
    983 
    984 bool ByFilename::count(CountBase& countBase, mozilla::MallocSizeOf mallocSizeOf,
    985                       const Node& node) {
    986  Count& count = static_cast<Count&>(countBase);
    987 
    988  const char* filename = node.scriptFilename();
    989  if (!filename) {
    990    return count.noFilename->count(mallocSizeOf, node);
    991  }
    992 
    993  UniqueCString myFilename = DuplicateString(filename);
    994  if (!myFilename) {
    995    return false;
    996  }
    997 
    998  Table::AddPtr p = count.table.lookupForAdd(myFilename);
    999  if (!p) {
   1000    CountBasePtr thenCount(thenType->makeCount());
   1001    if (!thenCount ||
   1002        !count.table.add(p, std::move(myFilename), std::move(thenCount))) {
   1003      return false;
   1004    }
   1005  }
   1006  return p->value()->count(mallocSizeOf, node);
   1007 }
   1008 
   1009 bool ByFilename::report(JSContext* cx, CountBase& countBase,
   1010                        MutableHandleValue report) {
   1011  Count& count = static_cast<Count&>(countBase);
   1012 
   1013  Rooted<PlainObject*> obj(
   1014      cx, countMapToObject(cx, count.table, [cx](const UniqueCString& key) {
   1015        const char* utf8chars = key.get();
   1016        return AtomizeUTF8Chars(cx, utf8chars, strlen(utf8chars));
   1017      }));
   1018  if (!obj) {
   1019    return false;
   1020  }
   1021 
   1022  RootedValue noFilenameReport(cx);
   1023  if (!count.noFilename->report(cx, &noFilenameReport) ||
   1024      !DefineDataProperty(cx, obj, cx->names().noFilename, noFilenameReport)) {
   1025    return false;
   1026  }
   1027 
   1028  report.setObject(*obj);
   1029  return true;
   1030 }
   1031 
   1032 /*** Census Handler *********************************************************/
   1033 
   1034 JS_PUBLIC_API bool CensusHandler::operator()(
   1035    BreadthFirst<CensusHandler>& traversal, Node origin, const Edge& edge,
   1036    NodeData* referentData, bool first) {
   1037  // We're only interested in the first time we reach edge.referent, not
   1038  // in every edge arriving at that node.
   1039  if (!first) {
   1040    return true;
   1041  }
   1042 
   1043  // Don't count nodes outside the debuggee zones. Do count things in the
   1044  // special atoms zone, but don't traverse their outgoing edges, on the
   1045  // assumption that they are shared resources that debuggee is using.
   1046  // Symbols are always allocated in the atoms zone, even if they were
   1047  // created for exactly one compartment and never shared; this rule will
   1048  // include such nodes in the count.
   1049  const Node& referent = edge.referent;
   1050  Zone* zone = referent.zone();
   1051 
   1052  if (census.targetZones.count() == 0 || census.targetZones.has(zone)) {
   1053    return rootCount->count(mallocSizeOf, referent);
   1054  }
   1055 
   1056  if (zone && zone->isAtomsZone()) {
   1057    traversal.abandonReferent();
   1058    return rootCount->count(mallocSizeOf, referent);
   1059  }
   1060 
   1061  traversal.abandonReferent();
   1062  return true;
   1063 }
   1064 
   1065 /*** Parsing Breakdowns *****************************************************/
   1066 
   1067 static CountTypePtr ParseChildBreakdown(
   1068    JSContext* cx, HandleObject breakdown, PropertyName* prop,
   1069    MutableHandle<GCVector<JSLinearString*>> seen) {
   1070  RootedValue v(cx);
   1071  if (!GetProperty(cx, breakdown, breakdown, prop, &v)) {
   1072    return nullptr;
   1073  }
   1074  return ParseBreakdown(cx, v, seen);
   1075 }
   1076 
   1077 JS_PUBLIC_API CountTypePtr
   1078 ParseBreakdown(JSContext* cx, HandleValue breakdownValue,
   1079               MutableHandle<GCVector<JSLinearString*>> seen) {
   1080  if (breakdownValue.isUndefined()) {
   1081    // Construct the default type, { by: 'count' }
   1082    CountTypePtr simple(cx->new_<SimpleCount>());
   1083    return simple;
   1084  }
   1085 
   1086  RootedObject breakdown(cx, ToObject(cx, breakdownValue));
   1087  if (!breakdown) {
   1088    return nullptr;
   1089  }
   1090 
   1091  RootedValue byValue(cx);
   1092  if (!GetProperty(cx, breakdown, breakdown, cx->names().by, &byValue)) {
   1093    return nullptr;
   1094  }
   1095  RootedString byString(cx, ToString(cx, byValue));
   1096  if (!byString) {
   1097    return nullptr;
   1098  }
   1099  Rooted<JSLinearString*> by(cx, byString->ensureLinear(cx));
   1100  if (!by) {
   1101    return nullptr;
   1102  }
   1103 
   1104  for (auto candidate : seen.get()) {
   1105    if (EqualStrings(by, candidate)) {
   1106      UniqueChars byBytes = QuoteString(cx, by, '"');
   1107      if (!byBytes) {
   1108        return nullptr;
   1109      }
   1110 
   1111      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1112                                JSMSG_DEBUG_CENSUS_BREAKDOWN_NESTED,
   1113                                byBytes.get());
   1114      return nullptr;
   1115    }
   1116  }
   1117  if (!seen.append(by)) {
   1118    return nullptr;
   1119  }
   1120  auto popper = mozilla::MakeScopeExit([&]() { seen.popBack(); });
   1121 
   1122  if (StringEqualsLiteral(by, "count")) {
   1123    RootedValue countValue(cx), bytesValue(cx);
   1124    if (!GetProperty(cx, breakdown, breakdown, cx->names().count,
   1125                     &countValue) ||
   1126        !GetProperty(cx, breakdown, breakdown, cx->names().bytes, &bytesValue))
   1127      return nullptr;
   1128 
   1129    // Both 'count' and 'bytes' default to true if omitted, but ToBoolean
   1130    // naturally treats 'undefined' as false; fix this up.
   1131    if (countValue.isUndefined()) countValue.setBoolean(true);
   1132    if (bytesValue.isUndefined()) bytesValue.setBoolean(true);
   1133 
   1134    // Undocumented feature, for testing: { by: 'count' } breakdowns can have
   1135    // a 'label' property whose value is converted to a string and included as
   1136    // a 'label' property on the report object.
   1137    RootedValue label(cx);
   1138    if (!GetProperty(cx, breakdown, breakdown, cx->names().label, &label)) {
   1139      return nullptr;
   1140    }
   1141 
   1142    UniqueTwoByteChars labelUnique(nullptr);
   1143    if (!label.isUndefined()) {
   1144      RootedString labelString(cx, ToString(cx, label));
   1145      if (!labelString) {
   1146        return nullptr;
   1147      }
   1148 
   1149      labelUnique = JS_CopyStringCharsZ(cx, labelString);
   1150      if (!labelUnique) {
   1151        return nullptr;
   1152      }
   1153    }
   1154 
   1155    CountTypePtr simple(cx->new_<SimpleCount>(
   1156        labelUnique, ToBoolean(countValue), ToBoolean(bytesValue)));
   1157    return simple;
   1158  }
   1159 
   1160  if (StringEqualsLiteral(by, "bucket")) {
   1161    return CountTypePtr(cx->new_<BucketCount>());
   1162  }
   1163 
   1164  if (StringEqualsLiteral(by, "objectClass")) {
   1165    CountTypePtr thenType(
   1166        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
   1167    if (!thenType) {
   1168      return nullptr;
   1169    }
   1170 
   1171    CountTypePtr otherType(
   1172        ParseChildBreakdown(cx, breakdown, cx->names().other, seen));
   1173    if (!otherType) {
   1174      return nullptr;
   1175    }
   1176 
   1177    return CountTypePtr(cx->new_<ByObjectClass>(thenType, otherType));
   1178  }
   1179 
   1180  if (StringEqualsLiteral(by, "coarseType")) {
   1181    CountTypePtr objectsType(
   1182        ParseChildBreakdown(cx, breakdown, cx->names().objects, seen));
   1183    if (!objectsType) {
   1184      return nullptr;
   1185    }
   1186    CountTypePtr scriptsType(
   1187        ParseChildBreakdown(cx, breakdown, cx->names().scripts, seen));
   1188    if (!scriptsType) {
   1189      return nullptr;
   1190    }
   1191    CountTypePtr stringsType(
   1192        ParseChildBreakdown(cx, breakdown, cx->names().strings, seen));
   1193    if (!stringsType) {
   1194      return nullptr;
   1195    }
   1196    CountTypePtr otherType(
   1197        ParseChildBreakdown(cx, breakdown, cx->names().other, seen));
   1198    if (!otherType) {
   1199      return nullptr;
   1200    }
   1201    CountTypePtr domNodeType(
   1202        ParseChildBreakdown(cx, breakdown, cx->names().domNode, seen));
   1203    if (!domNodeType) {
   1204      return nullptr;
   1205    }
   1206 
   1207    return CountTypePtr(cx->new_<ByCoarseType>(
   1208        objectsType, scriptsType, stringsType, otherType, domNodeType));
   1209  }
   1210 
   1211  if (StringEqualsLiteral(by, "internalType")) {
   1212    CountTypePtr thenType(
   1213        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
   1214    if (!thenType) {
   1215      return nullptr;
   1216    }
   1217 
   1218    return CountTypePtr(cx->new_<ByUbinodeType>(thenType));
   1219  }
   1220 
   1221  if (StringEqualsLiteral(by, "descriptiveType")) {
   1222    CountTypePtr thenType(
   1223        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
   1224    if (!thenType) {
   1225      return nullptr;
   1226    }
   1227    return CountTypePtr(cx->new_<ByDomObjectClass>(thenType));
   1228  }
   1229 
   1230  if (StringEqualsLiteral(by, "allocationStack")) {
   1231    CountTypePtr thenType(
   1232        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
   1233    if (!thenType) {
   1234      return nullptr;
   1235    }
   1236    CountTypePtr noStackType(
   1237        ParseChildBreakdown(cx, breakdown, cx->names().noStack, seen));
   1238    if (!noStackType) {
   1239      return nullptr;
   1240    }
   1241 
   1242    return CountTypePtr(cx->new_<ByAllocationStack>(thenType, noStackType));
   1243  }
   1244 
   1245  if (StringEqualsLiteral(by, "filename")) {
   1246    CountTypePtr thenType(
   1247        ParseChildBreakdown(cx, breakdown, cx->names().then, seen));
   1248    if (!thenType) {
   1249      return nullptr;
   1250    }
   1251 
   1252    CountTypePtr noFilenameType(
   1253        ParseChildBreakdown(cx, breakdown, cx->names().noFilename, seen));
   1254    if (!noFilenameType) {
   1255      return nullptr;
   1256    }
   1257 
   1258    return CountTypePtr(
   1259        cx->new_<ByFilename>(std::move(thenType), std::move(noFilenameType)));
   1260  }
   1261 
   1262  // We didn't recognize the breakdown type; complain.
   1263  UniqueChars byBytes = QuoteString(cx, by, '"');
   1264  if (!byBytes) {
   1265    return nullptr;
   1266  }
   1267 
   1268  JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
   1269                            JSMSG_DEBUG_CENSUS_BREAKDOWN, byBytes.get());
   1270  return nullptr;
   1271 }
   1272 
   1273 // Get the default census breakdown:
   1274 //
   1275 // { by: "coarseType",
   1276 //   objects: { by: "objectClass" },
   1277 //   other:   { by: "internalType" },
   1278 //   domNode: { by: "descriptiveType" }
   1279 // }
   1280 static CountTypePtr GetDefaultBreakdown(JSContext* cx) {
   1281  CountTypePtr byDomClass(cx->new_<SimpleCount>());
   1282  if (!byDomClass) {
   1283    return nullptr;
   1284  }
   1285  CountTypePtr byClass(cx->new_<SimpleCount>());
   1286  if (!byClass) {
   1287    return nullptr;
   1288  }
   1289 
   1290  CountTypePtr byClassElse(cx->new_<SimpleCount>());
   1291  if (!byClassElse) {
   1292    return nullptr;
   1293  }
   1294 
   1295  CountTypePtr objects(cx->new_<ByObjectClass>(byClass, byClassElse));
   1296  if (!objects) {
   1297    return nullptr;
   1298  }
   1299 
   1300  CountTypePtr scripts(cx->new_<SimpleCount>());
   1301  if (!scripts) {
   1302    return nullptr;
   1303  }
   1304 
   1305  CountTypePtr strings(cx->new_<SimpleCount>());
   1306  if (!strings) {
   1307    return nullptr;
   1308  }
   1309 
   1310  CountTypePtr byType(cx->new_<SimpleCount>());
   1311  if (!byType) {
   1312    return nullptr;
   1313  }
   1314 
   1315  CountTypePtr other(cx->new_<ByUbinodeType>(byType));
   1316  if (!other) {
   1317    return nullptr;
   1318  }
   1319  CountTypePtr domNode(cx->new_<ByDomObjectClass>(byDomClass));
   1320  if (!domNode) {
   1321    return nullptr;
   1322  }
   1323 
   1324  return CountTypePtr(
   1325      cx->new_<ByCoarseType>(objects, scripts, strings, other, domNode));
   1326 }
   1327 
   1328 JS_PUBLIC_API bool ParseCensusOptions(JSContext* cx, Census& census,
   1329                                      HandleObject options,
   1330                                      CountTypePtr& outResult) {
   1331  RootedValue breakdown(cx, UndefinedValue());
   1332  if (options &&
   1333      !GetProperty(cx, options, options, cx->names().breakdown, &breakdown)) {
   1334    return false;
   1335  }
   1336 
   1337  Rooted<GCVector<JSLinearString*>> seen(cx, cx);
   1338  outResult = breakdown.isUndefined() ? GetDefaultBreakdown(cx)
   1339                                      : ParseBreakdown(cx, breakdown, &seen);
   1340  return !!outResult;
   1341 }
   1342 
   1343 }  // namespace ubi
   1344 }  // namespace JS