2018-11-30 19:52:05 +00:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
2018-11-30 15:39:55 +00:00
|
|
|
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
2016-02-11 09:38:00 +00:00
|
|
|
* 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
|
|
|
|
|
2016-06-17 12:52:44 +00:00
|
|
|
#include "mozilla/Attributes.h"
|
2016-02-11 09:38:00 +00:00
|
|
|
#include "mozilla/Maybe.h"
|
|
|
|
#include "mozilla/Move.h"
|
|
|
|
|
2018-02-21 16:30:19 +00:00
|
|
|
#include "js/AllocPolicy.h"
|
2016-02-11 09:38:00 +00:00
|
|
|
#include "js/UbiNodeBreadthFirst.h"
|
2018-06-29 09:33:51 +00:00
|
|
|
#include "js/UniquePtr.h"
|
2016-02-11 09:38:00 +00:00
|
|
|
#include "js/Vector.h"
|
|
|
|
|
|
|
|
namespace JS {
|
|
|
|
namespace ubi {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A back edge along a path in the heap graph.
|
|
|
|
*/
|
2018-11-19 17:02:47 +00:00
|
|
|
struct JS_PUBLIC_API BackEdge {
|
2016-02-11 09:38:00 +00:00
|
|
|
private:
|
|
|
|
Node predecessor_;
|
|
|
|
EdgeName name_;
|
|
|
|
|
|
|
|
public:
|
2018-06-29 09:33:51 +00:00
|
|
|
using Ptr = js::UniquePtr<BackEdge>;
|
2016-02-11 09:38:00 +00:00
|
|
|
|
|
|
|
BackEdge() : predecessor_(), name_(nullptr) {}
|
|
|
|
|
2016-06-17 12:52:44 +00:00
|
|
|
MOZ_MUST_USE bool init(const Node& predecessor, Edge& edge) {
|
2016-02-11 09:38:00 +00:00
|
|
|
MOZ_ASSERT(!predecessor_);
|
|
|
|
MOZ_ASSERT(!name_);
|
|
|
|
|
|
|
|
predecessor_ = predecessor;
|
2018-05-30 19:15:35 +00:00
|
|
|
name_ = std::move(edge.name);
|
2016-02-11 09:38:00 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
BackEdge(const BackEdge&) = delete;
|
|
|
|
BackEdge& operator=(const BackEdge&) = delete;
|
|
|
|
|
|
|
|
BackEdge(BackEdge&& rhs)
|
|
|
|
: predecessor_(rhs.predecessor_), name_(std::move(rhs.name_)) {
|
|
|
|
MOZ_ASSERT(&rhs != this);
|
|
|
|
}
|
|
|
|
|
|
|
|
BackEdge& operator=(BackEdge&& rhs) {
|
|
|
|
this->~BackEdge();
|
2018-05-30 19:15:35 +00:00
|
|
|
new (this) BackEdge(std::move(rhs));
|
2016-02-11 09:38:00 +00:00
|
|
|
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.
|
|
|
|
*/
|
2016-06-18 09:46:13 +00:00
|
|
|
using Path = JS::ubi::Vector<BackEdge*>;
|
2016-02-11 09:38:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-11-19 17:02:47 +00:00
|
|
|
struct JS_PUBLIC_API ShortestPaths {
|
2016-02-11 09:38:00 +00:00
|
|
|
private:
|
|
|
|
// Types, type aliases, and data members.
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-06-18 09:46:13 +00:00
|
|
|
using BackEdgeVector = JS::ubi::Vector<BackEdge::Ptr>;
|
2016-02-11 09:38:00 +00:00
|
|
|
using NodeToBackEdgeVectorMap =
|
|
|
|
js::HashMap<Node, BackEdgeVector, js::DefaultHasher<Node>,
|
|
|
|
js::SystemAllocPolicy>;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
struct Handler;
|
|
|
|
using Traversal = BreadthFirst<Handler>;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
ShortestPaths& shortestPaths;
|
|
|
|
size_t totalMaxPathsToRecord;
|
|
|
|
size_t totalPathsRecorded;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
explicit Handler(ShortestPaths& shortestPaths)
|
|
|
|
: shortestPaths(shortestPaths),
|
|
|
|
totalMaxPathsToRecord(shortestPaths.targets_.count() *
|
|
|
|
shortestPaths.maxNumPaths_),
|
|
|
|
totalPathsRecorded(0) {}
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
bool operator()(Traversal& traversal, const 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);
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
if (first && !back->init(origin, edge)) {
|
|
|
|
return false;
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
if (!shortestPaths.targets_.has(edge.referent)) {
|
|
|
|
return true;
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
// 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.
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
if (first) {
|
|
|
|
BackEdgeVector paths;
|
|
|
|
if (!paths.reserve(shortestPaths.maxNumPaths_)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto cloned = back->clone();
|
2018-09-06 10:11:07 +00:00
|
|
|
if (!cloned) {
|
2016-02-22 07:55:00 +00:00
|
|
|
return false;
|
2016-02-11 09:38:00 +00:00
|
|
|
}
|
2018-05-30 19:15:35 +00:00
|
|
|
paths.infallibleAppend(std::move(cloned));
|
2018-09-06 10:11:07 +00:00
|
|
|
if (!shortestPaths.paths_.putNew(edge.referent, std::move(paths))) {
|
2016-02-11 09:38:00 +00:00
|
|
|
return false;
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
2016-02-11 09:38:00 +00:00
|
|
|
totalPathsRecorded++;
|
2018-11-30 10:46:48 +00:00
|
|
|
} else {
|
2016-02-22 07:55:00 +00:00
|
|
|
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.");
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-22 07:55:00 +00:00
|
|
|
if (ptr->value().length() < shortestPaths.maxNumPaths_) {
|
2018-06-29 09:33:51 +00:00
|
|
|
auto thisBackEdge = js::MakeUnique<BackEdge>();
|
2018-09-06 10:11:07 +00:00
|
|
|
if (!thisBackEdge || !thisBackEdge->init(origin, edge)) {
|
2016-02-11 09:38:00 +00:00
|
|
|
return false;
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
2018-05-30 19:15:35 +00:00
|
|
|
ptr->value().infallibleAppend(std::move(thisBackEdge));
|
2016-02-22 07:55:00 +00:00
|
|
|
totalPathsRecorded++;
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-11 09:38:00 +00:00
|
|
|
|
|
|
|
MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord);
|
|
|
|
if (totalPathsRecorded == totalMaxPathsToRecord) {
|
|
|
|
traversal.stop();
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
2016-02-11 09:38:00 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2018-11-30 10:46:48 +00:00
|
|
|
};
|
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
// The maximum number of paths to record for each node.
|
|
|
|
uint32_t maxNumPaths_;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
// The root node we are starting the search from.
|
|
|
|
Node root_;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
// The set of nodes we are searching for paths to.
|
|
|
|
NodeSet targets_;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
// The resulting paths.
|
|
|
|
NodeToBackEdgeVectorMap paths_;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
// 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_;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
|
|
|
private:
|
2016-02-11 09:38:00 +00:00
|
|
|
// Private methods.
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets)
|
|
|
|
: maxNumPaths_(maxNumPaths),
|
|
|
|
root_(root),
|
2018-05-30 19:15:35 +00:00
|
|
|
targets_(std::move(targets)),
|
Bug 1481998 - Make mozilla::Hash{Map,Set}'s entry storage allocation lazy. r=luke,sfink
Entry storage allocation now occurs on the first lookupForAdd()/put()/putNew().
This removes the need for init() and initialized(), and matches how
PLDHashTable/nsTHashtable work. It also removes the need for init() functions
in a lot of types that are built on top of mozilla::Hash{Map,Set}.
Pros:
- No need for init() calls and subsequent checks.
- No memory allocated for empty tables, which are not that uncommon.
Cons:
- An extra branch in lookup() and lookupForAdd(), but not in put()/putNew(),
because the existing checkOverloaded() can handle it.
Specifics:
- Construction now can take a length parameter.
- init() is removed. Explicit length-setting, when necessary, now occurs in the
constructors.
- initialized() is removed.
- capacity() now returns zero when the entry storage is absent.
- lookupForAdd() is no longer `const`, because it can instantiate the storage,
which requires modifications.
- lookupForAdd() can now return an invalid AddPtr in two cases:
- old: hashing failure (due to OOM in the hasher)
- new: OOM while instantiating entry storage
The existing failure handling paths for the old case work for the new case.
- clear(), finish(), and clearAndShrink() are replaced by clear(), compact(),
and reserve(). The old compactIfUnderloaded() is also removed.
- Capacity computation code is now in its own functions, bestCapacity() and
hashShift(). setTableSizeLog2() is removed.
- uint32_t is used throughout for capacities, instead of size_t, for
consistency with other similar values.
- changeTableSize() now takes a capacity instead of a deltaLog2, and it can now
handle !mTable.
Measurements:
- Total source code size is reduced by over 900 lines. Also, lots of existing
lines got shorter (i.e. two checks were reduced to one).
- Executable size barely changed, down by 2 KiB on Linux64. The extra branches
are compensated for by the lack of init() calls.
- Speed changed negligibly. The instruction count for Bench_Cpp_MozHash
increased from 2.84 billion to 2.89 billion but any execution time change was
well below noise.
2018-08-10 08:00:29 +00:00
|
|
|
paths_(targets_.count()),
|
2016-02-11 09:38:00 +00:00
|
|
|
backEdges_() {
|
|
|
|
MOZ_ASSERT(maxNumPaths_ > 0);
|
|
|
|
MOZ_ASSERT(root_);
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
public:
|
|
|
|
// Public methods.
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
ShortestPaths(ShortestPaths&& rhs)
|
|
|
|
: maxNumPaths_(rhs.maxNumPaths_),
|
|
|
|
root_(rhs.root_),
|
2018-05-30 19:15:35 +00:00
|
|
|
targets_(std::move(rhs.targets_)),
|
|
|
|
paths_(std::move(rhs.paths_)),
|
|
|
|
backEdges_(std::move(rhs.backEdges_)) {
|
2016-02-11 09:38:00 +00:00
|
|
|
MOZ_ASSERT(this != &rhs, "self-move is not allowed");
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
ShortestPaths& operator=(ShortestPaths&& rhs) {
|
|
|
|
this->~ShortestPaths();
|
2018-05-30 19:15:35 +00:00
|
|
|
new (this) ShortestPaths(std::move(rhs));
|
2016-02-11 09:38:00 +00:00
|
|
|
return *this;
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
ShortestPaths(const ShortestPaths&) = delete;
|
|
|
|
ShortestPaths& operator=(const ShortestPaths&) = delete;
|
2018-11-30 10:46:48 +00:00
|
|
|
|
|
|
|
/**
|
2016-02-11 09:38:00 +00:00
|
|
|
* Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths`
|
|
|
|
* shortest retaining paths for each target node in `targets` starting from
|
|
|
|
* `root`.
|
2018-11-30 10:46:48 +00:00
|
|
|
*
|
2016-02-11 09:38:00 +00:00
|
|
|
* The resulting `ShortestPaths` instance must not outlive the
|
|
|
|
* `JS::ubi::Node` graph it was constructed from.
|
2018-11-30 10:46:48 +00:00
|
|
|
*
|
2016-02-11 09:38:00 +00:00
|
|
|
* - 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.
|
2018-11-30 10:46:48 +00:00
|
|
|
*
|
2016-02-11 09:38:00 +00:00
|
|
|
* - 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.
|
2018-11-30 10:46:48 +00:00
|
|
|
*
|
2016-02-11 09:38:00 +00:00
|
|
|
* Returns `mozilla::Nothing()` on OOM failure. It is the caller's
|
|
|
|
* responsibility to handle and report the OOM.
|
2018-11-30 10:46:48 +00:00
|
|
|
*/
|
2016-02-11 09:38:00 +00:00
|
|
|
static mozilla::Maybe<ShortestPaths> Create(JSContext* cx,
|
2016-07-23 17:52:25 +00:00
|
|
|
AutoCheckCannotGC& noGC,
|
|
|
|
uint32_t maxNumPaths,
|
|
|
|
const Node& root,
|
|
|
|
NodeSet&& targets) {
|
2016-02-11 09:38:00 +00:00
|
|
|
MOZ_ASSERT(targets.count() > 0);
|
|
|
|
MOZ_ASSERT(maxNumPaths > 0);
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2018-05-30 19:15:35 +00:00
|
|
|
ShortestPaths paths(maxNumPaths, root, std::move(targets));
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
Handler handler(paths);
|
2016-07-23 17:52:25 +00:00
|
|
|
Traversal traversal(cx, handler, noGC);
|
2016-02-11 09:38:00 +00:00
|
|
|
traversal.wantNames = true;
|
2018-09-06 10:11:07 +00:00
|
|
|
if (!traversal.addStart(root) || !traversal.traverse()) {
|
2016-02-11 09:38:00 +00:00
|
|
|
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.
|
2018-05-30 19:15:35 +00:00
|
|
|
paths.backEdges_ = std::move(traversal.visited);
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2018-05-30 19:15:35 +00:00
|
|
|
return mozilla::Some(std::move(paths));
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-07-31 00:31:00 +00:00
|
|
|
* Get an iterator over each target node we searched for retaining paths
|
2018-05-30 19:15:35 +00:00
|
|
|
* for. The returned iterator must not outlive the `ShortestPaths`
|
2016-02-11 09:38:00 +00:00
|
|
|
* instance.
|
2018-11-30 10:46:48 +00:00
|
|
|
*/
|
2018-05-30 19:15:35 +00:00
|
|
|
NodeSet::Iterator targetIter() const { return targets_.iter(); }
|
2018-11-30 10:46:48 +00:00
|
|
|
|
|
|
|
/**
|
2016-02-11 09:38:00 +00:00
|
|
|
* 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
|
2018-11-30 10:46:48 +00:00
|
|
|
* call.
|
|
|
|
*
|
2016-02-11 09:38:00 +00:00
|
|
|
* 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.
|
2018-11-30 10:46:48 +00:00
|
|
|
*/
|
2016-02-11 09:38:00 +00:00
|
|
|
template <class Func>
|
2016-06-17 12:52:44 +00:00
|
|
|
MOZ_MUST_USE bool forEachPath(const Node& target, Func func) {
|
2016-02-11 09:38:00 +00:00
|
|
|
MOZ_ASSERT(targets_.has(target));
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
auto ptr = paths_.lookup(target);
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
// We didn't find any paths to this target, so nothing to do here.
|
2018-09-06 10:11:07 +00:00
|
|
|
if (!ptr) {
|
2016-02-11 09:38:00 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT(ptr->value().length() <= maxNumPaths_);
|
|
|
|
|
|
|
|
Path path;
|
2018-05-30 19:15:35 +00:00
|
|
|
for (const auto& backEdge : ptr->value()) {
|
|
|
|
path.clear();
|
2016-02-11 09:38:00 +00:00
|
|
|
|
2018-05-30 19:15:35 +00:00
|
|
|
if (!path.append(backEdge.get())) {
|
|
|
|
return false;
|
2016-02-11 09:38:00 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 00:31:00 +00:00
|
|
|
Node here = backEdge->predecessor();
|
|
|
|
MOZ_ASSERT(here);
|
2016-02-11 09:38:00 +00:00
|
|
|
|
|
|
|
while (here != root_) {
|
|
|
|
auto p = backEdges_.lookup(here);
|
|
|
|
MOZ_ASSERT(p);
|
2018-09-06 10:11:07 +00:00
|
|
|
if (!path.append(&p->value())) {
|
2016-02-11 09:38:00 +00:00
|
|
|
return false;
|
2018-09-06 10:11:07 +00:00
|
|
|
}
|
2016-02-11 09:38:00 +00:00
|
|
|
here = p->value().predecessor();
|
|
|
|
MOZ_ASSERT(here);
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
2016-02-11 09:38:00 +00:00
|
|
|
|
|
|
|
path.reverse();
|
|
|
|
|
2018-09-06 10:11:07 +00:00
|
|
|
if (!func(path)) {
|
2016-02-11 09:38:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2018-11-30 10:46:48 +00:00
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
return true;
|
2018-11-30 10:46:48 +00:00
|
|
|
}
|
2016-02-11 09:38:00 +00:00
|
|
|
};
|
|
|
|
|
2016-04-22 02:23:22 +00:00
|
|
|
#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
|
|
|
|
|
2016-02-11 09:38:00 +00:00
|
|
|
} // namespace ubi
|
|
|
|
} // namespace JS
|
|
|
|
|
|
|
|
#endif // js_UbiNodeShortestPaths_h
|