Coverage Report

Created: 2026-04-27 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libxml2/dict.c
Line
Count
Source
1
/*
2
 * dict.c: dictionary of reusable strings, just used to avoid allocation
3
 *         and freeing operations.
4
 *
5
 * Copyright (C) 2003-2012 Daniel Veillard.
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
12
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND
14
 * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER.
15
 *
16
 * Author: Daniel Veillard
17
 */
18
19
#define IN_LIBXML
20
#include "libxml.h"
21
22
#include <errno.h>
23
#include <limits.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include "private/dict.h"
28
#include "private/error.h"
29
#include "private/globals.h"
30
#include "private/threads.h"
31
32
#include <libxml/parser.h>
33
#include <libxml/dict.h>
34
#include <libxml/xmlmemory.h>
35
#include <libxml/xmlstring.h>
36
37
#ifndef SIZE_MAX
38
  #define SIZE_MAX ((size_t) -1)
39
#endif
40
41
3.62M
#define MAX_FILL_NUM 7
42
3.62M
#define MAX_FILL_DENOM 8
43
439k
#define MIN_HASH_SIZE 8
44
40.5M
#define MAX_HASH_SIZE (1u << 31)
45
46
typedef struct _xmlDictStrings xmlDictStrings;
47
typedef xmlDictStrings *xmlDictStringsPtr;
48
struct _xmlDictStrings {
49
    xmlDictStringsPtr next;
50
    xmlChar *free;
51
    xmlChar *end;
52
    size_t size;
53
    size_t nbStrings;
54
    xmlChar array[1];
55
};
56
57
typedef xmlHashedString xmlDictEntry;
58
59
/*
60
 * The entire dictionary
61
 */
62
struct _xmlDict {
63
    int ref_counter;
64
65
    xmlDictEntry *table;
66
    size_t size;
67
    unsigned int nbElems;
68
    xmlDictStringsPtr strings;
69
70
    struct _xmlDict *subdict;
71
    /* used for randomization */
72
    unsigned seed;
73
    /* used to impose a limit on size */
74
    size_t limit;
75
};
76
77
/*
78
 * A mutex for modifying the reference counter for shared
79
 * dictionaries.
80
 */
81
static xmlMutex xmlDictMutex;
82
83
/**
84
 * @deprecated Alias for #xmlInitParser.
85
 *
86
 * @returns 0.
87
 */
88
int
89
0
xmlInitializeDict(void) {
90
0
    xmlInitParser();
91
0
    return(0);
92
0
}
93
94
/**
95
 * Initialize mutex.
96
 */
97
void
98
9.97k
xmlInitDictInternal(void) {
99
9.97k
    xmlInitMutex(&xmlDictMutex);
100
9.97k
}
101
102
/**
103
 * @deprecated This function is a no-op. Call #xmlCleanupParser
104
 * to free global state but see the warnings there. #xmlCleanupParser
105
 * should be only called once at program exit. In most cases, you don't
106
 * have call cleanup functions at all.
107
 */
108
void
109
0
xmlDictCleanup(void) {
110
0
}
111
112
/**
113
 * Free the dictionary mutex.
114
 */
115
void
116
9.95k
xmlCleanupDictInternal(void) {
117
9.95k
    xmlCleanupMutex(&xmlDictMutex);
118
9.95k
}
119
120
/*
121
 * @param dict  the dictionary
122
 * @param name  the name of the userdata
123
 * @param len  the length of the name
124
 *
125
 * Add the string to the array[s]
126
 *
127
 * @returns the pointer of the local string, or NULL in case of error.
128
 */
129
static const xmlChar *
130
4.03M
xmlDictAddString(xmlDictPtr dict, const xmlChar *name, unsigned int namelen) {
131
4.03M
    xmlDictStringsPtr pool;
132
4.03M
    const xmlChar *ret;
133
4.03M
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
134
4.03M
    size_t limit = 0;
135
136
4.03M
    pool = dict->strings;
137
4.05M
    while (pool != NULL) {
138
3.60M
  if ((size_t)(pool->end - pool->free) > namelen)
139
3.58M
      goto found_pool;
140
17.9k
  if (pool->size > size) size = pool->size;
141
17.9k
        limit += pool->size;
142
17.9k
  pool = pool->next;
143
17.9k
    }
144
    /*
145
     * Not found, need to allocate
146
     */
147
448k
    if (pool == NULL) {
148
448k
        if ((dict->limit > 0) && (limit > dict->limit)) {
149
0
            return(NULL);
150
0
        }
151
152
448k
        if (size == 0) {
153
438k
            size = 1000;
154
438k
        } else {
155
10.0k
            if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
156
10.0k
                size *= 4; /* exponential growth */
157
0
            else
158
0
                size = SIZE_MAX - sizeof(xmlDictStrings);
159
10.0k
        }
160
448k
        if (size / 4 < namelen) {
161
5.17k
            if ((size_t) namelen + 0 < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
162
5.17k
                size = 4 * (size_t) namelen; /* just in case ! */
163
0
            else
164
0
                return(NULL);
165
5.17k
        }
166
448k
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
167
448k
  if (pool == NULL)
168
799
      return(NULL);
169
448k
  pool->size = size;
170
448k
  pool->nbStrings = 0;
171
448k
  pool->free = &pool->array[0];
172
448k
  pool->end = &pool->array[size];
173
448k
  pool->next = dict->strings;
174
448k
  dict->strings = pool;
175
448k
    }
176
4.03M
found_pool:
177
4.03M
    ret = pool->free;
178
4.03M
    memcpy(pool->free, name, namelen);
179
4.03M
    pool->free += namelen;
180
4.03M
    *(pool->free++) = 0;
181
4.03M
    pool->nbStrings++;
182
4.03M
    return(ret);
183
448k
}
184
185
/*
186
 * @param dict  the dictionary
187
 * @param prefix  the prefix of the userdata
188
 * @param plen  the prefix length
189
 * @param name  the name of the userdata
190
 * @param len  the length of the name
191
 *
192
 * Add the QName to the array[s]
193
 *
194
 * @returns the pointer of the local string, or NULL in case of error.
195
 */
196
static const xmlChar *
197
xmlDictAddQString(xmlDictPtr dict, const xmlChar *prefix, unsigned int plen,
198
                 const xmlChar *name, unsigned int namelen)
199
30.6k
{
200
30.6k
    xmlDictStringsPtr pool;
201
30.6k
    const xmlChar *ret;
202
30.6k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
203
30.6k
    size_t limit = 0;
204
205
30.6k
    pool = dict->strings;
206
35.4k
    while (pool != NULL) {
207
34.5k
  if ((size_t)(pool->end - pool->free) > namelen + plen + 1)
208
29.6k
      goto found_pool;
209
4.87k
  if (pool->size > size) size = pool->size;
210
4.87k
        limit += pool->size;
211
4.87k
  pool = pool->next;
212
4.87k
    }
213
    /*
214
     * Not found, need to allocate
215
     */
216
966
    if (pool == NULL) {
217
966
        if ((dict->limit > 0) && (limit > dict->limit)) {
218
0
            return(NULL);
219
0
        }
220
221
966
        if (size == 0) {
222
0
            size = 1000;
223
966
        } else {
224
966
            if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
225
966
                size *= 4; /* exponential growth */
226
0
            else
227
0
                size = SIZE_MAX - sizeof(xmlDictStrings);
228
966
        }
229
966
        if (size / 4 < namelen + plen + 1) {
230
0
            if ((size_t) namelen + plen + 1 <
231
0
                    (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
232
0
                size = 4 * ((size_t) namelen + plen + 1); /* just in case ! */
233
0
            else
234
0
                return(NULL);
235
0
        }
236
966
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
237
966
  if (pool == NULL)
238
15
      return(NULL);
239
951
  pool->size = size;
240
951
  pool->nbStrings = 0;
241
951
  pool->free = &pool->array[0];
242
951
  pool->end = &pool->array[size];
243
951
  pool->next = dict->strings;
244
951
  dict->strings = pool;
245
951
    }
246
30.5k
found_pool:
247
30.5k
    ret = pool->free;
248
30.5k
    memcpy(pool->free, prefix, plen);
249
30.5k
    pool->free += plen;
250
30.5k
    *(pool->free++) = ':';
251
30.5k
    memcpy(pool->free, name, namelen);
252
30.5k
    pool->free += namelen;
253
30.5k
    *(pool->free++) = 0;
254
30.5k
    pool->nbStrings++;
255
30.5k
    return(ret);
256
966
}
257
258
/**
259
 * Create a new dictionary
260
 *
261
 * @returns the newly created dictionary, or NULL if an error occurred.
262
 */
263
xmlDict *
264
536k
xmlDictCreate(void) {
265
536k
    xmlDictPtr dict;
266
267
536k
    xmlInitParser();
268
269
536k
    dict = xmlMalloc(sizeof(xmlDict));
270
536k
    if (dict == NULL)
271
74
        return(NULL);
272
536k
    dict->ref_counter = 1;
273
536k
    dict->limit = 0;
274
275
536k
    dict->size = 0;
276
536k
    dict->nbElems = 0;
277
536k
    dict->table = NULL;
278
536k
    dict->strings = NULL;
279
536k
    dict->subdict = NULL;
280
536k
    dict->seed = xmlRandom();
281
536k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
282
536k
    dict->seed = 0;
283
536k
#endif
284
536k
    return(dict);
285
536k
}
286
287
/**
288
 * Create a new dictionary, inheriting strings from the read-only
289
 * dictionary `sub`. On lookup, strings are first searched in the
290
 * new dictionary, then in `sub`, and if not found are created in the
291
 * new dictionary.
292
 *
293
 * @param sub  an existing dictionary
294
 * @returns the newly created dictionary, or NULL if an error occurred.
295
 */
296
xmlDict *
297
0
xmlDictCreateSub(xmlDict *sub) {
298
0
    xmlDictPtr dict = xmlDictCreate();
299
300
0
    if ((dict != NULL) && (sub != NULL)) {
301
0
        dict->seed = sub->seed;
302
0
        dict->subdict = sub;
303
0
  xmlDictReference(dict->subdict);
304
0
    }
305
0
    return(dict);
306
0
}
307
308
/**
309
 * Increment the reference counter of a dictionary
310
 *
311
 * @param dict  the dictionary
312
 * @returns 0 in case of success and -1 in case of error
313
 */
314
int
315
724k
xmlDictReference(xmlDict *dict) {
316
724k
    if (dict == NULL) return -1;
317
638k
    xmlMutexLock(&xmlDictMutex);
318
638k
    dict->ref_counter++;
319
638k
    xmlMutexUnlock(&xmlDictMutex);
320
638k
    return(0);
321
724k
}
322
323
/**
324
 * Free the hash `dict` and its contents. The userdata is
325
 * deallocated with `f` if provided.
326
 *
327
 * @param dict  the dictionary
328
 */
329
void
330
1.17M
xmlDictFree(xmlDict *dict) {
331
1.17M
    xmlDictStringsPtr pool, nextp;
332
333
1.17M
    if (dict == NULL)
334
2
  return;
335
336
    /* decrement the counter, it may be shared by a parser and docs */
337
1.17M
    xmlMutexLock(&xmlDictMutex);
338
1.17M
    dict->ref_counter--;
339
1.17M
    if (dict->ref_counter > 0) {
340
638k
        xmlMutexUnlock(&xmlDictMutex);
341
638k
        return;
342
638k
    }
343
344
536k
    xmlMutexUnlock(&xmlDictMutex);
345
346
536k
    if (dict->subdict != NULL) {
347
0
        xmlDictFree(dict->subdict);
348
0
    }
349
350
536k
    if (dict->table) {
351
438k
  xmlFree(dict->table);
352
438k
    }
353
536k
    pool = dict->strings;
354
985k
    while (pool != NULL) {
355
449k
        nextp = pool->next;
356
449k
  xmlFree(pool);
357
449k
  pool = nextp;
358
449k
    }
359
536k
    xmlFree(dict);
360
536k
}
361
362
/**
363
 * check if a string is owned by the dictionary
364
 *
365
 * @param dict  the dictionary
366
 * @param str  the string
367
 * @returns 1 if true, 0 if false and -1 in case of error
368
 * -1 in case of error
369
 */
370
int
371
23.1M
xmlDictOwns(xmlDict *dict, const xmlChar *str) {
372
23.1M
    xmlDictStringsPtr pool;
373
374
23.1M
    if ((dict == NULL) || (str == NULL))
375
0
  return(-1);
376
23.1M
    pool = dict->strings;
377
34.4M
    while (pool != NULL) {
378
26.0M
        if ((str >= &pool->array[0]) && (str <= pool->free))
379
14.6M
      return(1);
380
11.3M
  pool = pool->next;
381
11.3M
    }
382
8.45M
    if (dict->subdict)
383
0
        return(xmlDictOwns(dict->subdict, str));
384
8.45M
    return(0);
385
8.45M
}
386
387
/**
388
 * Query the number of elements installed in the hash `dict`.
389
 *
390
 * @param dict  the dictionary
391
 * @returns the number of elements in the dictionary or
392
 * -1 in case of error
393
 */
394
int
395
0
xmlDictSize(xmlDict *dict) {
396
0
    if (dict == NULL)
397
0
  return(-1);
398
0
    if (dict->subdict)
399
0
        return(dict->nbElems + dict->subdict->nbElems);
400
0
    return(dict->nbElems);
401
0
}
402
403
/**
404
 * Set a size limit for the dictionary
405
 * Added in 2.9.0
406
 *
407
 * @param dict  the dictionary
408
 * @param limit  the limit in bytes
409
 * @returns the previous limit of the dictionary or 0
410
 */
411
size_t
412
465k
xmlDictSetLimit(xmlDict *dict, size_t limit) {
413
465k
    size_t ret;
414
415
465k
    if (dict == NULL)
416
0
  return(0);
417
465k
    ret = dict->limit;
418
465k
    dict->limit = limit;
419
465k
    return(ret);
420
465k
}
421
422
/**
423
 * Get how much memory is used by a dictionary for strings
424
 * Added in 2.9.0
425
 *
426
 * @param dict  the dictionary
427
 * @returns the amount of strings allocated
428
 */
429
size_t
430
0
xmlDictGetUsage(xmlDict *dict) {
431
0
    xmlDictStringsPtr pool;
432
0
    size_t limit = 0;
433
434
0
    if (dict == NULL)
435
0
  return(0);
436
0
    pool = dict->strings;
437
0
    while (pool != NULL) {
438
0
        limit += pool->size;
439
0
  pool = pool->next;
440
0
    }
441
0
    return(limit);
442
0
}
443
444
/*****************************************************************
445
 *
446
 * The code below was rewritten and is additionally licensed under
447
 * the main license in file 'Copyright'.
448
 *
449
 *****************************************************************/
450
451
ATTRIBUTE_NO_SANITIZE_INTEGER
452
static unsigned
453
xmlDictHashName(unsigned seed, const xmlChar* data, size_t maxLen,
454
40.1M
                size_t *plen) {
455
40.1M
    unsigned h1, h2;
456
40.1M
    size_t i;
457
458
40.1M
    HASH_INIT(h1, h2, seed);
459
460
3.03G
    for (i = 0; i < maxLen && data[i]; i++) {
461
2.99G
        HASH_UPDATE(h1, h2, data[i]);
462
2.99G
    }
463
464
40.1M
    HASH_FINISH(h1, h2);
465
466
40.1M
    *plen = i;
467
40.1M
    return(h2 | MAX_HASH_SIZE);
468
40.1M
}
469
470
ATTRIBUTE_NO_SANITIZE_INTEGER
471
static unsigned
472
xmlDictHashQName(unsigned seed, const xmlChar *prefix, const xmlChar *name,
473
224k
                 size_t *pplen, size_t *plen) {
474
224k
    unsigned h1, h2;
475
224k
    size_t i;
476
477
224k
    HASH_INIT(h1, h2, seed);
478
479
3.26M
    for (i = 0; prefix[i] != 0; i++) {
480
3.04M
        HASH_UPDATE(h1, h2, prefix[i]);
481
3.04M
    }
482
224k
    *pplen = i;
483
484
224k
    HASH_UPDATE(h1, h2, ':');
485
486
111M
    for (i = 0; name[i] != 0; i++) {
487
111M
        HASH_UPDATE(h1, h2, name[i]);
488
111M
    }
489
224k
    *plen = i;
490
491
224k
    HASH_FINISH(h1, h2);
492
493
    /*
494
     * Always set the upper bit of hash values since 0 means an unoccupied
495
     * bucket.
496
     */
497
224k
    return(h2 | MAX_HASH_SIZE);
498
224k
}
499
500
/**
501
 * Compute the hash value of a C string.
502
 *
503
 * @param dict  dictionary
504
 * @param string  C string
505
 * @returns the hash value.
506
 */
507
unsigned
508
1.65M
xmlDictComputeHash(const xmlDict *dict, const xmlChar *string) {
509
1.65M
    size_t len;
510
1.65M
    return(xmlDictHashName(dict->seed, string, SIZE_MAX, &len));
511
1.65M
}
512
513
1.53M
#define HASH_ROL31(x,n) ((x) << (n) | ((x) & 0x7FFFFFFF) >> (31 - (n)))
514
515
/**
516
 * Combine two hash values.
517
 *
518
 * @param v1  first hash value
519
 * @param v2  second hash value
520
 * @returns the combined hash value.
521
 */
522
ATTRIBUTE_NO_SANITIZE_INTEGER
523
unsigned
524
1.53M
xmlDictCombineHash(unsigned v1, unsigned v2) {
525
    /*
526
     * The upper bit of hash values is always set, so we have to operate on
527
     * 31-bit hashes here.
528
     */
529
1.53M
    v1 ^= v2;
530
1.53M
    v1 += HASH_ROL31(v2, 5);
531
532
1.53M
    return((v1 & 0xFFFFFFFF) | 0x80000000);
533
1.53M
}
534
535
/**
536
 * Try to find a matching hash table entry. If an entry was found, set
537
 * `found` to 1 and return the entry. Otherwise, set `found` to 0 and return
538
 * the location where a new entry should be inserted.
539
 *
540
 * @param dict  dict
541
 * @param prefix  optional QName prefix
542
 * @param name  string
543
 * @param len  length of string
544
 * @param hashValue  valid hash value of string
545
 * @param pfound  result of search
546
 */
547
ATTRIBUTE_NO_SANITIZE_INTEGER
548
static xmlDictEntry *
549
xmlDictFindEntry(const xmlDict *dict, const xmlChar *prefix,
550
                 const xmlChar *name, int len, unsigned hashValue,
551
38.2M
                 int *pfound) {
552
38.2M
    xmlDictEntry *entry;
553
38.2M
    unsigned mask, pos, displ;
554
38.2M
    int found = 0;
555
556
38.2M
    mask = dict->size - 1;
557
38.2M
    pos = hashValue & mask;
558
38.2M
    entry = &dict->table[pos];
559
560
38.2M
    if (entry->hashValue != 0) {
561
        /*
562
         * Robin hood hashing: abort if the displacement of the entry
563
         * is smaller than the displacement of the key we look for.
564
         * This also stops at the correct position when inserting.
565
         */
566
36.2M
        displ = 0;
567
568
62.8M
        do {
569
62.8M
            if (entry->hashValue == hashValue) {
570
34.6M
                if (prefix == NULL) {
571
                    /*
572
                     * name is not necessarily null-terminated.
573
                     */
574
34.4M
                    if ((strncmp((const char *) entry->name,
575
34.4M
                                 (const char *) name, len) == 0) &&
576
34.4M
                        (entry->name[len] == 0)) {
577
34.4M
                        found = 1;
578
34.4M
                        break;
579
34.4M
                    }
580
34.4M
                } else {
581
194k
                    if (xmlStrQEqual(prefix, name, entry->name)) {
582
193k
                        found = 1;
583
193k
                        break;
584
193k
                    }
585
194k
                }
586
34.6M
            }
587
588
28.1M
            displ++;
589
28.1M
            pos++;
590
28.1M
            entry++;
591
28.1M
            if ((pos & mask) == 0)
592
1.17M
                entry = dict->table;
593
28.1M
        } while ((entry->hashValue != 0) &&
594
27.4M
                 (((pos - entry->hashValue) & mask) >= displ));
595
36.2M
    }
596
597
38.2M
    *pfound = found;
598
38.2M
    return(entry);
599
38.2M
}
600
601
/**
602
 * Resize the dictionary hash table.
603
 *
604
 * @param dict  dictionary
605
 * @param size  new size of the dictionary
606
 * @returns 0 in case of success, -1 if a memory allocation failed.
607
 */
608
static int
609
629k
xmlDictGrow(xmlDictPtr dict, unsigned size) {
610
629k
    const xmlDictEntry *oldentry, *oldend, *end;
611
629k
    xmlDictEntry *table;
612
629k
    unsigned oldsize, i;
613
614
    /* Add 0 to avoid spurious -Wtype-limits warning on 64-bit GCC */
615
629k
    if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0]))
616
0
        return(-1);
617
629k
    table = xmlMalloc(size * sizeof(table[0]));
618
629k
    if (table == NULL)
619
1.19k
        return(-1);
620
628k
    memset(table, 0, size * sizeof(table[0]));
621
622
628k
    oldsize = dict->size;
623
628k
    if (oldsize == 0)
624
438k
        goto done;
625
626
190k
    oldend = &dict->table[oldsize];
627
190k
    end = &table[size];
628
629
    /*
630
     * Robin Hood sorting order is maintained if we
631
     *
632
     * - compute dict indices with modulo
633
     * - resize by an integer factor
634
     * - start to copy from the beginning of a probe sequence
635
     */
636
190k
    oldentry = dict->table;
637
1.14M
    while (oldentry->hashValue != 0) {
638
953k
        if (++oldentry >= oldend)
639
0
            oldentry = dict->table;
640
953k
    }
641
642
3.63M
    for (i = 0; i < oldsize; i++) {
643
3.44M
        if (oldentry->hashValue != 0) {
644
3.01M
            xmlDictEntry *entry = &table[oldentry->hashValue & (size - 1)];
645
646
3.93M
            while (entry->hashValue != 0) {
647
926k
                if (++entry >= end)
648
69.4k
                    entry = table;
649
926k
            }
650
3.01M
            *entry = *oldentry;
651
3.01M
        }
652
653
3.44M
        if (++oldentry >= oldend)
654
190k
            oldentry = dict->table;
655
3.44M
    }
656
657
190k
    xmlFree(dict->table);
658
659
628k
done:
660
628k
    dict->table = table;
661
628k
    dict->size = size;
662
663
628k
    return(0);
664
190k
}
665
666
/**
667
 * Internal lookup and update function.
668
 *
669
 * @param dict  dict
670
 * @param prefix  optional QName prefix
671
 * @param name  string
672
 * @param maybeLen  length of string or -1 if unknown
673
 * @param update  whether the string should be added
674
 */
675
ATTRIBUTE_NO_SANITIZE_INTEGER
676
static const xmlDictEntry *
677
xmlDictLookupInternal(xmlDict *dict, const xmlChar *prefix,
678
38.6M
                      const xmlChar *name, int maybeLen, int update) {
679
38.6M
    xmlDictEntry *entry = NULL;
680
38.6M
    const xmlChar *ret;
681
38.6M
    unsigned hashValue, newSize;
682
38.6M
    size_t maxLen, len, plen, klen;
683
38.6M
    int found = 0;
684
685
38.6M
    if ((dict == NULL) || (name == NULL))
686
70
  return(NULL);
687
688
38.6M
    maxLen = (maybeLen < 0) ? SIZE_MAX : (size_t) maybeLen;
689
690
38.6M
    if (prefix == NULL) {
691
38.4M
        hashValue = xmlDictHashName(dict->seed, name, maxLen, &len);
692
38.4M
        if (len > INT_MAX / 2)
693
0
            return(NULL);
694
38.4M
        klen = len;
695
38.4M
    } else {
696
224k
        hashValue = xmlDictHashQName(dict->seed, prefix, name, &plen, &len);
697
224k
        if ((len > INT_MAX / 2) || (plen >= INT_MAX / 2 - len))
698
0
            return(NULL);
699
224k
        klen = plen + 1 + len;
700
224k
    }
701
702
38.6M
    if ((dict->limit > 0) && (klen >= dict->limit))
703
0
        return(NULL);
704
705
    /*
706
     * Check for an existing entry
707
     */
708
38.6M
    if (dict->size == 0) {
709
439k
        newSize = MIN_HASH_SIZE;
710
38.2M
    } else {
711
38.2M
        entry = xmlDictFindEntry(dict, prefix, name, klen, hashValue, &found);
712
38.2M
        if (found)
713
34.6M
            return(entry);
714
715
3.62M
        if (dict->nbElems + 1 > dict->size / MAX_FILL_DENOM * MAX_FILL_NUM) {
716
190k
            if (dict->size >= MAX_HASH_SIZE)
717
0
                return(NULL);
718
190k
            newSize = dict->size * 2;
719
3.43M
        } else {
720
3.43M
            newSize = 0;
721
3.43M
        }
722
3.62M
    }
723
724
4.06M
    if ((dict->subdict != NULL) && (dict->subdict->size > 0)) {
725
0
        xmlDictEntry *subEntry;
726
0
        unsigned subHashValue;
727
728
0
        if (prefix == NULL)
729
0
            subHashValue = xmlDictHashName(dict->subdict->seed, name, len,
730
0
                                           &len);
731
0
        else
732
0
            subHashValue = xmlDictHashQName(dict->subdict->seed, prefix, name,
733
0
                                            &plen, &len);
734
0
        subEntry = xmlDictFindEntry(dict->subdict, prefix, name, klen,
735
0
                                    subHashValue, &found);
736
0
        if (found)
737
0
            return(subEntry);
738
0
    }
739
740
4.06M
    if (!update)
741
0
        return(NULL);
742
743
    /*
744
     * Grow the hash table if needed
745
     */
746
4.06M
    if (newSize > 0) {
747
629k
        unsigned mask, displ, pos;
748
749
629k
        if (xmlDictGrow(dict, newSize) != 0)
750
1.19k
            return(NULL);
751
752
        /*
753
         * Find new entry
754
         */
755
628k
        mask = dict->size - 1;
756
628k
        displ = 0;
757
628k
        pos = hashValue & mask;
758
628k
        entry = &dict->table[pos];
759
760
724k
        while ((entry->hashValue != 0) &&
761
125k
               ((pos - entry->hashValue) & mask) >= displ) {
762
95.9k
            displ++;
763
95.9k
            pos++;
764
95.9k
            entry++;
765
95.9k
            if ((pos & mask) == 0)
766
10.0k
                entry = dict->table;
767
95.9k
        }
768
628k
    }
769
770
4.06M
    if (prefix == NULL)
771
4.03M
        ret = xmlDictAddString(dict, name, len);
772
30.6k
    else
773
30.6k
        ret = xmlDictAddQString(dict, prefix, plen, name, len);
774
4.06M
    if (ret == NULL)
775
814
        return(NULL);
776
777
    /*
778
     * Shift the remainder of the probe sequence to the right
779
     */
780
4.06M
    if (entry->hashValue != 0) {
781
788k
        const xmlDictEntry *end = &dict->table[dict->size];
782
788k
        const xmlDictEntry *cur = entry;
783
784
4.19M
        do {
785
4.19M
            cur++;
786
4.19M
            if (cur >= end)
787
148k
                cur = dict->table;
788
4.19M
        } while (cur->hashValue != 0);
789
790
788k
        if (cur < entry) {
791
            /*
792
             * If we traversed the end of the buffer, handle the part
793
             * at the start of the buffer.
794
             */
795
148k
            memmove(&dict->table[1], dict->table,
796
148k
                    (char *) cur - (char *) dict->table);
797
148k
            cur = end - 1;
798
148k
            dict->table[0] = *cur;
799
148k
        }
800
801
788k
        memmove(&entry[1], entry, (char *) cur - (char *) entry);
802
788k
    }
803
804
    /*
805
     * Populate entry
806
     */
807
4.06M
    entry->hashValue = hashValue;
808
4.06M
    entry->name = ret;
809
810
4.06M
    dict->nbElems++;
811
812
4.06M
    return(entry);
813
4.06M
}
814
815
/**
816
 * Lookup a string and add it to the dictionary if it wasn't found.
817
 *
818
 * @param dict  dictionary
819
 * @param name  string key
820
 * @param len  length of the key, if -1 it is recomputed
821
 * @returns the interned copy of the string or NULL if a memory allocation
822
 * failed.
823
 */
824
const xmlChar *
825
20.9M
xmlDictLookup(xmlDict *dict, const xmlChar *name, int len) {
826
20.9M
    const xmlDictEntry *entry;
827
828
20.9M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
829
20.9M
    if (entry == NULL)
830
1.68k
        return(NULL);
831
20.9M
    return(entry->name);
832
20.9M
}
833
834
/**
835
 * Lookup a dictionary entry and add the string to the dictionary if
836
 * it wasn't found.
837
 *
838
 * @param dict  dictionary
839
 * @param name  string key
840
 * @param len  length of the key, if -1 it is recomputed
841
 * @returns the dictionary entry.
842
 */
843
xmlHashedString
844
17.4M
xmlDictLookupHashed(xmlDict *dict, const xmlChar *name, int len) {
845
17.4M
    const xmlDictEntry *entry;
846
17.4M
    xmlHashedString ret;
847
848
17.4M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
849
850
17.4M
    if (entry == NULL) {
851
338
        ret.name = NULL;
852
338
        ret.hashValue = 0;
853
17.4M
    } else {
854
17.4M
        ret = *entry;
855
17.4M
    }
856
857
17.4M
    return(ret);
858
17.4M
}
859
860
/**
861
 * Check if a string exists in the dictionary.
862
 *
863
 * @param dict  the dictionary
864
 * @param name  the name of the userdata
865
 * @param len  the length of the name, if -1 it is recomputed
866
 * @returns the internal copy of the name or NULL if not found.
867
 */
868
const xmlChar *
869
0
xmlDictExists(xmlDict *dict, const xmlChar *name, int len) {
870
0
    const xmlDictEntry *entry;
871
872
0
    entry = xmlDictLookupInternal(dict, NULL, name, len, 0);
873
0
    if (entry == NULL)
874
0
        return(NULL);
875
0
    return(entry->name);
876
0
}
877
878
/**
879
 * Lookup the QName `prefix:name` and add it to the dictionary if
880
 * it wasn't found.
881
 *
882
 * @param dict  the dictionary
883
 * @param prefix  the prefix
884
 * @param name  the name
885
 * @returns the interned copy of the string or NULL if a memory allocation
886
 * failed.
887
 */
888
const xmlChar *
889
224k
xmlDictQLookup(xmlDict *dict, const xmlChar *prefix, const xmlChar *name) {
890
224k
    const xmlDictEntry *entry;
891
892
224k
    entry = xmlDictLookupInternal(dict, prefix, name, -1, 1);
893
224k
    if (entry == NULL)
894
52
        return(NULL);
895
224k
    return(entry->name);
896
224k
}
897
898
/*
899
 * Pseudo-random generator
900
 */
901
902
#ifdef _WIN32
903
  #define WIN32_LEAN_AND_MEAN
904
  #include <windows.h>
905
  #include <bcrypt.h>
906
#else
907
  #if HAVE_DECL_GETENTROPY
908
    /* POSIX 2024 */
909
    #include <unistd.h>
910
    /* Older platforms */
911
    #include <sys/random.h>
912
  #endif
913
  #include <time.h>
914
#endif
915
916
static xmlMutex xmlRngMutex;
917
918
static unsigned globalRngState[2];
919
920
/*
921
 *
922
 * Initialize the PRNG.
923
 */
924
ATTRIBUTE_NO_SANITIZE_INTEGER
925
void
926
9.97k
xmlInitRandom(void) {
927
9.97k
    xmlInitMutex(&xmlRngMutex);
928
929
9.97k
    {
930
#ifdef _WIN32
931
        NTSTATUS status;
932
933
        /*
934
         * You can find many (recent as of 2025) discussions how
935
         * to get a pseudo-random seed on Windows in projects like
936
         * Golang, Rust, Chromium and Firefox.
937
         *
938
         * TODO: Support ProcessPrng available since Windows 10.
939
         */
940
        status = BCryptGenRandom(NULL, (unsigned char *) globalRngState,
941
                                 sizeof(globalRngState),
942
                                 BCRYPT_USE_SYSTEM_PREFERRED_RNG);
943
        if (!BCRYPT_SUCCESS(status))
944
            xmlAbort("libxml2: BCryptGenRandom failed with error code %lu\n",
945
                     GetLastError());
946
#else
947
9.97k
        int var;
948
949
9.97k
#if HAVE_DECL_GETENTROPY
950
9.97k
        while (1) {
951
9.97k
            if (getentropy(globalRngState, sizeof(globalRngState)) == 0)
952
9.97k
                return;
953
954
            /*
955
             * This most likely means that libxml2 was compiled on
956
             * a system supporting certain system calls and is running
957
             * on a system that doesn't support these calls, as can
958
             * be the case on Linux.
959
             */
960
0
            if (errno == ENOSYS)
961
0
                break;
962
963
            /*
964
             * We really don't want to fallback to the unsafe PRNG
965
             * for possibly accidental reasons, so we abort on any
966
             * unknown error.
967
             */
968
0
            if (errno != EINTR)
969
0
                xmlAbort("libxml2: getentropy failed with error code %d\n",
970
0
                         errno);
971
0
        }
972
0
#endif
973
974
        /*
975
         * TODO: Fallback to /dev/urandom for older POSIX systems.
976
         */
977
0
        globalRngState[0] =
978
0
                (unsigned) time(NULL) ^
979
0
                HASH_ROL((unsigned) ((size_t) &xmlInitRandom & 0xFFFFFFFF), 8);
980
0
        globalRngState[1] =
981
0
                HASH_ROL((unsigned) ((size_t) &xmlRngMutex & 0xFFFFFFFF), 16) ^
982
0
                HASH_ROL((unsigned) ((size_t) &var & 0xFFFFFFFF), 24);
983
0
#endif
984
0
    }
985
0
}
986
987
/*
988
 *
989
 * Clean up PRNG globals.
990
 */
991
void
992
9.95k
xmlCleanupRandom(void) {
993
9.95k
    xmlCleanupMutex(&xmlRngMutex);
994
9.95k
}
995
996
ATTRIBUTE_NO_SANITIZE_INTEGER
997
static unsigned
998
1.19M
xoroshiro64ss(unsigned *s) {
999
1.19M
    unsigned s0 = s[0];
1000
1.19M
    unsigned s1 = s[1];
1001
1.19M
    unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5;
1002
1003
1.19M
    s1 ^= s0;
1004
1.19M
    s[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9);
1005
1.19M
    s[1] = HASH_ROL(s1, 13);
1006
1007
1.19M
    return(result & 0xFFFFFFFF);
1008
1.19M
}
1009
1010
/*
1011
 *
1012
 * Generate a pseudo-random value using the global PRNG.
1013
 *
1014
 * @returns a random value.
1015
 */
1016
unsigned
1017
19.9k
xmlGlobalRandom(void) {
1018
19.9k
    unsigned ret;
1019
1020
19.9k
    xmlMutexLock(&xmlRngMutex);
1021
19.9k
    ret = xoroshiro64ss(globalRngState);
1022
19.9k
    xmlMutexUnlock(&xmlRngMutex);
1023
1024
19.9k
    return(ret);
1025
19.9k
}
1026
1027
/*
1028
 *
1029
 * Generate a pseudo-random value using the thread-local PRNG.
1030
 *
1031
 * @returns a random value.
1032
 */
1033
unsigned
1034
1.17M
xmlRandom(void) {
1035
1.17M
    return(xoroshiro64ss(xmlGetLocalRngState()));
1036
1.17M
}
1037