BT_TEST,
} build_target;
-jmp_buf *get_test_bug_jump(char *msg);
+jmp_buf *get_test_bug_jump(const char *msg);
static inline int uint_cmp(uint i1, uint i2)
{ return (int)(i1 > i2) - (int)(i1 < i2); }
#ifndef _BIRD_HASH_H_
#define _BIRD_HASH_H_
-#define HASH(type) struct { type **data; uint count, order; char* is_in_walk; }
+enum hash_walk_state {
+ NO_WALK,
+ WALK,
+ WALK_DELSAFE,
+ WALK_RESIZABLE,
+ NEED_RESIZE
+};
+
+
+#define HASH(type) struct { type **data; uint count, order; char* is_in_walk; int* deep_of_walks;}
#define HASH_TYPE(v) typeof(** (v).data)
#define HASH_SIZE(v) (1U << (v).order)
(v).order = (init_order); \
(v).data = mb_allocz(pool, HASH_SIZE(v) * sizeof(* (v).data)); \
(v).is_in_walk = mb_allocz(pool, sizeof(char)); \
- *(v).is_in_walk = 0; \
+ *(v).is_in_walk = NO_WALK; \
+ (v).deep_of_walks = mb_allocz(pool, sizeof(char)); \
+ *(v).deep_of_walks = 0; \
})
#define HASH_FREE(v) \
#define HASH_INSERT(v,id,node) \
({ \
- if (*(v).is_in_walk) \
- bug("HASH_INSERT: Attempt to insert in HASH_WALK"); \
u32 _h = HASH_FN(v, id, id##_KEY((node))); \
HASH_TYPE(v) **_nn = (v).data + _h; \
id##_NEXT(node) = *_nn; \
#define HASH_DO_REMOVE(v,id,_nn) \
({ \
- if (*(v).is_in_walk) \
- bug("HASH_DELETE: Attempt to remove in HASH_WALK"); \
*_nn = id##_NEXT((*_nn)); \
(v).count--; \
})
#define HASH_DELETE(v,id,key...) \
({ \
+ if (*(v).is_in_walk == WALK) \
+ bug("HASH_DELETE: Attempt to delete in HASH_WALK"); \
+ if (*(v).deep_of_walks > 1) \
+ bug("HASH_DELETE: Attempt to delete inside multiple hash walks"); \
u32 _h = HASH_FN(v, id, key); \
HASH_TYPE(v) *_n, **_nn = (v).data + _h; \
\
#define HASH_REMOVE(v,id,node) \
({ \
+ if (*(v).is_in_walk == WALK) \
+ bug("HASH_REMOVE: Attempt to remove in HASH_WALK"); \
+ if (*(v).deep_of_walks > 1) \
+ bug("HASH_REMOVE: Attempt to remove inside multiple hash walks"); \
u32 _h = HASH_FN(v, id, id##_KEY((node))); \
HASH_TYPE(v) *_n, **_nn = (v).data + _h; \
\
#define HASH_MAY_STEP_UP_(v,pool,rehash_fn,args) \
({ \
- if (((v).count > (HASH_SIZE(v) REHASH_HI_MARK(args))) && \
- ((v).order < (REHASH_HI_BOUND(args)))) \
+ if (((v).count > (HASH_SIZE(v) REHASH_HI_MARK(args))) && \
+ ((v).order <= (REHASH_HI_BOUND(args) - REHASH_HI_STEP(args)))) \
rehash_fn(&(v), pool, REHASH_HI_STEP(args)); \
})
#define HASH_MAY_STEP_DOWN_(v,pool,rehash_fn,args) \
({ \
- if (((v).count < (HASH_SIZE(v) REHASH_LO_MARK(args))) && \
- ((v).order > (REHASH_LO_BOUND(args)))) \
+ if (((v).count < (HASH_SIZE(v) REHASH_LO_MARK(args))) && \
+ ((v).order >= (REHASH_LO_BOUND(args) + REHASH_LO_STEP(args)))) \
rehash_fn(&(v), pool, -(REHASH_LO_STEP(args))); \
})
#define HASH_MAY_RESIZE_DOWN_(v,pool,rehash_fn,args) \
- ({ \
- uint _o = (v).order; \
+ ({ \
+ uint _o = (v).order; \
while (((v).count < ((1U << _o) REHASH_LO_MARK(args))) && \
(_o > (REHASH_LO_BOUND(args)))) \
_o -= (REHASH_LO_STEP(args)); \
- if (_o < (v).order) \
+ if (_o < (v).order) \
rehash_fn(&(v), pool, _o - (v).order); \
})
#define HASH_INSERT2(v,id,pool,node) \
({ \
+ if (*(v).is_in_walk == WALK || *(v).is_in_walk == WALK_DELSAFE) \
+ bug("HASH_INSERT2: called in hash walk or hash delsafe walk"); \
HASH_INSERT(v, id, node); \
- HASH_MAY_STEP_UP(v, id, pool); \
+ if (*(v).is_in_walk == NO_WALK) \
+ HASH_MAY_STEP_UP(v, id, pool); \
+ else if (*(v).is_in_walk == WALK_RESIZABLE) \
+ *(v).is_in_walk = NEED_RESIZE; \
})
#define HASH_DELETE2(v,id,pool,key...) \
({ \
+ if (*(v).is_in_walk == WALK || *(v).is_in_walk == WALK_DELSAFE) \
+ bug("HASH_DELETE2 called in hash walk or hash delsafe walk"); \
HASH_TYPE(v) *_n = HASH_DELETE(v, id, key); \
- if (_n) HASH_MAY_STEP_DOWN(v, id, pool); \
+ if (*(v).is_in_walk == WALK_RESIZABLE) \
+ *(v).is_in_walk = NEED_RESIZE; \
+ else if (*(v).is_in_walk == NO_WALK) \
+ if (_n) HASH_MAY_STEP_DOWN(v, id, pool); \
_n; \
})
#define HASH_REMOVE2(v,id,pool,node) \
({ \
+ if (*(v).is_in_walk == WALK || *(v).is_in_walk == WALK_DELSAFE) \
+ bug("HASH_REMOVE2 called in hash walk or hash delsafe walk"); \
HASH_TYPE(v) *_n = HASH_REMOVE(v, id, node); \
- if (_n) HASH_MAY_STEP_DOWN(v, id, pool); \
+ if (*(v).is_in_walk == WALK_RESIZABLE) \
+ *(v).is_in_walk = NEED_RESIZE; \
+ else if (*(v).is_in_walk == NO_WALK) \
+ if (_n) HASH_MAY_STEP_DOWN(v, id, pool); \
_n; \
})
#define HASH_WALK(v,next,n) \
do { \
HASH_TYPE(v) *n; \
- *(v).is_in_walk = 1; \
+ if (*(v).is_in_walk != WALK && *(v).is_in_walk != NO_WALK) \
+ bug("HASH_WALK can not be called from other walks"); \
+ *(v).is_in_walk = WALK; \
+ *(v).deep_of_walks += 1; \
uint _i; \
uint _s = HASH_SIZE(v); \
for (_i = 0; _i < _s; _i++) \
for (n = (v).data[_i]; n; n = n->next)
-#define HASH_WALK_END(v) *(v).is_in_walk = 0; } while (0)
+#define HASH_WALK_END(v) \
+ if (*(v).is_in_walk != WALK) \
+ bug("HASH_WALK_END called when HASH_WALK is not opened"); \
+ *(v).deep_of_walks -= 1; \
+ if (*(v).deep_of_walks == 0) \
+ *(v).is_in_walk = NO_WALK; \
+ } while (0) \
#define HASH_WALK_DELSAFE(v,next,n) \
do { \
HASH_TYPE(v) *n, *_next; \
+ if (*(v).is_in_walk != NO_WALK && *(v).is_in_walk != WALK_DELSAFE) \
+ bug("HASH_WALK_DELSAFE can not be called from other walks"); \
+ *(v).is_in_walk = WALK_DELSAFE; \
+ *(v).deep_of_walks += 1; \
uint _i; \
uint _s = HASH_SIZE(v); \
for (_i = 0; _i < _s; _i++) \
for (n = (v).data[_i]; n && (_next = n->next, 1); n = _next)
-#define HASH_WALK_DELSAFE_END } while (0)
+#define HASH_WALK_DELSAFE_END(v) \
+ if (*(v).is_in_walk != WALK_DELSAFE) \
+ bug("HASH_WALK_DELSAFE_END called when HASH_WALK_DELSAFE is not opened"); \
+ *(v).deep_of_walks -= 1; \
+ if (*(v).deep_of_walks == 0) \
+ *(v).is_in_walk = NO_WALK; \
+ } while (0)
+
+
+#define HASH_WALK_RESIZABLE(v,next,n) \
+ do { \
+ HASH_TYPE(v) *n, *_next; \
+ if (*(v).is_in_walk == NO_WALK) \
+ *(v).is_in_walk = WALK_RESIZABLE; \
+ else if (*(v).is_in_walk != WALK_RESIZABLE && *(v).is_in_walk != NEED_RESIZE) \
+ bug("HASH_WALK_RESIZABLE can not be called from other walks"); \
+ *(v).deep_of_walks += 1; \
+ uint _i; \
+ uint _s = HASH_SIZE(v); \
+ for (_i = 0; _i < _s; _i++) \
+ for (n = (v).data[_i]; n && (_next = n->next, 1); n = _next)
+#define HASH_WALK_RESIZABLE_END(v, id, pool) \
+ if (*(v).is_in_walk != WALK_RESIZABLE && *(v).is_in_walk != NEED_RESIZE) \
+ bug("HASH_WALK_RESIZABLE_END called when HASH_WALK_RESIZABLE is not opened"); \
+ *(v).deep_of_walks -= 1; \
+ if (*(v).deep_of_walks == 0) \
+ { \
+ if (*(v).is_in_walk == NEED_RESIZE) \
+ { \
+ *(v).is_in_walk = NO_WALK; \
+ uint order; \
+ do { \
+ order = (v).order; \
+ HASH_MAY_STEP_DOWN(v, id, pool); \
+ } while (order!=(v).order); \
+ do { \
+ order = (v).order; \
+ HASH_MAY_STEP_UP(v, id, pool); \
+ } while (order!=(v).order); \
+ } \
+ *(v).is_in_walk = NO_WALK; \
+ } \
+ } while (0)
#define HASH_WALK_FILTER(v,next,n,nn) \
do { \
#define TEST_EQ(n1,n2) n1 == n2
#define TEST_FN(n) (n) ^ u32_hash((n))
#define TEST_ORDER 13
-#define TEST_PARAMS /TEST_ORDER, *2, 2, 2, TEST_ORDER, 20
+#define TEST_PARAMS /TEST_ORDER, *2, 2, 2, 8, 20
#define TEST_REHASH test_rehash
HASH_DEFINE_REHASH_FN(TEST, struct test_node);
{
HASH_DELETE(hash, TEST, n->key);
}
- HASH_WALK_DELSAFE_END;
+ HASH_WALK_DELSAFE_END(hash);
validate_empty_hash();
{
HASH_REMOVE(hash, TEST, n);
}
- HASH_WALK_DELSAFE_END;
+ HASH_WALK_DELSAFE_END(hash);
validate_empty_hash();
}
static int
-t_walk_delsafe_delete2(void)
+t_walk_resizable_delete2(void)
{
init_hash();
fill_hash();
- HASH_WALK_DELSAFE(hash, next, n)
+ HASH_WALK_RESIZABLE(hash, next, n)
{
HASH_DELETE2(hash, TEST, my_pool, n->key);
}
- HASH_WALK_DELSAFE_END;
+ HASH_WALK_RESIZABLE_END(hash, TEST, my_pool);
validate_empty_hash();
-
return 1;
}
static int
-t_walk_delsafe_remove2(void)
+t_walk_resizable_remove2(void)
{
init_hash();
fill_hash();
+ bt_assert(hash.order == 13);
- HASH_WALK_DELSAFE(hash, next, n)
+ HASH_WALK_RESIZABLE(hash, next, n)
{
HASH_REMOVE2(hash, TEST, my_pool, n);
}
- HASH_WALK_DELSAFE_END;
+ HASH_WALK_RESIZABLE_END(hash, TEST, my_pool);
+
+ bt_assert(hash.order == 9);
validate_empty_hash();
return 1;
}
+static int
+t_walk_multilevel(void)
+{
+ init_hash();
+ fill_hash();
+
+ int check = 0;
+
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ check++;
+ }
+ HASH_WALK_DELSAFE_END(hash);
+ }
+ HASH_WALK_DELSAFE_END(hash);
+
+ bt_assert(check == MAX_NUM * MAX_NUM);
+ return 1;
+}
+
+
static int
t_walk_filter(void)
{
void
do_walk_delete_error(void)
{
- init_hash();
+ init_hash();
fill_hash();
HASH_WALK(hash, next, n)
HASH_WALK_END(hash);
}
+void
+do_walk_remove_error(void)
+{
+ init_hash();
+ fill_hash();
+
+ HASH_WALK(hash, next, n)
+ {
+ HASH_REMOVE(hash, TEST, n);
+ }
+ HASH_WALK_END(hash);
+}
+
+void
+do_bad_end_error(void)
+{
+init_hash();
+ fill_hash();
+
+ int i = 0;
+ HASH_WALK(hash, next, n)
+ {
+ i++;
+ }
+ HASH_WALK_DELSAFE_END(hash);
+}
+
+void
+delete_from_multiple_walks_bug(void)
+{
+ init_hash();
+ fill_hash();
+
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ HASH_DELETE(hash, TEST, n->key);
+ }
+ HASH_WALK_DELSAFE_END(hash);
+ }
+ HASH_WALK_DELSAFE_END(hash);
+}
+
+void
+remove_from_multiple_walks_bug(void)
+{
+ init_hash();
+ fill_hash();
+
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ HASH_REMOVE(hash, TEST, n);
+ }
+ HASH_WALK_DELSAFE_END(hash);
+ }
+ HASH_WALK_DELSAFE_END(hash);
+}
+
+void
+delsafe_insert2_bug(void)
+{
+ init_hash();
+ fill_hash();
+
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ struct test_node *node; // The test should crash soon enough not to recognise uninitialized pointer
+ HASH_INSERT2(hash, TEST, my_pool, node);
+ }
+ HASH_WALK_DELSAFE_END(hash);
+}
+
+void
+walk_delete2_bug(void)
+{
+ init_hash();
+ fill_hash();
+
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ HASH_DELETE2(hash, TEST, my_pool, n->key);
+ }
+ HASH_WALK_DELSAFE_END(hash);
+}
+
+void
+delsafe_different_walks_bug(void)
+{
+ init_hash();
+ fill_hash();
+
+ int i = 0;
+ HASH_WALK(hash, next, n)
+ {
+ HASH_WALK_DELSAFE(hash, next, n)
+ {
+ i++;
+ }
+ HASH_WALK_DELSAFE_END(hash);
+ }
+ HASH_WALK_END(hash);
+}
+
+void
+walk_different_walks_bug(void)
+{
+ init_hash();
+ fill_hash();
+
+ int i = 0;
+ HASH_WALK_RESIZABLE(hash, next, n)
+ {
+ HASH_WALK(hash, next, n)
+ {
+ i++;
+ }
+ HASH_WALK_END(hash);
+ }
+ HASH_WALK_RESIZABLE_END(hash, TEST, my_pool);
+}
+
+void
+resizable_different_walks_bug(void)
+{
+ init_hash();
+ fill_hash();
+
+ int i = 0;
+ HASH_WALK(hash, next, n)
+ {
+ HASH_WALK_RESIZABLE(hash, next, n)
+ {
+ i++;
+ }
+ HASH_WALK_RESIZABLE_END(hash, TEST, my_pool);
+ }
+ HASH_WALK_END(hash);
+}
+
+static int
+t_walk_check_delete_bug(void)
+{
+ return bt_assert_bug(do_walk_delete_error, "HASH_DELETE: Attempt to delete in HASH_WALK");
+}
+
+static int
+t_walk_check_remove_bug(void)
+{
+ return bt_assert_bug(do_walk_remove_error, "HASH_REMOVE: Attempt to remove in HASH_WALK");
+}
+
+static int
+t_walk_check_end_bug(void)
+{
+ return bt_assert_bug(do_bad_end_error, "HASH_WALK_DELSAFE_END called when HASH_WALK_DELSAFE is not opened");
+}
+
static int
-t_walk_check_bug(void)
+t_delete_from_multiple_walks_bug(void)
{
- return bt_assert_bug(do_walk_delete_error, "HASH_DELETE: Attempt to remove in HASH_WALK");
+ return bt_assert_bug(delete_from_multiple_walks_bug, "HASH_DELETE: Attempt to delete inside multiple hash walks");
}
+static int
+t_remove_from_multiple_walks_bug(void)
+{
+ return bt_assert_bug(remove_from_multiple_walks_bug, "HASH_REMOVE: Attempt to remove inside multiple hash walks");
+}
+
+static int
+t_delete2_bug(void)
+{
+ return bt_assert_bug(walk_delete2_bug, "HASH_DELETE2 called in hash walk or hash delsafe walk");
+}
+
+static int
+t_insert2_bug(void)
+{
+ return bt_assert_bug(delsafe_insert2_bug, "HASH_INSERT2: called in hash walk or hash delsafe walk");
+}
+
+static int
+t_mixing_walks_bug(void)
+{
+ int ret = 1;
+ ret = ret && bt_assert_bug(walk_different_walks_bug, "HASH_WALK can not be called from other walks");
+ ret = ret && bt_assert_bug(resizable_different_walks_bug, "HASH_WALK_RESIZABLE can not be called from other walks");
+ ret = ret && bt_assert_bug(delsafe_different_walks_bug, "HASH_WALK_DELSAFE can not be called from other walks");
+ return ret;
+}
+
+
int
main(int argc, char *argv[])
bt_test_suite(t_insert2_find, "HASH_INSERT2 and HASH_FIND. HASH_INSERT2 is HASH_INSERT and a smart auto-resize function");
bt_test_suite(t_walk, "HASH_WALK");
bt_test_suite(t_walk_delsafe_delete, "HASH_WALK_DELSAFE and HASH_DELETE");
- bt_test_suite(t_walk_delsafe_delete2, "HASH_WALK_DELSAFE and HASH_DELETE2. HASH_DELETE2 is HASH_DELETE and smart auto-resize function");
+ bt_test_suite(t_walk_resizable_delete2, "HASH_WALK_DELSAFE and HASH_DELETE2. HASH_DELETE2 is HASH_DELETE and smart auto-resize function");
bt_test_suite(t_walk_delsafe_remove, "HASH_WALK_DELSAFE and HASH_REMOVE");
- bt_test_suite(t_walk_delsafe_remove2, "HASH_WALK_DELSAFE and HASH_REMOVE2. HASH_REMOVE2 is HASH_REMOVE and smart auto-resize function");
+ bt_test_suite(t_walk_resizable_remove2, "HASH_WALK_RESIZABLE and HASH_REMOVE2. HASH_REMOVE2 is HASH_REMOVE and smart auto-resize function");
bt_test_suite(t_walk_filter, "HASH_WALK_FILTER");
- bt_test_suite(t_walk_check_bug, "HASH_DO_REMOVE returns error, because called from HASH_WALK");
+ bt_test_suite(t_walk_check_remove_bug, "HASH_DO_REMOVE returns error, because called from HASH_WALK");
+ bt_test_suite(t_walk_check_delete_bug, "HASH_DO_DELETE returns error, because called from HASH_WALK");
+ bt_test_suite(t_walk_check_end_bug, "HASH_WALK_DELSAFE_END called when HASH_WALK_DELSAFE is not opened");
+ bt_test_suite(t_delete_from_multiple_walks_bug, "HASH_DELETE called inside multiple hash walks");
+ bt_test_suite(t_remove_from_multiple_walks_bug, "HASH_REMOVE called inside multiple hash walks");
+ bt_test_suite(t_delete2_bug, "HASH_DELETE2 called inside hash walk");
+ bt_test_suite(t_insert2_bug, "HASH_INSERT2 called inside delsafe hash walk");
+ bt_test_suite(t_mixing_walks_bug, "Mixing multiple types of walks");
+ bt_test_suite(t_walk_multilevel, "HASH_WALK walk inside walk");
return bt_exit_value();
}
debug("\n");
}
+#ifndef BACKTRACE_MAX_LINES
+// not in test
+// To be compilatible with some systems, we need to define get_test_bug_jump here
+jmp_buf *
+get_test_bug_jump(const char *msg UNUSED)
+{
+ return NULL;
+}
+#endif
+
+
/*
* Dropping privileges
*/
}
jmp_buf *
-get_test_bug_jump(char *msg)
+get_test_bug_jump(const char *msg)
{
if (!bug_expected || strcmp(msg, expected_bug_message) != 0)
abort();