Coverage Report

Created: 2018-09-25 14:53

/work/obj-fuzz/dist/include/js/UbiNodeShortestPaths.h
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
 * vim: set ts=8 sts=4 et sw=4 tw=99:
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/Attributes.h"
11
#include "mozilla/Maybe.h"
12
#include "mozilla/Move.h"
13
14
#include "js/AllocPolicy.h"
15
#include "js/UbiNodeBreadthFirst.h"
16
#include "js/UniquePtr.h"
17
#include "js/Vector.h"
18
19
namespace JS {
20
namespace ubi {
21
22
/**
23
 * A back edge along a path in the heap graph.
24
 */
25
struct JS_PUBLIC_API(BackEdge)
26
{
27
  private:
28
    Node predecessor_;
29
    EdgeName name_;
30
31
  public:
32
    using Ptr = js::UniquePtr<BackEdge>;
33
34
0
    BackEdge() : predecessor_(), name_(nullptr) { }
35
36
0
    MOZ_MUST_USE bool init(const Node& predecessor, Edge& edge) {
37
0
        MOZ_ASSERT(!predecessor_);
38
0
        MOZ_ASSERT(!name_);
39
0
40
0
        predecessor_ = predecessor;
41
0
        name_ = std::move(edge.name);
42
0
        return true;
43
0
    }
44
45
    BackEdge(const BackEdge&) = delete;
46
    BackEdge& operator=(const BackEdge&) = delete;
47
48
    BackEdge(BackEdge&& rhs)
49
      : predecessor_(rhs.predecessor_)
50
      , name_(std::move(rhs.name_))
51
0
    {
52
0
        MOZ_ASSERT(&rhs != this);
53
0
    }
54
55
0
    BackEdge& operator=(BackEdge&& rhs) {
56
0
        this->~BackEdge();
57
0
        new(this) BackEdge(std::move(rhs));
58
0
        return *this;
59
0
    }
60
61
    Ptr clone() const;
62
63
0
    const EdgeName& name() const { return name_; }
64
0
    EdgeName& name() { return name_; }
65
66
0
    const JS::ubi::Node& predecessor() const { return predecessor_; }
67
};
68
69
/**
70
 * A path is a series of back edges from which we discovered a target node.
71
 */
72
using Path = JS::ubi::Vector<BackEdge*>;
73
74
/**
75
 * The `JS::ubi::ShortestPaths` type represents a collection of up to N shortest
76
 * retaining paths for each of a target set of nodes, starting from the same
77
 * root node.
78
 */
79
struct JS_PUBLIC_API(ShortestPaths)
80
{
81
  private:
82
    // Types, type aliases, and data members.
83
84
    using BackEdgeVector = JS::ubi::Vector<BackEdge::Ptr>;
85
    using NodeToBackEdgeVectorMap = js::HashMap<Node, BackEdgeVector, js::DefaultHasher<Node>,
86
                                                js::SystemAllocPolicy>;
87
88
    struct Handler;
89
    using Traversal = BreadthFirst<Handler>;
90
91
    /**
92
     * A `JS::ubi::BreadthFirst` traversal handler that records back edges for
93
     * how we reached each node, allowing us to reconstruct the shortest
94
     * retaining paths after the traversal.
95
     */
96
    struct Handler
97
    {
98
        using NodeData = BackEdge;
99
100
        ShortestPaths& shortestPaths;
101
        size_t totalMaxPathsToRecord;
102
        size_t totalPathsRecorded;
103
104
        explicit Handler(ShortestPaths& shortestPaths)
105
          : shortestPaths(shortestPaths)
106
          , totalMaxPathsToRecord(shortestPaths.targets_.count() * shortestPaths.maxNumPaths_)
107
          , totalPathsRecorded(0)
108
0
        {
109
0
        }
110
111
        bool
112
        operator()(Traversal& traversal, const JS::ubi::Node& origin, JS::ubi::Edge& edge,
113
                   BackEdge* back, bool first)
114
0
        {
115
0
            MOZ_ASSERT(back);
116
0
            MOZ_ASSERT(origin == shortestPaths.root_ || traversal.visited.has(origin));
117
0
            MOZ_ASSERT(totalPathsRecorded < totalMaxPathsToRecord);
118
0
119
0
            if (first && !back->init(origin, edge)) {
120
0
                return false;
121
0
            }
122
0
123
0
            if (!shortestPaths.targets_.has(edge.referent)) {
124
0
                return true;
125
0
            }
126
0
127
0
            // If `first` is true, then we moved the edge's name into `back` in
128
0
            // the above call to `init`. So clone that back edge to get the
129
0
            // correct edge name. If `first` is not true, then our edge name is
130
0
            // still in `edge`. This accounts for the asymmetry between
131
0
            // `back->clone()` in the first branch, and the `init` call in the
132
0
            // second branch.
133
0
134
0
            if (first) {
135
0
                BackEdgeVector paths;
136
0
                if (!paths.reserve(shortestPaths.maxNumPaths_)) {
137
0
                    return false;
138
0
                }
139
0
                auto cloned = back->clone();
140
0
                if (!cloned) {
141
0
                    return false;
142
0
                }
143
0
                paths.infallibleAppend(std::move(cloned));
144
0
                if (!shortestPaths.paths_.putNew(edge.referent, std::move(paths))) {
145
0
                    return false;
146
0
                }
147
0
                totalPathsRecorded++;
148
0
            } else {
149
0
                auto ptr = shortestPaths.paths_.lookup(edge.referent);
150
0
                MOZ_ASSERT(ptr,
151
0
                           "This isn't the first time we have seen the target node `edge.referent`. "
152
0
                           "We should have inserted it into shortestPaths.paths_ the first time we "
153
0
                           "saw it.");
154
0
155
0
                if (ptr->value().length() < shortestPaths.maxNumPaths_) {
156
0
                    auto thisBackEdge = js::MakeUnique<BackEdge>();
157
0
                    if (!thisBackEdge || !thisBackEdge->init(origin, edge)) {
158
0
                        return false;
159
0
                    }
160
0
                    ptr->value().infallibleAppend(std::move(thisBackEdge));
161
0
                    totalPathsRecorded++;
162
0
                }
163
0
            }
164
0
165
0
            MOZ_ASSERT(totalPathsRecorded <= totalMaxPathsToRecord);
166
0
            if (totalPathsRecorded == totalMaxPathsToRecord) {
167
0
                traversal.stop();
168
0
            }
169
0
170
0
            return true;
171
0
        }
172
173
    };
174
175
    // The maximum number of paths to record for each node.
176
    uint32_t maxNumPaths_;
177
178
    // The root node we are starting the search from.
179
    Node root_;
180
181
    // The set of nodes we are searching for paths to.
182
    NodeSet targets_;
183
184
    // The resulting paths.
185
    NodeToBackEdgeVectorMap paths_;
186
187
    // Need to keep alive the traversal's back edges so we can walk them later
188
    // when the traversal is over when recreating the shortest paths.
189
    Traversal::NodeMap backEdges_;
190
191
  private:
192
    // Private methods.
193
194
    ShortestPaths(uint32_t maxNumPaths, const Node& root, NodeSet&& targets)
195
      : maxNumPaths_(maxNumPaths)
196
      , root_(root)
197
      , targets_(std::move(targets))
198
      , paths_(targets_.count())
199
      , backEdges_()
200
0
    {
201
0
        MOZ_ASSERT(maxNumPaths_ > 0);
202
0
        MOZ_ASSERT(root_);
203
0
    }
204
205
  public:
206
    // Public methods.
207
208
    ShortestPaths(ShortestPaths&& rhs)
209
      : maxNumPaths_(rhs.maxNumPaths_)
210
      , root_(rhs.root_)
211
      , targets_(std::move(rhs.targets_))
212
      , paths_(std::move(rhs.paths_))
213
      , backEdges_(std::move(rhs.backEdges_))
214
0
    {
215
0
        MOZ_ASSERT(this != &rhs, "self-move is not allowed");
216
0
    }
217
218
0
    ShortestPaths& operator=(ShortestPaths&& rhs) {
219
0
        this->~ShortestPaths();
220
0
        new (this) ShortestPaths(std::move(rhs));
221
0
        return *this;
222
0
    }
223
224
    ShortestPaths(const ShortestPaths&) = delete;
225
    ShortestPaths& operator=(const ShortestPaths&) = delete;
226
227
    /**
228
     * Construct a new `JS::ubi::ShortestPaths`, finding up to `maxNumPaths`
229
     * shortest retaining paths for each target node in `targets` starting from
230
     * `root`.
231
     *
232
     * The resulting `ShortestPaths` instance must not outlive the
233
     * `JS::ubi::Node` graph it was constructed from.
234
     *
235
     *   - For `JS::ubi::Node` graphs backed by the live heap graph, this means
236
     *     that the `ShortestPaths`'s lifetime _must_ be contained within the
237
     *     scope of the provided `AutoCheckCannotGC` reference because a GC will
238
     *     invalidate the nodes.
239
     *
240
     *   - For `JS::ubi::Node` graphs backed by some other offline structure
241
     *     provided by the embedder, the resulting `ShortestPaths`'s lifetime is
242
     *     bounded by that offline structure's lifetime.
243
     *
244
     * Returns `mozilla::Nothing()` on OOM failure. It is the caller's
245
     * responsibility to handle and report the OOM.
246
     */
247
    static mozilla::Maybe<ShortestPaths>
248
0
    Create(JSContext* cx, AutoCheckCannotGC& noGC, uint32_t maxNumPaths, const Node& root, NodeSet&& targets) {
249
0
        MOZ_ASSERT(targets.count() > 0);
250
0
        MOZ_ASSERT(maxNumPaths > 0);
251
0
252
0
        ShortestPaths paths(maxNumPaths, root, std::move(targets));
253
0
254
0
        Handler handler(paths);
255
0
        Traversal traversal(cx, handler, noGC);
256
0
        traversal.wantNames = true;
257
0
        if (!traversal.addStart(root) || !traversal.traverse()) {
258
0
            return mozilla::Nothing();
259
0
        }
260
0
261
0
        // Take ownership of the back edges we created while traversing the
262
0
        // graph so that we can follow them from `paths_` and don't
263
0
        // use-after-free.
264
0
        paths.backEdges_ = std::move(traversal.visited);
265
0
266
0
        return mozilla::Some(std::move(paths));
267
0
    }
268
269
    /**
270
     * Get an iterator over each target node we searched for retaining paths
271
     * for. The returned iterator must not outlive the `ShortestPaths`
272
     * instance.
273
     */
274
0
    NodeSet::Iterator targetIter() const {
275
0
        return targets_.iter();
276
0
    }
277
278
    /**
279
     * Invoke the provided functor/lambda/callable once for each retaining path
280
     * discovered for `target`. The `func` is passed a single `JS::ubi::Path&`
281
     * argument, which contains each edge along the path ordered starting from
282
     * the root and ending at the target, and must not outlive the scope of the
283
     * call.
284
     *
285
     * Note that it is possible that we did not find any paths from the root to
286
     * the given target, in which case `func` will not be invoked.
287
     */
288
    template <class Func>
289
0
    MOZ_MUST_USE bool forEachPath(const Node& target, Func func) {
290
0
        MOZ_ASSERT(targets_.has(target));
291
0
292
0
        auto ptr = paths_.lookup(target);
293
0
294
0
        // We didn't find any paths to this target, so nothing to do here.
295
0
        if (!ptr) {
296
0
            return true;
297
0
        }
298
0
299
0
        MOZ_ASSERT(ptr->value().length() <= maxNumPaths_);
300
0
301
0
        Path path;
302
0
        for (const auto& backEdge : ptr->value()) {
303
0
            path.clear();
304
0
305
0
            if (!path.append(backEdge.get())) {
306
0
                return false;
307
0
            }
308
0
309
0
            Node here = backEdge->predecessor();
310
0
            MOZ_ASSERT(here);
311
0
312
0
            while (here != root_) {
313
0
                auto p = backEdges_.lookup(here);
314
0
                MOZ_ASSERT(p);
315
0
                if (!path.append(&p->value())) {
316
0
                    return false;
317
0
                }
318
0
                here = p->value().predecessor();
319
0
                MOZ_ASSERT(here);
320
0
            }
321
0
322
0
            path.reverse();
323
0
324
0
            if (!func(path)) {
325
0
                return false;
326
0
            }
327
0
        }
328
0
329
0
        return true;
330
0
    }
Unexecuted instantiation: HeapSnapshot.cpp:bool JS::ubi::ShortestPaths::forEachPath<mozilla::devtools::HeapSnapshot::ComputeShortestPaths(JSContext*, unsigned long, mozilla::dom::Sequence<unsigned long> const&, unsigned long, JS::MutableHandle<JSObject*>, mozilla::ErrorResult&)::$_0>(JS::ubi::Node const&, mozilla::devtools::HeapSnapshot::ComputeShortestPaths(JSContext*, unsigned long, mozilla::dom::Sequence<unsigned long> const&, unsigned long, JS::MutableHandle<JSObject*>, mozilla::ErrorResult&)::$_0)
Unexecuted instantiation: Unified_cpp_js_src2.cpp:bool JS::ubi::ShortestPaths::forEachPath<ShortestPaths(JSContext*, unsigned int, JS::Value*)::$_3>(JS::ubi::Node const&, ShortestPaths(JSContext*, unsigned int, JS::Value*)::$_3)
331
};
332
333
#ifdef DEBUG
334
// A helper function to dump the first `maxNumPaths` shortest retaining paths to
335
// `node` from the GC roots. Useful when GC things you expect to have been
336
// reclaimed by the collector haven't been!
337
//
338
// Usage:
339
//
340
//     JSObject* foo = ...;
341
//     JS::ubi::dumpPaths(rt, JS::ubi::Node(foo));
342
JS_PUBLIC_API(void)
343
dumpPaths(JSRuntime* rt, Node node, uint32_t maxNumPaths = 10);
344
#endif
345
346
} // namespace ubi
347
} // namespace JS
348
349
#endif // js_UbiNodeShortestPaths_h