blob: ffbbc9474ad60cc8217c126d4e8eb4a4ff2f26a5 [file] [log] [blame]
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#include <libxml/dict.h>
/**** dictionary tests ****/
#ifdef __clang__
#define ATTRIBUTE_NO_SANITIZE_INTEGER \
__attribute__ ((no_sanitize("unsigned-integer-overflow"))) \
__attribute__ ((no_sanitize("unsigned-shift-base")))
#else
#define ATTRIBUTE_NO_SANITIZE_INTEGER
#endif
/* #define WITH_PRINT */
static const char *seeds1[] = {
"a", "b", "c",
"d", "e", "f",
"g", "h", "i",
"j", "k", "l",
NULL
};
static const char *seeds2[] = {
"m", "n", "o",
"p", "q", "r",
"s", "t", "u",
"v", "w", "x",
NULL
};
#define NB_STRINGS_MAX 100000
#define NB_STRINGS_NS 10000
#define NB_STRINGS_PREFIX (NB_STRINGS_NS / 20)
#define NB_STRINGS_MIN 10
static xmlChar **strings1;
static xmlChar **strings2;
static const xmlChar **test1;
static const xmlChar **test2;
static int nbErrors = 0;
static void
fill_string_pool(xmlChar **strings, const char **seeds) {
int i, j, k;
int start_ns = NB_STRINGS_MAX - NB_STRINGS_NS;
/*
* That's a bit nasty but the output is fine and it doesn't take hours
* there is a small but sufficient number of duplicates, and we have
* ":xxx" and full QNames in the last NB_STRINGS_NS values
*/
for (i = 0; seeds[i] != NULL; i++) {
strings[i] = xmlStrdup((const xmlChar *) seeds[i]);
if (strings[i] == NULL) {
fprintf(stderr, "Out of memory while generating strings\n");
exit(1);
}
}
for (j = 0, k = 0; i < start_ns; i++) {
strings[i] = xmlStrncatNew(strings[j], strings[k], -1);
if (strings[i] == NULL) {
fprintf(stderr, "Out of memory while generating strings\n");
exit(1);
}
if (xmlStrlen(strings[i]) > 30) {
fprintf(stderr, "### %s %s\n", strings[start_ns+j], strings[k]);
abort();
}
j++;
if (j >= 50) {
j = 0;
k++;
}
}
for (j = 0, k = 0; (j < NB_STRINGS_PREFIX) && (i < NB_STRINGS_MAX);
i++, j++) {
strings[i] = xmlStrncatNew(strings[k], (const xmlChar *) ":", -1);
if (strings[i] == NULL) {
fprintf(stderr, "Out of memory while generating strings\n");
exit(1);
}
k += 1;
if (k >= start_ns) k = 0;
}
for (j = 0, k = 0; i < NB_STRINGS_MAX; i++) {
strings[i] = xmlStrncatNew(strings[start_ns+j], strings[k], -1);
if (strings[i] == NULL) {
fprintf(stderr, "Out of memory while generating strings\n");
exit(1);
}
j++;
if (j >= NB_STRINGS_PREFIX) j = 0;
k += 5;
if (k >= start_ns) k = 0;
}
}
#ifdef WITH_PRINT
static void print_strings(void) {
int i;
for (i = 0; i < NB_STRINGS_MAX;i++) {
printf("%s\n", strings1[i]);
}
for (i = 0; i < NB_STRINGS_MAX;i++) {
printf("%s\n", strings2[i]);
}
}
#endif
static void clean_strings(void) {
int i;
for (i = 0; i < NB_STRINGS_MAX; i++) {
if (strings1[i] != NULL) /* really should not happen */
xmlFree(strings1[i]);
}
for (i = 0; i < NB_STRINGS_MAX; i++) {
if (strings2[i] != NULL) /* really should not happen */
xmlFree(strings2[i]);
}
}
/*
* This tests the sub-dictionary support
*/
static int
test_subdict(xmlDictPtr parent) {
int i, j;
xmlDictPtr dict;
int ret = 0;
xmlChar prefix[40];
xmlChar *cur, *pref;
const xmlChar *tmp;
dict = xmlDictCreateSub(parent);
if (dict == NULL) {
fprintf(stderr, "Out of memory while creating sub-dictionary\n");
exit(1);
}
/* Cast to avoid buggy warning on MSVC. */
memset((void *) test2, 0, sizeof(test2));
/*
* Fill in NB_STRINGS_MIN, at this point the dictionary should not grow
* and we allocate all those doing the fast key computations
* All the strings are based on a different seeds subset so we know
* they are allocated in the main dictionary, not coming from the parent
*/
for (i = 0;i < NB_STRINGS_MIN;i++) {
test2[i] = xmlDictLookup(dict, strings2[i], -1);
if (test2[i] == NULL) {
fprintf(stderr, "Failed lookup for '%s'\n", strings2[i]);
ret = 1;
nbErrors++;
}
}
j = NB_STRINGS_MAX - NB_STRINGS_NS;
/* ":foo" like strings2 */
for (i = 0;i < NB_STRINGS_MIN;i++, j++) {
test2[j] = xmlDictLookup(dict, strings2[j], xmlStrlen(strings2[j]));
if (test2[j] == NULL) {
fprintf(stderr, "Failed lookup for '%s'\n", strings2[j]);
ret = 1;
nbErrors++;
}
}
/* "a:foo" like strings2 */
j = NB_STRINGS_MAX - NB_STRINGS_MIN;
for (i = 0;i < NB_STRINGS_MIN;i++, j++) {
test2[j] = xmlDictLookup(dict, strings2[j], xmlStrlen(strings2[j]));
if (test2[j] == NULL) {
fprintf(stderr, "Failed lookup for '%s'\n", strings2[j]);
ret = 1;
nbErrors++;
}
}
/*
* At this point allocate all the strings
* the dictionary will grow in the process, reallocate more string tables
* and switch to the better key generator
*/
for (i = 0;i < NB_STRINGS_MAX;i++) {
if (test2[i] != NULL)
continue;
test2[i] = xmlDictLookup(dict, strings2[i], -1);
if (test2[i] == NULL) {
fprintf(stderr, "Failed lookup for '%s'\n", strings2[i]);
ret = 1;
nbErrors++;
}
}
/*
* Now we can start to test things, first that all strings2 belongs to
* the dict, and that none of them was actually allocated in the parent
*/
for (i = 0;i < NB_STRINGS_MAX;i++) {
if (!xmlDictOwns(dict, test2[i])) {
fprintf(stderr, "Failed ownership failure for '%s'\n",
strings2[i]);
ret = 1;
nbErrors++;
}
if (xmlDictOwns(parent, test2[i])) {
fprintf(stderr, "Failed parent ownership failure for '%s'\n",
strings2[i]);
ret = 1;
nbErrors++;
}
}
/*
* Also verify that all strings from the parent are seen from the subdict
*/
for (i = 0;i < NB_STRINGS_MAX;i++) {
if (!xmlDictOwns(dict, test1[i])) {
fprintf(stderr, "Failed sub-ownership failure for '%s'\n",
strings1[i]);
ret = 1;
nbErrors++;
}
}
/*
* Then that another lookup to the string in sub will return the same
*/
for (i = 0;i < NB_STRINGS_MAX;i++) {
if (xmlDictLookup(dict, strings2[i], -1) != test2[i]) {
fprintf(stderr, "Failed re-lookup check for %d, '%s'\n",
i, strings2[i]);
ret = 1;
nbErrors++;
}
}
/*
* But also that any lookup for a string in the parent will be provided
* as in the parent
*/
for (i = 0;i < NB_STRINGS_MAX;i++) {
if (xmlDictLookup(dict, strings1[i], -1) != test1[i]) {
fprintf(stderr, "Failed parent string lookup check for %d, '%s'\n",
i, strings1[i]);
ret = 1;
nbErrors++;
}
}
/*
* check the QName lookups
*/
for (i = NB_STRINGS_MAX - NB_STRINGS_NS;i < NB_STRINGS_MAX;i++) {
cur = strings2[i];
pref = &prefix[0];
while (*cur != ':') *pref++ = *cur++;
cur++;
*pref = 0;
tmp = xmlDictQLookup(dict, &prefix[0], cur);
if (tmp != test2[i]) {
fprintf(stderr, "Failed lookup check for '%s':'%s'\n",
&prefix[0], cur);
ret = 1;
nbErrors++;
}
}
/*
* check the QName lookups for strings from the parent
*/
for (i = NB_STRINGS_MAX - NB_STRINGS_NS;i < NB_STRINGS_MAX;i++) {
cur = strings1[i];
pref = &prefix[0];
while (*cur != ':') *pref++ = *cur++;
cur++;
*pref = 0;
tmp = xmlDictQLookup(dict, &prefix[0], cur);
if (xmlDictQLookup(dict, &prefix[0], cur) != test1[i]) {
fprintf(stderr, "Failed parent lookup check for '%s':'%s'\n",
&prefix[0], cur);
ret = 1;
nbErrors++;
}
}
xmlDictFree(dict);
return(ret);
}
/*
* Test a single dictionary
*/
static int
test_dict(xmlDict *dict) {
int i, j;
int ret = 0;
xmlChar prefix[40];
xmlChar *cur, *pref;
const xmlChar *tmp;
/* Cast to avoid buggy warning on MSVC. */
memset((void *) test1, 0, sizeof(test1));
/*
* Fill in NB_STRINGS_MIN, at this point the dictionary should not grow
* and we allocate all those doing the fast key computations
*/
for (i = 0;i < NB_STRINGS_MIN;i++) {
test1[i] = xmlDictLookup(dict, strings1[i], -1);
if (test1[i] == NULL) {
fprintf(stderr, "Failed lookup for '%s'\n", strings1[i]);
ret = 1;
nbErrors++;
}
}
j = NB_STRINGS_MAX - NB_STRINGS_NS;
/* ":foo" like strings1 */
for (i = 0;i < NB_STRINGS_MIN;i++, j++) {
test1[j] = xmlDictLookup(dict, strings1[j], xmlStrlen(strings1[j]));
if (test1[j] == NULL) {
fprintf(stderr, "Failed lookup for '%s'\n", strings1[j]);
ret = 1;
nbErrors++;
}
}
/* "a:foo" like strings1 */
j = NB_STRINGS_MAX - NB_STRINGS_MIN;
for (i = 0;i < NB_STRINGS_MIN;i++, j++) {
test1[j] = xmlDictLookup(dict, strings1[j], xmlStrlen(strings1[j]));
if (test1[j] == NULL) {
fprintf(stderr, "Failed lookup for '%s'\n", strings1[j]);
ret = 1;
nbErrors++;
}
}
/*
* At this point allocate all the strings
* the dictionary will grow in the process, reallocate more string tables
* and switch to the better key generator
*/
for (i = 0;i < NB_STRINGS_MAX;i++) {
if (test1[i] != NULL)
continue;
test1[i] = xmlDictLookup(dict, strings1[i], -1);
if (test1[i] == NULL) {
fprintf(stderr, "Failed lookup for '%s'\n", strings1[i]);
ret = 1;
nbErrors++;
}
}
/*
* Now we can start to test things, first that all strings1 belongs to
* the dict
*/
for (i = 0;i < NB_STRINGS_MAX;i++) {
if (!xmlDictOwns(dict, test1[i])) {
fprintf(stderr, "Failed ownership failure for '%s'\n",
strings1[i]);
ret = 1;
nbErrors++;
}
}
/*
* Then that another lookup to the string will return the same
*/
for (i = 0;i < NB_STRINGS_MAX;i++) {
if (xmlDictLookup(dict, strings1[i], -1) != test1[i]) {
fprintf(stderr, "Failed re-lookup check for %d, '%s'\n",
i, strings1[i]);
ret = 1;
nbErrors++;
}
}
/*
* More complex, check the QName lookups
*/
for (i = NB_STRINGS_MAX - NB_STRINGS_NS;i < NB_STRINGS_MAX;i++) {
cur = strings1[i];
pref = &prefix[0];
while (*cur != ':') *pref++ = *cur++;
cur++;
*pref = 0;
tmp = xmlDictQLookup(dict, &prefix[0], cur);
if (tmp != test1[i]) {
fprintf(stderr, "Failed lookup check for '%s':'%s'\n",
&prefix[0], cur);
ret = 1;
nbErrors++;
}
}
return(ret);
}
static int
testall_dict(void) {
xmlDictPtr dict;
int ret = 0;
strings1 = xmlMalloc(NB_STRINGS_MAX * sizeof(strings1[0]));
memset(strings1, 0, NB_STRINGS_MAX * sizeof(strings1[0]));
strings2 = xmlMalloc(NB_STRINGS_MAX * sizeof(strings2[0]));
memset(strings2, 0, NB_STRINGS_MAX * sizeof(strings2[0]));
test1 = xmlMalloc(NB_STRINGS_MAX * sizeof(test1[0]));
memset(test1, 0, NB_STRINGS_MAX * sizeof(test1[0]));
test2 = xmlMalloc(NB_STRINGS_MAX * sizeof(test2[0]));
memset(test2, 0, NB_STRINGS_MAX * sizeof(test2[0]));
fill_string_pool(strings1, seeds1);
fill_string_pool(strings2, seeds2);
#ifdef WITH_PRINT
print_strings();
#endif
dict = xmlDictCreate();
if (dict == NULL) {
fprintf(stderr, "Out of memory while creating dictionary\n");
exit(1);
}
if (test_dict(dict) != 0) {
ret = 1;
}
if (test_subdict(dict) != 0) {
ret = 1;
}
xmlDictFree(dict);
clean_strings();
xmlFree(strings1);
xmlFree(strings2);
xmlFree(test1);
xmlFree(test2);
return ret;
}
/**** Hash table tests ****/
static unsigned
rng_state[2] = { 123, 456 };
#define HASH_ROL(x,n) ((x) << (n) | ((x) & 0xFFFFFFFF) >> (32 - (n)))
ATTRIBUTE_NO_SANITIZE_INTEGER
static unsigned
my_rand(unsigned max) {
unsigned s0 = rng_state[0];
unsigned s1 = rng_state[1];
unsigned result = HASH_ROL(s0 * 0x9E3779BB, 5) * 5;
s1 ^= s0;
rng_state[0] = HASH_ROL(s0, 26) ^ s1 ^ (s1 << 9);
rng_state[1] = HASH_ROL(s1, 13);
return((result & 0xFFFFFFFF) % max);
}
static xmlChar *
gen_random_string(xmlChar id) {
unsigned size = my_rand(64) + 1;
unsigned id_pos = my_rand(size);
size_t j;
xmlChar *str = xmlMalloc(size + 1);
for (j = 0; j < size; j++) {
str[j] = 'a' + my_rand(26);
}
str[id_pos] = id;
str[size] = 0;
/* Generate QName in 75% of cases */
if (size > 3 && my_rand(4) > 0) {
unsigned colon_pos = my_rand(size - 3) + 1;
if (colon_pos >= id_pos)
colon_pos++;
str[colon_pos] = ':';
}
return str;
}
typedef struct {
xmlChar **strings;
size_t num_entries;
size_t num_keys;
size_t num_strings;
size_t index;
xmlChar id;
} StringPool;
static StringPool *
pool_new(size_t num_entries, size_t num_keys, xmlChar id) {
StringPool *ret;
size_t num_strings;
ret = xmlMalloc(sizeof(*ret));
ret->num_entries = num_entries;
ret->num_keys = num_keys;
num_strings = num_entries * num_keys;
ret->strings = xmlMalloc(num_strings * sizeof(ret->strings[0]));
memset(ret->strings, 0, num_strings * sizeof(ret->strings[0]));
ret->num_strings = num_strings;
ret->index = 0;
ret->id = id;
return ret;
}
static void
pool_free(StringPool *pool) {
size_t i;
for (i = 0; i < pool->num_strings; i++) {
xmlFree(pool->strings[i]);
}
xmlFree(pool->strings);
xmlFree(pool);
}
static int
pool_done(StringPool *pool) {
return pool->index >= pool->num_strings;
}
static void
pool_reset(StringPool *pool) {
pool->index = 0;
}
static int
pool_bulk_insert(StringPool *pool, xmlHashTablePtr hash, size_t num) {
size_t i, j;
int ret = 0;
for (i = pool->index, j = 0; i < pool->num_strings && j < num; j++) {
xmlChar *str[3];
size_t k;
while (1) {
xmlChar tmp_key[1];
int res;
for (k = 0; k < pool->num_keys; k++)
str[k] = gen_random_string(pool->id);
switch (pool->num_keys) {
case 1:
res = xmlHashAddEntry(hash, str[0], tmp_key);
if (res == 0 &&
xmlHashUpdateEntry(hash, str[0], str[0], NULL) != 0)
ret = -1;
break;
case 2:
res = xmlHashAddEntry2(hash, str[0], str[1], tmp_key);
if (res == 0 &&
xmlHashUpdateEntry2(hash, str[0], str[1], str[0],
NULL) != 0)
ret = -1;
break;
case 3:
res = xmlHashAddEntry3(hash, str[0], str[1], str[2],
tmp_key);
if (res == 0 &&
xmlHashUpdateEntry3(hash, str[0], str[1], str[2],
str[0], NULL) != 0)
ret = -1;
break;
}
if (res == 0)
break;
for (k = 0; k < pool->num_keys; k++)
xmlFree(str[k]);
}
for (k = 0; k < pool->num_keys; k++)
pool->strings[i++] = str[k];
}
pool->index = i;
return ret;
}
static xmlChar *
hash_qlookup(xmlHashTable *hash, xmlChar **names, size_t num_keys) {
xmlChar *prefix[3];
const xmlChar *local[3];
xmlChar *res;
size_t i;
for (i = 0; i < 3; ++i) {
if (i >= num_keys) {
prefix[i] = NULL;
local[i] = NULL;
} else {
const xmlChar *name = names[i];
const xmlChar *colon = BAD_CAST strchr((const char *) name, ':');
if (colon == NULL) {
prefix[i] = NULL;
local[i] = name;
} else {
prefix[i] = xmlStrndup(name, colon - name);
local[i] = &colon[1];
}
}
}
res = xmlHashQLookup3(hash, prefix[0], local[0], prefix[1], local[1],
prefix[2], local[2]);
for (i = 0; i < 3; ++i)
xmlFree(prefix[i]);
return res;
}
static int
pool_bulk_lookup(StringPool *pool, xmlHashTablePtr hash, size_t num,
int existing) {
size_t i, j;
int ret = 0;
for (i = pool->index, j = 0; i < pool->num_strings && j < num; j++) {
xmlChar **str = &pool->strings[i];
int q;
for (q = 0; q < 2; q++) {
xmlChar *res = NULL;
if (q) {
res = hash_qlookup(hash, str, pool->num_keys);
} else {
switch (pool->num_keys) {
case 1:
res = xmlHashLookup(hash, str[0]);
break;
case 2:
res = xmlHashLookup2(hash, str[0], str[1]);
break;
case 3:
res = xmlHashLookup3(hash, str[0], str[1], str[2]);
break;
}
}
if (existing) {
if (res != str[0])
ret = -1;
} else {
if (res != NULL)
ret = -1;
}
}
i += pool->num_keys;
}
pool->index = i;
return ret;
}
static int
pool_bulk_remove(StringPool *pool, xmlHashTablePtr hash, size_t num) {
size_t i, j;
int ret = 0;
for (i = pool->index, j = 0; i < pool->num_strings && j < num; j++) {
xmlChar **str = &pool->strings[i];
int res = -1;
switch (pool->num_keys) {
case 1:
res = xmlHashRemoveEntry(hash, str[0], NULL);
break;
case 2:
res = xmlHashRemoveEntry2(hash, str[0], str[1], NULL);
break;
case 3:
res = xmlHashRemoveEntry3(hash, str[0], str[1], str[2], NULL);
break;
}
if (res != 0)
ret = -1;
i += pool->num_keys;
}
pool->index = i;
return ret;
}
static int
test_hash(size_t num_entries, size_t num_keys, int use_dict) {
xmlDict *dict = NULL;
xmlHashTable *hash;
StringPool *pool1, *pool2;
int ret = 0;
if (use_dict) {
dict = xmlDictCreate();
hash = xmlHashCreateDict(0, dict);
} else {
hash = xmlHashCreate(0);
}
pool1 = pool_new(num_entries, num_keys, '1');
pool2 = pool_new(num_entries, num_keys, '2');
/* Insert all strings from pool2 and about half of pool1. */
while (!pool_done(pool2)) {
if (pool_bulk_insert(pool1, hash, my_rand(50)) != 0) {
fprintf(stderr, "pool1: hash insert failed\n");
ret = 1;
}
if (pool_bulk_insert(pool2, hash, my_rand(100)) != 0) {
fprintf(stderr, "pool1: hash insert failed\n");
ret = 1;
}
}
/* Check existing entries */
pool_reset(pool2);
if (pool_bulk_lookup(pool2, hash, pool2->num_entries, 1) != 0) {
fprintf(stderr, "pool2: hash lookup failed\n");
ret = 1;
}
/* Remove all strings from pool2 and insert the rest of pool1. */
pool_reset(pool2);
while (!pool_done(pool1) || !pool_done(pool2)) {
if (pool_bulk_insert(pool1, hash, my_rand(50)) != 0) {
fprintf(stderr, "pool1: hash insert failed\n");
ret = 1;
}
if (pool_bulk_remove(pool2, hash, my_rand(100)) != 0) {
fprintf(stderr, "pool2: hash remove failed\n");
ret = 1;
}
}
/* Check existing entries */
pool_reset(pool1);
if (pool_bulk_lookup(pool1, hash, pool1->num_entries, 1) != 0) {
fprintf(stderr, "pool1: hash lookup failed\n");
ret = 1;
}
/* Check removed entries */
pool_reset(pool2);
if (pool_bulk_lookup(pool2, hash, pool2->num_entries, 0) != 0) {
fprintf(stderr, "pool2: hash lookup succeeded unexpectedly\n");
ret = 1;
}
pool_free(pool1);
pool_free(pool2);
xmlHashFree(hash, NULL);
xmlDictFree(dict);
return ret;
}
static int
testall_hash(void) {
size_t num_keys;
for (num_keys = 1; num_keys <= 3; num_keys++) {
size_t num_strings;
size_t max_strings = num_keys == 1 ? 100000 : 1000;
for (num_strings = 10; num_strings <= max_strings; num_strings *= 10) {
size_t reps, i;
reps = 1000 / num_strings;
if (reps == 0)
reps = 1;
for (i = 0; i < reps; i++) {
if (test_hash(num_strings, num_keys, /* use_dict */ 0) != 0)
return(1);
}
if (test_hash(num_strings, num_keys, /* use_dict */ 1) != 0)
return(1);
}
}
return(0);
}
/**** main ****/
int
main(void) {
int ret = 0;
LIBXML_TEST_VERSION
if (testall_dict() != 0) {
fprintf(stderr, "dictionary tests failed\n");
ret = 1;
}
if (testall_hash() != 0) {
fprintf(stderr, "hash tests failed\n");
ret = 1;
}
xmlCleanupParser();
return(ret);
}