Fix bug in TreeIndex erase().
erase() is supposed to avoid accessing the row that is being erased. This is important in particular when rolling back an insertion due to a later index reporting an insertion failure.
diff --git a/c++/src/kj/table-test.c++ b/c++/src/kj/table-test.c++
index 77f6d9c..acde654 100644
--- a/c++/src/kj/table-test.c++
+++ b/c++/src/kj/table-test.c++
@@ -1317,6 +1317,94 @@
KJ_EXPECT(iter == range.end());
}
+// =======================================================================================
+// Test bug where insertion failure on a later index in the table would not be rolled back
+// correctly if a previous index was TreeIndex.
+
+class StringLengthCompare {
+ // Considers two strings equal if they have the same length.
+public:
+ inline size_t keyForRow(StringPtr entry) const {
+ return entry.size();
+ }
+
+ inline bool matches(StringPtr e, size_t key) const {
+ return e.size() == key;
+ }
+
+ inline bool isBefore(StringPtr e, size_t key) const {
+ return e.size() < key;
+ }
+
+ uint hashCode(size_t size) const {
+ return size;
+ }
+};
+
+KJ_TEST("HashIndex rollback on insertion failure") {
+ // Test that when an insertion produces a duplicate on a later index, changes to previous indexes
+ // are properly rolled back.
+
+ Table<StringPtr, HashIndex<StringHasher>, HashIndex<StringLengthCompare>> table;
+ table.insert("a"_kj);
+ table.insert("ab"_kj);
+ table.insert("abc"_kj);
+
+ {
+ // We use upsert() so that we don't throw an exception from the duplicate, but this exercises
+ // the same logic as a duplicate insert() other than throwing.
+ kj::StringPtr& found = table.upsert("xyz"_kj, [&](StringPtr& existing, StringPtr&& param) {
+ KJ_EXPECT(existing == "abc");
+ KJ_EXPECT(param == "xyz");
+ });
+ KJ_EXPECT(found == "abc");
+
+ table.erase(found);
+ }
+
+ table.insert("xyz"_kj);
+
+ {
+ kj::StringPtr& found = table.upsert("tuv"_kj, [&](StringPtr& existing, StringPtr&& param) {
+ KJ_EXPECT(existing == "xyz");
+ KJ_EXPECT(param == "tuv");
+ });
+ KJ_EXPECT(found == "xyz");
+ }
+}
+
+KJ_TEST("TreeIndex rollback on insertion failure") {
+ // Test that when an insertion produces a duplicate on a later index, changes to previous indexes
+ // are properly rolled back.
+
+ Table<StringPtr, TreeIndex<StringCompare>, TreeIndex<StringLengthCompare>> table;
+ table.insert("a"_kj);
+ table.insert("ab"_kj);
+ table.insert("abc"_kj);
+
+ {
+ // We use upsert() so that we don't throw an exception from the duplicate, but this exercises
+ // the same logic as a duplicate insert() other than throwing.
+ kj::StringPtr& found = table.upsert("xyz"_kj, [&](StringPtr& existing, StringPtr&& param) {
+ KJ_EXPECT(existing == "abc");
+ KJ_EXPECT(param == "xyz");
+ });
+ KJ_EXPECT(found == "abc");
+
+ table.erase(found);
+ }
+
+ table.insert("xyz"_kj);
+
+ {
+ kj::StringPtr& found = table.upsert("tuv"_kj, [&](StringPtr& existing, StringPtr&& param) {
+ KJ_EXPECT(existing == "xyz");
+ KJ_EXPECT(param == "tuv");
+ });
+ KJ_EXPECT(found == "xyz");
+ }
+}
+
} // namespace
} // namespace _
} // namespace kj
diff --git a/c++/src/kj/table.h b/c++/src/kj/table.h
index 7af8409..630b79c 100644
--- a/c++/src/kj/table.h
+++ b/c++/src/kj/table.h
@@ -1484,7 +1484,7 @@
template <typename Row, typename... Params>
void erase(kj::ArrayPtr<Row> table, size_t pos, Params&&... params) {
- impl.erase(pos, searchKey(table, params...));
+ impl.erase(pos, searchKeyForErase(table, pos, params...));
}
template <typename Row, typename... Params>
@@ -1537,6 +1537,16 @@
auto predicate = [&](uint i) { return cb.isBefore(table[i], params...); };
return SearchKeyImpl<decltype(predicate)>(kj::mv(predicate));
}
+
+ template <typename Row, typename... Params>
+ inline auto searchKeyForErase(kj::ArrayPtr<Row>& table, uint pos, Params&... params) const {
+ // When erasing, the table entry for the erased row may already be invalid, so we must avoid
+ // accessing it.
+ auto predicate = [&,pos](uint i) {
+ return i != pos && cb.isBefore(table[i], params...);
+ };
+ return SearchKeyImpl<decltype(predicate)>(kj::mv(predicate));
+ }
};
// -----------------------------------------------------------------------------