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