Coverage Report

Created: 2025-07-07 10:01

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