summaryrefslogtreecommitdiffstats
path: root/devtools/shared/heapsnapshot/HeapSnapshot.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2020-02-22 17:32:39 -0500
committerwolfbeast <mcwerewolf@wolfbeast.com>2020-04-14 12:50:57 +0200
commit36fc5f674ef1a02d1498484c563a7108f4de44ed (patch)
tree120483cd8fc0decd189d5118941a9b23d6156ad5 /devtools/shared/heapsnapshot/HeapSnapshot.cpp
parent7b30664f59e65cadb7d5eb2e42591e90a32871f8 (diff)
downloadUXP-36fc5f674ef1a02d1498484c563a7108f4de44ed.tar
UXP-36fc5f674ef1a02d1498484c563a7108f4de44ed.tar.gz
UXP-36fc5f674ef1a02d1498484c563a7108f4de44ed.tar.lz
UXP-36fc5f674ef1a02d1498484c563a7108f4de44ed.tar.xz
UXP-36fc5f674ef1a02d1498484c563a7108f4de44ed.zip
Reclassify heapsnapshot and nsJSInspector as not part of devtools
This resolves Issue #316
Diffstat (limited to 'devtools/shared/heapsnapshot/HeapSnapshot.cpp')
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshot.cpp1619
1 files changed, 0 insertions, 1619 deletions
diff --git a/devtools/shared/heapsnapshot/HeapSnapshot.cpp b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
deleted file mode 100644
index 299a96a9c..000000000
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ /dev/null
@@ -1,1619 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "HeapSnapshot.h"
-
-#include <google/protobuf/io/coded_stream.h>
-#include <google/protobuf/io/gzip_stream.h>
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-
-#include "js/Debug.h"
-#include "js/TypeDecls.h"
-#include "js/UbiNodeBreadthFirst.h"
-#include "js/UbiNodeCensus.h"
-#include "js/UbiNodeDominatorTree.h"
-#include "js/UbiNodeShortestPaths.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/CycleCollectedJSContext.h"
-#include "mozilla/devtools/AutoMemMap.h"
-#include "mozilla/devtools/CoreDump.pb.h"
-#include "mozilla/devtools/DeserializedNode.h"
-#include "mozilla/devtools/DominatorTree.h"
-#include "mozilla/devtools/FileDescriptorOutputStream.h"
-#include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
-#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
-#include "mozilla/dom/ChromeUtils.h"
-#include "mozilla/dom/ContentChild.h"
-#include "mozilla/dom/HeapSnapshotBinding.h"
-#include "mozilla/RangedPtr.h"
-#include "mozilla/Unused.h"
-
-#include "jsapi.h"
-#include "jsfriendapi.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsCRTGlue.h"
-#include "nsDirectoryServiceDefs.h"
-#include "nsIFile.h"
-#include "nsIOutputStream.h"
-#include "nsISupportsImpl.h"
-#include "nsNetUtil.h"
-#include "nsPrintfCString.h"
-#include "prerror.h"
-#include "prio.h"
-#include "prtypes.h"
-
-namespace mozilla {
-namespace devtools {
-
-using namespace JS;
-using namespace dom;
-
-using ::google::protobuf::io::ArrayInputStream;
-using ::google::protobuf::io::CodedInputStream;
-using ::google::protobuf::io::GzipInputStream;
-using ::google::protobuf::io::ZeroCopyInputStream;
-
-using JS::ubi::AtomOrTwoByteChars;
-using JS::ubi::ShortestPaths;
-
-MallocSizeOf
-GetCurrentThreadDebuggerMallocSizeOf()
-{
- auto ccjscx = CycleCollectedJSContext::Get();
- MOZ_ASSERT(ccjscx);
- auto cx = ccjscx->Context();
- MOZ_ASSERT(cx);
- auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(cx);
- MOZ_ASSERT(mallocSizeOf);
- return mallocSizeOf;
-}
-
-/*** Cycle Collection Boilerplate *****************************************************************/
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HeapSnapshot, mParent)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnapshot)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HeapSnapshot)
- NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
- NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-/* virtual */ JSObject*
-HeapSnapshot::WrapObject(JSContext* aCx, HandleObject aGivenProto)
-{
- return HeapSnapshotBinding::Wrap(aCx, this, aGivenProto);
-}
-
-/*** Reading Heap Snapshots ***********************************************************************/
-
-/* static */ already_AddRefed<HeapSnapshot>
-HeapSnapshot::Create(JSContext* cx,
- GlobalObject& global,
- const uint8_t* buffer,
- uint32_t size,
- ErrorResult& rv)
-{
- RefPtr<HeapSnapshot> snapshot = new HeapSnapshot(cx, global.GetAsSupports());
- if (!snapshot->init(cx, buffer, size)) {
- rv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- return snapshot.forget();
-}
-
-template<typename MessageType>
-static bool
-parseMessage(ZeroCopyInputStream& stream, uint32_t sizeOfMessage, MessageType& message)
-{
- // We need to create a new `CodedInputStream` for each message so that the
- // 64MB limit is applied per-message rather than to the whole stream.
- CodedInputStream codedStream(&stream);
-
- // The protobuf message nesting that core dumps exhibit is dominated by
- // allocation stacks' frames. In the most deeply nested case, each frame has
- // two messages: a StackFrame message and a StackFrame::Data message. These
- // frames are on top of a small constant of other messages. There are a
- // MAX_STACK_DEPTH number of frames, so we multiply this by 3 to make room for
- // the two messages per frame plus some head room for the constant number of
- // non-dominating messages.
- codedStream.SetRecursionLimit(HeapSnapshot::MAX_STACK_DEPTH * 3);
-
- auto limit = codedStream.PushLimit(sizeOfMessage);
- if (NS_WARN_IF(!message.ParseFromCodedStream(&codedStream)) ||
- NS_WARN_IF(!codedStream.ConsumedEntireMessage()) ||
- NS_WARN_IF(codedStream.BytesUntilLimit() != 0))
- {
- return false;
- }
-
- codedStream.PopLimit(limit);
- return true;
-}
-
-template<typename CharT, typename InternedStringSet>
-struct GetOrInternStringMatcher
-{
- InternedStringSet& internedStrings;
-
- explicit GetOrInternStringMatcher(InternedStringSet& strings) : internedStrings(strings) { }
-
- const CharT* match(const std::string* str) {
- MOZ_ASSERT(str);
- size_t length = str->length() / sizeof(CharT);
- auto tempString = reinterpret_cast<const CharT*>(str->data());
-
- UniquePtr<CharT[], NSFreePolicy> owned(NS_strndup(tempString, length));
- if (!owned || !internedStrings.append(Move(owned)))
- return nullptr;
-
- return internedStrings.back().get();
- }
-
- const CharT* match(uint64_t ref) {
- if (MOZ_LIKELY(ref < internedStrings.length())) {
- auto& string = internedStrings[ref];
- MOZ_ASSERT(string);
- return string.get();
- }
-
- return nullptr;
- }
-};
-
-template<
- // Either char or char16_t.
- typename CharT,
- // A reference to either `internedOneByteStrings` or `internedTwoByteStrings`
- // if CharT is char or char16_t respectively.
- typename InternedStringSet>
-const CharT*
-HeapSnapshot::getOrInternString(InternedStringSet& internedStrings,
- Maybe<StringOrRef>& maybeStrOrRef)
-{
- // Incomplete message: has neither a string nor a reference to an already
- // interned string.
- if (MOZ_UNLIKELY(maybeStrOrRef.isNothing()))
- return nullptr;
-
- GetOrInternStringMatcher<CharT, InternedStringSet> m(internedStrings);
- return maybeStrOrRef->match(m);
-}
-
-// Get a de-duplicated string as a Maybe<StringOrRef> from the given `msg`.
-#define GET_STRING_OR_REF_WITH_PROP_NAMES(msg, strPropertyName, refPropertyName) \
- (msg.has_##refPropertyName() \
- ? Some(StringOrRef(msg.refPropertyName())) \
- : msg.has_##strPropertyName() \
- ? Some(StringOrRef(&msg.strPropertyName())) \
- : Nothing())
-
-#define GET_STRING_OR_REF(msg, property) \
- (msg.has_##property##ref() \
- ? Some(StringOrRef(msg.property##ref())) \
- : msg.has_##property() \
- ? Some(StringOrRef(&msg.property())) \
- : Nothing())
-
-bool
-HeapSnapshot::saveNode(const protobuf::Node& node, NodeIdSet& edgeReferents)
-{
- // NB: de-duplicated string properties must be read back and interned in the
- // same order here as they are written and serialized in
- // `CoreDumpWriter::writeNode` or else indices in references to already
- // serialized strings will be off.
-
- if (NS_WARN_IF(!node.has_id()))
- return false;
- NodeId id = node.id();
-
- // NodeIds are derived from pointers (at most 48 bits) and we rely on them
- // fitting into JS numbers (IEEE 754 doubles, can precisely store 53 bit
- // integers) despite storing them on disk as 64 bit integers.
- if (NS_WARN_IF(!JS::Value::isNumberRepresentable(id)))
- return false;
-
- // Should only deserialize each node once.
- if (NS_WARN_IF(nodes.has(id)))
- return false;
-
- if (NS_WARN_IF(!JS::ubi::Uint32IsValidCoarseType(node.coarsetype())))
- return false;
- auto coarseType = JS::ubi::Uint32ToCoarseType(node.coarsetype());
-
- Maybe<StringOrRef> typeNameOrRef = GET_STRING_OR_REF_WITH_PROP_NAMES(node, typename_, typenameref);
- auto typeName = getOrInternString<char16_t>(internedTwoByteStrings, typeNameOrRef);
- if (NS_WARN_IF(!typeName))
- return false;
-
- if (NS_WARN_IF(!node.has_size()))
- return false;
- uint64_t size = node.size();
-
- auto edgesLength = node.edges_size();
- DeserializedNode::EdgeVector edges;
- if (NS_WARN_IF(!edges.reserve(edgesLength)))
- return false;
- for (decltype(edgesLength) i = 0; i < edgesLength; i++) {
- auto& protoEdge = node.edges(i);
-
- if (NS_WARN_IF(!protoEdge.has_referent()))
- return false;
- NodeId referent = protoEdge.referent();
-
- if (NS_WARN_IF(!edgeReferents.put(referent)))
- return false;
-
- const char16_t* edgeName = nullptr;
- if (protoEdge.EdgeNameOrRef_case() != protobuf::Edge::EDGENAMEORREF_NOT_SET) {
- Maybe<StringOrRef> edgeNameOrRef = GET_STRING_OR_REF(protoEdge, name);
- edgeName = getOrInternString<char16_t>(internedTwoByteStrings, edgeNameOrRef);
- if (NS_WARN_IF(!edgeName))
- return false;
- }
-
- edges.infallibleAppend(DeserializedEdge(referent, edgeName));
- }
-
- Maybe<StackFrameId> allocationStack;
- if (node.has_allocationstack()) {
- StackFrameId id = 0;
- if (NS_WARN_IF(!saveStackFrame(node.allocationstack(), id)))
- return false;
- allocationStack.emplace(id);
- }
- MOZ_ASSERT(allocationStack.isSome() == node.has_allocationstack());
-
- const char* jsObjectClassName = nullptr;
- if (node.JSObjectClassNameOrRef_case() != protobuf::Node::JSOBJECTCLASSNAMEORREF_NOT_SET) {
- Maybe<StringOrRef> clsNameOrRef = GET_STRING_OR_REF(node, jsobjectclassname);
- jsObjectClassName = getOrInternString<char>(internedOneByteStrings, clsNameOrRef);
- if (NS_WARN_IF(!jsObjectClassName))
- return false;
- }
-
- const char* scriptFilename = nullptr;
- if (node.ScriptFilenameOrRef_case() != protobuf::Node::SCRIPTFILENAMEORREF_NOT_SET) {
- Maybe<StringOrRef> scriptFilenameOrRef = GET_STRING_OR_REF(node, scriptfilename);
- scriptFilename = getOrInternString<char>(internedOneByteStrings, scriptFilenameOrRef);
- if (NS_WARN_IF(!scriptFilename))
- return false;
- }
-
- if (NS_WARN_IF(!nodes.putNew(id, DeserializedNode(id, coarseType, typeName,
- size, Move(edges),
- allocationStack,
- jsObjectClassName,
- scriptFilename, *this))))
- {
- return false;
- };
-
- return true;
-}
-
-bool
-HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame,
- StackFrameId& outFrameId)
-{
- // NB: de-duplicated string properties must be read in the same order here as
- // they are written in `CoreDumpWriter::getProtobufStackFrame` or else indices
- // in references to already serialized strings will be off.
-
- if (frame.has_ref()) {
- // We should only get a reference to the previous frame if we have already
- // seen the previous frame.
- if (!frames.has(frame.ref()))
- return false;
-
- outFrameId = frame.ref();
- return true;
- }
-
- // Incomplete message.
- if (!frame.has_data())
- return false;
-
- auto data = frame.data();
-
- if (!data.has_id())
- return false;
- StackFrameId id = data.id();
-
- // This should be the first and only time we see this frame.
- if (frames.has(id))
- return false;
-
- if (!data.has_line())
- return false;
- uint32_t line = data.line();
-
- if (!data.has_column())
- return false;
- uint32_t column = data.column();
-
- if (!data.has_issystem())
- return false;
- bool isSystem = data.issystem();
-
- if (!data.has_isselfhosted())
- return false;
- bool isSelfHosted = data.isselfhosted();
-
- Maybe<StringOrRef> sourceOrRef = GET_STRING_OR_REF(data, source);
- auto source = getOrInternString<char16_t>(internedTwoByteStrings, sourceOrRef);
- if (!source)
- return false;
-
- const char16_t* functionDisplayName = nullptr;
- if (data.FunctionDisplayNameOrRef_case() !=
- protobuf::StackFrame_Data::FUNCTIONDISPLAYNAMEORREF_NOT_SET)
- {
- Maybe<StringOrRef> nameOrRef = GET_STRING_OR_REF(data, functiondisplayname);
- functionDisplayName = getOrInternString<char16_t>(internedTwoByteStrings, nameOrRef);
- if (!functionDisplayName)
- return false;
- }
-
- Maybe<StackFrameId> parent;
- if (data.has_parent()) {
- StackFrameId parentId = 0;
- if (!saveStackFrame(data.parent(), parentId))
- return false;
- parent = Some(parentId);
- }
-
- if (!frames.putNew(id, DeserializedStackFrame(id, parent, line, column,
- source, functionDisplayName,
- isSystem, isSelfHosted, *this)))
- {
- return false;
- }
-
- outFrameId = id;
- return true;
-}
-
-#undef GET_STRING_OR_REF_WITH_PROP_NAMES
-#undef GET_STRING_OR_REF
-
-// Because protobuf messages aren't self-delimiting, we serialize each message
-// preceded by its size in bytes. When deserializing, we read this size and then
-// limit reading from the stream to the given byte size. If we didn't, then the
-// first message would consume the entire stream.
-static bool
-readSizeOfNextMessage(ZeroCopyInputStream& stream, uint32_t* sizep)
-{
- MOZ_ASSERT(sizep);
- CodedInputStream codedStream(&stream);
- return codedStream.ReadVarint32(sizep) && *sizep > 0;
-}
-
-bool
-HeapSnapshot::init(JSContext* cx, const uint8_t* buffer, uint32_t size)
-{
- if (!nodes.init() || !frames.init())
- return false;
-
- ArrayInputStream stream(buffer, size);
- GzipInputStream gzipStream(&stream);
- uint32_t sizeOfMessage = 0;
-
- // First is the metadata.
-
- protobuf::Metadata metadata;
- if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
- return false;
- if (!parseMessage(gzipStream, sizeOfMessage, metadata))
- return false;
- if (metadata.has_timestamp())
- timestamp.emplace(metadata.timestamp());
-
- // Next is the root node.
-
- protobuf::Node root;
- if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
- return false;
- if (!parseMessage(gzipStream, sizeOfMessage, root))
- return false;
-
- // Although the id is optional in the protobuf format for future proofing, we
- // can't currently do anything without it.
- if (NS_WARN_IF(!root.has_id()))
- return false;
- rootId = root.id();
-
- // The set of all node ids we've found edges pointing to.
- NodeIdSet edgeReferents(cx);
- if (NS_WARN_IF(!edgeReferents.init()))
- return false;
-
- if (NS_WARN_IF(!saveNode(root, edgeReferents)))
- return false;
-
- // Finally, the rest of the nodes in the core dump.
-
- // Test for the end of the stream. The protobuf library gives no way to tell
- // the difference between an underlying read error and the stream being
- // done. All we can do is attempt to read the size of the next message and
- // extrapolate guestimations from the result of that operation.
- while (readSizeOfNextMessage(gzipStream, &sizeOfMessage)) {
- protobuf::Node node;
- if (!parseMessage(gzipStream, sizeOfMessage, node))
- return false;
- if (NS_WARN_IF(!saveNode(node, edgeReferents)))
- return false;
- }
-
- // Check the set of node ids referred to by edges we found and ensure that we
- // have the node corresponding to each id. If we don't have all of them, it is
- // unsafe to perform analyses of this heap snapshot.
- for (auto range = edgeReferents.all(); !range.empty(); range.popFront()) {
- if (NS_WARN_IF(!nodes.has(range.front())))
- return false;
- }
-
- return true;
-}
-
-
-/*** Heap Snapshot Analyses ***********************************************************************/
-
-void
-HeapSnapshot::TakeCensus(JSContext* cx, JS::HandleObject options,
- JS::MutableHandleValue rval, ErrorResult& rv)
-{
- JS::ubi::Census census(cx);
- if (NS_WARN_IF(!census.init())) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- JS::ubi::CountTypePtr rootType;
- if (NS_WARN_IF(!JS::ubi::ParseCensusOptions(cx, census, options, rootType))) {
- rv.Throw(NS_ERROR_UNEXPECTED);
- return;
- }
-
- JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
- if (NS_WARN_IF(!rootCount)) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- JS::ubi::CensusHandler handler(census, rootCount, GetCurrentThreadDebuggerMallocSizeOf());
-
- {
- JS::AutoCheckCannotGC nogc;
-
- JS::ubi::CensusTraversal traversal(cx, handler, nogc);
- if (NS_WARN_IF(!traversal.init())) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- if (NS_WARN_IF(!traversal.addStart(getRoot()))) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- if (NS_WARN_IF(!traversal.traverse())) {
- rv.Throw(NS_ERROR_UNEXPECTED);
- return;
- }
- }
-
- if (NS_WARN_IF(!handler.report(cx, rval))) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-}
-
-void
-HeapSnapshot::DescribeNode(JSContext* cx, JS::HandleObject breakdown, uint64_t nodeId,
- JS::MutableHandleValue rval, ErrorResult& rv) {
- MOZ_ASSERT(breakdown);
- JS::RootedValue breakdownVal(cx, JS::ObjectValue(*breakdown));
- JS::ubi::CountTypePtr rootType = JS::ubi::ParseBreakdown(cx, breakdownVal);
- if (NS_WARN_IF(!rootType)) {
- rv.Throw(NS_ERROR_UNEXPECTED);
- return;
- }
-
- JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
- if (NS_WARN_IF(!rootCount)) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- JS::ubi::Node::Id id(nodeId);
- Maybe<JS::ubi::Node> node = getNodeById(id);
- if (NS_WARN_IF(node.isNothing())) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return;
- }
-
- MallocSizeOf mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
- if (NS_WARN_IF(!rootCount->count(mallocSizeOf, *node))) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- if (NS_WARN_IF(!rootCount->report(cx, rval))) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-}
-
-
-already_AddRefed<DominatorTree>
-HeapSnapshot::ComputeDominatorTree(ErrorResult& rv)
-{
- Maybe<JS::ubi::DominatorTree> maybeTree;
- {
- auto ccjscx = CycleCollectedJSContext::Get();
- MOZ_ASSERT(ccjscx);
- auto cx = ccjscx->Context();
- MOZ_ASSERT(cx);
- JS::AutoCheckCannotGC nogc(cx);
- maybeTree = JS::ubi::DominatorTree::Create(cx, nogc, getRoot());
- }
-
- if (NS_WARN_IF(maybeTree.isNothing())) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return nullptr;
- }
-
- return MakeAndAddRef<DominatorTree>(Move(*maybeTree), this, mParent);
-}
-
-void
-HeapSnapshot::ComputeShortestPaths(JSContext*cx, uint64_t start,
- const Sequence<uint64_t>& targets,
- uint64_t maxNumPaths,
- JS::MutableHandleObject results,
- ErrorResult& rv)
-{
- // First ensure that our inputs are valid.
-
- if (NS_WARN_IF(maxNumPaths == 0)) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return;
- }
-
- Maybe<JS::ubi::Node> startNode = getNodeById(start);
- if (NS_WARN_IF(startNode.isNothing())) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return;
- }
-
- if (NS_WARN_IF(targets.Length() == 0)) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return;
- }
-
- // Aggregate the targets into a set and make sure that they exist in the heap
- // snapshot.
-
- JS::ubi::NodeSet targetsSet;
- if (NS_WARN_IF(!targetsSet.init())) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- for (const auto& target : targets) {
- Maybe<JS::ubi::Node> targetNode = getNodeById(target);
- if (NS_WARN_IF(targetNode.isNothing())) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return;
- }
-
- if (NS_WARN_IF(!targetsSet.put(*targetNode))) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
- }
-
- // Walk the heap graph and find the shortest paths.
-
- Maybe<ShortestPaths> maybeShortestPaths;
- {
- JS::AutoCheckCannotGC nogc(cx);
- maybeShortestPaths = ShortestPaths::Create(cx, nogc, maxNumPaths, *startNode,
- Move(targetsSet));
- }
-
- if (NS_WARN_IF(maybeShortestPaths.isNothing())) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- auto& shortestPaths = *maybeShortestPaths;
-
- // Convert the results into a Map object mapping target node IDs to arrays of
- // paths found.
-
- RootedObject resultsMap(cx, JS::NewMapObject(cx));
- if (NS_WARN_IF(!resultsMap)) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- for (auto range = shortestPaths.eachTarget(); !range.empty(); range.popFront()) {
- JS::RootedValue key(cx, JS::NumberValue(range.front().identifier()));
- JS::AutoValueVector paths(cx);
-
- bool ok = shortestPaths.forEachPath(range.front(), [&](JS::ubi::Path& path) {
- JS::AutoValueVector pathValues(cx);
-
- for (JS::ubi::BackEdge* edge : path) {
- JS::RootedObject pathPart(cx, JS_NewPlainObject(cx));
- if (!pathPart) {
- return false;
- }
-
- JS::RootedValue predecessor(cx, NumberValue(edge->predecessor().identifier()));
- if (!JS_DefineProperty(cx, pathPart, "predecessor", predecessor, JSPROP_ENUMERATE)) {
- return false;
- }
-
- RootedValue edgeNameVal(cx, NullValue());
- if (edge->name()) {
- RootedString edgeName(cx, JS_AtomizeUCString(cx, edge->name().get()));
- if (!edgeName) {
- return false;
- }
- edgeNameVal = StringValue(edgeName);
- }
-
- if (!JS_DefineProperty(cx, pathPart, "edge", edgeNameVal, JSPROP_ENUMERATE)) {
- return false;
- }
-
- if (!pathValues.append(ObjectValue(*pathPart))) {
- return false;
- }
- }
-
- RootedObject pathObj(cx, JS_NewArrayObject(cx, pathValues));
- return pathObj && paths.append(ObjectValue(*pathObj));
- });
-
- if (NS_WARN_IF(!ok)) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- JS::RootedObject pathsArray(cx, JS_NewArrayObject(cx, paths));
- if (NS_WARN_IF(!pathsArray)) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- JS::RootedValue pathsVal(cx, ObjectValue(*pathsArray));
- if (NS_WARN_IF(!JS::MapSet(cx, resultsMap, key, pathsVal))) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
- }
-
- results.set(resultsMap);
-}
-
-/*** Saving Heap Snapshots ************************************************************************/
-
-// If we are only taking a snapshot of the heap affected by the given set of
-// globals, find the set of compartments the globals are allocated
-// within. Returns false on OOM failure.
-static bool
-PopulateCompartmentsWithGlobals(CompartmentSet& compartments, AutoObjectVector& globals)
-{
- if (!compartments.init())
- return false;
-
- unsigned length = globals.length();
- for (unsigned i = 0; i < length; i++) {
- if (!compartments.put(GetObjectCompartment(globals[i])))
- return false;
- }
-
- return true;
-}
-
-// Add the given set of globals as explicit roots in the given roots
-// list. Returns false on OOM failure.
-static bool
-AddGlobalsAsRoots(AutoObjectVector& globals, ubi::RootList& roots)
-{
- unsigned length = globals.length();
- for (unsigned i = 0; i < length; i++) {
- if (!roots.addRoot(ubi::Node(globals[i].get()),
- u"heap snapshot global"))
- {
- return false;
- }
- }
- return true;
-}
-
-// Choose roots and limits for a traversal, given `boundaries`. Set `roots` to
-// the set of nodes within the boundaries that are referred to by nodes
-// outside. If `boundaries` does not include all JS compartments, initialize
-// `compartments` to the set of included compartments; otherwise, leave
-// `compartments` uninitialized. (You can use compartments.initialized() to
-// check.)
-//
-// If `boundaries` is incoherent, or we encounter an error while trying to
-// handle it, or we run out of memory, set `rv` appropriately and return
-// `false`.
-static bool
-EstablishBoundaries(JSContext* cx,
- ErrorResult& rv,
- const HeapSnapshotBoundaries& boundaries,
- ubi::RootList& roots,
- CompartmentSet& compartments)
-{
- MOZ_ASSERT(!roots.initialized());
- MOZ_ASSERT(!compartments.initialized());
-
- bool foundBoundaryProperty = false;
-
- if (boundaries.mRuntime.WasPassed()) {
- foundBoundaryProperty = true;
-
- if (!boundaries.mRuntime.Value()) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return false;
- }
-
- if (!roots.init()) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return false;
- }
- }
-
- if (boundaries.mDebugger.WasPassed()) {
- if (foundBoundaryProperty) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return false;
- }
- foundBoundaryProperty = true;
-
- JSObject* dbgObj = boundaries.mDebugger.Value();
- if (!dbgObj || !dbg::IsDebugger(*dbgObj)) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return false;
- }
-
- AutoObjectVector globals(cx);
- if (!dbg::GetDebuggeeGlobals(cx, *dbgObj, globals) ||
- !PopulateCompartmentsWithGlobals(compartments, globals) ||
- !roots.init(compartments) ||
- !AddGlobalsAsRoots(globals, roots))
- {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return false;
- }
- }
-
- if (boundaries.mGlobals.WasPassed()) {
- if (foundBoundaryProperty) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return false;
- }
- foundBoundaryProperty = true;
-
- uint32_t length = boundaries.mGlobals.Value().Length();
- if (length == 0) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return false;
- }
-
- AutoObjectVector globals(cx);
- for (uint32_t i = 0; i < length; i++) {
- JSObject* global = boundaries.mGlobals.Value().ElementAt(i);
- if (!JS_IsGlobalObject(global)) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return false;
- }
- if (!globals.append(global)) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return false;
- }
- }
-
- if (!PopulateCompartmentsWithGlobals(compartments, globals) ||
- !roots.init(compartments) ||
- !AddGlobalsAsRoots(globals, roots))
- {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return false;
- }
- }
-
- if (!foundBoundaryProperty) {
- rv.Throw(NS_ERROR_INVALID_ARG);
- return false;
- }
-
- MOZ_ASSERT(roots.initialized());
- MOZ_ASSERT_IF(boundaries.mDebugger.WasPassed(), compartments.initialized());
- MOZ_ASSERT_IF(boundaries.mGlobals.WasPassed(), compartments.initialized());
- return true;
-}
-
-
-// A variant covering all the various two-byte strings that we can get from the
-// ubi::Node API.
-class TwoByteString : public Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>
-{
- using Base = Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>;
-
- struct AsTwoByteStringMatcher
- {
- TwoByteString match(JSAtom* atom) {
- return TwoByteString(atom);
- }
-
- TwoByteString match(const char16_t* chars) {
- return TwoByteString(chars);
- }
- };
-
- struct IsNonNullMatcher
- {
- template<typename T>
- bool match(const T& t) { return t != nullptr; }
- };
-
- struct LengthMatcher
- {
- size_t match(JSAtom* atom) {
- MOZ_ASSERT(atom);
- JS::ubi::AtomOrTwoByteChars s(atom);
- return s.length();
- }
-
- size_t match(const char16_t* chars) {
- MOZ_ASSERT(chars);
- return NS_strlen(chars);
- }
-
- size_t match(const JS::ubi::EdgeName& ptr) {
- MOZ_ASSERT(ptr);
- return NS_strlen(ptr.get());
- }
- };
-
- struct CopyToBufferMatcher
- {
- RangedPtr<char16_t> destination;
- size_t maxLength;
-
- CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength)
- : destination(destination)
- , maxLength(maxLength)
- { }
-
- size_t match(JS::ubi::EdgeName& ptr) {
- return ptr ? match(ptr.get()) : 0;
- }
-
- size_t match(JSAtom* atom) {
- MOZ_ASSERT(atom);
- JS::ubi::AtomOrTwoByteChars s(atom);
- return s.copyToBuffer(destination, maxLength);
- }
-
- size_t match(const char16_t* chars) {
- MOZ_ASSERT(chars);
- JS::ubi::AtomOrTwoByteChars s(chars);
- return s.copyToBuffer(destination, maxLength);
- }
- };
-
-public:
- template<typename T>
- MOZ_IMPLICIT TwoByteString(T&& rhs) : Base(Forward<T>(rhs)) { }
-
- template<typename T>
- TwoByteString& operator=(T&& rhs) {
- MOZ_ASSERT(this != &rhs, "self-move disallowed");
- this->~TwoByteString();
- new (this) TwoByteString(Forward<T>(rhs));
- return *this;
- }
-
- TwoByteString(const TwoByteString&) = delete;
- TwoByteString& operator=(const TwoByteString&) = delete;
-
- // Rewrap the inner value of a JS::ubi::AtomOrTwoByteChars as a TwoByteString.
- static TwoByteString from(JS::ubi::AtomOrTwoByteChars&& s) {
- AsTwoByteStringMatcher m;
- return s.match(m);
- }
-
- // Returns true if the given TwoByteString is non-null, false otherwise.
- bool isNonNull() const {
- IsNonNullMatcher m;
- return match(m);
- }
-
- // Return the length of the string, 0 if it is null.
- size_t length() const {
- LengthMatcher m;
- return match(m);
- }
-
- // Copy the contents of a TwoByteString into the provided buffer. The buffer
- // is NOT null terminated. The number of characters written is returned.
- size_t copyToBuffer(RangedPtr<char16_t> destination, size_t maxLength) {
- CopyToBufferMatcher m(destination, maxLength);
- return match(m);
- }
-
- struct HashPolicy;
-};
-
-// A hashing policy for TwoByteString.
-//
-// Atoms are pointer hashed and use pointer equality, which means that we
-// tolerate some duplication across atoms and the other two types of two-byte
-// strings. In practice, we expect the amount of this duplication to be very low
-// because each type is generally a different semantic thing in addition to
-// having a slightly different representation. For example, the set of edge
-// names and the set stack frames' source names naturally tend not to overlap
-// very much if at all.
-struct TwoByteString::HashPolicy {
- using Lookup = TwoByteString;
-
- struct HashingMatcher {
- js::HashNumber match(const JSAtom* atom) {
- return js::DefaultHasher<const JSAtom*>::hash(atom);
- }
-
- js::HashNumber match(const char16_t* chars) {
- MOZ_ASSERT(chars);
- auto length = NS_strlen(chars);
- return HashString(chars, length);
- }
-
- js::HashNumber match(const JS::ubi::EdgeName& ptr) {
- MOZ_ASSERT(ptr);
- return match(ptr.get());
- }
- };
-
- static js::HashNumber hash(const Lookup& l) {
- HashingMatcher hasher;
- return l.match(hasher);
- }
-
- struct EqualityMatcher {
- const TwoByteString& rhs;
- explicit EqualityMatcher(const TwoByteString& rhs) : rhs(rhs) { }
-
- bool match(const JSAtom* atom) {
- return rhs.is<JSAtom*>() && rhs.as<JSAtom*>() == atom;
- }
-
- bool match(const char16_t* chars) {
- MOZ_ASSERT(chars);
-
- const char16_t* rhsChars = nullptr;
- if (rhs.is<const char16_t*>())
- rhsChars = rhs.as<const char16_t*>();
- else if (rhs.is<JS::ubi::EdgeName>())
- rhsChars = rhs.as<JS::ubi::EdgeName>().get();
- else
- return false;
- MOZ_ASSERT(rhsChars);
-
- auto length = NS_strlen(chars);
- if (NS_strlen(rhsChars) != length)
- return false;
-
- return memcmp(chars, rhsChars, length * sizeof(char16_t)) == 0;
- }
-
- bool match(const JS::ubi::EdgeName& ptr) {
- MOZ_ASSERT(ptr);
- return match(ptr.get());
- }
- };
-
- static bool match(const TwoByteString& k, const Lookup& l) {
- EqualityMatcher eq(l);
- return k.match(eq);
- }
-
- static void rekey(TwoByteString& k, TwoByteString&& newKey) {
- k = Move(newKey);
- }
-};
-
-// Returns whether `edge` should be included in a heap snapshot of
-// `compartments`. The optional `policy` out-param is set to INCLUDE_EDGES
-// if we want to include the referent's edges, or EXCLUDE_EDGES if we don't
-// want to include them.
-static bool
-ShouldIncludeEdge(JS::CompartmentSet* compartments,
- const ubi::Node& origin, const ubi::Edge& edge,
- CoreDumpWriter::EdgePolicy* policy = nullptr)
-{
- if (policy) {
- *policy = CoreDumpWriter::INCLUDE_EDGES;
- }
-
- if (!compartments) {
- // We aren't targeting a particular set of compartments, so serialize all the
- // things!
- return true;
- }
-
- // We are targeting a particular set of compartments. If this node is in our target
- // set, serialize it and all of its edges. If this node is _not_ in our
- // target set, we also serialize under the assumption that it is a shared
- // resource being used by something in our target compartments since we reached it
- // by traversing the heap graph. However, we do not serialize its outgoing
- // edges and we abandon further traversal from this node.
- //
- // If the node does not belong to any compartment, we also serialize its outgoing
- // edges. This case is relevant for Shapes: they don't belong to a specific
- // compartment and contain edges to parent/kids Shapes we want to include. Note
- // that these Shapes may contain pointers into our target compartment (the
- // Shape's getter/setter JSObjects). However, we do not serialize nodes in other
- // compartments that are reachable from these non-compartment nodes.
-
- JSCompartment* compartment = edge.referent.compartment();
-
- if (!compartment || compartments->has(compartment)) {
- return true;
- }
-
- if (policy) {
- *policy = CoreDumpWriter::EXCLUDE_EDGES;
- }
-
- return !!origin.compartment();
-}
-
-// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to the
-// given `ZeroCopyOutputStream`.
-class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter
-{
- using FrameSet = js::HashSet<uint64_t>;
- using TwoByteStringMap = js::HashMap<TwoByteString, uint64_t, TwoByteString::HashPolicy>;
- using OneByteStringMap = js::HashMap<const char*, uint64_t>;
-
- JSContext* cx;
- bool wantNames;
- // The set of |JS::ubi::StackFrame::identifier()|s that have already been
- // serialized and written to the core dump.
- FrameSet framesAlreadySerialized;
- // The set of two-byte strings that have already been serialized and written
- // to the core dump.
- TwoByteStringMap twoByteStringsAlreadySerialized;
- // The set of one-byte strings that have already been serialized and written
- // to the core dump.
- OneByteStringMap oneByteStringsAlreadySerialized;
-
- ::google::protobuf::io::ZeroCopyOutputStream& stream;
-
- JS::CompartmentSet* compartments;
-
- bool writeMessage(const ::google::protobuf::MessageLite& message) {
- // We have to create a new CodedOutputStream when writing each message so
- // that the 64MB size limit used by Coded{Output,Input}Stream to prevent
- // integer overflow is enforced per message rather than on the whole stream.
- ::google::protobuf::io::CodedOutputStream codedStream(&stream);
- codedStream.WriteVarint32(message.ByteSize());
- message.SerializeWithCachedSizes(&codedStream);
- return !codedStream.HadError();
- }
-
- // Attach the full two-byte string or a reference to a two-byte string that
- // has already been serialized to a protobuf message.
- template <typename SetStringFunction,
- typename SetRefFunction>
- bool attachTwoByteString(TwoByteString& string, SetStringFunction setString,
- SetRefFunction setRef) {
- auto ptr = twoByteStringsAlreadySerialized.lookupForAdd(string);
- if (ptr) {
- setRef(ptr->value());
- return true;
- }
-
- auto length = string.length();
- auto stringData = MakeUnique<std::string>(length * sizeof(char16_t), '\0');
- if (!stringData)
- return false;
-
- auto buf = const_cast<char16_t*>(reinterpret_cast<const char16_t*>(stringData->data()));
- string.copyToBuffer(RangedPtr<char16_t>(buf, length), length);
-
- uint64_t ref = twoByteStringsAlreadySerialized.count();
- if (!twoByteStringsAlreadySerialized.add(ptr, Move(string), ref))
- return false;
-
- setString(stringData.release());
- return true;
- }
-
- // Attach the full one-byte string or a reference to a one-byte string that
- // has already been serialized to a protobuf message.
- template <typename SetStringFunction,
- typename SetRefFunction>
- bool attachOneByteString(const char* string, SetStringFunction setString,
- SetRefFunction setRef) {
- auto ptr = oneByteStringsAlreadySerialized.lookupForAdd(string);
- if (ptr) {
- setRef(ptr->value());
- return true;
- }
-
- auto length = strlen(string);
- auto stringData = MakeUnique<std::string>(string, length);
- if (!stringData)
- return false;
-
- uint64_t ref = oneByteStringsAlreadySerialized.count();
- if (!oneByteStringsAlreadySerialized.add(ptr, string, ref))
- return false;
-
- setString(stringData.release());
- return true;
- }
-
- protobuf::StackFrame* getProtobufStackFrame(JS::ubi::StackFrame& frame,
- size_t depth = 1) {
- // NB: de-duplicated string properties must be written in the same order
- // here as they are read in `HeapSnapshot::saveStackFrame` or else indices
- // in references to already serialized strings will be off.
-
- MOZ_ASSERT(frame,
- "null frames should be represented as the lack of a serialized "
- "stack frame");
-
- auto id = frame.identifier();
- auto protobufStackFrame = MakeUnique<protobuf::StackFrame>();
- if (!protobufStackFrame)
- return nullptr;
-
- if (framesAlreadySerialized.has(id)) {
- protobufStackFrame->set_ref(id);
- return protobufStackFrame.release();
- }
-
- auto data = MakeUnique<protobuf::StackFrame_Data>();
- if (!data)
- return nullptr;
-
- data->set_id(id);
- data->set_line(frame.line());
- data->set_column(frame.column());
- data->set_issystem(frame.isSystem());
- data->set_isselfhosted(frame.isSelfHosted(cx));
-
- auto dupeSource = TwoByteString::from(frame.source());
- if (!attachTwoByteString(dupeSource,
- [&] (std::string* source) { data->set_allocated_source(source); },
- [&] (uint64_t ref) { data->set_sourceref(ref); }))
- {
- return nullptr;
- }
-
- auto dupeName = TwoByteString::from(frame.functionDisplayName());
- if (dupeName.isNonNull()) {
- if (!attachTwoByteString(dupeName,
- [&] (std::string* name) { data->set_allocated_functiondisplayname(name); },
- [&] (uint64_t ref) { data->set_functiondisplaynameref(ref); }))
- {
- return nullptr;
- }
- }
-
- auto parent = frame.parent();
- if (parent && depth < HeapSnapshot::MAX_STACK_DEPTH) {
- auto protobufParent = getProtobufStackFrame(parent, depth + 1);
- if (!protobufParent)
- return nullptr;
- data->set_allocated_parent(protobufParent);
- }
-
- protobufStackFrame->set_allocated_data(data.release());
-
- if (!framesAlreadySerialized.put(id))
- return nullptr;
-
- return protobufStackFrame.release();
- }
-
-public:
- StreamWriter(JSContext* cx,
- ::google::protobuf::io::ZeroCopyOutputStream& stream,
- bool wantNames,
- JS::CompartmentSet* compartments)
- : cx(cx)
- , wantNames(wantNames)
- , framesAlreadySerialized(cx)
- , twoByteStringsAlreadySerialized(cx)
- , oneByteStringsAlreadySerialized(cx)
- , stream(stream)
- , compartments(compartments)
- { }
-
- bool init() {
- return framesAlreadySerialized.init() &&
- twoByteStringsAlreadySerialized.init() &&
- oneByteStringsAlreadySerialized.init();
- }
-
- ~StreamWriter() override { }
-
- virtual bool writeMetadata(uint64_t timestamp) final {
- protobuf::Metadata metadata;
- metadata.set_timestamp(timestamp);
- return writeMessage(metadata);
- }
-
- virtual bool writeNode(const JS::ubi::Node& ubiNode,
- EdgePolicy includeEdges) override final {
- // NB: de-duplicated string properties must be written in the same order
- // here as they are read in `HeapSnapshot::saveNode` or else indices in
- // references to already serialized strings will be off.
-
- protobuf::Node protobufNode;
- protobufNode.set_id(ubiNode.identifier());
-
- protobufNode.set_coarsetype(JS::ubi::CoarseTypeToUint32(ubiNode.coarseType()));
-
- auto typeName = TwoByteString(ubiNode.typeName());
- if (NS_WARN_IF(!attachTwoByteString(typeName,
- [&] (std::string* name) { protobufNode.set_allocated_typename_(name); },
- [&] (uint64_t ref) { protobufNode.set_typenameref(ref); })))
- {
- return false;
- }
-
- mozilla::MallocSizeOf mallocSizeOf = dbg::GetDebuggerMallocSizeOf(cx);
- MOZ_ASSERT(mallocSizeOf);
- protobufNode.set_size(ubiNode.size(mallocSizeOf));
-
- if (includeEdges) {
- auto edges = ubiNode.edges(cx, wantNames);
- if (NS_WARN_IF(!edges))
- return false;
-
- for ( ; !edges->empty(); edges->popFront()) {
- ubi::Edge& ubiEdge = edges->front();
- if (!ShouldIncludeEdge(compartments, ubiNode, ubiEdge)) {
- continue;
- }
-
- protobuf::Edge* protobufEdge = protobufNode.add_edges();
- if (NS_WARN_IF(!protobufEdge)) {
- return false;
- }
-
- protobufEdge->set_referent(ubiEdge.referent.identifier());
-
- if (wantNames && ubiEdge.name) {
- TwoByteString edgeName(Move(ubiEdge.name));
- if (NS_WARN_IF(!attachTwoByteString(edgeName,
- [&] (std::string* name) { protobufEdge->set_allocated_name(name); },
- [&] (uint64_t ref) { protobufEdge->set_nameref(ref); })))
- {
- return false;
- }
- }
- }
- }
-
- if (ubiNode.hasAllocationStack()) {
- auto ubiStackFrame = ubiNode.allocationStack();
- auto protoStackFrame = getProtobufStackFrame(ubiStackFrame);
- if (NS_WARN_IF(!protoStackFrame))
- return false;
- protobufNode.set_allocated_allocationstack(protoStackFrame);
- }
-
- if (auto className = ubiNode.jsObjectClassName()) {
- if (NS_WARN_IF(!attachOneByteString(className,
- [&] (std::string* name) { protobufNode.set_allocated_jsobjectclassname(name); },
- [&] (uint64_t ref) { protobufNode.set_jsobjectclassnameref(ref); })))
- {
- return false;
- }
- }
-
- if (auto scriptFilename = ubiNode.scriptFilename()) {
- if (NS_WARN_IF(!attachOneByteString(scriptFilename,
- [&] (std::string* name) { protobufNode.set_allocated_scriptfilename(name); },
- [&] (uint64_t ref) { protobufNode.set_scriptfilenameref(ref); })))
- {
- return false;
- }
- }
-
- return writeMessage(protobufNode);
- }
-};
-
-// A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a
-// core dump.
-class MOZ_STACK_CLASS HeapSnapshotHandler
-{
- CoreDumpWriter& writer;
- JS::CompartmentSet* compartments;
-
-public:
- HeapSnapshotHandler(CoreDumpWriter& writer,
- JS::CompartmentSet* compartments)
- : writer(writer),
- compartments(compartments)
- { }
-
- // JS::ubi::BreadthFirst handler interface.
-
- class NodeData { };
- typedef JS::ubi::BreadthFirst<HeapSnapshotHandler> Traversal;
- bool operator() (Traversal& traversal,
- JS::ubi::Node origin,
- const JS::ubi::Edge& edge,
- NodeData*,
- bool first)
- {
- // We're only interested in the first time we reach edge.referent, not in
- // every edge arriving at that node. "But, don't we want to serialize every
- // edge in the heap graph?" you ask. Don't worry! This edge is still
- // serialized into the core dump. Serializing a node also serializes each of
- // its edges, and if we are traversing a given edge, we must have already
- // visited and serialized the origin node and its edges.
- if (!first)
- return true;
-
- CoreDumpWriter::EdgePolicy policy;
- if (!ShouldIncludeEdge(compartments, origin, edge, &policy))
- return true;
-
- if (policy == CoreDumpWriter::EXCLUDE_EDGES)
- traversal.abandonReferent();
-
- return writer.writeNode(edge.referent, policy);
- }
-};
-
-
-bool
-WriteHeapGraph(JSContext* cx,
- const JS::ubi::Node& node,
- CoreDumpWriter& writer,
- bool wantNames,
- JS::CompartmentSet* compartments,
- JS::AutoCheckCannotGC& noGC)
-{
- // Serialize the starting node to the core dump.
-
- if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
- return false;
- }
-
- // Walk the heap graph starting from the given node and serialize it into the
- // core dump.
-
- HeapSnapshotHandler handler(writer, compartments);
- HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
- if (!traversal.init())
- return false;
- traversal.wantNames = wantNames;
-
- bool ok = traversal.addStartVisited(node) &&
- traversal.traverse();
-
- return ok;
-}
-
-static unsigned long
-msSinceProcessCreation(const TimeStamp& now)
-{
- bool ignored;
- auto duration = now - TimeStamp::ProcessCreation(ignored);
- return (unsigned long) duration.ToMilliseconds();
-}
-
-/* static */ already_AddRefed<nsIFile>
-HeapSnapshot::CreateUniqueCoreDumpFile(ErrorResult& rv,
- const TimeStamp& now,
- nsAString& outFilePath)
-{
- nsCOMPtr<nsIFile> file;
- rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
- if (NS_WARN_IF(rv.Failed()))
- return nullptr;
-
- auto ms = msSinceProcessCreation(now);
- rv = file->AppendNative(nsPrintfCString("%lu.fxsnapshot", ms));
- if (NS_WARN_IF(rv.Failed()))
- return nullptr;
-
- rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
- if (NS_WARN_IF(rv.Failed()))
- return nullptr;
-
- rv = file->GetPath(outFilePath);
- if (NS_WARN_IF(rv.Failed()))
- return nullptr;
-
- return file.forget();
-}
-
-// Deletion policy for cleaning up PHeapSnapshotTempFileHelperChild pointers.
-class DeleteHeapSnapshotTempFileHelperChild
-{
-public:
- constexpr DeleteHeapSnapshotTempFileHelperChild() { }
-
- void operator()(PHeapSnapshotTempFileHelperChild* ptr) const {
- Unused << NS_WARN_IF(!HeapSnapshotTempFileHelperChild::Send__delete__(ptr));
- }
-};
-
-// A UniquePtr alias to automatically manage PHeapSnapshotTempFileHelperChild
-// pointers.
-using UniqueHeapSnapshotTempFileHelperChild = UniquePtr<PHeapSnapshotTempFileHelperChild,
- DeleteHeapSnapshotTempFileHelperChild>;
-
-// Get an nsIOutputStream that we can write the heap snapshot to. In non-e10s
-// and in the e10s parent process, open a file directly and create an output
-// stream for it. In e10s child processes, we are sandboxed without access to
-// the filesystem. Use IPDL to request a file descriptor from the parent
-// process.
-static already_AddRefed<nsIOutputStream>
-getCoreDumpOutputStream(ErrorResult& rv, TimeStamp& start, nsAString& outFilePath)
-{
- if (XRE_IsParentProcess()) {
- // Create the file and open the output stream directly.
-
- nsCOMPtr<nsIFile> file = HeapSnapshot::CreateUniqueCoreDumpFile(rv,
- start,
- outFilePath);
- if (NS_WARN_IF(rv.Failed()))
- return nullptr;
-
- nsCOMPtr<nsIOutputStream> outputStream;
- rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
- PR_WRONLY, -1, 0);
- if (NS_WARN_IF(rv.Failed()))
- return nullptr;
-
- return outputStream.forget();
- } else {
- // Request a file descriptor from the parent process over IPDL.
-
- auto cc = ContentChild::GetSingleton();
- if (!cc) {
- rv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
-
- UniqueHeapSnapshotTempFileHelperChild helper(
- cc->SendPHeapSnapshotTempFileHelperConstructor());
- if (NS_WARN_IF(!helper)) {
- rv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
-
- OpenHeapSnapshotTempFileResponse response;
- if (!helper->SendOpenHeapSnapshotTempFile(&response)) {
- rv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- if (response.type() == OpenHeapSnapshotTempFileResponse::Tnsresult) {
- rv.Throw(response.get_nsresult());
- return nullptr;
- }
-
- auto opened = response.get_OpenedFile();
- outFilePath = opened.path();
- nsCOMPtr<nsIOutputStream> outputStream =
- FileDescriptorOutputStream::Create(opened.descriptor());
- if (NS_WARN_IF(!outputStream)) {
- rv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
-
- return outputStream.forget();
- }
-}
-
-} // namespace devtools
-
-namespace dom {
-
-using namespace JS;
-using namespace devtools;
-
-/* static */ void
-ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global,
- const HeapSnapshotBoundaries& boundaries,
- nsAString& outFilePath,
- ErrorResult& rv)
-{
- auto start = TimeStamp::Now();
-
- bool wantNames = true;
- CompartmentSet compartments;
-
- nsCOMPtr<nsIOutputStream> outputStream = getCoreDumpOutputStream(rv, start, outFilePath);
- if (NS_WARN_IF(rv.Failed()))
- return;
-
- ZeroCopyNSIOutputStream zeroCopyStream(outputStream);
- ::google::protobuf::io::GzipOutputStream gzipStream(&zeroCopyStream);
-
- JSContext* cx = global.Context();
-
- {
- Maybe<AutoCheckCannotGC> maybeNoGC;
- ubi::RootList rootList(cx, maybeNoGC, wantNames);
- if (!EstablishBoundaries(cx, rv, boundaries, rootList, compartments))
- return;
-
- StreamWriter writer(cx, gzipStream, wantNames,
- compartments.initialized() ? &compartments : nullptr);
- if (NS_WARN_IF(!writer.init())) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- MOZ_ASSERT(maybeNoGC.isSome());
- ubi::Node roots(&rootList);
-
- // Serialize the initial heap snapshot metadata to the core dump.
- if (!writer.writeMetadata(PR_Now()) ||
- // Serialize the heap graph to the core dump, starting from our list of
- // roots.
- !WriteHeapGraph(cx,
- roots,
- writer,
- wantNames,
- compartments.initialized() ? &compartments : nullptr,
- maybeNoGC.ref()))
- {
- rv.Throw(zeroCopyStream.failed()
- ? zeroCopyStream.result()
- : NS_ERROR_UNEXPECTED);
- return;
- }
- }
-}
-
-/* static */ already_AddRefed<HeapSnapshot>
-ThreadSafeChromeUtils::ReadHeapSnapshot(GlobalObject& global,
- const nsAString& filePath,
- ErrorResult& rv)
-{
- UniquePtr<char[]> path(ToNewCString(filePath));
- if (!path) {
- rv.Throw(NS_ERROR_OUT_OF_MEMORY);
- return nullptr;
- }
-
- AutoMemMap mm;
- rv = mm.init(path.get());
- if (rv.Failed())
- return nullptr;
-
- RefPtr<HeapSnapshot> snapshot = HeapSnapshot::Create(
- global.Context(), global, reinterpret_cast<const uint8_t*>(mm.address()),
- mm.size(), rv);
-
- return snapshot.forget();
-}
-
-} // namespace dom
-} // namespace mozilla