helpers: add red-black tree validators

This one is considerably more complicated than the linked list one, but
it should catch lots of kinds of red-black tree mistakes.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
This commit is contained in:
Omar Sandoval 2022-05-17 13:59:05 -07:00
parent 9ae36fd12e
commit 243abf59e5
3 changed files with 304 additions and 7 deletions

View File

@ -9,9 +9,10 @@ The ``drgn.helpers.linux.rbtree`` module provides helpers for working with
red-black trees from :linux:`include/linux/rbtree.h`.
"""
from typing import Callable, Iterator, TypeVar, Union
from typing import Callable, Generator, Iterator, Optional, Tuple, TypeVar, Union
from drgn import NULL, Object, Type, container_of
from drgn.helpers import ValidationError
__all__ = (
"RB_EMPTY_ROOT",
@ -24,6 +25,8 @@ __all__ = (
"rb_prev",
"rbtree_inorder_for_each",
"rbtree_inorder_for_each_entry",
"validate_rbtree",
"validate_rbtree_inorder_for_each_entry",
)
@ -56,6 +59,12 @@ def rb_parent(node: Object) -> Object:
return Object(node.prog_, node.type_, value=node.__rb_parent_color.value_() & ~3)
# Return parent node and whether the node is black.
def _rb_parent_color(node: Object) -> Tuple[Object, bool]:
value = node.__rb_parent_color.value_()
return Object(node.prog_, node.type_, value=value & ~3), (value & 1) != 0
def rb_first(root: Object) -> Object:
"""
Return the first node (in sort order) in a red-black tree, or ``NULL`` if
@ -221,3 +230,122 @@ def rb_find(
else:
return entry
return NULL(prog, prog.pointer_type(type))
def validate_rbtree(
type: Union[str, Type],
root: Object,
member: str,
cmp: Callable[[Object, Object], int],
allow_equal: bool,
) -> None:
"""
Validate a red-black tree.
This checks that:
1. The tree is a valid binary search tree ordered according to *cmp*.
2. If *allow_equal* is ``False``, there are no nodes that compare equal
according to *cmp*.
3. The ``rb_parent`` pointers are consistent.
4. The red-black tree requirements are satisfied: the root node is black,
no red node has a red child, and every path from any node to any of its
descendant leaf nodes goes through the same number of black nodes.
"""
for _ in validate_rbtree_inorder_for_each_entry(
type, root, member, cmp, allow_equal
):
pass
def validate_rbtree_inorder_for_each_entry(
type: Union[str, Type],
root: Object,
member: str,
cmp: Callable[[Object, Object], int],
allow_equal: bool,
) -> Iterator[Object]:
prog = root.prog_
type = prog.type(type)
def visit(
node: Object,
parent_node: Object,
parent_entry: Object,
parent_is_red: bool,
is_left: bool,
) -> Generator[Object, None, int]:
if node:
node_rb_parent, black = _rb_parent_color(node)
if node_rb_parent != parent_node:
raise ValidationError(
f"{parent_node.format_(dereference=False, symbolize=False)}"
f" rb_{'left' if is_left else 'right'}"
f" {node.format_(dereference=False, symbolize=False, type_name=False)}"
f" has rb_parent {node_rb_parent.format_(dereference=False, symbolize=False, type_name=False)}"
)
if parent_is_red and not black:
raise ValidationError(
f"red node {parent_node.format_(dereference=False, symbolize=False)}"
f" has red child {node.format_(dereference=False, symbolize=False, type_name=False)}"
)
entry = container_of(node, type, member)
r = cmp(entry, parent_entry)
if r > 0:
if is_left:
raise ValidationError(
f"{parent_entry.format_(dereference=False, symbolize=False)}"
f" left child {entry.format_(dereference=False, symbolize=False, type_name=False)}"
" compares greater than it"
)
elif r < 0:
if not is_left:
raise ValidationError(
f"{parent_entry.format_(dereference=False, symbolize=False)}"
f" right child {entry.format_(dereference=False, symbolize=False, type_name=False)}"
" compares less than it"
)
elif not allow_equal:
raise ValidationError(
f"{parent_entry.format_(dereference=False, symbolize=False)}"
f" {'left' if is_left else 'right'}"
f" child {entry.format_(dereference=False, symbolize=False, type_name=False)}"
" compares equal to it"
)
return (yield from descend(node, entry, black))
else:
return 0
def descend(
node: Object, entry: Object, black: bool
) -> Generator[Object, None, int]:
left_black_height = yield from visit(
node.rb_left.read_(), node, entry, parent_is_red=not black, is_left=True
)
yield entry
right_black_height = yield from visit(
node.rb_right.read_(), node, entry, parent_is_red=not black, is_left=False
)
if left_black_height != right_black_height:
raise ValidationError(
f"left and right subtrees of {node.format_(dereference=False, symbolize=False)}"
f" have unequal black heights ({left_black_height} != {right_black_height})"
)
return left_black_height + black
root_node = root.rb_node.read_()
if root_node:
parent, black = _rb_parent_color(root_node)
if parent:
raise ValidationError(
f"root node {root_node.format_(dereference=False, symbolize=False)}"
f" has parent {parent.format_(dereference=False, symbolize=False, type_name=False)}"
)
if not black:
raise ValidationError(
f"root node {root_node.format_(dereference=False, symbolize=False)} is red"
)
yield from descend(root_node, container_of(root_node, type, member), black)

View File

@ -1,11 +1,10 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import re
import signal
import collections
from drgn import NULL
from drgn.helpers import ValidationError
from drgn.helpers.linux.rbtree import (
RB_EMPTY_NODE,
RB_EMPTY_ROOT,
@ -17,6 +16,8 @@ from drgn.helpers.linux.rbtree import (
rb_prev,
rbtree_inorder_for_each,
rbtree_inorder_for_each_entry,
validate_rbtree,
validate_rbtree_inorder_for_each_entry,
)
from tests.linux_kernel import LinuxKernelTestCase, skip_unless_have_test_kmod
@ -28,6 +29,7 @@ class TestRbtree(LinuxKernelTestCase):
cls.root = cls.prog["drgn_test_rb_root"].address_of_()
cls.entries = cls.prog["drgn_test_rb_entries"]
cls.num_entries = 4
cls.empty_root = cls.prog["drgn_test_empty_rb_root"].address_of_()
def node(self, n):
return self.entries[n].node.address_of_()
@ -36,9 +38,7 @@ class TestRbtree(LinuxKernelTestCase):
return self.entries[n].address_of_()
def test_RB_EMPTY_ROOT(self):
self.assertTrue(
RB_EMPTY_ROOT(self.prog["drgn_test_empty_rb_root"].address_of_())
)
self.assertTrue(RB_EMPTY_ROOT(self.empty_root))
self.assertFalse(RB_EMPTY_ROOT(self.root))
def test_RB_EMPTY_NODE(self):
@ -104,3 +104,91 @@ class TestRbtree(LinuxKernelTestCase):
),
NULL(self.prog, "struct drgn_test_rb_entry *"),
)
@staticmethod
def cmp_entries(a, b):
return a.value.value_() - b.value.value_()
def test_validate_rbtree_success(self):
for root, allow_equal in (
(self.root, False),
(self.empty_root, False),
(self.prog["drgn_test_rbtree_with_equal"].address_of_(), True),
):
validate_rbtree(
"struct drgn_test_rb_entry", root, "node", self.cmp_entries, allow_equal
)
self.assertEqual(
list(
validate_rbtree_inorder_for_each_entry(
"struct drgn_test_rb_entry",
root,
"node",
self.cmp_entries,
allow_equal,
)
),
list(
rbtree_inorder_for_each_entry(
"struct drgn_test_rb_entry", root, "node"
)
),
)
def assert_validation_error(self, regex, name):
self.assertRaisesRegex(
ValidationError,
regex,
validate_rbtree,
"struct drgn_test_rb_entry",
self.prog[name].address_of_(),
"node",
self.cmp_entries,
False,
)
self.assertRaisesRegex(
ValidationError,
regex,
collections.deque,
validate_rbtree_inorder_for_each_entry(
"struct drgn_test_rb_entry",
self.prog[name].address_of_(),
"node",
self.cmp_entries,
False,
),
0,
)
def test_validate_rbtree_has_equal(self):
self.assert_validation_error("compares equal", "drgn_test_rbtree_with_equal")
def test_validate_rbtree_out_of_order(self):
self.assert_validation_error(
"compares (greater|less) than", "drgn_test_rbtree_out_of_order"
)
def test_validate_rbtree_null_root_parent(self):
self.assert_validation_error(
"root node .* has parent", "drgn_test_rbtree_with_bad_root_parent"
)
def test_validate_rbtree_red_root(self):
self.assert_validation_error(
"root node .* is red", "drgn_test_rbtree_with_red_root"
)
def test_validate_rbtree_inconsistent_parents(self):
self.assert_validation_error(
"rb_parent", "drgn_test_rbtree_with_inconsistent_parents"
)
def test_validate_rbtree_red_violation(self):
self.assert_validation_error(
"red node .* has red child", "drgn_test_rbtree_with_red_violation"
)
def test_validate_rbtree_black_violation(self):
self.assert_validation_error(
"unequal black heights", "drgn_test_rbtree_with_black_violation"
)

View File

@ -12,6 +12,7 @@
#include <linux/list.h>
#include <linux/module.h>
#include <linux/rbtree.h>
#include <linux/rbtree_augmented.h>
#include <linux/slab.h>
// list
@ -96,6 +97,27 @@ struct drgn_test_rb_entry drgn_test_rb_entries[4];
struct rb_node drgn_test_empty_rb_node;
struct rb_root drgn_test_rbtree_with_equal = RB_ROOT;
struct drgn_test_rb_entry drgn_test_rb_entries_with_equal[4];
struct rb_root drgn_test_rbtree_out_of_order = RB_ROOT;
struct drgn_test_rb_entry drgn_test_rb_entries_out_of_order[4];
struct rb_root drgn_test_rbtree_with_bad_root_parent = RB_ROOT;
struct drgn_test_rb_entry drgn_test_rb_entry_bad_root_parent;
struct rb_root drgn_test_rbtree_with_red_root = RB_ROOT;
struct drgn_test_rb_entry drgn_test_rb_entry_red_root;
struct rb_root drgn_test_rbtree_with_inconsistent_parents = RB_ROOT;
struct drgn_test_rb_entry drgn_test_rb_entries_with_inconsistent_parents[2];
struct rb_root drgn_test_rbtree_with_red_violation = RB_ROOT;
struct drgn_test_rb_entry drgn_test_rb_entries_with_red_violation[3];
struct rb_root drgn_test_rbtree_with_black_violation = RB_ROOT;
struct drgn_test_rb_entry drgn_test_rb_entries_with_black_violation[2];
static void drgn_test_rbtree_insert(struct rb_root *root,
struct drgn_test_rb_entry *entry)
{
@ -118,6 +140,7 @@ static void drgn_test_rbtree_insert(struct rb_root *root,
static void drgn_test_rbtree_init(void)
{
struct rb_node *node;
size_t i;
for (i = 0; i < ARRAY_SIZE(drgn_test_rb_entries); i++) {
@ -126,6 +149,64 @@ static void drgn_test_rbtree_init(void)
&drgn_test_rb_entries[i]);
}
RB_CLEAR_NODE(&drgn_test_empty_rb_node);
// Red-black tree with entries that compare equal to each other.
for (i = 0; i < ARRAY_SIZE(drgn_test_rb_entries_with_equal); i++) {
drgn_test_rb_entries_with_equal[i].value = i / 2;
drgn_test_rbtree_insert(&drgn_test_rbtree_with_equal,
&drgn_test_rb_entries_with_equal[i]);
}
// Bad red-black tree whose entries are out of order.
for (i = 0; i < ARRAY_SIZE(drgn_test_rb_entries_out_of_order); i++) {
drgn_test_rb_entries_out_of_order[i].value = i;
drgn_test_rbtree_insert(&drgn_test_rbtree_out_of_order,
&drgn_test_rb_entries_out_of_order[i]);
}
drgn_test_rb_entries_out_of_order[0].value = 99;
// Bad red-black tree with a root node that has a non-NULL parent.
drgn_test_rbtree_insert(&drgn_test_rbtree_with_bad_root_parent,
&drgn_test_rb_entry_bad_root_parent);
rb_set_parent(&drgn_test_rb_entry_bad_root_parent.node,
&drgn_test_empty_rb_node);
// Bad red-black tree with a red root node.
rb_link_node(&drgn_test_rb_entry_red_root.node, NULL,
&drgn_test_rbtree_with_red_root.rb_node);
// Bad red-black tree with inconsistent rb_parent.
for (i = 0; i < ARRAY_SIZE(drgn_test_rb_entries_with_inconsistent_parents); i++) {
drgn_test_rb_entries_with_inconsistent_parents[i].value = i;
drgn_test_rbtree_insert(&drgn_test_rbtree_with_inconsistent_parents,
&drgn_test_rb_entries_with_inconsistent_parents[i]);
}
node = drgn_test_rbtree_with_inconsistent_parents.rb_node;
rb_set_parent(node->rb_left ? node->rb_left : node->rb_right,
&drgn_test_empty_rb_node);
// Bad red-black tree with red node with red child.
for (i = 0; i < ARRAY_SIZE(drgn_test_rb_entries_with_red_violation); i++)
drgn_test_rb_entries_with_red_violation[i].value = i;
drgn_test_rbtree_insert(&drgn_test_rbtree_with_red_violation,
&drgn_test_rb_entries_with_red_violation[0]);
rb_link_node(&drgn_test_rb_entries_with_red_violation[1].node,
&drgn_test_rb_entries_with_red_violation[0].node,
&drgn_test_rb_entries_with_red_violation[0].node.rb_right);
rb_link_node(&drgn_test_rb_entries_with_red_violation[2].node,
&drgn_test_rb_entries_with_red_violation[1].node,
&drgn_test_rb_entries_with_red_violation[1].node.rb_right);
// Bad red-black tree with unequal number of black nodes in paths from
// root to leaves.
for (i = 0; i < ARRAY_SIZE(drgn_test_rb_entries_with_black_violation); i++)
drgn_test_rb_entries_with_black_violation[i].value = i;
drgn_test_rbtree_insert(&drgn_test_rbtree_with_black_violation,
&drgn_test_rb_entries_with_black_violation[0]);
rb_link_node(&drgn_test_rb_entries_with_black_violation[1].node,
&drgn_test_rb_entries_with_black_violation[0].node,
&drgn_test_rb_entries_with_black_violation[0].node.rb_right);
drgn_test_rb_entries_with_black_violation[1].node.__rb_parent_color |= RB_BLACK;
}
// slab