LCOV - code coverage report
Current view: top level - devtools/shared/heapsnapshot - HeapSnapshot.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 763 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 102 0.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13