Coverage Report

Created: 2025-07-11 06:13

/src/libxml2/dict.c
Line
Count
Source (jump to first uncovered line)
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
2.20M
#define MAX_FILL_NUM 7
42
2.20M
#define MAX_FILL_DENOM 8
43
805k
#define MIN_HASH_SIZE 8
44
37.8M
#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
1.16M
xmlDictAddString(xmlDictPtr dict, const xmlChar *name, unsigned int namelen) {
131
1.16M
    xmlDictStringsPtr pool;
132
1.16M
    const xmlChar *ret;
133
1.16M
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
134
1.16M
    size_t limit = 0;
135
136
1.16M
    pool = dict->strings;
137
1.16M
    while (pool != NULL) {
138
1.09M
  if ((size_t)(pool->end - pool->free) > namelen)
139
1.08M
      goto found_pool;
140
3.69k
  if (pool->size > size) size = pool->size;
141
3.69k
        limit += pool->size;
142
3.69k
  pool = pool->next;
143
3.69k
    }
144
    /*
145
     * Not found, need to allocate
146
     */
147
72.6k
    if (pool == NULL) {
148
72.6k
        if ((dict->limit > 0) && (limit > dict->limit)) {
149
0
            return(NULL);
150
0
        }
151
152
72.6k
        if (size == 0) {
153
69.7k
            size = 1000;
154
69.7k
        } else {
155
2.86k
            if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
156
2.86k
                size *= 4; /* exponential growth */
157
0
            else
158
0
                size = SIZE_MAX - sizeof(xmlDictStrings);
159
2.86k
        }
160
72.6k
        if (size / 4 < namelen) {
161
894
            if ((size_t) namelen + 0 < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
162
894
                size = 4 * (size_t) namelen; /* just in case ! */
163
0
            else
164
0
                return(NULL);
165
894
        }
166
72.6k
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
167
72.6k
  if (pool == NULL)
168
732
      return(NULL);
169
71.9k
  pool->size = size;
170
71.9k
  pool->nbStrings = 0;
171
71.9k
  pool->free = &pool->array[0];
172
71.9k
  pool->end = &pool->array[size];
173
71.9k
  pool->next = dict->strings;
174
71.9k
  dict->strings = pool;
175
71.9k
    }
176
1.16M
found_pool:
177
1.16M
    ret = pool->free;
178
1.16M
    memcpy(pool->free, name, namelen);
179
1.16M
    pool->free += namelen;
180
1.16M
    *(pool->free++) = 0;
181
1.16M
    pool->nbStrings++;
182
1.16M
    return(ret);
183
72.6k
}
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
22.3k
{
200
22.3k
    xmlDictStringsPtr pool;
201
22.3k
    const xmlChar *ret;
202
22.3k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
203
22.3k
    size_t limit = 0;
204
205
22.3k
    pool = dict->strings;
206
22.4k
    while (pool != NULL) {
207
22.0k
  if ((size_t)(pool->end - pool->free) > namelen + plen + 1)
208
21.9k
      goto found_pool;
209
95
  if (pool->size > size) size = pool->size;
210
95
        limit += pool->size;
211
95
  pool = pool->next;
212
95
    }
213
    /*
214
     * Not found, need to allocate
215
     */
216
386
    if (pool == NULL) {
217
386
        if ((dict->limit > 0) && (limit > dict->limit)) {
218
0
            return(NULL);
219
0
        }
220
221
386
        if (size == 0) size = 1000;
222
28
  else size *= 4; /* exponential growth */
223
386
        if (size < 4 * (namelen + plen + 1))
224
1
      size = 4 * (namelen + plen + 1); /* just in case ! */
225
386
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
226
386
  if (pool == NULL)
227
1
      return(NULL);
228
385
  pool->size = size;
229
385
  pool->nbStrings = 0;
230
385
  pool->free = &pool->array[0];
231
385
  pool->end = &pool->array[size];
232
385
  pool->next = dict->strings;
233
385
  dict->strings = pool;
234
385
    }
235
22.3k
found_pool:
236
22.3k
    ret = pool->free;
237
22.3k
    memcpy(pool->free, prefix, plen);
238
22.3k
    pool->free += plen;
239
22.3k
    *(pool->free++) = ':';
240
22.3k
    memcpy(pool->free, name, namelen);
241
22.3k
    pool->free += namelen;
242
22.3k
    *(pool->free++) = 0;
243
22.3k
    pool->nbStrings++;
244
22.3k
    return(ret);
245
386
}
246
247
/**
248
 * Create a new dictionary
249
 *
250
 * @returns the newly created dictionary, or NULL if an error occurred.
251
 */
252
xmlDict *
253
1.56M
xmlDictCreate(void) {
254
1.56M
    xmlDictPtr dict;
255
256
1.56M
    xmlInitParser();
257
258
1.56M
    dict = xmlMalloc(sizeof(xmlDict));
259
1.56M
    if (dict == NULL)
260
356
        return(NULL);
261
1.56M
    dict->ref_counter = 1;
262
1.56M
    dict->limit = 0;
263
264
1.56M
    dict->size = 0;
265
1.56M
    dict->nbElems = 0;
266
1.56M
    dict->table = NULL;
267
1.56M
    dict->strings = NULL;
268
1.56M
    dict->subdict = NULL;
269
1.56M
    dict->seed = xmlRandom();
270
1.56M
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
271
1.56M
    dict->seed = 0;
272
1.56M
#endif
273
1.56M
    return(dict);
274
1.56M
}
275
276
/**
277
 * Create a new dictionary, inheriting strings from the read-only
278
 * dictionary `sub`. On lookup, strings are first searched in the
279
 * new dictionary, then in `sub`, and if not found are created in the
280
 * new dictionary.
281
 *
282
 * @param sub  an existing dictionary
283
 * @returns the newly created dictionary, or NULL if an error occurred.
284
 */
285
xmlDict *
286
17.2k
xmlDictCreateSub(xmlDict *sub) {
287
17.2k
    xmlDictPtr dict = xmlDictCreate();
288
289
17.2k
    if ((dict != NULL) && (sub != NULL)) {
290
17.2k
        dict->seed = sub->seed;
291
17.2k
        dict->subdict = sub;
292
17.2k
  xmlDictReference(dict->subdict);
293
17.2k
    }
294
17.2k
    return(dict);
295
17.2k
}
296
297
/**
298
 * Increment the reference counter of a dictionary
299
 *
300
 * @param dict  the dictionary
301
 * @returns 0 in case of success and -1 in case of error
302
 */
303
int
304
2.94M
xmlDictReference(xmlDict *dict) {
305
2.94M
    if (dict == NULL) return -1;
306
2.93M
    xmlMutexLock(&xmlDictMutex);
307
2.93M
    dict->ref_counter++;
308
2.93M
    xmlMutexUnlock(&xmlDictMutex);
309
2.93M
    return(0);
310
2.94M
}
311
312
/**
313
 * Free the hash `dict` and its contents. The userdata is
314
 * deallocated with `f` if provided.
315
 *
316
 * @param dict  the dictionary
317
 */
318
void
319
4.50M
xmlDictFree(xmlDict *dict) {
320
4.50M
    xmlDictStringsPtr pool, nextp;
321
322
4.50M
    if (dict == NULL)
323
312
  return;
324
325
    /* decrement the counter, it may be shared by a parser and docs */
326
4.49M
    xmlMutexLock(&xmlDictMutex);
327
4.49M
    dict->ref_counter--;
328
4.49M
    if (dict->ref_counter > 0) {
329
2.93M
        xmlMutexUnlock(&xmlDictMutex);
330
2.93M
        return;
331
2.93M
    }
332
333
1.56M
    xmlMutexUnlock(&xmlDictMutex);
334
335
1.56M
    if (dict->subdict != NULL) {
336
17.2k
        xmlDictFree(dict->subdict);
337
17.2k
    }
338
339
1.56M
    if (dict->table) {
340
69.9k
  xmlFree(dict->table);
341
69.9k
    }
342
1.56M
    pool = dict->strings;
343
1.64M
    while (pool != NULL) {
344
72.3k
        nextp = pool->next;
345
72.3k
  xmlFree(pool);
346
72.3k
  pool = nextp;
347
72.3k
    }
348
1.56M
    xmlFree(dict);
349
1.56M
}
350
351
/**
352
 * check if a string is owned by the dictionary
353
 *
354
 * @param dict  the dictionary
355
 * @param str  the string
356
 * @returns 1 if true, 0 if false and -1 in case of error
357
 * -1 in case of error
358
 */
359
int
360
38.3M
xmlDictOwns(xmlDict *dict, const xmlChar *str) {
361
38.3M
    xmlDictStringsPtr pool;
362
363
38.3M
    if ((dict == NULL) || (str == NULL))
364
0
  return(-1);
365
38.3M
    pool = dict->strings;
366
60.3M
    while (pool != NULL) {
367
43.0M
        if ((str >= &pool->array[0]) && (str <= pool->free))
368
21.1M
      return(1);
369
21.9M
  pool = pool->next;
370
21.9M
    }
371
17.2M
    if (dict->subdict)
372
5.69M
        return(xmlDictOwns(dict->subdict, str));
373
11.5M
    return(0);
374
17.2M
}
375
376
/**
377
 * Query the number of elements installed in the hash `dict`.
378
 *
379
 * @param dict  the dictionary
380
 * @returns the number of elements in the dictionary or
381
 * -1 in case of error
382
 */
383
int
384
0
xmlDictSize(xmlDict *dict) {
385
0
    if (dict == NULL)
386
0
  return(-1);
387
0
    if (dict->subdict)
388
0
        return(dict->nbElems + dict->subdict->nbElems);
389
0
    return(dict->nbElems);
390
0
}
391
392
/**
393
 * Set a size limit for the dictionary
394
 * Added in 2.9.0
395
 *
396
 * @param dict  the dictionary
397
 * @param limit  the limit in bytes
398
 * @returns the previous limit of the dictionary or 0
399
 */
400
size_t
401
851k
xmlDictSetLimit(xmlDict *dict, size_t limit) {
402
851k
    size_t ret;
403
404
851k
    if (dict == NULL)
405
0
  return(0);
406
851k
    ret = dict->limit;
407
851k
    dict->limit = limit;
408
851k
    return(ret);
409
851k
}
410
411
/**
412
 * Get how much memory is used by a dictionary for strings
413
 * Added in 2.9.0
414
 *
415
 * @param dict  the dictionary
416
 * @returns the amount of strings allocated
417
 */
418
size_t
419
0
xmlDictGetUsage(xmlDict *dict) {
420
0
    xmlDictStringsPtr pool;
421
0
    size_t limit = 0;
422
423
0
    if (dict == NULL)
424
0
  return(0);
425
0
    pool = dict->strings;
426
0
    while (pool != NULL) {
427
0
        limit += pool->size;
428
0
  pool = pool->next;
429
0
    }
430
0
    return(limit);
431
0
}
432
433
/*****************************************************************
434
 *
435
 * The code below was rewritten and is additionally licensed under
436
 * the main license in file 'Copyright'.
437
 *
438
 *****************************************************************/
439
440
ATTRIBUTE_NO_SANITIZE_INTEGER
441
static unsigned
442
xmlDictHashName(unsigned seed, const xmlChar* data, size_t maxLen,
443
37.5M
                size_t *plen) {
444
37.5M
    unsigned h1, h2;
445
37.5M
    size_t i;
446
447
37.5M
    HASH_INIT(h1, h2, seed);
448
449
1.72G
    for (i = 0; i < maxLen && data[i]; i++) {
450
1.68G
        HASH_UPDATE(h1, h2, data[i]);
451
1.68G
    }
452
453
37.5M
    HASH_FINISH(h1, h2);
454
455
37.5M
    *plen = i;
456
37.5M
    return(h2 | MAX_HASH_SIZE);
457
37.5M
}
458
459
ATTRIBUTE_NO_SANITIZE_INTEGER
460
static unsigned
461
xmlDictHashQName(unsigned seed, const xmlChar *prefix, const xmlChar *name,
462
199k
                 size_t *pplen, size_t *plen) {
463
199k
    unsigned h1, h2;
464
199k
    size_t i;
465
466
199k
    HASH_INIT(h1, h2, seed);
467
468
842k
    for (i = 0; prefix[i] != 0; i++) {
469
642k
        HASH_UPDATE(h1, h2, prefix[i]);
470
642k
    }
471
199k
    *pplen = i;
472
473
199k
    HASH_UPDATE(h1, h2, ':');
474
475
1.76M
    for (i = 0; name[i] != 0; i++) {
476
1.56M
        HASH_UPDATE(h1, h2, name[i]);
477
1.56M
    }
478
199k
    *plen = i;
479
480
199k
    HASH_FINISH(h1, h2);
481
482
    /*
483
     * Always set the upper bit of hash values since 0 means an unoccupied
484
     * bucket.
485
     */
486
199k
    return(h2 | MAX_HASH_SIZE);
487
199k
}
488
489
/**
490
 * Compute the hash value of a C string.
491
 *
492
 * @param dict  dictionary
493
 * @param string  C string
494
 * @returns the hash value.
495
 */
496
unsigned
497
3.26M
xmlDictComputeHash(const xmlDict *dict, const xmlChar *string) {
498
3.26M
    size_t len;
499
3.26M
    return(xmlDictHashName(dict->seed, string, SIZE_MAX, &len));
500
3.26M
}
501
502
1.14M
#define HASH_ROL31(x,n) ((x) << (n) | ((x) & 0x7FFFFFFF) >> (31 - (n)))
503
504
/**
505
 * Combine two hash values.
506
 *
507
 * @param v1  first hash value
508
 * @param v2  second hash value
509
 * @returns the combined hash value.
510
 */
511
ATTRIBUTE_NO_SANITIZE_INTEGER
512
unsigned
513
1.14M
xmlDictCombineHash(unsigned v1, unsigned v2) {
514
    /*
515
     * The upper bit of hash values is always set, so we have to operate on
516
     * 31-bit hashes here.
517
     */
518
1.14M
    v1 ^= v2;
519
1.14M
    v1 += HASH_ROL31(v2, 5);
520
521
1.14M
    return((v1 & 0xFFFFFFFF) | 0x80000000);
522
1.14M
}
523
524
/**
525
 * Try to find a matching hash table entry. If an entry was found, set
526
 * `found` to 1 and return the entry. Otherwise, set `found` to 0 and return
527
 * the location where a new entry should be inserted.
528
 *
529
 * @param dict  dict
530
 * @param prefix  optional QName prefix
531
 * @param name  string
532
 * @param len  length of string
533
 * @param hashValue  valid hash value of string
534
 * @param pfound  result of search
535
 */
536
ATTRIBUTE_NO_SANITIZE_INTEGER
537
static xmlDictEntry *
538
xmlDictFindEntry(const xmlDict *dict, const xmlChar *prefix,
539
                 const xmlChar *name, int len, unsigned hashValue,
540
33.6M
                 int *pfound) {
541
33.6M
    xmlDictEntry *entry;
542
33.6M
    unsigned mask, pos, displ;
543
33.6M
    int found = 0;
544
545
33.6M
    mask = dict->size - 1;
546
33.6M
    pos = hashValue & mask;
547
33.6M
    entry = &dict->table[pos];
548
549
33.6M
    if (entry->hashValue != 0) {
550
        /*
551
         * Robin hood hashing: abort if the displacement of the entry
552
         * is smaller than the displacement of the key we look for.
553
         * This also stops at the correct position when inserting.
554
         */
555
32.3M
        displ = 0;
556
557
53.7M
        do {
558
53.7M
            if (entry->hashValue == hashValue) {
559
31.4M
                if (prefix == NULL) {
560
                    /*
561
                     * name is not necessarily null-terminated.
562
                     */
563
31.2M
                    if ((strncmp((const char *) entry->name,
564
31.2M
                                 (const char *) name, len) == 0) &&
565
31.2M
                        (entry->name[len] == 0)) {
566
31.2M
                        found = 1;
567
31.2M
                        break;
568
31.2M
                    }
569
31.2M
                } else {
570
176k
                    if (xmlStrQEqual(prefix, name, entry->name)) {
571
176k
                        found = 1;
572
176k
                        break;
573
176k
                    }
574
176k
                }
575
31.4M
            }
576
577
22.3M
            displ++;
578
22.3M
            pos++;
579
22.3M
            entry++;
580
22.3M
            if ((pos & mask) == 0)
581
772k
                entry = dict->table;
582
22.3M
        } while ((entry->hashValue != 0) &&
583
22.3M
                 (((pos - entry->hashValue) & mask) >= displ));
584
32.3M
    }
585
586
0
    *pfound = found;
587
33.6M
    return(entry);
588
33.6M
}
589
590
/**
591
 * Resize the dictionary hash table.
592
 *
593
 * @param dict  dictionary
594
 * @param size  new size of the dictionary
595
 * @returns 0 in case of success, -1 if a memory allocation failed.
596
 */
597
static int
598
162k
xmlDictGrow(xmlDictPtr dict, unsigned size) {
599
162k
    const xmlDictEntry *oldentry, *oldend, *end;
600
162k
    xmlDictEntry *table;
601
162k
    unsigned oldsize, i;
602
603
    /* Add 0 to avoid spurious -Wtype-limits warning on 64-bit GCC */
604
162k
    if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0]))
605
0
        return(-1);
606
162k
    table = xmlMalloc(size * sizeof(table[0]));
607
162k
    if (table == NULL)
608
1.33k
        return(-1);
609
161k
    memset(table, 0, size * sizeof(table[0]));
610
611
161k
    oldsize = dict->size;
612
161k
    if (oldsize == 0)
613
69.9k
        goto done;
614
615
91.5k
    oldend = &dict->table[oldsize];
616
91.5k
    end = &table[size];
617
618
    /*
619
     * Robin Hood sorting order is maintained if we
620
     *
621
     * - compute dict indices with modulo
622
     * - resize by an integer factor
623
     * - start to copy from the beginning of a probe sequence
624
     */
625
91.5k
    oldentry = dict->table;
626
665k
    while (oldentry->hashValue != 0) {
627
574k
        if (++oldentry >= oldend)
628
0
            oldentry = dict->table;
629
574k
    }
630
631
1.48M
    for (i = 0; i < oldsize; i++) {
632
1.39M
        if (oldentry->hashValue != 0) {
633
1.22M
            xmlDictEntry *entry = &table[oldentry->hashValue & (size - 1)];
634
635
1.55M
            while (entry->hashValue != 0) {
636
335k
                if (++entry >= end)
637
21.3k
                    entry = table;
638
335k
            }
639
1.22M
            *entry = *oldentry;
640
1.22M
        }
641
642
1.39M
        if (++oldentry >= oldend)
643
91.5k
            oldentry = dict->table;
644
1.39M
    }
645
646
91.5k
    xmlFree(dict->table);
647
648
161k
done:
649
161k
    dict->table = table;
650
161k
    dict->size = size;
651
652
161k
    return(0);
653
91.5k
}
654
655
/**
656
 * Internal lookup and update function.
657
 *
658
 * @param dict  dict
659
 * @param prefix  optional QName prefix
660
 * @param name  string
661
 * @param maybeLen  length of string or -1 if unknown
662
 * @param update  whether the string should be added
663
 */
664
ATTRIBUTE_NO_SANITIZE_INTEGER
665
static const xmlDictEntry *
666
xmlDictLookupInternal(xmlDict *dict, const xmlChar *prefix,
667
32.6M
                      const xmlChar *name, int maybeLen, int update) {
668
32.6M
    xmlDictEntry *entry = NULL;
669
32.6M
    const xmlChar *ret;
670
32.6M
    unsigned hashValue, newSize;
671
32.6M
    size_t maxLen, len, plen, klen;
672
32.6M
    int found = 0;
673
674
32.6M
    if ((dict == NULL) || (name == NULL))
675
12.3k
  return(NULL);
676
677
32.6M
    maxLen = (maybeLen < 0) ? SIZE_MAX : (size_t) maybeLen;
678
679
32.6M
    if (prefix == NULL) {
680
32.4M
        hashValue = xmlDictHashName(dict->seed, name, maxLen, &len);
681
32.4M
        if (len > INT_MAX / 2)
682
0
            return(NULL);
683
32.4M
        klen = len;
684
32.4M
    } else {
685
199k
        hashValue = xmlDictHashQName(dict->seed, prefix, name, &plen, &len);
686
199k
        if ((len > INT_MAX / 2) || (plen >= INT_MAX / 2 - len))
687
0
            return(NULL);
688
199k
        klen = plen + 1 + len;
689
199k
    }
690
691
32.6M
    if ((dict->limit > 0) && (klen >= dict->limit))
692
0
        return(NULL);
693
694
    /*
695
     * Check for an existing entry
696
     */
697
32.6M
    if (dict->size == 0) {
698
805k
        newSize = MIN_HASH_SIZE;
699
31.8M
    } else {
700
31.8M
        entry = xmlDictFindEntry(dict, prefix, name, klen, hashValue, &found);
701
31.8M
        if (found)
702
29.5M
            return(entry);
703
704
2.20M
        if (dict->nbElems + 1 > dict->size / MAX_FILL_DENOM * MAX_FILL_NUM) {
705
100k
            if (dict->size >= MAX_HASH_SIZE)
706
0
                return(NULL);
707
100k
            newSize = dict->size * 2;
708
2.10M
        } else {
709
2.10M
            newSize = 0;
710
2.10M
        }
711
2.20M
    }
712
713
3.01M
    if ((dict->subdict != NULL) && (dict->subdict->size > 0)) {
714
1.83M
        xmlDictEntry *subEntry;
715
1.83M
        unsigned subHashValue;
716
717
1.83M
        if (prefix == NULL)
718
1.83M
            subHashValue = xmlDictHashName(dict->subdict->seed, name, len,
719
1.83M
                                           &len);
720
587
        else
721
587
            subHashValue = xmlDictHashQName(dict->subdict->seed, prefix, name,
722
587
                                            &plen, &len);
723
1.83M
        subEntry = xmlDictFindEntry(dict->subdict, prefix, name, klen,
724
1.83M
                                    subHashValue, &found);
725
1.83M
        if (found)
726
1.82M
            return(subEntry);
727
1.83M
    }
728
729
1.18M
    if (!update)
730
0
        return(NULL);
731
732
    /*
733
     * Grow the hash table if needed
734
     */
735
1.18M
    if (newSize > 0) {
736
162k
        unsigned mask, displ, pos;
737
738
162k
        if (xmlDictGrow(dict, newSize) != 0)
739
1.33k
            return(NULL);
740
741
        /*
742
         * Find new entry
743
         */
744
161k
        mask = dict->size - 1;
745
161k
        displ = 0;
746
161k
        pos = hashValue & mask;
747
161k
        entry = &dict->table[pos];
748
749
202k
        while ((entry->hashValue != 0) &&
750
202k
               ((pos - entry->hashValue) & mask) >= displ) {
751
41.5k
            displ++;
752
41.5k
            pos++;
753
41.5k
            entry++;
754
41.5k
            if ((pos & mask) == 0)
755
4.96k
                entry = dict->table;
756
41.5k
        }
757
161k
    }
758
759
1.18M
    if (prefix == NULL)
760
1.16M
        ret = xmlDictAddString(dict, name, len);
761
22.3k
    else
762
22.3k
        ret = xmlDictAddQString(dict, prefix, plen, name, len);
763
1.18M
    if (ret == NULL)
764
733
        return(NULL);
765
766
    /*
767
     * Shift the remainder of the probe sequence to the right
768
     */
769
1.18M
    if (entry->hashValue != 0) {
770
324k
        const xmlDictEntry *end = &dict->table[dict->size];
771
324k
        const xmlDictEntry *cur = entry;
772
773
1.10M
        do {
774
1.10M
            cur++;
775
1.10M
            if (cur >= end)
776
67.1k
                cur = dict->table;
777
1.10M
        } while (cur->hashValue != 0);
778
779
324k
        if (cur < entry) {
780
            /*
781
             * If we traversed the end of the buffer, handle the part
782
             * at the start of the buffer.
783
             */
784
67.1k
            memmove(&dict->table[1], dict->table,
785
67.1k
                    (char *) cur - (char *) dict->table);
786
67.1k
            cur = end - 1;
787
67.1k
            dict->table[0] = *cur;
788
67.1k
        }
789
790
324k
        memmove(&entry[1], entry, (char *) cur - (char *) entry);
791
324k
    }
792
793
    /*
794
     * Populate entry
795
     */
796
1.18M
    entry->hashValue = hashValue;
797
1.18M
    entry->name = ret;
798
799
1.18M
    dict->nbElems++;
800
801
1.18M
    return(entry);
802
1.18M
}
803
804
/**
805
 * Lookup a string and add it to the dictionary if it wasn't found.
806
 *
807
 * @param dict  dictionary
808
 * @param name  string key
809
 * @param len  length of the key, if -1 it is recomputed
810
 * @returns the interned copy of the string or NULL if a memory allocation
811
 * failed.
812
 */
813
const xmlChar *
814
15.7M
xmlDictLookup(xmlDict *dict, const xmlChar *name, int len) {
815
15.7M
    const xmlDictEntry *entry;
816
817
15.7M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
818
15.7M
    if (entry == NULL)
819
14.2k
        return(NULL);
820
15.7M
    return(entry->name);
821
15.7M
}
822
823
/**
824
 * Lookup a dictionary entry and add the string to the dictionary if
825
 * it wasn't found.
826
 *
827
 * @param dict  dictionary
828
 * @param name  string key
829
 * @param len  length of the key, if -1 it is recomputed
830
 * @returns the dictionary entry.
831
 */
832
xmlHashedString
833
16.6M
xmlDictLookupHashed(xmlDict *dict, const xmlChar *name, int len) {
834
16.6M
    const xmlDictEntry *entry;
835
16.6M
    xmlHashedString ret;
836
837
16.6M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
838
839
16.6M
    if (entry == NULL) {
840
15
        ret.name = NULL;
841
15
        ret.hashValue = 0;
842
16.6M
    } else {
843
16.6M
        ret = *entry;
844
16.6M
    }
845
846
16.6M
    return(ret);
847
16.6M
}
848
849
/**
850
 * Check if a string exists in the dictionary.
851
 *
852
 * @param dict  the dictionary
853
 * @param name  the name of the userdata
854
 * @param len  the length of the name, if -1 it is recomputed
855
 * @returns the internal copy of the name or NULL if not found.
856
 */
857
const xmlChar *
858
0
xmlDictExists(xmlDict *dict, const xmlChar *name, int len) {
859
0
    const xmlDictEntry *entry;
860
861
0
    entry = xmlDictLookupInternal(dict, NULL, name, len, 0);
862
0
    if (entry == NULL)
863
0
        return(NULL);
864
0
    return(entry->name);
865
0
}
866
867
/**
868
 * Lookup the QName `prefix:name` and add it to the dictionary if
869
 * it wasn't found.
870
 *
871
 * @param dict  the dictionary
872
 * @param prefix  the prefix
873
 * @param name  the name
874
 * @returns the interned copy of the string or NULL if a memory allocation
875
 * failed.
876
 */
877
const xmlChar *
878
199k
xmlDictQLookup(xmlDict *dict, const xmlChar *prefix, const xmlChar *name) {
879
199k
    const xmlDictEntry *entry;
880
881
199k
    entry = xmlDictLookupInternal(dict, prefix, name, -1, 1);
882
199k
    if (entry == NULL)
883
114
        return(NULL);
884
199k
    return(entry->name);
885
199k
}
886
887
/*
888
 * Pseudo-random generator
889
 */
890
891
#ifdef _WIN32
892
  #define WIN32_LEAN_AND_MEAN
893
  #include <windows.h>
894
  #include <bcrypt.h>
895
#else
896
  #if HAVE_DECL_GETENTROPY
897
    /* POSIX 2024 */
898
    #include <unistd.h>
899
    /* Older platforms */
900
    #include <sys/random.h>
901
  #endif
902
  #include <time.h>
903
#endif
904
905
static xmlMutex xmlRngMutex;
906
907
static unsigned globalRngState[2];
908
909
/*
910
 *
911
 * Initialize the PRNG.
912
 */
913
ATTRIBUTE_NO_SANITIZE_INTEGER
914
void
915
2
xmlInitRandom(void) {
916
2
    xmlInitMutex(&xmlRngMutex);
917
918
2
    {
919
#ifdef _WIN32
920
        NTSTATUS status;
921
922
        /*
923
         * You can find many (recent as of 2025) discussions how
924
         * to get a pseudo-random seed on Windows in projects like
925
         * Golang, Rust, Chromium and Firefox.
926
         *
927
         * TODO: Support ProcessPrng available since Windows 10.
928
         */
929
        status = BCryptGenRandom(NULL, (unsigned char *) globalRngState,
930
                                 sizeof(globalRngState),
931
                                 BCRYPT_USE_SYSTEM_PREFERRED_RNG);
932
        if (!BCRYPT_SUCCESS(status))
933
            xmlAbort("libxml2: BCryptGenRandom failed with error code %lu\n",
934
                     GetLastError());
935
#else
936
2
        int var;
937
938
2
#if HAVE_DECL_GETENTROPY
939
2
        while (1) {
940
2
            if (getentropy(globalRngState, sizeof(globalRngState)) == 0)
941
2
                return;
942
943
            /*
944
             * This most likely means that libxml2 was compiled on
945
             * a system supporting certain system calls and is running
946
             * on a system that doesn't support these calls, as can
947
             * be the case on Linux.
948
             */
949
0
            if (errno == ENOSYS)
950
0
                break;
951
952
            /*
953
             * We really don't want to fallback to the unsafe PRNG
954
             * for possibly accidental reasons, so we abort on any
955
             * unknown error.
956
             */
957
0
            if (errno != EINTR)
958
0
                xmlAbort("libxml2: getentropy failed with error code %d\n",
959
0
                         errno);
960
0
        }
961
0
#endif
962
963
        /*
964
         * TODO: Fallback to /dev/urandom for older POSIX systems.
965
         */
966
0
        globalRngState[0] =
967
0
                (unsigned) time(NULL) ^
968
0
                HASH_ROL((unsigned) ((size_t) &xmlInitRandom & 0xFFFFFFFF), 8);
969
0
        globalRngState[1] =
970
0
                HASH_ROL((unsigned) ((size_t) &xmlRngMutex & 0xFFFFFFFF), 16) ^
971
0
                HASH_ROL((unsigned) ((size_t) &var & 0xFFFFFFFF), 24);
972
0
#endif
973
0
    }
974
0
}
975
976
/*
977
 *
978
 * Clean up PRNG globals.
979
 */
980
void
981
0
xmlCleanupRandom(void) {
982
0
    xmlCleanupMutex(&xmlRngMutex);
983
0
}
984
985
ATTRIBUTE_NO_SANITIZE_INTEGER
986
static unsigned
987
1.92M
xoroshiro64ss(unsigned *s) {
988
1.92M
    unsigned s0 = s[0];
989
1.92M
    unsigned s1 = s[1];
990
1.92M
    unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5;
991
992
1.92M
    s1 ^= s0;
993
1.92M
    s[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9);
994
1.92M
    s[1] = HASH_ROL(s1, 13);
995
996
1.92M
    return(result & 0xFFFFFFFF);
997
1.92M
}
998
999
/*
1000
 *
1001
 * Generate a pseudo-random value using the global PRNG.
1002
 *
1003
 * @returns a random value.
1004
 */
1005
unsigned
1006
4
xmlGlobalRandom(void) {
1007
4
    unsigned ret;
1008
1009
4
    xmlMutexLock(&xmlRngMutex);
1010
4
    ret = xoroshiro64ss(globalRngState);
1011
4
    xmlMutexUnlock(&xmlRngMutex);
1012
1013
4
    return(ret);
1014
4
}
1015
1016
/*
1017
 *
1018
 * Generate a pseudo-random value using the thread-local PRNG.
1019
 *
1020
 * @returns a random value.
1021
 */
1022
unsigned
1023
1.92M
xmlRandom(void) {
1024
1.92M
    return(xoroshiro64ss(xmlGetLocalRngState()));
1025
1.92M
}
1026