Coverage Report

Created: 2018-09-25 14:53

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