tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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