diff options
author | rofl0r <rofl0r@users.noreply.github.com> | 2021-03-16 21:33:30 +0000 |
---|---|---|
committer | rofl0r <rofl0r@users.noreply.github.com> | 2021-03-28 20:33:17 +0100 |
commit | 64badd6b373b243e6f431c8adcff92e58925a8b9 (patch) | |
tree | 71f893b2805f3ed1eb2f45156c5438e05729a28a | |
parent | 48860bbe26867cd0ad880b6358cfcba8d142c267 (diff) |
htab: prevent filling up of table with tombstones
as pointed out by @craigbarnes [0], using the latest fix for
the tombstone issue, it's possible to provoke a situation
that causes an endless loop when all free slots in the table
are filled up with tombstones and htab_find() is called.
therefore we need to account for those as well when deciding
if there's a need to call resize() so there's never more than
75% of the table used by either dead or live items.
the resize() serves as a rehash which gets rid of all deleted
entries, and it might cause the table size to shrink if
htab_insert() is called after a lot of items have been removed.
[0]: https://github.com/rofl0r/htab/issues/1#issuecomment-800094442
testcase:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hsearch.h"
#define HTAB_OOM_TEST
#include "hsearch.c"
static char *xstrdup(const char *str)
{
char *dup = strdup(str);
assert(dup);
return dup;
}
void utoa(unsigned number, char* buffer) {
int lentest, len = 0, i, start = 0;
lentest = number;
do {
len++;
lentest /= 10;
} while(lentest);
buffer[start+len] = 0;
do {
i = number % 10;
buffer[start+len - 1] = '0' + i;
number -= i;
len -= 1;
number /= 10;
} while (number);
}
#define TESTSIZE 8
#define KEEP 1
static char* notorious[TESTSIZE];
static void prep() {
srand(0);
char buf[16];
size_t filled = 0;
while(filled < TESTSIZE) {
utoa(rand(), buf);
size_t idx = keyhash(buf) & (TESTSIZE-1);
if(!notorious[idx]) {
notorious[idx] = xstrdup(buf);
++filled;
}
}
}
int main(void)
{
struct htab *h = htab_create(TESTSIZE);
size_t i;
assert(h);
prep();
for(i=0; i<TESTSIZE; ++i) {
char *key = notorious[i];
printf("[%zu] = \"%s\"\n", i, key);
int r = htab_insert(h, key, HTV_N(42));
if(!r == 1) {
printf("element %zu couldn't be inserted\n", i);
break;
}
assert(r == 1);
// Ensure newly inserted entry can be found
assert(htab_find(h, key));
if(i >= KEEP) htab_delete(h, key);
}
htab_find(h, "looooop");
return 0;
}
-rw-r--r-- | src/hsearch.c | 12 |
1 files changed, 10 insertions, 2 deletions
diff --git a/src/hsearch.c b/src/hsearch.c index 70d757a..be0434c 100644 --- a/src/hsearch.c +++ b/src/hsearch.c @@ -49,6 +49,7 @@ struct htab { size_t mask; size_t used; size_t seed; + size_t dead; }; #define MINSIZE 8 @@ -171,27 +172,34 @@ int htab_delete(struct htab *htab, const char* key) e->item.key = 0; e->hash = 0xdeadc0de; --htab->used; + ++htab->dead; return 1; } int htab_insert(struct htab *htab, char* key, htab_value value) { - size_t hash = keyhash(key, htab->seed); + size_t hash = keyhash(key, htab->seed), oh; struct elem *e = lookup(htab, key, hash, 0xdeadc0de); if(e->item.key) { /* it's not allowed to overwrite existing data */ return 0; } + oh = e->hash; /* save old hash in case it's tombstone marker */ e->item.key = key; e->item.data = value; e->hash = hash; - if (++htab->used > htab->mask - htab->mask/4) { + if (++htab->used + htab->dead > htab->mask - htab->mask/4) { if (!resize(htab, 2*htab->used)) { htab->used--; e->item.key = 0; + e->hash = oh; return 0; } + htab->dead = 0; + } else if (oh == 0xdeadc0de) { + /* re-used tomb */ + --htab->dead; } return 1; } |