Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "HeapSnapshot.h"
7 :
8 : #include <google/protobuf/io/coded_stream.h>
9 : #include <google/protobuf/io/gzip_stream.h>
10 : #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
11 :
12 : #include "js/Debug.h"
13 : #include "js/TypeDecls.h"
14 : #include "js/UbiNodeBreadthFirst.h"
15 : #include "js/UbiNodeCensus.h"
16 : #include "js/UbiNodeDominatorTree.h"
17 : #include "js/UbiNodeShortestPaths.h"
18 : #include "mozilla/Attributes.h"
19 : #include "mozilla/CycleCollectedJSContext.h"
20 : #include "mozilla/devtools/AutoMemMap.h"
21 : #include "mozilla/devtools/CoreDump.pb.h"
22 : #include "mozilla/devtools/DeserializedNode.h"
23 : #include "mozilla/devtools/DominatorTree.h"
24 : #include "mozilla/devtools/FileDescriptorOutputStream.h"
25 : #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
26 : #include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
27 : #include "mozilla/dom/ChromeUtils.h"
28 : #include "mozilla/dom/ContentChild.h"
29 : #include "mozilla/dom/HeapSnapshotBinding.h"
30 : #include "mozilla/RangedPtr.h"
31 : #include "mozilla/Telemetry.h"
32 : #include "mozilla/Unused.h"
33 :
34 : #include "jsapi.h"
35 : #include "jsfriendapi.h"
36 : #include "nsCycleCollectionParticipant.h"
37 : #include "nsCRTGlue.h"
38 : #include "nsDirectoryServiceDefs.h"
39 : #include "nsIFile.h"
40 : #include "nsIOutputStream.h"
41 : #include "nsISupportsImpl.h"
42 : #include "nsNetUtil.h"
43 : #include "nsPrintfCString.h"
44 : #include "prerror.h"
45 : #include "prio.h"
46 : #include "prtypes.h"
47 :
48 : namespace mozilla {
49 : namespace devtools {
50 :
51 : using namespace JS;
52 : using namespace dom;
53 :
54 : using ::google::protobuf::io::ArrayInputStream;
55 : using ::google::protobuf::io::CodedInputStream;
56 : using ::google::protobuf::io::GzipInputStream;
57 : using ::google::protobuf::io::ZeroCopyInputStream;
58 :
59 : using JS::ubi::AtomOrTwoByteChars;
60 : using JS::ubi::ShortestPaths;
61 :
62 : MallocSizeOf
63 0 : GetCurrentThreadDebuggerMallocSizeOf()
64 : {
65 0 : auto ccjscx = CycleCollectedJSContext::Get();
66 0 : MOZ_ASSERT(ccjscx);
67 0 : auto cx = ccjscx->Context();
68 0 : MOZ_ASSERT(cx);
69 0 : auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(cx);
70 0 : MOZ_ASSERT(mallocSizeOf);
71 0 : return mallocSizeOf;
72 : }
73 :
74 : /*** Cycle Collection Boilerplate *****************************************************************/
75 :
76 0 : NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HeapSnapshot, mParent)
77 :
78 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnapshot)
79 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot)
80 :
81 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HeapSnapshot)
82 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
83 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
84 0 : NS_INTERFACE_MAP_END
85 :
86 : /* virtual */ JSObject*
87 0 : HeapSnapshot::WrapObject(JSContext* aCx, HandleObject aGivenProto)
88 : {
89 0 : return HeapSnapshotBinding::Wrap(aCx, this, aGivenProto);
90 : }
91 :
92 : /*** Reading Heap Snapshots ***********************************************************************/
93 :
94 : /* static */ already_AddRefed<HeapSnapshot>
95 0 : HeapSnapshot::Create(JSContext* cx,
96 : GlobalObject& global,
97 : const uint8_t* buffer,
98 : uint32_t size,
99 : ErrorResult& rv)
100 : {
101 0 : RefPtr<HeapSnapshot> snapshot = new HeapSnapshot(cx, global.GetAsSupports());
102 0 : if (!snapshot->init(cx, buffer, size)) {
103 0 : rv.Throw(NS_ERROR_UNEXPECTED);
104 0 : return nullptr;
105 : }
106 0 : return snapshot.forget();
107 : }
108 :
109 : template<typename MessageType>
110 : static bool
111 0 : parseMessage(ZeroCopyInputStream& stream, uint32_t sizeOfMessage, MessageType& message)
112 : {
113 : // We need to create a new `CodedInputStream` for each message so that the
114 : // 64MB limit is applied per-message rather than to the whole stream.
115 0 : CodedInputStream codedStream(&stream);
116 :
117 : // The protobuf message nesting that core dumps exhibit is dominated by
118 : // allocation stacks' frames. In the most deeply nested case, each frame has
119 : // two messages: a StackFrame message and a StackFrame::Data message. These
120 : // frames are on top of a small constant of other messages. There are a
121 : // MAX_STACK_DEPTH number of frames, so we multiply this by 3 to make room for
122 : // the two messages per frame plus some head room for the constant number of
123 : // non-dominating messages.
124 0 : codedStream.SetRecursionLimit(HeapSnapshot::MAX_STACK_DEPTH * 3);
125 :
126 0 : auto limit = codedStream.PushLimit(sizeOfMessage);
127 0 : if (NS_WARN_IF(!message.ParseFromCodedStream(&codedStream)) ||
128 0 : NS_WARN_IF(!codedStream.ConsumedEntireMessage()) ||
129 0 : NS_WARN_IF(codedStream.BytesUntilLimit() != 0))
130 : {
131 0 : return false;
132 : }
133 :
134 0 : codedStream.PopLimit(limit);
135 0 : return true;
136 : }
137 :
138 : template<typename CharT, typename InternedStringSet>
139 : struct GetOrInternStringMatcher
140 : {
141 : InternedStringSet& internedStrings;
142 :
143 0 : explicit GetOrInternStringMatcher(InternedStringSet& strings) : internedStrings(strings) { }
144 :
145 0 : const CharT* match(const std::string* str) {
146 0 : MOZ_ASSERT(str);
147 0 : size_t length = str->length() / sizeof(CharT);
148 0 : auto tempString = reinterpret_cast<const CharT*>(str->data());
149 :
150 0 : UniquePtr<CharT[], NSFreePolicy> owned(NS_strndup(tempString, length));
151 0 : if (!owned || !internedStrings.append(Move(owned)))
152 0 : return nullptr;
153 :
154 0 : return internedStrings.back().get();
155 : }
156 :
157 0 : const CharT* match(uint64_t ref) {
158 0 : if (MOZ_LIKELY(ref < internedStrings.length())) {
159 0 : auto& string = internedStrings[ref];
160 0 : MOZ_ASSERT(string);
161 0 : return string.get();
162 : }
163 :
164 0 : return nullptr;
165 : }
166 : };
167 :
168 : template<
169 : // Either char or char16_t.
170 : typename CharT,
171 : // A reference to either `internedOneByteStrings` or `internedTwoByteStrings`
172 : // if CharT is char or char16_t respectively.
173 : typename InternedStringSet>
174 : const CharT*
175 0 : HeapSnapshot::getOrInternString(InternedStringSet& internedStrings,
176 : Maybe<StringOrRef>& maybeStrOrRef)
177 : {
178 : // Incomplete message: has neither a string nor a reference to an already
179 : // interned string.
180 0 : if (MOZ_UNLIKELY(maybeStrOrRef.isNothing()))
181 0 : return nullptr;
182 :
183 0 : GetOrInternStringMatcher<CharT, InternedStringSet> m(internedStrings);
184 0 : return maybeStrOrRef->match(m);
185 : }
186 :
187 : // Get a de-duplicated string as a Maybe<StringOrRef> from the given `msg`.
188 : #define GET_STRING_OR_REF_WITH_PROP_NAMES(msg, strPropertyName, refPropertyName) \
189 : (msg.has_##refPropertyName() \
190 : ? Some(StringOrRef(msg.refPropertyName())) \
191 : : msg.has_##strPropertyName() \
192 : ? Some(StringOrRef(&msg.strPropertyName())) \
193 : : Nothing())
194 :
195 : #define GET_STRING_OR_REF(msg, property) \
196 : (msg.has_##property##ref() \
197 : ? Some(StringOrRef(msg.property##ref())) \
198 : : msg.has_##property() \
199 : ? Some(StringOrRef(&msg.property())) \
200 : : Nothing())
201 :
202 : bool
203 0 : HeapSnapshot::saveNode(const protobuf::Node& node, NodeIdSet& edgeReferents)
204 : {
205 : // NB: de-duplicated string properties must be read back and interned in the
206 : // same order here as they are written and serialized in
207 : // `CoreDumpWriter::writeNode` or else indices in references to already
208 : // serialized strings will be off.
209 :
210 0 : if (NS_WARN_IF(!node.has_id()))
211 0 : return false;
212 0 : NodeId id = node.id();
213 :
214 : // NodeIds are derived from pointers (at most 48 bits) and we rely on them
215 : // fitting into JS numbers (IEEE 754 doubles, can precisely store 53 bit
216 : // integers) despite storing them on disk as 64 bit integers.
217 0 : if (NS_WARN_IF(!JS::Value::isNumberRepresentable(id)))
218 0 : return false;
219 :
220 : // Should only deserialize each node once.
221 0 : if (NS_WARN_IF(nodes.has(id)))
222 0 : return false;
223 :
224 0 : if (NS_WARN_IF(!JS::ubi::Uint32IsValidCoarseType(node.coarsetype())))
225 0 : return false;
226 0 : auto coarseType = JS::ubi::Uint32ToCoarseType(node.coarsetype());
227 :
228 0 : Maybe<StringOrRef> typeNameOrRef = GET_STRING_OR_REF_WITH_PROP_NAMES(node, typename_, typenameref);
229 0 : auto typeName = getOrInternString<char16_t>(internedTwoByteStrings, typeNameOrRef);
230 0 : if (NS_WARN_IF(!typeName))
231 0 : return false;
232 :
233 0 : if (NS_WARN_IF(!node.has_size()))
234 0 : return false;
235 0 : uint64_t size = node.size();
236 :
237 0 : auto edgesLength = node.edges_size();
238 0 : DeserializedNode::EdgeVector edges;
239 0 : if (NS_WARN_IF(!edges.reserve(edgesLength)))
240 0 : return false;
241 0 : for (decltype(edgesLength) i = 0; i < edgesLength; i++) {
242 0 : auto& protoEdge = node.edges(i);
243 :
244 0 : if (NS_WARN_IF(!protoEdge.has_referent()))
245 0 : return false;
246 0 : NodeId referent = protoEdge.referent();
247 :
248 0 : if (NS_WARN_IF(!edgeReferents.put(referent)))
249 0 : return false;
250 :
251 0 : const char16_t* edgeName = nullptr;
252 0 : if (protoEdge.EdgeNameOrRef_case() != protobuf::Edge::EDGENAMEORREF_NOT_SET) {
253 0 : Maybe<StringOrRef> edgeNameOrRef = GET_STRING_OR_REF(protoEdge, name);
254 0 : edgeName = getOrInternString<char16_t>(internedTwoByteStrings, edgeNameOrRef);
255 0 : if (NS_WARN_IF(!edgeName))
256 0 : return false;
257 : }
258 :
259 0 : edges.infallibleAppend(DeserializedEdge(referent, edgeName));
260 : }
261 :
262 0 : Maybe<StackFrameId> allocationStack;
263 0 : if (node.has_allocationstack()) {
264 0 : StackFrameId id = 0;
265 0 : if (NS_WARN_IF(!saveStackFrame(node.allocationstack(), id)))
266 0 : return false;
267 0 : allocationStack.emplace(id);
268 : }
269 0 : MOZ_ASSERT(allocationStack.isSome() == node.has_allocationstack());
270 :
271 0 : const char* jsObjectClassName = nullptr;
272 0 : if (node.JSObjectClassNameOrRef_case() != protobuf::Node::JSOBJECTCLASSNAMEORREF_NOT_SET) {
273 0 : Maybe<StringOrRef> clsNameOrRef = GET_STRING_OR_REF(node, jsobjectclassname);
274 0 : jsObjectClassName = getOrInternString<char>(internedOneByteStrings, clsNameOrRef);
275 0 : if (NS_WARN_IF(!jsObjectClassName))
276 0 : return false;
277 : }
278 :
279 0 : const char* scriptFilename = nullptr;
280 0 : if (node.ScriptFilenameOrRef_case() != protobuf::Node::SCRIPTFILENAMEORREF_NOT_SET) {
281 0 : Maybe<StringOrRef> scriptFilenameOrRef = GET_STRING_OR_REF(node, scriptfilename);
282 0 : scriptFilename = getOrInternString<char>(internedOneByteStrings, scriptFilenameOrRef);
283 0 : if (NS_WARN_IF(!scriptFilename))
284 0 : return false;
285 : }
286 :
287 0 : if (NS_WARN_IF(!nodes.putNew(id, DeserializedNode(id, coarseType, typeName,
288 : size, Move(edges),
289 : allocationStack,
290 : jsObjectClassName,
291 : scriptFilename, *this))))
292 : {
293 0 : return false;
294 : };
295 :
296 0 : return true;
297 : }
298 :
299 : bool
300 0 : HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame,
301 : StackFrameId& outFrameId)
302 : {
303 : // NB: de-duplicated string properties must be read in the same order here as
304 : // they are written in `CoreDumpWriter::getProtobufStackFrame` or else indices
305 : // in references to already serialized strings will be off.
306 :
307 0 : if (frame.has_ref()) {
308 : // We should only get a reference to the previous frame if we have already
309 : // seen the previous frame.
310 0 : if (!frames.has(frame.ref()))
311 0 : return false;
312 :
313 0 : outFrameId = frame.ref();
314 0 : return true;
315 : }
316 :
317 : // Incomplete message.
318 0 : if (!frame.has_data())
319 0 : return false;
320 :
321 0 : auto data = frame.data();
322 :
323 0 : if (!data.has_id())
324 0 : return false;
325 0 : StackFrameId id = data.id();
326 :
327 : // This should be the first and only time we see this frame.
328 0 : if (frames.has(id))
329 0 : return false;
330 :
331 0 : if (!data.has_line())
332 0 : return false;
333 0 : uint32_t line = data.line();
334 :
335 0 : if (!data.has_column())
336 0 : return false;
337 0 : uint32_t column = data.column();
338 :
339 0 : if (!data.has_issystem())
340 0 : return false;
341 0 : bool isSystem = data.issystem();
342 :
343 0 : if (!data.has_isselfhosted())
344 0 : return false;
345 0 : bool isSelfHosted = data.isselfhosted();
346 :
347 0 : Maybe<StringOrRef> sourceOrRef = GET_STRING_OR_REF(data, source);
348 0 : auto source = getOrInternString<char16_t>(internedTwoByteStrings, sourceOrRef);
349 0 : if (!source)
350 0 : return false;
351 :
352 0 : const char16_t* functionDisplayName = nullptr;
353 0 : if (data.FunctionDisplayNameOrRef_case() !=
354 : protobuf::StackFrame_Data::FUNCTIONDISPLAYNAMEORREF_NOT_SET)
355 : {
356 0 : Maybe<StringOrRef> nameOrRef = GET_STRING_OR_REF(data, functiondisplayname);
357 0 : functionDisplayName = getOrInternString<char16_t>(internedTwoByteStrings, nameOrRef);
358 0 : if (!functionDisplayName)
359 0 : return false;
360 : }
361 :
362 0 : Maybe<StackFrameId> parent;
363 0 : if (data.has_parent()) {
364 0 : StackFrameId parentId = 0;
365 0 : if (!saveStackFrame(data.parent(), parentId))
366 0 : return false;
367 0 : parent = Some(parentId);
368 : }
369 :
370 0 : if (!frames.putNew(id, DeserializedStackFrame(id, parent, line, column,
371 : source, functionDisplayName,
372 : isSystem, isSelfHosted, *this)))
373 : {
374 0 : return false;
375 : }
376 :
377 0 : outFrameId = id;
378 0 : return true;
379 : }
380 :
381 : #undef GET_STRING_OR_REF_WITH_PROP_NAMES
382 : #undef GET_STRING_OR_REF
383 :
384 : // Because protobuf messages aren't self-delimiting, we serialize each message
385 : // preceded by its size in bytes. When deserializing, we read this size and then
386 : // limit reading from the stream to the given byte size. If we didn't, then the
387 : // first message would consume the entire stream.
388 : static bool
389 0 : readSizeOfNextMessage(ZeroCopyInputStream& stream, uint32_t* sizep)
390 : {
391 0 : MOZ_ASSERT(sizep);
392 0 : CodedInputStream codedStream(&stream);
393 0 : return codedStream.ReadVarint32(sizep) && *sizep > 0;
394 : }
395 :
396 : bool
397 0 : HeapSnapshot::init(JSContext* cx, const uint8_t* buffer, uint32_t size)
398 : {
399 0 : if (!nodes.init() || !frames.init())
400 0 : return false;
401 :
402 0 : ArrayInputStream stream(buffer, size);
403 0 : GzipInputStream gzipStream(&stream);
404 0 : uint32_t sizeOfMessage = 0;
405 :
406 : // First is the metadata.
407 :
408 0 : protobuf::Metadata metadata;
409 0 : if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
410 0 : return false;
411 0 : if (!parseMessage(gzipStream, sizeOfMessage, metadata))
412 0 : return false;
413 0 : if (metadata.has_timestamp())
414 0 : timestamp.emplace(metadata.timestamp());
415 :
416 : // Next is the root node.
417 :
418 0 : protobuf::Node root;
419 0 : if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
420 0 : return false;
421 0 : if (!parseMessage(gzipStream, sizeOfMessage, root))
422 0 : return false;
423 :
424 : // Although the id is optional in the protobuf format for future proofing, we
425 : // can't currently do anything without it.
426 0 : if (NS_WARN_IF(!root.has_id()))
427 0 : return false;
428 0 : rootId = root.id();
429 :
430 : // The set of all node ids we've found edges pointing to.
431 0 : NodeIdSet edgeReferents(cx);
432 0 : if (NS_WARN_IF(!edgeReferents.init()))
433 0 : return false;
434 :
435 0 : if (NS_WARN_IF(!saveNode(root, edgeReferents)))
436 0 : return false;
437 :
438 : // Finally, the rest of the nodes in the core dump.
439 :
440 : // Test for the end of the stream. The protobuf library gives no way to tell
441 : // the difference between an underlying read error and the stream being
442 : // done. All we can do is attempt to read the size of the next message and
443 : // extrapolate guestimations from the result of that operation.
444 0 : while (readSizeOfNextMessage(gzipStream, &sizeOfMessage)) {
445 0 : protobuf::Node node;
446 0 : if (!parseMessage(gzipStream, sizeOfMessage, node))
447 0 : return false;
448 0 : if (NS_WARN_IF(!saveNode(node, edgeReferents)))
449 0 : return false;
450 : }
451 :
452 : // Check the set of node ids referred to by edges we found and ensure that we
453 : // have the node corresponding to each id. If we don't have all of them, it is
454 : // unsafe to perform analyses of this heap snapshot.
455 0 : for (auto range = edgeReferents.all(); !range.empty(); range.popFront()) {
456 0 : if (NS_WARN_IF(!nodes.has(range.front())))
457 0 : return false;
458 : }
459 :
460 0 : return true;
461 : }
462 :
463 :
464 : /*** Heap Snapshot Analyses ***********************************************************************/
465 :
466 : void
467 0 : HeapSnapshot::TakeCensus(JSContext* cx, JS::HandleObject options,
468 : JS::MutableHandleValue rval, ErrorResult& rv)
469 : {
470 0 : JS::ubi::Census census(cx);
471 0 : if (NS_WARN_IF(!census.init())) {
472 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
473 0 : return;
474 : }
475 :
476 0 : JS::ubi::CountTypePtr rootType;
477 0 : if (NS_WARN_IF(!JS::ubi::ParseCensusOptions(cx, census, options, rootType))) {
478 0 : rv.Throw(NS_ERROR_UNEXPECTED);
479 0 : return;
480 : }
481 :
482 0 : JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
483 0 : if (NS_WARN_IF(!rootCount)) {
484 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
485 0 : return;
486 : }
487 :
488 0 : JS::ubi::CensusHandler handler(census, rootCount, GetCurrentThreadDebuggerMallocSizeOf());
489 :
490 : {
491 0 : JS::AutoCheckCannotGC nogc;
492 :
493 0 : JS::ubi::CensusTraversal traversal(cx, handler, nogc);
494 0 : if (NS_WARN_IF(!traversal.init())) {
495 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
496 0 : return;
497 : }
498 :
499 0 : if (NS_WARN_IF(!traversal.addStart(getRoot()))) {
500 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
501 0 : return;
502 : }
503 :
504 0 : if (NS_WARN_IF(!traversal.traverse())) {
505 0 : rv.Throw(NS_ERROR_UNEXPECTED);
506 0 : return;
507 : }
508 : }
509 :
510 0 : if (NS_WARN_IF(!handler.report(cx, rval))) {
511 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
512 0 : return;
513 : }
514 : }
515 :
516 : void
517 0 : HeapSnapshot::DescribeNode(JSContext* cx, JS::HandleObject breakdown, uint64_t nodeId,
518 : JS::MutableHandleValue rval, ErrorResult& rv) {
519 0 : MOZ_ASSERT(breakdown);
520 0 : JS::RootedValue breakdownVal(cx, JS::ObjectValue(*breakdown));
521 0 : JS::ubi::CountTypePtr rootType = JS::ubi::ParseBreakdown(cx, breakdownVal);
522 0 : if (NS_WARN_IF(!rootType)) {
523 0 : rv.Throw(NS_ERROR_UNEXPECTED);
524 0 : return;
525 : }
526 :
527 0 : JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
528 0 : if (NS_WARN_IF(!rootCount)) {
529 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
530 0 : return;
531 : }
532 :
533 0 : JS::ubi::Node::Id id(nodeId);
534 0 : Maybe<JS::ubi::Node> node = getNodeById(id);
535 0 : if (NS_WARN_IF(node.isNothing())) {
536 0 : rv.Throw(NS_ERROR_INVALID_ARG);
537 0 : return;
538 : }
539 :
540 0 : MallocSizeOf mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
541 0 : if (NS_WARN_IF(!rootCount->count(mallocSizeOf, *node))) {
542 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
543 0 : return;
544 : }
545 :
546 0 : if (NS_WARN_IF(!rootCount->report(cx, rval))) {
547 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
548 0 : return;
549 : }
550 : }
551 :
552 :
553 : already_AddRefed<DominatorTree>
554 0 : HeapSnapshot::ComputeDominatorTree(ErrorResult& rv)
555 : {
556 0 : Maybe<JS::ubi::DominatorTree> maybeTree;
557 : {
558 0 : auto ccjscx = CycleCollectedJSContext::Get();
559 0 : MOZ_ASSERT(ccjscx);
560 0 : auto cx = ccjscx->Context();
561 0 : MOZ_ASSERT(cx);
562 0 : JS::AutoCheckCannotGC nogc(cx);
563 0 : maybeTree = JS::ubi::DominatorTree::Create(cx, nogc, getRoot());
564 : }
565 :
566 0 : if (NS_WARN_IF(maybeTree.isNothing())) {
567 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
568 0 : return nullptr;
569 : }
570 :
571 0 : return MakeAndAddRef<DominatorTree>(Move(*maybeTree), this, mParent);
572 : }
573 :
574 : void
575 0 : HeapSnapshot::ComputeShortestPaths(JSContext*cx, uint64_t start,
576 : const Sequence<uint64_t>& targets,
577 : uint64_t maxNumPaths,
578 : JS::MutableHandleObject results,
579 : ErrorResult& rv)
580 : {
581 : // First ensure that our inputs are valid.
582 :
583 0 : if (NS_WARN_IF(maxNumPaths == 0)) {
584 0 : rv.Throw(NS_ERROR_INVALID_ARG);
585 0 : return;
586 : }
587 :
588 0 : Maybe<JS::ubi::Node> startNode = getNodeById(start);
589 0 : if (NS_WARN_IF(startNode.isNothing())) {
590 0 : rv.Throw(NS_ERROR_INVALID_ARG);
591 0 : return;
592 : }
593 :
594 0 : if (NS_WARN_IF(targets.Length() == 0)) {
595 0 : rv.Throw(NS_ERROR_INVALID_ARG);
596 0 : return;
597 : }
598 :
599 : // Aggregate the targets into a set and make sure that they exist in the heap
600 : // snapshot.
601 :
602 0 : JS::ubi::NodeSet targetsSet;
603 0 : if (NS_WARN_IF(!targetsSet.init())) {
604 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
605 0 : return;
606 : }
607 :
608 0 : for (const auto& target : targets) {
609 0 : Maybe<JS::ubi::Node> targetNode = getNodeById(target);
610 0 : if (NS_WARN_IF(targetNode.isNothing())) {
611 0 : rv.Throw(NS_ERROR_INVALID_ARG);
612 0 : return;
613 : }
614 :
615 0 : if (NS_WARN_IF(!targetsSet.put(*targetNode))) {
616 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
617 0 : return;
618 : }
619 : }
620 :
621 : // Walk the heap graph and find the shortest paths.
622 :
623 0 : Maybe<ShortestPaths> maybeShortestPaths;
624 : {
625 0 : JS::AutoCheckCannotGC nogc(cx);
626 0 : maybeShortestPaths = ShortestPaths::Create(cx, nogc, maxNumPaths, *startNode,
627 0 : Move(targetsSet));
628 : }
629 :
630 0 : if (NS_WARN_IF(maybeShortestPaths.isNothing())) {
631 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
632 0 : return;
633 : }
634 :
635 0 : auto& shortestPaths = *maybeShortestPaths;
636 :
637 : // Convert the results into a Map object mapping target node IDs to arrays of
638 : // paths found.
639 :
640 0 : RootedObject resultsMap(cx, JS::NewMapObject(cx));
641 0 : if (NS_WARN_IF(!resultsMap)) {
642 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
643 0 : return;
644 : }
645 :
646 0 : for (auto range = shortestPaths.eachTarget(); !range.empty(); range.popFront()) {
647 0 : JS::RootedValue key(cx, JS::NumberValue(range.front().identifier()));
648 0 : JS::AutoValueVector paths(cx);
649 :
650 0 : bool ok = shortestPaths.forEachPath(range.front(), [&](JS::ubi::Path& path) {
651 0 : JS::AutoValueVector pathValues(cx);
652 :
653 0 : for (JS::ubi::BackEdge* edge : path) {
654 0 : JS::RootedObject pathPart(cx, JS_NewPlainObject(cx));
655 0 : if (!pathPart) {
656 0 : return false;
657 : }
658 :
659 0 : JS::RootedValue predecessor(cx, NumberValue(edge->predecessor().identifier()));
660 0 : if (!JS_DefineProperty(cx, pathPart, "predecessor", predecessor, JSPROP_ENUMERATE)) {
661 0 : return false;
662 : }
663 :
664 0 : RootedValue edgeNameVal(cx, NullValue());
665 0 : if (edge->name()) {
666 0 : RootedString edgeName(cx, JS_AtomizeUCString(cx, edge->name().get()));
667 0 : if (!edgeName) {
668 0 : return false;
669 : }
670 0 : edgeNameVal = StringValue(edgeName);
671 : }
672 :
673 0 : if (!JS_DefineProperty(cx, pathPart, "edge", edgeNameVal, JSPROP_ENUMERATE)) {
674 0 : return false;
675 : }
676 :
677 0 : if (!pathValues.append(ObjectValue(*pathPart))) {
678 0 : return false;
679 : }
680 : }
681 :
682 0 : RootedObject pathObj(cx, JS_NewArrayObject(cx, pathValues));
683 0 : return pathObj && paths.append(ObjectValue(*pathObj));
684 0 : });
685 :
686 0 : if (NS_WARN_IF(!ok)) {
687 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
688 0 : return;
689 : }
690 :
691 0 : JS::RootedObject pathsArray(cx, JS_NewArrayObject(cx, paths));
692 0 : if (NS_WARN_IF(!pathsArray)) {
693 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
694 0 : return;
695 : }
696 :
697 0 : JS::RootedValue pathsVal(cx, ObjectValue(*pathsArray));
698 0 : if (NS_WARN_IF(!JS::MapSet(cx, resultsMap, key, pathsVal))) {
699 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
700 0 : return;
701 : }
702 : }
703 :
704 0 : results.set(resultsMap);
705 : }
706 :
707 : /*** Saving Heap Snapshots ************************************************************************/
708 :
709 : // If we are only taking a snapshot of the heap affected by the given set of
710 : // globals, find the set of compartments the globals are allocated
711 : // within. Returns false on OOM failure.
712 : static bool
713 0 : PopulateCompartmentsWithGlobals(CompartmentSet& compartments, AutoObjectVector& globals)
714 : {
715 0 : if (!compartments.init())
716 0 : return false;
717 :
718 0 : unsigned length = globals.length();
719 0 : for (unsigned i = 0; i < length; i++) {
720 0 : if (!compartments.put(GetObjectCompartment(globals[i])))
721 0 : return false;
722 : }
723 :
724 0 : return true;
725 : }
726 :
727 : // Add the given set of globals as explicit roots in the given roots
728 : // list. Returns false on OOM failure.
729 : static bool
730 0 : AddGlobalsAsRoots(AutoObjectVector& globals, ubi::RootList& roots)
731 : {
732 0 : unsigned length = globals.length();
733 0 : for (unsigned i = 0; i < length; i++) {
734 0 : if (!roots.addRoot(ubi::Node(globals[i].get()),
735 : u"heap snapshot global"))
736 : {
737 0 : return false;
738 : }
739 : }
740 0 : return true;
741 : }
742 :
743 : // Choose roots and limits for a traversal, given `boundaries`. Set `roots` to
744 : // the set of nodes within the boundaries that are referred to by nodes
745 : // outside. If `boundaries` does not include all JS compartments, initialize
746 : // `compartments` to the set of included compartments; otherwise, leave
747 : // `compartments` uninitialized. (You can use compartments.initialized() to
748 : // check.)
749 : //
750 : // If `boundaries` is incoherent, or we encounter an error while trying to
751 : // handle it, or we run out of memory, set `rv` appropriately and return
752 : // `false`.
753 : static bool
754 0 : EstablishBoundaries(JSContext* cx,
755 : ErrorResult& rv,
756 : const HeapSnapshotBoundaries& boundaries,
757 : ubi::RootList& roots,
758 : CompartmentSet& compartments)
759 : {
760 0 : MOZ_ASSERT(!roots.initialized());
761 0 : MOZ_ASSERT(!compartments.initialized());
762 :
763 0 : bool foundBoundaryProperty = false;
764 :
765 0 : if (boundaries.mRuntime.WasPassed()) {
766 0 : foundBoundaryProperty = true;
767 :
768 0 : if (!boundaries.mRuntime.Value()) {
769 0 : rv.Throw(NS_ERROR_INVALID_ARG);
770 0 : return false;
771 : }
772 :
773 0 : if (!roots.init()) {
774 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
775 0 : return false;
776 : }
777 : }
778 :
779 0 : if (boundaries.mDebugger.WasPassed()) {
780 0 : if (foundBoundaryProperty) {
781 0 : rv.Throw(NS_ERROR_INVALID_ARG);
782 0 : return false;
783 : }
784 0 : foundBoundaryProperty = true;
785 :
786 0 : JSObject* dbgObj = boundaries.mDebugger.Value();
787 0 : if (!dbgObj || !dbg::IsDebugger(*dbgObj)) {
788 0 : rv.Throw(NS_ERROR_INVALID_ARG);
789 0 : return false;
790 : }
791 :
792 0 : AutoObjectVector globals(cx);
793 0 : if (!dbg::GetDebuggeeGlobals(cx, *dbgObj, globals) ||
794 0 : !PopulateCompartmentsWithGlobals(compartments, globals) ||
795 0 : !roots.init(compartments) ||
796 0 : !AddGlobalsAsRoots(globals, roots))
797 : {
798 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
799 0 : return false;
800 : }
801 : }
802 :
803 0 : if (boundaries.mGlobals.WasPassed()) {
804 0 : if (foundBoundaryProperty) {
805 0 : rv.Throw(NS_ERROR_INVALID_ARG);
806 0 : return false;
807 : }
808 0 : foundBoundaryProperty = true;
809 :
810 0 : uint32_t length = boundaries.mGlobals.Value().Length();
811 0 : if (length == 0) {
812 0 : rv.Throw(NS_ERROR_INVALID_ARG);
813 0 : return false;
814 : }
815 :
816 0 : AutoObjectVector globals(cx);
817 0 : for (uint32_t i = 0; i < length; i++) {
818 0 : JSObject* global = boundaries.mGlobals.Value().ElementAt(i);
819 0 : if (!JS_IsGlobalObject(global)) {
820 0 : rv.Throw(NS_ERROR_INVALID_ARG);
821 0 : return false;
822 : }
823 0 : if (!globals.append(global)) {
824 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
825 0 : return false;
826 : }
827 : }
828 :
829 0 : if (!PopulateCompartmentsWithGlobals(compartments, globals) ||
830 0 : !roots.init(compartments) ||
831 0 : !AddGlobalsAsRoots(globals, roots))
832 : {
833 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
834 0 : return false;
835 : }
836 : }
837 :
838 0 : if (!foundBoundaryProperty) {
839 0 : rv.Throw(NS_ERROR_INVALID_ARG);
840 0 : return false;
841 : }
842 :
843 0 : MOZ_ASSERT(roots.initialized());
844 0 : MOZ_ASSERT_IF(boundaries.mDebugger.WasPassed(), compartments.initialized());
845 0 : MOZ_ASSERT_IF(boundaries.mGlobals.WasPassed(), compartments.initialized());
846 0 : return true;
847 : }
848 :
849 :
850 : // A variant covering all the various two-byte strings that we can get from the
851 : // ubi::Node API.
852 0 : class TwoByteString : public Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>
853 : {
854 : using Base = Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>;
855 :
856 : struct AsTwoByteStringMatcher
857 : {
858 0 : TwoByteString match(JSAtom* atom) {
859 0 : return TwoByteString(atom);
860 : }
861 :
862 0 : TwoByteString match(const char16_t* chars) {
863 0 : return TwoByteString(chars);
864 : }
865 : };
866 :
867 : struct IsNonNullMatcher
868 : {
869 : template<typename T>
870 0 : bool match(const T& t) { return t != nullptr; }
871 : };
872 :
873 : struct LengthMatcher
874 : {
875 0 : size_t match(JSAtom* atom) {
876 0 : MOZ_ASSERT(atom);
877 0 : JS::ubi::AtomOrTwoByteChars s(atom);
878 0 : return s.length();
879 : }
880 :
881 0 : size_t match(const char16_t* chars) {
882 0 : MOZ_ASSERT(chars);
883 0 : return NS_strlen(chars);
884 : }
885 :
886 0 : size_t match(const JS::ubi::EdgeName& ptr) {
887 0 : MOZ_ASSERT(ptr);
888 0 : return NS_strlen(ptr.get());
889 : }
890 : };
891 :
892 : struct CopyToBufferMatcher
893 : {
894 : RangedPtr<char16_t> destination;
895 : size_t maxLength;
896 :
897 0 : CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength)
898 0 : : destination(destination)
899 0 : , maxLength(maxLength)
900 0 : { }
901 :
902 0 : size_t match(JS::ubi::EdgeName& ptr) {
903 0 : return ptr ? match(ptr.get()) : 0;
904 : }
905 :
906 0 : size_t match(JSAtom* atom) {
907 0 : MOZ_ASSERT(atom);
908 0 : JS::ubi::AtomOrTwoByteChars s(atom);
909 0 : return s.copyToBuffer(destination, maxLength);
910 : }
911 :
912 0 : size_t match(const char16_t* chars) {
913 0 : MOZ_ASSERT(chars);
914 0 : JS::ubi::AtomOrTwoByteChars s(chars);
915 0 : return s.copyToBuffer(destination, maxLength);
916 : }
917 : };
918 :
919 : public:
920 : template<typename T>
921 0 : MOZ_IMPLICIT TwoByteString(T&& rhs) : Base(Forward<T>(rhs)) { }
922 :
923 : template<typename T>
924 : TwoByteString& operator=(T&& rhs) {
925 : MOZ_ASSERT(this != &rhs, "self-move disallowed");
926 : this->~TwoByteString();
927 : new (this) TwoByteString(Forward<T>(rhs));
928 : return *this;
929 : }
930 :
931 : TwoByteString(const TwoByteString&) = delete;
932 : TwoByteString& operator=(const TwoByteString&) = delete;
933 :
934 : // Rewrap the inner value of a JS::ubi::AtomOrTwoByteChars as a TwoByteString.
935 0 : static TwoByteString from(JS::ubi::AtomOrTwoByteChars&& s) {
936 : AsTwoByteStringMatcher m;
937 0 : return s.match(m);
938 : }
939 :
940 : // Returns true if the given TwoByteString is non-null, false otherwise.
941 0 : bool isNonNull() const {
942 : IsNonNullMatcher m;
943 0 : return match(m);
944 : }
945 :
946 : // Return the length of the string, 0 if it is null.
947 0 : size_t length() const {
948 : LengthMatcher m;
949 0 : return match(m);
950 : }
951 :
952 : // Copy the contents of a TwoByteString into the provided buffer. The buffer
953 : // is NOT null terminated. The number of characters written is returned.
954 0 : size_t copyToBuffer(RangedPtr<char16_t> destination, size_t maxLength) {
955 0 : CopyToBufferMatcher m(destination, maxLength);
956 0 : return match(m);
957 : }
958 :
959 : struct HashPolicy;
960 : };
961 :
962 : // A hashing policy for TwoByteString.
963 : //
964 : // Atoms are pointer hashed and use pointer equality, which means that we
965 : // tolerate some duplication across atoms and the other two types of two-byte
966 : // strings. In practice, we expect the amount of this duplication to be very low
967 : // because each type is generally a different semantic thing in addition to
968 : // having a slightly different representation. For example, the set of edge
969 : // names and the set stack frames' source names naturally tend not to overlap
970 : // very much if at all.
971 : struct TwoByteString::HashPolicy {
972 : using Lookup = TwoByteString;
973 :
974 : struct HashingMatcher {
975 0 : js::HashNumber match(const JSAtom* atom) {
976 0 : return js::DefaultHasher<const JSAtom*>::hash(atom);
977 : }
978 :
979 0 : js::HashNumber match(const char16_t* chars) {
980 0 : MOZ_ASSERT(chars);
981 0 : auto length = NS_strlen(chars);
982 0 : return HashString(chars, length);
983 : }
984 :
985 0 : js::HashNumber match(const JS::ubi::EdgeName& ptr) {
986 0 : MOZ_ASSERT(ptr);
987 0 : return match(ptr.get());
988 : }
989 : };
990 :
991 0 : static js::HashNumber hash(const Lookup& l) {
992 : HashingMatcher hasher;
993 0 : return l.match(hasher);
994 : }
995 :
996 : struct EqualityMatcher {
997 : const TwoByteString& rhs;
998 0 : explicit EqualityMatcher(const TwoByteString& rhs) : rhs(rhs) { }
999 :
1000 0 : bool match(const JSAtom* atom) {
1001 0 : return rhs.is<JSAtom*>() && rhs.as<JSAtom*>() == atom;
1002 : }
1003 :
1004 0 : bool match(const char16_t* chars) {
1005 0 : MOZ_ASSERT(chars);
1006 :
1007 0 : const char16_t* rhsChars = nullptr;
1008 0 : if (rhs.is<const char16_t*>())
1009 0 : rhsChars = rhs.as<const char16_t*>();
1010 0 : else if (rhs.is<JS::ubi::EdgeName>())
1011 0 : rhsChars = rhs.as<JS::ubi::EdgeName>().get();
1012 : else
1013 0 : return false;
1014 0 : MOZ_ASSERT(rhsChars);
1015 :
1016 0 : auto length = NS_strlen(chars);
1017 0 : if (NS_strlen(rhsChars) != length)
1018 0 : return false;
1019 :
1020 0 : return memcmp(chars, rhsChars, length * sizeof(char16_t)) == 0;
1021 : }
1022 :
1023 0 : bool match(const JS::ubi::EdgeName& ptr) {
1024 0 : MOZ_ASSERT(ptr);
1025 0 : return match(ptr.get());
1026 : }
1027 : };
1028 :
1029 0 : static bool match(const TwoByteString& k, const Lookup& l) {
1030 0 : EqualityMatcher eq(l);
1031 0 : return k.match(eq);
1032 : }
1033 :
1034 : static void rekey(TwoByteString& k, TwoByteString&& newKey) {
1035 : k = Move(newKey);
1036 : }
1037 : };
1038 :
1039 : // Returns whether `edge` should be included in a heap snapshot of
1040 : // `compartments`. The optional `policy` out-param is set to INCLUDE_EDGES
1041 : // if we want to include the referent's edges, or EXCLUDE_EDGES if we don't
1042 : // want to include them.
1043 : static bool
1044 0 : ShouldIncludeEdge(JS::CompartmentSet* compartments,
1045 : const ubi::Node& origin, const ubi::Edge& edge,
1046 : CoreDumpWriter::EdgePolicy* policy = nullptr)
1047 : {
1048 0 : if (policy) {
1049 0 : *policy = CoreDumpWriter::INCLUDE_EDGES;
1050 : }
1051 :
1052 0 : if (!compartments) {
1053 : // We aren't targeting a particular set of compartments, so serialize all the
1054 : // things!
1055 0 : return true;
1056 : }
1057 :
1058 : // We are targeting a particular set of compartments. If this node is in our target
1059 : // set, serialize it and all of its edges. If this node is _not_ in our
1060 : // target set, we also serialize under the assumption that it is a shared
1061 : // resource being used by something in our target compartments since we reached it
1062 : // by traversing the heap graph. However, we do not serialize its outgoing
1063 : // edges and we abandon further traversal from this node.
1064 : //
1065 : // If the node does not belong to any compartment, we also serialize its outgoing
1066 : // edges. This case is relevant for Shapes: they don't belong to a specific
1067 : // compartment and contain edges to parent/kids Shapes we want to include. Note
1068 : // that these Shapes may contain pointers into our target compartment (the
1069 : // Shape's getter/setter JSObjects). However, we do not serialize nodes in other
1070 : // compartments that are reachable from these non-compartment nodes.
1071 :
1072 0 : JSCompartment* compartment = edge.referent.compartment();
1073 :
1074 0 : if (!compartment || compartments->has(compartment)) {
1075 0 : return true;
1076 : }
1077 :
1078 0 : if (policy) {
1079 0 : *policy = CoreDumpWriter::EXCLUDE_EDGES;
1080 : }
1081 :
1082 0 : return !!origin.compartment();
1083 : }
1084 :
1085 : // A `CoreDumpWriter` that serializes nodes to protobufs and writes them to the
1086 : // given `ZeroCopyOutputStream`.
1087 : class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter
1088 : {
1089 : using FrameSet = js::HashSet<uint64_t>;
1090 : using TwoByteStringMap = js::HashMap<TwoByteString, uint64_t, TwoByteString::HashPolicy>;
1091 : using OneByteStringMap = js::HashMap<const char*, uint64_t>;
1092 :
1093 : JSContext* cx;
1094 : bool wantNames;
1095 : // The set of |JS::ubi::StackFrame::identifier()|s that have already been
1096 : // serialized and written to the core dump.
1097 : FrameSet framesAlreadySerialized;
1098 : // The set of two-byte strings that have already been serialized and written
1099 : // to the core dump.
1100 : TwoByteStringMap twoByteStringsAlreadySerialized;
1101 : // The set of one-byte strings that have already been serialized and written
1102 : // to the core dump.
1103 : OneByteStringMap oneByteStringsAlreadySerialized;
1104 :
1105 : ::google::protobuf::io::ZeroCopyOutputStream& stream;
1106 :
1107 : JS::CompartmentSet* compartments;
1108 :
1109 0 : bool writeMessage(const ::google::protobuf::MessageLite& message) {
1110 : // We have to create a new CodedOutputStream when writing each message so
1111 : // that the 64MB size limit used by Coded{Output,Input}Stream to prevent
1112 : // integer overflow is enforced per message rather than on the whole stream.
1113 0 : ::google::protobuf::io::CodedOutputStream codedStream(&stream);
1114 0 : codedStream.WriteVarint32(message.ByteSize());
1115 0 : message.SerializeWithCachedSizes(&codedStream);
1116 0 : return !codedStream.HadError();
1117 : }
1118 :
1119 : // Attach the full two-byte string or a reference to a two-byte string that
1120 : // has already been serialized to a protobuf message.
1121 : template <typename SetStringFunction,
1122 : typename SetRefFunction>
1123 0 : bool attachTwoByteString(TwoByteString& string, SetStringFunction setString,
1124 : SetRefFunction setRef) {
1125 0 : auto ptr = twoByteStringsAlreadySerialized.lookupForAdd(string);
1126 0 : if (ptr) {
1127 0 : setRef(ptr->value());
1128 0 : return true;
1129 : }
1130 :
1131 0 : auto length = string.length();
1132 0 : auto stringData = MakeUnique<std::string>(length * sizeof(char16_t), '\0');
1133 0 : if (!stringData)
1134 0 : return false;
1135 :
1136 0 : auto buf = const_cast<char16_t*>(reinterpret_cast<const char16_t*>(stringData->data()));
1137 0 : string.copyToBuffer(RangedPtr<char16_t>(buf, length), length);
1138 :
1139 0 : uint64_t ref = twoByteStringsAlreadySerialized.count();
1140 0 : if (!twoByteStringsAlreadySerialized.add(ptr, Move(string), ref))
1141 0 : return false;
1142 :
1143 0 : setString(stringData.release());
1144 0 : return true;
1145 : }
1146 :
1147 : // Attach the full one-byte string or a reference to a one-byte string that
1148 : // has already been serialized to a protobuf message.
1149 : template <typename SetStringFunction,
1150 : typename SetRefFunction>
1151 0 : bool attachOneByteString(const char* string, SetStringFunction setString,
1152 : SetRefFunction setRef) {
1153 0 : auto ptr = oneByteStringsAlreadySerialized.lookupForAdd(string);
1154 0 : if (ptr) {
1155 0 : setRef(ptr->value());
1156 0 : return true;
1157 : }
1158 :
1159 0 : auto length = strlen(string);
1160 0 : auto stringData = MakeUnique<std::string>(string, length);
1161 0 : if (!stringData)
1162 0 : return false;
1163 :
1164 0 : uint64_t ref = oneByteStringsAlreadySerialized.count();
1165 0 : if (!oneByteStringsAlreadySerialized.add(ptr, string, ref))
1166 0 : return false;
1167 :
1168 0 : setString(stringData.release());
1169 0 : return true;
1170 : }
1171 :
1172 0 : protobuf::StackFrame* getProtobufStackFrame(JS::ubi::StackFrame& frame,
1173 : size_t depth = 1) {
1174 : // NB: de-duplicated string properties must be written in the same order
1175 : // here as they are read in `HeapSnapshot::saveStackFrame` or else indices
1176 : // in references to already serialized strings will be off.
1177 :
1178 0 : MOZ_ASSERT(frame,
1179 : "null frames should be represented as the lack of a serialized "
1180 : "stack frame");
1181 :
1182 0 : auto id = frame.identifier();
1183 0 : auto protobufStackFrame = MakeUnique<protobuf::StackFrame>();
1184 0 : if (!protobufStackFrame)
1185 0 : return nullptr;
1186 :
1187 0 : if (framesAlreadySerialized.has(id)) {
1188 0 : protobufStackFrame->set_ref(id);
1189 0 : return protobufStackFrame.release();
1190 : }
1191 :
1192 0 : auto data = MakeUnique<protobuf::StackFrame_Data>();
1193 0 : if (!data)
1194 0 : return nullptr;
1195 :
1196 0 : data->set_id(id);
1197 0 : data->set_line(frame.line());
1198 0 : data->set_column(frame.column());
1199 0 : data->set_issystem(frame.isSystem());
1200 0 : data->set_isselfhosted(frame.isSelfHosted(cx));
1201 :
1202 0 : auto dupeSource = TwoByteString::from(frame.source());
1203 0 : if (!attachTwoByteString(dupeSource,
1204 0 : [&] (std::string* source) { data->set_allocated_source(source); },
1205 0 : [&] (uint64_t ref) { data->set_sourceref(ref); }))
1206 : {
1207 0 : return nullptr;
1208 : }
1209 :
1210 0 : auto dupeName = TwoByteString::from(frame.functionDisplayName());
1211 0 : if (dupeName.isNonNull()) {
1212 0 : if (!attachTwoByteString(dupeName,
1213 0 : [&] (std::string* name) { data->set_allocated_functiondisplayname(name); },
1214 0 : [&] (uint64_t ref) { data->set_functiondisplaynameref(ref); }))
1215 : {
1216 0 : return nullptr;
1217 : }
1218 : }
1219 :
1220 0 : auto parent = frame.parent();
1221 0 : if (parent && depth < HeapSnapshot::MAX_STACK_DEPTH) {
1222 0 : auto protobufParent = getProtobufStackFrame(parent, depth + 1);
1223 0 : if (!protobufParent)
1224 0 : return nullptr;
1225 0 : data->set_allocated_parent(protobufParent);
1226 : }
1227 :
1228 0 : protobufStackFrame->set_allocated_data(data.release());
1229 :
1230 0 : if (!framesAlreadySerialized.put(id))
1231 0 : return nullptr;
1232 :
1233 0 : return protobufStackFrame.release();
1234 : }
1235 :
1236 : public:
1237 0 : StreamWriter(JSContext* cx,
1238 : ::google::protobuf::io::ZeroCopyOutputStream& stream,
1239 : bool wantNames,
1240 : JS::CompartmentSet* compartments)
1241 0 : : cx(cx)
1242 : , wantNames(wantNames)
1243 : , framesAlreadySerialized(cx)
1244 : , twoByteStringsAlreadySerialized(cx)
1245 : , oneByteStringsAlreadySerialized(cx)
1246 : , stream(stream)
1247 0 : , compartments(compartments)
1248 0 : { }
1249 :
1250 0 : bool init() {
1251 0 : return framesAlreadySerialized.init() &&
1252 0 : twoByteStringsAlreadySerialized.init() &&
1253 0 : oneByteStringsAlreadySerialized.init();
1254 : }
1255 :
1256 0 : ~StreamWriter() override { }
1257 :
1258 0 : virtual bool writeMetadata(uint64_t timestamp) final {
1259 0 : protobuf::Metadata metadata;
1260 0 : metadata.set_timestamp(timestamp);
1261 0 : return writeMessage(metadata);
1262 : }
1263 :
1264 0 : virtual bool writeNode(const JS::ubi::Node& ubiNode,
1265 : EdgePolicy includeEdges) override final {
1266 : // NB: de-duplicated string properties must be written in the same order
1267 : // here as they are read in `HeapSnapshot::saveNode` or else indices in
1268 : // references to already serialized strings will be off.
1269 :
1270 0 : protobuf::Node protobufNode;
1271 0 : protobufNode.set_id(ubiNode.identifier());
1272 :
1273 0 : protobufNode.set_coarsetype(JS::ubi::CoarseTypeToUint32(ubiNode.coarseType()));
1274 :
1275 0 : auto typeName = TwoByteString(ubiNode.typeName());
1276 0 : if (NS_WARN_IF(!attachTwoByteString(typeName,
1277 : [&] (std::string* name) { protobufNode.set_allocated_typename_(name); },
1278 : [&] (uint64_t ref) { protobufNode.set_typenameref(ref); })))
1279 : {
1280 0 : return false;
1281 : }
1282 :
1283 0 : mozilla::MallocSizeOf mallocSizeOf = dbg::GetDebuggerMallocSizeOf(cx);
1284 0 : MOZ_ASSERT(mallocSizeOf);
1285 0 : protobufNode.set_size(ubiNode.size(mallocSizeOf));
1286 :
1287 0 : if (includeEdges) {
1288 0 : auto edges = ubiNode.edges(cx, wantNames);
1289 0 : if (NS_WARN_IF(!edges))
1290 0 : return false;
1291 :
1292 0 : for ( ; !edges->empty(); edges->popFront()) {
1293 0 : ubi::Edge& ubiEdge = edges->front();
1294 0 : if (!ShouldIncludeEdge(compartments, ubiNode, ubiEdge)) {
1295 0 : continue;
1296 : }
1297 :
1298 0 : protobuf::Edge* protobufEdge = protobufNode.add_edges();
1299 0 : if (NS_WARN_IF(!protobufEdge)) {
1300 0 : return false;
1301 : }
1302 :
1303 0 : protobufEdge->set_referent(ubiEdge.referent.identifier());
1304 :
1305 0 : if (wantNames && ubiEdge.name) {
1306 0 : TwoByteString edgeName(Move(ubiEdge.name));
1307 0 : if (NS_WARN_IF(!attachTwoByteString(edgeName,
1308 : [&] (std::string* name) { protobufEdge->set_allocated_name(name); },
1309 : [&] (uint64_t ref) { protobufEdge->set_nameref(ref); })))
1310 : {
1311 0 : return false;
1312 : }
1313 : }
1314 : }
1315 : }
1316 :
1317 0 : if (ubiNode.hasAllocationStack()) {
1318 0 : auto ubiStackFrame = ubiNode.allocationStack();
1319 0 : auto protoStackFrame = getProtobufStackFrame(ubiStackFrame);
1320 0 : if (NS_WARN_IF(!protoStackFrame))
1321 0 : return false;
1322 0 : protobufNode.set_allocated_allocationstack(protoStackFrame);
1323 : }
1324 :
1325 0 : if (auto className = ubiNode.jsObjectClassName()) {
1326 0 : if (NS_WARN_IF(!attachOneByteString(className,
1327 : [&] (std::string* name) { protobufNode.set_allocated_jsobjectclassname(name); },
1328 : [&] (uint64_t ref) { protobufNode.set_jsobjectclassnameref(ref); })))
1329 : {
1330 0 : return false;
1331 : }
1332 : }
1333 :
1334 0 : if (auto scriptFilename = ubiNode.scriptFilename()) {
1335 0 : if (NS_WARN_IF(!attachOneByteString(scriptFilename,
1336 : [&] (std::string* name) { protobufNode.set_allocated_scriptfilename(name); },
1337 : [&] (uint64_t ref) { protobufNode.set_scriptfilenameref(ref); })))
1338 : {
1339 0 : return false;
1340 : }
1341 : }
1342 :
1343 0 : return writeMessage(protobufNode);
1344 : }
1345 : };
1346 :
1347 : // A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a
1348 : // core dump.
1349 : class MOZ_STACK_CLASS HeapSnapshotHandler
1350 : {
1351 : CoreDumpWriter& writer;
1352 : JS::CompartmentSet* compartments;
1353 :
1354 : public:
1355 : // For telemetry.
1356 : uint32_t nodeCount;
1357 : uint32_t edgeCount;
1358 :
1359 0 : HeapSnapshotHandler(CoreDumpWriter& writer,
1360 : JS::CompartmentSet* compartments)
1361 0 : : writer(writer),
1362 0 : compartments(compartments)
1363 0 : { }
1364 :
1365 : // JS::ubi::BreadthFirst handler interface.
1366 :
1367 : class NodeData { };
1368 : typedef JS::ubi::BreadthFirst<HeapSnapshotHandler> Traversal;
1369 0 : bool operator() (Traversal& traversal,
1370 : JS::ubi::Node origin,
1371 : const JS::ubi::Edge& edge,
1372 : NodeData*,
1373 : bool first)
1374 : {
1375 0 : edgeCount++;
1376 :
1377 : // We're only interested in the first time we reach edge.referent, not in
1378 : // every edge arriving at that node. "But, don't we want to serialize every
1379 : // edge in the heap graph?" you ask. Don't worry! This edge is still
1380 : // serialized into the core dump. Serializing a node also serializes each of
1381 : // its edges, and if we are traversing a given edge, we must have already
1382 : // visited and serialized the origin node and its edges.
1383 0 : if (!first)
1384 0 : return true;
1385 :
1386 : CoreDumpWriter::EdgePolicy policy;
1387 0 : if (!ShouldIncludeEdge(compartments, origin, edge, &policy))
1388 0 : return true;
1389 :
1390 0 : nodeCount++;
1391 :
1392 0 : if (policy == CoreDumpWriter::EXCLUDE_EDGES)
1393 0 : traversal.abandonReferent();
1394 :
1395 0 : return writer.writeNode(edge.referent, policy);
1396 : }
1397 : };
1398 :
1399 :
1400 : bool
1401 0 : WriteHeapGraph(JSContext* cx,
1402 : const JS::ubi::Node& node,
1403 : CoreDumpWriter& writer,
1404 : bool wantNames,
1405 : JS::CompartmentSet* compartments,
1406 : JS::AutoCheckCannotGC& noGC,
1407 : uint32_t& outNodeCount,
1408 : uint32_t& outEdgeCount)
1409 : {
1410 : // Serialize the starting node to the core dump.
1411 :
1412 0 : if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
1413 0 : return false;
1414 : }
1415 :
1416 : // Walk the heap graph starting from the given node and serialize it into the
1417 : // core dump.
1418 :
1419 0 : HeapSnapshotHandler handler(writer, compartments);
1420 0 : HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
1421 0 : if (!traversal.init())
1422 0 : return false;
1423 0 : traversal.wantNames = wantNames;
1424 :
1425 0 : bool ok = traversal.addStartVisited(node) &&
1426 0 : traversal.traverse();
1427 :
1428 0 : if (ok) {
1429 0 : outNodeCount = handler.nodeCount;
1430 0 : outEdgeCount = handler.edgeCount;
1431 : }
1432 :
1433 0 : return ok;
1434 : }
1435 :
1436 : static unsigned long
1437 0 : msSinceProcessCreation(const TimeStamp& now)
1438 : {
1439 0 : auto duration = now - TimeStamp::ProcessCreation();
1440 0 : return (unsigned long) duration.ToMilliseconds();
1441 : }
1442 :
1443 : /* static */ already_AddRefed<nsIFile>
1444 0 : HeapSnapshot::CreateUniqueCoreDumpFile(ErrorResult& rv,
1445 : const TimeStamp& now,
1446 : nsAString& outFilePath,
1447 : nsAString& outSnapshotId)
1448 : {
1449 0 : nsCOMPtr<nsIFile> file;
1450 0 : rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
1451 0 : if (NS_WARN_IF(rv.Failed()))
1452 0 : return nullptr;
1453 :
1454 0 : nsAutoString tempPath;
1455 0 : rv = file->GetPath(tempPath);
1456 0 : if (NS_WARN_IF(rv.Failed()))
1457 0 : return nullptr;
1458 :
1459 0 : auto ms = msSinceProcessCreation(now);
1460 0 : rv = file->AppendNative(nsPrintfCString("%lu.fxsnapshot", ms));
1461 0 : if (NS_WARN_IF(rv.Failed()))
1462 0 : return nullptr;
1463 :
1464 0 : rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
1465 0 : if (NS_WARN_IF(rv.Failed()))
1466 0 : return nullptr;
1467 :
1468 0 : rv = file->GetPath(outFilePath);
1469 0 : if (NS_WARN_IF(rv.Failed()))
1470 0 : return nullptr;
1471 :
1472 : // The snapshot ID must be computed in the process that created the
1473 : // temp file, because TmpD may not be the same in all processes.
1474 0 : outSnapshotId.Assign(Substring(outFilePath, tempPath.Length() + 1,
1475 0 : outFilePath.Length() - tempPath.Length() - sizeof(".fxsnapshot")));
1476 :
1477 0 : return file.forget();
1478 : }
1479 :
1480 : // Deletion policy for cleaning up PHeapSnapshotTempFileHelperChild pointers.
1481 : class DeleteHeapSnapshotTempFileHelperChild
1482 : {
1483 : public:
1484 0 : constexpr DeleteHeapSnapshotTempFileHelperChild() { }
1485 :
1486 0 : void operator()(PHeapSnapshotTempFileHelperChild* ptr) const {
1487 0 : Unused << NS_WARN_IF(!HeapSnapshotTempFileHelperChild::Send__delete__(ptr));
1488 0 : }
1489 : };
1490 :
1491 : // A UniquePtr alias to automatically manage PHeapSnapshotTempFileHelperChild
1492 : // pointers.
1493 : using UniqueHeapSnapshotTempFileHelperChild = UniquePtr<PHeapSnapshotTempFileHelperChild,
1494 : DeleteHeapSnapshotTempFileHelperChild>;
1495 :
1496 : // Get an nsIOutputStream that we can write the heap snapshot to. In non-e10s
1497 : // and in the e10s parent process, open a file directly and create an output
1498 : // stream for it. In e10s child processes, we are sandboxed without access to
1499 : // the filesystem. Use IPDL to request a file descriptor from the parent
1500 : // process.
1501 : static already_AddRefed<nsIOutputStream>
1502 0 : getCoreDumpOutputStream(ErrorResult& rv,
1503 : TimeStamp& start,
1504 : nsAString& outFilePath,
1505 : nsAString& outSnapshotId)
1506 : {
1507 0 : if (XRE_IsParentProcess()) {
1508 : // Create the file and open the output stream directly.
1509 :
1510 0 : nsCOMPtr<nsIFile> file = HeapSnapshot::CreateUniqueCoreDumpFile(rv,
1511 : start,
1512 : outFilePath,
1513 0 : outSnapshotId);
1514 0 : if (NS_WARN_IF(rv.Failed()))
1515 0 : return nullptr;
1516 :
1517 0 : nsCOMPtr<nsIOutputStream> outputStream;
1518 0 : rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
1519 0 : PR_WRONLY, -1, 0);
1520 0 : if (NS_WARN_IF(rv.Failed()))
1521 0 : return nullptr;
1522 :
1523 0 : return outputStream.forget();
1524 : }
1525 : // Request a file descriptor from the parent process over IPDL.
1526 :
1527 0 : auto cc = ContentChild::GetSingleton();
1528 0 : if (!cc) {
1529 0 : rv.Throw(NS_ERROR_UNEXPECTED);
1530 0 : return nullptr;
1531 : }
1532 :
1533 : UniqueHeapSnapshotTempFileHelperChild helper(
1534 0 : cc->SendPHeapSnapshotTempFileHelperConstructor());
1535 0 : if (NS_WARN_IF(!helper)) {
1536 0 : rv.Throw(NS_ERROR_UNEXPECTED);
1537 0 : return nullptr;
1538 : }
1539 :
1540 0 : OpenHeapSnapshotTempFileResponse response;
1541 0 : if (!helper->SendOpenHeapSnapshotTempFile(&response)) {
1542 0 : rv.Throw(NS_ERROR_UNEXPECTED);
1543 0 : return nullptr;
1544 : }
1545 0 : if (response.type() == OpenHeapSnapshotTempFileResponse::Tnsresult) {
1546 0 : rv.Throw(response.get_nsresult());
1547 0 : return nullptr;
1548 : }
1549 :
1550 0 : auto opened = response.get_OpenedFile();
1551 0 : outFilePath = opened.path();
1552 0 : outSnapshotId = opened.snapshotId();
1553 : nsCOMPtr<nsIOutputStream> outputStream =
1554 0 : FileDescriptorOutputStream::Create(opened.descriptor());
1555 0 : if (NS_WARN_IF(!outputStream)) {
1556 0 : rv.Throw(NS_ERROR_UNEXPECTED);
1557 0 : return nullptr;
1558 : }
1559 :
1560 0 : return outputStream.forget();
1561 : }
1562 :
1563 : } // namespace devtools
1564 :
1565 : namespace dom {
1566 :
1567 : using namespace JS;
1568 : using namespace devtools;
1569 :
1570 : /* static */ void
1571 0 : ThreadSafeChromeUtils::SaveHeapSnapshotShared(GlobalObject& global,
1572 : const HeapSnapshotBoundaries& boundaries,
1573 : nsAString& outFilePath,
1574 : nsAString& outSnapshotId,
1575 : ErrorResult& rv)
1576 : {
1577 0 : auto start = TimeStamp::Now();
1578 :
1579 0 : bool wantNames = true;
1580 0 : CompartmentSet compartments;
1581 0 : uint32_t nodeCount = 0;
1582 0 : uint32_t edgeCount = 0;
1583 :
1584 0 : nsCOMPtr<nsIOutputStream> outputStream = getCoreDumpOutputStream(rv, start,
1585 : outFilePath,
1586 0 : outSnapshotId);
1587 0 : if (NS_WARN_IF(rv.Failed()))
1588 0 : return;
1589 :
1590 0 : ZeroCopyNSIOutputStream zeroCopyStream(outputStream);
1591 0 : ::google::protobuf::io::GzipOutputStream gzipStream(&zeroCopyStream);
1592 :
1593 0 : JSContext* cx = global.Context();
1594 :
1595 : {
1596 0 : Maybe<AutoCheckCannotGC> maybeNoGC;
1597 0 : ubi::RootList rootList(cx, maybeNoGC, wantNames);
1598 0 : if (!EstablishBoundaries(cx, rv, boundaries, rootList, compartments))
1599 0 : return;
1600 :
1601 : StreamWriter writer(cx, gzipStream, wantNames,
1602 0 : compartments.initialized() ? &compartments : nullptr);
1603 0 : if (NS_WARN_IF(!writer.init())) {
1604 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
1605 0 : return;
1606 : }
1607 :
1608 0 : MOZ_ASSERT(maybeNoGC.isSome());
1609 0 : ubi::Node roots(&rootList);
1610 :
1611 : // Serialize the initial heap snapshot metadata to the core dump.
1612 0 : if (!writer.writeMetadata(PR_Now()) ||
1613 : // Serialize the heap graph to the core dump, starting from our list of
1614 : // roots.
1615 0 : !WriteHeapGraph(cx,
1616 : roots,
1617 : writer,
1618 : wantNames,
1619 0 : compartments.initialized() ? &compartments : nullptr,
1620 : maybeNoGC.ref(),
1621 : nodeCount,
1622 : edgeCount))
1623 : {
1624 0 : rv.Throw(zeroCopyStream.failed()
1625 : ? zeroCopyStream.result()
1626 0 : : NS_ERROR_UNEXPECTED);
1627 0 : return;
1628 : }
1629 : }
1630 :
1631 0 : Telemetry::AccumulateTimeDelta(Telemetry::DEVTOOLS_SAVE_HEAP_SNAPSHOT_MS,
1632 0 : start);
1633 : Telemetry::Accumulate(Telemetry::DEVTOOLS_HEAP_SNAPSHOT_NODE_COUNT,
1634 0 : nodeCount);
1635 : Telemetry::Accumulate(Telemetry::DEVTOOLS_HEAP_SNAPSHOT_EDGE_COUNT,
1636 0 : edgeCount);
1637 : }
1638 :
1639 : /* static */ void
1640 0 : ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global,
1641 : const HeapSnapshotBoundaries& boundaries,
1642 : nsAString& outFilePath,
1643 : ErrorResult& rv)
1644 : {
1645 0 : nsAutoString snapshotId;
1646 0 : SaveHeapSnapshotShared(global, boundaries, outFilePath, snapshotId, rv);
1647 0 : }
1648 :
1649 : /* static */ void
1650 0 : ThreadSafeChromeUtils::SaveHeapSnapshotGetId(GlobalObject& global,
1651 : const HeapSnapshotBoundaries& boundaries,
1652 : nsAString& outSnapshotId,
1653 : ErrorResult& rv)
1654 : {
1655 0 : nsAutoString filePath;
1656 0 : SaveHeapSnapshotShared(global, boundaries, filePath, outSnapshotId, rv);
1657 0 : }
1658 :
1659 : /* static */ already_AddRefed<HeapSnapshot>
1660 0 : ThreadSafeChromeUtils::ReadHeapSnapshot(GlobalObject& global,
1661 : const nsAString& filePath,
1662 : ErrorResult& rv)
1663 : {
1664 0 : auto start = TimeStamp::Now();
1665 :
1666 0 : UniquePtr<char[]> path(ToNewCString(filePath));
1667 0 : if (!path) {
1668 0 : rv.Throw(NS_ERROR_OUT_OF_MEMORY);
1669 0 : return nullptr;
1670 : }
1671 :
1672 0 : AutoMemMap mm;
1673 0 : rv = mm.init(path.get());
1674 0 : if (rv.Failed())
1675 0 : return nullptr;
1676 :
1677 0 : RefPtr<HeapSnapshot> snapshot = HeapSnapshot::Create(
1678 0 : global.Context(), global, reinterpret_cast<const uint8_t*>(mm.address()),
1679 0 : mm.size(), rv);
1680 :
1681 0 : if (!rv.Failed())
1682 0 : Telemetry::AccumulateTimeDelta(Telemetry::DEVTOOLS_READ_HEAP_SNAPSHOT_MS,
1683 0 : start);
1684 :
1685 0 : return snapshot.forget();
1686 : }
1687 :
1688 : } // namespace dom
1689 : } // namespace mozilla
|