UbiNodeShortestPaths.h (10577B)
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/. */ 6 7 #ifndef js_UbiNodeShortestPaths_h 8 #define js_UbiNodeShortestPaths_h 9 10 #include "mozilla/CheckedInt.h" 11 #include "mozilla/Maybe.h" 12 13 #include <utility> 14 15 #include "js/AllocPolicy.h" 16 #include "js/GCAPI.h" 17 #include "js/UbiNode.h" 18 #include "js/UbiNodeBreadthFirst.h" 19 #include "js/UniquePtr.h" 20 21 namespace JS { 22 namespace ubi { 23 24 /** 25 * A back edge along a path in the heap graph. 26 */ 27 struct JS_PUBLIC_API BackEdge { 28 private: 29 Node predecessor_; 30 EdgeName name_; 31 32 public: 33 using Ptr = js::UniquePtr<BackEdge>; 34 35 BackEdge() : predecessor_(), name_(nullptr) {} 36 37 [[nodiscard]] bool init(const Node& predecessor, Edge& edge) { 38 MOZ_ASSERT(!predecessor_); 39 MOZ_ASSERT(!name_); 40 41 predecessor_ = predecessor; 42 name_ = std::move(edge.name); 43 return true; 44 } 45 46 BackEdge(const BackEdge&) = delete; 47 BackEdge& operator=(const BackEdge&) = delete; 48 49 BackEdge(BackEdge&& rhs) 50 : predecessor_(rhs.predecessor_), name_(std::move(rhs.name_)) { 51 MOZ_ASSERT(&rhs != this); 52 } 53 54 BackEdge& operator=(BackEdge&& rhs) { 55 this->~BackEdge(); 56 new (this) BackEdge(std::move(rhs)); 57 return *this; 58 } 59 60 Ptr clone() const; 61 62 const EdgeName& name() const { return name_; } 63 EdgeName& name() { return name_; } 64 65 const JS::ubi::Node& predecessor() const { return predecessor_; } 66 }; 67 68 /** 69 * A path is a series of back edges from which we discovered a target node. 70 */ 71 using Path = JS::ubi::Vector<BackEdge*>; 72 73 /** 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 76 * root node. 77 */ 78 struct JS_PUBLIC_API ShortestPaths { 79 private: 80 // Types, type aliases, and data members. 81 82 using BackEdgeVector = JS::ubi::Vector<BackEdge::Ptr>; 83 using NodeToBackEdgeVectorMap = 84 js::HashMap<Node, BackEdgeVector, js::DefaultHasher<Node>, 85 js::SystemAllocPolicy>; 86 87 struct Handler; 88 using Traversal = BreadthFirst<Handler>; 89 90 /** 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. 94 */ 95 struct Handler { 96 using NodeData = BackEdge; 97 98 ShortestPaths& shortestPaths; 99 size_t totalMaxPathsToRecord; 100 size_t totalPathsRecorded; 101 102 explicit Handler(ShortestPaths& shortestPaths) 103 : shortestPaths(shortestPaths), 104 totalMaxPathsToRecord(shortestPaths.targets_.count() * 105 shortestPaths.maxNumPaths_), 106 totalPathsRecorded(0) {} 107 108 bool operator()(Traversal& traversal, const JS::ubi::Node& origin, 109 JS::ubi::Edge& edge, BackEdge* back, bool first) { 110 MOZ_ASSERT(back); 111 MOZ_ASSERT(origin == shortestPaths.root_ || 112 traversal.visited.has(origin)); 113 MOZ_ASSERT(totalPathsRecorded < totalMaxPathsToRecord); 114 115 if (first && !back->init(origin, edge)) { 116 return false; 117 } 118 119 if (!shortestPaths.targets_.has(edge.referent)) { 120 return true; 121 } 122 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 128 // second branch. 129 130 if (first) { 131 BackEdgeVector paths; 132 if (!paths.reserve(shortestPaths.maxNumPaths_)) { 133 return false; 134 } 135 auto cloned = back->clone(); 136 if (!cloned) { 137 return false; 138 } 139 paths.infallibleAppend(std::move(cloned)); 140 if (!shortestPaths.paths_.putNew(edge.referent, std::move(paths))) { 141 return false; 142 } 143 totalPathsRecorded++; 144 } else { 145 auto ptr = shortestPaths.paths_.lookup(edge.referent); 146 MOZ_ASSERT(ptr, 147 "This isn't the first time we have seen the target node " 148 "`edge.referent`. " 149 "We should have inserted it into shortestPaths.paths_ the " 150 "first time we " 151 "saw it."); 152 153 if (ptr->value().length() < shortestPaths.maxNumPaths_) { 154 auto thisBackEdge = js::MakeUnique<BackEdge>(); 155 if (!thisBackEdge || !thisBackEdge->init(origin, edge)) { 156 return false; 157 } 158 ptr->value().infallibleAppend(std::move(thisBackEdge)); 159 totalPathsRecorded++; 160 } 161 } 162 163 MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord); 164 if (totalPathsRecorded == totalMaxPathsToRecord) { 165 traversal.stop(); 166 } 167 168 return true; 169 } 170 }; 171 172 // The maximum number of paths to record for each node. 173 uint32_t maxNumPaths_; 174 175 // The root node we are starting the search from. 176 Node root_; 177 178 // The set of nodes we are searching for paths to. 179 NodeSet targets_; 180 181 // The resulting paths. 182 NodeToBackEdgeVectorMap paths_; 183 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_; 187 188 private: 189 // Private methods. 190 191 ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets) 192 : maxNumPaths_(maxNumPaths), 193 root_(root), 194 targets_(std::move(targets)), 195 paths_(targets_.count()), 196 backEdges_() { 197 MOZ_ASSERT(maxNumPaths_ > 0); 198 MOZ_ASSERT(root_); 199 } 200 201 public: 202 // Public methods. 203 204 ShortestPaths(ShortestPaths&& rhs) 205 : maxNumPaths_(rhs.maxNumPaths_), 206 root_(rhs.root_), 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"); 211 } 212 213 ShortestPaths& operator=(ShortestPaths&& rhs) { 214 this->~ShortestPaths(); 215 new (this) ShortestPaths(std::move(rhs)); 216 return *this; 217 } 218 219 ShortestPaths(const ShortestPaths&) = delete; 220 ShortestPaths& operator=(const ShortestPaths&) = delete; 221 222 /** 223 * Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths` 224 * shortest retaining paths for each target node in `targets` starting from 225 * `root`. 226 * 227 * The resulting `ShortestPaths` instance must not outlive the 228 * `JS::ubi::Node` graph it was constructed from. 229 * 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. 234 * 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. 238 * 239 * Returns `mozilla::Nothing()` on OOM failure. It is the caller's 240 * responsibility to handle and report the OOM. 241 */ 242 static mozilla::Maybe<ShortestPaths> Create(JSContext* cx, 243 AutoCheckCannotGC& noGC, 244 uint32_t maxNumPaths, 245 const Node& root, 246 NodeSet&& targets) { 247 MOZ_ASSERT(targets.count() > 0); 248 MOZ_ASSERT(maxNumPaths > 0); 249 250 mozilla::CheckedInt<uint32_t> max = maxNumPaths; 251 max *= targets.count(); 252 if (!max.isValid()) { 253 return mozilla::Nothing(); 254 } 255 256 ShortestPaths paths(maxNumPaths, root, std::move(targets)); 257 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(); 263 } 264 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 267 // use-after-free. 268 paths.backEdges_ = std::move(traversal.visited); 269 270 return mozilla::Some(std::move(paths)); 271 } 272 273 /** 274 * Get an iterator over each target node we searched for retaining paths 275 * for. The returned iterator must not outlive the `ShortestPaths` 276 * instance. 277 */ 278 NodeSet::Iterator targetIter() const { return targets_.iter(); } 279 280 /** 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 285 * call. 286 * 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. 289 */ 290 template <class Func> 291 [[nodiscard]] bool forEachPath(const Node& target, Func func) { 292 MOZ_ASSERT(targets_.has(target)); 293 294 auto ptr = paths_.lookup(target); 295 296 // We didn't find any paths to this target, so nothing to do here. 297 if (!ptr) { 298 return true; 299 } 300 301 MOZ_ASSERT(ptr->value().length() <= maxNumPaths_); 302 303 Path path; 304 for (const auto& backEdge : ptr->value()) { 305 path.clear(); 306 307 if (!path.append(backEdge.get())) { 308 return false; 309 } 310 311 Node here = backEdge->predecessor(); 312 MOZ_ASSERT(here); 313 314 while (here != root_) { 315 auto p = backEdges_.lookup(here); 316 MOZ_ASSERT(p); 317 if (!path.append(&p->value())) { 318 return false; 319 } 320 here = p->value().predecessor(); 321 MOZ_ASSERT(here); 322 } 323 324 path.reverse(); 325 326 if (!func(path)) { 327 return false; 328 } 329 } 330 331 return true; 332 } 333 }; 334 335 #ifdef DEBUG 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! 339 // 340 // Usage: 341 // 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); 346 #endif 347 348 } // namespace ubi 349 } // namespace JS 350 351 #endif // js_UbiNodeShortestPaths_h