diff --git a/src/util/IntrusiveTreeSet.hxx b/src/util/IntrusiveTreeSet.hxx index 8320529a3..3b788a60a 100644 --- a/src/util/IntrusiveTreeSet.hxx +++ b/src/util/IntrusiveTreeSet.hxx @@ -176,6 +176,16 @@ public: [[nodiscard]] IntrusiveTreeSet() noexcept = default; +#ifndef NDEBUG + /** + * For debugging only: check the integrity of the red-black + * tree. + */ + void Check() noexcept { + RedBlackTreeNode::BlackHeight(GetRoot()); + } +#endif + [[nodiscard]] constexpr bool empty() const noexcept { return GetRoot() == nullptr; diff --git a/src/util/RedBlackTree.hxx b/src/util/RedBlackTree.hxx index 8cb205a5a..07012cede 100644 --- a/src/util/RedBlackTree.hxx +++ b/src/util/RedBlackTree.hxx @@ -173,6 +173,36 @@ struct RedBlackTreeNode { return GetLeftMost(this); } +#ifndef NDEBUG + /** + * Determine the "black height" (the number of black nodes in + * any path from the root to the leaves). This is for + * debugging only. It walks the whole tree and aborts if the + * black height is not consistent. + */ + static unsigned BlackHeight(const RedBlackTreeNode *node) noexcept { + if (node == nullptr) + /* leaf nodes (NIL / nullptr) count as + black */ + return 1; + + assert(node->parent != nullptr); + assert(node->color != Color::HEAD); + assert(node->color != Color::RED || node->parent->color != Color::RED); + + assert(node->children[0] == nullptr || node->children[0]->parent == node); + assert(node->children[1] == nullptr || node->children[1]->parent == node); + + const unsigned left_height = BlackHeight(node->children[0]); + const unsigned right_height = BlackHeight(node->children[1]); + + /* the black height must be equal in all paths */ + assert(left_height == right_height); + + return left_height + (node->color == Color::BLACK); + } +#endif + private: [[nodiscard]] constexpr static RedBlackTreeNode *GetLeftHandedParent(RedBlackTreeNode *node) noexcept { diff --git a/test/util/TestIntrusiveTreeSet.cxx b/test/util/TestIntrusiveTreeSet.cxx index e2fca6ca7..71e3fab88 100644 --- a/test/util/TestIntrusiveTreeSet.cxx +++ b/test/util/TestIntrusiveTreeSet.cxx @@ -182,6 +182,10 @@ TEST(IntrusiveTreeSet, RandomOrder) set.pop_front(); EXPECT_FALSE(items[remove].is_linked()); +#ifndef NDEBUG + set.Check(); +#endif + expected = remove + 1; for (const auto &i : set) { EXPECT_EQ(i.value, expected++);