summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorrofl0r <rofl0r@users.noreply.github.com>2021-03-16 21:33:30 +0000
committerrofl0r <rofl0r@users.noreply.github.com>2021-03-28 20:33:17 +0100
commit64badd6b373b243e6f431c8adcff92e58925a8b9 (patch)
tree71f893b2805f3ed1eb2f45156c5438e05729a28a
parent48860bbe26867cd0ad880b6358cfcba8d142c267 (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.c12
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;
}