Coverage Report

Created: 2026-07-16 06:17

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
755k
#define MAX_FILL_NUM 7
42
755k
#define MAX_FILL_DENOM 8
43
167k
#define MIN_HASH_SIZE 8
44
2.70M
#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
2
xmlInitDictInternal(void) {
99
2
    xmlInitMutex(&xmlDictMutex);
100
2
}
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
0
xmlCleanupDictInternal(void) {
117
0
    xmlCleanupMutex(&xmlDictMutex);
118
0
}
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
922k
xmlDictAddString(xmlDictPtr dict, const xmlChar *name, unsigned int namelen) {
131
922k
    xmlDictStringsPtr pool;
132
922k
    const xmlChar *ret;
133
922k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
134
922k
    size_t limit = 0;
135
136
922k
    pool = dict->strings;
137
923k
    while (pool != NULL) {
138
754k
  if ((size_t)(pool->end - pool->free) > namelen)
139
754k
      goto found_pool;
140
725
  if (pool->size > size) size = pool->size;
141
725
        limit += pool->size;
142
725
  pool = pool->next;
143
725
    }
144
    /*
145
     * Not found, need to allocate
146
     */
147
168k
    if (pool == NULL) {
148
168k
        if ((dict->limit > 0) && (limit > dict->limit)) {
149
0
            return(NULL);
150
0
        }
151
152
168k
        if (size == 0) {
153
167k
            size = 1000;
154
167k
        } else {
155
625
            if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
156
625
                size *= 4; /* exponential growth */
157
0
            else
158
0
                size = SIZE_MAX - sizeof(xmlDictStrings);
159
625
        }
160
168k
        if (size / 4 < namelen) {
161
340
            if ((size_t) namelen + 0 < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
162
340
                size = 4 * (size_t) namelen; /* just in case ! */
163
0
            else
164
0
                return(NULL);
165
340
        }
166
168k
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
167
168k
  if (pool == NULL)
168
34
      return(NULL);
169
168k
  pool->size = size;
170
168k
  pool->nbStrings = 0;
171
168k
  pool->free = &pool->array[0];
172
168k
  pool->end = &pool->array[size];
173
168k
  pool->next = dict->strings;
174
168k
  dict->strings = pool;
175
168k
    }
176
922k
found_pool:
177
922k
    ret = pool->free;
178
922k
    memcpy(pool->free, name, namelen);
179
922k
    pool->free += namelen;
180
922k
    *(pool->free++) = 0;
181
922k
    pool->nbStrings++;
182
922k
    return(ret);
183
168k
}
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
1.06k
{
200
1.06k
    xmlDictStringsPtr pool;
201
1.06k
    const xmlChar *ret;
202
1.06k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
203
1.06k
    size_t limit = 0;
204
205
1.06k
    pool = dict->strings;
206
1.49k
    while (pool != NULL) {
207
1.06k
  if ((size_t)(pool->end - pool->free) > namelen + plen + 1)
208
643
      goto found_pool;
209
426
  if (pool->size > size) size = pool->size;
210
426
        limit += pool->size;
211
426
  pool = pool->next;
212
426
    }
213
    /*
214
     * Not found, need to allocate
215
     */
216
426
    if (pool == NULL) {
217
426
        if ((dict->limit > 0) && (limit > dict->limit)) {
218
0
            return(NULL);
219
0
        }
220
221
426
        if (size == 0) {
222
0
            size = 1000;
223
426
        } else {
224
426
            if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
225
426
                size *= 4; /* exponential growth */
226
0
            else
227
0
                size = SIZE_MAX - sizeof(xmlDictStrings);
228
426
        }
229
426
        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
426
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
237
426
  if (pool == NULL)
238
1
      return(NULL);
239
425
  pool->size = size;
240
425
  pool->nbStrings = 0;
241
425
  pool->free = &pool->array[0];
242
425
  pool->end = &pool->array[size];
243
425
  pool->next = dict->strings;
244
425
  dict->strings = pool;
245
425
    }
246
1.06k
found_pool:
247
1.06k
    ret = pool->free;
248
1.06k
    memcpy(pool->free, prefix, plen);
249
1.06k
    pool->free += plen;
250
1.06k
    *(pool->free++) = ':';
251
1.06k
    memcpy(pool->free, name, namelen);
252
1.06k
    pool->free += namelen;
253
1.06k
    *(pool->free++) = 0;
254
1.06k
    pool->nbStrings++;
255
1.06k
    return(ret);
256
426
}
257
258
/**
259
 * Create a new dictionary
260
 *
261
 * @returns the newly created dictionary, or NULL if an error occurred.
262
 */
263
xmlDict *
264
181k
xmlDictCreate(void) {
265
181k
    xmlDictPtr dict;
266
267
181k
    xmlInitParser();
268
269
181k
    dict = xmlMalloc(sizeof(xmlDict));
270
181k
    if (dict == NULL)
271
22
        return(NULL);
272
181k
    dict->ref_counter = 1;
273
181k
    dict->limit = 0;
274
275
181k
    dict->size = 0;
276
181k
    dict->nbElems = 0;
277
181k
    dict->table = NULL;
278
181k
    dict->strings = NULL;
279
181k
    dict->subdict = NULL;
280
181k
    dict->seed = xmlRandom();
281
181k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
282
181k
    dict->seed = 0;
283
181k
#endif
284
181k
    return(dict);
285
181k
}
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
221k
xmlDictReference(xmlDict *dict) {
316
221k
    if (dict == NULL) return -1;
317
181k
    xmlMutexLock(&xmlDictMutex);
318
181k
    dict->ref_counter++;
319
181k
    xmlMutexUnlock(&xmlDictMutex);
320
181k
    return(0);
321
221k
}
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
362k
xmlDictFree(xmlDict *dict) {
331
362k
    xmlDictStringsPtr pool, nextp;
332
333
362k
    if (dict == NULL)
334
0
  return;
335
336
    /* decrement the counter, it may be shared by a parser and docs */
337
362k
    xmlMutexLock(&xmlDictMutex);
338
362k
    dict->ref_counter--;
339
362k
    if (dict->ref_counter > 0) {
340
181k
        xmlMutexUnlock(&xmlDictMutex);
341
181k
        return;
342
181k
    }
343
344
181k
    xmlMutexUnlock(&xmlDictMutex);
345
346
181k
    if (dict->subdict != NULL) {
347
0
        xmlDictFree(dict->subdict);
348
0
    }
349
350
181k
    if (dict->table) {
351
167k
  xmlFree(dict->table);
352
167k
    }
353
181k
    pool = dict->strings;
354
349k
    while (pool != NULL) {
355
168k
        nextp = pool->next;
356
168k
  xmlFree(pool);
357
168k
  pool = nextp;
358
168k
    }
359
181k
    xmlFree(dict);
360
181k
}
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
965k
xmlDictOwns(xmlDict *dict, const xmlChar *str) {
372
965k
    xmlDictStringsPtr pool;
373
374
965k
    if ((dict == NULL) || (str == NULL))
375
0
  return(-1);
376
965k
    pool = dict->strings;
377
1.29M
    while (pool != NULL) {
378
975k
        if ((str >= &pool->array[0]) && (str <= pool->free))
379
646k
      return(1);
380
328k
  pool = pool->next;
381
328k
    }
382
318k
    if (dict->subdict)
383
0
        return(xmlDictOwns(dict->subdict, str));
384
318k
    return(0);
385
318k
}
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
170k
xmlDictSetLimit(xmlDict *dict, size_t limit) {
413
170k
    size_t ret;
414
415
170k
    if (dict == NULL)
416
0
  return(0);
417
170k
    ret = dict->limit;
418
170k
    dict->limit = limit;
419
170k
    return(ret);
420
170k
}
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
2.64M
                size_t *plen) {
455
2.64M
    unsigned h1, h2;
456
2.64M
    size_t i;
457
458
2.64M
    HASH_INIT(h1, h2, seed);
459
460
21.5M
    for (i = 0; i < maxLen && data[i]; i++) {
461
18.8M
        HASH_UPDATE(h1, h2, data[i]);
462
18.8M
    }
463
464
2.64M
    HASH_FINISH(h1, h2);
465
466
2.64M
    *plen = i;
467
2.64M
    return(h2 | MAX_HASH_SIZE);
468
2.64M
}
469
470
ATTRIBUTE_NO_SANITIZE_INTEGER
471
static unsigned
472
xmlDictHashQName(unsigned seed, const xmlChar *prefix, const xmlChar *name,
473
24.4k
                 size_t *pplen, size_t *plen) {
474
24.4k
    unsigned h1, h2;
475
24.4k
    size_t i;
476
477
24.4k
    HASH_INIT(h1, h2, seed);
478
479
152k
    for (i = 0; prefix[i] != 0; i++) {
480
127k
        HASH_UPDATE(h1, h2, prefix[i]);
481
127k
    }
482
24.4k
    *pplen = i;
483
484
24.4k
    HASH_UPDATE(h1, h2, ':');
485
486
69.6k
    for (i = 0; name[i] != 0; i++) {
487
45.2k
        HASH_UPDATE(h1, h2, name[i]);
488
45.2k
    }
489
24.4k
    *plen = i;
490
491
24.4k
    HASH_FINISH(h1, h2);
492
493
    /*
494
     * Always set the upper bit of hash values since 0 means an unoccupied
495
     * bucket.
496
     */
497
24.4k
    return(h2 | MAX_HASH_SIZE);
498
24.4k
}
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
60.0k
xmlDictComputeHash(const xmlDict *dict, const xmlChar *string) {
509
60.0k
    size_t len;
510
60.0k
    return(xmlDictHashName(dict->seed, string, SIZE_MAX, &len));
511
60.0k
}
512
513
67.9k
#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
67.9k
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
67.9k
    v1 ^= v2;
530
67.9k
    v1 += HASH_ROL31(v2, 5);
531
532
67.9k
    return((v1 & 0xFFFFFFFF) | 0x80000000);
533
67.9k
}
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
2.44M
                 int *pfound) {
552
2.44M
    xmlDictEntry *entry;
553
2.44M
    unsigned mask, pos, displ;
554
2.44M
    int found = 0;
555
556
2.44M
    mask = dict->size - 1;
557
2.44M
    pos = hashValue & mask;
558
2.44M
    entry = &dict->table[pos];
559
560
2.44M
    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
1.87M
        displ = 0;
567
568
2.74M
        do {
569
2.74M
            if (entry->hashValue == hashValue) {
570
1.68M
                if (prefix == NULL) {
571
                    /*
572
                     * name is not necessarily null-terminated.
573
                     */
574
1.66M
                    if ((strncmp((const char *) entry->name,
575
1.66M
                                 (const char *) name, len) == 0) &&
576
1.66M
                        (entry->name[len] == 0)) {
577
1.66M
                        found = 1;
578
1.66M
                        break;
579
1.66M
                    }
580
1.66M
                } else {
581
23.3k
                    if (xmlStrQEqual(prefix, name, entry->name)) {
582
23.3k
                        found = 1;
583
23.3k
                        break;
584
23.3k
                    }
585
23.3k
                }
586
1.68M
            }
587
588
1.05M
            displ++;
589
1.05M
            pos++;
590
1.05M
            entry++;
591
1.05M
            if ((pos & mask) == 0)
592
238k
                entry = dict->table;
593
1.05M
        } while ((entry->hashValue != 0) &&
594
957k
                 (((pos - entry->hashValue) & mask) >= displ));
595
1.87M
    }
596
597
2.44M
    *pfound = found;
598
2.44M
    return(entry);
599
2.44M
}
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
197k
xmlDictGrow(xmlDictPtr dict, unsigned size) {
610
197k
    const xmlDictEntry *oldentry, *oldend, *end;
611
197k
    xmlDictEntry *table;
612
197k
    unsigned oldsize, i;
613
614
    /* Add 0 to avoid spurious -Wtype-limits warning on 64-bit GCC */
615
197k
    if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0]))
616
0
        return(-1);
617
197k
    table = xmlMalloc(size * sizeof(table[0]));
618
197k
    if (table == NULL)
619
61
        return(-1);
620
197k
    memset(table, 0, size * sizeof(table[0]));
621
622
197k
    oldsize = dict->size;
623
197k
    if (oldsize == 0)
624
167k
        goto done;
625
626
29.8k
    oldend = &dict->table[oldsize];
627
29.8k
    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
29.8k
    oldentry = dict->table;
637
137k
    while (oldentry->hashValue != 0) {
638
107k
        if (++oldentry >= oldend)
639
0
            oldentry = dict->table;
640
107k
    }
641
642
283k
    for (i = 0; i < oldsize; i++) {
643
254k
        if (oldentry->hashValue != 0) {
644
222k
            xmlDictEntry *entry = &table[oldentry->hashValue & (size - 1)];
645
646
282k
            while (entry->hashValue != 0) {
647
60.2k
                if (++entry >= end)
648
11.8k
                    entry = table;
649
60.2k
            }
650
222k
            *entry = *oldentry;
651
222k
        }
652
653
254k
        if (++oldentry >= oldend)
654
29.8k
            oldentry = dict->table;
655
254k
    }
656
657
29.8k
    xmlFree(dict->table);
658
659
197k
done:
660
197k
    dict->table = table;
661
197k
    dict->size = size;
662
663
197k
    return(0);
664
29.8k
}
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
2.61M
                      const xmlChar *name, int maybeLen, int update) {
679
2.61M
    xmlDictEntry *entry = NULL;
680
2.61M
    const xmlChar *ret;
681
2.61M
    unsigned hashValue, newSize;
682
2.61M
    size_t maxLen, len, plen, klen;
683
2.61M
    int found = 0;
684
685
2.61M
    if ((dict == NULL) || (name == NULL))
686
0
  return(NULL);
687
688
2.61M
    maxLen = (maybeLen < 0) ? SIZE_MAX : (size_t) maybeLen;
689
690
2.61M
    if (prefix == NULL) {
691
2.58M
        hashValue = xmlDictHashName(dict->seed, name, maxLen, &len);
692
2.58M
        if (len > INT_MAX / 2)
693
0
            return(NULL);
694
2.58M
        klen = len;
695
2.58M
    } else {
696
24.4k
        hashValue = xmlDictHashQName(dict->seed, prefix, name, &plen, &len);
697
24.4k
        if ((len > INT_MAX / 2) || (plen >= INT_MAX / 2 - len))
698
0
            return(NULL);
699
24.4k
        klen = plen + 1 + len;
700
24.4k
    }
701
702
2.61M
    if ((dict->limit > 0) && (klen >= dict->limit))
703
0
        return(NULL);
704
705
    /*
706
     * Check for an existing entry
707
     */
708
2.61M
    if (dict->size == 0) {
709
167k
        newSize = MIN_HASH_SIZE;
710
2.44M
    } else {
711
2.44M
        entry = xmlDictFindEntry(dict, prefix, name, klen, hashValue, &found);
712
2.44M
        if (found)
713
1.68M
            return(entry);
714
715
755k
        if (dict->nbElems + 1 > dict->size / MAX_FILL_DENOM * MAX_FILL_NUM) {
716
29.8k
            if (dict->size >= MAX_HASH_SIZE)
717
0
                return(NULL);
718
29.8k
            newSize = dict->size * 2;
719
725k
        } else {
720
725k
            newSize = 0;
721
725k
        }
722
755k
    }
723
724
923k
    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
923k
    if (!update)
741
0
        return(NULL);
742
743
    /*
744
     * Grow the hash table if needed
745
     */
746
923k
    if (newSize > 0) {
747
197k
        unsigned mask, displ, pos;
748
749
197k
        if (xmlDictGrow(dict, newSize) != 0)
750
61
            return(NULL);
751
752
        /*
753
         * Find new entry
754
         */
755
197k
        mask = dict->size - 1;
756
197k
        displ = 0;
757
197k
        pos = hashValue & mask;
758
197k
        entry = &dict->table[pos];
759
760
213k
        while ((entry->hashValue != 0) &&
761
20.4k
               ((pos - entry->hashValue) & mask) >= displ) {
762
15.5k
            displ++;
763
15.5k
            pos++;
764
15.5k
            entry++;
765
15.5k
            if ((pos & mask) == 0)
766
1.37k
                entry = dict->table;
767
15.5k
        }
768
197k
    }
769
770
923k
    if (prefix == NULL)
771
922k
        ret = xmlDictAddString(dict, name, len);
772
1.06k
    else
773
1.06k
        ret = xmlDictAddQString(dict, prefix, plen, name, len);
774
923k
    if (ret == NULL)
775
35
        return(NULL);
776
777
    /*
778
     * Shift the remainder of the probe sequence to the right
779
     */
780
923k
    if (entry->hashValue != 0) {
781
66.7k
        const xmlDictEntry *end = &dict->table[dict->size];
782
66.7k
        const xmlDictEntry *cur = entry;
783
784
120k
        do {
785
120k
            cur++;
786
120k
            if (cur >= end)
787
17.0k
                cur = dict->table;
788
120k
        } while (cur->hashValue != 0);
789
790
66.7k
        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
17.0k
            memmove(&dict->table[1], dict->table,
796
17.0k
                    (char *) cur - (char *) dict->table);
797
17.0k
            cur = end - 1;
798
17.0k
            dict->table[0] = *cur;
799
17.0k
        }
800
801
66.7k
        memmove(&entry[1], entry, (char *) cur - (char *) entry);
802
66.7k
    }
803
804
    /*
805
     * Populate entry
806
     */
807
923k
    entry->hashValue = hashValue;
808
923k
    entry->name = ret;
809
810
923k
    dict->nbElems++;
811
812
923k
    return(entry);
813
923k
}
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
1.82M
xmlDictLookup(xmlDict *dict, const xmlChar *name, int len) {
826
1.82M
    const xmlDictEntry *entry;
827
828
1.82M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
829
1.82M
    if (entry == NULL)
830
81
        return(NULL);
831
1.82M
    return(entry->name);
832
1.82M
}
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
765k
xmlDictLookupHashed(xmlDict *dict, const xmlChar *name, int len) {
845
765k
    const xmlDictEntry *entry;
846
765k
    xmlHashedString ret;
847
848
765k
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
849
850
765k
    if (entry == NULL) {
851
12
        ret.name = NULL;
852
12
        ret.hashValue = 0;
853
765k
    } else {
854
765k
        ret = *entry;
855
765k
    }
856
857
765k
    return(ret);
858
765k
}
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
24.4k
xmlDictQLookup(xmlDict *dict, const xmlChar *prefix, const xmlChar *name) {
890
24.4k
    const xmlDictEntry *entry;
891
892
24.4k
    entry = xmlDictLookupInternal(dict, prefix, name, -1, 1);
893
24.4k
    if (entry == NULL)
894
3
        return(NULL);
895
24.4k
    return(entry->name);
896
24.4k
}
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
2
xmlInitRandom(void) {
927
2
    xmlInitMutex(&xmlRngMutex);
928
929
2
    {
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
2
        int var;
948
949
2
#if HAVE_DECL_GETENTROPY
950
2
        while (1) {
951
2
            if (getentropy(globalRngState, sizeof(globalRngState)) == 0)
952
2
                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
0
xmlCleanupRandom(void) {
993
0
    xmlCleanupMutex(&xmlRngMutex);
994
0
}
995
996
ATTRIBUTE_NO_SANITIZE_INTEGER
997
static unsigned
998
344k
xoroshiro64ss(unsigned *s) {
999
344k
    unsigned s0 = s[0];
1000
344k
    unsigned s1 = s[1];
1001
344k
    unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5;
1002
1003
344k
    s1 ^= s0;
1004
344k
    s[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9);
1005
344k
    s[1] = HASH_ROL(s1, 13);
1006
1007
344k
    return(result & 0xFFFFFFFF);
1008
344k
}
1009
1010
/*
1011
 *
1012
 * Generate a pseudo-random value using the global PRNG.
1013
 *
1014
 * @returns a random value.
1015
 */
1016
unsigned
1017
4
xmlGlobalRandom(void) {
1018
4
    unsigned ret;
1019
1020
4
    xmlMutexLock(&xmlRngMutex);
1021
4
    ret = xoroshiro64ss(globalRngState);
1022
4
    xmlMutexUnlock(&xmlRngMutex);
1023
1024
4
    return(ret);
1025
4
}
1026
1027
/*
1028
 *
1029
 * Generate a pseudo-random value using the thread-local PRNG.
1030
 *
1031
 * @returns a random value.
1032
 */
1033
unsigned
1034
344k
xmlRandom(void) {
1035
344k
    return(xoroshiro64ss(xmlGetLocalRngState()));
1036
344k
}
1037