| /* | |
| * hash.c: hash tables | |
| * | |
| * Hash table with open addressing, linear probing and | |
| * Robin Hood reordering. | |
| * | |
| * See Copyright for the status of this software. | |
| */ | |
| #define IN_LIBXML | |
| #include "libxml.h" | |
| #include <string.h> | |
| #include <limits.h> | |
| #include <libxml/parser.h> | |
| #include <libxml/hash.h> | |
| #include <libxml/dict.h> | |
| #include <libxml/xmlmemory.h> | |
| #include <libxml/xmlstring.h> | |
| #include "private/dict.h" | |
| #ifndef SIZE_MAX | |
| #define SIZE_MAX ((size_t) -1) | |
| #endif | |
| #define MAX_FILL_NUM 7 | |
| #define MAX_FILL_DENOM 8 | |
| #define MIN_HASH_SIZE 8 | |
| #define MAX_HASH_SIZE (1u << 31) | |
| /* | |
| * A single entry in the hash table | |
| */ | |
| typedef struct { | |
| unsigned hashValue; /* 0 means unoccupied, occupied entries have the | |
| * MAX_HASH_SIZE bit set to 1 */ | |
| xmlChar *key; | |
| xmlChar *key2; /* TODO: Don't allocate possibly empty keys */ | |
| xmlChar *key3; | |
| void *payload; | |
| } xmlHashEntry; | |
| /* | |
| * The entire hash table | |
| */ | |
| struct _xmlHashTable { | |
| xmlHashEntry *table; | |
| unsigned size; /* power of two */ | |
| unsigned nbElems; | |
| xmlDictPtr dict; | |
| unsigned randomSeed; | |
| }; | |
| static int | |
| xmlHashGrow(xmlHashTablePtr hash, unsigned size); | |
| ATTRIBUTE_NO_SANITIZE_INTEGER | |
| static unsigned | |
| xmlHashValue(unsigned seed, const xmlChar *key, const xmlChar *key2, | |
| const xmlChar *key3, size_t *lengths) { | |
| unsigned h1, h2; | |
| size_t i; | |
| HASH_INIT(h1, h2, seed); | |
| for (i = 0; key[i] != 0; i++) { | |
| HASH_UPDATE(h1, h2, key[i]); | |
| } | |
| if (lengths) | |
| lengths[0] = i; | |
| HASH_UPDATE(h1, h2, 0); | |
| if (key2 != NULL) { | |
| for (i = 0; key2[i] != 0; i++) { | |
| HASH_UPDATE(h1, h2, key2[i]); | |
| } | |
| if (lengths) | |
| lengths[1] = i; | |
| } | |
| HASH_UPDATE(h1, h2, 0); | |
| if (key3 != NULL) { | |
| for (i = 0; key3[i] != 0; i++) { | |
| HASH_UPDATE(h1, h2, key3[i]); | |
| } | |
| if (lengths) | |
| lengths[2] = i; | |
| } | |
| HASH_FINISH(h1, h2); | |
| return(h2); | |
| } | |
| ATTRIBUTE_NO_SANITIZE_INTEGER | |
| static unsigned | |
| xmlHashQNameValue(unsigned seed, | |
| const xmlChar *prefix, const xmlChar *name, | |
| const xmlChar *prefix2, const xmlChar *name2, | |
| const xmlChar *prefix3, const xmlChar *name3) { | |
| unsigned h1, h2, ch; | |
| HASH_INIT(h1, h2, seed); | |
| if (prefix != NULL) { | |
| while ((ch = *prefix++) != 0) { | |
| HASH_UPDATE(h1, h2, ch); | |
| } | |
| HASH_UPDATE(h1, h2, ':'); | |
| } | |
| if (name != NULL) { | |
| while ((ch = *name++) != 0) { | |
| HASH_UPDATE(h1, h2, ch); | |
| } | |
| } | |
| HASH_UPDATE(h1, h2, 0); | |
| if (prefix2 != NULL) { | |
| while ((ch = *prefix2++) != 0) { | |
| HASH_UPDATE(h1, h2, ch); | |
| } | |
| HASH_UPDATE(h1, h2, ':'); | |
| } | |
| if (name2 != NULL) { | |
| while ((ch = *name2++) != 0) { | |
| HASH_UPDATE(h1, h2, ch); | |
| } | |
| } | |
| HASH_UPDATE(h1, h2, 0); | |
| if (prefix3 != NULL) { | |
| while ((ch = *prefix3++) != 0) { | |
| HASH_UPDATE(h1, h2, ch); | |
| } | |
| HASH_UPDATE(h1, h2, ':'); | |
| } | |
| if (name3 != NULL) { | |
| while ((ch = *name3++) != 0) { | |
| HASH_UPDATE(h1, h2, ch); | |
| } | |
| } | |
| HASH_FINISH(h1, h2); | |
| return(h2); | |
| } | |
| /** | |
| * Create a new hash table. Set size to zero if the number of entries | |
| * can't be estimated. | |
| * | |
| * @param size initial size of the hash table | |
| * @returns the newly created object, or NULL if a memory allocation failed. | |
| */ | |
| xmlHashTable * | |
| xmlHashCreate(int size) { | |
| xmlHashTablePtr hash; | |
| xmlInitParser(); | |
| hash = xmlMalloc(sizeof(*hash)); | |
| if (hash == NULL) | |
| return(NULL); | |
| hash->dict = NULL; | |
| hash->size = 0; | |
| hash->table = NULL; | |
| hash->nbElems = 0; | |
| hash->randomSeed = xmlRandom(); | |
| #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION | |
| hash->randomSeed = 0; | |
| #endif | |
| /* | |
| * Unless a larger size is passed, the backing table is created | |
| * lazily with MIN_HASH_SIZE capacity. In practice, there are many | |
| * hash tables which are never filled. | |
| */ | |
| if (size > MIN_HASH_SIZE) { | |
| unsigned newSize = MIN_HASH_SIZE * 2; | |
| while ((newSize < (unsigned) size) && (newSize < MAX_HASH_SIZE)) | |
| newSize *= 2; | |
| if (xmlHashGrow(hash, newSize) != 0) { | |
| xmlFree(hash); | |
| return(NULL); | |
| } | |
| } | |
| return(hash); | |
| } | |
| /** | |
| * Create a new hash table backed by a dictionary. This can reduce | |
| * resource usage considerably if most keys passed to API functions | |
| * originate from this dictionary. | |
| * | |
| * @param size the size of the hash table | |
| * @param dict a dictionary to use for the hash | |
| * @returns the newly created object, or NULL if a memory allocation failed. | |
| */ | |
| xmlHashTable * | |
| xmlHashCreateDict(int size, xmlDict *dict) { | |
| xmlHashTablePtr hash; | |
| hash = xmlHashCreate(size); | |
| if (hash != NULL) { | |
| hash->dict = dict; | |
| xmlDictReference(dict); | |
| } | |
| return(hash); | |
| } | |
| /** | |
| * Free the hash and its contents. The payload is deallocated with | |
| * `dealloc` if provided. | |
| * | |
| * @param hash hash table | |
| * @param dealloc deallocator function or NULL | |
| */ | |
| void | |
| xmlHashFree(xmlHashTable *hash, xmlHashDeallocator dealloc) { | |
| if (hash == NULL) | |
| return; | |
| if (hash->table) { | |
| const xmlHashEntry *end = &hash->table[hash->size]; | |
| const xmlHashEntry *entry; | |
| for (entry = hash->table; entry < end; entry++) { | |
| if (entry->hashValue == 0) | |
| continue; | |
| if ((dealloc != NULL) && (entry->payload != NULL)) | |
| dealloc(entry->payload, entry->key); | |
| if (hash->dict == NULL) { | |
| if (entry->key) | |
| xmlFree(entry->key); | |
| if (entry->key2) | |
| xmlFree(entry->key2); | |
| if (entry->key3) | |
| xmlFree(entry->key3); | |
| } | |
| } | |
| xmlFree(hash->table); | |
| } | |
| if (hash->dict) | |
| xmlDictFree(hash->dict); | |
| xmlFree(hash); | |
| } | |
| /** | |
| * Compare two strings for equality, allowing NULL values. | |
| * | |
| * @param s1 string | |
| * @param s2 string | |
| */ | |
| static int | |
| xmlFastStrEqual(const xmlChar *s1, const xmlChar *s2) { | |
| if (s1 == NULL) | |
| return(s2 == NULL); | |
| else | |
| return((s2 != NULL) && | |
| (strcmp((const char *) s1, (const char *) s2) == 0)); | |
| } | |
| /** | |
| * Try to find a matching hash table entry. If an entry was found, set | |
| * `found` to 1 and return the entry. Otherwise, set `found` to 0 and return | |
| * the location where a new entry should be inserted. | |
| * | |
| * @param hash hash table, non-NULL, size > 0 | |
| * @param key first string key, non-NULL | |
| * @param key2 second string key | |
| * @param key3 third string key | |
| * @param hashValue valid hash value of keys | |
| * @param pfound result of search | |
| */ | |
| ATTRIBUTE_NO_SANITIZE_INTEGER | |
| static xmlHashEntry * | |
| xmlHashFindEntry(const xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3, | |
| unsigned hashValue, int *pfound) { | |
| xmlHashEntry *entry; | |
| unsigned mask, pos, displ; | |
| int found = 0; | |
| mask = hash->size - 1; | |
| pos = hashValue & mask; | |
| entry = &hash->table[pos]; | |
| if (entry->hashValue != 0) { | |
| /* | |
| * Robin hood hashing: abort if the displacement of the entry | |
| * is smaller than the displacement of the key we look for. | |
| * This also stops at the correct position when inserting. | |
| */ | |
| displ = 0; | |
| hashValue |= MAX_HASH_SIZE; | |
| do { | |
| if (entry->hashValue == hashValue) { | |
| if (hash->dict) { | |
| if ((entry->key == key) && | |
| (entry->key2 == key2) && | |
| (entry->key3 == key3)) { | |
| found = 1; | |
| break; | |
| } | |
| } | |
| if ((strcmp((const char *) entry->key, | |
| (const char *) key) == 0) && | |
| (xmlFastStrEqual(entry->key2, key2)) && | |
| (xmlFastStrEqual(entry->key3, key3))) { | |
| found = 1; | |
| break; | |
| } | |
| } | |
| displ++; | |
| pos++; | |
| entry++; | |
| if ((pos & mask) == 0) | |
| entry = hash->table; | |
| } while ((entry->hashValue != 0) && | |
| (((pos - entry->hashValue) & mask) >= displ)); | |
| } | |
| *pfound = found; | |
| return(entry); | |
| } | |
| /** | |
| * Resize the hash table. | |
| * | |
| * @param hash hash table | |
| * @param size new size of the hash table | |
| * @returns 0 in case of success, -1 if a memory allocation failed. | |
| */ | |
| static int | |
| xmlHashGrow(xmlHashTablePtr hash, unsigned size) { | |
| const xmlHashEntry *oldentry, *oldend, *end; | |
| xmlHashEntry *table; | |
| unsigned oldsize, i; | |
| /* Add 0 to avoid spurious -Wtype-limits warning on 64-bit GCC */ | |
| if ((size_t) size + 0 > SIZE_MAX / sizeof(table[0])) | |
| return(-1); | |
| table = xmlMalloc(size * sizeof(table[0])); | |
| if (table == NULL) | |
| return(-1); | |
| memset(table, 0, size * sizeof(table[0])); | |
| oldsize = hash->size; | |
| if (oldsize == 0) | |
| goto done; | |
| oldend = &hash->table[oldsize]; | |
| end = &table[size]; | |
| /* | |
| * Robin Hood sorting order is maintained if we | |
| * | |
| * - compute hash indices with modulo | |
| * - resize by an integer factor | |
| * - start to copy from the beginning of a probe sequence | |
| */ | |
| oldentry = hash->table; | |
| while (oldentry->hashValue != 0) { | |
| if (++oldentry >= oldend) | |
| oldentry = hash->table; | |
| } | |
| for (i = 0; i < oldsize; i++) { | |
| if (oldentry->hashValue != 0) { | |
| xmlHashEntry *entry = &table[oldentry->hashValue & (size - 1)]; | |
| while (entry->hashValue != 0) { | |
| if (++entry >= end) | |
| entry = table; | |
| } | |
| *entry = *oldentry; | |
| } | |
| if (++oldentry >= oldend) | |
| oldentry = hash->table; | |
| } | |
| xmlFree(hash->table); | |
| done: | |
| hash->table = table; | |
| hash->size = size; | |
| return(0); | |
| } | |
| /** | |
| * Internal function to add or update hash entries. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param key3 third string key | |
| * @param payload pointer to the payload | |
| * @param dealloc deallocator function for replaced item or NULL | |
| * @param update whether existing entries should be updated | |
| */ | |
| ATTRIBUTE_NO_SANITIZE_INTEGER | |
| static int | |
| xmlHashUpdateInternal(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3, | |
| void *payload, xmlHashDeallocator dealloc, int update) { | |
| xmlChar *copy, *copy2, *copy3; | |
| xmlHashEntry *entry = NULL; | |
| size_t lengths[3] = {0, 0, 0}; | |
| unsigned hashValue, newSize; | |
| if ((hash == NULL) || (key == NULL)) | |
| return(-1); | |
| hashValue = xmlHashValue(hash->randomSeed, key, key2, key3, lengths); | |
| /* | |
| * Check for an existing entry | |
| */ | |
| if (hash->size == 0) { | |
| newSize = MIN_HASH_SIZE; | |
| } else { | |
| int found = 0; | |
| entry = xmlHashFindEntry(hash, key, key2, key3, hashValue, &found); | |
| if (found) { | |
| if (update) { | |
| if (dealloc) | |
| dealloc(entry->payload, entry->key); | |
| entry->payload = payload; | |
| } | |
| return(0); | |
| } | |
| if (hash->nbElems + 1 > hash->size / MAX_FILL_DENOM * MAX_FILL_NUM) { | |
| /* This guarantees that nbElems < INT_MAX */ | |
| if (hash->size >= MAX_HASH_SIZE) | |
| return(-1); | |
| newSize = hash->size * 2; | |
| } else { | |
| newSize = 0; | |
| } | |
| } | |
| /* | |
| * Grow the hash table if needed | |
| */ | |
| if (newSize > 0) { | |
| unsigned mask, displ, pos; | |
| if (xmlHashGrow(hash, newSize) != 0) | |
| return(-1); | |
| /* | |
| * Find new entry | |
| */ | |
| mask = hash->size - 1; | |
| displ = 0; | |
| pos = hashValue & mask; | |
| entry = &hash->table[pos]; | |
| if (entry->hashValue != 0) { | |
| do { | |
| displ++; | |
| pos++; | |
| entry++; | |
| if ((pos & mask) == 0) | |
| entry = hash->table; | |
| } while ((entry->hashValue != 0) && | |
| ((pos - entry->hashValue) & mask) >= displ); | |
| } | |
| } | |
| /* | |
| * Copy keys | |
| */ | |
| if (hash->dict != NULL) { | |
| if (xmlDictOwns(hash->dict, key)) { | |
| copy = (xmlChar *) key; | |
| } else { | |
| copy = (xmlChar *) xmlDictLookup(hash->dict, key, -1); | |
| if (copy == NULL) | |
| return(-1); | |
| } | |
| if ((key2 == NULL) || (xmlDictOwns(hash->dict, key2))) { | |
| copy2 = (xmlChar *) key2; | |
| } else { | |
| copy2 = (xmlChar *) xmlDictLookup(hash->dict, key2, -1); | |
| if (copy2 == NULL) | |
| return(-1); | |
| } | |
| if ((key3 == NULL) || (xmlDictOwns(hash->dict, key3))) { | |
| copy3 = (xmlChar *) key3; | |
| } else { | |
| copy3 = (xmlChar *) xmlDictLookup(hash->dict, key3, -1); | |
| if (copy3 == NULL) | |
| return(-1); | |
| } | |
| } else { | |
| copy = xmlMalloc(lengths[0] + 1); | |
| if (copy == NULL) | |
| return(-1); | |
| memcpy(copy, key, lengths[0] + 1); | |
| if (key2 != NULL) { | |
| copy2 = xmlMalloc(lengths[1] + 1); | |
| if (copy2 == NULL) { | |
| xmlFree(copy); | |
| return(-1); | |
| } | |
| memcpy(copy2, key2, lengths[1] + 1); | |
| } else { | |
| copy2 = NULL; | |
| } | |
| if (key3 != NULL) { | |
| copy3 = xmlMalloc(lengths[2] + 1); | |
| if (copy3 == NULL) { | |
| xmlFree(copy); | |
| xmlFree(copy2); | |
| return(-1); | |
| } | |
| memcpy(copy3, key3, lengths[2] + 1); | |
| } else { | |
| copy3 = NULL; | |
| } | |
| } | |
| /* | |
| * Shift the remainder of the probe sequence to the right | |
| */ | |
| if (entry->hashValue != 0) { | |
| const xmlHashEntry *end = &hash->table[hash->size]; | |
| const xmlHashEntry *cur = entry; | |
| do { | |
| cur++; | |
| if (cur >= end) | |
| cur = hash->table; | |
| } while (cur->hashValue != 0); | |
| if (cur < entry) { | |
| /* | |
| * If we traversed the end of the buffer, handle the part | |
| * at the start of the buffer. | |
| */ | |
| memmove(&hash->table[1], hash->table, | |
| (char *) cur - (char *) hash->table); | |
| cur = end - 1; | |
| hash->table[0] = *cur; | |
| } | |
| memmove(&entry[1], entry, (char *) cur - (char *) entry); | |
| } | |
| /* | |
| * Populate entry | |
| */ | |
| entry->key = copy; | |
| entry->key2 = copy2; | |
| entry->key3 = copy3; | |
| entry->payload = payload; | |
| /* OR with MAX_HASH_SIZE to make sure that the value is non-zero */ | |
| entry->hashValue = hashValue | MAX_HASH_SIZE; | |
| hash->nbElems++; | |
| return(1); | |
| } | |
| /** | |
| * Free a hash table entry with xmlFree. | |
| * | |
| * @param entry hash table entry | |
| * @param key the entry's string key | |
| */ | |
| void | |
| xmlHashDefaultDeallocator(void *entry, const xmlChar *key ATTRIBUTE_UNUSED) { | |
| xmlFree(entry); | |
| } | |
| /** | |
| * Add a hash table entry. If an entry with this key already exists, | |
| * payload will not be updated and 0 is returned. This return value | |
| * can't be distinguished from out-of-memory errors, so this function | |
| * should be used with care. | |
| * | |
| * @since 2.13.0 | |
| * | |
| * @param hash hash table | |
| * @param key string key | |
| * @param payload pointer to the payload | |
| * @returns 1 on success, 0 if an entry exists and -1 in case of error. | |
| */ | |
| int | |
| xmlHashAdd(xmlHashTable *hash, const xmlChar *key, void *payload) { | |
| return(xmlHashUpdateInternal(hash, key, NULL, NULL, payload, NULL, 0)); | |
| } | |
| /** | |
| * Add a hash table entry with two strings as key. | |
| * | |
| * See #xmlHashAdd. | |
| * | |
| * @since 2.13.0 | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param payload pointer to the payload | |
| * @returns 1 on success, 0 if an entry exists and -1 in case of error. | |
| */ | |
| int | |
| xmlHashAdd2(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, void *payload) { | |
| return(xmlHashUpdateInternal(hash, key, key2, NULL, payload, NULL, 0)); | |
| } | |
| /** | |
| * Add a hash table entry with three strings as key. | |
| * | |
| * See #xmlHashAdd. | |
| * | |
| * @since 2.13.0 | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param key3 third string key | |
| * @param payload pointer to the payload | |
| * @returns 1 on success, 0 if an entry exists and -1 in case of error. | |
| */ | |
| int | |
| xmlHashAdd3(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3, | |
| void *payload) { | |
| return(xmlHashUpdateInternal(hash, key, key2, key3, payload, NULL, 0)); | |
| } | |
| /** | |
| * Add a hash table entry. If an entry with this key already exists, | |
| * payload will not be updated and -1 is returned. This return value | |
| * can't be distinguished from out-of-memory errors, so this function | |
| * should be used with care. | |
| * | |
| * NOTE: This function doesn't allow to distinguish malloc failures from | |
| * existing entries. Use #xmlHashAdd instead. | |
| * | |
| * @param hash hash table | |
| * @param key string key | |
| * @param payload pointer to the payload | |
| * @returns 0 on success and -1 in case of error. | |
| */ | |
| int | |
| xmlHashAddEntry(xmlHashTable *hash, const xmlChar *key, void *payload) { | |
| int res = xmlHashUpdateInternal(hash, key, NULL, NULL, payload, NULL, 0); | |
| if (res == 0) | |
| res = -1; | |
| else if (res == 1) | |
| res = 0; | |
| return(res); | |
| } | |
| /** | |
| * Add a hash table entry with two strings as key. | |
| * | |
| * See #xmlHashAddEntry. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param payload pointer to the payload | |
| * @returns 0 on success and -1 in case of error. | |
| */ | |
| int | |
| xmlHashAddEntry2(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, void *payload) { | |
| int res = xmlHashUpdateInternal(hash, key, key2, NULL, payload, NULL, 0); | |
| if (res == 0) | |
| res = -1; | |
| else if (res == 1) | |
| res = 0; | |
| return(res); | |
| } | |
| /** | |
| * Add a hash table entry with three strings as key. | |
| * | |
| * See #xmlHashAddEntry. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param key3 third string key | |
| * @param payload pointer to the payload | |
| * @returns 0 on success and -1 in case of error. | |
| */ | |
| int | |
| xmlHashAddEntry3(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3, | |
| void *payload) { | |
| int res = xmlHashUpdateInternal(hash, key, key2, key3, payload, NULL, 0); | |
| if (res == 0) | |
| res = -1; | |
| else if (res == 1) | |
| res = 0; | |
| return(res); | |
| } | |
| /** | |
| * Add a hash table entry. If an entry with this key already exists, | |
| * the old payload will be freed and updated with the new value. | |
| * | |
| * @param hash hash table | |
| * @param key string key | |
| * @param payload pointer to the payload | |
| * @param dealloc deallocator function for replaced item or NULL | |
| * @returns 0 in case of success, -1 if a memory allocation failed. | |
| */ | |
| int | |
| xmlHashUpdateEntry(xmlHashTable *hash, const xmlChar *key, | |
| void *payload, xmlHashDeallocator dealloc) { | |
| int res = xmlHashUpdateInternal(hash, key, NULL, NULL, payload, | |
| dealloc, 1); | |
| if (res == 1) | |
| res = 0; | |
| return(res); | |
| } | |
| /** | |
| * Add a hash table entry with two strings as key. | |
| * | |
| * See #xmlHashUpdateEntry. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param payload pointer to the payload | |
| * @param dealloc deallocator function for replaced item or NULL | |
| * @returns 0 on success and -1 in case of error. | |
| */ | |
| int | |
| xmlHashUpdateEntry2(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, void *payload, | |
| xmlHashDeallocator dealloc) { | |
| int res = xmlHashUpdateInternal(hash, key, key2, NULL, payload, | |
| dealloc, 1); | |
| if (res == 1) | |
| res = 0; | |
| return(res); | |
| } | |
| /** | |
| * Add a hash table entry with three strings as key. | |
| * | |
| * See #xmlHashUpdateEntry. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param key3 third string key | |
| * @param payload pointer to the payload | |
| * @param dealloc deallocator function for replaced item or NULL | |
| * @returns 0 on success and -1 in case of error. | |
| */ | |
| int | |
| xmlHashUpdateEntry3(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3, | |
| void *payload, xmlHashDeallocator dealloc) { | |
| int res = xmlHashUpdateInternal(hash, key, key2, key3, payload, | |
| dealloc, 1); | |
| if (res == 1) | |
| res = 0; | |
| return(res); | |
| } | |
| /** | |
| * Find the entry specified by `key`. | |
| * | |
| * @param hash hash table | |
| * @param key string key | |
| * @returns a pointer to the payload or NULL if no entry was found. | |
| */ | |
| void * | |
| xmlHashLookup(xmlHashTable *hash, const xmlChar *key) { | |
| return(xmlHashLookup3(hash, key, NULL, NULL)); | |
| } | |
| /** | |
| * Find the payload specified by the (`key`, `key2`) tuple. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @returns a pointer to the payload or NULL if no entry was found. | |
| */ | |
| void * | |
| xmlHashLookup2(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2) { | |
| return(xmlHashLookup3(hash, key, key2, NULL)); | |
| } | |
| /** | |
| * Find the payload specified by the QName `prefix:name` or `name`. | |
| * | |
| * @param hash hash table | |
| * @param prefix prefix of the string key | |
| * @param name local name of the string key | |
| * @returns a pointer to the payload or NULL if no entry was found. | |
| */ | |
| void * | |
| xmlHashQLookup(xmlHashTable *hash, const xmlChar *prefix, | |
| const xmlChar *name) { | |
| return(xmlHashQLookup3(hash, prefix, name, NULL, NULL, NULL, NULL)); | |
| } | |
| /** | |
| * Find the payload specified by the QNames tuple. | |
| * | |
| * @param hash hash table | |
| * @param prefix first prefix | |
| * @param name first local name | |
| * @param prefix2 second prefix | |
| * @param name2 second local name | |
| * @returns a pointer to the payload or NULL if no entry was found. | |
| */ | |
| void * | |
| xmlHashQLookup2(xmlHashTable *hash, const xmlChar *prefix, | |
| const xmlChar *name, const xmlChar *prefix2, | |
| const xmlChar *name2) { | |
| return(xmlHashQLookup3(hash, prefix, name, prefix2, name2, NULL, NULL)); | |
| } | |
| /** | |
| * Find the payload specified by the (`key`, `key2`, `key3`) tuple. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param key3 third string key | |
| * @returns a pointer to the payload or NULL if no entry was found. | |
| */ | |
| void * | |
| xmlHashLookup3(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3) { | |
| const xmlHashEntry *entry; | |
| unsigned hashValue; | |
| int found; | |
| if ((hash == NULL) || (hash->size == 0) || (key == NULL)) | |
| return(NULL); | |
| hashValue = xmlHashValue(hash->randomSeed, key, key2, key3, NULL); | |
| entry = xmlHashFindEntry(hash, key, key2, key3, hashValue, &found); | |
| if (found) | |
| return(entry->payload); | |
| return(NULL); | |
| } | |
| /** | |
| * Find the payload specified by the QNames tuple. | |
| * | |
| * @param hash hash table | |
| * @param prefix first prefix | |
| * @param name first local name | |
| * @param prefix2 second prefix | |
| * @param name2 second local name | |
| * @param prefix3 third prefix | |
| * @param name3 third local name | |
| * @returns a pointer to the payload or NULL if no entry was found. | |
| */ | |
| ATTRIBUTE_NO_SANITIZE_INTEGER | |
| void * | |
| xmlHashQLookup3(xmlHashTable *hash, | |
| const xmlChar *prefix, const xmlChar *name, | |
| const xmlChar *prefix2, const xmlChar *name2, | |
| const xmlChar *prefix3, const xmlChar *name3) { | |
| const xmlHashEntry *entry; | |
| unsigned hashValue, mask, pos, displ; | |
| if ((hash == NULL) || (hash->size == 0) || (name == NULL)) | |
| return(NULL); | |
| hashValue = xmlHashQNameValue(hash->randomSeed, prefix, name, prefix2, | |
| name2, prefix3, name3); | |
| mask = hash->size - 1; | |
| pos = hashValue & mask; | |
| entry = &hash->table[pos]; | |
| if (entry->hashValue != 0) { | |
| displ = 0; | |
| hashValue |= MAX_HASH_SIZE; | |
| do { | |
| if ((hashValue == entry->hashValue) && | |
| (xmlStrQEqual(prefix, name, entry->key)) && | |
| (xmlStrQEqual(prefix2, name2, entry->key2)) && | |
| (xmlStrQEqual(prefix3, name3, entry->key3))) | |
| return(entry->payload); | |
| displ++; | |
| pos++; | |
| entry++; | |
| if ((pos & mask) == 0) | |
| entry = hash->table; | |
| } while ((entry->hashValue != 0) && | |
| (((pos - entry->hashValue) & mask) >= displ)); | |
| } | |
| return(NULL); | |
| } | |
| typedef struct { | |
| xmlHashScanner scan; | |
| void *data; | |
| } stubData; | |
| static void | |
| stubHashScannerFull(void *payload, void *data, const xmlChar *key, | |
| const xmlChar *key2 ATTRIBUTE_UNUSED, | |
| const xmlChar *key3 ATTRIBUTE_UNUSED) { | |
| stubData *sdata = (stubData *) data; | |
| sdata->scan(payload, sdata->data, key); | |
| } | |
| /** | |
| * Scan the hash `table` and apply `scan` to each value. | |
| * | |
| * @param hash hash table | |
| * @param scan scanner function for items in the hash | |
| * @param data extra data passed to `scan` | |
| */ | |
| void | |
| xmlHashScan(xmlHashTable *hash, xmlHashScanner scan, void *data) { | |
| stubData sdata; | |
| sdata.data = data; | |
| sdata.scan = scan; | |
| xmlHashScanFull(hash, stubHashScannerFull, &sdata); | |
| } | |
| /** | |
| * Scan the hash `table` and apply `scan` to each value. | |
| * | |
| * @param hash hash table | |
| * @param scan scanner function for items in the hash | |
| * @param data extra data passed to `scan` | |
| */ | |
| void | |
| xmlHashScanFull(xmlHashTable *hash, xmlHashScannerFull scan, void *data) { | |
| const xmlHashEntry *entry, *end; | |
| xmlHashEntry old; | |
| unsigned i; | |
| if ((hash == NULL) || (hash->size == 0) || (scan == NULL)) | |
| return; | |
| /* | |
| * We must handle the case that a scanned entry is removed when executing | |
| * the callback (xmlCleanSpecialAttr and possibly other places). | |
| * | |
| * Find the start of a probe sequence to avoid scanning entries twice if | |
| * a deletion happens. | |
| */ | |
| entry = hash->table; | |
| end = &hash->table[hash->size]; | |
| while (entry->hashValue != 0) { | |
| if (++entry >= end) | |
| entry = hash->table; | |
| } | |
| for (i = 0; i < hash->size; i++) { | |
| if ((entry->hashValue != 0) && (entry->payload != NULL)) { | |
| /* | |
| * Make sure to rescan after a possible deletion. | |
| */ | |
| do { | |
| old = *entry; | |
| scan(entry->payload, data, entry->key, entry->key2, entry->key3); | |
| } while ((entry->hashValue != 0) && | |
| (entry->payload != NULL) && | |
| ((entry->key != old.key) || | |
| (entry->key2 != old.key2) || | |
| (entry->key3 != old.key3))); | |
| } | |
| if (++entry >= end) | |
| entry = hash->table; | |
| } | |
| } | |
| /** | |
| * Scan the hash `table` and apply `scan` to each value matching | |
| * (`key`, `key2`, `key3`) tuple. If one of the keys is null, | |
| * the comparison is considered to match. | |
| * | |
| * @param hash hash table | |
| * @param key first string key or NULL | |
| * @param key2 second string key or NULL | |
| * @param key3 third string key or NULL | |
| * @param scan scanner function for items in the hash | |
| * @param data extra data passed to `scan` | |
| */ | |
| void | |
| xmlHashScan3(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3, | |
| xmlHashScanner scan, void *data) { | |
| stubData sdata; | |
| sdata.data = data; | |
| sdata.scan = scan; | |
| xmlHashScanFull3(hash, key, key2, key3, stubHashScannerFull, &sdata); | |
| } | |
| /** | |
| * Scan the hash `table` and apply `scan` to each value matching | |
| * (`key`, `key2`, `key3`) tuple. If one of the keys is null, | |
| * the comparison is considered to match. | |
| * | |
| * @param hash hash table | |
| * @param key first string key or NULL | |
| * @param key2 second string key or NULL | |
| * @param key3 third string key or NULL | |
| * @param scan scanner function for items in the hash | |
| * @param data extra data passed to `scan` | |
| */ | |
| void | |
| xmlHashScanFull3(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3, | |
| xmlHashScannerFull scan, void *data) { | |
| const xmlHashEntry *entry, *end; | |
| xmlHashEntry old; | |
| unsigned i; | |
| if ((hash == NULL) || (hash->size == 0) || (scan == NULL)) | |
| return; | |
| /* | |
| * We must handle the case that a scanned entry is removed when executing | |
| * the callback (xmlCleanSpecialAttr and possibly other places). | |
| * | |
| * Find the start of a probe sequence to avoid scanning entries twice if | |
| * a deletion happens. | |
| */ | |
| entry = hash->table; | |
| end = &hash->table[hash->size]; | |
| while (entry->hashValue != 0) { | |
| if (++entry >= end) | |
| entry = hash->table; | |
| } | |
| for (i = 0; i < hash->size; i++) { | |
| if ((entry->hashValue != 0) && (entry->payload != NULL)) { | |
| /* | |
| * Make sure to rescan after a possible deletion. | |
| */ | |
| do { | |
| if (((key != NULL) && (strcmp((const char *) key, | |
| (const char *) entry->key) != 0)) || | |
| ((key2 != NULL) && (!xmlFastStrEqual(key2, entry->key2))) || | |
| ((key3 != NULL) && (!xmlFastStrEqual(key3, entry->key3)))) | |
| break; | |
| old = *entry; | |
| scan(entry->payload, data, entry->key, entry->key2, entry->key3); | |
| } while ((entry->hashValue != 0) && | |
| (entry->payload != NULL) && | |
| ((entry->key != old.key) || | |
| (entry->key2 != old.key2) || | |
| (entry->key3 != old.key3))); | |
| } | |
| if (++entry >= end) | |
| entry = hash->table; | |
| } | |
| } | |
| /** | |
| * @param hash hash table | |
| * @param copyFunc copier function for items in the hash | |
| * @param deallocFunc deallocation function in case of errors | |
| * | |
| * Copy the hash table using `copyFunc` to copy payloads. | |
| * | |
| * @since 2.13.0 | |
| * | |
| * @returns the new table or NULL if a memory allocation failed. | |
| */ | |
| xmlHashTable * | |
| xmlHashCopySafe(xmlHashTable *hash, xmlHashCopier copyFunc, | |
| xmlHashDeallocator deallocFunc) { | |
| const xmlHashEntry *entry, *end; | |
| xmlHashTablePtr ret; | |
| if ((hash == NULL) || (copyFunc == NULL)) | |
| return(NULL); | |
| ret = xmlHashCreate(hash->size); | |
| if (ret == NULL) | |
| return(NULL); | |
| if (hash->size == 0) | |
| return(ret); | |
| end = &hash->table[hash->size]; | |
| for (entry = hash->table; entry < end; entry++) { | |
| if (entry->hashValue != 0) { | |
| void *copy; | |
| copy = copyFunc(entry->payload, entry->key); | |
| if (copy == NULL) | |
| goto error; | |
| if (xmlHashAdd3(ret, entry->key, entry->key2, entry->key3, | |
| copy) <= 0) { | |
| if (deallocFunc != NULL) | |
| deallocFunc(copy, entry->key); | |
| goto error; | |
| } | |
| } | |
| } | |
| return(ret); | |
| error: | |
| xmlHashFree(ret, deallocFunc); | |
| return(NULL); | |
| } | |
| /** | |
| * @param hash hash table | |
| * @param copy copier function for items in the hash | |
| * | |
| * @deprecated Leaks memory in error case. | |
| * | |
| * Copy the hash table using `copy` to copy payloads. | |
| * | |
| * @returns the new table or NULL if a memory allocation failed. | |
| */ | |
| xmlHashTable * | |
| xmlHashCopy(xmlHashTable *hash, xmlHashCopier copy) { | |
| return(xmlHashCopySafe(hash, copy, NULL)); | |
| } | |
| /** | |
| * Query the number of elements in the hash table. | |
| * | |
| * @param hash hash table | |
| * @returns the number of elements in the hash table or | |
| * -1 in case of error. | |
| */ | |
| int | |
| xmlHashSize(xmlHashTable *hash) { | |
| if (hash == NULL) | |
| return(-1); | |
| return(hash->nbElems); | |
| } | |
| /** | |
| * Find the entry specified by the `key` and remove it from the hash table. | |
| * Payload will be freed with `dealloc`. | |
| * | |
| * @param hash hash table | |
| * @param key string key | |
| * @param dealloc deallocator function for removed item or NULL | |
| * @returns 0 on success and -1 if no entry was found. | |
| */ | |
| int xmlHashRemoveEntry(xmlHashTable *hash, const xmlChar *key, | |
| xmlHashDeallocator dealloc) { | |
| return(xmlHashRemoveEntry3(hash, key, NULL, NULL, dealloc)); | |
| } | |
| /** | |
| * Remove an entry with two strings as key. | |
| * | |
| * See #xmlHashRemoveEntry. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param dealloc deallocator function for removed item or NULL | |
| * @returns 0 on success and -1 in case of error. | |
| */ | |
| int | |
| xmlHashRemoveEntry2(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, xmlHashDeallocator dealloc) { | |
| return(xmlHashRemoveEntry3(hash, key, key2, NULL, dealloc)); | |
| } | |
| /** | |
| * Remove an entry with three strings as key. | |
| * | |
| * See #xmlHashRemoveEntry. | |
| * | |
| * @param hash hash table | |
| * @param key first string key | |
| * @param key2 second string key | |
| * @param key3 third string key | |
| * @param dealloc deallocator function for removed item or NULL | |
| * @returns 0 on success and -1 in case of error. | |
| */ | |
| ATTRIBUTE_NO_SANITIZE_INTEGER | |
| int | |
| xmlHashRemoveEntry3(xmlHashTable *hash, const xmlChar *key, | |
| const xmlChar *key2, const xmlChar *key3, | |
| xmlHashDeallocator dealloc) { | |
| xmlHashEntry *entry, *cur, *next; | |
| unsigned hashValue, mask, pos, nextpos; | |
| int found; | |
| if ((hash == NULL) || (hash->size == 0) || (key == NULL)) | |
| return(-1); | |
| hashValue = xmlHashValue(hash->randomSeed, key, key2, key3, NULL); | |
| entry = xmlHashFindEntry(hash, key, key2, key3, hashValue, &found); | |
| if (!found) | |
| return(-1); | |
| if ((dealloc != NULL) && (entry->payload != NULL)) | |
| dealloc(entry->payload, entry->key); | |
| if (hash->dict == NULL) { | |
| if (entry->key) | |
| xmlFree(entry->key); | |
| if (entry->key2) | |
| xmlFree(entry->key2); | |
| if (entry->key3) | |
| xmlFree(entry->key3); | |
| } | |
| /* | |
| * Find end of probe sequence. Entries at their initial probe | |
| * position start a new sequence. | |
| */ | |
| mask = hash->size - 1; | |
| pos = entry - hash->table; | |
| cur = entry; | |
| while (1) { | |
| nextpos = pos + 1; | |
| next = cur + 1; | |
| if ((nextpos & mask) == 0) | |
| next = hash->table; | |
| if ((next->hashValue == 0) || | |
| (((next->hashValue - nextpos) & mask) == 0)) | |
| break; | |
| cur = next; | |
| pos = nextpos; | |
| } | |
| /* | |
| * Backward shift | |
| */ | |
| next = entry + 1; | |
| if (cur < entry) { | |
| xmlHashEntry *end = &hash->table[hash->size]; | |
| memmove(entry, next, (char *) end - (char *) next); | |
| entry = hash->table; | |
| end[-1] = *entry; | |
| next = entry + 1; | |
| } | |
| memmove(entry, next, (char *) cur - (char *) entry); | |
| /* | |
| * Update entry | |
| */ | |
| cur->hashValue = 0; | |
| hash->nbElems--; | |
| return(0); | |
| } | |