/src/libxlsxwriter/src/hash_table.c
Line | Count | Source |
1 | | /***************************************************************************** |
2 | | * hash_table - Hash table functions for libxlsxwriter. |
3 | | * |
4 | | * Used in conjunction with the libxlsxwriter library. |
5 | | * |
6 | | * SPDX-License-Identifier: BSD-2-Clause |
7 | | * Copyright 2014-2025, John McNamara, jmcnamara@cpan.org. |
8 | | * |
9 | | */ |
10 | | |
11 | | #include <stdlib.h> |
12 | | #include <stdio.h> |
13 | | #include <string.h> |
14 | | #include <stdint.h> |
15 | | #include "xlsxwriter/hash_table.h" |
16 | | |
17 | | /* |
18 | | * Calculate the hash key using the FNV function. See: |
19 | | * http://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function |
20 | | */ |
21 | | STATIC size_t |
22 | | _generate_hash_key(void *data, size_t data_len, size_t num_buckets) |
23 | 7.17k | { |
24 | 7.17k | unsigned char *p = data; |
25 | 7.17k | size_t hash = 2166136261U; |
26 | 7.17k | size_t i; |
27 | | |
28 | 1.22M | for (i = 0; i < data_len; i++) |
29 | 1.22M | hash = (hash * 16777619) ^ p[i]; |
30 | | |
31 | 7.17k | return hash % num_buckets; |
32 | 7.17k | } |
33 | | |
34 | | /* |
35 | | * Check if an element exists in the hash table and return a pointer |
36 | | * to it if it does. |
37 | | */ |
38 | | lxw_hash_element * |
39 | | lxw_hash_key_exists(lxw_hash_table *lxw_hash, void *key, size_t key_len) |
40 | 3.18k | { |
41 | 3.18k | size_t hash_key = _generate_hash_key(key, key_len, lxw_hash->num_buckets); |
42 | 3.18k | struct lxw_hash_bucket_list *list; |
43 | 3.18k | lxw_hash_element *element; |
44 | | |
45 | 3.18k | if (!lxw_hash->buckets[hash_key]) { |
46 | | /* The key isn't in the LXW_HASH hash table. */ |
47 | 2.39k | return NULL; |
48 | 2.39k | } |
49 | 797 | else { |
50 | | /* The key is already in the table or there is a hash collision. */ |
51 | 797 | list = lxw_hash->buckets[hash_key]; |
52 | | |
53 | | /* Iterate over the keys in the bucket's linked list. */ |
54 | 797 | SLIST_FOREACH(element, list, lxw_hash_list_pointers) { |
55 | 797 | if (memcmp(element->key, key, key_len) == 0) { |
56 | | /* The key already exists in the table. */ |
57 | 797 | return element; |
58 | 797 | } |
59 | 797 | } |
60 | | |
61 | | /* Key doesn't exist in the list so this is a hash collision. */ |
62 | 0 | return NULL; |
63 | 797 | } |
64 | 3.18k | } |
65 | | |
66 | | /* |
67 | | * Insert or update a value in the LXW_HASH table based on a key |
68 | | * and return a pointer to the new or updated element. |
69 | | */ |
70 | | lxw_hash_element * |
71 | | lxw_insert_hash_element(lxw_hash_table *lxw_hash, void *key, void *value, |
72 | | size_t key_len) |
73 | 3.98k | { |
74 | 3.98k | size_t hash_key = _generate_hash_key(key, key_len, lxw_hash->num_buckets); |
75 | 3.98k | struct lxw_hash_bucket_list *list = NULL; |
76 | 3.98k | lxw_hash_element *element = NULL; |
77 | | |
78 | 3.98k | if (!lxw_hash->buckets[hash_key]) { |
79 | | /* The key isn't in the LXW_HASH hash table. */ |
80 | | |
81 | | /* Create a linked list in the bucket to hold the lxw_hash keys. */ |
82 | 3.98k | list = calloc(1, sizeof(struct lxw_hash_bucket_list)); |
83 | 3.98k | GOTO_LABEL_ON_MEM_ERROR(list, mem_error1); |
84 | | |
85 | | /* Initialize the bucket linked list. */ |
86 | 3.98k | SLIST_INIT(list); |
87 | | |
88 | | /* Create an lxw_hash element to add to the linked list. */ |
89 | 3.98k | element = calloc(1, sizeof(lxw_hash_element)); |
90 | 3.98k | GOTO_LABEL_ON_MEM_ERROR(element, mem_error1); |
91 | | |
92 | | /* Store the key and value. */ |
93 | 3.98k | element->key = key; |
94 | 3.98k | element->value = value; |
95 | | |
96 | | /* Add the lxw_hash element to the bucket's linked list. */ |
97 | 3.98k | SLIST_INSERT_HEAD(list, element, lxw_hash_list_pointers); |
98 | | |
99 | | /* Also add it to the insertion order linked list. */ |
100 | 3.98k | STAILQ_INSERT_TAIL(lxw_hash->order_list, element, |
101 | 3.98k | lxw_hash_order_pointers); |
102 | | |
103 | | /* Store the bucket list at the hash index. */ |
104 | 3.98k | lxw_hash->buckets[hash_key] = list; |
105 | | |
106 | 3.98k | lxw_hash->used_buckets++; |
107 | 3.98k | lxw_hash->unique_count++; |
108 | | |
109 | 3.98k | return element; |
110 | 3.98k | } |
111 | 0 | else { |
112 | | /* The key is already in the table or there is a hash collision. */ |
113 | 0 | list = lxw_hash->buckets[hash_key]; |
114 | | |
115 | | /* Iterate over the keys in the bucket's linked list. */ |
116 | 0 | SLIST_FOREACH(element, list, lxw_hash_list_pointers) { |
117 | 0 | if (memcmp(element->key, key, key_len) == 0) { |
118 | | /* The key already exists in the table. Update the value. */ |
119 | 0 | if (lxw_hash->free_value) |
120 | 0 | free(element->value); |
121 | |
|
122 | 0 | element->value = value; |
123 | 0 | return element; |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | | /* Key doesn't exist in the list so this is a hash collision. |
128 | | * Create an lxw_hash element to add to the linked list. */ |
129 | 0 | element = calloc(1, sizeof(lxw_hash_element)); |
130 | 0 | GOTO_LABEL_ON_MEM_ERROR(element, mem_error2); |
131 | | |
132 | | /* Store the key and value. */ |
133 | 0 | element->key = key; |
134 | 0 | element->value = value; |
135 | | |
136 | | /* Add the lxw_hash element to the bucket linked list. */ |
137 | 0 | SLIST_INSERT_HEAD(list, element, lxw_hash_list_pointers); |
138 | | |
139 | | /* Also add it to the insertion order linked list. */ |
140 | 0 | STAILQ_INSERT_TAIL(lxw_hash->order_list, element, |
141 | 0 | lxw_hash_order_pointers); |
142 | |
|
143 | 0 | lxw_hash->unique_count++; |
144 | |
|
145 | 0 | return element; |
146 | 0 | } |
147 | | |
148 | 0 | mem_error1: |
149 | 0 | free(list); |
150 | |
|
151 | 0 | mem_error2: |
152 | 0 | free(element); |
153 | 0 | return NULL; |
154 | 0 | } |
155 | | |
156 | | /* |
157 | | * Create a new LXW_HASH hash table object. |
158 | | */ |
159 | | lxw_hash_table * |
160 | | lxw_hash_new(uint32_t num_buckets, uint8_t free_key, uint8_t free_value) |
161 | 4.78k | { |
162 | | /* Create the new hash table. */ |
163 | 4.78k | lxw_hash_table *lxw_hash = calloc(1, sizeof(lxw_hash_table)); |
164 | 4.78k | RETURN_ON_MEM_ERROR(lxw_hash, NULL); |
165 | | |
166 | 4.78k | lxw_hash->free_key = free_key; |
167 | 4.78k | lxw_hash->free_value = free_value; |
168 | | |
169 | | /* Add the lxw_hash element buckets. */ |
170 | 4.78k | lxw_hash->buckets = |
171 | 4.78k | calloc(num_buckets, sizeof(struct lxw_hash_bucket_list *)); |
172 | 4.78k | GOTO_LABEL_ON_MEM_ERROR(lxw_hash->buckets, mem_error); |
173 | | |
174 | | /* Add a list for tracking the insertion order. */ |
175 | 4.78k | lxw_hash->order_list = calloc(1, sizeof(struct lxw_hash_order_list)); |
176 | 4.78k | GOTO_LABEL_ON_MEM_ERROR(lxw_hash->order_list, mem_error); |
177 | | |
178 | | /* Initialize the order list. */ |
179 | 4.78k | STAILQ_INIT(lxw_hash->order_list); |
180 | | |
181 | | /* Store the number of buckets to calculate the load factor. */ |
182 | 4.78k | lxw_hash->num_buckets = num_buckets; |
183 | | |
184 | 4.78k | return lxw_hash; |
185 | | |
186 | 0 | mem_error: |
187 | 0 | lxw_hash_free(lxw_hash); |
188 | 0 | return NULL; |
189 | 4.78k | } |
190 | | |
191 | | /* |
192 | | * Free the LXW_HASH hash table object. |
193 | | */ |
194 | | void |
195 | | lxw_hash_free(lxw_hash_table *lxw_hash) |
196 | 4.78k | { |
197 | 4.78k | size_t i; |
198 | 4.78k | lxw_hash_element *element; |
199 | 4.78k | lxw_hash_element *element_temp; |
200 | | |
201 | 4.78k | if (!lxw_hash) |
202 | 0 | return; |
203 | | |
204 | | /* Free the lxw_hash_elements and data using the ordered linked list. */ |
205 | 4.78k | if (lxw_hash->order_list) { |
206 | 4.78k | STAILQ_FOREACH_SAFE(element, lxw_hash->order_list, |
207 | 4.78k | lxw_hash_order_pointers, element_temp) { |
208 | 3.98k | if (lxw_hash->free_key) |
209 | 3.98k | free(element->key); |
210 | 3.98k | if (lxw_hash->free_value) |
211 | 3.18k | free(element->value); |
212 | 3.98k | free(element); |
213 | 3.98k | } |
214 | 4.78k | } |
215 | | |
216 | | /* Free the buckets from the hash table. */ |
217 | 616k | for (i = 0; i < lxw_hash->num_buckets; i++) { |
218 | 612k | free(lxw_hash->buckets[i]); |
219 | 612k | } |
220 | | |
221 | 4.78k | free(lxw_hash->order_list); |
222 | 4.78k | free(lxw_hash->buckets); |
223 | 4.78k | free(lxw_hash); |
224 | 4.78k | } |