Coverage Report

Created: 2026-09-18 06:12

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