Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ cmake-build-debug
.idea
*.log
_codeql_build_dir/
_codeql_detected_source_root
2 changes: 1 addition & 1 deletion include/art/node_16.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ template <class T> inner_node<T> *node_16<T>::grow() {
new_node->n_children_ = this->n_children_;
std::copy(this->children_, this->children_ + this->n_children_, new_node->children_);
for (int i = 0; i < n_children_; ++i) {
new_node->indexes_[(uint8_t) this->keys_[i]] = i;
new_node->indexes_[128 + (uint8_t) this->keys_[i]] = i;
}
delete this;
return new_node;
Expand Down
81 changes: 81 additions & 0 deletions test/node_16.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,85 @@ TEST_SUITE("node 16") {
REQUIRE_THROWS_AS(n.prev_partial_key(0), std::out_of_range);
}
}

TEST_CASE("grow to node_48 preserves offset (PR #20)") {
// This test reproduces the bug reported in PR #20:
// When node_16 grows to node_48, the indexes_ array must use 128 offset

leaf_node<void*>* dummy_children[17];

// Create dummy children
for (int i = 0; i < 17; ++i) {
dummy_children[i] = new leaf_node<void*>(nullptr);
}

// Create a node_16 and fill it with 16 children
node_16<void*>* n16 = new node_16<void*>();

// Use ASCII printable characters (which have values > 0)
// This reproduces the scenario described in PR #20 with 'a', 'b', etc.
char test_keys[17];
for (int i = 0; i < 17; ++i) {
test_keys[i] = 'a' + i; // a, b, c, ..., q
}

// Fill node_16 to capacity
for (int i = 0; i < 16; ++i) {
n16->set_child(test_keys[i], dummy_children[i]);
}

REQUIRE(n16->is_full());

// Grow to node_48 by adding one more child
auto* n48 = static_cast<node_48<void*>*>(n16->grow());
REQUIRE(n48 != nullptr);

// CRITICAL TEST: After growing, all original children must still be findable
// This fails without the fix because node_16::grow() doesn't add the 128 offset
for (int i = 0; i < 16; ++i) {
auto** child_ptr = n48->find_child(test_keys[i]);
REQUIRE(child_ptr != nullptr);
REQUIRE(*child_ptr == dummy_children[i]);
}

// Test that we can still add the 17th child
n48->set_child(test_keys[16], dummy_children[16]);

// Verify all 17 children are now accessible
for (int i = 0; i < 17; ++i) {
auto** child_ptr = n48->find_child(test_keys[i]);
REQUIRE(child_ptr != nullptr);
REQUIRE(*child_ptr == dummy_children[i]);
}

// Test iteration (next_partial_key and prev_partial_key)
// This also fails without the fix
SUBCASE("next_partial_key after grow") {
// Starting from first key 'a', we should find all keys in order
char current = n48->next_partial_key('a');
REQUIRE_EQ('a', current);

for (int i = 1; i < 17; ++i) {
current = n48->next_partial_key(current + 1);
REQUIRE_EQ(test_keys[i], current);
}
}

SUBCASE("prev_partial_key after grow") {
// Starting from last key 'q', we should find all keys in reverse order
char current = n48->prev_partial_key('q');
REQUIRE_EQ('q', current);

for (int i = 15; i >= 0; --i) {
current = n48->prev_partial_key(current - 1);
REQUIRE_EQ(test_keys[i], current);
}
}

// Clean up
delete n48;
for (int i = 0; i < 17; ++i) {
delete dummy_children[i];
}
}
}