Coverage Report

Created: 2026-01-10 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libxml2/dict.c
Line
Count
Source
1
/*
2
 * dict.c: dictionary of reusable strings, just used to avoid allocation
3
 *         and freeing operations.
4
 *
5
 * Copyright (C) 2003-2012 Daniel Veillard.
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
12
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS AND
14
 * CONTRIBUTORS ACCEPT NO RESPONSIBILITY IN ANY CONCEIVABLE MANNER.
15
 *
16
 * Author: daniel@veillard.com
17
 */
18
19
#define IN_LIBXML
20
#include "libxml.h"
21
22
#include <errno.h>
23
#include <limits.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include "private/dict.h"
28
#include "private/error.h"
29
#include "private/globals.h"
30
#include "private/threads.h"
31
32
#include <libxml/parser.h>
33
#include <libxml/dict.h>
34
#include <libxml/xmlmemory.h>
35
#include <libxml/xmlstring.h>
36
37
#ifndef SIZE_MAX
38
  #define SIZE_MAX ((size_t) -1)
39
#endif
40
41
1.26M
#define MAX_FILL_NUM 7
42
1.26M
#define MAX_FILL_DENOM 8
43
425k
#define MIN_HASH_SIZE 8
44
20.0M
#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
4
xmlInitDictInternal(void) {
103
4
    xmlInitMutex(&xmlDictMutex);
104
4
}
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
815k
xmlDictAddString(xmlDictPtr dict, const xmlChar *name, unsigned int namelen) {
140
815k
    xmlDictStringsPtr pool;
141
815k
    const xmlChar *ret;
142
815k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
143
815k
    size_t limit = 0;
144
145
815k
    pool = dict->strings;
146
817k
    while (pool != NULL) {
147
764k
  if ((size_t)(pool->end - pool->free) > namelen)
148
762k
      goto found_pool;
149
2.40k
  if (pool->size > size) size = pool->size;
150
2.40k
        limit += pool->size;
151
2.40k
  pool = pool->next;
152
2.40k
    }
153
    /*
154
     * Not found, need to allocate
155
     */
156
52.9k
    if (pool == NULL) {
157
52.9k
        if ((dict->limit > 0) && (limit > dict->limit)) {
158
0
            return(NULL);
159
0
        }
160
161
52.9k
        if (size == 0) {
162
51.0k
            size = 1000;
163
51.0k
        } else {
164
1.87k
            if (size < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
165
1.87k
                size *= 4; /* exponential growth */
166
0
            else
167
0
                size = SIZE_MAX - sizeof(xmlDictStrings);
168
1.87k
        }
169
52.9k
        if (size / 4 < namelen) {
170
732
            if ((size_t) namelen + 0 < (SIZE_MAX - sizeof(xmlDictStrings)) / 4)
171
732
                size = 4 * (size_t) namelen; /* just in case ! */
172
0
            else
173
0
                return(NULL);
174
732
        }
175
52.9k
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
176
52.9k
  if (pool == NULL)
177
413
      return(NULL);
178
52.5k
  pool->size = size;
179
52.5k
  pool->nbStrings = 0;
180
52.5k
  pool->free = &pool->array[0];
181
52.5k
  pool->end = &pool->array[size];
182
52.5k
  pool->next = dict->strings;
183
52.5k
  dict->strings = pool;
184
52.5k
    }
185
815k
found_pool:
186
815k
    ret = pool->free;
187
815k
    memcpy(pool->free, name, namelen);
188
815k
    pool->free += namelen;
189
815k
    *(pool->free++) = 0;
190
815k
    pool->nbStrings++;
191
815k
    return(ret);
192
52.9k
}
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
18.0k
{
210
18.0k
    xmlDictStringsPtr pool;
211
18.0k
    const xmlChar *ret;
212
18.0k
    size_t size = 0; /* + sizeof(_xmlDictStrings) == 1024 */
213
18.0k
    size_t limit = 0;
214
215
18.0k
    pool = dict->strings;
216
18.1k
    while (pool != NULL) {
217
17.9k
  if ((size_t)(pool->end - pool->free) > namelen + plen + 1)
218
17.9k
      goto found_pool;
219
68
  if (pool->size > size) size = pool->size;
220
68
        limit += pool->size;
221
68
  pool = pool->next;
222
68
    }
223
    /*
224
     * Not found, need to allocate
225
     */
226
136
    if (pool == NULL) {
227
136
        if ((dict->limit > 0) && (limit > dict->limit)) {
228
0
            return(NULL);
229
0
        }
230
231
136
        if (size == 0) size = 1000;
232
23
  else size *= 4; /* exponential growth */
233
136
        if (size < 4 * (namelen + plen + 1))
234
0
      size = 4 * (namelen + plen + 1); /* just in case ! */
235
136
  pool = (xmlDictStringsPtr) xmlMalloc(sizeof(xmlDictStrings) + size);
236
136
  if (pool == NULL)
237
2
      return(NULL);
238
134
  pool->size = size;
239
134
  pool->nbStrings = 0;
240
134
  pool->free = &pool->array[0];
241
134
  pool->end = &pool->array[size];
242
134
  pool->next = dict->strings;
243
134
  dict->strings = pool;
244
134
    }
245
18.0k
found_pool:
246
18.0k
    ret = pool->free;
247
18.0k
    memcpy(pool->free, prefix, plen);
248
18.0k
    pool->free += plen;
249
18.0k
    *(pool->free++) = ':';
250
18.0k
    memcpy(pool->free, name, namelen);
251
18.0k
    pool->free += namelen;
252
18.0k
    *(pool->free++) = 0;
253
18.0k
    pool->nbStrings++;
254
18.0k
    return(ret);
255
136
}
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
861k
xmlDictCreate(void) {
266
861k
    xmlDictPtr dict;
267
268
861k
    xmlInitParser();
269
270
861k
    dict = xmlMalloc(sizeof(xmlDict));
271
861k
    if (dict == NULL)
272
301
        return(NULL);
273
861k
    dict->ref_counter = 1;
274
861k
    dict->limit = 0;
275
276
861k
    dict->size = 0;
277
861k
    dict->nbElems = 0;
278
861k
    dict->table = NULL;
279
861k
    dict->strings = NULL;
280
861k
    dict->subdict = NULL;
281
861k
    dict->seed = xmlRandom();
282
861k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
283
861k
    dict->seed = 0;
284
861k
#endif
285
861k
    return(dict);
286
861k
}
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
12.7k
xmlDictCreateSub(xmlDictPtr sub) {
301
12.7k
    xmlDictPtr dict = xmlDictCreate();
302
303
12.7k
    if ((dict != NULL) && (sub != NULL)) {
304
12.7k
        dict->seed = sub->seed;
305
12.7k
        dict->subdict = sub;
306
12.7k
  xmlDictReference(dict->subdict);
307
12.7k
    }
308
12.7k
    return(dict);
309
12.7k
}
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
1.69M
xmlDictReference(xmlDictPtr dict) {
321
1.69M
    if (dict == NULL) return -1;
322
1.68M
    xmlMutexLock(&xmlDictMutex);
323
1.68M
    dict->ref_counter++;
324
1.68M
    xmlMutexUnlock(&xmlDictMutex);
325
1.68M
    return(0);
326
1.69M
}
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
2.54M
xmlDictFree(xmlDictPtr dict) {
337
2.54M
    xmlDictStringsPtr pool, nextp;
338
339
2.54M
    if (dict == NULL)
340
254
  return;
341
342
    /* decrement the counter, it may be shared by a parser and docs */
343
2.54M
    xmlMutexLock(&xmlDictMutex);
344
2.54M
    dict->ref_counter--;
345
2.54M
    if (dict->ref_counter > 0) {
346
1.68M
        xmlMutexUnlock(&xmlDictMutex);
347
1.68M
        return;
348
1.68M
    }
349
350
861k
    xmlMutexUnlock(&xmlDictMutex);
351
352
861k
    if (dict->subdict != NULL) {
353
12.7k
        xmlDictFree(dict->subdict);
354
12.7k
    }
355
356
861k
    if (dict->table) {
357
51.0k
  xmlFree(dict->table);
358
51.0k
    }
359
861k
    pool = dict->strings;
360
913k
    while (pool != NULL) {
361
52.6k
        nextp = pool->next;
362
52.6k
  xmlFree(pool);
363
52.6k
  pool = nextp;
364
52.6k
    }
365
861k
    xmlFree(dict);
366
861k
}
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
21.6M
xmlDictOwns(xmlDictPtr dict, const xmlChar *str) {
380
21.6M
    xmlDictStringsPtr pool;
381
382
21.6M
    if ((dict == NULL) || (str == NULL))
383
0
  return(-1);
384
21.6M
    pool = dict->strings;
385
33.2M
    while (pool != NULL) {
386
23.3M
        if ((str >= &pool->array[0]) && (str <= pool->free))
387
11.7M
      return(1);
388
11.5M
  pool = pool->next;
389
11.5M
    }
390
9.91M
    if (dict->subdict)
391
3.44M
        return(xmlDictOwns(dict->subdict, str));
392
6.46M
    return(0);
393
9.91M
}
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
496k
xmlDictSetLimit(xmlDictPtr dict, size_t limit) {
425
496k
    size_t ret;
426
427
496k
    if (dict == NULL)
428
0
  return(0);
429
496k
    ret = dict->limit;
430
496k
    dict->limit = limit;
431
496k
    return(ret);
432
496k
}
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
19.8M
                size_t *plen) {
469
19.8M
    unsigned h1, h2;
470
19.8M
    size_t i;
471
472
19.8M
    HASH_INIT(h1, h2, seed);
473
474
741M
    for (i = 0; i < maxLen && data[i]; i++) {
475
721M
        HASH_UPDATE(h1, h2, data[i]);
476
721M
    }
477
478
19.8M
    HASH_FINISH(h1, h2);
479
480
19.8M
    *plen = i;
481
19.8M
    return(h2 | MAX_HASH_SIZE);
482
19.8M
}
483
484
ATTRIBUTE_NO_SANITIZE_INTEGER
485
static unsigned
486
xmlDictHashQName(unsigned seed, const xmlChar *prefix, const xmlChar *name,
487
107k
                 size_t *pplen, size_t *plen) {
488
107k
    unsigned h1, h2;
489
107k
    size_t i;
490
491
107k
    HASH_INIT(h1, h2, seed);
492
493
709k
    for (i = 0; prefix[i] != 0; i++) {
494
602k
        HASH_UPDATE(h1, h2, prefix[i]);
495
602k
    }
496
107k
    *pplen = i;
497
498
107k
    HASH_UPDATE(h1, h2, ':');
499
500
1.18M
    for (i = 0; name[i] != 0; i++) {
501
1.08M
        HASH_UPDATE(h1, h2, name[i]);
502
1.08M
    }
503
107k
    *plen = i;
504
505
107k
    HASH_FINISH(h1, h2);
506
507
    /*
508
     * Always set the upper bit of hash values since 0 means an unoccupied
509
     * bucket.
510
     */
511
107k
    return(h2 | MAX_HASH_SIZE);
512
107k
}
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.58M
xmlDictComputeHash(const xmlDict *dict, const xmlChar *string) {
525
1.58M
    size_t len;
526
1.58M
    return(xmlDictHashName(dict->seed, string, SIZE_MAX, &len));
527
1.58M
}
528
529
1.07M
#define HASH_ROL31(x,n) ((x) << (n) | ((x) & 0x7FFFFFFF) >> (31 - (n)))
530
531
/**
532
 * xmlDictCombineHash:
533
 * @v1:  first hash value
534
 * @v2: second hash value
535
 *
536
 * Combine two hash values.
537
 *
538
 * Returns the combined hash value.
539
 */
540
ATTRIBUTE_NO_SANITIZE_INTEGER
541
unsigned
542
1.07M
xmlDictCombineHash(unsigned v1, unsigned v2) {
543
    /*
544
     * The upper bit of hash values is always set, so we have to operate on
545
     * 31-bit hashes here.
546
     */
547
1.07M
    v1 ^= v2;
548
1.07M
    v1 += HASH_ROL31(v2, 5);
549
550
1.07M
    return((v1 & 0xFFFFFFFF) | 0x80000000);
551
1.07M
}
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
17.9M
                 int *pfound) {
571
17.9M
    xmlDictEntry *entry;
572
17.9M
    unsigned mask, pos, displ;
573
17.9M
    int found = 0;
574
575
17.9M
    mask = dict->size - 1;
576
17.9M
    pos = hashValue & mask;
577
17.9M
    entry = &dict->table[pos];
578
579
17.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
17.3M
        displ = 0;
586
587
26.9M
        do {
588
26.9M
            if (entry->hashValue == hashValue) {
589
16.7M
                if (prefix == NULL) {
590
                    /*
591
                     * name is not necessarily null-terminated.
592
                     */
593
16.6M
                    if ((strncmp((const char *) entry->name,
594
16.6M
                                 (const char *) name, len) == 0) &&
595
16.6M
                        (entry->name[len] == 0)) {
596
16.6M
                        found = 1;
597
16.6M
                        break;
598
16.6M
                    }
599
16.6M
                } else {
600
88.7k
                    if (xmlStrQEqual(prefix, name, entry->name)) {
601
88.7k
                        found = 1;
602
88.7k
                        break;
603
88.7k
                    }
604
88.7k
                }
605
16.7M
            }
606
607
10.2M
            displ++;
608
10.2M
            pos++;
609
10.2M
            entry++;
610
10.2M
            if ((pos & mask) == 0)
611
312k
                entry = dict->table;
612
10.2M
        } while ((entry->hashValue != 0) &&
613
9.96M
                 (((pos - entry->hashValue) & mask) >= displ));
614
17.3M
    }
615
616
17.9M
    *pfound = found;
617
17.9M
    return(entry);
618
17.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
115k
xmlDictGrow(xmlDictPtr dict, unsigned size) {
631
115k
    const xmlDictEntry *oldentry, *oldend, *end;
632
115k
    xmlDictEntry *table;
633
115k
    unsigned oldsize, i;
634
635
    /* Add 0 to avoid spurious -Wtype-limits warning on 64-bit GCC */
636
115k
    if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0]))
637
0
        return(-1);
638
115k
    table = xmlMalloc(size * sizeof(table[0]));
639
115k
    if (table == NULL)
640
758
        return(-1);
641
114k
    memset(table, 0, size * sizeof(table[0]));
642
643
114k
    oldsize = dict->size;
644
114k
    if (oldsize == 0)
645
51.0k
        goto done;
646
647
63.5k
    oldend = &dict->table[oldsize];
648
63.5k
    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
63.5k
    oldentry = dict->table;
658
462k
    while (oldentry->hashValue != 0) {
659
399k
        if (++oldentry >= oldend)
660
0
            oldentry = dict->table;
661
399k
    }
662
663
1.03M
    for (i = 0; i < oldsize; i++) {
664
967k
        if (oldentry->hashValue != 0) {
665
846k
            xmlDictEntry *entry = &table[oldentry->hashValue & (size - 1)];
666
667
1.08M
            while (entry->hashValue != 0) {
668
237k
                if (++entry >= end)
669
15.1k
                    entry = table;
670
237k
            }
671
846k
            *entry = *oldentry;
672
846k
        }
673
674
967k
        if (++oldentry >= oldend)
675
63.5k
            oldentry = dict->table;
676
967k
    }
677
678
63.5k
    xmlFree(dict->table);
679
680
114k
done:
681
114k
    dict->table = table;
682
114k
    dict->size = size;
683
684
114k
    return(0);
685
63.5k
}
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
17.5M
                      const xmlChar *name, int maybeLen, int update) {
701
17.5M
    xmlDictEntry *entry = NULL;
702
17.5M
    const xmlChar *ret;
703
17.5M
    unsigned hashValue, newSize;
704
17.5M
    size_t maxLen, len, plen, klen;
705
17.5M
    int found = 0;
706
707
17.5M
    if ((dict == NULL) || (name == NULL))
708
2.50k
  return(NULL);
709
710
17.5M
    maxLen = (maybeLen < 0) ? SIZE_MAX : (size_t) maybeLen;
711
712
17.5M
    if (prefix == NULL) {
713
17.4M
        hashValue = xmlDictHashName(dict->seed, name, maxLen, &len);
714
17.4M
        if (len > INT_MAX / 2)
715
0
            return(NULL);
716
17.4M
        klen = len;
717
17.4M
    } else {
718
106k
        hashValue = xmlDictHashQName(dict->seed, prefix, name, &plen, &len);
719
106k
        if ((len > INT_MAX / 2) || (plen >= INT_MAX / 2 - len))
720
0
            return(NULL);
721
106k
        klen = plen + 1 + len;
722
106k
    }
723
724
17.5M
    if ((dict->limit > 0) && (klen >= dict->limit))
725
0
        return(NULL);
726
727
    /*
728
     * Check for an existing entry
729
     */
730
17.5M
    if (dict->size == 0) {
731
425k
        newSize = MIN_HASH_SIZE;
732
17.1M
    } else {
733
17.1M
        entry = xmlDictFindEntry(dict, prefix, name, klen, hashValue, &found);
734
17.1M
        if (found)
735
15.8M
            return(entry);
736
737
1.26M
        if (dict->nbElems + 1 > dict->size / MAX_FILL_DENOM * MAX_FILL_NUM) {
738
64.0k
            if (dict->size >= MAX_HASH_SIZE)
739
0
                return(NULL);
740
64.0k
            newSize = dict->size * 2;
741
1.19M
        } else {
742
1.19M
            newSize = 0;
743
1.19M
        }
744
1.26M
    }
745
746
1.68M
    if ((dict->subdict != NULL) && (dict->subdict->size > 0)) {
747
858k
        xmlDictEntry *subEntry;
748
858k
        unsigned subHashValue;
749
750
858k
        if (prefix == NULL)
751
857k
            subHashValue = xmlDictHashName(dict->subdict->seed, name, len,
752
857k
                                           &len);
753
257
        else
754
257
            subHashValue = xmlDictHashQName(dict->subdict->seed, prefix, name,
755
257
                                            &plen, &len);
756
858k
        subEntry = xmlDictFindEntry(dict->subdict, prefix, name, klen,
757
858k
                                    subHashValue, &found);
758
858k
        if (found)
759
853k
            return(subEntry);
760
858k
    }
761
762
834k
    if (!update)
763
0
        return(NULL);
764
765
    /*
766
     * Grow the hash table if needed
767
     */
768
834k
    if (newSize > 0) {
769
115k
        unsigned mask, displ, pos;
770
771
115k
        if (xmlDictGrow(dict, newSize) != 0)
772
758
            return(NULL);
773
774
        /*
775
         * Find new entry
776
         */
777
114k
        mask = dict->size - 1;
778
114k
        displ = 0;
779
114k
        pos = hashValue & mask;
780
114k
        entry = &dict->table[pos];
781
782
143k
        while ((entry->hashValue != 0) &&
783
37.8k
               ((pos - entry->hashValue) & mask) >= displ) {
784
29.1k
            displ++;
785
29.1k
            pos++;
786
29.1k
            entry++;
787
29.1k
            if ((pos & mask) == 0)
788
3.25k
                entry = dict->table;
789
29.1k
        }
790
114k
    }
791
792
833k
    if (prefix == NULL)
793
815k
        ret = xmlDictAddString(dict, name, len);
794
18.0k
    else
795
18.0k
        ret = xmlDictAddQString(dict, prefix, plen, name, len);
796
833k
    if (ret == NULL)
797
415
        return(NULL);
798
799
    /*
800
     * Shift the remainder of the probe sequence to the right
801
     */
802
833k
    if (entry->hashValue != 0) {
803
221k
        const xmlDictEntry *end = &dict->table[dict->size];
804
221k
        const xmlDictEntry *cur = entry;
805
806
771k
        do {
807
771k
            cur++;
808
771k
            if (cur >= end)
809
42.0k
                cur = dict->table;
810
771k
        } while (cur->hashValue != 0);
811
812
221k
        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
42.0k
            memmove(&dict->table[1], dict->table,
818
42.0k
                    (char *) cur - (char *) dict->table);
819
42.0k
            cur = end - 1;
820
42.0k
            dict->table[0] = *cur;
821
42.0k
        }
822
823
221k
        memmove(&entry[1], entry, (char *) cur - (char *) entry);
824
221k
    }
825
826
    /*
827
     * Populate entry
828
     */
829
833k
    entry->hashValue = hashValue;
830
833k
    entry->name = ret;
831
832
833k
    dict->nbElems++;
833
834
833k
    return(entry);
835
833k
}
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
8.42M
xmlDictLookup(xmlDictPtr dict, const xmlChar *name, int len) {
850
8.42M
    const xmlDictEntry *entry;
851
852
8.42M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
853
8.42M
    if (entry == NULL)
854
3.57k
        return(NULL);
855
8.41M
    return(entry->name);
856
8.42M
}
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
9.03M
xmlDictLookupHashed(xmlDictPtr dict, const xmlChar *name, int len) {
871
9.03M
    const xmlDictEntry *entry;
872
9.03M
    xmlHashedString ret;
873
874
9.03M
    entry = xmlDictLookupInternal(dict, NULL, name, len, 1);
875
876
9.03M
    if (entry == NULL) {
877
20
        ret.name = NULL;
878
20
        ret.hashValue = 0;
879
9.03M
    } else {
880
9.03M
        ret = *entry;
881
9.03M
    }
882
883
9.03M
    return(ret);
884
9.03M
}
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
106k
xmlDictQLookup(xmlDictPtr dict, const xmlChar *prefix, const xmlChar *name) {
920
106k
    const xmlDictEntry *entry;
921
922
106k
    entry = xmlDictLookupInternal(dict, prefix, name, -1, 1);
923
106k
    if (entry == NULL)
924
82
        return(NULL);
925
106k
    return(entry->name);
926
106k
}
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
4
xmlInitRandom(void) {
958
4
    xmlInitMutex(&xmlRngMutex);
959
960
4
    {
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
4
        int var;
979
980
4
#if HAVE_DECL_GETENTROPY
981
4
        while (1) {
982
4
            if (getentropy(globalRngState, sizeof(globalRngState)) == 0)
983
4
                return;
984
985
            /*
986
             * This most likely means that libxml2 was compiled on
987
             * a system supporting certain system calls and is running
988
             * on a system that doesn't support these calls, as can
989
             * be the case on Linux.
990
             */
991
0
            if (errno == ENOSYS)
992
0
                break;
993
994
            /*
995
             * We really don't want to fallback to the unsafe PRNG
996
             * for possibly accidental reasons, so we abort on any
997
             * unknown error.
998
             */
999
0
            if (errno != EINTR)
1000
0
                xmlAbort("libxml2: getentropy failed with error code %d\n",
1001
0
                         errno);
1002
0
        }
1003
0
#endif
1004
1005
        /*
1006
         * TODO: Fallback to /dev/urandom for older POSIX systems.
1007
         */
1008
0
        globalRngState[0] =
1009
0
                (unsigned) time(NULL) ^
1010
0
                HASH_ROL((unsigned) ((size_t) &xmlInitRandom & 0xFFFFFFFF), 8);
1011
0
        globalRngState[1] =
1012
0
                HASH_ROL((unsigned) ((size_t) &xmlRngMutex & 0xFFFFFFFF), 16) ^
1013
0
                HASH_ROL((unsigned) ((size_t) &var & 0xFFFFFFFF), 24);
1014
0
#endif
1015
0
    }
1016
0
}
1017
1018
/*
1019
 * xmlCleanupRandom:
1020
 *
1021
 * Clean up PRNG globals.
1022
 */
1023
void
1024
0
xmlCleanupRandom(void) {
1025
0
    xmlCleanupMutex(&xmlRngMutex);
1026
0
}
1027
1028
ATTRIBUTE_NO_SANITIZE_INTEGER
1029
static unsigned
1030
1.08M
xoroshiro64ss(unsigned *s) {
1031
1.08M
    unsigned s0 = s[0];
1032
1.08M
    unsigned s1 = s[1];
1033
1.08M
    unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5;
1034
1035
1.08M
    s1 ^= s0;
1036
1.08M
    s[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9);
1037
1.08M
    s[1] = HASH_ROL(s1, 13);
1038
1039
1.08M
    return(result & 0xFFFFFFFF);
1040
1.08M
}
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
8
xmlGlobalRandom(void) {
1051
8
    unsigned ret;
1052
1053
8
    xmlMutexLock(&xmlRngMutex);
1054
8
    ret = xoroshiro64ss(globalRngState);
1055
8
    xmlMutexUnlock(&xmlRngMutex);
1056
1057
8
    return(ret);
1058
8
}
1059
1060
/*
1061
 * xmlRandom:
1062
 *
1063
 * Generate a pseudo-random value using the thread-local PRNG.
1064
 *
1065
 * Returns a random value.
1066
 */
1067
unsigned
1068
1.08M
xmlRandom(void) {
1069
1.08M
    return(xoroshiro64ss(xmlGetLocalRngState()));
1070
1.08M
}
1071