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