Coverage Report

Created: 2026-02-14 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/boringssl/crypto/lhash/lhash.cc
Line
Count
Source
1
// Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include <assert.h>
16
#include <limits.h>
17
#include <string.h>
18
19
#include <openssl/mem.h>
20
21
#include "../internal.h"
22
#include "../mem_internal.h"
23
#include "internal.h"
24
25
26
BSSL_NAMESPACE_BEGIN
27
28
// kMinNumBuckets is the minimum size of the buckets array in an |_LHASH|.
29
static const size_t kMinNumBuckets = 16;
30
31
// kMaxAverageChainLength contains the maximum, average chain length. When the
32
// average chain length exceeds this value, the hash table will be resized.
33
static const size_t kMaxAverageChainLength = 2;
34
static const size_t kMinAverageChainLength = 1;
35
36
// lhash_item_st is an element of a hash chain. It points to the opaque data
37
// for this element and to the next item in the chain. The linked-list is NULL
38
// terminated.
39
typedef struct lhash_item_st {
40
  void *data;
41
  struct lhash_item_st *next;
42
  // hash contains the cached, hash value of |data|.
43
  uint32_t hash;
44
} LHASH_ITEM;
45
46
struct lhash_st {
47
  // num_items contains the total number of items in the hash table.
48
  size_t num_items;
49
  // buckets is an array of |num_buckets| pointers. Each points to the head of
50
  // a chain of LHASH_ITEM objects that have the same hash value, mod
51
  // |num_buckets|.
52
  LHASH_ITEM **buckets;
53
  // num_buckets contains the length of |buckets|. This value is always >=
54
  // kMinNumBuckets.
55
  size_t num_buckets;
56
  // callback_depth contains the current depth of |lh_doall| or |lh_doall_arg|
57
  // calls. If non-zero then this suppresses resizing of the |buckets| array,
58
  // which would otherwise disrupt the iteration.
59
  unsigned callback_depth;
60
61
  lhash_cmp_func comp;
62
  lhash_hash_func hash;
63
};
64
65
25.8k
_LHASH *OPENSSL_lh_new(lhash_hash_func hash, lhash_cmp_func comp) {
66
25.8k
  _LHASH *ret = NewZeroed<_LHASH>();
67
25.8k
  if (ret == nullptr) {
68
0
    return nullptr;
69
0
  }
70
71
25.8k
  ret->num_buckets = kMinNumBuckets;
72
25.8k
  ret->buckets = reinterpret_cast<LHASH_ITEM **>(
73
25.8k
      OPENSSL_calloc(ret->num_buckets, sizeof(LHASH_ITEM *)));
74
25.8k
  if (ret->buckets == nullptr) {
75
0
    Delete(ret);
76
0
    return nullptr;
77
0
  }
78
79
25.8k
  ret->comp = comp;
80
25.8k
  ret->hash = hash;
81
25.8k
  return ret;
82
25.8k
}
83
84
25.8k
void OPENSSL_lh_free(_LHASH *lh) {
85
25.8k
  if (lh == nullptr) {
86
0
    return;
87
0
  }
88
89
443k
  for (size_t i = 0; i < lh->num_buckets; i++) {
90
417k
    LHASH_ITEM *next;
91
462k
    for (LHASH_ITEM *n = lh->buckets[i]; n != nullptr; n = next) {
92
44.2k
      next = n->next;
93
44.2k
      Delete(n);
94
44.2k
    }
95
417k
  }
96
97
25.8k
  OPENSSL_free(lh->buckets);
98
25.8k
  Delete(lh);
99
25.8k
}
100
101
58.1k
size_t OPENSSL_lh_num_items(const _LHASH *lh) { return lh->num_items; }
102
103
// get_next_ptr_and_hash returns a pointer to the pointer that points to the
104
// item equal to |data|. In other words, it searches for an item equal to |data|
105
// and, if it's at the start of a chain, then it returns a pointer to an
106
// element of |lh->buckets|, otherwise it returns a pointer to the |next|
107
// element of the previous item in the chain. If an element equal to |data| is
108
// not found, it returns a pointer that points to a NULL pointer. If |out_hash|
109
// is not NULL, then it also puts the hash value of |data| in |*out_hash|.
110
static LHASH_ITEM **get_next_ptr_and_hash(const _LHASH *lh, uint32_t *out_hash,
111
                                          const void *data,
112
                                          lhash_hash_func_helper call_hash_func,
113
370k
                                          lhash_cmp_func_helper call_cmp_func) {
114
370k
  const uint32_t hash = call_hash_func(lh->hash, data);
115
370k
  if (out_hash != nullptr) {
116
106k
    *out_hash = hash;
117
106k
  }
118
119
370k
  LHASH_ITEM **ret = &lh->buckets[hash % lh->num_buckets];
120
531k
  for (LHASH_ITEM *cur = *ret; cur != nullptr; cur = *ret) {
121
468k
    if (call_cmp_func(lh->comp, cur->data, data) == 0) {
122
306k
      break;
123
306k
    }
124
161k
    ret = &cur->next;
125
161k
  }
126
127
370k
  return ret;
128
370k
}
129
130
// get_next_ptr_by_key behaves like |get_next_ptr_and_hash| but takes a key
131
// which may be a different type from the values stored in |lh|.
132
static LHASH_ITEM **get_next_ptr_by_key(const _LHASH *lh, const void *key,
133
                                        uint32_t key_hash,
134
                                        int (*cmp_key)(const void *key,
135
720
                                                       const void *value)) {
136
720
  LHASH_ITEM **ret = &lh->buckets[key_hash % lh->num_buckets];
137
1.29k
  for (LHASH_ITEM *cur = *ret; cur != nullptr; cur = *ret) {
138
829
    if (cmp_key(key, cur->data) == 0) {
139
255
      break;
140
255
    }
141
574
    ret = &cur->next;
142
574
  }
143
144
720
  return ret;
145
720
}
146
147
void *OPENSSL_lh_retrieve(const _LHASH *lh, const void *data,
148
                          lhash_hash_func_helper call_hash_func,
149
253k
                          lhash_cmp_func_helper call_cmp_func) {
150
253k
  LHASH_ITEM **next_ptr =
151
253k
      get_next_ptr_and_hash(lh, nullptr, data, call_hash_func, call_cmp_func);
152
253k
  return *next_ptr == nullptr ? nullptr : (*next_ptr)->data;
153
253k
}
154
155
void *OPENSSL_lh_retrieve_key(const _LHASH *lh, const void *key,
156
                              uint32_t key_hash,
157
                              int (*cmp_key)(const void *key,
158
720
                                             const void *value)) {
159
720
  LHASH_ITEM **next_ptr = get_next_ptr_by_key(lh, key, key_hash, cmp_key);
160
720
  return *next_ptr == nullptr ? nullptr : (*next_ptr)->data;
161
720
}
162
163
// lh_rebucket allocates a new array of |new_num_buckets| pointers and
164
// redistributes the existing items into it before making it |lh->buckets| and
165
// freeing the old array.
166
212
static void lh_rebucket(_LHASH *lh, const size_t new_num_buckets) {
167
212
  LHASH_ITEM **new_buckets, *cur, *next;
168
212
  size_t i, alloc_size;
169
170
212
  alloc_size = sizeof(LHASH_ITEM *) * new_num_buckets;
171
212
  if (alloc_size / sizeof(LHASH_ITEM *) != new_num_buckets) {
172
0
    return;
173
0
  }
174
175
212
  new_buckets = reinterpret_cast<LHASH_ITEM **>(OPENSSL_zalloc(alloc_size));
176
212
  if (new_buckets == nullptr) {
177
0
    return;
178
0
  }
179
180
6.18k
  for (i = 0; i < lh->num_buckets; i++) {
181
19.9k
    for (cur = lh->buckets[i]; cur != nullptr; cur = next) {
182
13.9k
      const size_t new_bucket = cur->hash % new_num_buckets;
183
13.9k
      next = cur->next;
184
13.9k
      cur->next = new_buckets[new_bucket];
185
13.9k
      new_buckets[new_bucket] = cur;
186
13.9k
    }
187
5.96k
  }
188
189
212
  OPENSSL_free(lh->buckets);
190
191
212
  lh->num_buckets = new_num_buckets;
192
212
  lh->buckets = new_buckets;
193
212
}
194
195
// lh_maybe_resize resizes the |buckets| array if needed.
196
143k
static void lh_maybe_resize(_LHASH *lh) {
197
143k
  size_t avg_chain_length;
198
199
143k
  if (lh->callback_depth > 0) {
200
    // Don't resize the hash if we are currently iterating over it.
201
11.1k
    return;
202
11.1k
  }
203
204
143k
  assert(lh->num_buckets >= kMinNumBuckets);
205
132k
  avg_chain_length = lh->num_items / lh->num_buckets;
206
207
132k
  if (avg_chain_length > kMaxAverageChainLength) {
208
176
    const size_t new_num_buckets = lh->num_buckets * 2;
209
210
176
    if (new_num_buckets > lh->num_buckets) {
211
176
      lh_rebucket(lh, new_num_buckets);
212
176
    }
213
132k
  } else if (avg_chain_length < kMinAverageChainLength &&
214
122k
             lh->num_buckets > kMinNumBuckets) {
215
36
    size_t new_num_buckets = lh->num_buckets / 2;
216
217
36
    if (new_num_buckets < kMinNumBuckets) {
218
0
      new_num_buckets = kMinNumBuckets;
219
0
    }
220
221
36
    lh_rebucket(lh, new_num_buckets);
222
36
  }
223
132k
}
224
225
int OPENSSL_lh_insert(_LHASH *lh, void **old_data, void *data,
226
                      lhash_hash_func_helper call_hash_func,
227
106k
                      lhash_cmp_func_helper call_cmp_func) {
228
106k
  uint32_t hash;
229
106k
  LHASH_ITEM **next_ptr, *item;
230
231
106k
  *old_data = nullptr;
232
106k
  next_ptr =
233
106k
      get_next_ptr_and_hash(lh, &hash, data, call_hash_func, call_cmp_func);
234
235
236
106k
  if (*next_ptr != nullptr) {
237
    // An element equal to |data| already exists in the hash table. It will be
238
    // replaced.
239
50.6k
    *old_data = (*next_ptr)->data;
240
50.6k
    (*next_ptr)->data = data;
241
50.6k
    return 1;
242
50.6k
  }
243
244
  // An element equal to |data| doesn't exist in the hash table yet.
245
55.4k
  item = New<LHASH_ITEM>();
246
55.4k
  if (item == nullptr) {
247
0
    return 0;
248
0
  }
249
250
55.4k
  item->data = data;
251
55.4k
  item->hash = hash;
252
55.4k
  item->next = nullptr;
253
55.4k
  *next_ptr = item;
254
55.4k
  lh->num_items++;
255
55.4k
  lh_maybe_resize(lh);
256
257
55.4k
  return 1;
258
55.4k
}
259
260
void *OPENSSL_lh_delete(_LHASH *lh, const void *data,
261
                        lhash_hash_func_helper call_hash_func,
262
11.1k
                        lhash_cmp_func_helper call_cmp_func) {
263
11.1k
  LHASH_ITEM **next_ptr, *item, *ret;
264
265
11.1k
  next_ptr =
266
11.1k
      get_next_ptr_and_hash(lh, nullptr, data, call_hash_func, call_cmp_func);
267
268
11.1k
  if (*next_ptr == nullptr) {
269
    // No such element.
270
0
    return nullptr;
271
0
  }
272
273
11.1k
  item = *next_ptr;
274
11.1k
  *next_ptr = item->next;
275
11.1k
  ret = reinterpret_cast<LHASH_ITEM *>(item->data);
276
11.1k
  Delete(item);
277
278
11.1k
  lh->num_items--;
279
11.1k
  lh_maybe_resize(lh);
280
281
11.1k
  return ret;
282
11.1k
}
283
284
77.3k
void OPENSSL_lh_doall_arg(_LHASH *lh, void (*func)(void *, void *), void *arg) {
285
77.3k
  if (lh == nullptr) {
286
0
    return;
287
0
  }
288
289
77.3k
  if (lh->callback_depth < UINT_MAX) {
290
    // |callback_depth| is a saturating counter.
291
77.3k
    lh->callback_depth++;
292
77.3k
  }
293
294
1.31M
  for (size_t i = 0; i < lh->num_buckets; i++) {
295
1.24M
    LHASH_ITEM *next;
296
1.29M
    for (LHASH_ITEM *cur = lh->buckets[i]; cur != nullptr; cur = next) {
297
55.4k
      next = cur->next;
298
55.4k
      func(cur->data, arg);
299
55.4k
    }
300
1.24M
  }
301
302
77.3k
  if (lh->callback_depth < UINT_MAX) {
303
77.3k
    lh->callback_depth--;
304
77.3k
  }
305
306
  // The callback may have added or removed elements and the non-zero value of
307
  // |callback_depth| will have suppressed any resizing. Thus any needed
308
  // resizing is done here.
309
77.3k
  lh_maybe_resize(lh);
310
77.3k
}
311
312
BSSL_NAMESPACE_END