1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #ifndef mozilla_devtools_HeapSnapshot__
7 #define mozilla_devtools_HeapSnapshot__
8
9 #include "js/HashTable.h"
10 #include "mozilla/ErrorResult.h"
11 #include "mozilla/devtools/DeserializedNode.h"
12 #include "mozilla/dom/BindingDeclarations.h"
13 #include "mozilla/dom/Nullable.h"
14 #include "mozilla/HashFunctions.h"
15 #include "mozilla/Maybe.h"
16 #include "mozilla/RefCounted.h"
17 #include "mozilla/RefPtr.h"
18 #include "mozilla/TimeStamp.h"
19 #include "mozilla/UniquePtr.h"
20
21 #include "CoreDump.pb.h"
22 #include "nsCOMPtr.h"
23 #include "nsCRTGlue.h"
24 #include "nsCycleCollectionParticipant.h"
25 #include "nsISupports.h"
26 #include "nsWrapperCache.h"
27 #include "nsXPCOM.h"
28
29 namespace mozilla {
30 namespace devtools {
31
32 class DominatorTree;
33
34 struct NSFreePolicy {
operatorNSFreePolicy35 void operator()(void* ptr) {
36 NS_Free(ptr);
37 }
38 };
39
40 using UniqueTwoByteString = UniquePtr<char16_t[], NSFreePolicy>;
41 using UniqueOneByteString = UniquePtr<char[], NSFreePolicy>;
42
43 class HeapSnapshot final : public nsISupports
44 , public nsWrapperCache
45 {
46 friend struct DeserializedNode;
47 friend struct DeserializedEdge;
48 friend struct DeserializedStackFrame;
49 friend class JS::ubi::Concrete<JS::ubi::DeserializedNode>;
50
HeapSnapshot(JSContext * cx,nsISupports * aParent)51 explicit HeapSnapshot(JSContext* cx, nsISupports* aParent)
52 : timestamp(Nothing())
53 , rootId(0)
54 , nodes(cx)
55 , frames(cx)
56 , mParent(aParent)
57 {
58 MOZ_ASSERT(aParent);
59 };
60
61 // Initialize this HeapSnapshot from the given buffer that contains a
62 // serialized core dump. Do NOT take ownership of the buffer, only borrow it
63 // for the duration of the call. Return false on failure.
64 bool init(JSContext* cx, const uint8_t* buffer, uint32_t size);
65
66 using NodeIdSet = js::HashSet<NodeId>;
67
68 // Save the given `protobuf::Node` message in this `HeapSnapshot` as a
69 // `DeserializedNode`.
70 bool saveNode(const protobuf::Node& node, NodeIdSet& edgeReferents);
71
72 // Save the given `protobuf::StackFrame` message in this `HeapSnapshot` as a
73 // `DeserializedStackFrame`. The saved stack frame's id is returned via the
74 // out parameter.
75 bool saveStackFrame(const protobuf::StackFrame& frame,
76 StackFrameId& outFrameId);
77
78 public:
79 // The maximum number of stack frames that we will serialize into a core
80 // dump. This helps prevent over-recursion in the protobuf library when
81 // deserializing stacks.
82 static const size_t MAX_STACK_DEPTH = 60;
83
84 private:
85 // If present, a timestamp in the same units that `PR_Now` gives.
86 Maybe<uint64_t> timestamp;
87
88 // The id of the root node for this deserialized heap graph.
89 NodeId rootId;
90
91 // The set of nodes in this deserialized heap graph, keyed by id.
92 using NodeSet = js::HashSet<DeserializedNode, DeserializedNode::HashPolicy>;
93 NodeSet nodes;
94
95 // The set of stack frames in this deserialized heap graph, keyed by id.
96 using FrameSet = js::HashSet<DeserializedStackFrame,
97 DeserializedStackFrame::HashPolicy>;
98 FrameSet frames;
99
100 Vector<UniqueTwoByteString> internedTwoByteStrings;
101 Vector<UniqueOneByteString> internedOneByteStrings;
102
103 using StringOrRef = Variant<const std::string*, uint64_t>;
104
105 template<typename CharT,
106 typename InternedStringSet>
107 const CharT* getOrInternString(InternedStringSet& internedStrings,
108 Maybe<StringOrRef>& maybeStrOrRef);
109
110 protected:
111 nsCOMPtr<nsISupports> mParent;
112
~HeapSnapshot()113 virtual ~HeapSnapshot() { }
114
115 public:
116 // Create a `HeapSnapshot` from the given buffer that contains a serialized
117 // core dump. Do NOT take ownership of the buffer, only borrow it for the
118 // duration of the call.
119 static already_AddRefed<HeapSnapshot> Create(JSContext* cx,
120 dom::GlobalObject& global,
121 const uint8_t* buffer,
122 uint32_t size,
123 ErrorResult& rv);
124
125 // Creates the `$TEMP_DIR/XXXXXX-XXX.fxsnapshot` core dump file that heap
126 // snapshots are serialized into.
127 static already_AddRefed<nsIFile> CreateUniqueCoreDumpFile(ErrorResult& rv,
128 const TimeStamp& now,
129 nsAString& outFilePath);
130
131 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(HeapSnapshot)132 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(HeapSnapshot)
133 MOZ_DECLARE_REFCOUNTED_TYPENAME(HeapSnapshot)
134
135 nsISupports* GetParentObject() const { return mParent; }
136
137 virtual JSObject* WrapObject(JSContext* aCx,
138 JS::Handle<JSObject*> aGivenProto) override;
139
140 const char16_t* borrowUniqueString(const char16_t* duplicateString,
141 size_t length);
142
143 // Get the root node of this heap snapshot's graph.
getRoot()144 JS::ubi::Node getRoot() {
145 MOZ_ASSERT(nodes.initialized());
146 auto p = nodes.lookup(rootId);
147 MOZ_ASSERT(p);
148 const DeserializedNode& node = *p;
149 return JS::ubi::Node(const_cast<DeserializedNode*>(&node));
150 }
151
getNodeById(JS::ubi::Node::Id nodeId)152 Maybe<JS::ubi::Node> getNodeById(JS::ubi::Node::Id nodeId) {
153 auto p = nodes.lookup(nodeId);
154 if (!p)
155 return Nothing();
156 return Some(JS::ubi::Node(const_cast<DeserializedNode*>(&*p)));
157 }
158
159 void TakeCensus(JSContext* cx, JS::HandleObject options,
160 JS::MutableHandleValue rval, ErrorResult& rv);
161
162 void DescribeNode(JSContext* cx, JS::HandleObject breakdown, uint64_t nodeId,
163 JS::MutableHandleValue rval, ErrorResult& rv);
164
165 already_AddRefed<DominatorTree> ComputeDominatorTree(ErrorResult& rv);
166
167 void ComputeShortestPaths(JSContext*cx, uint64_t start,
168 const dom::Sequence<uint64_t>& targets,
169 uint64_t maxNumPaths,
170 JS::MutableHandleObject results,
171 ErrorResult& rv);
172
GetCreationTime()173 dom::Nullable<uint64_t> GetCreationTime() {
174 static const uint64_t maxTime = uint64_t(1) << 53;
175 if (timestamp.isSome() && timestamp.ref() <= maxTime) {
176 return dom::Nullable<uint64_t>(timestamp.ref());
177 }
178
179 return dom::Nullable<uint64_t>();
180 }
181 };
182
183 // A `CoreDumpWriter` is given the data we wish to save in a core dump and
184 // serializes it to disk, or memory, or a socket, etc.
185 class CoreDumpWriter
186 {
187 public:
~CoreDumpWriter()188 virtual ~CoreDumpWriter() { };
189
190 // Write the given bits of metadata we would like to associate with this core
191 // dump.
192 virtual bool writeMetadata(uint64_t timestamp) = 0;
193
194 enum EdgePolicy : bool {
195 INCLUDE_EDGES = true,
196 EXCLUDE_EDGES = false
197 };
198
199 // Write the given `JS::ubi::Node` to the core dump. The given `EdgePolicy`
200 // dictates whether its outgoing edges should also be written to the core
201 // dump, or excluded.
202 virtual bool writeNode(const JS::ubi::Node& node,
203 EdgePolicy includeEdges) = 0;
204 };
205
206 // Serialize the heap graph as seen from `node` with the given `CoreDumpWriter`.
207 // If `wantNames` is true, capture edge names. If `zones` is non-null, only
208 // capture the sub-graph within the zone set, otherwise capture the whole heap
209 // graph. Returns false on failure.
210 bool
211 WriteHeapGraph(JSContext* cx,
212 const JS::ubi::Node& node,
213 CoreDumpWriter& writer,
214 bool wantNames,
215 JS::CompartmentSet* compartments,
216 JS::AutoCheckCannotGC& noGC,
217 uint32_t& outNodeCount,
218 uint32_t& outEdgeCount);
219 inline bool
WriteHeapGraph(JSContext * cx,const JS::ubi::Node & node,CoreDumpWriter & writer,bool wantNames,JS::CompartmentSet * compartments,JS::AutoCheckCannotGC & noGC)220 WriteHeapGraph(JSContext* cx,
221 const JS::ubi::Node& node,
222 CoreDumpWriter& writer,
223 bool wantNames,
224 JS::CompartmentSet* compartments,
225 JS::AutoCheckCannotGC& noGC)
226 {
227 uint32_t ignoreNodeCount;
228 uint32_t ignoreEdgeCount;
229 return WriteHeapGraph(cx, node, writer, wantNames, compartments, noGC,
230 ignoreNodeCount, ignoreEdgeCount);
231 }
232
233 // Get the mozilla::MallocSizeOf for the current thread's JSRuntime.
234 MallocSizeOf GetCurrentThreadDebuggerMallocSizeOf();
235
236 } // namespace devtools
237 } // namespace mozilla
238
239 #endif // mozilla_devtools_HeapSnapshot__
240