| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * 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_UbiNodePostOrder_h |
| #define js_UbiNodePostOrder_h |
| |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/Maybe.h" |
| #include "mozilla/Move.h" |
| |
| #include "jsalloc.h" |
| |
| #include "js/UbiNode.h" |
| #include "js/Utility.h" |
| #include "js/Vector.h" |
| |
| namespace JS { |
| namespace ubi { |
| |
| /** |
| * A post-order depth-first traversal of `ubi::Node` graphs. |
| * |
| * No GC may occur while an instance of `PostOrder` is live. |
| * |
| * The `NodeVisitor` type provided to `PostOrder::traverse` must have the |
| * following member: |
| * |
| * bool operator()(Node& node) |
| * |
| * The node visitor method. This method is called once for each `node` |
| * reachable from the start set in post-order. |
| * |
| * This visitor function should return true on success, or false if an error |
| * occurs. A false return value terminates the traversal immediately, and |
| * causes `PostOrder::traverse` to return false. |
| * |
| * The `EdgeVisitor` type provided to `PostOrder::traverse` must have the |
| * following member: |
| * |
| * bool operator()(Node& origin, Edge& edge) |
| * |
| * The edge visitor method. This method is called once for each outgoing |
| * `edge` from `origin` that is reachable from the start set. |
| * |
| * NB: UNLIKE NODES, THERE IS NO GUARANTEED ORDER IN WHICH EDGES AND THEIR |
| * ORIGINS ARE VISITED! |
| * |
| * This visitor function should return true on success, or false if an error |
| * occurs. A false return value terminates the traversal immediately, and |
| * causes `PostOrder::traverse` to return false. |
| */ |
| struct PostOrder { |
| private: |
| struct OriginAndEdges { |
| Node origin; |
| EdgeVector edges; |
| |
| OriginAndEdges(const Node& node, EdgeVector&& edges) |
| : origin(node) |
| , edges(mozilla::Move(edges)) |
| { } |
| |
| OriginAndEdges(const OriginAndEdges& rhs) = delete; |
| OriginAndEdges& operator=(const OriginAndEdges& rhs) = delete; |
| |
| OriginAndEdges(OriginAndEdges&& rhs) |
| : origin(rhs.origin) |
| , edges(mozilla::Move(rhs.edges)) |
| { |
| MOZ_ASSERT(&rhs != this, "self-move disallowed"); |
| } |
| |
| OriginAndEdges& operator=(OriginAndEdges&& rhs) { |
| this->~OriginAndEdges(); |
| new (this) OriginAndEdges(mozilla::Move(rhs)); |
| return *this; |
| } |
| }; |
| |
| using Stack = js::Vector<OriginAndEdges, 256, js::SystemAllocPolicy>; |
| using Set = js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>; |
| |
| JSRuntime* rt; |
| Set seen; |
| Stack stack; |
| mozilla::DebugOnly<bool> traversed; |
| |
| private: |
| bool fillEdgesFromRange(EdgeVector& edges, UniquePtr<EdgeRange>& range) { |
| MOZ_ASSERT(range); |
| for ( ; !range->empty(); range->popFront()) { |
| if (!edges.append(mozilla::Move(range->front()))) |
| return false; |
| } |
| return true; |
| } |
| |
| bool pushForTraversing(const Node& node) { |
| EdgeVector edges; |
| auto range = node.edges(rt, /* wantNames */ false); |
| return range && |
| fillEdgesFromRange(edges, range) && |
| stack.append(OriginAndEdges(node, mozilla::Move(edges))); |
| } |
| |
| |
| public: |
| // Construct a post-order traversal object. |
| // |
| // The traversal asserts that no GC happens in its runtime during its |
| // lifetime via the `AutoCheckCannotGC&` parameter. We do nothing with it, |
| // other than require it to exist with a lifetime that encloses our own. |
| PostOrder(JSRuntime* rt, AutoCheckCannotGC&) |
| : rt(rt) |
| , seen() |
| , stack() |
| , traversed(false) |
| { } |
| |
| // Initialize this traversal object. Return false on OOM. |
| bool init() { return seen.init(); } |
| |
| // Add `node` as a starting point for the traversal. You may add |
| // as many starting points as you like. Returns false on OOM. |
| bool addStart(const Node& node) { |
| if (!seen.put(node)) |
| return false; |
| return pushForTraversing(node); |
| } |
| |
| // Traverse the graph in post-order, starting with the set of nodes passed |
| // to `addStart` and applying `onNode::operator()` for each node in the |
| // graph and `onEdge::operator()` for each edge in the graph, as described |
| // above. |
| // |
| // This should be called only once per instance of this class. |
| // |
| // Return false on OOM or error return from `onNode::operator()` or |
| // `onEdge::operator()`. |
| template<typename NodeVisitor, typename EdgeVisitor> |
| bool traverse(NodeVisitor onNode, EdgeVisitor onEdge) { |
| MOZ_ASSERT(!traversed, "Can only traverse() once!"); |
| traversed = true; |
| |
| while (!stack.empty()) { |
| auto& origin = stack.back().origin; |
| auto& edges = stack.back().edges; |
| |
| if (edges.empty()) { |
| if (!onNode(origin)) |
| return false; |
| stack.popBack(); |
| continue; |
| } |
| |
| Edge edge = mozilla::Move(edges.back()); |
| edges.popBack(); |
| |
| if (!onEdge(origin, edge)) |
| return false; |
| |
| auto ptr = seen.lookupForAdd(edge.referent); |
| // We've already seen this node, don't follow its edges. |
| if (ptr) |
| continue; |
| |
| // Mark the referent as seen and follow its edges. |
| if (!seen.add(ptr, edge.referent) || |
| !pushForTraversing(edge.referent)) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| }; |
| |
| } // namespace ubi |
| } // namespace JS |
| |
| #endif // js_UbiNodePostOrder_h |