Refactor {str,digest}map into a common implementation; add digest256map

Needed for #13399.
This commit is contained in:
Nick Mathewson 2014-10-31 10:54:12 -04:00
parent 5ad8721838
commit 542b470164
3 changed files with 298 additions and 410 deletions

3
changes/bug13399_part1 Normal file
View File

@ -0,0 +1,3 @@
o Code simplifications and refactoring:
- Refactor our generic strmap and digestmap types into a single
implementation, so that we can add a new digest256map type trivially.

View File

@ -1021,6 +1021,7 @@ smartlist_uniq_digests256(smartlist_t *sl)
DEFINE_MAP_STRUCTS(strmap_t, char *key, strmap_);
DEFINE_MAP_STRUCTS(digestmap_t, char key[DIGEST_LEN], digestmap_);
DEFINE_MAP_STRUCTS(digest256map_t, uint8_t key[DIGEST256_LEN], digest256map_);
/** Helper: compare strmap_entry_t objects by key value. */
static INLINE int
@ -1050,6 +1051,21 @@ digestmap_entry_hash(const digestmap_entry_t *a)
return (unsigned) siphash24g(a->key, DIGEST_LEN);
}
/** Helper: compare digestmap_entry_t objects by key value. */
static INLINE int
digest256map_entries_eq(const digest256map_entry_t *a,
const digest256map_entry_t *b)
{
return tor_memeq(a->key, b->key, DIGEST256_LEN);
}
/** Helper: return a hash value for a digest_map_t. */
static INLINE unsigned int
digest256map_entry_hash(const digest256map_entry_t *a)
{
return (unsigned) siphash24g(a->key, DIGEST256_LEN);
}
HT_PROTOTYPE(strmap_impl, strmap_entry_t, node, strmap_entry_hash,
strmap_entries_eq)
HT_GENERATE2(strmap_impl, strmap_entry_t, node, strmap_entry_hash,
@ -1060,194 +1076,278 @@ HT_PROTOTYPE(digestmap_impl, digestmap_entry_t, node, digestmap_entry_hash,
HT_GENERATE2(digestmap_impl, digestmap_entry_t, node, digestmap_entry_hash,
digestmap_entries_eq, 0.6, tor_reallocarray_, tor_free_)
/** Constructor to create a new empty map from strings to void*'s.
HT_PROTOTYPE(digest256map_impl, digest256map_entry_t, node,
digest256map_entry_hash,
digest256map_entries_eq)
HT_GENERATE2(digest256map_impl, digest256map_entry_t, node,
digest256map_entry_hash,
digest256map_entries_eq, 0.6, tor_reallocarray_, tor_free_)
static INLINE void
strmap_entry_free(strmap_entry_t *ent)
{
tor_free(ent->key);
tor_free(ent);
}
static INLINE void
digestmap_entry_free(digestmap_entry_t *ent)
{
tor_free(ent);
}
static INLINE void
digest256map_entry_free(digest256map_entry_t *ent)
{
tor_free(ent);
}
static INLINE void
strmap_assign_tmp_key(strmap_entry_t *ent, const char *key)
{
ent->key = (char*)key;
}
static INLINE void
digestmap_assign_tmp_key(digestmap_entry_t *ent, const char *key)
{
memcpy(ent->key, key, DIGEST_LEN);
}
static INLINE void
digest256map_assign_tmp_key(digest256map_entry_t *ent, const uint8_t *key)
{
memcpy(ent->key, key, DIGEST256_LEN);
}
static INLINE void
strmap_assign_key(strmap_entry_t *ent, const char *key)
{
ent->key = tor_strdup(key);
}
static INLINE void
digestmap_assign_key(digestmap_entry_t *ent, const char *key)
{
memcpy(ent->key, key, DIGEST_LEN);
}
static INLINE void
digest256map_assign_key(digest256map_entry_t *ent, const uint8_t *key)
{
memcpy(ent->key, key, DIGEST256_LEN);
}
/**
* Macro: implement all the functions for a map that are declared in
* container.h by the DECLARE_MAP_FNS() macro. You must additionally define a
* prefix_entry_free_() function to free entries (and their keys), a
* prefix_assign_tmp_key() function to temporarily set a stack-allocated
* entry to hold a key, and a prefix_assign_key() function to set a
* heap-allocated entry to hold a key.
*/
MOCK_IMPL(strmap_t *,
strmap_new,(void))
{
strmap_t *result;
result = tor_malloc(sizeof(strmap_t));
HT_INIT(strmap_impl, &result->head);
return result;
}
/** Constructor to create a new empty map from digests to void*'s.
*/
MOCK_IMPL(digestmap_t *,
digestmap_new,(void))
{
digestmap_t *result;
result = tor_malloc(sizeof(digestmap_t));
HT_INIT(digestmap_impl, &result->head);
return result;
}
/** Set the current value for <b>key</b> to <b>val</b>. Returns the previous
* value for <b>key</b> if one was set, or NULL if one was not.
*
* This function makes a copy of <b>key</b> if necessary, but not of
* <b>val</b>.
*/
void *
strmap_set(strmap_t *map, const char *key, void *val)
{
strmap_entry_t *resolve;
strmap_entry_t search;
void *oldval;
tor_assert(map);
tor_assert(key);
tor_assert(val);
search.key = (char*)key;
resolve = HT_FIND(strmap_impl, &map->head, &search);
if (resolve) {
oldval = resolve->val;
resolve->val = val;
return oldval;
} else {
resolve = tor_malloc_zero(sizeof(strmap_entry_t));
resolve->key = tor_strdup(key);
resolve->val = val;
tor_assert(!HT_FIND(strmap_impl, &map->head, resolve));
HT_INSERT(strmap_impl, &map->head, resolve);
return NULL;
#define IMPLEMENT_MAP_FNS(maptype, keytype, prefix) \
/** Create and return a new empty map. */ \
MOCK_IMPL(maptype *, \
prefix##_new,(void)) \
{ \
maptype *result; \
result = tor_malloc(sizeof(maptype)); \
HT_INIT(prefix##_impl, &result->head); \
return result; \
} \
\
/** Return the item from <b>map</b> whose key matches <b>key</b>, or \
* NULL if no such value exists. */ \
void * \
prefix##_get(const maptype *map, const keytype key) \
{ \
prefix ##_entry_t *resolve; \
prefix ##_entry_t search; \
tor_assert(map); \
tor_assert(key); \
prefix ##_assign_tmp_key(&search, key); \
resolve = HT_FIND(prefix ##_impl, &map->head, &search); \
if (resolve) { \
return resolve->val; \
} else { \
return NULL; \
} \
} \
\
/** Add an entry to <b>map</b> mapping <b>key</b> to <b>val</b>; \
* return the previous value, or NULL if no such value existed. */ \
void * \
prefix##_set(maptype *map, const keytype key, void *val) \
{ \
prefix##_entry_t search; \
void *oldval; \
tor_assert(map); \
tor_assert(key); \
tor_assert(val); \
prefix##_assign_tmp_key(&search, key); \
/* We a lot of our time in this function, so the code below is */ \
/* meant to optimize the check/alloc/set cycle by avoiding the two */\
/* trips to the hash table that we would do in the unoptimized */ \
/* version of this code. (Each of HT_INSERT and HT_FIND calls */ \
/* HT_SET_HASH and HT_FIND_P.) */ \
HT_FIND_OR_INSERT_(prefix##_impl, node, prefix##_entry_hash, \
&(map->head), \
prefix##_entry_t, &search, ptr, \
{ \
/* we found an entry. */ \
oldval = (*ptr)->val; \
(*ptr)->val = val; \
return oldval; \
}, \
{ \
/* We didn't find the entry. */ \
prefix##_entry_t *newent = \
tor_malloc_zero(sizeof(prefix##_entry_t)); \
prefix##_assign_key(newent, key); \
newent->val = val; \
HT_FOI_INSERT_(node, &(map->head), \
&search, newent, ptr); \
return NULL; \
}); \
} \
\
/** Remove the value currently associated with <b>key</b> from the map. \
* Return the value if one was set, or NULL if there was no entry for \
* <b>key</b>. \
* \
* Note: you must free any storage associated with the returned value. \
*/ \
void * \
prefix##_remove(maptype *map, const keytype key) \
{ \
prefix##_entry_t *resolve; \
prefix##_entry_t search; \
void *oldval; \
tor_assert(map); \
tor_assert(key); \
prefix##_assign_tmp_key(&search, key); \
resolve = HT_REMOVE(prefix##_impl, &map->head, &search); \
if (resolve) { \
oldval = resolve->val; \
prefix##_entry_free(resolve); \
return oldval; \
} else { \
return NULL; \
} \
} \
\
/** Return the number of elements in <b>map</b>. */ \
int \
prefix##_size(const maptype *map) \
{ \
return HT_SIZE(&map->head); \
} \
\
/** Return true iff <b>map</b> has no entries. */ \
int \
prefix##_isempty(const maptype *map) \
{ \
return HT_EMPTY(&map->head); \
} \
\
/** Assert that <b>map</b> is not corrupt. */ \
void \
prefix##_assert_ok(const maptype *map) \
{ \
tor_assert(!prefix##_impl_HT_REP_IS_BAD_(&map->head)); \
} \
\
/** Remove all entries from <b>map</b>, and deallocate storage for \
* those entries. If free_val is provided, invoked it every value in \
* <b>map</b>. */ \
MOCK_IMPL(void, \
prefix##_free, (maptype *map, void (*free_val)(void*))) \
{ \
prefix##_entry_t **ent, **next, *this; \
if (!map) \
return; \
for (ent = HT_START(prefix##_impl, &map->head); ent != NULL; \
ent = next) { \
this = *ent; \
next = HT_NEXT_RMV(prefix##_impl, &map->head, ent); \
if (free_val) \
free_val(this->val); \
prefix##_entry_free(this); \
} \
tor_assert(HT_EMPTY(&map->head)); \
HT_CLEAR(prefix##_impl, &map->head); \
tor_free(map); \
} \
\
/** return an <b>iterator</b> pointer to the front of a map. \
* \
* Iterator example: \
* \
* \code \
* // uppercase values in "map", removing empty values. \
* \
* strmap_iter_t *iter; \
* const char *key; \
* void *val; \
* char *cp; \
* \
* for (iter = strmap_iter_init(map); !strmap_iter_done(iter); ) { \
* strmap_iter_get(iter, &key, &val); \
* cp = (char*)val; \
* if (!*cp) { \
* iter = strmap_iter_next_rmv(map,iter); \
* free(val); \
* } else { \
* for (;*cp;cp++) *cp = TOR_TOUPPER(*cp); \
*/ \
prefix##_iter_t * \
prefix##_iter_init(maptype *map) \
{ \
tor_assert(map); \
return HT_START(prefix##_impl, &map->head); \
} \
\
/** Advance <b>iter</b> a single step to the next entry, and return \
* its new value. */ \
prefix##_iter_t * \
prefix##_iter_next(maptype *map, prefix##_iter_t *iter) \
{ \
tor_assert(map); \
tor_assert(iter); \
return HT_NEXT(prefix##_impl, &map->head, iter); \
} \
/** Advance <b>iter</b> a single step to the next entry, removing the \
* current entry, and return its new value. */ \
prefix##_iter_t * \
prefix##_iter_next_rmv(maptype *map, prefix##_iter_t *iter) \
{ \
prefix##_entry_t *rmv; \
tor_assert(map); \
tor_assert(iter); \
tor_assert(*iter); \
rmv = *iter; \
iter = HT_NEXT_RMV(prefix##_impl, &map->head, iter); \
prefix##_entry_free(rmv); \
return iter; \
} \
/** Set *<b>keyp</b> and *<b>valp</b> to the current entry pointed \
* to by iter. */ \
void \
prefix##_iter_get(prefix##_iter_t *iter, const keytype *keyp, \
void **valp) \
{ \
tor_assert(iter); \
tor_assert(*iter); \
tor_assert(keyp); \
tor_assert(valp); \
*keyp = (*iter)->key; \
*valp = (*iter)->val; \
} \
/** Return true iff <b>iter</b> has advanced past the last entry of \
* <b>map</b>. */ \
int \
prefix##_iter_done(prefix##_iter_t *iter) \
{ \
return iter == NULL; \
}
}
#define OPTIMIZED_DIGESTMAP_SET
/** Like strmap_set() above but for digestmaps. */
void *
digestmap_set(digestmap_t *map, const char *key, void *val)
{
#ifndef OPTIMIZED_DIGESTMAP_SET
digestmap_entry_t *resolve;
#endif
digestmap_entry_t search;
void *oldval;
tor_assert(map);
tor_assert(key);
tor_assert(val);
memcpy(&search.key, key, DIGEST_LEN);
#ifndef OPTIMIZED_DIGESTMAP_SET
resolve = HT_FIND(digestmap_impl, &map->head, &search);
if (resolve) {
oldval = resolve->val;
resolve->val = val;
return oldval;
} else {
resolve = tor_malloc_zero(sizeof(digestmap_entry_t));
memcpy(resolve->key, key, DIGEST_LEN);
resolve->val = val;
HT_INSERT(digestmap_impl, &map->head, resolve);
return NULL;
}
#else
/* We spend up to 5% of our time in this function, so the code below is
* meant to optimize the check/alloc/set cycle by avoiding the two trips to
* the hash table that we do in the unoptimized code above. (Each of
* HT_INSERT and HT_FIND calls HT_SET_HASH and HT_FIND_P.)
*/
HT_FIND_OR_INSERT_(digestmap_impl, node, digestmap_entry_hash, &(map->head),
digestmap_entry_t, &search, ptr,
{
/* we found an entry. */
oldval = (*ptr)->val;
(*ptr)->val = val;
return oldval;
},
{
/* We didn't find the entry. */
digestmap_entry_t *newent =
tor_malloc_zero(sizeof(digestmap_entry_t));
memcpy(newent->key, key, DIGEST_LEN);
newent->val = val;
HT_FOI_INSERT_(node, &(map->head), &search, newent, ptr);
return NULL;
});
#endif
}
/** Return the current value associated with <b>key</b>, or NULL if no
* value is set.
*/
void *
strmap_get(const strmap_t *map, const char *key)
{
strmap_entry_t *resolve;
strmap_entry_t search;
tor_assert(map);
tor_assert(key);
search.key = (char*)key;
resolve = HT_FIND(strmap_impl, &map->head, &search);
if (resolve) {
return resolve->val;
} else {
return NULL;
}
}
/** Like strmap_get() above but for digestmaps. */
void *
digestmap_get(const digestmap_t *map, const char *key)
{
digestmap_entry_t *resolve;
digestmap_entry_t search;
tor_assert(map);
tor_assert(key);
memcpy(&search.key, key, DIGEST_LEN);
resolve = HT_FIND(digestmap_impl, &map->head, &search);
if (resolve) {
return resolve->val;
} else {
return NULL;
}
}
/** Remove the value currently associated with <b>key</b> from the map.
* Return the value if one was set, or NULL if there was no entry for
* <b>key</b>.
*
* Note: you must free any storage associated with the returned value.
*/
void *
strmap_remove(strmap_t *map, const char *key)
{
strmap_entry_t *resolve;
strmap_entry_t search;
void *oldval;
tor_assert(map);
tor_assert(key);
search.key = (char*)key;
resolve = HT_REMOVE(strmap_impl, &map->head, &search);
if (resolve) {
oldval = resolve->val;
tor_free(resolve->key);
tor_free(resolve);
return oldval;
} else {
return NULL;
}
}
/** Like strmap_remove() above but for digestmaps. */
void *
digestmap_remove(digestmap_t *map, const char *key)
{
digestmap_entry_t *resolve;
digestmap_entry_t search;
void *oldval;
tor_assert(map);
tor_assert(key);
memcpy(&search.key, key, DIGEST_LEN);
resolve = HT_REMOVE(digestmap_impl, &map->head, &search);
if (resolve) {
oldval = resolve->val;
tor_free(resolve);
return oldval;
} else {
return NULL;
}
}
IMPLEMENT_MAP_FNS(strmap_t, char *, strmap)
IMPLEMENT_MAP_FNS(digestmap_t, char *, digestmap)
IMPLEMENT_MAP_FNS(digest256map_t, uint8_t *, digest256map)
/** Same as strmap_set, but first converts <b>key</b> to lowercase. */
void *
@ -1287,231 +1387,6 @@ strmap_remove_lc(strmap_t *map, const char *key)
return v;
}
/** return an <b>iterator</b> pointer to the front of a map.
*
* Iterator example:
*
* \code
* // uppercase values in "map", removing empty values.
*
* strmap_iter_t *iter;
* const char *key;
* void *val;
* char *cp;
*
* for (iter = strmap_iter_init(map); !strmap_iter_done(iter); ) {
* strmap_iter_get(iter, &key, &val);
* cp = (char*)val;
* if (!*cp) {
* iter = strmap_iter_next_rmv(map,iter);
* free(val);
* } else {
* for (;*cp;cp++) *cp = TOR_TOUPPER(*cp);
* iter = strmap_iter_next(map,iter);
* }
* }
* \endcode
*
*/
strmap_iter_t *
strmap_iter_init(strmap_t *map)
{
tor_assert(map);
return HT_START(strmap_impl, &map->head);
}
/** Start iterating through <b>map</b>. See strmap_iter_init() for example. */
digestmap_iter_t *
digestmap_iter_init(digestmap_t *map)
{
tor_assert(map);
return HT_START(digestmap_impl, &map->head);
}
/** Advance the iterator <b>iter</b> for <b>map</b> a single step to the next
* entry, and return its new value. */
strmap_iter_t *
strmap_iter_next(strmap_t *map, strmap_iter_t *iter)
{
tor_assert(map);
tor_assert(iter);
return HT_NEXT(strmap_impl, &map->head, iter);
}
/** Advance the iterator <b>iter</b> for map a single step to the next entry,
* and return its new value. */
digestmap_iter_t *
digestmap_iter_next(digestmap_t *map, digestmap_iter_t *iter)
{
tor_assert(map);
tor_assert(iter);
return HT_NEXT(digestmap_impl, &map->head, iter);
}
/** Advance the iterator <b>iter</b> a single step to the next entry, removing
* the current entry, and return its new value.
*/
strmap_iter_t *
strmap_iter_next_rmv(strmap_t *map, strmap_iter_t *iter)
{
strmap_entry_t *rmv;
tor_assert(map);
tor_assert(iter);
tor_assert(*iter);
rmv = *iter;
iter = HT_NEXT_RMV(strmap_impl, &map->head, iter);
tor_free(rmv->key);
tor_free(rmv);
return iter;
}
/** Advance the iterator <b>iter</b> a single step to the next entry, removing
* the current entry, and return its new value.
*/
digestmap_iter_t *
digestmap_iter_next_rmv(digestmap_t *map, digestmap_iter_t *iter)
{
digestmap_entry_t *rmv;
tor_assert(map);
tor_assert(iter);
tor_assert(*iter);
rmv = *iter;
iter = HT_NEXT_RMV(digestmap_impl, &map->head, iter);
tor_free(rmv);
return iter;
}
/** Set *<b>keyp</b> and *<b>valp</b> to the current entry pointed to by
* iter. */
void
strmap_iter_get(strmap_iter_t *iter, const char **keyp, void **valp)
{
tor_assert(iter);
tor_assert(*iter);
tor_assert(keyp);
tor_assert(valp);
*keyp = (*iter)->key;
*valp = (*iter)->val;
}
/** Set *<b>keyp</b> and *<b>valp</b> to the current entry pointed to by
* iter. */
void
digestmap_iter_get(digestmap_iter_t *iter, const char **keyp, void **valp)
{
tor_assert(iter);
tor_assert(*iter);
tor_assert(keyp);
tor_assert(valp);
*keyp = (*iter)->key;
*valp = (*iter)->val;
}
/** Return true iff <b>iter</b> has advanced past the last entry of
* <b>map</b>. */
int
strmap_iter_done(strmap_iter_t *iter)
{
return iter == NULL;
}
/** Return true iff <b>iter</b> has advanced past the last entry of
* <b>map</b>. */
int
digestmap_iter_done(digestmap_iter_t *iter)
{
return iter == NULL;
}
/** Remove all entries from <b>map</b>, and deallocate storage for those
* entries. If free_val is provided, it is invoked on every value in
* <b>map</b>.
*/
MOCK_IMPL(void,
strmap_free,(strmap_t *map, void (*free_val)(void*)))
{
strmap_entry_t **ent, **next, *this;
if (!map)
return;
for (ent = HT_START(strmap_impl, &map->head); ent != NULL; ent = next) {
this = *ent;
next = HT_NEXT_RMV(strmap_impl, &map->head, ent);
tor_free(this->key);
if (free_val)
free_val(this->val);
tor_free(this);
}
tor_assert(HT_EMPTY(&map->head));
HT_CLEAR(strmap_impl, &map->head);
tor_free(map);
}
/** Remove all entries from <b>map</b>, and deallocate storage for those
* entries. If free_val is provided, it is invoked on every value in
* <b>map</b>.
*/
MOCK_IMPL(void,
digestmap_free, (digestmap_t *map, void (*free_val)(void*)))
{
digestmap_entry_t **ent, **next, *this;
if (!map)
return;
for (ent = HT_START(digestmap_impl, &map->head); ent != NULL; ent = next) {
this = *ent;
next = HT_NEXT_RMV(digestmap_impl, &map->head, ent);
if (free_val)
free_val(this->val);
tor_free(this);
}
tor_assert(HT_EMPTY(&map->head));
HT_CLEAR(digestmap_impl, &map->head);
tor_free(map);
}
/** Fail with an assertion error if anything has gone wrong with the internal
* representation of <b>map</b>. */
void
strmap_assert_ok(const strmap_t *map)
{
tor_assert(!strmap_impl_HT_REP_IS_BAD_(&map->head));
}
/** Fail with an assertion error if anything has gone wrong with the internal
* representation of <b>map</b>. */
void
digestmap_assert_ok(const digestmap_t *map)
{
tor_assert(!digestmap_impl_HT_REP_IS_BAD_(&map->head));
}
/** Return true iff <b>map</b> has no entries. */
int
strmap_isempty(const strmap_t *map)
{
return HT_EMPTY(&map->head);
}
/** Return true iff <b>map</b> has no entries. */
int
digestmap_isempty(const digestmap_t *map)
{
return HT_EMPTY(&map->head);
}
/** Return the number of items in <b>map</b>. */
int
strmap_size(const strmap_t *map)
{
return HT_SIZE(&map->head);
}
/** Return the number of items in <b>map</b>. */
int
digestmap_size(const digestmap_t *map)
{
return HT_SIZE(&map->head);
}
/** Declare a function called <b>funcname</b> that acts as a find_nth_FOO
* function for an array of type <b>elt_t</b>*.
*

View File

@ -346,6 +346,9 @@ char *smartlist_join_strings2(smartlist_t *sl, const char *join,
DECLARE_MAP_FNS(strmap_t, const char *, strmap_);
/* Map from const char[DIGEST_LEN] to void *. Implemented with a hash table. */
DECLARE_MAP_FNS(digestmap_t, const char *, digestmap_);
/* Map from const uint8_t[DIGEST_LEN] to void *. Implemented with a hash
* table. */
DECLARE_MAP_FNS(digest256map_t, const uint8_t *, digest256map_);
#undef DECLARE_MAP_FNS
@ -461,6 +464,13 @@ DECLARE_MAP_FNS(digestmap_t, const char *, digestmap_);
/** Used to end a DIGESTMAP_FOREACH() block. */
#define DIGESTMAP_FOREACH_END MAP_FOREACH_END
#define DIGEST256MAP_FOREACH(map, keyvar, valtype, valvar) \
MAP_FOREACH(digest256map_, map, const uint8_t *, keyvar, valtype, valvar)
#define DIGEST256MAP_FOREACH_MODIFY(map, keyvar, valtype, valvar) \
MAP_FOREACH_MODIFY(digest256map_, map, const uint8_t *, \
keyvar, valtype, valvar)
#define DIGEST256MAP_FOREACH_END MAP_FOREACH_END
#define STRMAP_FOREACH(map, keyvar, valtype, valvar) \
MAP_FOREACH(strmap_, map, const char *, keyvar, valtype, valvar)
#define STRMAP_FOREACH_MODIFY(map, keyvar, valtype, valvar) \