gtk2/gtk/gtkrbtree.c
Benjamin Otte e356d59a92 build: Add -Wnull-dereference
Sprinkle various g_assert() around the code where gcc cannot figure out
on its own that a variable is not NULL and too much refactoring would be
needed to make it do that.

Also fix usage of g_assert_nonnull(x) to use g_assert(x) because the
first is not marked as G_GNUC_NORETURN because of course GTester
supports not aborting on aborts.
2020-03-05 08:14:37 +01:00

801 lines
16 KiB
C

/* gtkrbtree.c
* Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkrbtreeprivate.h"
#include "gtkdebug.h"
/* Define the following to print adds and removals to stdout.
* The format of the printout will be suitable for addition as a new test to
* testsuite/gtk/rbtree-crash.c
* by just grepping the printouts from the relevant rbtree.
*
* This is meant to be a trivial way to add rbtree tests to the testsuite.
*/
#undef DUMP_MODIFICATION
typedef struct _GtkRbNode GtkRbNode;
struct _GtkRbTree
{
guint ref_count;
gsize element_size;
gsize augment_size;
GtkRbTreeAugmentFunc augment_func;
GDestroyNotify clear_func;
GDestroyNotify clear_augment_func;
GtkRbNode *root;
};
struct _GtkRbNode
{
guint red :1;
guint dirty :1;
GtkRbNode *left;
GtkRbNode *right;
/* The difference between tree and parent here is that we OR the tree with 1 and because
* pointers are always multiples of 4, we can know if we've stored a parent or the tree here */
union {
gpointer parent_or_tree;
GtkRbNode *parent;
GtkRbTree *tree;
};
};
#define NODE_FROM_POINTER(ptr) ((GtkRbNode *) (((guchar *) (ptr)) - sizeof (GtkRbNode)))
#define NODE_TO_POINTER(node) ((node) ? ((gpointer) (((guchar *) (node)) + sizeof (GtkRbNode))) : NULL)
#define NODE_TO_AUG_POINTER(tree, node) ((gpointer) ((node) ? (((guchar *) (node)) + sizeof (GtkRbNode) + (tree)->element_size) : NULL))
static inline gboolean
is_root (GtkRbNode *node)
{
return GPOINTER_TO_SIZE (node->parent_or_tree) & 1 ? TRUE : FALSE;
}
static inline GtkRbNode *
parent (GtkRbNode *node)
{
if (is_root (node))
return NULL;
else
return node->parent;
}
static GtkRbTree *
tree (GtkRbNode *node)
{
while (!is_root (node))
node = parent (node);
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (node->tree) & ~1);
}
static void
set_parent (GtkRbTree *tree,
GtkRbNode *node,
GtkRbNode *new_parent)
{
if (new_parent != NULL)
{
node->parent = new_parent;
}
else
{
node->tree = GSIZE_TO_POINTER (GPOINTER_TO_SIZE (tree) | 1);
tree->root = node;
}
}
static inline gsize
gtk_rb_node_get_size (GtkRbTree *tree)
{
return sizeof (GtkRbNode) + tree->element_size + tree->augment_size;
}
static GtkRbNode *
gtk_rb_node_new (GtkRbTree *tree)
{
GtkRbNode *result;
result = g_slice_alloc0 (gtk_rb_node_get_size (tree));
result->red = TRUE;
result->dirty = TRUE;
return result;
}
static void
gtk_rb_node_free (GtkRbTree *tree,
GtkRbNode *node)
{
if (tree->clear_func)
tree->clear_func (NODE_TO_POINTER (node));
if (tree->clear_augment_func)
tree->clear_augment_func (NODE_TO_AUG_POINTER (tree, node));
g_slice_free1 (gtk_rb_node_get_size (tree), node);
}
static void
gtk_rb_node_free_deep (GtkRbTree *tree,
GtkRbNode *node)
{
GtkRbNode *right = node->right;
if (node->left)
gtk_rb_node_free_deep (tree, node->left);
gtk_rb_node_free (tree, node);
if (right)
gtk_rb_node_free_deep (tree, right);
}
static void
gtk_rb_node_mark_dirty (GtkRbNode *node,
gboolean mark_parent)
{
if (node->dirty)
return;
node->dirty = TRUE;
if (mark_parent && parent (node))
gtk_rb_node_mark_dirty (parent (node), TRUE);
}
static void
gtk_rb_node_clean (GtkRbTree *tree,
GtkRbNode *node)
{
if (!node->dirty)
return;
node->dirty = FALSE;
if (tree->augment_func)
tree->augment_func (tree,
NODE_TO_AUG_POINTER (tree, node),
NODE_TO_POINTER (node),
NODE_TO_POINTER (node->left),
NODE_TO_POINTER (node->right));
}
static GtkRbNode *
gtk_rb_node_get_first (GtkRbNode *node)
{
while (node->left)
node = node->left;
return node;
}
static GtkRbNode *
gtk_rb_node_get_last (GtkRbNode *node)
{
while (node->right)
node = node->right;
return node;
}
static GtkRbNode *
gtk_rb_node_get_previous (GtkRbNode *node)
{
GtkRbNode *p;
if (node->left)
return gtk_rb_node_get_last (node->left);
for (p = parent (node); p != NULL; p = parent (node))
{
if (p->right == node)
return p;
node = p;
}
return NULL;
}
static GtkRbNode *
gtk_rb_node_get_next (GtkRbNode *node)
{
GtkRbNode *p;
if (node->right)
return gtk_rb_node_get_first (node->right);
for (p = parent (node); p != NULL; p = parent (node))
{
if (p->left == node)
return p;
node = p;
}
return NULL;
}
#ifdef DUMP_MODIFICATION
static guint
position (GtkRbTree *tree,
GtkRbNode *node)
{
GtkRbNode *n;
guint i;
i = 0;
for (n = gtk_rb_node_get_first (tree->root);
n != node;
n = gtk_rb_node_get_next (n))
i++;
return i;
}
#endif
static void
gtk_rb_node_rotate_left (GtkRbTree *tree,
GtkRbNode *node)
{
GtkRbNode *right, *p;
right = node->right;
p = parent (node);
node->right = right->left;
if (right->left)
set_parent (tree, right->left, node);
set_parent (tree, right, p);
if (p)
{
if (node == p->left)
p->left = right;
else
p->right = right;
}
right->left = node;
set_parent (tree, node, right);
gtk_rb_node_mark_dirty (node, FALSE);
gtk_rb_node_mark_dirty (right, FALSE);
}
static void
gtk_rb_node_rotate_right (GtkRbTree *tree,
GtkRbNode *node)
{
GtkRbNode *left, *p;
left = node->left;
p = parent (node);
node->left = left->right;
if (left->right)
set_parent (tree, left->right, node);
set_parent (tree, left, p);
if (p)
{
if (node == p->right)
p->right = left;
else
p->left = left;
}
/* link node and left */
left->right = node;
set_parent (tree, node, left);
gtk_rb_node_mark_dirty (node, FALSE);
gtk_rb_node_mark_dirty (left, FALSE);
}
static gboolean
is_red (GtkRbNode *node_or_null)
{
if (node_or_null == NULL)
return FALSE;
else
return node_or_null->red;
}
static inline gboolean
is_black (GtkRbNode *node_or_null)
{
return !is_red (node_or_null);
}
static void
set_black (GtkRbNode *node_or_null)
{
if (node_or_null == NULL)
return;
node_or_null->red = FALSE;
}
static void
set_red (GtkRbNode *node_or_null)
{
if (node_or_null == NULL)
return;
node_or_null->red = TRUE;
}
static void
gtk_rb_tree_insert_fixup (GtkRbTree *tree,
GtkRbNode *node)
{
GtkRbNode *p;
/* check Red-Black properties */
for (p = parent (node);
p && is_red (p);
p = parent (node))
{
GtkRbNode *pp = parent (p);
/* we have a violation */
g_assert (pp);
if (p == pp->left)
{
GtkRbNode *uncle = pp->right;
if (is_red (uncle))
{
/* uncle is red */
set_black (p);
set_black (uncle);
set_red (pp);
node = pp;
}
else
{
/* uncle is black */
if (node == p->right)
{
/* make node a left child */
gtk_rb_node_rotate_left (tree, p);
p = node;
node = p->left;
}
/* recolor and rotate */
set_black (p);
set_red (pp);
gtk_rb_node_rotate_right (tree, pp);
}
}
else
{
/* mirror image of above code */
GtkRbNode *uncle = pp->left;
if (is_red (uncle))
{
/* uncle is red */
set_black (p);
set_black (uncle);
set_red (pp);
node = pp;
}
else
{
/* uncle is black */
if (node == p->left)
{
gtk_rb_node_rotate_right (tree, p);
p = node;
node = p->right;
}
set_black (p);
set_red (pp);
gtk_rb_node_rotate_left (tree, pp);
}
}
}
set_black (tree->root);
}
static void
gtk_rb_tree_remove_node_fixup (GtkRbTree *tree,
GtkRbNode *node,
GtkRbNode *p)
{
while (node != tree->root && is_black (node))
{
if (node == p->left)
{
GtkRbNode *w = p->right;
if (is_red (w))
{
set_black (w);
set_red (p);
gtk_rb_node_rotate_left (tree, p);
w = p->right;
}
g_assert (w);
if (is_black (w->left) && is_black (w->right))
{
set_red (w);
node = p;
}
else
{
if (is_black (w->right))
{
set_black (w->left);
set_red (w);
gtk_rb_node_rotate_right (tree, w);
w = p->right;
}
w->red = p->red;
set_black (p);
set_black (w->right);
gtk_rb_node_rotate_left (tree, p);
node = tree->root;
}
}
else
{
GtkRbNode *w = p->left;
if (is_red (w))
{
set_black (w);
set_red (p);
gtk_rb_node_rotate_right (tree, p);
w = p->left;
}
g_assert (w);
if (is_black (w->right) && is_black (w->left))
{
set_red (w);
node = p;
}
else
{
if (is_black (w->left))
{
set_black (w->right);
set_red (w);
gtk_rb_node_rotate_left (tree, w);
w = p->left;
}
w->red = p->red;
set_black (p);
set_black (w->left);
gtk_rb_node_rotate_right (tree, p);
node = tree->root;
}
}
p = parent (node);
}
set_black (node);
}
GtkRbTree *
gtk_rb_tree_new_for_size (gsize element_size,
gsize augment_size,
GtkRbTreeAugmentFunc augment_func,
GDestroyNotify clear_func,
GDestroyNotify clear_augment_func)
{
GtkRbTree *tree;
tree = g_slice_new0 (GtkRbTree);
tree->ref_count = 1;
tree->element_size = element_size;
tree->augment_size = augment_size;
tree->augment_func = augment_func;
tree->clear_func = clear_func;
tree->clear_augment_func = clear_augment_func;
return tree;
}
GtkRbTree *
gtk_rb_tree_ref (GtkRbTree *tree)
{
tree->ref_count++;
return tree;
}
void
gtk_rb_tree_unref (GtkRbTree *tree)
{
tree->ref_count--;
if (tree->ref_count > 0)
return;
if (tree->root)
gtk_rb_node_free_deep (tree, tree->root);
g_slice_free (GtkRbTree, tree);
}
gpointer
gtk_rb_tree_get_first (GtkRbTree *tree)
{
if (tree->root == NULL)
return NULL;
return NODE_TO_POINTER (gtk_rb_node_get_first (tree->root));
}
gpointer
gtk_rb_tree_get_last (GtkRbTree *tree)
{
if (tree->root == NULL)
return NULL;
return NODE_TO_POINTER (gtk_rb_node_get_last (tree->root));
}
gpointer
gtk_rb_tree_node_get_previous (gpointer node)
{
return NODE_TO_POINTER (gtk_rb_node_get_previous (NODE_FROM_POINTER (node)));
}
gpointer
gtk_rb_tree_node_get_next (gpointer node)
{
return NODE_TO_POINTER (gtk_rb_node_get_next (NODE_FROM_POINTER (node)));
}
gpointer
gtk_rb_tree_get_root (GtkRbTree *tree)
{
return NODE_TO_POINTER (tree->root);
}
gpointer
gtk_rb_tree_node_get_parent (gpointer node)
{
return NODE_TO_POINTER (parent (NODE_FROM_POINTER (node)));
}
gpointer
gtk_rb_tree_node_get_left (gpointer node)
{
return NODE_TO_POINTER (NODE_FROM_POINTER (node)->left);
}
gpointer
gtk_rb_tree_node_get_right (gpointer node)
{
return NODE_TO_POINTER (NODE_FROM_POINTER (node)->right);
}
gpointer
gtk_rb_tree_get_augment (GtkRbTree *tree,
gpointer node)
{
GtkRbNode *rbnode = NODE_FROM_POINTER (node);
gtk_rb_node_clean (tree, rbnode);
return NODE_TO_AUG_POINTER (tree, rbnode);
}
GtkRbTree *
gtk_rb_tree_node_get_tree (gpointer node)
{
return tree (NODE_FROM_POINTER (node));
}
void
gtk_rb_tree_node_mark_dirty (gpointer node)
{
gtk_rb_node_mark_dirty (NODE_FROM_POINTER (node), TRUE);
}
gpointer
gtk_rb_tree_insert_before (GtkRbTree *tree,
gpointer node)
{
GtkRbNode *result;
if (tree->root == NULL)
{
#ifdef DUMP_MODIFICATION
g_print ("add (tree, 0); /* 0x%p */\n", tree);
#endif /* DUMP_MODIFICATION */
g_assert (node == NULL);
result = gtk_rb_node_new (tree);
tree->root = result;
}
else if (node == NULL)
{
return gtk_rb_tree_insert_after (tree, gtk_rb_tree_get_last (tree));
}
else
{
GtkRbNode *current = NODE_FROM_POINTER (node);
#ifdef DUMP_MODIFICATION
g_print ("add (tree, %u); /* 0x%p */\n", position (tree, current), tree);
#endif /* DUMP_MODIFICATION */
/* setup new node */
result = gtk_rb_node_new (tree);
if (current->left)
{
current = gtk_rb_node_get_last (current->left);
current->right = result;
}
else
{
current->left = result;
}
set_parent (tree, result, current);
gtk_rb_node_mark_dirty (current, TRUE);
}
gtk_rb_tree_insert_fixup (tree, result);
return NODE_TO_POINTER (result);
}
gpointer
gtk_rb_tree_insert_after (GtkRbTree *tree,
gpointer node)
{
GtkRbNode *current, *result;
if (node == NULL)
return gtk_rb_tree_insert_before (tree, gtk_rb_tree_get_first (tree));
current = NODE_FROM_POINTER (node);
#ifdef DUMP_MODIFICATION
g_print ("add (tree, %u); /* 0x%p */\n", position (tree, current) + 1, tree);
#endif /* DUMP_MODIFICATION */
/* setup new node */
result = gtk_rb_node_new (tree);
if (current->right)
{
current = gtk_rb_node_get_first (current->right);
current->left = result;
}
else
{
current->right = result;
}
set_parent (tree, result, current);
gtk_rb_node_mark_dirty (current, TRUE);
gtk_rb_tree_insert_fixup (tree, result);
return NODE_TO_POINTER (result);
}
void
gtk_rb_tree_remove (GtkRbTree *tree,
gpointer node)
{
GtkRbNode *x, *y, *p, *real_node;
real_node = NODE_FROM_POINTER (node);
#ifdef DUMP_MODIFICATION
g_print ("delete (tree, %u); /* 0x%p */\n", position (tree, real_node), tree);
#endif /* DUMP_MODIFICATION */
y = real_node;
if (y->left && y->right)
{
y = y->right;
while (y->left)
y = y->left;
}
/* x is y's only child, or nil */
if (y->left)
x = y->left;
else
x = y->right;
/* remove y from the parent chain */
p = parent (y);
if (x != NULL)
set_parent (tree, x, p);
if (p)
{
if (y == p->left)
p->left = x;
else
p->right = x;
gtk_rb_node_mark_dirty (p, TRUE);
}
else
{
if (x == NULL)
tree->root = NULL;
}
/* We need to clean up the validity of the tree.
*/
if (is_black (y))
gtk_rb_tree_remove_node_fixup (tree, x, p);
if (y != real_node)
{
/* Move the node over */
if (is_red (real_node) != is_red (y))
y->red = !y->red;
y->left = real_node->left;
if (y->left)
set_parent (tree, y->left, y);
y->right = real_node->right;
if (y->right)
set_parent (tree, y->right, y);
p = parent (real_node);
set_parent (tree, y, p);
if (p)
{
if (p->left == real_node)
p->left = y;
else
p->right = y;
gtk_rb_node_mark_dirty (p, TRUE);
}
gtk_rb_node_mark_dirty (y, TRUE);
}
gtk_rb_node_free (tree, real_node);
}
void
gtk_rb_tree_remove_all (GtkRbTree *tree)
{
#ifdef DUMP_MODIFICATION
g_print ("delete_all (tree); /* 0x%p */\n", tree);
#endif /* DUMP_MODIFICATION */
if (tree->root)
gtk_rb_node_free_deep (tree, tree->root);
tree->root = NULL;
}