Coverage Report

Created: 2025-08-26 07:06

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