Coverage Report

Created: 2025-08-03 06:54

/src/libxml2/hash.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * hash.c: hash tables
3
 *
4
 * Hash table with open addressing, linear probing and
5
 * Robin Hood reordering.
6
 *
7
 * See Copyright for the status of this software.
8
 */
9
10
#define IN_LIBXML
11
#include "libxml.h"
12
13
#include <string.h>
14
#include <limits.h>
15
16
#include <libxml/parser.h>
17
#include <libxml/hash.h>
18
#include <libxml/dict.h>
19
#include <libxml/xmlmemory.h>
20
#include <libxml/xmlstring.h>
21
22
#include "private/dict.h"
23
24
#ifndef SIZE_MAX
25
  #define SIZE_MAX ((size_t) -1)
26
#endif
27
28
71.4k
#define MAX_FILL_NUM 7
29
71.4k
#define MAX_FILL_DENOM 8
30
123k
#define MIN_HASH_SIZE 8
31
5.85M
#define MAX_HASH_SIZE (1u << 31)
32
33
/*
34
 * A single entry in the hash table
35
 */
36
typedef struct {
37
    unsigned hashValue; /* 0 means unoccupied, occupied entries have the
38
                         * MAX_HASH_SIZE bit set to 1 */
39
    xmlChar *key;
40
    xmlChar *key2; /* TODO: Don't allocate possibly empty keys */
41
    xmlChar *key3;
42
    void *payload;
43
} xmlHashEntry;
44
45
/*
46
 * The entire hash table
47
 */
48
struct _xmlHashTable {
49
    xmlHashEntry *table;
50
    unsigned size; /* power of two */
51
    unsigned nbElems;
52
    xmlDictPtr dict;
53
    unsigned randomSeed;
54
};
55
56
static int
57
xmlHashGrow(xmlHashTablePtr hash, unsigned size);
58
59
ATTRIBUTE_NO_SANITIZE_INTEGER
60
static unsigned
61
xmlHashValue(unsigned seed, const xmlChar *key, const xmlChar *key2,
62
6.32M
             const xmlChar *key3, size_t *lengths) {
63
6.32M
    unsigned h1, h2;
64
6.32M
    size_t i;
65
66
6.32M
    HASH_INIT(h1, h2, seed);
67
68
50.7M
    for (i = 0; key[i] != 0; i++) {
69
44.4M
        HASH_UPDATE(h1, h2, key[i]);
70
44.4M
    }
71
6.32M
    if (lengths)
72
243k
        lengths[0] = i;
73
74
6.32M
    HASH_UPDATE(h1, h2, 0);
75
76
6.32M
    if (key2 != NULL) {
77
1.12M
        for (i = 0; key2[i] != 0; i++) {
78
886k
            HASH_UPDATE(h1, h2, key2[i]);
79
886k
        }
80
236k
        if (lengths)
81
44.9k
            lengths[1] = i;
82
236k
    }
83
84
6.32M
    HASH_UPDATE(h1, h2, 0);
85
86
6.32M
    if (key3 != NULL) {
87
4.59M
        for (i = 0; key3[i] != 0; i++) {
88
3.98M
            HASH_UPDATE(h1, h2, key3[i]);
89
3.98M
        }
90
612k
        if (lengths)
91
45.2k
            lengths[2] = i;
92
612k
    }
93
94
6.32M
    HASH_FINISH(h1, h2);
95
96
6.32M
    return(h2);
97
6.32M
}
98
99
ATTRIBUTE_NO_SANITIZE_INTEGER
100
static unsigned
101
xmlHashQNameValue(unsigned seed,
102
                  const xmlChar *prefix, const xmlChar *name,
103
                  const xmlChar *prefix2, const xmlChar *name2,
104
56.0k
                  const xmlChar *prefix3, const xmlChar *name3) {
105
56.0k
    unsigned h1, h2, ch;
106
107
56.0k
    HASH_INIT(h1, h2, seed);
108
109
56.0k
    if (prefix != NULL) {
110
50.9k
        while ((ch = *prefix++) != 0) {
111
35.7k
            HASH_UPDATE(h1, h2, ch);
112
35.7k
        }
113
15.1k
        HASH_UPDATE(h1, h2, ':');
114
15.1k
    }
115
56.0k
    if (name != NULL) {
116
5.64M
        while ((ch = *name++) != 0) {
117
5.59M
            HASH_UPDATE(h1, h2, ch);
118
5.59M
        }
119
56.0k
    }
120
56.0k
    HASH_UPDATE(h1, h2, 0);
121
56.0k
    if (prefix2 != NULL) {
122
68.8k
        while ((ch = *prefix2++) != 0) {
123
45.9k
            HASH_UPDATE(h1, h2, ch);
124
45.9k
        }
125
22.9k
        HASH_UPDATE(h1, h2, ':');
126
22.9k
    }
127
56.0k
    if (name2 != NULL) {
128
409k
        while ((ch = *name2++) != 0) {
129
353k
            HASH_UPDATE(h1, h2, ch);
130
353k
        }
131
56.0k
    }
132
56.0k
    HASH_UPDATE(h1, h2, 0);
133
56.0k
    if (prefix3 != NULL) {
134
0
        while ((ch = *prefix3++) != 0) {
135
0
            HASH_UPDATE(h1, h2, ch);
136
0
        }
137
0
        HASH_UPDATE(h1, h2, ':');
138
0
    }
139
56.0k
    if (name3 != NULL) {
140
0
        while ((ch = *name3++) != 0) {
141
0
            HASH_UPDATE(h1, h2, ch);
142
0
        }
143
0
    }
144
145
56.0k
    HASH_FINISH(h1, h2);
146
147
56.0k
    return(h2);
148
56.0k
}
149
150
/**
151
 * Create a new hash table. Set size to zero if the number of entries
152
 * can't be estimated.
153
 *
154
 * @param size  initial size of the hash table
155
 * @returns the newly created object, or NULL if a memory allocation failed.
156
 */
157
xmlHashTable *
158
62.0k
xmlHashCreate(int size) {
159
62.0k
    xmlHashTablePtr hash;
160
161
62.0k
    xmlInitParser();
162
163
62.0k
    hash = xmlMalloc(sizeof(*hash));
164
62.0k
    if (hash == NULL)
165
19
        return(NULL);
166
62.0k
    hash->dict = NULL;
167
62.0k
    hash->size = 0;
168
62.0k
    hash->table = NULL;
169
62.0k
    hash->nbElems = 0;
170
62.0k
    hash->randomSeed = xmlRandom();
171
62.0k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
172
62.0k
    hash->randomSeed = 0;
173
62.0k
#endif
174
175
    /*
176
     * Unless a larger size is passed, the backing table is created
177
     * lazily with MIN_HASH_SIZE capacity. In practice, there are many
178
     * hash tables which are never filled.
179
     */
180
62.0k
    if (size > MIN_HASH_SIZE) {
181
5.40k
        unsigned newSize = MIN_HASH_SIZE * 2;
182
183
5.40k
        while ((newSize < (unsigned) size) && (newSize < MAX_HASH_SIZE))
184
0
            newSize *= 2;
185
186
5.40k
        if (xmlHashGrow(hash, newSize) != 0) {
187
3
            xmlFree(hash);
188
3
            return(NULL);
189
3
        }
190
5.40k
    }
191
192
62.0k
    return(hash);
193
62.0k
}
194
195
/**
196
 * Create a new hash table backed by a dictionary. This can reduce
197
 * resource usage considerably if most keys passed to API functions
198
 * originate from this dictionary.
199
 *
200
 * @param size  the size of the hash table
201
 * @param dict  a dictionary to use for the hash
202
 * @returns the newly created object, or NULL if a memory allocation failed.
203
 */
204
xmlHashTable *
205
25.4k
xmlHashCreateDict(int size, xmlDict *dict) {
206
25.4k
    xmlHashTablePtr hash;
207
208
25.4k
    hash = xmlHashCreate(size);
209
25.4k
    if (hash != NULL) {
210
25.3k
        hash->dict = dict;
211
25.3k
        xmlDictReference(dict);
212
25.3k
    }
213
25.4k
    return(hash);
214
25.4k
}
215
216
/**
217
 * Free the hash and its contents. The payload is deallocated with
218
 * `dealloc` if provided.
219
 *
220
 * @param hash  hash table
221
 * @param dealloc  deallocator function or NULL
222
 */
223
void
224
77.7k
xmlHashFree(xmlHashTable *hash, xmlHashDeallocator dealloc) {
225
77.7k
    if (hash == NULL)
226
15.7k
        return;
227
228
62.0k
    if (hash->table) {
229
61.8k
        const xmlHashEntry *end = &hash->table[hash->size];
230
61.8k
        const xmlHashEntry *entry;
231
232
651k
        for (entry = hash->table; entry < end; entry++) {
233
589k
            if (entry->hashValue == 0)
234
465k
                continue;
235
123k
            if ((dealloc != NULL) && (entry->payload != NULL))
236
107k
                dealloc(entry->payload, entry->key);
237
123k
            if (hash->dict == NULL) {
238
69.1k
                if (entry->key)
239
69.1k
                    xmlFree(entry->key);
240
69.1k
                if (entry->key2)
241
2.60k
                    xmlFree(entry->key2);
242
69.1k
                if (entry->key3)
243
5.97k
                    xmlFree(entry->key3);
244
69.1k
            }
245
123k
        }
246
247
61.8k
        xmlFree(hash->table);
248
61.8k
    }
249
250
62.0k
    if (hash->dict)
251
17.0k
        xmlDictFree(hash->dict);
252
253
62.0k
    xmlFree(hash);
254
62.0k
}
255
256
/**
257
 * Compare two strings for equality, allowing NULL values.
258
 *
259
 * @param s1  string
260
 * @param s2  string
261
 */
262
static int
263
8.13M
xmlFastStrEqual(const xmlChar *s1, const xmlChar *s2) {
264
8.13M
    if (s1 == NULL)
265
7.79M
        return(s2 == NULL);
266
344k
    else
267
344k
        return((s2 != NULL) &&
268
344k
               (strcmp((const char *) s1, (const char *) s2) == 0));
269
8.13M
}
270
271
/**
272
 * Try to find a matching hash table entry. If an entry was found, set
273
 * `found` to 1 and return the entry. Otherwise, set `found` to 0 and return
274
 * the location where a new entry should be inserted.
275
 *
276
 * @param hash  hash table, non-NULL, size > 0
277
 * @param key  first string key, non-NULL
278
 * @param key2  second string key
279
 * @param key3  third string key
280
 * @param hashValue  valid hash value of keys
281
 * @param pfound  result of search
282
 */
283
ATTRIBUTE_NO_SANITIZE_INTEGER
284
static xmlHashEntry *
285
xmlHashFindEntry(const xmlHashTable *hash, const xmlChar *key,
286
                 const xmlChar *key2, const xmlChar *key3,
287
6.26M
                 unsigned hashValue, int *pfound) {
288
6.26M
    xmlHashEntry *entry;
289
6.26M
    unsigned mask, pos, displ;
290
6.26M
    int found = 0;
291
292
6.26M
    mask = hash->size - 1;
293
6.26M
    pos = hashValue & mask;
294
6.26M
    entry = &hash->table[pos];
295
296
6.26M
    if (entry->hashValue != 0) {
297
        /*
298
         * Robin hood hashing: abort if the displacement of the entry
299
         * is smaller than the displacement of the key we look for.
300
         * This also stops at the correct position when inserting.
301
         */
302
5.70M
        displ = 0;
303
5.70M
        hashValue |= MAX_HASH_SIZE;
304
305
6.34M
        do {
306
6.34M
            if (entry->hashValue == hashValue) {
307
5.45M
                if (hash->dict) {
308
3.45M
                    if ((entry->key == key) &&
309
3.45M
                        (entry->key2 == key2) &&
310
3.45M
                        (entry->key3 == key3)) {
311
1.38M
                        found = 1;
312
1.38M
                        break;
313
1.38M
                    }
314
3.45M
                }
315
4.06M
                if ((strcmp((const char *) entry->key,
316
4.06M
                            (const char *) key) == 0) &&
317
4.06M
                    (xmlFastStrEqual(entry->key2, key2)) &&
318
4.06M
                    (xmlFastStrEqual(entry->key3, key3))) {
319
4.06M
                    found = 1;
320
4.06M
                    break;
321
4.06M
                }
322
4.06M
            }
323
324
891k
            displ++;
325
891k
            pos++;
326
891k
            entry++;
327
891k
            if ((pos & mask) == 0)
328
27.7k
                entry = hash->table;
329
891k
        } while ((entry->hashValue != 0) &&
330
891k
                 (((pos - entry->hashValue) & mask) >= displ));
331
5.70M
    }
332
333
0
    *pfound = found;
334
6.26M
    return(entry);
335
6.26M
}
336
337
/**
338
 * Resize the hash table.
339
 *
340
 * @param hash  hash table
341
 * @param size  new size of the hash table
342
 * @returns 0 in case of success, -1 if a memory allocation failed.
343
 */
344
static int
345
63.7k
xmlHashGrow(xmlHashTablePtr hash, unsigned size) {
346
63.7k
    const xmlHashEntry *oldentry, *oldend, *end;
347
63.7k
    xmlHashEntry *table;
348
63.7k
    unsigned oldsize, i;
349
350
    /* Add 0 to avoid spurious -Wtype-limits warning on 64-bit GCC */
351
63.7k
    if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0]))
352
0
        return(-1);
353
63.7k
    table = xmlMalloc(size * sizeof(table[0]));
354
63.7k
    if (table == NULL)
355
16
        return(-1);
356
63.7k
    memset(table, 0, size * sizeof(table[0]));
357
358
63.7k
    oldsize = hash->size;
359
63.7k
    if (oldsize == 0)
360
61.8k
        goto done;
361
362
1.86k
    oldend = &hash->table[oldsize];
363
1.86k
    end = &table[size];
364
365
    /*
366
     * Robin Hood sorting order is maintained if we
367
     *
368
     * - compute hash indices with modulo
369
     * - resize by an integer factor
370
     * - start to copy from the beginning of a probe sequence
371
     */
372
1.86k
    oldentry = hash->table;
373
11.9k
    while (oldentry->hashValue != 0) {
374
10.0k
        if (++oldentry >= oldend)
375
0
            oldentry = hash->table;
376
10.0k
    }
377
378
52.7k
    for (i = 0; i < oldsize; i++) {
379
50.9k
        if (oldentry->hashValue != 0) {
380
44.5k
            xmlHashEntry *entry = &table[oldentry->hashValue & (size - 1)];
381
382
60.6k
            while (entry->hashValue != 0) {
383
16.0k
                if (++entry >= end)
384
218
                    entry = table;
385
16.0k
            }
386
44.5k
            *entry = *oldentry;
387
44.5k
        }
388
389
50.9k
        if (++oldentry >= oldend)
390
1.86k
            oldentry = hash->table;
391
50.9k
    }
392
393
1.86k
    xmlFree(hash->table);
394
395
63.7k
done:
396
63.7k
    hash->table = table;
397
63.7k
    hash->size = size;
398
399
63.7k
    return(0);
400
1.86k
}
401
402
/**
403
 * Internal function to add or update hash entries.
404
 *
405
 * @param hash  hash table
406
 * @param key  first string key
407
 * @param key2  second string key
408
 * @param key3  third string key
409
 * @param payload  pointer to the payload
410
 * @param dealloc  deallocator function for replaced item or NULL
411
 * @param update  whether existing entries should be updated
412
 */
413
ATTRIBUTE_NO_SANITIZE_INTEGER
414
static int
415
xmlHashUpdateInternal(xmlHashTable *hash, const xmlChar *key,
416
                      const xmlChar *key2, const xmlChar *key3,
417
243k
                      void *payload, xmlHashDeallocator dealloc, int update) {
418
243k
    xmlChar *copy, *copy2, *copy3;
419
243k
    xmlHashEntry *entry = NULL;
420
243k
    size_t lengths[3] = {0, 0, 0};
421
243k
    unsigned hashValue, newSize;
422
423
243k
    if ((hash == NULL) || (key == NULL))
424
0
        return(-1);
425
426
243k
    hashValue = xmlHashValue(hash->randomSeed, key, key2, key3, lengths);
427
428
    /*
429
     * Check for an existing entry
430
     */
431
243k
    if (hash->size == 0) {
432
56.4k
        newSize = MIN_HASH_SIZE;
433
187k
    } else {
434
187k
        int found = 0;
435
436
187k
        entry = xmlHashFindEntry(hash, key, key2, key3, hashValue, &found);
437
438
187k
        if (found) {
439
116k
            if (update) {
440
773
                if (dealloc)
441
19
                    dealloc(entry->payload, entry->key);
442
773
                entry->payload = payload;
443
773
            }
444
445
116k
            return(0);
446
116k
        }
447
448
71.4k
        if (hash->nbElems + 1 > hash->size / MAX_FILL_DENOM * MAX_FILL_NUM) {
449
            /* This guarantees that nbElems < INT_MAX */
450
1.86k
            if (hash->size >= MAX_HASH_SIZE)
451
0
                return(-1);
452
1.86k
            newSize = hash->size * 2;
453
69.5k
        } else {
454
69.5k
            newSize = 0;
455
69.5k
        }
456
71.4k
    }
457
458
    /*
459
     * Grow the hash table if needed
460
     */
461
127k
    if (newSize > 0) {
462
58.3k
        unsigned mask, displ, pos;
463
464
58.3k
        if (xmlHashGrow(hash, newSize) != 0)
465
13
            return(-1);
466
467
        /*
468
         * Find new entry
469
         */
470
58.3k
        mask = hash->size - 1;
471
58.3k
        displ = 0;
472
58.3k
        pos = hashValue & mask;
473
58.3k
        entry = &hash->table[pos];
474
475
58.3k
        if (entry->hashValue != 0) {
476
1.33k
            do {
477
1.33k
                displ++;
478
1.33k
                pos++;
479
1.33k
                entry++;
480
1.33k
                if ((pos & mask) == 0)
481
57
                    entry = hash->table;
482
1.33k
            } while ((entry->hashValue != 0) &&
483
1.33k
                     ((pos - entry->hashValue) & mask) >= displ);
484
954
        }
485
58.3k
    }
486
487
    /*
488
     * Copy keys
489
     */
490
127k
    if (hash->dict != NULL) {
491
56.1k
        if (xmlDictOwns(hash->dict, key)) {
492
56.1k
            copy = (xmlChar *) key;
493
56.1k
        } else {
494
0
            copy = (xmlChar *) xmlDictLookup(hash->dict, key, -1);
495
0
            if (copy == NULL)
496
0
                return(-1);
497
0
        }
498
499
56.1k
        if ((key2 == NULL) || (xmlDictOwns(hash->dict, key2))) {
500
55.4k
            copy2 = (xmlChar *) key2;
501
55.4k
        } else {
502
672
            copy2 = (xmlChar *) xmlDictLookup(hash->dict, key2, -1);
503
672
            if (copy2 == NULL)
504
1
                return(-1);
505
672
        }
506
56.1k
        if ((key3 == NULL) || (xmlDictOwns(hash->dict, key3))) {
507
56.1k
            copy3 = (xmlChar *) key3;
508
56.1k
        } else {
509
0
            copy3 = (xmlChar *) xmlDictLookup(hash->dict, key3, -1);
510
0
            if (copy3 == NULL)
511
0
                return(-1);
512
0
        }
513
71.8k
    } else {
514
71.8k
        copy = xmlMalloc(lengths[0] + 1);
515
71.8k
        if (copy == NULL)
516
11
            return(-1);
517
71.7k
        memcpy(copy, key, lengths[0] + 1);
518
519
71.7k
        if (key2 != NULL) {
520
2.61k
            copy2 = xmlMalloc(lengths[1] + 1);
521
2.61k
            if (copy2 == NULL) {
522
3
                xmlFree(copy);
523
3
                return(-1);
524
3
            }
525
2.61k
            memcpy(copy2, key2, lengths[1] + 1);
526
69.1k
        } else {
527
69.1k
            copy2 = NULL;
528
69.1k
        }
529
530
71.7k
        if (key3 != NULL) {
531
5.97k
            copy3 = xmlMalloc(lengths[2] + 1);
532
5.97k
            if (copy3 == NULL) {
533
2
                xmlFree(copy);
534
2
                xmlFree(copy2);
535
2
                return(-1);
536
2
            }
537
5.97k
            memcpy(copy3, key3, lengths[2] + 1);
538
65.8k
        } else {
539
65.8k
            copy3 = NULL;
540
65.8k
        }
541
71.7k
    }
542
543
    /*
544
     * Shift the remainder of the probe sequence to the right
545
     */
546
127k
    if (entry->hashValue != 0) {
547
13.3k
        const xmlHashEntry *end = &hash->table[hash->size];
548
13.3k
        const xmlHashEntry *cur = entry;
549
550
53.6k
        do {
551
53.6k
            cur++;
552
53.6k
            if (cur >= end)
553
1.69k
                cur = hash->table;
554
53.6k
        } while (cur->hashValue != 0);
555
556
13.3k
        if (cur < entry) {
557
            /*
558
             * If we traversed the end of the buffer, handle the part
559
             * at the start of the buffer.
560
             */
561
1.69k
            memmove(&hash->table[1], hash->table,
562
1.69k
                    (char *) cur - (char *) hash->table);
563
1.69k
            cur = end - 1;
564
1.69k
            hash->table[0] = *cur;
565
1.69k
        }
566
567
13.3k
        memmove(&entry[1], entry, (char *) cur - (char *) entry);
568
13.3k
    }
569
570
    /*
571
     * Populate entry
572
     */
573
127k
    entry->key = copy;
574
127k
    entry->key2 = copy2;
575
127k
    entry->key3 = copy3;
576
127k
    entry->payload = payload;
577
    /* OR with MAX_HASH_SIZE to make sure that the value is non-zero */
578
127k
    entry->hashValue = hashValue | MAX_HASH_SIZE;
579
580
127k
    hash->nbElems++;
581
582
127k
    return(1);
583
127k
}
584
585
/**
586
 * Free a hash table entry with xmlFree.
587
 *
588
 * @param entry  hash table entry
589
 * @param key  the entry's string key
590
 */
591
void
592
50.9k
xmlHashDefaultDeallocator(void *entry, const xmlChar *key ATTRIBUTE_UNUSED) {
593
50.9k
    xmlFree(entry);
594
50.9k
}
595
596
/**
597
 * Add a hash table entry. If an entry with this key already exists,
598
 * payload will not be updated and 0 is returned. This return value
599
 * can't be distinguished from out-of-memory errors, so this function
600
 * should be used with care.
601
 *
602
 * @since 2.13.0
603
 *
604
 * @param hash  hash table
605
 * @param key  string key
606
 * @param payload  pointer to the payload
607
 * @returns 1 on success, 0 if an entry exists and -1 in case of error.
608
 */
609
int
610
92.6k
xmlHashAdd(xmlHashTable *hash, const xmlChar *key, void *payload) {
611
92.6k
    return(xmlHashUpdateInternal(hash, key, NULL, NULL, payload, NULL, 0));
612
92.6k
}
613
614
/**
615
 * Add a hash table entry with two strings as key.
616
 *
617
 * See #xmlHashAdd.
618
 *
619
 * @since 2.13.0
620
 *
621
 * @param hash  hash table
622
 * @param key  first string key
623
 * @param key2  second string key
624
 * @param payload  pointer to the payload
625
 * @returns 1 on success, 0 if an entry exists and -1 in case of error.
626
 */
627
int
628
xmlHashAdd2(xmlHashTable *hash, const xmlChar *key,
629
50.4k
                 const xmlChar *key2, void *payload) {
630
50.4k
    return(xmlHashUpdateInternal(hash, key, key2, NULL, payload, NULL, 0));
631
50.4k
}
632
633
/**
634
 * Add a hash table entry with three strings as key.
635
 *
636
 * See #xmlHashAdd.
637
 *
638
 * @since 2.13.0
639
 *
640
 * @param hash  hash table
641
 * @param key  first string key
642
 * @param key2  second string key
643
 * @param key3  third string key
644
 * @param payload  pointer to the payload
645
 * @returns 1 on success, 0 if an entry exists and -1 in case of error.
646
 */
647
int
648
xmlHashAdd3(xmlHashTable *hash, const xmlChar *key,
649
                 const xmlChar *key2, const xmlChar *key3,
650
45.2k
                 void *payload) {
651
45.2k
    return(xmlHashUpdateInternal(hash, key, key2, key3, payload, NULL, 0));
652
45.2k
}
653
654
/**
655
 * Add a hash table entry. If an entry with this key already exists,
656
 * payload will not be updated and -1 is returned. This return value
657
 * can't be distinguished from out-of-memory errors, so this function
658
 * should be used with care.
659
 *
660
 * NOTE: This function doesn't allow to distinguish malloc failures from
661
 *       existing entries. Use #xmlHashAdd instead.
662
 *
663
 * @param hash  hash table
664
 * @param key  string key
665
 * @param payload  pointer to the payload
666
 * @returns 0 on success and -1 in case of error.
667
 */
668
int
669
52.2k
xmlHashAddEntry(xmlHashTable *hash, const xmlChar *key, void *payload) {
670
52.2k
    int res = xmlHashUpdateInternal(hash, key, NULL, NULL, payload, NULL, 0);
671
672
52.2k
    if (res == 0)
673
0
        res = -1;
674
52.2k
    else if (res == 1)
675
52.2k
        res = 0;
676
677
52.2k
    return(res);
678
52.2k
}
679
680
/**
681
 * Add a hash table entry with two strings as key.
682
 *
683
 * See #xmlHashAddEntry.
684
 *
685
 * @param hash  hash table
686
 * @param key  first string key
687
 * @param key2  second string key
688
 * @param payload  pointer to the payload
689
 * @returns 0 on success and -1 in case of error.
690
 */
691
int
692
xmlHashAddEntry2(xmlHashTable *hash, const xmlChar *key,
693
0
                 const xmlChar *key2, void *payload) {
694
0
    int res = xmlHashUpdateInternal(hash, key, key2, NULL, payload, NULL, 0);
695
696
0
    if (res == 0)
697
0
        res = -1;
698
0
    else if (res == 1)
699
0
        res = 0;
700
701
0
    return(res);
702
0
}
703
704
/**
705
 * Add a hash table entry with three strings as key.
706
 *
707
 * See #xmlHashAddEntry.
708
 *
709
 * @param hash  hash table
710
 * @param key  first string key
711
 * @param key2  second string key
712
 * @param key3  third string key
713
 * @param payload  pointer to the payload
714
 * @returns 0 on success and -1 in case of error.
715
 */
716
int
717
xmlHashAddEntry3(xmlHashTable *hash, const xmlChar *key,
718
                 const xmlChar *key2, const xmlChar *key3,
719
0
                 void *payload) {
720
0
    int res = xmlHashUpdateInternal(hash, key, key2, key3, payload, NULL, 0);
721
722
0
    if (res == 0)
723
0
        res = -1;
724
0
    else if (res == 1)
725
0
        res = 0;
726
727
0
    return(res);
728
0
}
729
730
/**
731
 * Add a hash table entry. If an entry with this key already exists,
732
 * the old payload will be freed and updated with the new value.
733
 *
734
 * @param hash  hash table
735
 * @param key  string key
736
 * @param payload  pointer to the payload
737
 * @param dealloc  deallocator function for replaced item or NULL
738
 * @returns 0 in case of success, -1 if a memory allocation failed.
739
 */
740
int
741
xmlHashUpdateEntry(xmlHashTable *hash, const xmlChar *key,
742
43
                   void *payload, xmlHashDeallocator dealloc) {
743
43
    int res = xmlHashUpdateInternal(hash, key, NULL, NULL, payload,
744
43
                                    dealloc, 1);
745
746
43
    if (res == 1)
747
23
        res = 0;
748
749
43
    return(res);
750
43
}
751
752
/**
753
 * Add a hash table entry with two strings as key.
754
 *
755
 * See #xmlHashUpdateEntry.
756
 *
757
 * @param hash  hash table
758
 * @param key  first string key
759
 * @param key2  second string key
760
 * @param payload  pointer to the payload
761
 * @param dealloc  deallocator function for replaced item or NULL
762
 * @returns 0 on success and -1 in case of error.
763
 */
764
int
765
xmlHashUpdateEntry2(xmlHashTable *hash, const xmlChar *key,
766
                   const xmlChar *key2, void *payload,
767
3.32k
                   xmlHashDeallocator dealloc) {
768
3.32k
    int res = xmlHashUpdateInternal(hash, key, key2, NULL, payload,
769
3.32k
                                    dealloc, 1);
770
771
3.32k
    if (res == 1)
772
2.56k
        res = 0;
773
774
3.32k
    return(res);
775
3.32k
}
776
777
/**
778
 * Add a hash table entry with three strings as key.
779
 *
780
 * See #xmlHashUpdateEntry.
781
 *
782
 * @param hash  hash table
783
 * @param key  first string key
784
 * @param key2  second string key
785
 * @param key3  third string key
786
 * @param payload  pointer to the payload
787
 * @param dealloc  deallocator function for replaced item or NULL
788
 * @returns 0 on success and -1 in case of error.
789
 */
790
int
791
xmlHashUpdateEntry3(xmlHashTable *hash, const xmlChar *key,
792
                   const xmlChar *key2, const xmlChar *key3,
793
0
                   void *payload, xmlHashDeallocator dealloc) {
794
0
    int res = xmlHashUpdateInternal(hash, key, key2, key3, payload,
795
0
                                    dealloc, 1);
796
797
0
    if (res == 1)
798
0
        res = 0;
799
800
0
    return(res);
801
0
}
802
803
/**
804
 * Find the entry specified by `key`.
805
 *
806
 * @param hash  hash table
807
 * @param key  string key
808
 * @returns a pointer to the payload or NULL if no entry was found.
809
 */
810
void *
811
4.99M
xmlHashLookup(xmlHashTable *hash, const xmlChar *key) {
812
4.99M
    return(xmlHashLookup3(hash, key, NULL, NULL));
813
4.99M
}
814
815
/**
816
 * Find the payload specified by the (`key`, `key2`) tuple.
817
 *
818
 * @param hash  hash table
819
 * @param key  first string key
820
 * @param key2  second string key
821
 * @returns a pointer to the payload or NULL if no entry was found.
822
 */
823
void *
824
xmlHashLookup2(xmlHashTable *hash, const xmlChar *key,
825
569k
              const xmlChar *key2) {
826
569k
    return(xmlHashLookup3(hash, key, key2, NULL));
827
569k
}
828
829
/**
830
 * Find the payload specified by the QName `prefix:name` or `name`.
831
 *
832
 * @param hash  hash table
833
 * @param prefix  prefix of the string key
834
 * @param name  local name of the string key
835
 * @returns a pointer to the payload or NULL if no entry was found.
836
 */
837
void *
838
xmlHashQLookup(xmlHashTable *hash, const xmlChar *prefix,
839
0
               const xmlChar *name) {
840
0
    return(xmlHashQLookup3(hash, prefix, name, NULL, NULL, NULL, NULL));
841
0
}
842
843
/**
844
 * Find the payload specified by the QNames tuple.
845
 *
846
 * @param hash  hash table
847
 * @param prefix  first prefix
848
 * @param name  first local name
849
 * @param prefix2  second prefix
850
 * @param name2  second local name
851
 * @returns a pointer to the payload or NULL if no entry was found.
852
 */
853
void *
854
xmlHashQLookup2(xmlHashTable *hash, const xmlChar *prefix,
855
                const xmlChar *name, const xmlChar *prefix2,
856
56.0k
                const xmlChar *name2) {
857
56.0k
    return(xmlHashQLookup3(hash, prefix, name, prefix2, name2, NULL, NULL));
858
56.0k
}
859
860
/**
861
 * Find the payload specified by the (`key`, `key2`, `key3`) tuple.
862
 *
863
 * @param hash  hash table
864
 * @param key  first string key
865
 * @param key2  second string key
866
 * @param key3  third string key
867
 * @returns a pointer to the payload or NULL if no entry was found.
868
 */
869
void *
870
xmlHashLookup3(xmlHashTable *hash, const xmlChar *key,
871
6.13M
               const xmlChar *key2, const xmlChar *key3) {
872
6.13M
    const xmlHashEntry *entry;
873
6.13M
    unsigned hashValue;
874
6.13M
    int found;
875
876
6.13M
    if ((hash == NULL) || (hash->size == 0) || (key == NULL))
877
58.8k
        return(NULL);
878
6.07M
    hashValue = xmlHashValue(hash->randomSeed, key, key2, key3, NULL);
879
6.07M
    entry = xmlHashFindEntry(hash, key, key2, key3, hashValue, &found);
880
6.07M
    if (found)
881
5.33M
        return(entry->payload);
882
739k
    return(NULL);
883
6.07M
}
884
885
/**
886
 * Find the payload specified by the QNames tuple.
887
 *
888
 * @param hash  hash table
889
 * @param prefix  first prefix
890
 * @param name  first local name
891
 * @param prefix2  second prefix
892
 * @param name2  second local name
893
 * @param prefix3  third prefix
894
 * @param name3  third local name
895
 * @returns a pointer to the payload or NULL if no entry was found.
896
 */
897
ATTRIBUTE_NO_SANITIZE_INTEGER
898
void *
899
xmlHashQLookup3(xmlHashTable *hash,
900
                const xmlChar *prefix, const xmlChar *name,
901
                const xmlChar *prefix2, const xmlChar *name2,
902
56.0k
                const xmlChar *prefix3, const xmlChar *name3) {
903
56.0k
    const xmlHashEntry *entry;
904
56.0k
    unsigned hashValue, mask, pos, displ;
905
906
56.0k
    if ((hash == NULL) || (hash->size == 0) || (name == NULL))
907
0
        return(NULL);
908
909
56.0k
    hashValue = xmlHashQNameValue(hash->randomSeed, prefix, name, prefix2,
910
56.0k
                                  name2, prefix3, name3);
911
56.0k
    mask = hash->size - 1;
912
56.0k
    pos = hashValue & mask;
913
56.0k
    entry = &hash->table[pos];
914
915
56.0k
    if (entry->hashValue != 0) {
916
18.9k
        displ = 0;
917
18.9k
        hashValue |= MAX_HASH_SIZE;
918
919
20.7k
        do {
920
20.7k
            if ((hashValue == entry->hashValue) &&
921
20.7k
                (xmlStrQEqual(prefix, name, entry->key)) &&
922
20.7k
                (xmlStrQEqual(prefix2, name2, entry->key2)) &&
923
20.7k
                (xmlStrQEqual(prefix3, name3, entry->key3)))
924
9.17k
                return(entry->payload);
925
926
11.5k
            displ++;
927
11.5k
            pos++;
928
11.5k
            entry++;
929
11.5k
            if ((pos & mask) == 0)
930
404
                entry = hash->table;
931
11.5k
        } while ((entry->hashValue != 0) &&
932
11.5k
                 (((pos - entry->hashValue) & mask) >= displ));
933
18.9k
    }
934
935
46.9k
    return(NULL);
936
56.0k
}
937
938
typedef struct {
939
    xmlHashScanner scan;
940
    void *data;
941
} stubData;
942
943
static void
944
stubHashScannerFull(void *payload, void *data, const xmlChar *key,
945
                    const xmlChar *key2 ATTRIBUTE_UNUSED,
946
12.5k
                    const xmlChar *key3 ATTRIBUTE_UNUSED) {
947
12.5k
    stubData *sdata = (stubData *) data;
948
12.5k
    sdata->scan(payload, sdata->data, key);
949
12.5k
}
950
951
/**
952
 * Scan the hash `table` and apply `scan` to each value.
953
 *
954
 * @param hash  hash table
955
 * @param scan  scanner function for items in the hash
956
 * @param data  extra data passed to `scan`
957
 */
958
void
959
5.93k
xmlHashScan(xmlHashTable *hash, xmlHashScanner scan, void *data) {
960
5.93k
    stubData sdata;
961
5.93k
    sdata.data = data;
962
5.93k
    sdata.scan = scan;
963
5.93k
    xmlHashScanFull(hash, stubHashScannerFull, &sdata);
964
5.93k
}
965
966
/**
967
 * Scan the hash `table` and apply `scan` to each value.
968
 *
969
 * @param hash  hash table
970
 * @param scan  scanner function for items in the hash
971
 * @param data  extra data passed to `scan`
972
 */
973
void
974
8.97k
xmlHashScanFull(xmlHashTable *hash, xmlHashScannerFull scan, void *data) {
975
8.97k
    const xmlHashEntry *entry, *end;
976
8.97k
    xmlHashEntry old;
977
8.97k
    unsigned i;
978
979
8.97k
    if ((hash == NULL) || (hash->size == 0) || (scan == NULL))
980
327
        return;
981
982
    /*
983
     * We must handle the case that a scanned entry is removed when executing
984
     * the callback (xmlCleanSpecialAttr and possibly other places).
985
     *
986
     * Find the start of a probe sequence to avoid scanning entries twice if
987
     * a deletion happens.
988
     */
989
8.64k
    entry = hash->table;
990
8.64k
    end = &hash->table[hash->size];
991
12.0k
    while (entry->hashValue != 0) {
992
3.38k
        if (++entry >= end)
993
0
            entry = hash->table;
994
3.38k
    }
995
996
125k
    for (i = 0; i < hash->size; i++) {
997
116k
        if ((entry->hashValue != 0) && (entry->payload != NULL)) {
998
            /*
999
             * Make sure to rescan after a possible deletion.
1000
             */
1001
30.3k
            do {
1002
30.3k
                old = *entry;
1003
30.3k
                scan(entry->payload, data, entry->key, entry->key2, entry->key3);
1004
30.3k
            } while ((entry->hashValue != 0) &&
1005
30.3k
                     (entry->payload != NULL) &&
1006
30.3k
                     ((entry->key != old.key) ||
1007
28.9k
                      (entry->key2 != old.key2) ||
1008
28.9k
                      (entry->key3 != old.key3)));
1009
29.8k
        }
1010
116k
        if (++entry >= end)
1011
8.64k
            entry = hash->table;
1012
116k
    }
1013
8.64k
}
1014
1015
/**
1016
 * Scan the hash `table` and apply `scan` to each value matching
1017
 * (`key`, `key2`, `key3`) tuple. If one of the keys is null,
1018
 * the comparison is considered to match.
1019
 *
1020
 * @param hash  hash table
1021
 * @param key  first string key or NULL
1022
 * @param key2  second string key or NULL
1023
 * @param key3  third string key or NULL
1024
 * @param scan  scanner function for items in the hash
1025
 * @param data  extra data passed to `scan`
1026
 */
1027
void
1028
xmlHashScan3(xmlHashTable *hash, const xmlChar *key,
1029
             const xmlChar *key2, const xmlChar *key3,
1030
361
             xmlHashScanner scan, void *data) {
1031
361
    stubData sdata;
1032
361
    sdata.data = data;
1033
361
    sdata.scan = scan;
1034
361
    xmlHashScanFull3(hash, key, key2, key3, stubHashScannerFull, &sdata);
1035
361
}
1036
1037
/**
1038
 * Scan the hash `table` and apply `scan` to each value matching
1039
 * (`key`, `key2`, `key3`) tuple. If one of the keys is null,
1040
 * the comparison is considered to match.
1041
 *
1042
 * @param hash  hash table
1043
 * @param key  first string key or NULL
1044
 * @param key2  second string key or NULL
1045
 * @param key3  third string key or NULL
1046
 * @param scan  scanner function for items in the hash
1047
 * @param data  extra data passed to `scan`
1048
 */
1049
void
1050
xmlHashScanFull3(xmlHashTable *hash, const xmlChar *key,
1051
                 const xmlChar *key2, const xmlChar *key3,
1052
361
                 xmlHashScannerFull scan, void *data) {
1053
361
    const xmlHashEntry *entry, *end;
1054
361
    xmlHashEntry old;
1055
361
    unsigned i;
1056
1057
361
    if ((hash == NULL) || (hash->size == 0) || (scan == NULL))
1058
155
        return;
1059
1060
    /*
1061
     * We must handle the case that a scanned entry is removed when executing
1062
     * the callback (xmlCleanSpecialAttr and possibly other places).
1063
     *
1064
     * Find the start of a probe sequence to avoid scanning entries twice if
1065
     * a deletion happens.
1066
     */
1067
206
    entry = hash->table;
1068
206
    end = &hash->table[hash->size];
1069
286
    while (entry->hashValue != 0) {
1070
80
        if (++entry >= end)
1071
0
            entry = hash->table;
1072
80
    }
1073
1074
1.85k
    for (i = 0; i < hash->size; i++) {
1075
1.64k
        if ((entry->hashValue != 0) && (entry->payload != NULL)) {
1076
            /*
1077
             * Make sure to rescan after a possible deletion.
1078
             */
1079
263
            do {
1080
263
                if (((key != NULL) && (strcmp((const char *) key,
1081
0
                                              (const char *) entry->key) != 0)) ||
1082
263
                    ((key2 != NULL) && (!xmlFastStrEqual(key2, entry->key2))) ||
1083
263
                    ((key3 != NULL) && (!xmlFastStrEqual(key3, entry->key3))))
1084
253
                    break;
1085
10
                old = *entry;
1086
10
                scan(entry->payload, data, entry->key, entry->key2, entry->key3);
1087
10
            } while ((entry->hashValue != 0) &&
1088
10
                     (entry->payload != NULL) &&
1089
10
                     ((entry->key != old.key) ||
1090
10
                      (entry->key2 != old.key2) ||
1091
10
                      (entry->key3 != old.key3)));
1092
263
        }
1093
1.64k
        if (++entry >= end)
1094
206
            entry = hash->table;
1095
1.64k
    }
1096
206
}
1097
1098
/**
1099
 * @param hash  hash table
1100
 * @param copyFunc  copier function for items in the hash
1101
 * @param deallocFunc  deallocation function in case of errors
1102
 *
1103
 * Copy the hash table using `copyFunc` to copy payloads.
1104
 *
1105
 * @since 2.13.0
1106
 *
1107
 * @returns the new table or NULL if a memory allocation failed.
1108
 */
1109
xmlHashTable *
1110
xmlHashCopySafe(xmlHashTable *hash, xmlHashCopier copyFunc,
1111
0
                xmlHashDeallocator deallocFunc) {
1112
0
    const xmlHashEntry *entry, *end;
1113
0
    xmlHashTablePtr ret;
1114
1115
0
    if ((hash == NULL) || (copyFunc == NULL))
1116
0
        return(NULL);
1117
1118
0
    ret = xmlHashCreate(hash->size);
1119
0
    if (ret == NULL)
1120
0
        return(NULL);
1121
1122
0
    if (hash->size == 0)
1123
0
        return(ret);
1124
1125
0
    end = &hash->table[hash->size];
1126
1127
0
    for (entry = hash->table; entry < end; entry++) {
1128
0
        if (entry->hashValue != 0) {
1129
0
            void *copy;
1130
1131
0
            copy = copyFunc(entry->payload, entry->key);
1132
0
            if (copy == NULL)
1133
0
                goto error;
1134
0
            if (xmlHashAdd3(ret, entry->key, entry->key2, entry->key3,
1135
0
                            copy) <= 0) {
1136
0
                if (deallocFunc != NULL)
1137
0
                    deallocFunc(copy, entry->key);
1138
0
                goto error;
1139
0
            }
1140
0
        }
1141
0
    }
1142
1143
0
    return(ret);
1144
1145
0
error:
1146
0
    xmlHashFree(ret, deallocFunc);
1147
0
    return(NULL);
1148
0
}
1149
1150
/**
1151
 * @param hash  hash table
1152
 * @param copy  copier function for items in the hash
1153
 *
1154
 * @deprecated Leaks memory in error case.
1155
 *
1156
 * Copy the hash table using `copy` to copy payloads.
1157
 *
1158
 * @returns the new table or NULL if a memory allocation failed.
1159
 */
1160
xmlHashTable *
1161
0
xmlHashCopy(xmlHashTable *hash, xmlHashCopier copy) {
1162
0
    return(xmlHashCopySafe(hash, copy, NULL));
1163
0
}
1164
1165
/**
1166
 * Query the number of elements in the hash table.
1167
 *
1168
 * @param hash  hash table
1169
 * @returns the number of elements in the hash table or
1170
 * -1 in case of error.
1171
 */
1172
int
1173
3.04k
xmlHashSize(xmlHashTable *hash) {
1174
3.04k
    if (hash == NULL)
1175
0
        return(-1);
1176
3.04k
    return(hash->nbElems);
1177
3.04k
}
1178
1179
/**
1180
 * Find the entry specified by the `key` and remove it from the hash table.
1181
 * Payload will be freed with `dealloc`.
1182
 *
1183
 * @param hash  hash table
1184
 * @param key  string key
1185
 * @param dealloc  deallocator function for removed item or NULL
1186
 * @returns 0 on success and -1 if no entry was found.
1187
 */
1188
int xmlHashRemoveEntry(xmlHashTable *hash, const xmlChar *key,
1189
2.63k
                       xmlHashDeallocator dealloc) {
1190
2.63k
    return(xmlHashRemoveEntry3(hash, key, NULL, NULL, dealloc));
1191
2.63k
}
1192
1193
/**
1194
 * Remove an entry with two strings as key.
1195
 *
1196
 * See #xmlHashRemoveEntry.
1197
 *
1198
 * @param hash  hash table
1199
 * @param key  first string key
1200
 * @param key2  second string key
1201
 * @param dealloc  deallocator function for removed item or NULL
1202
 * @returns 0 on success and -1 in case of error.
1203
 */
1204
int
1205
xmlHashRemoveEntry2(xmlHashTable *hash, const xmlChar *key,
1206
2.00k
                    const xmlChar *key2, xmlHashDeallocator dealloc) {
1207
2.00k
    return(xmlHashRemoveEntry3(hash, key, key2, NULL, dealloc));
1208
2.00k
}
1209
1210
/**
1211
 * Remove an entry with three strings as key.
1212
 *
1213
 * See #xmlHashRemoveEntry.
1214
 *
1215
 * @param hash  hash table
1216
 * @param key  first string key
1217
 * @param key2  second string key
1218
 * @param key3  third string key
1219
 * @param dealloc  deallocator function for removed item or NULL
1220
 * @returns 0 on success and -1 in case of error.
1221
 */
1222
ATTRIBUTE_NO_SANITIZE_INTEGER
1223
int
1224
xmlHashRemoveEntry3(xmlHashTable *hash, const xmlChar *key,
1225
                    const xmlChar *key2, const xmlChar *key3,
1226
4.63k
                    xmlHashDeallocator dealloc) {
1227
4.63k
    xmlHashEntry *entry, *cur, *next;
1228
4.63k
    unsigned hashValue, mask, pos, nextpos;
1229
4.63k
    int found;
1230
1231
4.63k
    if ((hash == NULL) || (hash->size == 0) || (key == NULL))
1232
0
        return(-1);
1233
1234
4.63k
    hashValue = xmlHashValue(hash->randomSeed, key, key2, key3, NULL);
1235
4.63k
    entry = xmlHashFindEntry(hash, key, key2, key3, hashValue, &found);
1236
4.63k
    if (!found)
1237
0
        return(-1);
1238
1239
4.63k
    if ((dealloc != NULL) && (entry->payload != NULL))
1240
2.63k
        dealloc(entry->payload, entry->key);
1241
4.63k
    if (hash->dict == NULL) {
1242
2.67k
        if (entry->key)
1243
2.67k
            xmlFree(entry->key);
1244
2.67k
        if (entry->key2)
1245
6
            xmlFree(entry->key2);
1246
2.67k
        if (entry->key3)
1247
0
            xmlFree(entry->key3);
1248
2.67k
    }
1249
1250
    /*
1251
     * Find end of probe sequence. Entries at their initial probe
1252
     * position start a new sequence.
1253
     */
1254
4.63k
    mask = hash->size - 1;
1255
4.63k
    pos = entry - hash->table;
1256
4.63k
    cur = entry;
1257
1258
9.00k
    while (1) {
1259
9.00k
        nextpos = pos + 1;
1260
9.00k
        next = cur + 1;
1261
9.00k
        if ((nextpos & mask) == 0)
1262
861
            next = hash->table;
1263
1264
9.00k
        if ((next->hashValue == 0) ||
1265
9.00k
            (((next->hashValue - nextpos) & mask) == 0))
1266
4.63k
            break;
1267
1268
4.37k
        cur = next;
1269
4.37k
        pos = nextpos;
1270
4.37k
    }
1271
1272
    /*
1273
     * Backward shift
1274
     */
1275
4.63k
    next = entry + 1;
1276
1277
4.63k
    if (cur < entry) {
1278
420
        xmlHashEntry *end = &hash->table[hash->size];
1279
1280
420
        memmove(entry, next, (char *) end - (char *) next);
1281
420
        entry = hash->table;
1282
420
        end[-1] = *entry;
1283
420
        next = entry + 1;
1284
420
    }
1285
1286
4.63k
    memmove(entry, next, (char *) cur - (char *) entry);
1287
1288
    /*
1289
     * Update entry
1290
     */
1291
4.63k
    cur->hashValue = 0;
1292
1293
4.63k
    hash->nbElems--;
1294
1295
4.63k
    return(0);
1296
4.63k
}
1297