123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #ifndef js_UbiNodeShortestPaths_h
- #define js_UbiNodeShortestPaths_h
- #include "mozilla/Attributes.h"
- #include "mozilla/Maybe.h"
- #include "mozilla/Move.h"
- #include "jsalloc.h"
- #include "js/UbiNodeBreadthFirst.h"
- #include "js/Vector.h"
- namespace JS {
- namespace ubi {
- /**
- * A back edge along a path in the heap graph.
- */
- struct JS_PUBLIC_API(BackEdge)
- {
- private:
- Node predecessor_;
- EdgeName name_;
- public:
- using Ptr = mozilla::UniquePtr<BackEdge, JS::DeletePolicy<BackEdge>>;
- BackEdge() : predecessor_(), name_(nullptr) { }
- MOZ_MUST_USE bool init(const Node& predecessor, Edge& edge) {
- MOZ_ASSERT(!predecessor_);
- MOZ_ASSERT(!name_);
- predecessor_ = predecessor;
- name_ = mozilla::Move(edge.name);
- return true;
- }
- BackEdge(const BackEdge&) = delete;
- BackEdge& operator=(const BackEdge&) = delete;
- BackEdge(BackEdge&& rhs)
- : predecessor_(rhs.predecessor_)
- , name_(mozilla::Move(rhs.name_))
- {
- MOZ_ASSERT(&rhs != this);
- }
- BackEdge& operator=(BackEdge&& rhs) {
- this->~BackEdge();
- new(this) BackEdge(Move(rhs));
- return *this;
- }
- Ptr clone() const;
- const EdgeName& name() const { return name_; }
- EdgeName& name() { return name_; }
- const JS::ubi::Node& predecessor() const { return predecessor_; }
- };
- /**
- * A path is a series of back edges from which we discovered a target node.
- */
- using Path = JS::ubi::Vector<BackEdge*>;
- /**
- * The `JS::ubi::ShortestPaths` type represents a collection of up to N shortest
- * retaining paths for each of a target set of nodes, starting from the same
- * root node.
- */
- struct JS_PUBLIC_API(ShortestPaths)
- {
- private:
- // Types, type aliases, and data members.
- using BackEdgeVector = JS::ubi::Vector<BackEdge::Ptr>;
- using NodeToBackEdgeVectorMap = js::HashMap<Node, BackEdgeVector, js::DefaultHasher<Node>,
- js::SystemAllocPolicy>;
- struct Handler;
- using Traversal = BreadthFirst<Handler>;
- /**
- * A `JS::ubi::BreadthFirst` traversal handler that records back edges for
- * how we reached each node, allowing us to reconstruct the shortest
- * retaining paths after the traversal.
- */
- struct Handler
- {
- using NodeData = BackEdge;
- ShortestPaths& shortestPaths;
- size_t totalMaxPathsToRecord;
- size_t totalPathsRecorded;
- explicit Handler(ShortestPaths& shortestPaths)
- : shortestPaths(shortestPaths)
- , totalMaxPathsToRecord(shortestPaths.targets_.count() * shortestPaths.maxNumPaths_)
- , totalPathsRecorded(0)
- {
- }
- bool
- operator()(Traversal& traversal, JS::ubi::Node origin, JS::ubi::Edge& edge,
- BackEdge* back, bool first)
- {
- MOZ_ASSERT(back);
- MOZ_ASSERT(origin == shortestPaths.root_ || traversal.visited.has(origin));
- MOZ_ASSERT(totalPathsRecorded < totalMaxPathsToRecord);
- if (first && !back->init(origin, edge))
- return false;
- if (!shortestPaths.targets_.has(edge.referent))
- return true;
- // If `first` is true, then we moved the edge's name into `back` in
- // the above call to `init`. So clone that back edge to get the
- // correct edge name. If `first` is not true, then our edge name is
- // still in `edge`. This accounts for the asymmetry between
- // `back->clone()` in the first branch, and the `init` call in the
- // second branch.
- if (first) {
- BackEdgeVector paths;
- if (!paths.reserve(shortestPaths.maxNumPaths_))
- return false;
- auto cloned = back->clone();
- if (!cloned)
- return false;
- paths.infallibleAppend(mozilla::Move(cloned));
- if (!shortestPaths.paths_.putNew(edge.referent, mozilla::Move(paths)))
- return false;
- totalPathsRecorded++;
- } else {
- auto ptr = shortestPaths.paths_.lookup(edge.referent);
- MOZ_ASSERT(ptr,
- "This isn't the first time we have seen the target node `edge.referent`. "
- "We should have inserted it into shortestPaths.paths_ the first time we "
- "saw it.");
- if (ptr->value().length() < shortestPaths.maxNumPaths_) {
- BackEdge::Ptr thisBackEdge(js_new<BackEdge>());
- if (!thisBackEdge || !thisBackEdge->init(origin, edge))
- return false;
- ptr->value().infallibleAppend(mozilla::Move(thisBackEdge));
- totalPathsRecorded++;
- }
- }
- MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord);
- if (totalPathsRecorded == totalMaxPathsToRecord)
- traversal.stop();
- return true;
- }
- };
- // The maximum number of paths to record for each node.
- uint32_t maxNumPaths_;
- // The root node we are starting the search from.
- Node root_;
- // The set of nodes we are searching for paths to.
- NodeSet targets_;
- // The resulting paths.
- NodeToBackEdgeVectorMap paths_;
- // Need to keep alive the traversal's back edges so we can walk them later
- // when the traversal is over when recreating the shortest paths.
- Traversal::NodeMap backEdges_;
- private:
- // Private methods.
- ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets)
- : maxNumPaths_(maxNumPaths)
- , root_(root)
- , targets_(mozilla::Move(targets))
- , paths_()
- , backEdges_()
- {
- MOZ_ASSERT(maxNumPaths_ > 0);
- MOZ_ASSERT(root_);
- MOZ_ASSERT(targets_.initialized());
- }
- bool initialized() const {
- return targets_.initialized() &&
- paths_.initialized() &&
- backEdges_.initialized();
- }
- public:
- // Public methods.
- ShortestPaths(ShortestPaths&& rhs)
- : maxNumPaths_(rhs.maxNumPaths_)
- , root_(rhs.root_)
- , targets_(mozilla::Move(rhs.targets_))
- , paths_(mozilla::Move(rhs.paths_))
- , backEdges_(mozilla::Move(rhs.backEdges_))
- {
- MOZ_ASSERT(this != &rhs, "self-move is not allowed");
- }
- ShortestPaths& operator=(ShortestPaths&& rhs) {
- this->~ShortestPaths();
- new (this) ShortestPaths(mozilla::Move(rhs));
- return *this;
- }
- ShortestPaths(const ShortestPaths&) = delete;
- ShortestPaths& operator=(const ShortestPaths&) = delete;
- /**
- * Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths`
- * shortest retaining paths for each target node in `targets` starting from
- * `root`.
- *
- * The resulting `ShortestPaths` instance must not outlive the
- * `JS::ubi::Node` graph it was constructed from.
- *
- * - For `JS::ubi::Node` graphs backed by the live heap graph, this means
- * that the `ShortestPaths`'s lifetime _must_ be contained within the
- * scope of the provided `AutoCheckCannotGC` reference because a GC will
- * invalidate the nodes.
- *
- * - For `JS::ubi::Node` graphs backed by some other offline structure
- * provided by the embedder, the resulting `ShortestPaths`'s lifetime is
- * bounded by that offline structure's lifetime.
- *
- * Returns `mozilla::Nothing()` on OOM failure. It is the caller's
- * responsibility to handle and report the OOM.
- */
- static mozilla::Maybe<ShortestPaths>
- Create(JSContext* cx, AutoCheckCannotGC& noGC, uint32_t maxNumPaths, const Node& root, NodeSet&& targets) {
- MOZ_ASSERT(targets.count() > 0);
- MOZ_ASSERT(maxNumPaths > 0);
- size_t count = targets.count();
- ShortestPaths paths(maxNumPaths, root, mozilla::Move(targets));
- if (!paths.paths_.init(count))
- return mozilla::Nothing();
- Handler handler(paths);
- Traversal traversal(cx, handler, noGC);
- traversal.wantNames = true;
- if (!traversal.init() || !traversal.addStart(root) || !traversal.traverse())
- return mozilla::Nothing();
- // Take ownership of the back edges we created while traversing the
- // graph so that we can follow them from `paths_` and don't
- // use-after-free.
- paths.backEdges_ = mozilla::Move(traversal.visited);
- MOZ_ASSERT(paths.initialized());
- return mozilla::Some(mozilla::Move(paths));
- }
- /**
- * Get a range that iterates over each target node we searched for retaining
- * paths for. The returned range must not outlive the `ShortestPaths`
- * instance.
- */
- NodeSet::Range eachTarget() const {
- MOZ_ASSERT(initialized());
- return targets_.all();
- }
- /**
- * Invoke the provided functor/lambda/callable once for each retaining path
- * discovered for `target`. The `func` is passed a single `JS::ubi::Path&`
- * argument, which contains each edge along the path ordered starting from
- * the root and ending at the target, and must not outlive the scope of the
- * call.
- *
- * Note that it is possible that we did not find any paths from the root to
- * the given target, in which case `func` will not be invoked.
- */
- template <class Func>
- MOZ_MUST_USE bool forEachPath(const Node& target, Func func) {
- MOZ_ASSERT(initialized());
- MOZ_ASSERT(targets_.has(target));
- auto ptr = paths_.lookup(target);
- // We didn't find any paths to this target, so nothing to do here.
- if (!ptr)
- return true;
- MOZ_ASSERT(ptr->value().length() <= maxNumPaths_);
- Path path;
- for (const auto& backEdge : ptr->value()) {
- path.clear();
- if (!path.append(backEdge.get()))
- return false;
- Node here = backEdge->predecessor();
- MOZ_ASSERT(here);
- while (here != root_) {
- auto p = backEdges_.lookup(here);
- MOZ_ASSERT(p);
- if (!path.append(&p->value()))
- return false;
- here = p->value().predecessor();
- MOZ_ASSERT(here);
- }
- path.reverse();
- if (!func(path))
- return false;
- }
- return true;
- }
- };
- #ifdef DEBUG
- // A helper function to dump the first `maxNumPaths` shortest retaining paths to
- // `node` from the GC roots. Useful when GC things you expect to have been
- // reclaimed by the collector haven't been!
- //
- // Usage:
- //
- // JSObject* foo = ...;
- // JS::ubi::dumpPaths(rt, JS::ubi::Node(foo));
- JS_PUBLIC_API(void)
- dumpPaths(JSRuntime* rt, Node node, uint32_t maxNumPaths = 10);
- #endif
- } // namespace ubi
- } // namespace JS
- #endif // js_UbiNodeShortestPaths_h
|