Add bssl::Vector::EraseIf While I'm here, implement bssl::InplaceVector::EraseIf much more straightforwardly. The STL already provides the hard part. Change-Id: I6dfce1cfeba26e7b6bc749b4e64eceab4ba8564f Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/89007 Commit-Queue: David Benjamin <davidben@google.com> Reviewed-by: Lily Chen <chlily@google.com> Commit-Queue: Lily Chen <chlily@google.com> Auto-Submit: David Benjamin <davidben@google.com>
diff --git a/crypto/mem_internal.h b/crypto/mem_internal.h index 088e1b4..14254ec 100644 --- a/crypto/mem_internal.h +++ b/crypto/mem_internal.h
@@ -410,6 +410,14 @@ return true; } + // EraseIf removes all elements that satisfy the predicate |pred|. + template <typename Pred> + void EraseIf(Pred pred) { + auto it = std::remove_if(begin(), end(), pred); + std::destroy(it, end()); + size_ = it - begin(); + } + private: // If there is no room for one more element, creates a new backing array with // double the size of the old one and copies elements over. @@ -622,25 +630,11 @@ return *ret; } + // EraseIf removes all elements that satisfy the predicate |pred|. template <typename Pred> void EraseIf(Pred pred) { - // See if anything needs to be erased at all. This avoids a self-move. - auto iter = std::find_if(begin(), end(), pred); - if (iter == end()) { - return; - } - - // Elements before the first to be erased may be left as-is. - size_t new_size = iter - begin(); - // Swap all subsequent elements in if they are to be kept. - for (size_t i = new_size + 1; i < size(); i++) { - if (!pred((*this)[i])) { - (*this)[new_size] = std::move((*this)[i]); - new_size++; - } - } - - Shrink(new_size); + auto it = std::remove_if(begin(), end(), pred); + Shrink(it - begin()); } private:
diff --git a/crypto/mem_test.cc b/crypto/mem_test.cc index 714df18..848df01 100644 --- a/crypto/mem_test.cc +++ b/crypto/mem_test.cc
@@ -170,6 +170,81 @@ EXPECT_EQ(3u, vec[3].array.size()); } +TEST(VectorTest, EraseIf) { + // Test that EraseIf never causes a self-move, and also correctly works with + // a move-only type that cannot be default-constructed. + class NoSelfMove { + public: + explicit NoSelfMove(int v) : v_(std::make_unique<int>(v)) {} + NoSelfMove(NoSelfMove &&other) { *this = std::move(other); } + NoSelfMove &operator=(NoSelfMove &&other) { + BSSL_CHECK(this != &other); + v_ = std::move(other.v_); + return *this; + } + + int value() const { return *v_; } + + private: + std::unique_ptr<int> v_; + }; + + Vector<NoSelfMove> vec; + auto reset = [&] { + vec.clear(); + for (int i = 0; i < 8; i++) { + ASSERT_TRUE(vec.Push(NoSelfMove(i))); + } + }; + auto expect = [&](const std::vector<int> &expected) { + ASSERT_EQ(vec.size(), expected.size()); + for (size_t i = 0; i < vec.size(); i++) { + SCOPED_TRACE(i); + EXPECT_EQ(vec[i].value(), expected[i]); + } + }; + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &) { return false; }); + expect({0, 1, 2, 3, 4, 5, 6, 7}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &) { return true; }); + expect({}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &v) { return v.value() < 4; }); + expect({4, 5, 6, 7}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &v) { return v.value() >= 4; }); + expect({0, 1, 2, 3}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &v) { return v.value() % 2 == 0; }); + expect({1, 3, 5, 7}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &v) { return v.value() % 2 == 1; }); + expect({0, 2, 4, 6}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &v) { return 2 <= v.value() && v.value() <= 5; }); + expect({0, 1, 6, 7}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &v) { return v.value() == 0; }); + expect({1, 2, 3, 4, 5, 6, 7}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &v) { return v.value() == 4; }); + expect({0, 1, 2, 3, 5, 6, 7}); + + ASSERT_NO_FATAL_FAILURE(reset()); + vec.EraseIf([](const auto &v) { return v.value() == 7; }); + expect({0, 1, 2, 3, 4, 5, 6}); +} + TEST(VectorDeathTest, BoundsChecks) { Vector<int> vec; EXPECT_DEATH_IF_SUPPORTED(vec.front(), "");