Coverage Report

Created: 2018-09-25 14:53

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