From 64badd6b373b243e6f431c8adcff92e58925a8b9 Mon Sep 17 00:00:00 2001 From: rofl0r Date: Tue, 16 Mar 2021 21:33:30 +0000 Subject: [PATCH] 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 #include #include #include #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= KEEP) htab_delete(h, key); } htab_find(h, "looooop"); return 0; } --- src/hsearch.c | 12 ++++++++++-- 1 file 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; }