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"
17 #include "vm/Compartment.h"
19 #include "vm/SavedFrame.h"
21 #include "vm/JSObject-inl.h"
23 using JS::RootedObject
;
24 using JS::RootedScript
;
25 using JS::RootedString
;
28 // A helper JS::ubi::Node concrete implementation that can be used to make mock
29 // graphs for testing traversals with.
32 JS::ubi::EdgeVector edges
;
34 explicit FakeNode(char name
) : name(name
), edges() {}
36 bool addEdgeTo(FakeNode
& referent
, const char16_t
* edgeName
= nullptr) {
37 JS::ubi::Node
node(&referent
);
40 auto ownedName
= js::DuplicateString(edgeName
);
41 MOZ_RELEASE_ASSERT(ownedName
);
42 return edges
.emplaceBack(ownedName
.release(), node
);
45 return edges
.emplaceBack(nullptr, node
);
53 class Concrete
<FakeNode
> : public Base
{
55 explicit Concrete(FakeNode
* ptr
) : Base(ptr
) {}
56 FakeNode
& get() const { return *static_cast<FakeNode
*>(ptr
); }
59 static void construct(void* storage
, FakeNode
* ptr
) {
60 new (storage
) Concrete(ptr
);
63 UniquePtr
<EdgeRange
> edges(JSContext
* cx
, bool wantNames
) const override
{
64 return UniquePtr
<EdgeRange
>(js_new
<PreComputedEdgeRange
>(get().edges
));
67 Node::Size
size(mozilla::MallocSizeOf
) const override
{ return 1; }
69 static const char16_t concreteTypeName
[];
70 const char16_t
* typeName() const override
{ return concreteTypeName
; }
73 const char16_t Concrete
<FakeNode
>::concreteTypeName
[] = u
"FakeNode";
78 // ubi::Node::zone works
79 BEGIN_TEST(test_ubiNodeZone
) {
80 RootedObject
global1(cx
, JS::CurrentGlobalOrNull(cx
));
82 CHECK(JS::ubi::Node(global1
).zone() == cx
->zone());
84 JS::RealmOptions globalOptions
;
86 cx
, JS_NewGlobalObject(cx
, getGlobalClass(), nullptr,
87 JS::FireOnNewGlobalHook
, globalOptions
));
89 CHECK(global1
->zone() != global2
->zone());
90 CHECK(JS::ubi::Node(global2
).zone() == global2
->zone());
91 CHECK(JS::ubi::Node(global2
).zone() != global1
->zone());
93 JS::CompileOptions
options(cx
);
95 // Create a string and a script in the original zone...
97 cx
, JS_NewStringCopyZ(cx
, "Simpson's Individual Stringettes!"));
100 JS::SourceText
<mozilla::Utf8Unit
> emptySrcBuf
;
101 CHECK(emptySrcBuf
.init(cx
, "", 0, JS::SourceOwnership::Borrowed
));
103 RootedScript
script1(cx
, JS::Compile(cx
, options
, emptySrcBuf
));
107 // ... and then enter global2's zone and create a string and script
109 JSAutoRealm
ar(cx
, global2
);
111 RootedString
string2(cx
,
112 JS_NewStringCopyZ(cx
, "A million household uses!"));
114 RootedScript
script2(cx
, JS::Compile(cx
, options
, emptySrcBuf
));
117 CHECK(JS::ubi::Node(string1
).zone() == global1
->zone());
118 CHECK(JS::ubi::Node(script1
).zone() == global1
->zone());
120 CHECK(JS::ubi::Node(string2
).zone() == global2
->zone());
121 CHECK(JS::ubi::Node(script2
).zone() == global2
->zone());
126 END_TEST(test_ubiNodeZone
)
128 // ubi::Node::compartment works
129 BEGIN_TEST(test_ubiNodeCompartment
) {
130 RootedObject
global1(cx
, JS::CurrentGlobalOrNull(cx
));
132 CHECK(JS::ubi::Node(global1
).compartment() == cx
->compartment());
133 CHECK(JS::ubi::Node(global1
).realm() == cx
->realm());
135 JS::RealmOptions globalOptions
;
136 RootedObject
global2(
137 cx
, JS_NewGlobalObject(cx
, getGlobalClass(), nullptr,
138 JS::FireOnNewGlobalHook
, globalOptions
));
140 CHECK(global1
->compartment() != global2
->compartment());
141 CHECK(JS::ubi::Node(global2
).compartment() == global2
->compartment());
142 CHECK(JS::ubi::Node(global2
).compartment() != global1
->compartment());
143 CHECK(JS::ubi::Node(global2
).realm() == global2
->nonCCWRealm());
144 CHECK(JS::ubi::Node(global2
).realm() != global1
->nonCCWRealm());
146 JS::CompileOptions
options(cx
);
148 JS::SourceText
<mozilla::Utf8Unit
> emptySrcBuf
;
149 CHECK(emptySrcBuf
.init(cx
, "", 0, JS::SourceOwnership::Borrowed
));
151 // Create a script in the original realm...
152 RootedScript
script1(cx
, JS::Compile(cx
, options
, emptySrcBuf
));
156 // ... and then enter global2's realm and create a script
158 JSAutoRealm
ar(cx
, global2
);
160 RootedScript
script2(cx
, JS::Compile(cx
, options
, emptySrcBuf
));
163 CHECK(JS::ubi::Node(script1
).compartment() == global1
->compartment());
164 CHECK(JS::ubi::Node(script2
).compartment() == global2
->compartment());
165 CHECK(JS::ubi::Node(script1
).realm() == global1
->nonCCWRealm());
166 CHECK(JS::ubi::Node(script2
).realm() == global2
->nonCCWRealm());
168 // Now create a wrapper for global1 in global2's compartment.
169 RootedObject
wrappedGlobal1(cx
, global1
);
170 CHECK(cx
->compartment()->wrap(cx
, &wrappedGlobal1
));
172 // Cross-compartment wrappers have a compartment() but not a realm().
173 CHECK(JS::ubi::Node(wrappedGlobal1
).zone() == cx
->zone());
174 CHECK(JS::ubi::Node(wrappedGlobal1
).compartment() == cx
->compartment());
175 CHECK(JS::ubi::Node(wrappedGlobal1
).realm() == nullptr);
180 END_TEST(test_ubiNodeCompartment
)
182 template <typename F
, typename G
>
183 static bool checkString(const char* expected
, F fillBufferFunction
,
184 G stringGetterFunction
) {
185 auto expectedLength
= strlen(expected
);
187 if (fillBufferFunction(mozilla::RangedPtr
<char16_t
>(buf
, 1024), 1024) !=
189 !EqualChars(buf
, expected
, expectedLength
)) {
193 auto string
= stringGetterFunction();
194 // Expecting a |JSAtom*| from a live |JS::ubi::StackFrame|.
195 if (!string
.template is
<JSAtom
*>() ||
196 !StringEqualsAscii(string
.template as
<JSAtom
*>(), expected
)) {
203 BEGIN_TEST(test_ubiStackFrame
) {
204 CHECK(js::DefineTestingFunctions(cx
, global
, false, false));
206 JS::RootedValue
val(cx
);
208 evaluate("(function one() { \n" // 1
209 " return (function two() { \n" // 2
210 " return (function three() { \n" // 3
211 " return saveStack(); \n" // 4
215 "filename.js", 1, &val
));
217 CHECK(val
.isObject());
218 JS::RootedObject
obj(cx
, &val
.toObject());
220 CHECK(obj
->is
<SavedFrame
>());
221 JS::Rooted
<SavedFrame
*> savedFrame(cx
, &obj
->as
<SavedFrame
>());
223 JS::ubi::StackFrame
ubiFrame(savedFrame
);
225 // All frames should be from the "filename.js" source.
229 [&](mozilla::RangedPtr
<char16_t
> ptr
, size_t length
) {
230 return ubiFrame
.source(ptr
, length
);
232 [&] { return ubiFrame
.source(); }));
233 ubiFrame
= ubiFrame
.parent();
236 ubiFrame
= savedFrame
;
238 auto bufferFunctionDisplayName
= [&](mozilla::RangedPtr
<char16_t
> ptr
,
240 return ubiFrame
.functionDisplayName(ptr
, length
);
242 auto getFunctionDisplayName
= [&] { return ubiFrame
.functionDisplayName(); };
245 checkString("three", bufferFunctionDisplayName
, getFunctionDisplayName
));
246 CHECK(ubiFrame
.line() == 4);
248 ubiFrame
= ubiFrame
.parent();
249 CHECK(checkString("two", bufferFunctionDisplayName
, getFunctionDisplayName
));
250 CHECK(ubiFrame
.line() == 5);
252 ubiFrame
= ubiFrame
.parent();
253 CHECK(checkString("one", bufferFunctionDisplayName
, getFunctionDisplayName
));
254 CHECK(ubiFrame
.line() == 6);
256 ubiFrame
= ubiFrame
.parent();
257 CHECK(ubiFrame
.functionDisplayName().is
<JSAtom
*>());
258 CHECK(ubiFrame
.functionDisplayName().as
<JSAtom
*>() == nullptr);
259 CHECK(ubiFrame
.line() == 7);
261 ubiFrame
= ubiFrame
.parent();
266 END_TEST(test_ubiStackFrame
)
268 BEGIN_TEST(test_ubiCoarseType
) {
269 // Test that our explicit coarseType() overrides work as expected.
271 JSObject
* obj
= nullptr;
272 CHECK(JS::ubi::Node(obj
).coarseType() == JS::ubi::CoarseType::Object
);
274 JSScript
* script
= nullptr;
275 CHECK(JS::ubi::Node(script
).coarseType() == JS::ubi::CoarseType::Script
);
277 js::BaseScript
* baseScript
= nullptr;
278 CHECK(JS::ubi::Node(baseScript
).coarseType() == JS::ubi::CoarseType::Script
);
280 js::jit::JitCode
* jitCode
= nullptr;
281 CHECK(JS::ubi::Node(jitCode
).coarseType() == JS::ubi::CoarseType::Script
);
283 JSString
* str
= nullptr;
284 CHECK(JS::ubi::Node(str
).coarseType() == JS::ubi::CoarseType::String
);
286 // Test that the default when coarseType() is not overridden is Other.
288 JS::Symbol
* sym
= nullptr;
289 CHECK(JS::ubi::Node(sym
).coarseType() == JS::ubi::CoarseType::Other
);
293 END_TEST(test_ubiCoarseType
)
295 struct ExpectedEdge
{
299 ExpectedEdge(FakeNode
& fromNode
, FakeNode
& toNode
)
300 : from(fromNode
.name
), to(toNode
.name
) {}
306 struct DefaultHasher
<ExpectedEdge
> {
307 using Lookup
= ExpectedEdge
;
309 static HashNumber
hash(const Lookup
& l
) {
310 return mozilla::AddToHash(l
.from
, l
.to
);
313 static bool match(const ExpectedEdge
& k
, const Lookup
& l
) {
314 return k
.from
== l
.from
&& k
.to
== l
.to
;
318 } // namespace mozilla
320 BEGIN_TEST(test_ubiPostOrder
) {
321 // Construct the following graph:
325 // .-------| r |---------------.
331 // .------| a |------. .----| e |----.
333 // | '--^--' | | '-----' |
335 // .--V--. | .--V--. .--V--. .--V--.
337 // | b | '------| c |-----> f |---------> g |
339 // '-----' '-----' '-----' '-----'
343 // '------> d <------'
357 js::HashSet
<ExpectedEdge
> expectedEdges(cx
);
359 auto declareEdge
= [&](FakeNode
& from
, FakeNode
& to
) {
360 return from
.addEdgeTo(to
) && expectedEdges
.putNew(ExpectedEdge(from
, to
));
363 CHECK(declareEdge(r
, a
));
364 CHECK(declareEdge(r
, e
));
365 CHECK(declareEdge(a
, b
));
366 CHECK(declareEdge(a
, c
));
367 CHECK(declareEdge(b
, d
));
368 CHECK(declareEdge(c
, a
));
369 CHECK(declareEdge(c
, d
));
370 CHECK(declareEdge(c
, f
));
371 CHECK(declareEdge(e
, f
));
372 CHECK(declareEdge(e
, g
));
373 CHECK(declareEdge(f
, g
));
375 js::Vector
<char, 8, js::SystemAllocPolicy
> visited
;
377 // Do a PostOrder traversal, starting from r. Accumulate the names of
378 // the nodes we visit in `visited`. Remove edges we traverse from
379 // `expectedEdges` as we find them to ensure that we only find each edge
382 JS::AutoCheckCannotGC
nogc(cx
);
383 JS::ubi::PostOrder
traversal(cx
, nogc
);
384 CHECK(traversal
.addStart(&r
));
386 auto onNode
= [&](const JS::ubi::Node
& node
) {
387 return visited
.append(node
.as
<FakeNode
>()->name
);
390 auto onEdge
= [&](const JS::ubi::Node
& origin
, const JS::ubi::Edge
& edge
) {
391 ExpectedEdge
e(*origin
.as
<FakeNode
>(), *edge
.referent
.as
<FakeNode
>());
392 if (!expectedEdges
.has(e
)) {
393 fprintf(stderr
, "Error: Unexpected edge from %c to %c!\n",
394 origin
.as
<FakeNode
>()->name
,
395 edge
.referent
.as
<FakeNode
>()->name
);
399 expectedEdges
.remove(e
);
403 CHECK(traversal
.traverse(onNode
, onEdge
));
406 fprintf(stderr
, "visited.length() = %lu\n", (unsigned long)visited
.length());
407 for (size_t i
= 0; i
< visited
.length(); i
++) {
408 fprintf(stderr
, "visited[%lu] = '%c'\n", (unsigned long)i
, visited
[i
]);
411 CHECK(visited
.length() == 8);
412 CHECK(visited
[0] == 'g');
413 CHECK(visited
[1] == 'f');
414 CHECK(visited
[2] == 'e');
415 CHECK(visited
[3] == 'd');
416 CHECK(visited
[4] == 'c');
417 CHECK(visited
[5] == 'b');
418 CHECK(visited
[6] == 'a');
419 CHECK(visited
[7] == 'r');
421 // We found all the edges we expected.
422 CHECK(expectedEdges
.count() == 0);
426 END_TEST(test_ubiPostOrder
)
428 BEGIN_TEST(test_JS_ubi_DominatorTree
) {
429 // Construct the following graph:
432 // | <--------------------------------.
433 // .--------+--------------| r |--------------. |
436 // | .--V--. .--V--. |
438 // | | b | | c |--------. |
440 // | '-----' '-----' | |
441 // .--V--. | | .--V--. |
443 // | a <-----+ | .----| g | |
444 // | | | .----' | | | |
445 // '-----' | | | '-----' |
447 // .--V--. | .-----. .--V--. | | |
448 // | | | | | | | | | |
449 // | d <-----+----> e <----. | f | | | |
450 // | | | | | | | | | |
451 // '-----' '-----' | '-----' | | |
452 // | .-----. | | | | .--V--. |
453 // | | | | | | .-' | | |
454 // '-----> l | | | | | | j | |
455 // | | '--. | | | | | |
456 // '-----' | | | | '-----' |
457 // | .--V--. | | .--V--. | |
459 // '-------> h |-' '---> i <------' |
460 // | | .---------> | |
461 // '-----' | '-----' |
464 // '----------> k <---------' |
468 // '----------------------------'
470 // This graph has the following dominator tree:
486 // This graph and dominator tree are taken from figures 1 and 2 of "A Fast
487 // Algorithm for Finding Dominators in a Flowgraph" by Lengauer et al:
488 // http://www.cs.princeton.edu/courses/archive/spr03/cs423/download/dominators.pdf.
504 CHECK(r
.addEdgeTo(a
));
505 CHECK(r
.addEdgeTo(b
));
506 CHECK(r
.addEdgeTo(c
));
507 CHECK(a
.addEdgeTo(d
));
508 CHECK(b
.addEdgeTo(a
));
509 CHECK(b
.addEdgeTo(d
));
510 CHECK(b
.addEdgeTo(e
));
511 CHECK(c
.addEdgeTo(f
));
512 CHECK(c
.addEdgeTo(g
));
513 CHECK(d
.addEdgeTo(l
));
514 CHECK(e
.addEdgeTo(h
));
515 CHECK(f
.addEdgeTo(i
));
516 CHECK(g
.addEdgeTo(i
));
517 CHECK(g
.addEdgeTo(j
));
518 CHECK(h
.addEdgeTo(e
));
519 CHECK(h
.addEdgeTo(k
));
520 CHECK(i
.addEdgeTo(k
));
521 CHECK(j
.addEdgeTo(i
));
522 CHECK(k
.addEdgeTo(r
));
523 CHECK(k
.addEdgeTo(i
));
524 CHECK(l
.addEdgeTo(h
));
526 mozilla::Maybe
<JS::ubi::DominatorTree
> maybeTree
;
528 JS::AutoCheckCannotGC
noGC(cx
);
529 maybeTree
= JS::ubi::DominatorTree::Create(cx
, noGC
, &r
);
532 CHECK(maybeTree
.isSome());
533 auto& tree
= *maybeTree
;
535 // We return the null JS::ubi::Node for nodes that were not reachable in the
536 // graph when computing the dominator tree.
538 CHECK(tree
.getImmediateDominator(&m
) == JS::ubi::Node());
539 CHECK(tree
.getDominatedSet(&m
).isNothing());
544 } domination
[] = {{r
, r
}, {a
, r
}, {b
, r
}, {c
, r
}, {d
, r
}, {e
, r
}, {f
, c
},
545 {g
, c
}, {h
, r
}, {i
, r
}, {j
, g
}, {k
, r
}, {l
, d
}};
547 for (auto& relation
: domination
) {
548 // Test immediate dominator.
550 stderr
, "%c's immediate dominator is %c\n", relation
.dominated
.name
,
551 tree
.getImmediateDominator(&relation
.dominator
).as
<FakeNode
>()->name
);
552 CHECK(tree
.getImmediateDominator(&relation
.dominated
) ==
553 JS::ubi::Node(&relation
.dominator
));
555 // Test the dominated set. Build up the expected dominated set based on
556 // the set of nodes immediately dominated by this one in `domination`,
557 // then iterate over the actual dominated set and check against the
560 auto& node
= relation
.dominated
;
561 fprintf(stderr
, "Checking %c's dominated set:\n", node
.name
);
563 js::HashSet
<char> expectedDominatedSet(cx
);
564 for (auto& rel
: domination
) {
565 if (&rel
.dominator
== &node
) {
566 fprintf(stderr
, " Expecting %c\n", rel
.dominated
.name
);
567 CHECK(expectedDominatedSet
.putNew(rel
.dominated
.name
));
571 auto maybeActualDominatedSet
= tree
.getDominatedSet(&node
);
572 CHECK(maybeActualDominatedSet
.isSome());
573 auto& actualDominatedSet
= *maybeActualDominatedSet
;
575 for (const auto& dominated
: actualDominatedSet
) {
576 fprintf(stderr
, " Found %c\n", dominated
.as
<FakeNode
>()->name
);
577 CHECK(expectedDominatedSet
.has(dominated
.as
<FakeNode
>()->name
));
578 expectedDominatedSet
.remove(dominated
.as
<FakeNode
>()->name
);
581 // Ensure we found them all and aren't still expecting nodes we never
583 CHECK(expectedDominatedSet
.count() == 0);
585 fprintf(stderr
, "Done checking %c's dominated set.\n\n", node
.name
);
590 JS::ubi::Node::Size retainedSize
;
592 {r
, 13}, {a
, 1}, {b
, 1}, {c
, 4}, {d
, 2}, {e
, 1}, {f
, 1},
593 {g
, 2}, {h
, 1}, {i
, 1}, {j
, 1}, {k
, 1}, {l
, 1},
596 for (auto& expected
: sizes
) {
597 JS::ubi::Node::Size actual
= 0;
598 CHECK(tree
.getRetainedSize(&expected
.node
, nullptr, actual
));
599 CHECK(actual
== expected
.retainedSize
);
604 END_TEST(test_JS_ubi_DominatorTree
)
606 BEGIN_TEST(test_JS_ubi_Node_scriptFilename
) {
607 JS::RootedValue
val(cx
);
609 evaluate("(function one() { \n" // 1
610 " return (function two() { \n" // 2
611 " return (function three() { \n" // 3
612 " return function four() {}; \n" // 4
616 "my-cool-filename.js", 1, &val
));
618 CHECK(val
.isObject());
619 JS::RootedObject
obj(cx
, &val
.toObject());
621 CHECK(obj
->is
<JSFunction
>());
622 JS::RootedFunction
func(cx
, &obj
->as
<JSFunction
>());
624 JS::RootedScript
script(cx
, JSFunction::getOrCreateScript(cx
, func
));
626 CHECK(script
->filename());
628 JS::ubi::Node
node(script
);
629 const char* filename
= node
.scriptFilename();
631 CHECK(strcmp(filename
, script
->filename()) == 0);
632 CHECK(strcmp(filename
, "my-cool-filename.js") == 0);
636 END_TEST(test_JS_ubi_Node_scriptFilename
)
638 #define LAMBDA_CHECK(cond) \
641 fprintf(stderr, "%s:%d:CHECK failed: " #cond "\n", __FILE__, __LINE__); \
646 static void dumpPath(JS::ubi::Path
& path
) {
647 for (size_t i
= 0; i
< path
.length(); i
++) {
648 fprintf(stderr
, "path[%llu]->predecessor() = '%c'\n", (long long unsigned)i
,
649 path
[i
]->predecessor().as
<FakeNode
>()->name
);
653 BEGIN_TEST(test_JS_ubi_ShortestPaths_no_path
) {
654 // Create the following graph:
657 // | a | <--> | c | | b |
662 CHECK(a
.addEdgeTo(c
));
663 CHECK(c
.addEdgeTo(a
));
665 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
667 JS::AutoCheckCannotGC
noGC(cx
);
669 JS::ubi::NodeSet targets
;
670 CHECK(targets
.put(&b
));
673 JS::ubi::ShortestPaths::Create(cx
, noGC
, 10, &a
, std::move(targets
));
676 CHECK(maybeShortestPaths
);
677 auto& paths
= *maybeShortestPaths
;
679 size_t numPathsFound
= 0;
680 bool ok
= paths
.forEachPath(&b
, [&](JS::ubi::Path
& path
) {
686 CHECK(numPathsFound
== 0);
690 END_TEST(test_JS_ubi_ShortestPaths_no_path
)
692 BEGIN_TEST(test_JS_ubi_ShortestPaths_one_path
) {
693 // Create the following graph:
696 // | a | <--> | c | --> | b |
701 CHECK(a
.addEdgeTo(c
));
702 CHECK(c
.addEdgeTo(a
));
703 CHECK(c
.addEdgeTo(b
));
705 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
707 JS::AutoCheckCannotGC
noGC(cx
);
709 JS::ubi::NodeSet targets
;
710 CHECK(targets
.put(&b
));
713 JS::ubi::ShortestPaths::Create(cx
, noGC
, 10, &a
, std::move(targets
));
716 CHECK(maybeShortestPaths
);
717 auto& paths
= *maybeShortestPaths
;
719 size_t numPathsFound
= 0;
720 bool ok
= paths
.forEachPath(&b
, [&](JS::ubi::Path
& path
) {
724 LAMBDA_CHECK(path
.length() == 2);
725 LAMBDA_CHECK(path
[0]->predecessor() == JS::ubi::Node(&a
));
726 LAMBDA_CHECK(path
[1]->predecessor() == JS::ubi::Node(&c
));
732 CHECK(numPathsFound
== 1);
736 END_TEST(test_JS_ubi_ShortestPaths_one_path
)
738 BEGIN_TEST(test_JS_ubi_ShortestPaths_multiple_paths
) {
739 // Create the following graph:
762 CHECK(a
.addEdgeTo(b
));
763 CHECK(a
.addEdgeTo(f
));
764 CHECK(a
.addEdgeTo(d
));
765 CHECK(b
.addEdgeTo(c
));
766 CHECK(c
.addEdgeTo(f
));
767 CHECK(d
.addEdgeTo(e
));
768 CHECK(e
.addEdgeTo(f
));
770 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
772 JS::AutoCheckCannotGC
noGC(cx
);
774 JS::ubi::NodeSet targets
;
775 CHECK(targets
.put(&f
));
778 JS::ubi::ShortestPaths::Create(cx
, noGC
, 10, &a
, std::move(targets
));
781 CHECK(maybeShortestPaths
);
782 auto& paths
= *maybeShortestPaths
;
784 size_t numPathsFound
= 0;
785 bool ok
= paths
.forEachPath(&f
, [&](JS::ubi::Path
& path
) {
789 switch (path
.back()->predecessor().as
<FakeNode
>()->name
) {
791 LAMBDA_CHECK(path
.length() == 1);
796 LAMBDA_CHECK(path
.length() == 3);
797 LAMBDA_CHECK(path
[0]->predecessor() == JS::ubi::Node(&a
));
798 LAMBDA_CHECK(path
[1]->predecessor() == JS::ubi::Node(&b
));
799 LAMBDA_CHECK(path
[2]->predecessor() == JS::ubi::Node(&c
));
804 LAMBDA_CHECK(path
.length() == 3);
805 LAMBDA_CHECK(path
[0]->predecessor() == JS::ubi::Node(&a
));
806 LAMBDA_CHECK(path
[1]->predecessor() == JS::ubi::Node(&d
));
807 LAMBDA_CHECK(path
[2]->predecessor() == JS::ubi::Node(&e
));
821 fprintf(stderr
, "numPathsFound = %llu\n", (long long unsigned)numPathsFound
);
822 CHECK(numPathsFound
== 3);
826 END_TEST(test_JS_ubi_ShortestPaths_multiple_paths
)
828 BEGIN_TEST(test_JS_ubi_ShortestPaths_more_paths_than_max
) {
829 // Create the following graph:
852 CHECK(a
.addEdgeTo(b
));
853 CHECK(a
.addEdgeTo(f
));
854 CHECK(a
.addEdgeTo(d
));
855 CHECK(b
.addEdgeTo(c
));
856 CHECK(c
.addEdgeTo(f
));
857 CHECK(d
.addEdgeTo(e
));
858 CHECK(e
.addEdgeTo(f
));
860 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
862 JS::AutoCheckCannotGC
noGC(cx
);
864 JS::ubi::NodeSet targets
;
865 CHECK(targets
.put(&f
));
868 JS::ubi::ShortestPaths::Create(cx
, noGC
, 1, &a
, std::move(targets
));
871 CHECK(maybeShortestPaths
);
872 auto& paths
= *maybeShortestPaths
;
874 size_t numPathsFound
= 0;
875 bool ok
= paths
.forEachPath(&f
, [&](JS::ubi::Path
& path
) {
882 fprintf(stderr
, "numPathsFound = %llu\n", (long long unsigned)numPathsFound
);
883 CHECK(numPathsFound
== 1);
887 END_TEST(test_JS_ubi_ShortestPaths_more_paths_than_max
)
889 BEGIN_TEST(test_JS_ubi_ShortestPaths_multiple_edges_to_target
) {
890 // Create the following graph:
904 CHECK(a
.addEdgeTo(b
, u
"x"));
905 CHECK(a
.addEdgeTo(b
, u
"y"));
906 CHECK(a
.addEdgeTo(b
, u
"z"));
908 mozilla::Maybe
<JS::ubi::ShortestPaths
> maybeShortestPaths
;
910 JS::AutoCheckCannotGC
noGC(cx
);
912 JS::ubi::NodeSet targets
;
913 CHECK(targets
.put(&b
));
916 JS::ubi::ShortestPaths::Create(cx
, noGC
, 10, &a
, std::move(targets
));
919 CHECK(maybeShortestPaths
);
920 auto& paths
= *maybeShortestPaths
;
922 size_t numPathsFound
= 0;
927 bool ok
= paths
.forEachPath(&b
, [&](JS::ubi::Path
& path
) {
931 LAMBDA_CHECK(path
.length() == 1);
932 LAMBDA_CHECK(path
.back()->name());
933 LAMBDA_CHECK(js_strlen(path
.back()->name().get()) == 1);
935 auto c
= uint8_t(path
.back()->name().get()[0]);
936 fprintf(stderr
, "Edge name = '%c'\n", c
);
961 fprintf(stderr
, "numPathsFound = %llu\n", (long long unsigned)numPathsFound
);
962 CHECK(numPathsFound
== 3);
969 END_TEST(test_JS_ubi_ShortestPaths_multiple_edges_to_target
)