tor-browser

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

UbiNodeBreadthFirst.h (10152B)


      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_UbiNodeBreadthFirst_h
      8 #define js_UbiNodeBreadthFirst_h
      9 
     10 #include "js/HashTable.h"
     11 #include "js/UbiNode.h"
     12 #include "js/Vector.h"
     13 
     14 namespace JS {
     15 namespace ubi {
     16 
     17 // A breadth-first traversal template for graphs of ubi::Nodes.
     18 //
     19 // No GC may occur while an instance of this template is live.
     20 //
     21 // The provided Handler type should have two members:
     22 //
     23 //   typename NodeData;
     24 //
     25 //      The value type of |BreadthFirst<Handler>::visited|, the HashMap of
     26 //      ubi::Nodes that have been visited so far. Since the algorithm needs a
     27 //      hash table like this for its own use anyway, it is simple to let
     28 //      Handler store its own metadata about each node in the same table.
     29 //
     30 //      For example, if you want to find a shortest path to each node from any
     31 //      traversal starting point, your |NodeData| type could record the first
     32 //      edge to reach each node, and the node from which it originates. Then,
     33 //      when the traversal is complete, you can walk backwards from any node
     34 //      to some starting point, and the path recorded will be a shortest path.
     35 //
     36 //      This type must have a default constructor. If this type owns any other
     37 //      resources, move constructors and assignment operators are probably a
     38 //      good idea, too.
     39 //
     40 //   bool operator() (BreadthFirst& traversal,
     41 //                    Node origin, const Edge& edge,
     42 //                    Handler::NodeData* referentData, bool first);
     43 //
     44 //      The visitor function, called to report that we have traversed
     45 //      |edge| from |origin|. This is called once for each edge we traverse.
     46 //      As this is a breadth-first search, any prior calls to the visitor
     47 //      function were for origin nodes not further from the start nodes than
     48 //      |origin|.
     49 //
     50 //      |traversal| is this traversal object, passed along for convenience.
     51 //
     52 //      |referentData| is a pointer to the value of the entry in
     53 //      |traversal.visited| for |edge.referent|; the visitor function can
     54 //      store whatever metadata it likes about |edge.referent| there.
     55 //
     56 //      |first| is true if this is the first time we have visited an edge
     57 //      leading to |edge.referent|. This could be stored in NodeData, but
     58 //      the algorithm knows whether it has just created the entry in
     59 //      |traversal.visited|, so it passes it along for convenience.
     60 //
     61 //      The visitor function may call |traversal.abandonReferent()| if it
     62 //      doesn't want to traverse the outgoing edges of |edge.referent|. You can
     63 //      use this to limit the traversal to a given portion of the graph: it will
     64 //      never visit nodes reachable only through nodes that you have abandoned.
     65 //      Note that |abandonReferent| must be called the first time the given node
     66 //      is reached; that is, |first| must be true.
     67 //
     68 //      The visitor function may call |doNotMarkReferentAsVisited()| if it
     69 //      does not want a node to be considered 'visited' (and added to the
     70 //      'visited' set). This is useful when the visitor has custom logic to
     71 //      determine whether an edge is 'interesting'.
     72 //
     73 //      The visitor function may call |traversal.stop()| if it doesn't want
     74 //      to visit any more nodes at all.
     75 //
     76 //      The visitor function may consult |traversal.visited| for information
     77 //      about other nodes, but it should not add or remove entries.
     78 //
     79 //      The visitor function should return true on success, or false if an
     80 //      error occurs. A false return value terminates the traversal
     81 //      immediately, and causes BreadthFirst<Handler>::traverse to return
     82 //      false.
     83 template <typename Handler>
     84 struct BreadthFirst {
     85  // Construct a breadth-first traversal object that reports the nodes it
     86  // reaches to |handler|. The traversal asserts that no GC happens in its
     87  // runtime during its lifetime.
     88  //
     89  // We do nothing with noGC, other than require it to exist, with a lifetime
     90  // that encloses our own.
     91  BreadthFirst(JSContext* cx, Handler& handler, const JS::AutoRequireNoGC& noGC)
     92      : wantNames(true),
     93        cx(cx),
     94        visited(),
     95        handler(handler),
     96        pending(),
     97        traversalBegun(false),
     98        stopRequested(false),
     99        abandonRequested(false),
    100        markReferentAsVisited(false) {}
    101 
    102  // Add |node| as a starting point for the traversal. You may add
    103  // as many starting points as you like. Return false on OOM.
    104  bool addStart(Node node) { return pending.append(node); }
    105 
    106  // Add |node| as a starting point for the traversal (see addStart) and also
    107  // add it to the |visited| set. Return false on OOM.
    108  bool addStartVisited(Node node) {
    109    typename NodeMap::AddPtr ptr = visited.lookupForAdd(node);
    110    if (!ptr && !visited.add(ptr, node, typename Handler::NodeData())) {
    111      return false;
    112    }
    113    return addStart(node);
    114  }
    115 
    116  // True if the handler wants us to compute edge names; doing so can be
    117  // expensive in time and memory. True by default.
    118  bool wantNames;
    119 
    120  // Traverse the graph in breadth-first order, starting at the given
    121  // start nodes, applying |handler::operator()| for each edge traversed
    122  // as described above.
    123  //
    124  // This should be called only once per instance of this class.
    125  //
    126  // Return false on OOM or error return from |handler::operator()|.
    127  bool traverse() {
    128    MOZ_ASSERT(!traversalBegun);
    129    traversalBegun = true;
    130 
    131    // While there are pending nodes, visit them.
    132    while (!pending.empty()) {
    133      Node origin = pending.front();
    134      pending.popFront();
    135 
    136      // Get a range containing all origin's outgoing edges.
    137      auto range = origin.edges(cx, wantNames);
    138      if (!range) {
    139        return false;
    140      }
    141 
    142      // Traverse each edge.
    143      for (; !range->empty(); range->popFront()) {
    144        MOZ_ASSERT(!stopRequested);
    145 
    146        Edge& edge = range->front();
    147        typename NodeMap::AddPtr a = visited.lookupForAdd(edge.referent);
    148        bool first = !a;
    149 
    150        // Pass a pointer to a stack-allocated NodeData if the referent is not
    151        // in |visited|.
    152        typename Handler::NodeData nodeData;
    153        typename Handler::NodeData* nodeDataPtr =
    154            first ? &nodeData : &a->value();
    155 
    156        // Report this edge to the visitor function.
    157        markReferentAsVisited = true;
    158        if (!handler(*this, origin, edge, nodeDataPtr, first)) {
    159          return false;
    160        }
    161 
    162        if (first && markReferentAsVisited) {
    163          // This is the first time we've reached |edge.referent| and the
    164          // handler wants it marked as visited.
    165          if (!visited.add(a, edge.referent, std::move(nodeData))) {
    166            return false;
    167          }
    168        }
    169 
    170        if (stopRequested) {
    171          return true;
    172        }
    173 
    174        // Arrange to traverse this edge's referent's outgoing edges
    175        // later --- unless |handler| asked us not to.
    176        if (abandonRequested) {
    177          // Skip the enqueue; reset flag for future iterations.
    178          abandonRequested = false;
    179        } else if (first) {
    180          if (!pending.append(edge.referent)) {
    181            return false;
    182          }
    183        }
    184      }
    185    }
    186 
    187    return true;
    188  }
    189 
    190  // Stop traversal, and return true from |traverse| without visiting any
    191  // more nodes. Only |handler::operator()| should call this function; it
    192  // may do so to stop the traversal early, without returning false and
    193  // then making |traverse|'s caller disambiguate that result from a real
    194  // error.
    195  void stop() { stopRequested = true; }
    196 
    197  // Request that the current edge's referent's outgoing edges not be
    198  // traversed. This must be called the first time that referent is reached.
    199  // Other edges *to* that referent will still be traversed.
    200  void abandonReferent() { abandonRequested = true; }
    201 
    202  // Request the the current edge's referent not be added to the |visited| set
    203  // if this is the first time we're visiting it.
    204  void doNotMarkReferentAsVisited() { markReferentAsVisited = false; }
    205 
    206  // The context with which we were constructed.
    207  JSContext* cx;
    208 
    209  // A map associating each node N that we have reached with a
    210  // Handler::NodeData, for |handler|'s use. This is public, so that
    211  // |handler| can access it to see the traversal thus far.
    212  using NodeMap = js::HashMap<Node, typename Handler::NodeData,
    213                              js::DefaultHasher<Node>, js::SystemAllocPolicy>;
    214  NodeMap visited;
    215 
    216 private:
    217  // Our handler object.
    218  Handler& handler;
    219 
    220  // A queue template. Appending and popping the front are constant time.
    221  // Wasted space is never more than some recent actual population plus the
    222  // current population.
    223  template <typename T>
    224  class Queue {
    225    js::Vector<T, 0, js::SystemAllocPolicy> head, tail;
    226    size_t frontIndex;
    227 
    228   public:
    229    Queue() : head(), tail(), frontIndex(0) {}
    230    bool empty() { return frontIndex >= head.length(); }
    231    T& front() {
    232      MOZ_RELEASE_ASSERT(!empty());
    233      return head[frontIndex];
    234    }
    235    void popFront() {
    236      MOZ_RELEASE_ASSERT(!empty());
    237      frontIndex++;
    238      if (frontIndex >= head.length()) {
    239        head.clearAndFree();
    240        head.swap(tail);
    241        frontIndex = 0;
    242      }
    243    }
    244    bool append(const T& elt) {
    245      return frontIndex == 0 ? head.append(elt) : tail.append(elt);
    246    }
    247  };
    248 
    249  // A queue of nodes that we have reached, but whose outgoing edges we
    250  // have not yet traversed. Nodes reachable in fewer edges are enqueued
    251  // earlier.
    252  Queue<Node> pending;
    253 
    254  // True if our traverse function has been called.
    255  bool traversalBegun;
    256 
    257  // True if we've been asked to stop the traversal.
    258  bool stopRequested;
    259 
    260  // True if we've been asked to abandon the current edge's referent.
    261  bool abandonRequested;
    262 
    263  // True if the node should be added to the |visited| set after calling the
    264  // handler.
    265  bool markReferentAsVisited;
    266 };
    267 
    268 }  // namespace ubi
    269 }  // namespace JS
    270 
    271 #endif  // js_UbiNodeBreadthFirst_h