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