Make memory management uniform: _everything_ now goes through the
[u/mdw/putty] / tree234.c
index 1fedd76..7830aeb 100644 (file)
--- a/tree234.c
+++ b/tree234.c
@@ -6,10 +6,12 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include "puttymem.h"
+
 #include "tree234.h"
 
-#define mknew(typ) ( (typ *) malloc (sizeof (typ)) )
-#define sfree free
+#define mknew(typ) ( (typ *) smalloc (sizeof (typ)) )
+/* #define sfree free */
 
 #ifdef TEST
 #define LOG(x) (printf x)
@@ -288,15 +290,15 @@ void *find234(tree234 *t, void *e, cmpfn234 cmp) {
 
     n = t->root;
     while (n) {
-       if ( (c = t->cmp(e, n->elems[0])) < 0)
+       if ( (c = cmp(e, n->elems[0])) < 0)
            n = n->kids[0];
        else if (c == 0)
            return n->elems[0];
-       else if (n->elems[1] == NULL || (c = t->cmp(e, n->elems[1])) < 0)
+       else if (n->elems[1] == NULL || (c = cmp(e, n->elems[1])) < 0)
            n = n->kids[1];
        else if (c == 0)
            return n->elems[1];
-       else if (n->elems[2] == NULL || (c = t->cmp(e, n->elems[2])) < 0)
+       else if (n->elems[2] == NULL || (c = cmp(e, n->elems[2])) < 0)
            n = n->kids[2];
        else if (c == 0)
            return n->elems[2];
@@ -316,7 +318,7 @@ void *find234(tree234 *t, void *e, cmpfn234 cmp) {
  * Delete an element e in a 2-3-4 tree. Does not free the element,
  * merely removes all links to it from the tree nodes.
  */
-void *del234(tree234 *t, void *e) {
+void del234(tree234 *t, void *e) {
     node234 *n;
     int ei = -1;
 
@@ -373,6 +375,7 @@ void *del234(tree234 *t, void *e) {
                    sub->kids[1] = sub->kids[0];
                    sub->elems[0] = n->elems[ki-1];
                    sub->kids[0] = sib->kids[lastelem+1];
+                   if (sub->kids[0]) sub->kids[0]->parent = sub;
                    n->elems[ki-1] = sib->elems[lastelem];
                    sib->kids[lastelem+1] = NULL;
                    sib->elems[lastelem] = NULL;
@@ -392,6 +395,7 @@ void *del234(tree234 *t, void *e) {
                    int j;
                    sub->elems[1] = n->elems[ki];
                    sub->kids[2] = sib->kids[0];
+                   if (sub->kids[2]) sub->kids[2]->parent = sub;
                    n->elems[ki] = sib->elems[0];
                    sib->kids[0] = sib->kids[1];
                    for (j = 0; j < 2 && sib->elems[j+1]; j++) {
@@ -434,8 +438,10 @@ void *del234(tree234 *t, void *e) {
                    sub->kids[2] = sub->kids[0];
                    sub->elems[1] = n->elems[ki];
                    sub->kids[1] = sib->kids[1];
+                   if (sub->kids[1]) sub->kids[1]->parent = sub;
                    sub->elems[0] = sib->elems[0];
                    sub->kids[0] = sib->kids[0];
+                   if (sub->kids[0]) sub->kids[0]->parent = sub;
 
                    sfree(sib);
 
@@ -449,7 +455,7 @@ void *del234(tree234 *t, void *e) {
                    }
                    n->kids[j] = NULL;
                    if (j < 3) n->elems[j] = NULL;
-                   LOG(("  case 3b\n"));
+                   LOG(("  case 3b ki=%d\n", ki));
 
                    if (!n->elems[0]) {
                        /*
@@ -494,7 +500,7 @@ void *del234(tree234 *t, void *e) {
             */
            int i;
            LOG(("  case 1\n"));
-           for (i = ei; i < 3 && n->elems[i+1]; i++)
+           for (i = ei; i < 2 && n->elems[i+1]; i++)
                n->elems[i] = n->elems[i+1];
            n->elems[i] = NULL;
            return;                    /* finished! */
@@ -550,8 +556,10 @@ void *del234(tree234 *t, void *e) {
            LOG(("  case 2c\n"));
            a->elems[1] = n->elems[ei];
            a->kids[2] = b->kids[0];
+           if (a->kids[2]) a->kids[2]->parent = a;
            a->elems[2] = b->elems[0];
            a->kids[3] = b->kids[1];
+           if (a->kids[3]) a->kids[3]->parent = a;
            sfree(b);
            /*
             * That's built the big node in a, and destroyed b. Now
@@ -563,6 +571,17 @@ void *del234(tree234 *t, void *e) {
            }
            n->elems[j] = NULL;
            n->kids[j+1] = NULL;
+            /*
+             * It's possible, in this case, that we've just removed
+             * the only element in the root of the tree. If so,
+             * shift the root.
+             */
+            if (n->elems[0] == NULL) {
+                LOG(("  shifting root!\n"));
+                t->root = a;
+                a->parent = NULL;
+                sfree(n);
+            }
            /*
             * Now go round the deletion process again, with n
             * pointing at the new big node and e still the same.
@@ -599,9 +618,9 @@ void *next234(enum234 *e) {
        return n->elems[0];
     }
 
-    if (pos == 0 && n->elems[1]) {
-       e->posn = 1;
-       return n->elems[1];
+    if (pos < 2 && n->elems[pos+1]) {
+       e->posn = pos+1;
+       return n->elems[e->posn];
     }
 
     do {
@@ -621,75 +640,307 @@ void *next234(enum234 *e) {
 
 #ifdef TEST
 
-int pnode(node234 *n, int level) {
-    printf("%*s%p\n", level*4, "", n);
-    if (n->kids[0]) pnode(n->kids[0], level+1);
-    if (n->elems[0]) printf("%*s\"%s\"\n", level*4+4, "", n->elems[0]);
-    if (n->kids[1]) pnode(n->kids[1], level+1);
-    if (n->elems[1]) printf("%*s\"%s\"\n", level*4+4, "", n->elems[1]);
-    if (n->kids[2]) pnode(n->kids[2], level+1);
-    if (n->elems[2]) printf("%*s\"%s\"\n", level*4+4, "", n->elems[2]);
-    if (n->kids[3]) pnode(n->kids[3], level+1);
+/*
+ * Test code for the 2-3-4 tree. This code maintains an alternative
+ * representation of the data in the tree, in an array (using the
+ * obvious and slow insert and delete functions). After each tree
+ * operation, the verify() function is called, which ensures all
+ * the tree properties are preserved (node->child->parent always
+ * equals node; number of kids == 0 or number of elements + 1;
+ * ordering property between elements of a node and elements of its
+ * children is preserved; tree has the same depth everywhere; every
+ * node has at least one element) and also ensures the list
+ * represented by the tree is the same list it should be. (This
+ * last check also verifies the ordering properties, because the
+ * `same list it should be' is by definition correctly ordered. It
+ * also ensures all nodes are distinct, because the enum functions
+ * would get caught in a loop if not.)
+ */
+
+#include <stdarg.h>
+
+/*
+ * Error reporting function.
+ */
+void error(char *fmt, ...) {
+    va_list ap;
+    printf("ERROR: ");
+    va_start(ap, fmt);
+    vfprintf(stdout, fmt, ap);
+    va_end(ap);
+    printf("\n");
 }
-int ptree(tree234 *t) {
-    if (t->root)
-       pnode(t->root, 0);
-    else
-       printf("empty tree\n");
+
+/* The array representation of the data. */
+void **array;
+int arraylen, arraysize;
+cmpfn234 cmp;
+
+/* The tree representation of the same data. */
+tree234 *tree;
+
+typedef struct {
+    int treedepth;
+    int elemcount;
+} chkctx;
+
+void chknode(chkctx *ctx, int level, node234 *node,
+             void *lowbound, void *highbound) {
+    int nkids, nelems;
+    int i;
+
+    /* Count the non-NULL kids. */
+    for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++);
+    /* Ensure no kids beyond the first NULL are non-NULL. */
+    for (i = nkids; i < 4; i++)
+        if (node->kids[i]) {
+            error("node %p: nkids=%d but kids[%d] non-NULL",
+                   node, nkids, i);
+        }
+
+    /* Count the non-NULL elements. */
+    for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++);
+    /* Ensure no elements beyond the first NULL are non-NULL. */
+    for (i = nelems; i < 3; i++)
+        if (node->elems[i]) {
+            error("node %p: nelems=%d but elems[%d] non-NULL",
+                   node, nelems, i);
+        }
+
+    if (nkids == 0) {
+        /*
+         * If nkids==0, this is a leaf node; verify that the tree
+         * depth is the same everywhere.
+         */
+        if (ctx->treedepth < 0)
+            ctx->treedepth = level;    /* we didn't know the depth yet */
+        else if (ctx->treedepth != level)
+            error("node %p: leaf at depth %d, previously seen depth %d",
+                   node, level, ctx->treedepth);
+    } else {
+        /*
+         * If nkids != 0, then it should be nelems+1, unless nelems
+         * is 0 in which case nkids should also be 0 (and so we
+         * shouldn't be in this condition at all).
+         */
+        int shouldkids = (nelems ? nelems+1 : 0);
+        if (nkids != shouldkids) {
+            error("node %p: %d elems should mean %d kids but has %d",
+                   node, nelems, shouldkids, nkids);
+        }
+    }
+
+    /*
+     * nelems should be at least 1.
+     */
+    if (nelems == 0) {
+        error("node %p: no elems", node, nkids);
+    }
+
+    /*
+     * Add nelems to the running element count of the whole tree
+     * (to ensure the enum234 routines see them all).
+     */
+    ctx->elemcount += nelems;
+
+    /*
+     * Check ordering property: all elements should be strictly >
+     * lowbound, strictly < highbound, and strictly < each other in
+     * sequence. (lowbound and highbound are NULL at edges of tree
+     * - both NULL at root node - and NULL is considered to be <
+     * everything and > everything. IYSWIM.)
+     */
+    for (i = -1; i < nelems; i++) {
+        void *lower = (i == -1 ? lowbound : node->elems[i]);
+        void *higher = (i+1 == nelems ? highbound : node->elems[i+1]);
+        if (lower && higher && cmp(lower, higher) >= 0) {
+            error("node %p: kid comparison [%d=%s,%d=%s] failed",
+                   node, i, lower, i+1, higher);
+        }
+    }
+
+    /*
+     * Check parent pointers: all non-NULL kids should have a
+     * parent pointer coming back to this node.
+     */
+    for (i = 0; i < nkids; i++)
+        if (node->kids[i]->parent != node) {
+            error("node %p kid %d: parent ptr is %p not %p",
+                   node, i, node->kids[i]->parent, node);
+        }
+
+
+    /*
+     * Now (finally!) recurse into subtrees.
+     */
+    for (i = 0; i < nkids; i++) {
+        void *lower = (i == 0 ? lowbound : node->elems[i-1]);
+        void *higher = (i >= nelems ? highbound : node->elems[i]);
+        chknode(ctx, level+1, node->kids[i], lower, higher);
+    }
 }
 
-int cmp(void *av, void *bv) {
-    char *a = (char *)av;
-    char *b = (char *)bv;
+void verify(void) {
+    chkctx ctx;
+    enum234 e;
+    int i;
+    void *p;
+
+    ctx.treedepth = -1;                /* depth unknown yet */
+    ctx.elemcount = 0;                 /* no elements seen yet */
+    /*
+     * Verify validity of tree properties.
+     */
+    if (tree->root)
+        chknode(&ctx, 0, tree->root, NULL, NULL);
+    printf("tree depth: %d\n", ctx.treedepth);
+    /*
+     * Enumerate the tree and ensure it matches up to the array.
+     */
+    for (i = 0, p = first234(tree, &e);
+         p;
+         i++, p = next234(&e)) {
+        if (i >= arraylen)
+            error("tree contains more than %d elements", arraylen);
+        if (array[i] != p)
+            error("enum at position %d: array says %s, tree says %s",
+                   i, array[i], p);
+    }
+    if (i != ctx.elemcount) {
+        error("tree really contains %d elements, enum gave %d",
+               i, ctx.elemcount);
+    }
+    if (i < arraylen) {
+        error("enum gave only %d elements, array has %d", i, arraylen);
+    }
+}
+
+void addtest(void *elem) {
+    int i, j;
+    void *retval, *realret;
+
+    if (arraysize < arraylen+1) {
+        arraysize = arraylen+1+256;
+        array = (array == NULL ? smalloc(arraysize*sizeof(*array)) :
+                 srealloc(array, arraysize*sizeof(*array)));
+    }
+
+    i = 0;
+    while (i < arraylen && cmp(elem, array[i]) > 0)
+        i++;
+    /* now i points to the first element >= elem */
+    if (i < arraylen && !cmp(elem, array[i]))
+        retval = array[i];             /* expect that returned not elem */
+    else {
+        retval = elem;                  /* expect elem returned (success) */
+        for (j = arraylen; j > i; j--)
+            array[j] = array[j-1];
+        array[i] = elem;                /* add elem to array */
+        arraylen++;
+    }
+
+    realret = add234(tree, elem);
+    if (realret != retval) {
+        error("add: retval was %p expected %p", realret, retval);
+    }
+
+    verify();
+}
+
+void deltest(void *elem) {
+    int i;
+
+    i = 0;
+    while (i < arraylen && cmp(elem, array[i]) > 0)
+        i++;
+    /* now i points to the first element >= elem */
+    if (i >= arraylen || cmp(elem, array[i]) != 0)
+        return;                        /* don't do it! */
+    else {
+        while (i < arraylen-1) {
+            array[i] = array[i+1];
+            i++;
+        }
+        arraylen--;                    /* delete elem from array */
+    }
+
+    del234(tree, elem);
+
+    verify();
+}
+
+/* A sample data set and test utility. Designed for pseudo-randomness,
+ * and yet repeatability. */
+
+/*
+ * This random number generator uses the `portable implementation'
+ * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits;
+ * change it if not.
+ */
+int randomnumber(unsigned *seed) {
+    *seed *= 1103515245;
+    *seed += 12345;
+    return ((*seed) / 65536) % 32768;
+}
+
+int mycmp(void *av, void *bv) {
+    char const *a = (char const *)av;
+    char const *b = (char const *)bv;
     return strcmp(a, b);
 }
 
+#define lenof(x) ( sizeof((x)) / sizeof(*(x)) )
+
+char *strings[] = {
+    "a", "ab", "absque", "coram", "de",
+    "palam", "clam", "cum", "ex", "e",
+    "sine", "tenus", "pro", "prae",
+    "banana", "carrot", "cabbage", "broccoli", "onion", "zebra",
+    "penguin", "blancmange", "pangolin", "whale", "hedgehog",
+    "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux",
+    "murfl", "spoo", "breen", "flarn", "octothorpe",
+    "snail", "tiger", "elephant", "octopus", "warthog", "armadillo",
+    "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin",
+    "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper",
+    "wand", "ring", "amulet"
+};
+
+#define NSTR lenof(strings)
+
 int main(void) {
-    tree234 *t = newtree234(cmp);
-    
-    add234(t, "Richard");
-    add234(t, "Of");
-    add234(t, "York");
-    add234(t, "Gave");
-    add234(t, "Battle");
-    add234(t, "In");
-    add234(t, "Vain");
-    add234(t, "Rabbits");
-    add234(t, "On");
-    add234(t, "Your");
-    add234(t, "Garden");
-    add234(t, "Bring");
-    add234(t, "Invisible");
-    add234(t, "Vegetables");
-
-    ptree(t);
-    del234(t, find234(t, "Richard", NULL));
-    ptree(t);
-    del234(t, find234(t, "Of", NULL));
-    ptree(t);
-    del234(t, find234(t, "York", NULL));
-    ptree(t);
-    del234(t, find234(t, "Gave", NULL));
-    ptree(t);
-    del234(t, find234(t, "Battle", NULL));
-    ptree(t);
-    del234(t, find234(t, "In", NULL));
-    ptree(t);
-    del234(t, find234(t, "Vain", NULL));
-    ptree(t);
-    del234(t, find234(t, "Rabbits", NULL));
-    ptree(t);
-    del234(t, find234(t, "On", NULL));
-    ptree(t);
-    del234(t, find234(t, "Your", NULL));
-    ptree(t);
-    del234(t, find234(t, "Garden", NULL));
-    ptree(t);
-    del234(t, find234(t, "Bring", NULL));
-    ptree(t);
-    del234(t, find234(t, "Invisible", NULL));
-    ptree(t);
-    del234(t, find234(t, "Vegetables", NULL));
-    ptree(t);
+    int in[NSTR];
+    int i, j;
+    unsigned seed = 0;
+
+    for (i = 0; i < NSTR; i++) in[i] = 0;
+    array = NULL;
+    arraylen = arraysize = 0;
+    tree = newtree234(mycmp);
+    cmp = mycmp;
+
+    verify();
+    for (i = 0; i < 10000; i++) {
+        j = randomnumber(&seed);
+        j %= NSTR;
+        printf("trial: %d\n", i);
+        if (in[j]) {
+            printf("deleting %s (%d)\n", strings[j], j);
+            deltest(strings[j]);
+            in[j] = 0;
+        } else {
+            printf("adding %s (%d)\n", strings[j], j);
+            addtest(strings[j]);
+            in[j] = 1;
+        }
+    }
+
+    while (arraylen > 0) {
+        j = randomnumber(&seed);
+        j %= arraylen;
+        deltest(array[j]);
+    }
+
+    return 0;
 }
+
 #endif