UbiNodeShortestPaths.h 11 KB

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