diff --git a/include/roaring/art/art.h b/include/roaring/art/art.h index 99199b717..fd4afc956 100644 --- a/include/roaring/art/art.h +++ b/include/roaring/art/art.h @@ -99,6 +99,21 @@ size_t art_size_in_bytes(const art_t *art); */ void art_printf(const art_t *art); +/** + * Callback for validating the value stored in a leaf. + * + * Should return true if the value is valid, false otherwise + * If false is returned, `*reason` should be set to a static string describing + * the reason for the failure. + */ +typedef bool (*art_validate_cb_t)(const art_val_t *val, const char **reason); + +/** + * Validate the ART tree, ensuring it is internally consistent. + */ +bool art_internal_validate(const art_t *art, const char **reason, + art_validate_cb_t validate_cb); + /** * ART-internal iterator bookkeeping. Users should treat this as an opaque type. */ diff --git a/include/roaring/roaring64.h b/include/roaring/roaring64.h index fc547aa84..cd73ba604 100644 --- a/include/roaring/roaring64.h +++ b/include/roaring/roaring64.h @@ -292,6 +292,20 @@ uint64_t roaring64_bitmap_maximum(const roaring64_bitmap_t *r); */ bool roaring64_bitmap_run_optimize(roaring64_bitmap_t *r); +/** + * Perform internal consistency checks. + * + * Returns true if the bitmap is consistent. It may be useful to call this + * after deserializing bitmaps from untrusted sources. If + * roaring64_bitmap_internal_validate returns true, then the bitmap is + * consistent and can be trusted not to cause crashes or memory corruption. + * + * If reason is non-null, it will be set to a string describing the first + * inconsistency found if any. + */ +bool roaring64_bitmap_internal_validate(const roaring64_bitmap_t *r, + const char **reason); + /** * Return true if the two bitmaps contain the same elements. */ diff --git a/src/art/art.c b/src/art/art.c index 9ec72c4b0..65695349c 100644 --- a/src/art/art.c +++ b/src/art/art.c @@ -46,6 +46,21 @@ typedef uint8_t art_typecode_t; // of the trie internals. typedef art_val_t art_leaf_t; +typedef struct art_internal_validate_s { + const char **reason; + art_validate_cb_t validate_cb; + + int depth; + art_key_chunk_t current_key[ART_KEY_BYTES]; +} art_internal_validate_t; + +// Set the reason message, and return false for convenience. +static inline bool art_validate_fail(const art_internal_validate_t *validate, + const char *msg) { + *validate->reason = msg; + return false; +} + // Inner node, with prefix. // // We use a fixed-length array as a pointer would be larger than the array. @@ -308,6 +323,43 @@ static inline art_indexed_child_t art_node4_lower_bound( return indexed_child; } +static bool art_internal_validate_at(const art_node_t *node, + art_internal_validate_t validator); + +static bool art_node4_internal_validate(const art_node4_t *node, + art_internal_validate_t validator) { + if (node->count == 0) { + return art_validate_fail(&validator, "Node4 has no children"); + } + if (node->count > 4) { + return art_validate_fail(&validator, "Node4 has too many children"); + } + if (node->count == 1) { + return art_validate_fail( + &validator, "Node4 and child node should have been combined"); + } + validator.depth++; + for (int i = 0; i < node->count; ++i) { + if (i > 0) { + if (node->keys[i - 1] >= node->keys[i]) { + return art_validate_fail( + &validator, "Node4 keys are not strictly increasing"); + } + } + for (int j = i + 1; j < node->count; ++j) { + if (node->children[i] == node->children[j]) { + return art_validate_fail(&validator, + "Node4 has duplicate children"); + } + } + validator.current_key[validator.depth - 1] = node->keys[i]; + if (!art_internal_validate_at(node->children[i], validator)) { + return false; + } + } + return true; +} + static art_node16_t *art_node16_create(const art_key_chunk_t prefix[], uint8_t prefix_size) { art_node16_t *node = (art_node16_t *)roaring_malloc(sizeof(art_node16_t)); @@ -461,6 +513,36 @@ static inline art_indexed_child_t art_node16_lower_bound( return indexed_child; } +static bool art_node16_internal_validate(const art_node16_t *node, + art_internal_validate_t validator) { + if (node->count <= 4) { + return art_validate_fail(&validator, "Node16 has too few children"); + } + if (node->count > 16) { + return art_validate_fail(&validator, "Node16 has too many children"); + } + validator.depth++; + for (int i = 0; i < node->count; ++i) { + if (i > 0) { + if (node->keys[i - 1] >= node->keys[i]) { + return art_validate_fail( + &validator, "Node16 keys are not strictly increasing"); + } + } + for (int j = i + 1; j < node->count; ++j) { + if (node->children[i] == node->children[j]) { + return art_validate_fail(&validator, + "Node16 has duplicate children"); + } + } + validator.current_key[validator.depth - 1] = node->keys[i]; + if (!art_internal_validate_at(node->children[i], validator)) { + return false; + } + } + return true; +} + static art_node48_t *art_node48_create(const art_key_chunk_t prefix[], uint8_t prefix_size) { art_node48_t *node = (art_node48_t *)roaring_malloc(sizeof(art_node48_t)); @@ -614,6 +696,65 @@ static inline art_indexed_child_t art_node48_lower_bound( return indexed_child; } +static bool art_node48_internal_validate(const art_node48_t *node, + art_internal_validate_t validator) { + if (node->count <= 16) { + return art_validate_fail(&validator, "Node48 has too few children"); + } + if (node->count > 48) { + return art_validate_fail(&validator, "Node48 has too many children"); + } + uint64_t used_children = 0; + for (int i = 0; i < 256; ++i) { + uint8_t child_idx = node->keys[i]; + if (child_idx != ART_NODE48_EMPTY_VAL) { + if (used_children & (UINT64_C(1) << child_idx)) { + return art_validate_fail( + &validator, "Node48 keys point to the same child index"); + } + + art_node_t *child = node->children[child_idx]; + if (child == NULL) { + return art_validate_fail(&validator, "Node48 has a NULL child"); + } + used_children |= UINT64_C(1) << child_idx; + } + } + uint64_t expected_used_children = + (node->available_children) ^ NODE48_AVAILABLE_CHILDREN_MASK; + if (used_children != expected_used_children) { + return art_validate_fail( + &validator, + "Node48 available_children does not match actual children"); + } + while (used_children != 0) { + uint8_t child_idx = roaring_trailing_zeroes(used_children); + used_children &= used_children - 1; + + uint64_t other_children = used_children; + while (other_children != 0) { + uint8_t other_child_idx = roaring_trailing_zeroes(other_children); + if (node->children[child_idx] == node->children[other_child_idx]) { + return art_validate_fail(&validator, + "Node48 has duplicate children"); + } + other_children &= other_children - 1; + } + } + + validator.depth++; + for (int i = 0; i < 256; ++i) { + if (node->keys[i] != ART_NODE48_EMPTY_VAL) { + validator.current_key[validator.depth - 1] = i; + if (!art_internal_validate_at(node->children[node->keys[i]], + validator)) { + return false; + } + } + } + return true; +} + static art_node256_t *art_node256_create(const art_key_chunk_t prefix[], uint8_t prefix_size) { art_node256_t *node = @@ -735,6 +876,40 @@ static inline art_indexed_child_t art_node256_lower_bound( return indexed_child; } +static bool art_node256_internal_validate(const art_node256_t *node, + art_internal_validate_t validator) { + if (node->count <= 48) { + return art_validate_fail(&validator, "Node256 has too few children"); + } + if (node->count > 256) { + return art_validate_fail(&validator, "Node256 has too many children"); + } + validator.depth++; + int actual_count = 0; + for (int i = 0; i < 256; ++i) { + if (node->children[i] != NULL) { + actual_count++; + + for (int j = i + 1; j < 256; ++j) { + if (node->children[i] == node->children[j]) { + return art_validate_fail(&validator, + "Node256 has duplicate children"); + } + } + + validator.current_key[validator.depth - 1] = i; + if (!art_internal_validate_at(node->children[i], validator)) { + return false; + } + } + } + if (actual_count != node->count) { + return art_validate_fail( + &validator, "Node256 count does not match actual children"); + } + return true; +} + // Finds the child with the given key chunk in the inner node, returns NULL if // no such child is found. static art_node_t *art_find_child(const art_inner_node_t *node, @@ -1617,6 +1792,89 @@ art_val_t *art_iterator_erase(art_t *art, art_iterator_t *iterator) { return value_erased; } +static bool art_internal_validate_at(const art_node_t *node, + art_internal_validate_t validator) { + if (node == NULL) { + return art_validate_fail(&validator, "node is null"); + } + if (art_is_leaf(node)) { + art_leaf_t *leaf = CAST_LEAF(node); + if (art_compare_prefix(leaf->key, 0, validator.current_key, 0, + validator.depth) != 0) { + return art_validate_fail( + &validator, + "leaf key does not match its position's prefix in the tree"); + } + if (validator.validate_cb != NULL && + !validator.validate_cb(leaf, validator.reason)) { + if (*validator.reason == NULL) { + *validator.reason = "leaf validation failed"; + } + return false; + } + } else { + art_inner_node_t *inner_node = (art_inner_node_t *)node; + + if (validator.depth + inner_node->prefix_size + 1 > ART_KEY_BYTES) { + return art_validate_fail(&validator, + "node has too much prefix at given depth"); + } + memcpy(validator.current_key + validator.depth, inner_node->prefix, + inner_node->prefix_size); + validator.depth += inner_node->prefix_size; + + switch (inner_node->typecode) { + case ART_NODE4_TYPE: + if (!art_node4_internal_validate((art_node4_t *)inner_node, + validator)) { + return false; + } + break; + case ART_NODE16_TYPE: + if (!art_node16_internal_validate((art_node16_t *)inner_node, + validator)) { + return false; + } + break; + case ART_NODE48_TYPE: + if (!art_node48_internal_validate((art_node48_t *)inner_node, + validator)) { + return false; + } + break; + case ART_NODE256_TYPE: + if (!art_node256_internal_validate((art_node256_t *)inner_node, + validator)) { + return false; + } + break; + default: + return art_validate_fail(&validator, "invalid node type"); + } + } + return true; +} + +bool art_internal_validate(const art_t *art, const char **reason, + art_validate_cb_t validate_cb) { + const char *reason_local; + if (reason == NULL) { + // Always allow assigning through *reason + reason = &reason_local; + } + *reason = NULL; + if (art->root == NULL) { + return true; + } + art_internal_validate_t validator = { + .reason = reason, + .validate_cb = validate_cb, + .depth = 0, + .current_key = {0}, + }; + return art_internal_validate_at(art->root, validator); +} + #ifdef __cplusplus } // extern "C" } // namespace roaring diff --git a/src/roaring64.c b/src/roaring64.c index c565ab5f1..590857c55 100644 --- a/src/roaring64.c +++ b/src/roaring64.c @@ -808,6 +808,18 @@ bool roaring64_bitmap_run_optimize(roaring64_bitmap_t *r) { return has_run_container; } +static bool roaring64_leaf_internal_validate(const art_val_t *val, + const char **reason) { + leaf_t *leaf = (leaf_t *)val; + return container_internal_validate(leaf->container, leaf->typecode, reason); +} + +bool roaring64_bitmap_internal_validate(const roaring64_bitmap_t *r, + const char **reason) { + return art_internal_validate(&r->art, reason, + roaring64_leaf_internal_validate); +} + bool roaring64_bitmap_equals(const roaring64_bitmap_t *r1, const roaring64_bitmap_t *r2) { art_iterator_t it1 = art_init_iterator(&r1->art, /*first=*/true); diff --git a/src/roaring_array.c b/src/roaring_array.c index 444a47c55..fabc8b5b6 100644 --- a/src/roaring_array.c +++ b/src/roaring_array.c @@ -553,7 +553,7 @@ size_t ra_portable_serialize(const roaring_array_t *ra, char *buf) { uint32_t startOffset = 0; bool hasrun = ra_has_run_container(ra); if (hasrun) { - uint32_t cookie = SERIAL_COOKIE | ((ra->size - 1) << 16); + uint32_t cookie = SERIAL_COOKIE | ((uint32_t)(ra->size - 1) << 16); memcpy(buf, &cookie, sizeof(cookie)); buf += sizeof(cookie); uint32_t s = (ra->size + 7) / 8; diff --git a/tests/art_unit.cpp b/tests/art_unit.cpp index 6d84a9023..ad0e74dcb 100644 --- a/tests/art_unit.cpp +++ b/tests/art_unit.cpp @@ -30,11 +30,18 @@ void assert_key_eq(const art_key_chunk_t* key1, const art_key_chunk_t* key2) { print_key(key2); printf("\n"); - assert_true(false); + fail(); } } } +void assert_art_valid(art_t* art) { + const char* reason = nullptr; + if (!art_internal_validate(art, &reason, nullptr)) { + fail_msg("ART is invalid: '%s'\n", reason); + } +} + class Key { public: Key(uint64_t key) { @@ -176,11 +183,14 @@ DEFINE_TEST(test_art_erase_all) { art_t art{NULL}; art_insert(&art, (uint8_t*)keys[0], &values[0]); art_insert(&art, (uint8_t*)keys[1], &values[1]); + assert_art_valid(&art); Value* erased_val1 = (Value*)art_erase(&art, (uint8_t*)keys[0]); Value* erased_val2 = (Value*)art_erase(&art, (uint8_t*)keys[1]); assert_true(*erased_val1 == values[0]); assert_true(*erased_val2 == values[1]); + + assert_art_valid(&art); art_free(&art); } @@ -191,10 +201,12 @@ DEFINE_TEST(test_art_is_empty) { std::vector values = {{1}, {2}, {3}, {4}, {5}}; art_t art{NULL}; + assert_art_valid(&art); assert_true(art_is_empty(&art)); const char* key = "000001"; Value val{1}; art_insert(&art, (art_key_chunk_t*)key, &val); + assert_art_valid(&art); assert_false(art_is_empty(&art)); art_free(&art); } @@ -214,6 +226,7 @@ DEFINE_TEST(test_art_iterator_next) { art_t art{NULL}; for (size_t i = 0; i < keys.size(); ++i) { art_insert(&art, (art_key_chunk_t*)keys[i].data(), &values[i]); + assert_art_valid(&art); } art_iterator_t iterator = art_init_iterator(&art, true); @@ -241,6 +254,7 @@ DEFINE_TEST(test_art_iterator_prev) { art_t art{NULL}; for (size_t i = 0; i < keys.size(); ++i) { art_insert(&art, (art_key_chunk_t*)keys[i].data(), &values[i]); + assert_art_valid(&art); } art_iterator_t iterator = art_init_iterator(&art, /*first=*/false); @@ -261,6 +275,7 @@ DEFINE_TEST(test_art_iterator_lower_bound) { art_t art{NULL}; for (size_t i = 0; i < keys.size(); ++i) { art_insert(&art, (art_key_chunk_t*)keys[i], &values[i]); + assert_art_valid(&art); } art_iterator_t iterator = art_init_iterator(&art, true); @@ -280,6 +295,7 @@ DEFINE_TEST(test_art_iterator_lower_bound) { art_t art{NULL}; for (size_t i = 0; i < keys.size(); ++i) { art_insert(&art, (art_key_chunk_t*)keys[i], &values[i]); + assert_art_valid(&art); } art_iterator_t iterator = art_init_iterator(&art, true); @@ -304,6 +320,7 @@ DEFINE_TEST(test_art_iterator_lower_bound) { art_t art{NULL}; for (size_t i = 0; i < keys.size(); ++i) { art_insert(&art, (art_key_chunk_t*)keys[i], &values[i]); + assert_art_valid(&art); } art_iterator_t iterator = art_init_iterator(&art, true); @@ -357,6 +374,7 @@ DEFINE_TEST(test_art_lower_bound) { art_t art{NULL}; for (size_t i = 0; i < keys.size(); ++i) { art_insert(&art, (art_key_chunk_t*)keys[i], &values[i]); + assert_art_valid(&art); } { @@ -394,6 +412,7 @@ DEFINE_TEST(test_art_upper_bound) { art_t art{NULL}; for (size_t i = 0; i < keys.size(); ++i) { art_insert(&art, (art_key_chunk_t*)keys[i], &values[i]); + assert_art_valid(&art); } { @@ -438,6 +457,7 @@ DEFINE_TEST(test_art_iterator_erase) { art_t art{NULL}; for (size_t i = 0; i < keys.size(); ++i) { art_insert(&art, (art_key_chunk_t*)keys[i].data(), &values[i]); + assert_art_valid(&art); } art_iterator_t iterator = art_init_iterator(&art, true); size_t i = 0; @@ -445,6 +465,7 @@ DEFINE_TEST(test_art_iterator_erase) { assert_key_eq(iterator.key, (art_key_chunk_t*)keys[i].data()); assert_true(iterator.value == &values[i]); assert_true(art_iterator_erase(&art, &iterator) == &values[i]); + assert_art_valid(&art); assert_false(art_find(&art, (art_key_chunk_t*)keys[i].data())); ++i; } while (iterator.value != NULL); @@ -463,6 +484,7 @@ DEFINE_TEST(test_art_iterator_insert) { for (size_t i = 1; i < keys.size(); ++i) { art_iterator_insert(&art, &iterator, (art_key_chunk_t*)keys[i], &values[i]); + assert_art_valid(&art); assert_key_eq(iterator.key, (art_key_chunk_t*)keys[i]); assert_true(iterator.value == &values[i]); } @@ -492,11 +514,13 @@ DEFINE_TEST(test_art_shrink_grow_node48) { auto key = Key(i); values[i].val = i; art_insert(&art, key.data(), &values[i]); + assert_art_valid(&art); } // Remove the first several containers for (int i = 0; i < 8; i++) { auto key = Key(i); Value* removed_val = (Value*)(art_erase(&art, key.data())); + assert_art_valid(&art); assert_int_equal(removed_val->val, i); } { diff --git a/tests/roaring64_serialization.cpp b/tests/roaring64_serialization.cpp index 42717e1f8..2364088ea 100644 --- a/tests/roaring64_serialization.cpp +++ b/tests/roaring64_serialization.cpp @@ -22,17 +22,25 @@ bool test_serialization(const std::string& filename) { std::ifstream in(TEST_DATA_DIR + filename, std::ios::binary); std::vector buf1(std::istreambuf_iterator(in), {}); + const char* reason = nullptr; // Deserialize. size_t deserialized_size = roaring64_bitmap_portable_deserialize_size(buf1.data(), buf1.size()); + if (deserialized_size != 0) { + assert_int_equal(deserialized_size, buf1.size()); + } roaring64_bitmap_t* r = roaring64_bitmap_portable_deserialize_safe( buf1.data(), deserialized_size); - if (r == NULL) { + if (r == nullptr) { return false; } + if (!roaring64_bitmap_internal_validate(r, &reason)) { + fail_msg("Validation failed: %s", reason); + } // Reserialize. size_t serialized_size = roaring64_bitmap_portable_size_in_bytes(r); + assert_int_equal(serialized_size, deserialized_size); std::vector buf2(serialized_size, 0); size_t serialized = roaring64_bitmap_portable_serialize(r, buf2.data()); assert_int_equal(serialized, serialized_size); diff --git a/tests/roaring64_unit.cpp b/tests/roaring64_unit.cpp index 98a004533..f1bb4e736 100644 --- a/tests/roaring64_unit.cpp +++ b/tests/roaring64_unit.cpp @@ -23,14 +23,23 @@ void assert_vector_equal(const std::vector& lhs, } } +void assert_r64_valid(roaring64_bitmap_t* b) { + const char* reason = nullptr; + if (!roaring64_bitmap_internal_validate(b, &reason)) { + fail_msg("Roaring64 bitmap is invalid: '%s'\n", reason); + } +} + DEFINE_TEST(test_copy) { roaring64_bitmap_t* r1 = roaring64_bitmap_create(); + assert_r64_valid(r1); roaring64_bitmap_add(r1, 0); roaring64_bitmap_add(r1, 10000); roaring64_bitmap_add(r1, 200000); roaring64_bitmap_t* r2 = roaring64_bitmap_copy(r1); + assert_r64_valid(r1); assert_true(roaring64_bitmap_contains(r2, 0)); assert_true(roaring64_bitmap_contains(r2, 10000)); assert_true(roaring64_bitmap_contains(r2, 200000)); @@ -38,6 +47,7 @@ DEFINE_TEST(test_copy) { roaring64_bitmap_remove(r1, 200000); roaring64_bitmap_add(r1, 300000); + assert_r64_valid(r1); assert_true(roaring64_bitmap_contains(r2, 200000)); assert_false(roaring64_bitmap_contains(r2, 300000)); @@ -49,6 +59,7 @@ DEFINE_TEST(test_from_range) { { // Step greater than 2 ^ 16. roaring64_bitmap_t* r = roaring64_bitmap_from_range(0, 1000000, 200000); + assert_r64_valid(r); assert_true(roaring64_bitmap_contains(r, 0)); assert_true(roaring64_bitmap_contains(r, 200000)); assert_true(roaring64_bitmap_contains(r, 400000)); @@ -60,6 +71,7 @@ DEFINE_TEST(test_from_range) { { // Step less than 2 ^ 16 and within one container. roaring64_bitmap_t* r = roaring64_bitmap_from_range(0, 100, 20); + assert_r64_valid(r); assert_true(roaring64_bitmap_contains(r, 0)); assert_true(roaring64_bitmap_contains(r, 20)); assert_true(roaring64_bitmap_contains(r, 40)); @@ -72,6 +84,7 @@ DEFINE_TEST(test_from_range) { // Step less than 2 ^ 16 and across two containers. roaring64_bitmap_t* r = roaring64_bitmap_from_range((1 << 16) - 1, (1 << 16) + 5, 2); + assert_r64_valid(r); assert_true(roaring64_bitmap_contains(r, (1 << 16) - 1)); assert_true(roaring64_bitmap_contains(r, (1 << 16) + 1)); assert_true(roaring64_bitmap_contains(r, (1 << 16) + 3)); @@ -82,6 +95,7 @@ DEFINE_TEST(test_from_range) { // Step less than 2 ^ 16 and across multiple containers. roaring64_bitmap_t* r = roaring64_bitmap_from_range((1 << 16) - 1, (1 << 17) + 2, 1); + assert_r64_valid(r); assert_true(roaring64_bitmap_contains(r, (1 << 16) - 1)); assert_true(roaring64_bitmap_contains(r, (1 << 16) + 0)); assert_true(roaring64_bitmap_contains(r, (1 << 16) + 1)); @@ -97,6 +111,7 @@ DEFINE_TEST(test_of_ptr) { std::array vals; std::iota(vals.begin(), vals.end(), 0); roaring64_bitmap_t* r = roaring64_bitmap_of_ptr(vals.size(), vals.data()); + assert_r64_valid(r); for (uint64_t i = 0; i < 1000; ++i) { assert_true(roaring64_bitmap_contains(r, vals[i])); } @@ -105,6 +120,7 @@ DEFINE_TEST(test_of_ptr) { DEFINE_TEST(test_of) { roaring64_bitmap_t* r = roaring64_bitmap_from(1, 20000, 500000); + assert_r64_valid(r); assert_true(roaring64_bitmap_contains(r, 1)); assert_true(roaring64_bitmap_contains(r, 20000)); assert_true(roaring64_bitmap_contains(r, 500000)); @@ -118,6 +134,7 @@ DEFINE_TEST(test_add) { roaring64_bitmap_add(r, 10000); roaring64_bitmap_add(r, 200000); + assert_r64_valid(r); assert_true(roaring64_bitmap_contains(r, 0)); assert_true(roaring64_bitmap_contains(r, 10000)); assert_true(roaring64_bitmap_contains(r, 200000)); @@ -137,6 +154,7 @@ DEFINE_TEST(test_add_checked) { assert_true(roaring64_bitmap_add_checked(r, 200000)); assert_false(roaring64_bitmap_add_checked(r, 200000)); + assert_r64_valid(r); assert_true(roaring64_bitmap_contains(r, 0)); assert_true(roaring64_bitmap_contains(r, 10000)); assert_true(roaring64_bitmap_contains(r, 200000)); @@ -150,6 +168,7 @@ DEFINE_TEST(test_add_bulk) { roaring64_bulk_context_t context{}; for (uint64_t i = 0; i < 10000; ++i) { roaring64_bitmap_add_bulk(r, &context, i * 10000); + assert_r64_valid(r); } for (uint64_t i = 0; i < 10000; ++i) { assert_true(roaring64_bitmap_contains(r, i * 10000)); @@ -165,6 +184,7 @@ DEFINE_TEST(test_add_many) { std::iota(vals.begin(), vals.end(), 0); roaring64_bitmap_add_many(r, vals.size(), vals.data()); + assert_r64_valid(r); for (uint64_t i = 0; i < 1000; ++i) { assert_true(roaring64_bitmap_contains(r, vals[i])); } @@ -179,6 +199,7 @@ DEFINE_TEST(test_add_many) { roaring64_bitmap_add(r, value); assert_true(roaring64_bitmap_contains(r, value)); roaring64_bitmap_add_many(r, 1, &value); + assert_r64_valid(r); assert_true(roaring64_bitmap_contains(r, value)); assert_int_equal(roaring64_bitmap_get_cardinality(r), 1); roaring64_bitmap_free(r); @@ -190,6 +211,7 @@ DEFINE_TEST(test_add_range_closed) { // Entire range within one container. roaring64_bitmap_t* r = roaring64_bitmap_create(); roaring64_bitmap_add_range_closed(r, 10, 20); + assert_r64_valid(r); roaring64_bulk_context_t context{}; assert_false(roaring64_bitmap_contains_bulk(r, &context, 9)); for (uint64_t i = 10; i <= 20; ++i) { @@ -202,6 +224,7 @@ DEFINE_TEST(test_add_range_closed) { // Range spans two containers. roaring64_bitmap_t* r = roaring64_bitmap_create(); roaring64_bitmap_add_range_closed(r, (1 << 16) - 10, (1 << 16) + 10); + assert_r64_valid(r); roaring64_bulk_context_t context{}; assert_false( roaring64_bitmap_contains_bulk(r, &context, (1 << 16) - 11)); @@ -216,6 +239,7 @@ DEFINE_TEST(test_add_range_closed) { // Range spans more than two containers. roaring64_bitmap_t* r = roaring64_bitmap_create(); roaring64_bitmap_add_range_closed(r, 100, 300000); + assert_r64_valid(r); assert_int_equal(roaring64_bitmap_get_cardinality(r), 300000 - 100 + 1); roaring64_bulk_context_t context{}; assert_false(roaring64_bitmap_contains_bulk(r, &context, 99)); @@ -229,6 +253,7 @@ DEFINE_TEST(test_add_range_closed) { // Add range to existing container roaring64_bitmap_t* r = roaring64_bitmap_create(); roaring64_bitmap_add(r, 100); + assert_r64_valid(r); roaring64_bitmap_add_range_closed(r, 0, 0); assert_int_equal(roaring64_bitmap_get_cardinality(r), 2); assert_true(roaring64_bitmap_contains(r, 0)); @@ -241,6 +266,7 @@ DEFINE_TEST(test_add_range_closed) { uint64_t end = 0x101ffff; uint64_t start = 0; roaring64_bitmap_add_range_closed(r, start, end); + assert_r64_valid(r); assert_int_equal(roaring64_bitmap_get_cardinality(r), end - start + 1); roaring64_bitmap_free(r); } @@ -399,6 +425,7 @@ DEFINE_TEST(test_remove) { } for (uint64_t i = 0; i < 100; ++i) { roaring64_bitmap_remove(r, i * 10000); + assert_r64_valid(r); } for (uint64_t i = 0; i < 100; ++i) { assert_false(roaring64_bitmap_contains(r, i * 10000)); @@ -414,6 +441,7 @@ DEFINE_TEST(test_remove_checked) { for (uint64_t i = 0; i < 100; ++i) { assert_true(roaring64_bitmap_remove_checked(r, i * 10000)); assert_false(roaring64_bitmap_remove_checked(r, i * 10000)); + assert_r64_valid(r); } for (uint64_t i = 0; i < 100; ++i) { assert_false(roaring64_bitmap_contains(r, i * 10000)); @@ -430,6 +458,7 @@ DEFINE_TEST(test_remove_bulk) { context = {}; for (uint64_t i = 1; i < 9999; ++i) { roaring64_bitmap_remove_bulk(r, &context, i * 1000); + assert_r64_valid(r); } context = {}; assert_true(roaring64_bitmap_contains_bulk(r, &context, 0)); @@ -447,6 +476,7 @@ DEFINE_TEST(test_remove_many) { roaring64_bitmap_add_many(r, vals.size(), vals.data()); roaring64_bitmap_remove_many(r, vals.size(), vals.data()); + assert_r64_valid(r); for (uint64_t i = 0; i < 1000; ++i) { assert_false(roaring64_bitmap_contains(r, vals[i])); } @@ -459,6 +489,7 @@ DEFINE_TEST(test_remove_range_closed) { roaring64_bitmap_t* r = roaring64_bitmap_create(); roaring64_bitmap_add_range_closed(r, 10, 20); roaring64_bitmap_remove_range_closed(r, 11, 21); + assert_r64_valid(r); roaring64_bulk_context_t context{}; assert_true(roaring64_bitmap_contains_bulk(r, &context, 10)); for (uint64_t i = 11; i <= 21; ++i) { @@ -471,6 +502,7 @@ DEFINE_TEST(test_remove_range_closed) { roaring64_bitmap_t* r = roaring64_bitmap_create(); roaring64_bitmap_add_range_closed(r, (1 << 16) - 10, (1 << 16) + 10); roaring64_bitmap_remove_range_closed(r, (1 << 16) - 9, (1 << 16) + 9); + assert_r64_valid(r); roaring64_bulk_context_t context{}; assert_true( roaring64_bitmap_contains_bulk(r, &context, (1 << 16) - 10)); @@ -486,6 +518,7 @@ DEFINE_TEST(test_remove_range_closed) { roaring64_bitmap_t* r = roaring64_bitmap_create(); roaring64_bitmap_add_range_closed(r, 100, 300000); roaring64_bitmap_remove_range_closed(r, 101, 299999); + assert_r64_valid(r); roaring64_bulk_context_t context{}; assert_true(roaring64_bitmap_contains_bulk(r, &context, 100)); for (uint64_t i = 101; i <= 299999; ++i) { @@ -506,6 +539,7 @@ DEFINE_TEST(test_remove_range_closed) { roaring64_bitmap_add(r, i); } roaring64_bitmap_remove_range_closed(r, 0, 0x30000); + assert_r64_valid(r); assert_true(roaring64_bitmap_is_empty(r)); roaring64_bitmap_free(r); } @@ -544,8 +578,10 @@ DEFINE_TEST(test_range_cardinality) { DEFINE_TEST(test_is_empty) { roaring64_bitmap_t* r = roaring64_bitmap_create(); + assert_r64_valid(r); assert_true(roaring64_bitmap_is_empty(r)); roaring64_bitmap_add(r, 1); + assert_r64_valid(r); assert_false(roaring64_bitmap_is_empty(r)); roaring64_bitmap_free(r); } @@ -584,11 +620,13 @@ DEFINE_TEST(test_run_optimize) { roaring64_bitmap_add(r, 20000); assert_false(roaring64_bitmap_run_optimize(r)); + assert_r64_valid(r); for (uint64_t i = 0; i < 30000; ++i) { roaring64_bitmap_add(r, i); } assert_true(roaring64_bitmap_run_optimize(r)); + assert_r64_valid(r); roaring64_bitmap_free(r); } @@ -696,6 +734,7 @@ DEFINE_TEST(test_and) { roaring64_bitmap_t* r3 = roaring64_bitmap_and(r1, r2); + assert_r64_valid(r3); assert_false(roaring64_bitmap_contains(r3, 100000)); assert_true(roaring64_bitmap_contains(r3, 100001)); assert_true(roaring64_bitmap_contains(r3, 200000)); @@ -743,6 +782,7 @@ DEFINE_TEST(test_and_inplace) { roaring64_bitmap_and_inplace(r1, r2); + assert_r64_valid(r1); assert_false(roaring64_bitmap_contains(r1, 50000)); assert_false(roaring64_bitmap_contains(r1, 100000)); assert_true(roaring64_bitmap_contains(r1, 100001)); @@ -759,6 +799,7 @@ DEFINE_TEST(test_and_inplace) { roaring64_bitmap_t* r2 = roaring64_bitmap_from_range(100, 200, 1); roaring64_bitmap_and_inplace(r1, r2); + assert_r64_valid(r1); assert_true(roaring64_bitmap_is_empty(r1)); roaring64_bitmap_free(r1); @@ -863,6 +904,7 @@ DEFINE_TEST(test_or) { roaring64_bitmap_t* r3 = roaring64_bitmap_or(r1, r2); + assert_r64_valid(r3); assert_true(roaring64_bitmap_contains(r3, 100000)); assert_true(roaring64_bitmap_contains(r3, 100001)); assert_true(roaring64_bitmap_contains(r3, 200000)); @@ -908,6 +950,7 @@ DEFINE_TEST(test_or_inplace) { roaring64_bitmap_or_inplace(r1, r2); + assert_r64_valid(r1); assert_true(roaring64_bitmap_contains(r1, 100000)); assert_true(roaring64_bitmap_contains(r1, 100001)); assert_true(roaring64_bitmap_contains(r1, 200000)); @@ -933,6 +976,7 @@ DEFINE_TEST(test_xor) { roaring64_bitmap_t* r3 = roaring64_bitmap_xor(r1, r2); + assert_r64_valid(r3); assert_true(roaring64_bitmap_contains(r3, 100000)); assert_false(roaring64_bitmap_contains(r3, 100001)); assert_false(roaring64_bitmap_contains(r3, 200000)); @@ -978,6 +1022,7 @@ DEFINE_TEST(test_xor_inplace) { roaring64_bitmap_xor_inplace(r1, r2); + assert_r64_valid(r1); assert_true(roaring64_bitmap_contains(r1, 100000)); assert_false(roaring64_bitmap_contains(r1, 100001)); assert_false(roaring64_bitmap_contains(r1, 200000)); @@ -1003,6 +1048,7 @@ DEFINE_TEST(test_andnot) { roaring64_bitmap_t* r3 = roaring64_bitmap_andnot(r1, r2); + assert_r64_valid(r3); assert_true(roaring64_bitmap_contains(r3, 100000)); assert_false(roaring64_bitmap_contains(r3, 100001)); assert_false(roaring64_bitmap_contains(r3, 200000)); @@ -1049,6 +1095,7 @@ DEFINE_TEST(test_andnot_inplace) { roaring64_bitmap_andnot_inplace(r1, r2); + assert_r64_valid(r1); assert_true(roaring64_bitmap_contains(r1, 100000)); assert_false(roaring64_bitmap_contains(r1, 100001)); assert_false(roaring64_bitmap_contains(r1, 200000)); @@ -1064,6 +1111,7 @@ DEFINE_TEST(test_andnot_inplace) { roaring64_bitmap_t* r2 = roaring64_bitmap_from_range(0, 100, 1); roaring64_bitmap_andnot_inplace(r1, r2); + assert_r64_valid(r1); assert_true(roaring64_bitmap_is_empty(r1)); roaring64_bitmap_free(r1); @@ -1076,6 +1124,7 @@ DEFINE_TEST(test_flip) { // Flipping an empty bitmap should result in a non-empty range. roaring64_bitmap_t* r1 = roaring64_bitmap_create(); roaring64_bitmap_t* r2 = roaring64_bitmap_flip(r1, 10, 100000); + assert_r64_valid(r2); assert_true(roaring64_bitmap_contains_range(r2, 10, 100000)); roaring64_bitmap_free(r1); @@ -1085,6 +1134,7 @@ DEFINE_TEST(test_flip) { // Only the specified range should be flipped. roaring64_bitmap_t* r1 = roaring64_bitmap_from(1, 3, 6); roaring64_bitmap_t* r2 = roaring64_bitmap_flip(r1, 2, 5); + assert_r64_valid(r2); roaring64_bitmap_t* r3 = roaring64_bitmap_from(1, 2, 4, 6); assert_true(roaring64_bitmap_equals(r2, r3)); @@ -1096,6 +1146,7 @@ DEFINE_TEST(test_flip) { // An empty range does nothing. roaring64_bitmap_t* r1 = roaring64_bitmap_from(1, 3, 6); roaring64_bitmap_t* r2 = roaring64_bitmap_flip(r1, 3, 3); + assert_r64_valid(r2); assert_true(roaring64_bitmap_equals(r2, r1)); roaring64_bitmap_free(r1); @@ -1110,6 +1161,8 @@ DEFINE_TEST(test_flip) { roaring64_bitmap_t* r3 = roaring64_bitmap_from_range((2 << 16) + 1, (4 << 16) + 3, 1); roaring64_bitmap_remove(r3, (3 << 16) + 1); + assert_r64_valid(r2); + assert_r64_valid(r3); assert_true(roaring64_bitmap_equals(r2, r3)); roaring64_bitmap_free(r1); @@ -1123,6 +1176,7 @@ DEFINE_TEST(test_flip_inplace) { // Flipping an empty bitmap should result in a non-empty range. roaring64_bitmap_t* r1 = roaring64_bitmap_create(); roaring64_bitmap_flip_inplace(r1, 10, 100000); + assert_r64_valid(r1); assert_true(roaring64_bitmap_contains_range(r1, 10, 100000)); roaring64_bitmap_free(r1); @@ -1132,6 +1186,7 @@ DEFINE_TEST(test_flip_inplace) { roaring64_bitmap_t* r1 = roaring64_bitmap_from(1, 3, 6); roaring64_bitmap_flip_inplace(r1, 2, 5); roaring64_bitmap_t* r2 = roaring64_bitmap_from(1, 2, 4, 6); + assert_r64_valid(r1); assert_true(roaring64_bitmap_equals(r1, r2)); roaring64_bitmap_free(r1); @@ -1142,6 +1197,7 @@ DEFINE_TEST(test_flip_inplace) { roaring64_bitmap_t* r1 = roaring64_bitmap_from(1, 3, 6); roaring64_bitmap_flip_inplace(r1, 3, 3); roaring64_bitmap_t* r2 = roaring64_bitmap_from(1, 3, 6); + assert_r64_valid(r1); assert_true(roaring64_bitmap_equals(r1, r2)); roaring64_bitmap_free(r1); @@ -1155,6 +1211,7 @@ DEFINE_TEST(test_flip_inplace) { roaring64_bitmap_t* r2 = roaring64_bitmap_from_range((2 << 16) + 1, (4 << 16) + 3, 1); roaring64_bitmap_remove(r2, (3 << 16) + 1); + assert_r64_valid(r1); assert_true(roaring64_bitmap_equals(r1, r2)); roaring64_bitmap_free(r1); @@ -1172,6 +1229,7 @@ void check_portable_serialization(const roaring64_bitmap_t* r1) { assert_int_equal(deserialized_size, serialized_size); roaring64_bitmap_t* r2 = roaring64_bitmap_portable_deserialize_safe(buf.data(), serialized_size); + assert_r64_valid(r2); assert_true(roaring64_bitmap_equals(r2, r1)); roaring64_bitmap_free(r2); } @@ -1190,6 +1248,9 @@ DEFINE_TEST(test_portable_serialize) { roaring64_bitmap_add(r, UINT64_MAX); check_portable_serialization(r); + roaring64_bitmap_add_range(r, 1ULL << 16, 1ULL << 32); + check_portable_serialization(r); + roaring64_bitmap_free(r); }