diff options
author | Matt A. Tobin <email@mattatobin.com> | 2020-02-22 17:32:39 -0500 |
---|---|---|
committer | wolfbeast <mcwerewolf@wolfbeast.com> | 2020-04-14 12:50:57 +0200 |
commit | 36fc5f674ef1a02d1498484c563a7108f4de44ed (patch) | |
tree | 120483cd8fc0decd189d5118941a9b23d6156ad5 /devtools/shared/heapsnapshot/HeapSnapshot.cpp | |
parent | 7b30664f59e65cadb7d5eb2e42591e90a32871f8 (diff) | |
download | UXP-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.cpp | 1619 |
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 |