1 /**
2 * @file
3 * Hash Table data structure
4 *
5 * @authors
6 * Copyright (C) 1996-2009 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2017-2020 Richard Russon <rich@flatcap.org>
8 *
9 * @copyright
10 * This program is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 2 of the License, or (at your option) any later
13 * version.
14 *
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 * details.
19 *
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 /**
25 * @page mutt_hash Hash Table data structure
26 *
27 * Hash Table data structure.
28 */
29
30 #include "config.h"
31 #include <ctype.h>
32 #include <stdbool.h>
33 #include "hash.h"
34 #include "memory.h"
35 #include "string2.h"
36
37 #define SOME_PRIME 149711
38
39 /**
40 * gen_string_hash - Generate a hash from a string - Implements hash_gen_hash_t - @ingroup hash_gen_hash_api
41 *
42 * @note If the key is NULL or empty, the retval will be 0
43 */
gen_string_hash(union HashKey key,size_t num_elems)44 static size_t gen_string_hash(union HashKey key, size_t num_elems)
45 {
46 size_t hash = 0;
47 const unsigned char *s = (const unsigned char *) key.strkey;
48 if (!s)
49 return 0;
50
51 while (*s != '\0')
52 hash += ((hash << 7) + *s++);
53 hash = (hash * SOME_PRIME) % num_elems;
54
55 return hash;
56 }
57
58 /**
59 * cmp_string_key - Compare two string keys - Implements hash_cmp_key_t - @ingroup hash_cmp_key_api
60 */
cmp_string_key(union HashKey a,union HashKey b)61 static int cmp_string_key(union HashKey a, union HashKey b)
62 {
63 return mutt_str_cmp(a.strkey, b.strkey);
64 }
65
66 /**
67 * gen_case_string_hash - Generate a hash from a string (ignore the case) - Implements hash_gen_hash_t - @ingroup hash_gen_hash_api
68 */
gen_case_string_hash(union HashKey key,size_t num_elems)69 static size_t gen_case_string_hash(union HashKey key, size_t num_elems)
70 {
71 size_t hash = 0;
72 const unsigned char *s = (const unsigned char *) key.strkey;
73
74 while (*s != '\0')
75 hash += ((hash << 7) + tolower(*s++));
76 hash = (hash * SOME_PRIME) % num_elems;
77
78 return hash;
79 }
80
81 /**
82 * cmp_case_string_key - Compare two string keys (ignore case) - Implements hash_cmp_key_t - @ingroup hash_cmp_key_api
83 */
cmp_case_string_key(union HashKey a,union HashKey b)84 static int cmp_case_string_key(union HashKey a, union HashKey b)
85 {
86 return mutt_istr_cmp(a.strkey, b.strkey);
87 }
88
89 /**
90 * gen_int_hash - Generate a hash from an integer - Implements hash_gen_hash_t - @ingroup hash_gen_hash_api
91 */
gen_int_hash(union HashKey key,size_t num_elems)92 static size_t gen_int_hash(union HashKey key, size_t num_elems)
93 {
94 return (key.intkey % num_elems);
95 }
96
97 /**
98 * cmp_int_key - Compare two integer keys - Implements hash_cmp_key_t - @ingroup hash_cmp_key_api
99 */
cmp_int_key(union HashKey a,union HashKey b)100 static int cmp_int_key(union HashKey a, union HashKey b)
101 {
102 if (a.intkey == b.intkey)
103 return 0;
104 if (a.intkey < b.intkey)
105 return -1;
106 return 1;
107 }
108
109 /**
110 * hash_new - Create a new Hash Table
111 * @param num_elems Number of elements it should contain
112 * @retval ptr New Hash Table
113 *
114 * The Hash Table can contain more elements than num_elems, but they will be
115 * chained together.
116 */
hash_new(size_t num_elems)117 static struct HashTable *hash_new(size_t num_elems)
118 {
119 struct HashTable *table = mutt_mem_calloc(1, sizeof(struct HashTable));
120 if (num_elems == 0)
121 num_elems = 2;
122 table->num_elems = num_elems;
123 table->table = mutt_mem_calloc(num_elems, sizeof(struct HashElem *));
124 return table;
125 }
126
127 /**
128 * union_hash_insert - Insert into a hash table using a union as a key
129 * @param table Hash Table to update
130 * @param key Key to hash on
131 * @param type Data type
132 * @param data Data to associate with key
133 * @retval ptr Newly inserted HashElem
134 */
union_hash_insert(struct HashTable * table,union HashKey key,int type,void * data)135 static struct HashElem *union_hash_insert(struct HashTable *table,
136 union HashKey key, int type, void *data)
137 {
138 if (!table)
139 return NULL; // LCOV_EXCL_LINE
140
141 struct HashElem *he = mutt_mem_calloc(1, sizeof(struct HashElem));
142 size_t hash = table->gen_hash(key, table->num_elems);
143 he->key = key;
144 he->data = data;
145 he->type = type;
146
147 if (table->allow_dups)
148 {
149 he->next = table->table[hash];
150 table->table[hash] = he;
151 }
152 else
153 {
154 struct HashElem *tmp = NULL, *last = NULL;
155
156 for (tmp = table->table[hash], last = NULL; tmp; last = tmp, tmp = tmp->next)
157 {
158 const int rc = table->cmp_key(tmp->key, key);
159 if (rc == 0)
160 {
161 FREE(&he);
162 return NULL;
163 }
164 if (rc > 0)
165 break;
166 }
167 if (last)
168 last->next = he;
169 else
170 table->table[hash] = he;
171 he->next = tmp;
172 }
173 return he;
174 }
175
176 /**
177 * union_hash_find_elem - Find a HashElem in a Hash Table element using a key
178 * @param table Hash Table to search
179 * @param key Key (either string or integer)
180 * @retval ptr HashElem matching the key
181 */
union_hash_find_elem(const struct HashTable * table,union HashKey key)182 static struct HashElem *union_hash_find_elem(const struct HashTable *table, union HashKey key)
183 {
184 if (!table)
185 return NULL; // LCOV_EXCL_LINE
186
187 size_t hash = table->gen_hash(key, table->num_elems);
188 struct HashElem *he = table->table[hash];
189 for (; he; he = he->next)
190 {
191 if (table->cmp_key(key, he->key) == 0)
192 return he;
193 }
194 return NULL;
195 }
196
197 /**
198 * union_hash_find - Find the HashElem data in a Hash Table element using a key
199 * @param table Hash Table to search
200 * @param key Key (either string or integer)
201 * @retval ptr Data attached to the HashElem matching the key
202 */
union_hash_find(const struct HashTable * table,union HashKey key)203 static void *union_hash_find(const struct HashTable *table, union HashKey key)
204 {
205 if (!table)
206 return NULL; // LCOV_EXCL_LINE
207 struct HashElem *he = union_hash_find_elem(table, key);
208 if (he)
209 return he->data;
210 return NULL;
211 }
212
213 /**
214 * union_hash_delete - Remove an element from a Hash Table
215 * @param table Hash Table to use
216 * @param key Key (either string or integer)
217 * @param data Private data to match (or NULL for any match)
218 */
union_hash_delete(struct HashTable * table,union HashKey key,const void * data)219 static void union_hash_delete(struct HashTable *table, union HashKey key, const void *data)
220 {
221 if (!table)
222 return; // LCOV_EXCL_LINE
223
224 size_t hash = table->gen_hash(key, table->num_elems);
225 struct HashElem *he = table->table[hash];
226 struct HashElem **last = &table->table[hash];
227
228 while (he)
229 {
230 if (((data == he->data) || !data) && (table->cmp_key(he->key, key) == 0))
231 {
232 *last = he->next;
233 if (table->hdata_free)
234 table->hdata_free(he->type, he->data, table->hdata);
235 if (table->strdup_keys)
236 FREE(&he->key.strkey);
237 FREE(&he);
238
239 he = *last;
240 }
241 else
242 {
243 last = &he->next;
244 he = he->next;
245 }
246 }
247 }
248
249 /**
250 * mutt_hash_new - Create a new Hash Table (with string keys)
251 * @param num_elems Number of elements it should contain
252 * @param flags Flags, see #HashFlags
253 * @retval ptr New Hash Table
254 */
mutt_hash_new(size_t num_elems,HashFlags flags)255 struct HashTable *mutt_hash_new(size_t num_elems, HashFlags flags)
256 {
257 struct HashTable *table = hash_new(num_elems);
258 if (flags & MUTT_HASH_STRCASECMP)
259 {
260 table->gen_hash = gen_case_string_hash;
261 table->cmp_key = cmp_case_string_key;
262 }
263 else
264 {
265 table->gen_hash = gen_string_hash;
266 table->cmp_key = cmp_string_key;
267 }
268 if (flags & MUTT_HASH_STRDUP_KEYS)
269 table->strdup_keys = true;
270 if (flags & MUTT_HASH_ALLOW_DUPS)
271 table->allow_dups = true;
272 return table;
273 }
274
275 /**
276 * mutt_hash_int_new - Create a new Hash Table (with integer keys)
277 * @param num_elems Number of elements it should contain
278 * @param flags Flags, see #HashFlags
279 * @retval ptr New Hash Table
280 */
mutt_hash_int_new(size_t num_elems,HashFlags flags)281 struct HashTable *mutt_hash_int_new(size_t num_elems, HashFlags flags)
282 {
283 struct HashTable *table = hash_new(num_elems);
284 table->gen_hash = gen_int_hash;
285 table->cmp_key = cmp_int_key;
286 if (flags & MUTT_HASH_ALLOW_DUPS)
287 table->allow_dups = true;
288 return table;
289 }
290
291 /**
292 * mutt_hash_set_destructor - Set the destructor for a Hash Table
293 * @param table Hash Table to use
294 * @param fn Callback function to free Hash Table's resources
295 * @param fn_data Data to pass to the callback function
296 */
mutt_hash_set_destructor(struct HashTable * table,hash_hdata_free_t fn,intptr_t fn_data)297 void mutt_hash_set_destructor(struct HashTable *table, hash_hdata_free_t fn, intptr_t fn_data)
298 {
299 if (!table)
300 return;
301 table->hdata_free = fn;
302 table->hdata = fn_data;
303 }
304
305 /**
306 * mutt_hash_typed_insert - Insert a string with type info into a Hash Table
307 * @param table Hash Table to use
308 * @param strkey String key
309 * @param type Type to associate with the key
310 * @param data Private data associated with the key
311 * @retval ptr Newly inserted HashElem
312 */
mutt_hash_typed_insert(struct HashTable * table,const char * strkey,int type,void * data)313 struct HashElem *mutt_hash_typed_insert(struct HashTable *table,
314 const char *strkey, int type, void *data)
315 {
316 if (!table || !strkey || (strkey[0] == '\0'))
317 return NULL;
318
319 union HashKey key;
320 key.strkey = table->strdup_keys ? mutt_str_dup(strkey) : strkey;
321 return union_hash_insert(table, key, type, data);
322 }
323
324 /**
325 * mutt_hash_insert - Add a new element to the Hash Table (with string keys)
326 * @param table Hash Table (with string keys)
327 * @param strkey String key
328 * @param data Private data associated with the key
329 * @retval ptr Newly inserted HashElem
330 */
mutt_hash_insert(struct HashTable * table,const char * strkey,void * data)331 struct HashElem *mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
332 {
333 return mutt_hash_typed_insert(table, strkey, -1, data);
334 }
335
336 /**
337 * mutt_hash_int_insert - Add a new element to the Hash Table (with integer keys)
338 * @param table Hash Table (with integer keys)
339 * @param intkey Integer key
340 * @param data Private data associated with the key
341 * @retval ptr Newly inserted HashElem
342 */
mutt_hash_int_insert(struct HashTable * table,unsigned int intkey,void * data)343 struct HashElem *mutt_hash_int_insert(struct HashTable *table, unsigned int intkey, void *data)
344 {
345 if (!table)
346 return NULL;
347 union HashKey key;
348 key.intkey = intkey;
349 return union_hash_insert(table, key, -1, data);
350 }
351
352 /**
353 * mutt_hash_find - Find the HashElem data in a Hash Table element using a key
354 * @param table Hash Table to search
355 * @param strkey String key to search for
356 * @retval ptr Data attached to the HashElem matching the key
357 */
mutt_hash_find(const struct HashTable * table,const char * strkey)358 void *mutt_hash_find(const struct HashTable *table, const char *strkey)
359 {
360 if (!table || !strkey)
361 return NULL;
362 union HashKey key;
363 key.strkey = strkey;
364 return union_hash_find(table, key);
365 }
366
367 /**
368 * mutt_hash_find_elem - Find the HashElem in a Hash Table element using a key
369 * @param table Hash Table to search
370 * @param strkey String key to search for
371 * @retval ptr HashElem matching the key
372 */
mutt_hash_find_elem(const struct HashTable * table,const char * strkey)373 struct HashElem *mutt_hash_find_elem(const struct HashTable *table, const char *strkey)
374 {
375 if (!table || !strkey)
376 return NULL;
377 union HashKey key;
378 key.strkey = strkey;
379 return union_hash_find_elem(table, key);
380 }
381
382 /**
383 * mutt_hash_int_find - Find the HashElem data in a Hash Table element using a key
384 * @param table Hash Table to search
385 * @param intkey Integer key
386 * @retval ptr Data attached to the HashElem matching the key
387 */
mutt_hash_int_find(const struct HashTable * table,unsigned int intkey)388 void *mutt_hash_int_find(const struct HashTable *table, unsigned int intkey)
389 {
390 if (!table)
391 return NULL;
392 union HashKey key;
393 key.intkey = intkey;
394 return union_hash_find(table, key);
395 }
396
397 /**
398 * mutt_hash_find_bucket - Find the HashElem in a Hash Table element using a key
399 * @param table Hash Table to search
400 * @param strkey String key to search for
401 * @retval ptr HashElem matching the key
402 *
403 * Unlike mutt_hash_find_elem(), this will return the first matching entry.
404 */
mutt_hash_find_bucket(const struct HashTable * table,const char * strkey)405 struct HashElem *mutt_hash_find_bucket(const struct HashTable *table, const char *strkey)
406 {
407 if (!table || !strkey)
408 return NULL;
409
410 union HashKey key;
411
412 key.strkey = strkey;
413 size_t hash = table->gen_hash(key, table->num_elems);
414 return table->table[hash];
415 }
416
417 /**
418 * mutt_hash_delete - Remove an element from a Hash Table
419 * @param table Hash Table to use
420 * @param strkey String key to match
421 * @param data Private data to match (or NULL for any match)
422 */
mutt_hash_delete(struct HashTable * table,const char * strkey,const void * data)423 void mutt_hash_delete(struct HashTable *table, const char *strkey, const void *data)
424 {
425 if (!table || !strkey || (strkey[0] == '\0'))
426 return;
427 union HashKey key;
428 // Copy the key because union_hash_delete() may use it after the HashElem is freed.
429 key.strkey = mutt_str_dup(strkey);
430 union_hash_delete(table, key, data);
431 FREE(&key.strkey);
432 }
433
434 /**
435 * mutt_hash_int_delete - Remove an element from a Hash Table
436 * @param table Hash Table to use
437 * @param intkey Integer key to match
438 * @param data Private data to match (or NULL for any match)
439 */
mutt_hash_int_delete(struct HashTable * table,unsigned int intkey,const void * data)440 void mutt_hash_int_delete(struct HashTable *table, unsigned int intkey, const void *data)
441 {
442 if (!table)
443 return;
444 union HashKey key;
445 key.intkey = intkey;
446 union_hash_delete(table, key, data);
447 }
448
449 /**
450 * mutt_hash_free - Free a hash table
451 * @param[out] ptr Hash Table to be freed
452 */
mutt_hash_free(struct HashTable ** ptr)453 void mutt_hash_free(struct HashTable **ptr)
454 {
455 if (!ptr || !*ptr)
456 return;
457
458 struct HashTable *table = *ptr;
459 struct HashElem *elem = NULL, *tmp = NULL;
460
461 for (size_t i = 0; i < table->num_elems; i++)
462 {
463 for (elem = table->table[i]; elem;)
464 {
465 tmp = elem;
466 elem = elem->next;
467 if (table->hdata_free && tmp->data)
468 table->hdata_free(tmp->type, tmp->data, table->hdata);
469 if (table->strdup_keys)
470 FREE(&tmp->key.strkey);
471 FREE(&tmp);
472 }
473 }
474 FREE(&table->table);
475 FREE(ptr);
476 }
477
478 /**
479 * mutt_hash_walk - Iterate through all the HashElem's in a Hash Table
480 * @param table Hash Table to search
481 * @param state Cursor to keep track
482 * @retval ptr Next HashElem in the Hash Table
483 * @retval NULL When the last HashElem has been seen
484 */
mutt_hash_walk(const struct HashTable * table,struct HashWalkState * state)485 struct HashElem *mutt_hash_walk(const struct HashTable *table, struct HashWalkState *state)
486 {
487 if (!table || !state)
488 return NULL;
489
490 if (state->last && state->last->next)
491 {
492 state->last = state->last->next;
493 return state->last;
494 }
495
496 if (state->last)
497 state->index++;
498
499 while (state->index < table->num_elems)
500 {
501 if (table->table[state->index])
502 {
503 state->last = table->table[state->index];
504 return state->last;
505 }
506 state->index++;
507 }
508
509 state->index = 0;
510 state->last = NULL;
511 return NULL;
512 }
513