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/Attributes.h"
11 #include "mozilla/Maybe.h"
15 #include "js/AllocPolicy.h"
16 #include "js/UbiNodeBreadthFirst.h"
17 #include "js/UniquePtr.h"
18 #include "js/Vector.h"
24 * A back edge along a path in the heap graph.
26 struct JS_PUBLIC_API BackEdge
{
32 using Ptr
= js::UniquePtr
<BackEdge
>;
34 BackEdge() : predecessor_(), name_(nullptr) {}
36 [[nodiscard
]] bool init(const Node
& predecessor
, Edge
& edge
) {
37 MOZ_ASSERT(!predecessor_
);
40 predecessor_
= predecessor
;
41 name_
= std::move(edge
.name
);
45 BackEdge(const BackEdge
&) = delete;
46 BackEdge
& operator=(const BackEdge
&) = delete;
48 BackEdge(BackEdge
&& rhs
)
49 : predecessor_(rhs
.predecessor_
), name_(std::move(rhs
.name_
)) {
50 MOZ_ASSERT(&rhs
!= this);
53 BackEdge
& operator=(BackEdge
&& rhs
) {
55 new (this) BackEdge(std::move(rhs
));
61 const EdgeName
& name() const { return name_
; }
62 EdgeName
& name() { return name_
; }
64 const JS::ubi::Node
& predecessor() const { return predecessor_
; }
68 * A path is a series of back edges from which we discovered a target node.
70 using Path
= JS::ubi::Vector
<BackEdge
*>;
73 * The `JS::ubi::ShortestPaths` type represents a collection of up to N shortest
74 * retaining paths for each of a target set of nodes, starting from the same
77 struct JS_PUBLIC_API ShortestPaths
{
79 // Types, type aliases, and data members.
81 using BackEdgeVector
= JS::ubi::Vector
<BackEdge::Ptr
>;
82 using NodeToBackEdgeVectorMap
=
83 js::HashMap
<Node
, BackEdgeVector
, js::DefaultHasher
<Node
>,
84 js::SystemAllocPolicy
>;
87 using Traversal
= BreadthFirst
<Handler
>;
90 * A `JS::ubi::BreadthFirst` traversal handler that records back edges for
91 * how we reached each node, allowing us to reconstruct the shortest
92 * retaining paths after the traversal.
95 using NodeData
= BackEdge
;
97 ShortestPaths
& shortestPaths
;
98 size_t totalMaxPathsToRecord
;
99 size_t totalPathsRecorded
;
101 explicit Handler(ShortestPaths
& shortestPaths
)
102 : shortestPaths(shortestPaths
),
103 totalMaxPathsToRecord(shortestPaths
.targets_
.count() *
104 shortestPaths
.maxNumPaths_
),
105 totalPathsRecorded(0) {}
107 bool operator()(Traversal
& traversal
, const JS::ubi::Node
& origin
,
108 JS::ubi::Edge
& edge
, BackEdge
* back
, bool first
) {
110 MOZ_ASSERT(origin
== shortestPaths
.root_
||
111 traversal
.visited
.has(origin
));
112 MOZ_ASSERT(totalPathsRecorded
< totalMaxPathsToRecord
);
114 if (first
&& !back
->init(origin
, edge
)) {
118 if (!shortestPaths
.targets_
.has(edge
.referent
)) {
122 // If `first` is true, then we moved the edge's name into `back` in
123 // the above call to `init`. So clone that back edge to get the
124 // correct edge name. If `first` is not true, then our edge name is
125 // still in `edge`. This accounts for the asymmetry between
126 // `back->clone()` in the first branch, and the `init` call in the
130 BackEdgeVector paths
;
131 if (!paths
.reserve(shortestPaths
.maxNumPaths_
)) {
134 auto cloned
= back
->clone();
138 paths
.infallibleAppend(std::move(cloned
));
139 if (!shortestPaths
.paths_
.putNew(edge
.referent
, std::move(paths
))) {
142 totalPathsRecorded
++;
144 auto ptr
= shortestPaths
.paths_
.lookup(edge
.referent
);
146 "This isn't the first time we have seen the target node "
148 "We should have inserted it into shortestPaths.paths_ the "
152 if (ptr
->value().length() < shortestPaths
.maxNumPaths_
) {
153 auto thisBackEdge
= js::MakeUnique
<BackEdge
>();
154 if (!thisBackEdge
|| !thisBackEdge
->init(origin
, edge
)) {
157 ptr
->value().infallibleAppend(std::move(thisBackEdge
));
158 totalPathsRecorded
++;
162 MOZ_ASSERT(totalPathsRecorded
<= totalMaxPathsToRecord
);
163 if (totalPathsRecorded
== totalMaxPathsToRecord
) {
171 // The maximum number of paths to record for each node.
172 uint32_t maxNumPaths_
;
174 // The root node we are starting the search from.
177 // The set of nodes we are searching for paths to.
180 // The resulting paths.
181 NodeToBackEdgeVectorMap paths_
;
183 // Need to keep alive the traversal's back edges so we can walk them later
184 // when the traversal is over when recreating the shortest paths.
185 Traversal::NodeMap backEdges_
;
190 ShortestPaths(uint32_t maxNumPaths
, const Node
& root
, NodeSet
&& targets
)
191 : maxNumPaths_(maxNumPaths
),
193 targets_(std::move(targets
)),
194 paths_(targets_
.count()),
196 MOZ_ASSERT(maxNumPaths_
> 0);
203 ShortestPaths(ShortestPaths
&& rhs
)
204 : maxNumPaths_(rhs
.maxNumPaths_
),
206 targets_(std::move(rhs
.targets_
)),
207 paths_(std::move(rhs
.paths_
)),
208 backEdges_(std::move(rhs
.backEdges_
)) {
209 MOZ_ASSERT(this != &rhs
, "self-move is not allowed");
212 ShortestPaths
& operator=(ShortestPaths
&& rhs
) {
213 this->~ShortestPaths();
214 new (this) ShortestPaths(std::move(rhs
));
218 ShortestPaths(const ShortestPaths
&) = delete;
219 ShortestPaths
& operator=(const ShortestPaths
&) = delete;
222 * Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths`
223 * shortest retaining paths for each target node in `targets` starting from
226 * The resulting `ShortestPaths` instance must not outlive the
227 * `JS::ubi::Node` graph it was constructed from.
229 * - For `JS::ubi::Node` graphs backed by the live heap graph, this means
230 * that the `ShortestPaths`'s lifetime _must_ be contained within the
231 * scope of the provided `AutoCheckCannotGC` reference because a GC will
232 * invalidate the nodes.
234 * - For `JS::ubi::Node` graphs backed by some other offline structure
235 * provided by the embedder, the resulting `ShortestPaths`'s lifetime is
236 * bounded by that offline structure's lifetime.
238 * Returns `mozilla::Nothing()` on OOM failure. It is the caller's
239 * responsibility to handle and report the OOM.
241 static mozilla::Maybe
<ShortestPaths
> Create(JSContext
* cx
,
242 AutoCheckCannotGC
& noGC
,
243 uint32_t maxNumPaths
,
246 MOZ_ASSERT(targets
.count() > 0);
247 MOZ_ASSERT(maxNumPaths
> 0);
249 ShortestPaths
paths(maxNumPaths
, root
, std::move(targets
));
251 Handler
handler(paths
);
252 Traversal
traversal(cx
, handler
, noGC
);
253 traversal
.wantNames
= true;
254 if (!traversal
.addStart(root
) || !traversal
.traverse()) {
255 return mozilla::Nothing();
258 // Take ownership of the back edges we created while traversing the
259 // graph so that we can follow them from `paths_` and don't
261 paths
.backEdges_
= std::move(traversal
.visited
);
263 return mozilla::Some(std::move(paths
));
267 * Get an iterator over each target node we searched for retaining paths
268 * for. The returned iterator must not outlive the `ShortestPaths`
271 NodeSet::Iterator
targetIter() const { return targets_
.iter(); }
274 * Invoke the provided functor/lambda/callable once for each retaining path
275 * discovered for `target`. The `func` is passed a single `JS::ubi::Path&`
276 * argument, which contains each edge along the path ordered starting from
277 * the root and ending at the target, and must not outlive the scope of the
280 * Note that it is possible that we did not find any paths from the root to
281 * the given target, in which case `func` will not be invoked.
283 template <class Func
>
284 [[nodiscard
]] bool forEachPath(const Node
& target
, Func func
) {
285 MOZ_ASSERT(targets_
.has(target
));
287 auto ptr
= paths_
.lookup(target
);
289 // We didn't find any paths to this target, so nothing to do here.
294 MOZ_ASSERT(ptr
->value().length() <= maxNumPaths_
);
297 for (const auto& backEdge
: ptr
->value()) {
300 if (!path
.append(backEdge
.get())) {
304 Node here
= backEdge
->predecessor();
307 while (here
!= root_
) {
308 auto p
= backEdges_
.lookup(here
);
310 if (!path
.append(&p
->value())) {
313 here
= p
->value().predecessor();
329 // A helper function to dump the first `maxNumPaths` shortest retaining paths to
330 // `node` from the GC roots. Useful when GC things you expect to have been
331 // reclaimed by the collector haven't been!
335 // JSObject* foo = ...;
336 // JS::ubi::dumpPaths(rt, JS::ubi::Node(foo));
337 JS_PUBLIC_API
void dumpPaths(JSRuntime
* rt
, Node node
,
338 uint32_t maxNumPaths
= 10);
344 #endif // js_UbiNodeShortestPaths_h