Coverage Report

Created: 2026-04-27 07:05

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