1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "mozilla/Utf8.h" // mozilla::Utf8Unit
7 #include "builtin/TestingFunctions.h"
8 #include "js/CompilationAndEvaluation.h" // JS::Compile
9 #include "js/GlobalObject.h" // JS_NewGlobalObject
10 #include "js/SourceText.h" // JS::Source{Ownership,Text}
11 #include "js/UbiNode.h"
12 #include "js/UbiNodeDominatorTree.h"
13 #include "js/UbiNodePostOrder.h"
14 #include "js/UbiNodeShortestPaths.h"
15 #include "jsapi-tests/tests.h"
16 #include "util/Text.h"
18 #include "vm/SavedFrame.h"
20 #include "vm/JSObject-inl.h"
22 using JS::RootedObject
;
23 using JS::RootedScript
;
24 using JS::RootedString
;
27 // A helper JS::ubi::Node concrete implementation that can be used to make mock
28 // graphs for testing traversals with.
31 JS::ubi::EdgeVector edges
;
33 explicit FakeNode(char name
) : name(name
), edges() {}
35 bool addEdgeTo(FakeNode
& referent
, const char16_t
* edgeName
= nullptr) {
36 JS::ubi::Node
node(&referent
);
39 auto ownedName
= js::DuplicateString(edgeName
);
40 MOZ_RELEASE_ASSERT(ownedName
);
41 return edges
.emplaceBack(ownedName
.release(), node
);
44 return edges
.emplaceBack(nullptr, node
);
52 class Concrete
<FakeNode
> : public Base
{
54 explicit Concrete(FakeNode
* ptr
) : Base(ptr
) {}
55 FakeNode
& get() const { return *static_cast<FakeNode
*>(ptr
); }
58 static void construct(void* storage
, FakeNode
* ptr
) {
59 new (storage
) Concrete(ptr
);
62 UniquePtr
<EdgeRange
> edges(JSContext
* cx
, bool wantNames
) const override
{
63 return UniquePtr
<EdgeRange
>(js_new
<PreComputedEdgeRange
>(get().edges
));
66 Node::Size
size(mozilla::MallocSizeOf
) const override
{ return 1; }
68 static const char16_t concreteTypeName
[];
69 const char16_t
* typeName() const override
{ return concreteTypeName
; }
72 const char16_t Concrete
<FakeNode
>::concreteTypeName
[] = u
"FakeNode";
77 // ubi::Node::zone works
78 BEGIN_TEST(test_ubiNodeZone
) {
79 RootedObject
global1(cx
, JS::CurrentGlobalOrNull(cx
));
81 CHECK(JS::ubi::Node(global1
).zone() == cx
->zone());
83 JS::RealmOptions globalOptions
;
85 cx
, JS_NewGlobalObject(cx
, getGlobalClass(), nullptr,
86 JS::FireOnNewGlobalHook
, globalOptions
));
88 CHECK(global1
->zone() != global2
->zone());
89 CHECK(JS::ubi::Node(global2
).zone() == global2
->zone());
90 CHECK(JS::ubi::Node(global2
).zone() != global1
->zone());
92 JS::CompileOptions
options(cx
);
94 // Create a string and a script in the original zone...
96 cx
, JS_NewStringCopyZ(cx
, "Simpson's Individual Stringettes!"));
99 JS::SourceText
<mozilla::Utf8Unit
> emptySrcBuf
;
100 CHECK(emptySrcBuf
.init(cx
, "", 0, JS::SourceOwnership::Borrowed
));
102 RootedScript
script1(cx
, JS::Compile(cx
, options
, emptySrcBuf
));
106 // ... and then enter global2's zone and create a string and script
108 JSAutoRealm
ar(cx
, global2
);
110 RootedString
string2(cx
,
111 JS_NewStringCopyZ(cx
, "A million household uses!"));
113 RootedScript
script2(cx
, JS::Compile(cx
, options
, emptySrcBuf
));
116 CHECK(JS::ubi::Node(string1
).zone() == global1
->zone());
117 CHECK(JS::ubi::Node(script1
).zone() == global1
->zone());
119 CHECK(JS::ubi::Node(string2
).zone() == global2
->zone());
120 CHECK(JS::ubi::Node(script2
).zone() == global2
->zone());
125 END_TEST(test_ubiNodeZone
)
127 // ubi::Node::compartment works
128 BEGIN_TEST(test_ubiNodeCompartment
) {
129 RootedObject
global1(cx
, JS::CurrentGlobalOrNull(cx
));
131 CHECK(JS::ubi::Node(global1
).compartment() == cx
->compartment());
132 CHECK(JS::ubi::Node(global1
).realm() == cx
->realm());
134 JS::RealmOptions globalOptions
;
135 RootedObject
global2(
136 cx
, JS_NewGlobalObject(cx
, getGlobalClass(), nullptr,
137 JS::FireOnNewGlobalHook
, globalOptions
));
139 CHECK(global1
->compartment() != global2
->compartment());
140 CHECK(JS::ubi::Node(global2
).compartment() == global2
->compartment());
141 CHECK(JS::ubi::Node(global2
).compartment() != global1
->compartment());
142 CHECK(JS::ubi::Node(global2
).realm() == global2
->nonCCWRealm());
143 CHECK(JS::ubi::Node(global2
).realm() != global1
->nonCCWRealm());
145 JS::CompileOptions
options(cx
);
147 JS::SourceText
<mozilla::Utf8Unit
> emptySrcBuf
;
148 CHECK(emptySrcBuf
.init(cx
, "", 0, JS::SourceOwnership::Borrowed
));
150 // Create a script in the original realm...
151 RootedScript
script1(cx
, JS::Compile(cx
, options
, emptySrcBuf
));
155 // ... and then enter global2's realm and create a script
157 JSAutoRealm
ar(cx
, global2
);
159 RootedScript
script2(cx
, JS::Compile(cx
, options
, emptySrcBuf
));
162 CHECK(JS::ubi::Node(script1
).compartment() == global1
->compartment());
163 CHECK(JS::ubi::Node(script2
).compartment() == global2
->compartment());
164 CHECK(JS::ubi::Node(script1
).realm() == global1
->nonCCWRealm());
165 CHECK(JS::ubi::Node(script2
).realm() == global2
->nonCCWRealm());
167 // Now create a wrapper for global1 in global2's compartment.
168 RootedObject
wrappedGlobal1(cx
, global1
);
169 CHECK(cx
->compartment()->wrap(cx
, &wrappedGlobal1
));
171 // Cross-compartment wrappers have a compartment() but not a realm().
172 CHECK(JS::ubi::Node(wrappedGlobal1
).zone() == cx
->zone());
173 CHECK(JS::ubi::Node(wrappedGlobal1
).compartment() == cx
->compartment());
174 CHECK(JS::ubi::Node(wrappedGlobal1
).realm() == nullptr);
179 END_TEST(test_ubiNodeCompartment
)
181 template <typename F
, typename G
>
182 static bool checkString(const char* expected
, F fillBufferFunction
,
183 G stringGetterFunction
) {
184 auto expectedLength
= strlen(expected
);
186 if (fillBufferFunction(mozilla::RangedPtr
<char16_t
>(buf
, 1024), 1024) !=
188 !EqualChars(buf
, expected
, expectedLength
)) {
192 auto string
= stringGetterFunction();
193 // Expecting a |JSAtom*| from a live |JS::ubi::StackFrame|.
194 if (!string
.template is
<JSAtom
*>() ||
195 !StringEqualsAscii(string
.template as
<JSAtom
*>(), expected
)) {
202 BEGIN_TEST(test_ubiStackFrame
) {
203 CHECK(js::DefineTestingFunctions(cx
, global
, false, false));
205 JS::RootedValue
val(cx
);
207 evaluate("(function one() { \n" // 1
208 " return (function two() { \n" // 2
209 " return (function three() { \n" // 3
210 " return saveStack(); \n" // 4
214 "filename.js", 1, &val
));
216 CHECK(val
.isObject());
217 JS::RootedObject
obj(cx
, &val
.toObject());
219 CHECK(obj
->is
<SavedFrame
>());
220 JS::Rooted
<SavedFrame
*> savedFrame(cx
, &obj
->as
<SavedFrame
>());
222 JS::ubi::StackFrame
ubiFrame(savedFrame
);
224 // All frames should be from the "filename.js" source.
228 [&](mozilla::RangedPtr
<char16_t
> ptr
, size_t length
) {
229 return ubiFrame
.source(ptr
, length
);
231 [&] { return ubiFrame
.source(); }));
232 ubiFrame
= ubiFrame
.parent();
235 ubiFrame
= savedFrame
;
237 auto bufferFunctionDisplayName
= [&](mozilla::RangedPtr
<char16_t
> ptr
,
239 return ubiFrame
.functionDisplayName(ptr
, length
);
241 auto getFunctionDisplayName
= [&] { return ubiFrame
.functionDisplayName(); };
244 checkString("three", bufferFunctionDisplayName
, getFunctionDisplayName
));
245 CHECK(ubiFrame
.line() == 4);
247 ubiFrame
= ubiFrame
.parent();
248 CHECK(checkString("two", bufferFunctionDisplayName
, getFunctionDisplayName
));
249 CHECK(ubiFrame
.line() == 5);
251 ubiFrame
= ubiFrame
.parent();
252 CHECK(checkString("one", bufferFunctionDisplayName
, getFunctionDisplayName
));
253 CHECK(ubiFrame
.line() == 6);
255 ubiFrame
= ubiFrame
.parent();
256 CHECK(ubiFrame
.functionDisplayName().is
<JSAtom
*>());
257 CHECK(ubiFrame
.functionDisplayName().as
<JSAtom
*>() == nullptr);
258 CHECK(ubiFrame
.line() == 7);
260 ubiFrame
= ubiFrame
.parent();
265 END_TEST(test_ubiStackFrame
)
267 BEGIN_TEST(test_ubiCoarseType
) {
268 // Test that our explicit coarseType() overrides work as expected.
270 JSObject
* obj
= nullptr;
271 CHECK(JS::ubi::Node(obj
).coarseType() == JS::ubi::CoarseType::Object
);
273 JSScript
* script
= nullptr;
274 CHECK(JS::ubi::Node(script
).coarseType() == JS::ubi::CoarseType::Script
);
276 js::BaseScript
* baseScript
= nullptr;
277 CHECK(JS::ubi::Node(baseScript
).coarseType() == JS::ubi::CoarseType::Script
);
279 js::jit::JitCode
* jitCode
= nullptr;
280 CHECK(JS::ubi::Node(jitCode
).coarseType() == JS::ubi::CoarseType::Script
);
282 JSString
* str
= nullptr;
283 CHECK(JS::ubi::Node(str
).coarseType() == JS::ubi::CoarseType::String
);
285 // Test that the default when coarseType() is not overridden is Other.
287 JS::Symbol
* sym
= nullptr;
288 CHECK(JS::ubi::Node(sym
).coarseType() == JS::ubi::CoarseType::Other
);
292 END_TEST(test_ubiCoarseType
)
294 struct ExpectedEdge
{
298 ExpectedEdge(FakeNode
& fromNode
, FakeNode
& toNode
)
299 : from(fromNode
.name
), to(toNode
.name
) {}
305 struct DefaultHasher
<ExpectedEdge
> {
306 using Lookup
= ExpectedEdge
;
308 static HashNumber
hash(const Lookup
& l
) {
309 return mozilla::AddToHash(l
.from
, l
.to
);
312 static bool match(const ExpectedEdge
& k
, const Lookup
& l
) {
313 return k
.from
== l
.from
&& k
.to
== l
.to
;
317 } // namespace mozilla
319 BEGIN_TEST(test_ubiPostOrder
) {
320 // Construct the following graph:
324 // .-------| r |---------------.
330 // .------| a |------. .----| e |----.
332 // | '--^--' | | '-----' |
334 // .--V--. | .--V--. .--V--. .--V--.
336 // | b | '------| c |-----> f |---------> g |
338 // '-----' '-----' '-----' '-----'
342 // '------> d <------'
356 js::HashSet
<ExpectedEdge
> expectedEdges(cx
);
358 auto declareEdge
= [&](FakeNode
& from
, FakeNode
& to
) {
359 return from
.addEdgeTo(to
) && expectedEdges
.putNew(ExpectedEdge(from
, to
));
362 CHECK(declareEdge(r
, a
));
363 CHECK(declareEdge(r
, e
));
364 CHECK(declareEdge(a
, b
));
365 CHECK(declareEdge(a
, c
));
366 CHECK(declareEdge(b
, d
));
367 CHECK(declareEdge(c
, a
));
368 CHECK(declareEdge(c
, d
));
369 CHECK(declareEdge(c
, f
));
370 CHECK(declareEdge(e
, f
));
371 CHECK(declareEdge(e
, g
));
372 CHECK(declareEdge(f
, g
));
374 js::Vector
<char, 8, js::SystemAllocPolicy
> visited
;
376 // Do a PostOrder traversal, starting from r. Accumulate the names of
377 // the nodes we visit in `visited`. Remove edges we traverse from
378 // `expectedEdges` as we find them to ensure that we only find each edge
381 JS::AutoCheckCannotGC
nogc(cx
);
382 JS::ubi::PostOrder
traversal(cx
, nogc
);
383 CHECK(traversal
.addStart(&r
));
385 auto onNode
= [&](const JS::ubi::Node
& node
) {
386 return visited
.append(node
.as
<FakeNode
>()->name
);
389 auto onEdge
= [&](const JS::ubi::Node
& origin
, const JS::ubi::Edge
& edge
) {
390 ExpectedEdge
e(*origin
.as
<FakeNode
>(), *edge
.referent
.as
<FakeNode
>());
391 if (!expectedEdges
.has(e
)) {
392 fprintf(stderr
, "Error: Unexpected edge from %c to %c!\n",
393 origin
.as
<FakeNode
>()->name
,
394 edge
.referent
.as
<FakeNode
>()->name
);
398 expectedEdges
.remove(e
);
402 CHECK(traversal
.traverse(onNode
, onEdge
));
405 fprintf(stderr
, "visited.length() = %lu\n", (unsigned long)visited
.length());
406 for (size_t i
= 0; i
< visited
.length(); i
++) {
407 fprintf(stderr
, "visited[%lu] = '%c'\n", (unsigned long)i
, visited
[i
]);
410 CHECK(visited
.length() == 8);
411 CHECK(visited
[0] == 'g');
412 CHECK(visited
[1] == 'f');
413 CHECK(visited
[2] == 'e');
414 CHECK(visited
[3] == 'd');
415 CHECK(visited
[4] == 'c');
416 CHECK(visited
[5] == 'b');
417 CHECK(visited
[6] == 'a');
418 CHECK(visited
[7] == 'r');
420 // We found all the edges we expected.
421 CHECK(expectedEdges
.count() == 0);
425 END_TEST(test_ubiPostOrder
)
427 BEGIN_TEST(test_JS_ubi_DominatorTree
) {
428 // Construct the following graph:
431 // | <--------------------------------.
432 // .--------+--------------| r |--------------. |
435 // | .--V--. .--V--. |
437 // | | b | | c |--------. |
439 // | '-----' '-----' | |
440 // .--V--. | | .--V--. |
442 // | a <-----+ | .----| g | |
443 // | | | .----' | | | |
444 // '-----' | | | '-----' |
446 // .--V--. | .-----. .--V--. | | |
447 // | | | | | | | | | |
448 // | d <-----+----> e <----. | f | | | |
449 // | | | | | | | | | |
450 // '-----' '-----' | '-----' | | |
451 // | .-----. | | | | .--V--. |
452 // | | | | | | .-' | | |
453 // '-----> l | | | | | | j | |
454 // | | '--. | | | | | |
455 // '-----' | | | | '-----' |
456 // | .--V--. | | .--V--. | |
458 // '-------> h |-' '---> i <------' |
459 // | | .---------> | |
460 // '-----' | '-----' |
463 // '----------> k <---------' |
467 // '----------------------------'
469 // This graph has the following dominator tree:
485 // This graph and dominator tree are taken from figures 1 and 2 of "A Fast
486 // Algorithm for Finding Dominators in a Flowgraph" by Lengauer et al:
487 // http://www.cs.princeton.edu/courses/archive/spr03/cs423/download/dominators.pdf.
503 CHECK(r
.addEdgeTo(a
));
504 CHECK(r
.addEdgeTo(b
));
505 CHECK(r
.addEdgeTo(c
));
506 CHECK(a
.addEdgeTo(d
));
507 CHECK(b
.addEdgeTo(a
));
508 CHECK(b
.addEdgeTo(d
));
509 CHECK(b
.addEdgeTo(e
));
510 CHECK(c
.addEdgeTo(f
));
511 CHECK(c
.addEdgeTo(g
));
512 CHECK(d
.addEdgeTo(l
));
513 CHECK(e
.addEdgeTo(h
));
514 CHECK(f
.addEdgeTo(i
));
515 CHECK(g
.addEdgeTo(i
));
516 CHECK(g
.addEdgeTo(j
));
517 CHECK(h
.addEdgeTo(e
));
518 CHECK(h
.addEdgeTo(k
));
519 CHECK(i
.addEdgeTo(k
));
520 CHECK(j
.addEdgeTo(i
));
521 CHECK(k
.addEdgeTo(r
));
522 CHECK(k
.addEdgeTo(i
));
523 CHECK(l
.addEdgeTo(h
));
525 mozilla::Maybe
<JS::ubi::DominatorTree
> maybeTree
;
527 JS::AutoCheckCannotGC
noGC(cx
);
528 maybeTree
= JS::ubi::DominatorTree::Create(cx
, noGC
, &r
);
531 CHECK(maybeTree
.isSome());
532 auto& tree
= *maybeTree
;
534 // We return the null JS::ubi::Node for nodes that were not reachable in the
535 // graph when computing the dominator tree.
537 CHECK(tree
.getImmediateDominator(&m
) == JS::ubi::Node());
538 CHECK(tree
.getDominatedSet(&m
).isNothing());
543 } domination
[] = {{r
, r
}, {a
, r
}, {b
, r
}, {c
, r
}, {d
, r
}, {e
, r
}, {f
, c
},
544 {g
, c
}, {h
, r
}, {i
, r
}, {j
, g
}, {k
, r
}, {l
, d
}};
546 for (auto& relation
: domination
) {
547 // Test immediate dominator.
549 stderr
, "%c's immediate dominator is %c\n", relation
.dominated
.name
,
550 tree
.getImmediateDominator(&relation
.dominator
).as
<FakeNode
>()->name
);
551 CHECK(tree
.getImmediateDominator(&relation
.dominated
) ==
552 JS::ubi::Node(&relation
.dominator
));
554 // Test the dominated set. Build up the expected dominated set based on
555 // the set of nodes immediately dominated by this one in `domination`,
556 // then iterate over the actual dominated set and check against the
559 auto& node
= relation
.dominated
;
560 fprintf(stderr
, "Checking %c's dominated set:\n", node
.name
);
562 js::HashSet
<char> expectedDominatedSet(cx
);
563 for (auto& rel
: domination
) {
564 if (&rel
.dominator
== &node
) {
565 fprintf(stderr
, " Expecting %c\n", rel
.dominated
.name
);
566 CHECK(expectedDominatedSet
.putNew(rel
.dominated
.name
));
570 auto maybeActualDominatedSet
= tree
.getDominatedSet(&node
);
571 CHECK(maybeActualDominatedSet
.isSome());
572 auto& actualDominatedSet
= *maybeActualDominatedSet
;
574 for (const auto& dominated
: actualDominatedSet
) {
575 fprintf(stderr
, " Found %c\n", dominated
.as
<FakeNode
>()->name
);
576 CHECK(expectedDominatedSet
.has(dominated
.as
<FakeNode
>()->name
));
577 expectedDominatedSet
.remove(dominated
.as
<FakeNode
>()->name
);
580 // Ensure we found them all and aren't still expecting nodes we never
582 CHECK(expectedDominatedSet
.count() == 0);
584 fprintf(stderr
, "Done checking %c's dominated set.\n\n", node
.name
);
589 JS::ubi::Node::Size retainedSize
;
591 {r
, 13}, {a
, 1}, {b
, 1}, {c
, 4}, {d
, 2}, {e
, 1}, {f
, 1},
592 {g
, 2}, {h
, 1}, {i
, 1}, {j
, 1}, {k
, 1}, {l
, 1},
595 for (auto& expected
: sizes
) {
596 JS::ubi::Node::Size actual
= 0;
597 CHECK(tree
.getRetainedSize(&expected
.node
, nullptr, actual
));
598 CHECK(actual
== expected
.retainedSize
);
603 END_TEST(test_JS_ubi_DominatorTree
)
605 BEGIN_TEST(test_JS_ubi_Node_scriptFilename
) {
606 JS::RootedValue
val(cx
);
608 evaluate("(function one() { \n" // 1
609 " return (function two() { \n" // 2
610 " return (function three() { \n" // 3
611 " return function four() {}; \n" // 4
615 "my-cool-filename.js", 1, &val
));
617 CHECK(val
.isObject());
618 JS::RootedObject
obj(cx
, &val
.toObject());
620 CHECK(obj
->is
<JSFunction
>());
621 JS::RootedFunction
func(cx
, &obj
->as
<JSFunction
>());
623 JS::RootedScript
script(cx
, JSFunction::getOrCreateScript(cx
, func
));
625 CHECK(script
->filename());
627 JS::ubi::Node
node(script
);
628 const char* filename
= node
.scriptFilename();
630 CHECK(strcmp(filename
, script
->filename()) == 0);
631 CHECK(strcmp(filename
, "my-cool-filename.js") == 0);
635 END_TEST(test_JS_ubi_Node_scriptFilename
)
637 #define LAMBDA_CHECK(cond) \
640 fprintf(stderr, "%s:%d:CHECK failed: " #cond "\n", __FILE__, __LINE__); \
645 static void dumpPath(JS::ubi::Path
& path
) {
646 for (size_t i
= 0; i
< path
.length(); i
++) {
647 fprintf(stderr
, "path[%llu]->predecessor() = '%c'\n", (long long unsigned)i
,
648 path
[i
]->predecessor().as
<FakeNode
>()->name
);
652 BEGIN_TEST(test_JS_ubi_ShortestPaths_no_path
) {
653 // Create the following graph:
656 // | a | <--> | c | | b |
661 CHECK(a
.addEdgeTo(c
));
662 CHECK(c
.addEdgeTo(a
));
664 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
666 JS::AutoCheckCannotGC
noGC(cx
);
668 JS::ubi::NodeSet targets
;
669 CHECK(targets
.put(&b
));
672 JS::ubi::ShortestPaths::Create(cx
, noGC
, 10, &a
, std::move(targets
));
675 CHECK(maybeShortestPaths
);
676 auto& paths
= *maybeShortestPaths
;
678 size_t numPathsFound
= 0;
679 bool ok
= paths
.forEachPath(&b
, [&](JS::ubi::Path
& path
) {
685 CHECK(numPathsFound
== 0);
689 END_TEST(test_JS_ubi_ShortestPaths_no_path
)
691 BEGIN_TEST(test_JS_ubi_ShortestPaths_one_path
) {
692 // Create the following graph:
695 // | a | <--> | c | --> | b |
700 CHECK(a
.addEdgeTo(c
));
701 CHECK(c
.addEdgeTo(a
));
702 CHECK(c
.addEdgeTo(b
));
704 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
706 JS::AutoCheckCannotGC
noGC(cx
);
708 JS::ubi::NodeSet targets
;
709 CHECK(targets
.put(&b
));
712 JS::ubi::ShortestPaths::Create(cx
, noGC
, 10, &a
, std::move(targets
));
715 CHECK(maybeShortestPaths
);
716 auto& paths
= *maybeShortestPaths
;
718 size_t numPathsFound
= 0;
719 bool ok
= paths
.forEachPath(&b
, [&](JS::ubi::Path
& path
) {
723 LAMBDA_CHECK(path
.length() == 2);
724 LAMBDA_CHECK(path
[0]->predecessor() == JS::ubi::Node(&a
));
725 LAMBDA_CHECK(path
[1]->predecessor() == JS::ubi::Node(&c
));
731 CHECK(numPathsFound
== 1);
735 END_TEST(test_JS_ubi_ShortestPaths_one_path
)
737 BEGIN_TEST(test_JS_ubi_ShortestPaths_multiple_paths
) {
738 // Create the following graph:
761 CHECK(a
.addEdgeTo(b
));
762 CHECK(a
.addEdgeTo(f
));
763 CHECK(a
.addEdgeTo(d
));
764 CHECK(b
.addEdgeTo(c
));
765 CHECK(c
.addEdgeTo(f
));
766 CHECK(d
.addEdgeTo(e
));
767 CHECK(e
.addEdgeTo(f
));
769 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
771 JS::AutoCheckCannotGC
noGC(cx
);
773 JS::ubi::NodeSet targets
;
774 CHECK(targets
.put(&f
));
777 JS::ubi::ShortestPaths::Create(cx
, noGC
, 10, &a
, std::move(targets
));
780 CHECK(maybeShortestPaths
);
781 auto& paths
= *maybeShortestPaths
;
783 size_t numPathsFound
= 0;
784 bool ok
= paths
.forEachPath(&f
, [&](JS::ubi::Path
& path
) {
788 switch (path
.back()->predecessor().as
<FakeNode
>()->name
) {
790 LAMBDA_CHECK(path
.length() == 1);
795 LAMBDA_CHECK(path
.length() == 3);
796 LAMBDA_CHECK(path
[0]->predecessor() == JS::ubi::Node(&a
));
797 LAMBDA_CHECK(path
[1]->predecessor() == JS::ubi::Node(&b
));
798 LAMBDA_CHECK(path
[2]->predecessor() == JS::ubi::Node(&c
));
803 LAMBDA_CHECK(path
.length() == 3);
804 LAMBDA_CHECK(path
[0]->predecessor() == JS::ubi::Node(&a
));
805 LAMBDA_CHECK(path
[1]->predecessor() == JS::ubi::Node(&d
));
806 LAMBDA_CHECK(path
[2]->predecessor() == JS::ubi::Node(&e
));
820 fprintf(stderr
, "numPathsFound = %llu\n", (long long unsigned)numPathsFound
);
821 CHECK(numPathsFound
== 3);
825 END_TEST(test_JS_ubi_ShortestPaths_multiple_paths
)
827 BEGIN_TEST(test_JS_ubi_ShortestPaths_more_paths_than_max
) {
828 // Create the following graph:
851 CHECK(a
.addEdgeTo(b
));
852 CHECK(a
.addEdgeTo(f
));
853 CHECK(a
.addEdgeTo(d
));
854 CHECK(b
.addEdgeTo(c
));
855 CHECK(c
.addEdgeTo(f
));
856 CHECK(d
.addEdgeTo(e
));
857 CHECK(e
.addEdgeTo(f
));
859 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
861 JS::AutoCheckCannotGC
noGC(cx
);
863 JS::ubi::NodeSet targets
;
864 CHECK(targets
.put(&f
));
867 JS::ubi::ShortestPaths::Create(cx
, noGC
, 1, &a
, std::move(targets
));
870 CHECK(maybeShortestPaths
);
871 auto& paths
= *maybeShortestPaths
;
873 size_t numPathsFound
= 0;
874 bool ok
= paths
.forEachPath(&f
, [&](JS::ubi::Path
& path
) {
881 fprintf(stderr
, "numPathsFound = %llu\n", (long long unsigned)numPathsFound
);
882 CHECK(numPathsFound
== 1);
886 END_TEST(test_JS_ubi_ShortestPaths_more_paths_than_max
)
888 BEGIN_TEST(test_JS_ubi_ShortestPaths_multiple_edges_to_target
) {
889 // Create the following graph:
903 CHECK(a
.addEdgeTo(b
, u
"x"));
904 CHECK(a
.addEdgeTo(b
, u
"y"));
905 CHECK(a
.addEdgeTo(b
, u
"z"));
907 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
909 JS::AutoCheckCannotGC
noGC(cx
);
911 JS::ubi::NodeSet targets
;
912 CHECK(targets
.put(&b
));
915 JS::ubi::ShortestPaths::Create(cx
, noGC
, 10, &a
, std::move(targets
));
918 CHECK(maybeShortestPaths
);
919 auto& paths
= *maybeShortestPaths
;
921 size_t numPathsFound
= 0;
926 bool ok
= paths
.forEachPath(&b
, [&](JS::ubi::Path
& path
) {
930 LAMBDA_CHECK(path
.length() == 1);
931 LAMBDA_CHECK(path
.back()->name());
932 LAMBDA_CHECK(js_strlen(path
.back()->name().get()) == 1);
934 auto c
= uint8_t(path
.back()->name().get()[0]);
935 fprintf(stderr
, "Edge name = '%c'\n", c
);
960 fprintf(stderr
, "numPathsFound = %llu\n", (long long unsigned)numPathsFound
);
961 CHECK(numPathsFound
== 3);
968 END_TEST(test_JS_ubi_ShortestPaths_multiple_edges_to_target
)