/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 |