1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
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/. */
7 #ifndef js_UbiNodeShortestPaths_h
8 #define js_UbiNodeShortestPaths_h
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/Maybe.h"
15 #include "js/AllocPolicy.h"
17 #include "js/UbiNode.h"
18 #include "js/UbiNodeBreadthFirst.h"
19 #include "js/UniquePtr.h"
25 * A back edge along a path in the heap graph.
27 struct JS_PUBLIC_API BackEdge
{
33 using Ptr
= js::UniquePtr
<BackEdge
>;
35 BackEdge() : predecessor_(), name_(nullptr) {}
37 [[nodiscard
]] bool init(const Node
& predecessor
, Edge
& edge
) {
38 MOZ_ASSERT(!predecessor_
);
41 predecessor_
= predecessor
;
42 name_
= std::move(edge
.name
);
46 BackEdge(const BackEdge
&) = delete;
47 BackEdge
& operator=(const BackEdge
&) = delete;
49 BackEdge(BackEdge
&& rhs
)
50 : predecessor_(rhs
.predecessor_
), name_(std::move(rhs
.name_
)) {
51 MOZ_ASSERT(&rhs
!= this);
54 BackEdge
& operator=(BackEdge
&& rhs
) {
56 new (this) BackEdge(std::move(rhs
));
62 const EdgeName
& name() const { return name_
; }
63 EdgeName
& name() { return name_
; }
65 const JS::ubi::Node
& predecessor() const { return predecessor_
; }
69 * A path is a series of back edges from which we discovered a target node.
71 using Path
= JS::ubi::Vector
<BackEdge
*>;
74 * The `JS::ubi::ShortestPaths` type represents a collection of up to N shortest
75 * retaining paths for each of a target set of nodes, starting from the same
78 struct JS_PUBLIC_API ShortestPaths
{
80 // Types, type aliases, and data members.
82 using BackEdgeVector
= JS::ubi::Vector
<BackEdge::Ptr
>;
83 using NodeToBackEdgeVectorMap
=
84 js::HashMap
<Node
, BackEdgeVector
, js::DefaultHasher
<Node
>,
85 js::SystemAllocPolicy
>;
88 using Traversal
= BreadthFirst
<Handler
>;
91 * A `JS::ubi::BreadthFirst` traversal handler that records back edges for
92 * how we reached each node, allowing us to reconstruct the shortest
93 * retaining paths after the traversal.
96 using NodeData
= BackEdge
;
98 ShortestPaths
& shortestPaths
;
99 size_t totalMaxPathsToRecord
;
100 size_t totalPathsRecorded
;
102 explicit Handler(ShortestPaths
& shortestPaths
)
103 : shortestPaths(shortestPaths
),
104 totalMaxPathsToRecord(shortestPaths
.targets_
.count() *
105 shortestPaths
.maxNumPaths_
),
106 totalPathsRecorded(0) {}
108 bool operator()(Traversal
& traversal
, const JS::ubi::Node
& origin
,
109 JS::ubi::Edge
& edge
, BackEdge
* back
, bool first
) {
111 MOZ_ASSERT(origin
== shortestPaths
.root_
||
112 traversal
.visited
.has(origin
));
113 MOZ_ASSERT(totalPathsRecorded
< totalMaxPathsToRecord
);
115 if (first
&& !back
->init(origin
, edge
)) {
119 if (!shortestPaths
.targets_
.has(edge
.referent
)) {
123 // If `first` is true, then we moved the edge's name into `back` in
124 // the above call to `init`. So clone that back edge to get the
125 // correct edge name. If `first` is not true, then our edge name is
126 // still in `edge`. This accounts for the asymmetry between
127 // `back->clone()` in the first branch, and the `init` call in the
131 BackEdgeVector paths
;
132 if (!paths
.reserve(shortestPaths
.maxNumPaths_
)) {
135 auto cloned
= back
->clone();
139 paths
.infallibleAppend(std::move(cloned
));
140 if (!shortestPaths
.paths_
.putNew(edge
.referent
, std::move(paths
))) {
143 totalPathsRecorded
++;
145 auto ptr
= shortestPaths
.paths_
.lookup(edge
.referent
);
147 "This isn't the first time we have seen the target node "
149 "We should have inserted it into shortestPaths.paths_ the "
153 if (ptr
->value().length() < shortestPaths
.maxNumPaths_
) {
154 auto thisBackEdge
= js::MakeUnique
<BackEdge
>();
155 if (!thisBackEdge
|| !thisBackEdge
->init(origin
, edge
)) {
158 ptr
->value().infallibleAppend(std::move(thisBackEdge
));
159 totalPathsRecorded
++;
163 MOZ_ASSERT(totalPathsRecorded
<= totalMaxPathsToRecord
);
164 if (totalPathsRecorded
== totalMaxPathsToRecord
) {
172 // The maximum number of paths to record for each node.
173 uint32_t maxNumPaths_
;
175 // The root node we are starting the search from.
178 // The set of nodes we are searching for paths to.
181 // The resulting paths.
182 NodeToBackEdgeVectorMap paths_
;
184 // Need to keep alive the traversal's back edges so we can walk them later
185 // when the traversal is over when recreating the shortest paths.
186 Traversal::NodeMap backEdges_
;
191 ShortestPaths(uint32_t maxNumPaths
, const Node
& root
, NodeSet
&& targets
)
192 : maxNumPaths_(maxNumPaths
),
194 targets_(std::move(targets
)),
195 paths_(targets_
.count()),
197 MOZ_ASSERT(maxNumPaths_
> 0);
204 ShortestPaths(ShortestPaths
&& rhs
)
205 : maxNumPaths_(rhs
.maxNumPaths_
),
207 targets_(std::move(rhs
.targets_
)),
208 paths_(std::move(rhs
.paths_
)),
209 backEdges_(std::move(rhs
.backEdges_
)) {
210 MOZ_ASSERT(this != &rhs
, "self-move is not allowed");
213 ShortestPaths
& operator=(ShortestPaths
&& rhs
) {
214 this->~ShortestPaths();
215 new (this) ShortestPaths(std::move(rhs
));
219 ShortestPaths(const ShortestPaths
&) = delete;
220 ShortestPaths
& operator=(const ShortestPaths
&) = delete;
223 * Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths`
224 * shortest retaining paths for each target node in `targets` starting from
227 * The resulting `ShortestPaths` instance must not outlive the
228 * `JS::ubi::Node` graph it was constructed from.
230 * - For `JS::ubi::Node` graphs backed by the live heap graph, this means
231 * that the `ShortestPaths`'s lifetime _must_ be contained within the
232 * scope of the provided `AutoCheckCannotGC` reference because a GC will
233 * invalidate the nodes.
235 * - For `JS::ubi::Node` graphs backed by some other offline structure
236 * provided by the embedder, the resulting `ShortestPaths`'s lifetime is
237 * bounded by that offline structure's lifetime.
239 * Returns `mozilla::Nothing()` on OOM failure. It is the caller's
240 * responsibility to handle and report the OOM.
242 static mozilla::Maybe
<ShortestPaths
> Create(JSContext
* cx
,
243 AutoCheckCannotGC
& noGC
,
244 uint32_t maxNumPaths
,
247 MOZ_ASSERT(targets
.count() > 0);
248 MOZ_ASSERT(maxNumPaths
> 0);
250 mozilla::CheckedInt
<uint32_t> max
= maxNumPaths
;
251 max
*= targets
.count();
252 if (!max
.isValid()) {
253 return mozilla::Nothing();
256 ShortestPaths
paths(maxNumPaths
, root
, std::move(targets
));
258 Handler
handler(paths
);
259 Traversal
traversal(cx
, handler
, noGC
);
260 traversal
.wantNames
= true;
261 if (!traversal
.addStart(root
) || !traversal
.traverse()) {
262 return mozilla::Nothing();
265 // Take ownership of the back edges we created while traversing the
266 // graph so that we can follow them from `paths_` and don't
268 paths
.backEdges_
= std::move(traversal
.visited
);
270 return mozilla::Some(std::move(paths
));
274 * Get an iterator over each target node we searched for retaining paths
275 * for. The returned iterator must not outlive the `ShortestPaths`
278 NodeSet::Iterator
targetIter() const { return targets_
.iter(); }
281 * Invoke the provided functor/lambda/callable once for each retaining path
282 * discovered for `target`. The `func` is passed a single `JS::ubi::Path&`
283 * argument, which contains each edge along the path ordered starting from
284 * the root and ending at the target, and must not outlive the scope of the
287 * Note that it is possible that we did not find any paths from the root to
288 * the given target, in which case `func` will not be invoked.
290 template <class Func
>
291 [[nodiscard
]] bool forEachPath(const Node
& target
, Func func
) {
292 MOZ_ASSERT(targets_
.has(target
));
294 auto ptr
= paths_
.lookup(target
);
296 // We didn't find any paths to this target, so nothing to do here.
301 MOZ_ASSERT(ptr
->value().length() <= maxNumPaths_
);
304 for (const auto& backEdge
: ptr
->value()) {
307 if (!path
.append(backEdge
.get())) {
311 Node here
= backEdge
->predecessor();
314 while (here
!= root_
) {
315 auto p
= backEdges_
.lookup(here
);
317 if (!path
.append(&p
->value())) {
320 here
= p
->value().predecessor();
336 // A helper function to dump the first `maxNumPaths` shortest retaining paths to
337 // `node` from the GC roots. Useful when GC things you expect to have been
338 // reclaimed by the collector haven't been!
342 // JSObject* foo = ...;
343 // JS::ubi::dumpPaths(rt, JS::ubi::Node(foo));
344 JS_PUBLIC_API
void dumpPaths(JSRuntime
* rt
, Node node
,
345 uint32_t maxNumPaths
= 10);
351 #endif // js_UbiNodeShortestPaths_h