/work/obj-fuzz/dist/include/js/UbiNodeShortestPaths.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | | * vim: set ts=8 sts=4 et sw=4 tw=99: |
3 | | * This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #ifndef js_UbiNodeShortestPaths_h |
8 | | #define js_UbiNodeShortestPaths_h |
9 | | |
10 | | #include "mozilla/Attributes.h" |
11 | | #include "mozilla/Maybe.h" |
12 | | #include "mozilla/Move.h" |
13 | | |
14 | | #include "js/AllocPolicy.h" |
15 | | #include "js/UbiNodeBreadthFirst.h" |
16 | | #include "js/UniquePtr.h" |
17 | | #include "js/Vector.h" |
18 | | |
19 | | namespace JS { |
20 | | namespace ubi { |
21 | | |
22 | | /** |
23 | | * A back edge along a path in the heap graph. |
24 | | */ |
25 | | struct JS_PUBLIC_API(BackEdge) |
26 | | { |
27 | | private: |
28 | | Node predecessor_; |
29 | | EdgeName name_; |
30 | | |
31 | | public: |
32 | | using Ptr = js::UniquePtr<BackEdge>; |
33 | | |
34 | 0 | BackEdge() : predecessor_(), name_(nullptr) { } |
35 | | |
36 | 0 | MOZ_MUST_USE bool init(const Node& predecessor, Edge& edge) { |
37 | 0 | MOZ_ASSERT(!predecessor_); |
38 | 0 | MOZ_ASSERT(!name_); |
39 | 0 |
|
40 | 0 | predecessor_ = predecessor; |
41 | 0 | name_ = std::move(edge.name); |
42 | 0 | return true; |
43 | 0 | } |
44 | | |
45 | | BackEdge(const BackEdge&) = delete; |
46 | | BackEdge& operator=(const BackEdge&) = delete; |
47 | | |
48 | | BackEdge(BackEdge&& rhs) |
49 | | : predecessor_(rhs.predecessor_) |
50 | | , name_(std::move(rhs.name_)) |
51 | 0 | { |
52 | 0 | MOZ_ASSERT(&rhs != this); |
53 | 0 | } |
54 | | |
55 | 0 | BackEdge& operator=(BackEdge&& rhs) { |
56 | 0 | this->~BackEdge(); |
57 | 0 | new(this) BackEdge(std::move(rhs)); |
58 | 0 | return *this; |
59 | 0 | } |
60 | | |
61 | | Ptr clone() const; |
62 | | |
63 | 0 | const EdgeName& name() const { return name_; } |
64 | 0 | EdgeName& name() { return name_; } |
65 | | |
66 | 0 | const JS::ubi::Node& predecessor() const { return predecessor_; } |
67 | | }; |
68 | | |
69 | | /** |
70 | | * A path is a series of back edges from which we discovered a target node. |
71 | | */ |
72 | | using Path = JS::ubi::Vector<BackEdge*>; |
73 | | |
74 | | /** |
75 | | * The `JS::ubi::ShortestPaths` type represents a collection of up to N shortest |
76 | | * retaining paths for each of a target set of nodes, starting from the same |
77 | | * root node. |
78 | | */ |
79 | | struct JS_PUBLIC_API(ShortestPaths) |
80 | | { |
81 | | private: |
82 | | // Types, type aliases, and data members. |
83 | | |
84 | | using BackEdgeVector = JS::ubi::Vector<BackEdge::Ptr>; |
85 | | using NodeToBackEdgeVectorMap = js::HashMap<Node, BackEdgeVector, js::DefaultHasher<Node>, |
86 | | js::SystemAllocPolicy>; |
87 | | |
88 | | struct Handler; |
89 | | using Traversal = BreadthFirst<Handler>; |
90 | | |
91 | | /** |
92 | | * A `JS::ubi::BreadthFirst` traversal handler that records back edges for |
93 | | * how we reached each node, allowing us to reconstruct the shortest |
94 | | * retaining paths after the traversal. |
95 | | */ |
96 | | struct Handler |
97 | | { |
98 | | using NodeData = BackEdge; |
99 | | |
100 | | ShortestPaths& shortestPaths; |
101 | | size_t totalMaxPathsToRecord; |
102 | | size_t totalPathsRecorded; |
103 | | |
104 | | explicit Handler(ShortestPaths& shortestPaths) |
105 | | : shortestPaths(shortestPaths) |
106 | | , totalMaxPathsToRecord(shortestPaths.targets_.count() * shortestPaths.maxNumPaths_) |
107 | | , totalPathsRecorded(0) |
108 | 0 | { |
109 | 0 | } |
110 | | |
111 | | bool |
112 | | operator()(Traversal& traversal, const JS::ubi::Node& origin, JS::ubi::Edge& edge, |
113 | | BackEdge* back, bool first) |
114 | 0 | { |
115 | 0 | MOZ_ASSERT(back); |
116 | 0 | MOZ_ASSERT(origin == shortestPaths.root_ || traversal.visited.has(origin)); |
117 | 0 | MOZ_ASSERT(totalPathsRecorded < totalMaxPathsToRecord); |
118 | 0 |
|
119 | 0 | if (first && !back->init(origin, edge)) { |
120 | 0 | return false; |
121 | 0 | } |
122 | 0 | |
123 | 0 | if (!shortestPaths.targets_.has(edge.referent)) { |
124 | 0 | return true; |
125 | 0 | } |
126 | 0 | |
127 | 0 | // If `first` is true, then we moved the edge's name into `back` in |
128 | 0 | // the above call to `init`. So clone that back edge to get the |
129 | 0 | // correct edge name. If `first` is not true, then our edge name is |
130 | 0 | // still in `edge`. This accounts for the asymmetry between |
131 | 0 | // `back->clone()` in the first branch, and the `init` call in the |
132 | 0 | // second branch. |
133 | 0 | |
134 | 0 | if (first) { |
135 | 0 | BackEdgeVector paths; |
136 | 0 | if (!paths.reserve(shortestPaths.maxNumPaths_)) { |
137 | 0 | return false; |
138 | 0 | } |
139 | 0 | auto cloned = back->clone(); |
140 | 0 | if (!cloned) { |
141 | 0 | return false; |
142 | 0 | } |
143 | 0 | paths.infallibleAppend(std::move(cloned)); |
144 | 0 | if (!shortestPaths.paths_.putNew(edge.referent, std::move(paths))) { |
145 | 0 | return false; |
146 | 0 | } |
147 | 0 | totalPathsRecorded++; |
148 | 0 | } else { |
149 | 0 | auto ptr = shortestPaths.paths_.lookup(edge.referent); |
150 | 0 | MOZ_ASSERT(ptr, |
151 | 0 | "This isn't the first time we have seen the target node `edge.referent`. " |
152 | 0 | "We should have inserted it into shortestPaths.paths_ the first time we " |
153 | 0 | "saw it."); |
154 | 0 |
|
155 | 0 | if (ptr->value().length() < shortestPaths.maxNumPaths_) { |
156 | 0 | auto thisBackEdge = js::MakeUnique<BackEdge>(); |
157 | 0 | if (!thisBackEdge || !thisBackEdge->init(origin, edge)) { |
158 | 0 | return false; |
159 | 0 | } |
160 | 0 | ptr->value().infallibleAppend(std::move(thisBackEdge)); |
161 | 0 | totalPathsRecorded++; |
162 | 0 | } |
163 | 0 | } |
164 | 0 |
|
165 | 0 | MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord); |
166 | 0 | if (totalPathsRecorded == totalMaxPathsToRecord) { |
167 | 0 | traversal.stop(); |
168 | 0 | } |
169 | 0 |
|
170 | 0 | return true; |
171 | 0 | } |
172 | | |
173 | | }; |
174 | | |
175 | | // The maximum number of paths to record for each node. |
176 | | uint32_t maxNumPaths_; |
177 | | |
178 | | // The root node we are starting the search from. |
179 | | Node root_; |
180 | | |
181 | | // The set of nodes we are searching for paths to. |
182 | | NodeSet targets_; |
183 | | |
184 | | // The resulting paths. |
185 | | NodeToBackEdgeVectorMap paths_; |
186 | | |
187 | | // Need to keep alive the traversal's back edges so we can walk them later |
188 | | // when the traversal is over when recreating the shortest paths. |
189 | | Traversal::NodeMap backEdges_; |
190 | | |
191 | | private: |
192 | | // Private methods. |
193 | | |
194 | | ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets) |
195 | | : maxNumPaths_(maxNumPaths) |
196 | | , root_(root) |
197 | | , targets_(std::move(targets)) |
198 | | , paths_(targets_.count()) |
199 | | , backEdges_() |
200 | 0 | { |
201 | 0 | MOZ_ASSERT(maxNumPaths_ > 0); |
202 | 0 | MOZ_ASSERT(root_); |
203 | 0 | } |
204 | | |
205 | | public: |
206 | | // Public methods. |
207 | | |
208 | | ShortestPaths(ShortestPaths&& rhs) |
209 | | : maxNumPaths_(rhs.maxNumPaths_) |
210 | | , root_(rhs.root_) |
211 | | , targets_(std::move(rhs.targets_)) |
212 | | , paths_(std::move(rhs.paths_)) |
213 | | , backEdges_(std::move(rhs.backEdges_)) |
214 | 0 | { |
215 | 0 | MOZ_ASSERT(this != &rhs, "self-move is not allowed"); |
216 | 0 | } |
217 | | |
218 | 0 | ShortestPaths& operator=(ShortestPaths&& rhs) { |
219 | 0 | this->~ShortestPaths(); |
220 | 0 | new (this) ShortestPaths(std::move(rhs)); |
221 | 0 | return *this; |
222 | 0 | } |
223 | | |
224 | | ShortestPaths(const ShortestPaths&) = delete; |
225 | | ShortestPaths& operator=(const ShortestPaths&) = delete; |
226 | | |
227 | | /** |
228 | | * Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths` |
229 | | * shortest retaining paths for each target node in `targets` starting from |
230 | | * `root`. |
231 | | * |
232 | | * The resulting `ShortestPaths` instance must not outlive the |
233 | | * `JS::ubi::Node` graph it was constructed from. |
234 | | * |
235 | | * - For `JS::ubi::Node` graphs backed by the live heap graph, this means |
236 | | * that the `ShortestPaths`'s lifetime _must_ be contained within the |
237 | | * scope of the provided `AutoCheckCannotGC` reference because a GC will |
238 | | * invalidate the nodes. |
239 | | * |
240 | | * - For `JS::ubi::Node` graphs backed by some other offline structure |
241 | | * provided by the embedder, the resulting `ShortestPaths`'s lifetime is |
242 | | * bounded by that offline structure's lifetime. |
243 | | * |
244 | | * Returns `mozilla::Nothing()` on OOM failure. It is the caller's |
245 | | * responsibility to handle and report the OOM. |
246 | | */ |
247 | | static mozilla::Maybe<ShortestPaths> |
248 | 0 | Create(JSContext* cx, AutoCheckCannotGC& noGC, uint32_t maxNumPaths, const Node& root, NodeSet&& targets) { |
249 | 0 | MOZ_ASSERT(targets.count() > 0); |
250 | 0 | MOZ_ASSERT(maxNumPaths > 0); |
251 | 0 |
|
252 | 0 | ShortestPaths paths(maxNumPaths, root, std::move(targets)); |
253 | 0 |
|
254 | 0 | Handler handler(paths); |
255 | 0 | Traversal traversal(cx, handler, noGC); |
256 | 0 | traversal.wantNames = true; |
257 | 0 | if (!traversal.addStart(root) || !traversal.traverse()) { |
258 | 0 | return mozilla::Nothing(); |
259 | 0 | } |
260 | 0 | |
261 | 0 | // Take ownership of the back edges we created while traversing the |
262 | 0 | // graph so that we can follow them from `paths_` and don't |
263 | 0 | // use-after-free. |
264 | 0 | paths.backEdges_ = std::move(traversal.visited); |
265 | 0 |
|
266 | 0 | return mozilla::Some(std::move(paths)); |
267 | 0 | } |
268 | | |
269 | | /** |
270 | | * Get an iterator over each target node we searched for retaining paths |
271 | | * for. The returned iterator must not outlive the `ShortestPaths` |
272 | | * instance. |
273 | | */ |
274 | 0 | NodeSet::Iterator targetIter() const { |
275 | 0 | return targets_.iter(); |
276 | 0 | } |
277 | | |
278 | | /** |
279 | | * Invoke the provided functor/lambda/callable once for each retaining path |
280 | | * discovered for `target`. The `func` is passed a single `JS::ubi::Path&` |
281 | | * argument, which contains each edge along the path ordered starting from |
282 | | * the root and ending at the target, and must not outlive the scope of the |
283 | | * call. |
284 | | * |
285 | | * Note that it is possible that we did not find any paths from the root to |
286 | | * the given target, in which case `func` will not be invoked. |
287 | | */ |
288 | | template <class Func> |
289 | 0 | MOZ_MUST_USE bool forEachPath(const Node& target, Func func) { |
290 | 0 | MOZ_ASSERT(targets_.has(target)); |
291 | 0 |
|
292 | 0 | auto ptr = paths_.lookup(target); |
293 | 0 |
|
294 | 0 | // We didn't find any paths to this target, so nothing to do here. |
295 | 0 | if (!ptr) { |
296 | 0 | return true; |
297 | 0 | } |
298 | 0 | |
299 | 0 | MOZ_ASSERT(ptr->value().length() <= maxNumPaths_); |
300 | 0 |
|
301 | 0 | Path path; |
302 | 0 | for (const auto& backEdge : ptr->value()) { |
303 | 0 | path.clear(); |
304 | 0 |
|
305 | 0 | if (!path.append(backEdge.get())) { |
306 | 0 | return false; |
307 | 0 | } |
308 | 0 | |
309 | 0 | Node here = backEdge->predecessor(); |
310 | 0 | MOZ_ASSERT(here); |
311 | 0 |
|
312 | 0 | while (here != root_) { |
313 | 0 | auto p = backEdges_.lookup(here); |
314 | 0 | MOZ_ASSERT(p); |
315 | 0 | if (!path.append(&p->value())) { |
316 | 0 | return false; |
317 | 0 | } |
318 | 0 | here = p->value().predecessor(); |
319 | 0 | MOZ_ASSERT(here); |
320 | 0 | } |
321 | 0 |
|
322 | 0 | path.reverse(); |
323 | 0 |
|
324 | 0 | if (!func(path)) { |
325 | 0 | return false; |
326 | 0 | } |
327 | 0 | } |
328 | 0 |
|
329 | 0 | return true; |
330 | 0 | } Unexecuted instantiation: HeapSnapshot.cpp:bool JS::ubi::ShortestPaths::forEachPath<mozilla::devtools::HeapSnapshot::ComputeShortestPaths(JSContext*, unsigned long, mozilla::dom::Sequence<unsigned long> const&, unsigned long, JS::MutableHandle<JSObject*>, mozilla::ErrorResult&)::$_0>(JS::ubi::Node const&, mozilla::devtools::HeapSnapshot::ComputeShortestPaths(JSContext*, unsigned long, mozilla::dom::Sequence<unsigned long> const&, unsigned long, JS::MutableHandle<JSObject*>, mozilla::ErrorResult&)::$_0) Unexecuted instantiation: Unified_cpp_js_src2.cpp:bool JS::ubi::ShortestPaths::forEachPath<ShortestPaths(JSContext*, unsigned int, JS::Value*)::$_3>(JS::ubi::Node const&, ShortestPaths(JSContext*, unsigned int, JS::Value*)::$_3) |
331 | | }; |
332 | | |
333 | | #ifdef DEBUG |
334 | | // A helper function to dump the first `maxNumPaths` shortest retaining paths to |
335 | | // `node` from the GC roots. Useful when GC things you expect to have been |
336 | | // reclaimed by the collector haven't been! |
337 | | // |
338 | | // Usage: |
339 | | // |
340 | | // JSObject* foo = ...; |
341 | | // JS::ubi::dumpPaths(rt, JS::ubi::Node(foo)); |
342 | | JS_PUBLIC_API(void) |
343 | | dumpPaths(JSRuntime* rt, Node node, uint32_t maxNumPaths = 10); |
344 | | #endif |
345 | | |
346 | | } // namespace ubi |
347 | | } // namespace JS |
348 | | |
349 | | #endif // js_UbiNodeShortestPaths_h |