mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-23 09:43:06 +00:00
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:
parent
9ae36fd12e
commit
243abf59e5
@ -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)
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user