Coverage Report

Created: 2025-07-18 06:31

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