Coverage Report

Created: 2026-01-17 06:39

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.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
2.05M
#define MAX_FILL_NUM 7
42
2.05M
#define MAX_FILL_DENOM 8
43
597k
#define MIN_HASH_SIZE 8
44
27.2M
#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.09M
xmlDictAddString(xmlDictPtr dict, const xmlChar *name, unsigned int namelen) {
140
1.09M
    xmlDictStringsPtr pool;
141
1.09M
    const xmlChar *ret;
142
1.09M
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
143
1.09M
    size_t limit = 0;
144
145
1.09M
    pool = dict->strings;
146
1.09M
    while (pool != NULL) {
147
1.02M
  if ((size_t)(pool->end - pool->free) > namelen)
148
1.01M
      goto found_pool;
149
2.73k
  if (pool->size > size) size = pool->size;
150
2.73k
        limit += pool->size;
151
2.73k
  pool = pool->next;
152
2.73k
    }
153
    /*
154
     * Not found, need to allocate
155
     */
156
72.8k
    if (pool == NULL) {
157
72.8k
        if ((dict->limit > 0) && (limit > dict->limit)) {
158
0
            return(NULL);
159
0
        }
160
161
72.8k
        if (size == 0) {
162
70.7k
            size = 1000;
163
70.7k
        } else {
164
2.09k
            if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
165
2.09k
                size *= 4; /* exponential growth */
166
0
            else
167
0
                size = SIZE_MAX - sizeof(xmlDictStrings);
168
2.09k
        }
169
72.8k
        if (size / 4 < namelen) {
170
814
            if ((size_t) namelen + 0 < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
171
814
                size = 4 * (size_t) namelen; /* just in case ! */
172
0
            else
173
0
                return(NULL);
174
814
        }
175
72.8k
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
176
72.8k
  if (pool == NULL)
177
553
      return(NULL);
178
72.2k
  pool->size = size;
179
72.2k
  pool->nbStrings = 0;
180
72.2k
  pool->free = &pool->array[0];
181
72.2k
  pool->end = &pool->array[size];
182
72.2k
  pool->next = dict->strings;
183
72.2k
  dict->strings = pool;
184
72.2k
    }
185
1.09M
found_pool:
186
1.09M
    ret = pool->free;
187
1.09M
    memcpy(pool->free, name, namelen);
188
1.09M
    pool->free += namelen;
189
1.09M
    *(pool->free++) = 0;
190
1.09M
    pool->nbStrings++;
191
1.09M
    return(ret);
192
72.8k
}
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
23.7k
{
210
23.7k
    xmlDictStringsPtr pool;
211
23.7k
    const xmlChar *ret;
212
23.7k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
213
23.7k
    size_t limit = 0;
214
215
23.7k
    pool = dict->strings;
216
23.7k
    while (pool != NULL) {
217
23.6k
  if ((size_t)(pool->end - pool->free) > namelen + plen + 1)
218
23.5k
      goto found_pool;
219
88
  if (pool->size > size) size = pool->size;
220
88
        limit += pool->size;
221
88
  pool = pool->next;
222
88
    }
223
    /*
224
     * Not found, need to allocate
225
     */
226
181
    if (pool == NULL) {
227
181
        if ((dict->limit > 0) && (limit > dict->limit)) {
228
0
            return(NULL);
229
0
        }
230
231
181
        if (size == 0) size = 1000;
232
37
  else size *= 4; /* exponential growth */
233
181
        if (size < 4 * (namelen + plen + 1))
234
0
      size = 4 * (namelen + plen + 1); /* just in case ! */
235
181
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
236
181
  if (pool == NULL)
237
2
      return(NULL);
238
179
  pool->size = size;
239
179
  pool->nbStrings = 0;
240
179
  pool->free = &pool->array[0];
241
179
  pool->end = &pool->array[size];
242
179
  pool->next = dict->strings;
243
179
  dict->strings = pool;
244
179
    }
245
23.7k
found_pool:
246
23.7k
    ret = pool->free;
247
23.7k
    memcpy(pool->free, prefix, plen);
248
23.7k
    pool->free += plen;
249
23.7k
    *(pool->free++) = ':';
250
23.7k
    memcpy(pool->free, name, namelen);
251
23.7k
    pool->free += namelen;
252
23.7k
    *(pool->free++) = 0;
253
23.7k
    pool->nbStrings++;
254
23.7k
    return(ret);
255
181
}
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.26M
xmlDictCreate(void) {
266
1.26M
    xmlDictPtr dict;
267
268
1.26M
    xmlInitParser();
269
270
1.26M
    dict = xmlMalloc(sizeof(xmlDict));
271
1.26M
    if (dict == NULL)
272
307
        return(NULL);
273
1.26M
    dict->ref_counter = 1;
274
1.26M
    dict->limit = 0;
275
276
1.26M
    dict->size = 0;
277
1.26M
    dict->nbElems = 0;
278
1.26M
    dict->table = NULL;
279
1.26M
    dict->strings = NULL;
280
1.26M
    dict->subdict = NULL;
281
1.26M
    dict->seed = xmlRandom();
282
1.26M
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
283
1.26M
    dict->seed = 0;
284
1.26M
#endif
285
1.26M
    return(dict);
286
1.26M
}
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
17.8k
xmlDictCreateSub(xmlDictPtr sub) {
301
17.8k
    xmlDictPtr dict = xmlDictCreate();
302
303
17.8k
    if ((dict != NULL) && (sub != NULL)) {
304
17.8k
        dict->seed = sub->seed;
305
17.8k
        dict->subdict = sub;
306
17.8k
  xmlDictReference(dict->subdict);
307
17.8k
    }
308
17.8k
    return(dict);
309
17.8k
}
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
2.46M
xmlDictReference(xmlDictPtr dict) {
321
2.46M
    if (dict == NULL) return -1;
322
2.45M
    xmlMutexLock(&xmlDictMutex);
323
2.45M
    dict->ref_counter++;
324
2.45M
    xmlMutexUnlock(&xmlDictMutex);
325
2.45M
    return(0);
326
2.46M
}
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
3.72M
xmlDictFree(xmlDictPtr dict) {
337
3.72M
    xmlDictStringsPtr pool, nextp;
338
339
3.72M
    if (dict == NULL)
340
244
  return;
341
342
    /* decrement the counter, it may be shared by a parser and docs */
343
3.72M
    xmlMutexLock(&xmlDictMutex);
344
3.72M
    dict->ref_counter--;
345
3.72M
    if (dict->ref_counter > 0) {
346
2.45M
        xmlMutexUnlock(&xmlDictMutex);
347
2.45M
        return;
348
2.45M
    }
349
350
1.26M
    xmlMutexUnlock(&xmlDictMutex);
351
352
1.26M
    if (dict->subdict != NULL) {
353
17.8k
        xmlDictFree(dict->subdict);
354
17.8k
    }
355
356
1.26M
    if (dict->table) {
357
70.6k
  xmlFree(dict->table);
358
70.6k
    }
359
1.26M
    pool = dict->strings;
360
1.34M
    while (pool != NULL) {
361
72.4k
        nextp = pool->next;
362
72.4k
  xmlFree(pool);
363
72.4k
  pool = nextp;
364
72.4k
    }
365
1.26M
    xmlFree(dict);
366
1.26M
}
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
30.8M
xmlDictOwns(xmlDictPtr dict, const xmlChar *str) {
380
30.8M
    xmlDictStringsPtr pool;
381
382
30.8M
    if ((dict == NULL) || (str == NULL))
383
0
  return(-1);
384
30.8M
    pool = dict->strings;
385
47.7M
    while (pool != NULL) {
386
32.5M
        if ((str >= &pool->array[0]) && (str <= pool->free))
387
15.7M
      return(1);
388
16.8M
  pool = pool->next;
389
16.8M
    }
390
15.1M
    if (dict->subdict)
391
5.78M
        return(xmlDictOwns(dict->subdict, str));
392
9.38M
    return(0);
393
15.1M
}
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
709k
xmlDictSetLimit(xmlDictPtr dict, size_t limit) {
425
709k
    size_t ret;
426
427
709k
    if (dict == NULL)
428
0
  return(0);
429
709k
    ret = dict->limit;
430
709k
    dict->limit = limit;
431
709k
    return(ret);
432
709k
}
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
26.9M
                size_t *plen) {
469
26.9M
    unsigned h1, h2;
470
26.9M
    size_t i;
471
472
26.9M
    HASH_INIT(h1, h2, seed);
473
474
982M
    for (i = 0; i < maxLen && data[i]; i++) {
475
955M
        HASH_UPDATE(h1, h2, data[i]);
476
955M
    }
477
478
26.9M
    HASH_FINISH(h1, h2);
479
480
26.9M
    *plen = i;
481
26.9M
    return(h2 | MAX_HASH_SIZE);
482
26.9M
}
483
484
ATTRIBUTE_NO_SANITIZE_INTEGER
485
static unsigned
486
xmlDictHashQName(unsigned seed, const xmlChar *prefix, const xmlChar *name,
487
132k
                 size_t *pplen, size_t *plen) {
488
132k
    unsigned h1, h2;
489
132k
    size_t i;
490
491
132k
    HASH_INIT(h1, h2, seed);
492
493
1.41M
    for (i = 0; prefix[i] != 0; i++) {
494
1.28M
        HASH_UPDATE(h1, h2, prefix[i]);
495
1.28M
    }
496
132k
    *pplen = i;
497
498
132k
    HASH_UPDATE(h1, h2, ':');
499
500
1.24M
    for (i = 0; name[i] != 0; i++) {
501
1.11M
        HASH_UPDATE(h1, h2, name[i]);
502
1.11M
    }
503
132k
    *plen = i;
504
505
132k
    HASH_FINISH(h1, h2);
506
507
    /*
508
     * Always set the upper bit of hash values since 0 means an unoccupied
509
     * bucket.
510
     */
511
132k
    return(h2 | MAX_HASH_SIZE);
512
132k
}
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
2.24M
xmlDictComputeHash(const xmlDict *dict, const xmlChar *string) {
525
2.24M
    size_t len;
526
2.24M
    return(xmlDictHashName(dict->seed, string, SIZE_MAX, &len));
527
2.24M
}
528
529
1.95M
#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.95M
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.95M
    v1 ^= v2;
548
1.95M
    v1 += HASH_ROL31(v2, 5);
549
550
1.95M
    return((v1 & 0xFFFFFFFF) | 0x80000000);
551
1.95M
}
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
24.2M
                 int *pfound) {
571
24.2M
    xmlDictEntry *entry;
572
24.2M
    unsigned mask, pos, displ;
573
24.2M
    int found = 0;
574
575
24.2M
    mask = dict->size - 1;
576
24.2M
    pos = hashValue & mask;
577
24.2M
    entry = &dict->table[pos];
578
579
24.2M
    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
23.0M
        displ = 0;
586
587
36.5M
        do {
588
36.5M
            if (entry->hashValue == hashValue) {
589
22.2M
                if (prefix == NULL) {
590
                    /*
591
                     * name is not necessarily null-terminated.
592
                     */
593
22.1M
                    if ((strncmp((const char *) entry->name,
594
22.1M
                                 (const char *) name, len) == 0) &&
595
22.1M
                        (entry->name[len] == 0)) {
596
22.1M
                        found = 1;
597
22.1M
                        break;
598
22.1M
                    }
599
22.1M
                } else {
600
108k
                    if (xmlStrQEqual(prefix, name, entry->name)) {
601
108k
                        found = 1;
602
108k
                        break;
603
108k
                    }
604
108k
                }
605
22.2M
            }
606
607
14.3M
            displ++;
608
14.3M
            pos++;
609
14.3M
            entry++;
610
14.3M
            if ((pos & mask) == 0)
611
389k
                entry = dict->table;
612
14.3M
        } while ((entry->hashValue != 0) &&
613
13.8M
                 (((pos - entry->hashValue) & mask) >= displ));
614
23.0M
    }
615
616
24.2M
    *pfound = found;
617
24.2M
    return(entry);
618
24.2M
}
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
157k
xmlDictGrow(xmlDictPtr dict, unsigned size) {
631
157k
    const xmlDictEntry *oldentry, *oldend, *end;
632
157k
    xmlDictEntry *table;
633
157k
    unsigned oldsize, i;
634
635
    /* Add 0 to avoid spurious -Wtype-limits warning on 64-bit GCC */
636
157k
    if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0]))
637
0
        return(-1);
638
157k
    table = xmlMalloc(size * sizeof(table[0]));
639
157k
    if (table == NULL)
640
1.77k
        return(-1);
641
155k
    memset(table, 0, size * sizeof(table[0]));
642
643
155k
    oldsize = dict->size;
644
155k
    if (oldsize == 0)
645
70.6k
        goto done;
646
647
85.1k
    oldend = &dict->table[oldsize];
648
85.1k
    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
85.1k
    oldentry = dict->table;
658
620k
    while (oldentry->hashValue != 0) {
659
535k
        if (++oldentry >= oldend)
660
0
            oldentry = dict->table;
661
535k
    }
662
663
1.36M
    for (i = 0; i < oldsize; i++) {
664
1.27M
        if (oldentry->hashValue != 0) {
665
1.11M
            xmlDictEntry *entry = &table[oldentry->hashValue & (size - 1)];
666
667
1.43M
            while (entry->hashValue != 0) {
668
317k
                if (++entry >= end)
669
20.6k
                    entry = table;
670
317k
            }
671
1.11M
            *entry = *oldentry;
672
1.11M
        }
673
674
1.27M
        if (++oldentry >= oldend)
675
85.1k
            oldentry = dict->table;
676
1.27M
    }
677
678
85.1k
    xmlFree(dict->table);
679
680
155k
done:
681
155k
    dict->table = table;
682
155k
    dict->size = size;
683
684
155k
    return(0);
685
85.1k
}
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
23.3M
                      const xmlChar *name, int maybeLen, int update) {
701
23.3M
    xmlDictEntry *entry = NULL;
702
23.3M
    const xmlChar *ret;
703
23.3M
    unsigned hashValue, newSize;
704
23.3M
    size_t maxLen, len, plen, klen;
705
23.3M
    int found = 0;
706
707
23.3M
    if ((dict == NULL) || (name == NULL))
708
3.80k
  return(NULL);
709
710
23.3M
    maxLen = (maybeLen < 0) ? SIZE_MAX : (size_t) maybeLen;
711
712
23.3M
    if (prefix == NULL) {
713
23.2M
        hashValue = xmlDictHashName(dict->seed, name, maxLen, &len);
714
23.2M
        if (len > INT_MAX / 2)
715
0
            return(NULL);
716
23.2M
        klen = len;
717
23.2M
    } else {
718
131k
        hashValue = xmlDictHashQName(dict->seed, prefix, name, &plen, &len);
719
131k
        if ((len > INT_MAX / 2) || (plen >= INT_MAX / 2 - len))
720
0
            return(NULL);
721
131k
        klen = plen + 1 + len;
722
131k
    }
723
724
23.3M
    if ((dict->limit > 0) && (klen >= dict->limit))
725
0
        return(NULL);
726
727
    /*
728
     * Check for an existing entry
729
     */
730
23.3M
    if (dict->size == 0) {
731
597k
        newSize = MIN_HASH_SIZE;
732
22.7M
    } else {
733
22.7M
        entry = xmlDictFindEntry(dict, prefix, name, klen, hashValue, &found);
734
22.7M
        if (found)
735
20.6M
            return(entry);
736
737
2.05M
        if (dict->nbElems + 1 > dict->size / MAX_FILL_DENOM * MAX_FILL_NUM) {
738
86.1k
            if (dict->size >= MAX_HASH_SIZE)
739
0
                return(NULL);
740
86.1k
            newSize = dict->size * 2;
741
1.97M
        } else {
742
1.97M
            newSize = 0;
743
1.97M
        }
744
2.05M
    }
745
746
2.65M
    if ((dict->subdict != NULL) && (dict->subdict->size > 0)) {
747
1.54M
        xmlDictEntry *subEntry;
748
1.54M
        unsigned subHashValue;
749
750
1.54M
        if (prefix == NULL)
751
1.54M
            subHashValue = xmlDictHashName(dict->subdict->seed, name, len,
752
1.54M
                                           &len);
753
324
        else
754
324
            subHashValue = xmlDictHashQName(dict->subdict->seed, prefix, name,
755
324
                                            &plen, &len);
756
1.54M
        subEntry = xmlDictFindEntry(dict->subdict, prefix, name, klen,
757
1.54M
                                    subHashValue, &found);
758
1.54M
        if (found)
759
1.53M
            return(subEntry);
760
1.54M
    }
761
762
1.11M
    if (!update)
763
0
        return(NULL);
764
765
    /*
766
     * Grow the hash table if needed
767
     */
768
1.11M
    if (newSize > 0) {
769
157k
        unsigned mask, displ, pos;
770
771
157k
        if (xmlDictGrow(dict, newSize) != 0)
772
1.77k
            return(NULL);
773
774
        /*
775
         * Find new entry
776
         */
777
155k
        mask = dict->size - 1;
778
155k
        displ = 0;
779
155k
        pos = hashValue & mask;
780
155k
        entry = &dict->table[pos];
781
782
194k
        while ((entry->hashValue != 0) &&
783
50.6k
               ((pos - entry->hashValue) & mask) >= displ) {
784
39.1k
            displ++;
785
39.1k
            pos++;
786
39.1k
            entry++;
787
39.1k
            if ((pos & mask) == 0)
788
4.57k
                entry = dict->table;
789
39.1k
        }
790
155k
    }
791
792
1.11M
    if (prefix == NULL)
793
1.09M
        ret = xmlDictAddString(dict, name, len);
794
23.7k
    else
795
23.7k
        ret = xmlDictAddQString(dict, prefix, plen, name, len);
796
1.11M
    if (ret == NULL)
797
555
        return(NULL);
798
799
    /*
800
     * Shift the remainder of the probe sequence to the right
801
     */
802
1.11M
    if (entry->hashValue != 0) {
803
290k
        const xmlDictEntry *end = &dict->table[dict->size];
804
290k
        const xmlDictEntry *cur = entry;
805
806
1.00M
        do {
807
1.00M
            cur++;
808
1.00M
            if (cur >= end)
809
54.4k
                cur = dict->table;
810
1.00M
        } while (cur->hashValue != 0);
811
812
290k
        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
54.4k
            memmove(&dict->table[1], dict->table,
818
54.4k
                    (char *) cur - (char *) dict->table);
819
54.4k
            cur = end - 1;
820
54.4k
            dict->table[0] = *cur;
821
54.4k
        }
822
823
290k
        memmove(&entry[1], entry, (char *) cur - (char *) entry);
824
290k
    }
825
826
    /*
827
     * Populate entry
828
     */
829
1.11M
    entry->hashValue = hashValue;
830
1.11M
    entry->name = ret;
831
832
1.11M
    dict->nbElems++;
833
834
1.11M
    return(entry);
835
1.11M
}
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
12.1M
xmlDictLookup(xmlDictPtr dict, const xmlChar *name, int len) {
850
12.1M
    const xmlDictEntry *entry;
851
852
12.1M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
853
12.1M
    if (entry == NULL)
854
6.02k
        return(NULL);
855
12.1M
    return(entry->name);
856
12.1M
}
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
11.0M
xmlDictLookupHashed(xmlDictPtr dict, const xmlChar *name, int len) {
871
11.0M
    const xmlDictEntry *entry;
872
11.0M
    xmlHashedString ret;
873
874
11.0M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
875
876
11.0M
    if (entry == NULL) {
877
26
        ret.name = NULL;
878
26
        ret.hashValue = 0;
879
11.0M
    } else {
880
11.0M
        ret = *entry;
881
11.0M
    }
882
883
11.0M
    return(ret);
884
11.0M
}
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
131k
xmlDictQLookup(xmlDictPtr dict, const xmlChar *prefix, const xmlChar *name) {
920
131k
    const xmlDictEntry *entry;
921
922
131k
    entry = xmlDictLookupInternal(dict, prefix, name, -1, 1);
923
131k
    if (entry == NULL)
924
84
        return(NULL);
925
131k
    return(entry->name);
926
131k
}
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.58M
xoroshiro64ss(unsigned *s) {
1031
1.58M
    unsigned s0 = s[0];
1032
1.58M
    unsigned s1 = s[1];
1033
1.58M
    unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5;
1034
1035
1.58M
    s1 ^= s0;
1036
1.58M
    s[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9);
1037
1.58M
    s[1] = HASH_ROL(s1, 13);
1038
1039
1.58M
    return(result & 0xFFFFFFFF);
1040
1.58M
}
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.58M
xmlRandom(void) {
1069
1.58M
    return(xoroshiro64ss(xmlGetLocalRngState()));
1070
1.58M
}
1071