Bug 1733869 [wpt PR 30916] - Add a note about counterintuitive send_keys() code point...
[gecko.git] / js / public / UbiNodeShortestPaths.h
blob8a6e44af5ea9f24347816b9ff9e9dc7c4525a7f8
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"
13 #include <utility>
15 #include "js/AllocPolicy.h"
16 #include "js/UbiNodeBreadthFirst.h"
17 #include "js/UniquePtr.h"
18 #include "js/Vector.h"
20 namespace JS {
21 namespace ubi {
23 /**
24 * A back edge along a path in the heap graph.
26 struct JS_PUBLIC_API BackEdge {
27 private:
28 Node predecessor_;
29 EdgeName name_;
31 public:
32 using Ptr = js::UniquePtr<BackEdge>;
34 BackEdge() : predecessor_(), name_(nullptr) {}
36 [[nodiscard]] bool init(const Node& predecessor, Edge& edge) {
37 MOZ_ASSERT(!predecessor_);
38 MOZ_ASSERT(!name_);
40 predecessor_ = predecessor;
41 name_ = std::move(edge.name);
42 return true;
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) {
54 this->~BackEdge();
55 new (this) BackEdge(std::move(rhs));
56 return *this;
59 Ptr clone() const;
61 const EdgeName& name() const { return name_; }
62 EdgeName& name() { return name_; }
64 const JS::ubi::Node& predecessor() const { return predecessor_; }
67 /**
68 * A path is a series of back edges from which we discovered a target node.
70 using Path = JS::ubi::Vector<BackEdge*>;
72 /**
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
75 * root node.
77 struct JS_PUBLIC_API ShortestPaths {
78 private:
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>;
86 struct Handler;
87 using Traversal = BreadthFirst<Handler>;
89 /**
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.
94 struct Handler {
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) {
109 MOZ_ASSERT(back);
110 MOZ_ASSERT(origin == shortestPaths.root_ ||
111 traversal.visited.has(origin));
112 MOZ_ASSERT(totalPathsRecorded < totalMaxPathsToRecord);
114 if (first && !back->init(origin, edge)) {
115 return false;
118 if (!shortestPaths.targets_.has(edge.referent)) {
119 return true;
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
127 // second branch.
129 if (first) {
130 BackEdgeVector paths;
131 if (!paths.reserve(shortestPaths.maxNumPaths_)) {
132 return false;
134 auto cloned = back->clone();
135 if (!cloned) {
136 return false;
138 paths.infallibleAppend(std::move(cloned));
139 if (!shortestPaths.paths_.putNew(edge.referent, std::move(paths))) {
140 return false;
142 totalPathsRecorded++;
143 } else {
144 auto ptr = shortestPaths.paths_.lookup(edge.referent);
145 MOZ_ASSERT(ptr,
146 "This isn't the first time we have seen the target node "
147 "`edge.referent`. "
148 "We should have inserted it into shortestPaths.paths_ the "
149 "first time we "
150 "saw it.");
152 if (ptr->value().length() < shortestPaths.maxNumPaths_) {
153 auto thisBackEdge = js::MakeUnique<BackEdge>();
154 if (!thisBackEdge || !thisBackEdge->init(origin, edge)) {
155 return false;
157 ptr->value().infallibleAppend(std::move(thisBackEdge));
158 totalPathsRecorded++;
162 MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord);
163 if (totalPathsRecorded == totalMaxPathsToRecord) {
164 traversal.stop();
167 return true;
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.
175 Node root_;
177 // The set of nodes we are searching for paths to.
178 NodeSet targets_;
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_;
187 private:
188 // Private methods.
190 ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets)
191 : maxNumPaths_(maxNumPaths),
192 root_(root),
193 targets_(std::move(targets)),
194 paths_(targets_.count()),
195 backEdges_() {
196 MOZ_ASSERT(maxNumPaths_ > 0);
197 MOZ_ASSERT(root_);
200 public:
201 // Public methods.
203 ShortestPaths(ShortestPaths&& rhs)
204 : maxNumPaths_(rhs.maxNumPaths_),
205 root_(rhs.root_),
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));
215 return *this;
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
224 * `root`.
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,
244 const Node& root,
245 NodeSet&& targets) {
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
260 // use-after-free.
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`
269 * instance.
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
278 * call.
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.
290 if (!ptr) {
291 return true;
294 MOZ_ASSERT(ptr->value().length() <= maxNumPaths_);
296 Path path;
297 for (const auto& backEdge : ptr->value()) {
298 path.clear();
300 if (!path.append(backEdge.get())) {
301 return false;
304 Node here = backEdge->predecessor();
305 MOZ_ASSERT(here);
307 while (here != root_) {
308 auto p = backEdges_.lookup(here);
309 MOZ_ASSERT(p);
310 if (!path.append(&p->value())) {
311 return false;
313 here = p->value().predecessor();
314 MOZ_ASSERT(here);
317 path.reverse();
319 if (!func(path)) {
320 return false;
324 return true;
328 #ifdef DEBUG
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!
333 // Usage:
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);
339 #endif
341 } // namespace ubi
342 } // namespace JS
344 #endif // js_UbiNodeShortestPaths_h