Coverage Report

Created: 2026-08-25 06:40

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
79.9k
#define MAX_FILL_NUM 7
42
79.9k
#define MAX_FILL_DENOM 8
43
4.75k
#define MIN_HASH_SIZE 8
44
232k
#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
83.2k
xmlDictAddString(xmlDictPtr dict, const xmlChar *name, unsigned int namelen) {
131
83.2k
    xmlDictStringsPtr pool;
132
83.2k
    const xmlChar *ret;
133
83.2k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
134
83.2k
    size_t limit = 0;
135
136
83.2k
    pool = dict->strings;
137
83.2k
    while (pool != NULL) {
138
78.4k
  if ((size_t)(pool->end - pool->free) > namelen)
139
78.4k
      goto found_pool;
140
13
  if (pool->size > size) size = pool->size;
141
13
        limit += pool->size;
142
13
  pool = pool->next;
143
13
    }
144
    /*
145
     * Not found, need to allocate
146
     */
147
4.77k
    if (pool == NULL) {
148
4.77k
        if ((dict->limit > 0) && (limit > dict->limit)) {
149
0
            return(NULL);
150
0
        }
151
152
4.77k
        if (size == 0) {
153
4.75k
            size = 1000;
154
4.75k
        } else {
155
13
            if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
156
13
                size *= 4; /* exponential growth */
157
0
            else
158
0
                size = SIZE_MAX - sizeof(xmlDictStrings);
159
13
        }
160
4.77k
        if (size / 4 < namelen) {
161
9
            if ((size_t) namelen + 0 < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
162
9
                size = 4 * (size_t) namelen; /* just in case ! */
163
0
            else
164
0
                return(NULL);
165
9
        }
166
4.77k
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
167
4.77k
  if (pool == NULL)
168
0
      return(NULL);
169
4.77k
  pool->size = size;
170
4.77k
  pool->nbStrings = 0;
171
4.77k
  pool->free = &pool->array[0];
172
4.77k
  pool->end = &pool->array[size];
173
4.77k
  pool->next = dict->strings;
174
4.77k
  dict->strings = pool;
175
4.77k
    }
176
83.2k
found_pool:
177
83.2k
    ret = pool->free;
178
83.2k
    memcpy(pool->free, name, namelen);
179
83.2k
    pool->free += namelen;
180
83.2k
    *(pool->free++) = 0;
181
83.2k
    pool->nbStrings++;
182
83.2k
    return(ret);
183
4.77k
}
184
185
/*
186
 * @param dict  the dictionary
187
 * @param prefix  the prefix of the userdata
188
 * @param plen  the prefix length
189
 * @param name  the name of the userdata
190
 * @param len  the length of the name
191
 *
192
 * Add the QName to the array[s]
193
 *
194
 * @returns the pointer of the local string, or NULL in case of error.
195
 */
196
static const xmlChar *
197
xmlDictAddQString(xmlDictPtr dict, const xmlChar *prefix, unsigned int plen,
198
                 const xmlChar *name, unsigned int namelen)
199
1.48k
{
200
1.48k
    xmlDictStringsPtr pool;
201
1.48k
    const xmlChar *ret;
202
1.48k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
203
1.48k
    size_t limit = 0;
204
205
1.48k
    pool = dict->strings;
206
1.48k
    while (pool != NULL) {
207
1.48k
  if ((size_t)(pool->end - pool->free) > namelen + plen + 1)
208
1.48k
      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
1.48k
found_pool:
247
1.48k
    ret = pool->free;
248
1.48k
    memcpy(pool->free, prefix, plen);
249
1.48k
    pool->free += plen;
250
1.48k
    *(pool->free++) = ':';
251
1.48k
    memcpy(pool->free, name, namelen);
252
1.48k
    pool->free += namelen;
253
1.48k
    *(pool->free++) = 0;
254
1.48k
    pool->nbStrings++;
255
1.48k
    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
4.75k
xmlDictCreate(void) {
265
4.75k
    xmlDictPtr dict;
266
267
4.75k
    xmlInitParser();
268
269
4.75k
    dict = xmlMalloc(sizeof(xmlDict));
270
4.75k
    if (dict == NULL)
271
0
        return(NULL);
272
4.75k
    dict->ref_counter = 1;
273
4.75k
    dict->limit = 0;
274
275
4.75k
    dict->size = 0;
276
4.75k
    dict->nbElems = 0;
277
4.75k
    dict->table = NULL;
278
4.75k
    dict->strings = NULL;
279
4.75k
    dict->subdict = NULL;
280
4.75k
    dict->seed = xmlRandom();
281
4.75k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
282
4.75k
    dict->seed = 0;
283
4.75k
#endif
284
4.75k
    return(dict);
285
4.75k
}
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
4.26k
xmlDictReference(xmlDict *dict) {
316
4.26k
    if (dict == NULL) return -1;
317
4.26k
    xmlMutexLock(&xmlDictMutex);
318
4.26k
    dict->ref_counter++;
319
4.26k
    xmlMutexUnlock(&xmlDictMutex);
320
4.26k
    return(0);
321
4.26k
}
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
9.02k
xmlDictFree(xmlDict *dict) {
331
9.02k
    xmlDictStringsPtr pool, nextp;
332
333
9.02k
    if (dict == NULL)
334
0
  return;
335
336
    /* decrement the counter, it may be shared by a parser and docs */
337
9.02k
    xmlMutexLock(&xmlDictMutex);
338
9.02k
    dict->ref_counter--;
339
9.02k
    if (dict->ref_counter > 0) {
340
4.26k
        xmlMutexUnlock(&xmlDictMutex);
341
4.26k
        return;
342
4.26k
    }
343
344
4.75k
    xmlMutexUnlock(&xmlDictMutex);
345
346
4.75k
    if (dict->subdict != NULL) {
347
0
        xmlDictFree(dict->subdict);
348
0
    }
349
350
4.75k
    if (dict->table) {
351
4.75k
  xmlFree(dict->table);
352
4.75k
    }
353
4.75k
    pool = dict->strings;
354
9.53k
    while (pool != NULL) {
355
4.77k
        nextp = pool->next;
356
4.77k
  xmlFree(pool);
357
4.77k
  pool = nextp;
358
4.77k
    }
359
4.75k
    xmlFree(dict);
360
4.75k
}
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
89.9k
xmlDictOwns(xmlDict *dict, const xmlChar *str) {
372
89.9k
    xmlDictStringsPtr pool;
373
374
89.9k
    if ((dict == NULL) || (str == NULL))
375
0
  return(-1);
376
89.9k
    pool = dict->strings;
377
109k
    while (pool != NULL) {
378
89.9k
        if ((str >= &pool->array[0]) && (str <= pool->free))
379
69.9k
      return(1);
380
20.0k
  pool = pool->next;
381
20.0k
    }
382
19.9k
    if (dict->subdict)
383
0
        return(xmlDictOwns(dict->subdict, str));
384
19.9k
    return(0);
385
19.9k
}
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
4.78k
xmlDictSetLimit(xmlDict *dict, size_t limit) {
413
4.78k
    size_t ret;
414
415
4.78k
    if (dict == NULL)
416
0
  return(0);
417
4.78k
    ret = dict->limit;
418
4.78k
    dict->limit = limit;
419
4.78k
    return(ret);
420
4.78k
}
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
224k
                size_t *plen) {
455
224k
    unsigned h1, h2;
456
224k
    size_t i;
457
458
224k
    HASH_INIT(h1, h2, seed);
459
460
1.88M
    for (i = 0; i < maxLen && data[i]; i++) {
461
1.65M
        HASH_UPDATE(h1, h2, data[i]);
462
1.65M
    }
463
464
224k
    HASH_FINISH(h1, h2);
465
466
224k
    *plen = i;
467
224k
    return(h2 | MAX_HASH_SIZE);
468
224k
}
469
470
ATTRIBUTE_NO_SANITIZE_INTEGER
471
static unsigned
472
xmlDictHashQName(unsigned seed, const xmlChar *prefix, const xmlChar *name,
473
1.59k
                 size_t *pplen, size_t *plen) {
474
1.59k
    unsigned h1, h2;
475
1.59k
    size_t i;
476
477
1.59k
    HASH_INIT(h1, h2, seed);
478
479
6.03k
    for (i = 0; prefix[i] != 0; i++) {
480
4.43k
        HASH_UPDATE(h1, h2, prefix[i]);
481
4.43k
    }
482
1.59k
    *pplen = i;
483
484
1.59k
    HASH_UPDATE(h1, h2, ':');
485
486
11.6k
    for (i = 0; name[i] != 0; i++) {
487
10.0k
        HASH_UPDATE(h1, h2, name[i]);
488
10.0k
    }
489
1.59k
    *plen = i;
490
491
1.59k
    HASH_FINISH(h1, h2);
492
493
    /*
494
     * Always set the upper bit of hash values since 0 means an unoccupied
495
     * bucket.
496
     */
497
1.59k
    return(h2 | MAX_HASH_SIZE);
498
1.59k
}
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
23.8k
xmlDictComputeHash(const xmlDict *dict, const xmlChar *string) {
509
23.8k
    size_t len;
510
23.8k
    return(xmlDictHashName(dict->seed, string, SIZE_MAX, &len));
511
23.8k
}
512
513
28.6k
#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
28.6k
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
28.6k
    v1 ^= v2;
530
28.6k
    v1 += HASH_ROL31(v2, 5);
531
532
28.6k
    return((v1 & 0xFFFFFFFF) | 0x80000000);
533
28.6k
}
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
197k
                 int *pfound) {
552
197k
    xmlDictEntry *entry;
553
197k
    unsigned mask, pos, displ;
554
197k
    int found = 0;
555
556
197k
    mask = dict->size - 1;
557
197k
    pos = hashValue & mask;
558
197k
    entry = &dict->table[pos];
559
560
197k
    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
155k
        displ = 0;
567
568
241k
        do {
569
241k
            if (entry->hashValue == hashValue) {
570
118k
                if (prefix == NULL) {
571
                    /*
572
                     * name is not necessarily null-terminated.
573
                     */
574
117k
                    if ((strncmp((const char *) entry->name,
575
117k
                                 (const char *) name, len) == 0) &&
576
117k
                        (entry->name[len] == 0)) {
577
117k
                        found = 1;
578
117k
                        break;
579
117k
                    }
580
117k
                } else {
581
112
                    if (xmlStrQEqual(prefix, name, entry->name)) {
582
112
                        found = 1;
583
112
                        break;
584
112
                    }
585
112
                }
586
118k
            }
587
588
123k
            displ++;
589
123k
            pos++;
590
123k
            entry++;
591
123k
            if ((pos & mask) == 0)
592
5.07k
                entry = dict->table;
593
123k
        } while ((entry->hashValue != 0) &&
594
111k
                 (((pos - entry->hashValue) & mask) >= displ));
595
155k
    }
596
597
197k
    *pfound = found;
598
197k
    return(entry);
599
197k
}
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
11.0k
xmlDictGrow(xmlDictPtr dict, unsigned size) {
610
11.0k
    const xmlDictEntry *oldentry, *oldend, *end;
611
11.0k
    xmlDictEntry *table;
612
11.0k
    unsigned oldsize, i;
613
614
    /* Add 0 to avoid spurious -Wtype-limits warning on 64-bit GCC */
615
11.0k
    if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0]))
616
0
        return(-1);
617
11.0k
    table = xmlMalloc(size * sizeof(table[0]));
618
11.0k
    if (table == NULL)
619
0
        return(-1);
620
11.0k
    memset(table, 0, size * sizeof(table[0]));
621
622
11.0k
    oldsize = dict->size;
623
11.0k
    if (oldsize == 0)
624
4.75k
        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
51.1k
    while (oldentry->hashValue != 0) {
638
44.8k
        if (++oldentry >= oldend)
639
0
            oldentry = dict->table;
640
44.8k
    }
641
642
94.8k
    for (i = 0; i < oldsize; i++) {
643
88.6k
        if (oldentry->hashValue != 0) {
644
77.5k
            xmlDictEntry *entry = &table[oldentry->hashValue & (size - 1)];
645
646
92.1k
            while (entry->hashValue != 0) {
647
14.6k
                if (++entry >= end)
648
2.64k
                    entry = table;
649
14.6k
            }
650
77.5k
            *entry = *oldentry;
651
77.5k
        }
652
653
88.6k
        if (++oldentry >= oldend)
654
6.26k
            oldentry = dict->table;
655
88.6k
    }
656
657
6.26k
    xmlFree(dict->table);
658
659
11.0k
done:
660
11.0k
    dict->table = table;
661
11.0k
    dict->size = size;
662
663
11.0k
    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
202k
                      const xmlChar *name, int maybeLen, int update) {
679
202k
    xmlDictEntry *entry = NULL;
680
202k
    const xmlChar *ret;
681
202k
    unsigned hashValue, newSize;
682
202k
    size_t maxLen, len, plen, klen;
683
202k
    int found = 0;
684
685
202k
    if ((dict == NULL) || (name == NULL))
686
0
  return(NULL);
687
688
202k
    maxLen = (maybeLen < 0) ? SIZE_MAX : (size_t) maybeLen;
689
690
202k
    if (prefix == NULL) {
691
201k
        hashValue = xmlDictHashName(dict->seed, name, maxLen, &len);
692
201k
        if (len > INT_MAX / 2)
693
0
            return(NULL);
694
201k
        klen = len;
695
201k
    } else {
696
1.59k
        hashValue = xmlDictHashQName(dict->seed, prefix, name, &plen, &len);
697
1.59k
        if ((len > INT_MAX / 2) || (plen >= INT_MAX / 2 - len))
698
0
            return(NULL);
699
1.59k
        klen = plen + 1 + len;
700
1.59k
    }
701
702
202k
    if ((dict->limit > 0) && (klen >= dict->limit))
703
0
        return(NULL);
704
705
    /*
706
     * Check for an existing entry
707
     */
708
202k
    if (dict->size == 0) {
709
4.75k
        newSize = MIN_HASH_SIZE;
710
197k
    } else {
711
197k
        entry = xmlDictFindEntry(dict, prefix, name, klen, hashValue, &found);
712
197k
        if (found)
713
118k
            return(entry);
714
715
79.9k
        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
73.6k
        } else {
720
73.6k
            newSize = 0;
721
73.6k
        }
722
79.9k
    }
723
724
84.6k
    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
84.6k
    if (!update)
741
0
        return(NULL);
742
743
    /*
744
     * Grow the hash table if needed
745
     */
746
84.6k
    if (newSize > 0) {
747
11.0k
        unsigned mask, displ, pos;
748
749
11.0k
        if (xmlDictGrow(dict, newSize) != 0)
750
0
            return(NULL);
751
752
        /*
753
         * Find new entry
754
         */
755
11.0k
        mask = dict->size - 1;
756
11.0k
        displ = 0;
757
11.0k
        pos = hashValue & mask;
758
11.0k
        entry = &dict->table[pos];
759
760
15.4k
        while ((entry->hashValue != 0) &&
761
5.73k
               ((pos - entry->hashValue) & mask) >= displ) {
762
4.41k
            displ++;
763
4.41k
            pos++;
764
4.41k
            entry++;
765
4.41k
            if ((pos & mask) == 0)
766
33
                entry = dict->table;
767
4.41k
        }
768
11.0k
    }
769
770
84.6k
    if (prefix == NULL)
771
83.2k
        ret = xmlDictAddString(dict, name, len);
772
1.48k
    else
773
1.48k
        ret = xmlDictAddQString(dict, prefix, plen, name, len);
774
84.6k
    if (ret == NULL)
775
0
        return(NULL);
776
777
    /*
778
     * Shift the remainder of the probe sequence to the right
779
     */
780
84.6k
    if (entry->hashValue != 0) {
781
23.2k
        const xmlDictEntry *end = &dict->table[dict->size];
782
23.2k
        const xmlDictEntry *cur = entry;
783
784
85.2k
        do {
785
85.2k
            cur++;
786
85.2k
            if (cur >= end)
787
3.37k
                cur = dict->table;
788
85.2k
        } while (cur->hashValue != 0);
789
790
23.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
3.37k
            memmove(&dict->table[1], dict->table,
796
3.37k
                    (char *) cur - (char *) dict->table);
797
3.37k
            cur = end - 1;
798
3.37k
            dict->table[0] = *cur;
799
3.37k
        }
800
801
23.2k
        memmove(&entry[1], entry, (char *) cur - (char *) entry);
802
23.2k
    }
803
804
    /*
805
     * Populate entry
806
     */
807
84.6k
    entry->hashValue = hashValue;
808
84.6k
    entry->name = ret;
809
810
84.6k
    dict->nbElems++;
811
812
84.6k
    return(entry);
813
84.6k
}
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
80.0k
xmlDictLookup(xmlDict *dict, const xmlChar *name, int len) {
826
80.0k
    const xmlDictEntry *entry;
827
828
80.0k
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
829
80.0k
    if (entry == NULL)
830
0
        return(NULL);
831
80.0k
    return(entry->name);
832
80.0k
}
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
121k
xmlDictLookupHashed(xmlDict *dict, const xmlChar *name, int len) {
845
121k
    const xmlDictEntry *entry;
846
121k
    xmlHashedString ret;
847
848
121k
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
849
850
121k
    if (entry == NULL) {
851
0
        ret.name = NULL;
852
0
        ret.hashValue = 0;
853
121k
    } else {
854
121k
        ret = *entry;
855
121k
    }
856
857
121k
    return(ret);
858
121k
}
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
1.59k
xmlDictQLookup(xmlDict *dict, const xmlChar *prefix, const xmlChar *name) {
890
1.59k
    const xmlDictEntry *entry;
891
892
1.59k
    entry = xmlDictLookupInternal(dict, prefix, name, -1, 1);
893
1.59k
    if (entry == NULL)
894
0
        return(NULL);
895
1.59k
    return(entry->name);
896
1.59k
}
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
4.76k
xoroshiro64ss(unsigned *s) {
999
4.76k
    unsigned s0 = s[0];
1000
4.76k
    unsigned s1 = s[1];
1001
4.76k
    unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5;
1002
1003
4.76k
    s1 ^= s0;
1004
4.76k
    s[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9);
1005
4.76k
    s[1] = HASH_ROL(s1, 13);
1006
1007
4.76k
    return(result & 0xFFFFFFFF);
1008
4.76k
}
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
4.75k
xmlRandom(void) {
1035
4.75k
    return(xoroshiro64ss(xmlGetLocalRngState()));
1036
4.75k
}
1037