/* -*- 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/. */ #include #include #include "gc/FindSCCs.h" #include "jsapi-tests/tests.h" static const unsigned MaxVertices = 10; using js::gc::GraphNodeBase; using js::gc::ComponentFinder; struct TestComponentFinder; struct TestNode : public GraphNodeBase { unsigned index; bool hasEdge[MaxVertices]; void findOutgoingEdges(TestComponentFinder& finder); }; struct TestComponentFinder : public ComponentFinder { explicit TestComponentFinder(uintptr_t sl) : ComponentFinder(sl) {} }; static TestNode Vertex[MaxVertices]; void TestNode::findOutgoingEdges(TestComponentFinder& finder) { for (unsigned i = 0; i < MaxVertices; ++i) { if (hasEdge[i]) finder.addEdgeTo(&Vertex[i]); } } BEGIN_TEST(testFindSCCs) { // no vertices setup(0); run(); CHECK(end()); // no edges setup(1); run(); CHECK(group(0, -1)); CHECK(end()); setup(3); run(); CHECK(group(2, -1)); CHECK(group(1, -1)); CHECK(group(0, -1)); CHECK(end()); // linear setup(3); edge(0, 1); edge(1, 2); run(); CHECK(group(0, -1)); CHECK(group(1, -1)); CHECK(group(2, -1)); CHECK(end()); // tree setup(3); edge(0, 1); edge(0, 2); run(); CHECK(group(0, -1)); CHECK(group(2, -1)); CHECK(group(1, -1)); CHECK(end()); // cycles setup(3); edge(0, 1); edge(1, 2); edge(2, 0); run(); CHECK(group(0, 1, 2, -1)); CHECK(end()); setup(4); edge(0, 1); edge(1, 2); edge(2, 1); edge(2, 3); run(); CHECK(group(0, -1)); CHECK(group(1, 2, -1)); CHECK(group(3, -1)); CHECK(end()); // remaining setup(2); edge(0, 1); run(); CHECK(remaining(0, 1, -1)); CHECK(end()); setup(2); edge(0, 1); run(); CHECK(group(0, -1)); CHECK(remaining(1, -1)); CHECK(end()); setup(2); edge(0, 1); run(); CHECK(group(0, -1)); CHECK(group(1, -1)); CHECK(remaining(-1)); CHECK(end()); return true; } unsigned vertex_count; TestComponentFinder* finder; TestNode* resultsList; void setup(unsigned count) { vertex_count = count; for (unsigned i = 0; i < MaxVertices; ++i) { TestNode& v = Vertex[i]; v.gcNextGraphNode = nullptr; v.index = i; memset(&v.hasEdge, 0, sizeof(v.hasEdge)); } } void edge(unsigned src_index, unsigned dest_index) { Vertex[src_index].hasEdge[dest_index] = true; } void run() { finder = new TestComponentFinder(cx->nativeStackLimit[js::StackForSystemCode]); for (unsigned i = 0; i < vertex_count; ++i) finder->addNode(&Vertex[i]); resultsList = finder->getResultsList(); } bool group(int vertex, ...) { TestNode* v = resultsList; va_list ap; va_start(ap, vertex); while (vertex != -1) { CHECK(v != nullptr); CHECK(v->index == unsigned(vertex)); v = v->nextNodeInGroup(); vertex = va_arg(ap, int); } va_end(ap); CHECK(v == nullptr); resultsList = resultsList->nextGroup(); return true; } bool remaining(int vertex, ...) { TestNode* v = resultsList; va_list ap; va_start(ap, vertex); while (vertex != -1) { CHECK(v != nullptr); CHECK(v->index == unsigned(vertex)); v = (TestNode*)v->gcNextGraphNode; vertex = va_arg(ap, int); } va_end(ap); CHECK(v == nullptr); resultsList = nullptr; return true; } bool end() { CHECK(resultsList == nullptr); delete finder; finder = nullptr; return true; } END_TEST(testFindSCCs) struct TestComponentFinder2; struct TestNode2 : public GraphNodeBase { TestNode2* edge; TestNode2() : edge(nullptr) {} void findOutgoingEdges(TestComponentFinder2& finder); }; struct TestComponentFinder2 : public ComponentFinder { explicit TestComponentFinder2(uintptr_t sl) : ComponentFinder(sl) {} }; void TestNode2::findOutgoingEdges(TestComponentFinder2& finder) { if (edge) finder.addEdgeTo(edge); } BEGIN_TEST(testFindSCCsStackLimit) { /* * Test what happens if recusion causes the stack to become full while * traversing the graph. * * The test case is a large number of vertices, almost all of which are * arranged in a linear chain. The last few are left unlinked to exercise * adding vertices after the stack full condition has already been detected. * * Such an arrangement with no cycles would normally result in one group for * each vertex, but since the stack is exhasted in processing a single group * is returned containing all the vertices. */ const unsigned max = 1000000; const unsigned initial = 10; TestNode2* vertices = new TestNode2[max](); for (unsigned i = initial; i < (max - 10); ++i) vertices[i].edge = &vertices[i + 1]; TestComponentFinder2 finder(cx->nativeStackLimit[js::StackForSystemCode]); for (unsigned i = 0; i < max; ++i) finder.addNode(&vertices[i]); TestNode2* r = finder.getResultsList(); CHECK(r); TestNode2* v = r; unsigned count = 0; while (v) { ++count; v = v->nextNodeInGroup(); } CHECK(count == max - initial); count = 0; v = r->nextGroup(); while (v) { ++count; CHECK(!v->nextNodeInGroup()); v = v->nextGroup(); } delete [] vertices; return true; } END_TEST(testFindSCCsStackLimit)