UbiNodePostOrder.h (5444B)
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_UbiNodePostOrder_h 8 #define js_UbiNodePostOrder_h 9 10 #include <utility> 11 12 #include "js/UbiNode.h" 13 #include "js/Vector.h" 14 15 namespace js { 16 class SystemAllocPolicy; 17 } 18 19 namespace JS { 20 class AutoCheckCannotGC; 21 22 namespace ubi { 23 24 /** 25 * A post-order depth-first traversal of `ubi::Node` graphs. 26 * 27 * No GC may occur while an instance of `PostOrder` is live. 28 * 29 * The `NodeVisitor` type provided to `PostOrder::traverse` must have the 30 * following member: 31 * 32 * bool operator()(Node& node) 33 * 34 * The node visitor method. This method is called once for each `node` 35 * reachable from the start set in post-order. 36 * 37 * This visitor function should return true on success, or false if an error 38 * occurs. A false return value terminates the traversal immediately, and 39 * causes `PostOrder::traverse` to return false. 40 * 41 * The `EdgeVisitor` type provided to `PostOrder::traverse` must have the 42 * following member: 43 * 44 * bool operator()(Node& origin, Edge& edge) 45 * 46 * The edge visitor method. This method is called once for each outgoing 47 * `edge` from `origin` that is reachable from the start set. 48 * 49 * NB: UNLIKE NODES, THERE IS NO GUARANTEED ORDER IN WHICH EDGES AND THEIR 50 * ORIGINS ARE VISITED! 51 * 52 * This visitor function should return true on success, or false if an error 53 * occurs. A false return value terminates the traversal immediately, and 54 * causes `PostOrder::traverse` to return false. 55 */ 56 struct PostOrder { 57 private: 58 struct OriginAndEdges { 59 Node origin; 60 EdgeVector edges; 61 62 OriginAndEdges(const Node& node, EdgeVector&& edges) 63 : origin(node), edges(std::move(edges)) {} 64 65 OriginAndEdges(const OriginAndEdges& rhs) = delete; 66 OriginAndEdges& operator=(const OriginAndEdges& rhs) = delete; 67 68 OriginAndEdges(OriginAndEdges&& rhs) 69 : origin(rhs.origin), edges(std::move(rhs.edges)) { 70 MOZ_ASSERT(&rhs != this, "self-move disallowed"); 71 } 72 73 OriginAndEdges& operator=(OriginAndEdges&& rhs) { 74 this->~OriginAndEdges(); 75 new (this) OriginAndEdges(std::move(rhs)); 76 return *this; 77 } 78 }; 79 80 using Stack = js::Vector<OriginAndEdges, 256, js::SystemAllocPolicy>; 81 using Set = js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>; 82 83 JSContext* cx; 84 Set seen; 85 Stack stack; 86 #ifdef DEBUG 87 bool traversed; 88 #endif 89 90 private: 91 [[nodiscard]] bool fillEdgesFromRange(EdgeVector& edges, 92 js::UniquePtr<EdgeRange>& range) { 93 MOZ_ASSERT(range); 94 for (; !range->empty(); range->popFront()) { 95 if (!edges.append(std::move(range->front()))) { 96 return false; 97 } 98 } 99 return true; 100 } 101 102 [[nodiscard]] bool pushForTraversing(const Node& node) { 103 EdgeVector edges; 104 auto range = node.edges(cx, /* wantNames */ false); 105 return range && fillEdgesFromRange(edges, range) && 106 stack.append(OriginAndEdges(node, std::move(edges))); 107 } 108 109 public: 110 // Construct a post-order traversal object. 111 // 112 // The traversal asserts that no GC happens in its runtime during its 113 // lifetime via the `AutoCheckCannotGC&` parameter. We do nothing with it, 114 // other than require it to exist with a lifetime that encloses our own. 115 PostOrder(JSContext* cx, AutoCheckCannotGC&) 116 : cx(cx), 117 seen(), 118 stack() 119 #ifdef DEBUG 120 , 121 traversed(false) 122 #endif 123 { 124 } 125 126 // Add `node` as a starting point for the traversal. You may add 127 // as many starting points as you like. Returns false on OOM. 128 [[nodiscard]] bool addStart(const Node& node) { 129 if (!seen.put(node)) { 130 return false; 131 } 132 return pushForTraversing(node); 133 } 134 135 // Traverse the graph in post-order, starting with the set of nodes passed 136 // to `addStart` and applying `onNode::operator()` for each node in the 137 // graph and `onEdge::operator()` for each edge in the graph, as described 138 // above. 139 // 140 // This should be called only once per instance of this class. 141 // 142 // Return false on OOM or error return from `onNode::operator()` or 143 // `onEdge::operator()`. 144 template <typename NodeVisitor, typename EdgeVisitor> 145 [[nodiscard]] bool traverse(NodeVisitor onNode, EdgeVisitor onEdge) { 146 #ifdef DEBUG 147 MOZ_ASSERT(!traversed, "Can only traverse() once!"); 148 traversed = true; 149 #endif 150 151 while (!stack.empty()) { 152 auto& origin = stack.back().origin; 153 auto& edges = stack.back().edges; 154 155 if (edges.empty()) { 156 if (!onNode(origin)) { 157 return false; 158 } 159 stack.popBack(); 160 continue; 161 } 162 163 Edge edge = std::move(edges.back()); 164 edges.popBack(); 165 166 if (!onEdge(origin, edge)) { 167 return false; 168 } 169 170 auto ptr = seen.lookupForAdd(edge.referent); 171 // We've already seen this node, don't follow its edges. 172 if (ptr) { 173 continue; 174 } 175 176 // Mark the referent as seen and follow its edges. 177 if (!seen.add(ptr, edge.referent) || !pushForTraversing(edge.referent)) { 178 return false; 179 } 180 } 181 182 return true; 183 } 184 }; 185 186 } // namespace ubi 187 } // namespace JS 188 189 #endif // js_UbiNodePostOrder_h