Fix some vector issues.
Destructors are called too early which can be problematic when doing a push_back with an element of the vector.
Change-Id: I3a93d8f6783aa9a579d1d8dab2f454520df39622
Reviewed-on: https://chromium-review.googlesource.com/c/codecs/libwebp2/+/4251881
Tested-by: WebM Builds <builds@webmproject.org>
Reviewed-by: Maryla Ustarroz-Calonge <maryla@google.com>
diff --git a/src/utils/vector.h b/src/utils/vector.h
index e1fb4d1..e51501e 100644
--- a/src/utils/vector.h
+++ b/src/utils/vector.h
@@ -62,6 +62,7 @@
template <typename T>
class VectorBase {
public:
+ typedef T value_type;
typedef T* iterator;
typedef const T* const_iterator;
@@ -76,30 +77,15 @@
// Reallocates just enough memory if needed so that 'new_cap' items can fit.
WP2_NO_DISCARD bool reserve(size_t new_cap) {
if (capacity_ < new_cap) {
- // Not using realloc() because only 'num_items_' need to be moved.
T* const new_items = (T*)WP2Malloc(new_cap, sizeof(T));
if (new_items == nullptr) return false;
- if (num_items_ > 0) {
- if (std::is_trivially_copyable<T>::value &&
- std::is_trivially_destructible<T>::value) {
- std::memcpy((void*)new_items, (const void*)items_,
- num_items_ * sizeof(T));
- } else {
- for (size_t i = 0; i < num_items_; ++i) {
- new (&new_items[i]) T(std::move(items_[i]));
- items_[i].~T();
- }
- }
- }
- WP2Free(items_);
- items_ = new_items;
- capacity_ = new_cap;
+ move_items(new_cap, new_items);
}
return true;
}
// Reallocates less memory so that only the existing items can fit.
- bool shrink_to_fit() {
+ WP2_NO_DISCARD bool shrink_to_fit() {
if (capacity_ == num_items_) return true;
if (num_items_ == 0) {
WP2Free(items_);
@@ -122,12 +108,16 @@
// Constructs a new item by copy ctor. May reallocate if 'resize_if_needed'.
WP2_NO_DISCARD bool push_back(const T& value, bool resize_if_needed = true) {
- if (num_items_ >= capacity_ &&
- (!resize_if_needed ||
- !reserve(Internal::NextCapacity(num_items_ + 1)))) {
- return false;
+ if (num_items_ < capacity_) {
+ new (&items_[num_items_]) T(value);
+ } else {
+ if (!resize_if_needed) return false;
+ const size_t new_cap = Internal::NextCapacity(num_items_ + 1);
+ T* const new_items = (T*)WP2Malloc(new_cap, sizeof(T));
+ if (new_items == nullptr) return false;
+ new (&new_items[num_items_]) T(value);
+ move_items(new_cap, new_items);
}
- new (&items_[num_items_]) T(value);
++num_items_;
return true;
}
@@ -179,10 +169,17 @@
}
// Appends 'size' elements by copy. Returns false on error.
- bool append(const T data[], size_t size) {
+ WP2_NO_DISCARD bool append(const T data[], size_t size) {
if (size == 0) return true;
- if (!reserve(Internal::NextCapacity(num_items_ + size))) return false;
- std::copy(data, data + size, &items_[num_items_]);
+ const size_t new_cap = Internal::NextCapacity(num_items_ + size);
+ if (new_cap <= capacity_) {
+ std::copy(data, data + size, &items_[num_items_]);
+ } else {
+ T* const new_items = (T*)WP2Malloc(new_cap, sizeof(T));
+ if (new_items == nullptr) return false;
+ std::copy(data, data + size, &new_items[num_items_]);
+ move_items(new_cap, new_items);
+ }
num_items_ += size;
return true;
}
@@ -223,7 +220,8 @@
swap(capacity_, b.capacity_);
swap(num_items_, b.num_items_);
}
- bool copy_from(const VectorBase& src) {
+ WP2_NO_DISCARD bool copy_from(const VectorBase& src) {
+ if (src.items_ == items_) return true;
clear();
if (!reserve(src.capacity())) return false;
for (const auto& elmt : src) {
@@ -236,6 +234,27 @@
T* items_ = nullptr;
size_t capacity_ = 0;
size_t num_items_ = 0;
+
+ private:
+ // Moves data from items_ into new_items, and have new_items be the new
+ // items_.
+ void move_items(size_t new_cap, T* new_items) {
+ if (num_items_ > 0) {
+ if (std::is_trivially_copyable<T>::value &&
+ std::is_trivially_destructible<T>::value) {
+ std::memcpy((void*)new_items, (const void*)items_,
+ num_items_ * sizeof(T));
+ } else {
+ for (size_t i = 0; i < num_items_; ++i) {
+ new (&new_items[i]) T(std::move(items_[i]));
+ items_[i].~T();
+ }
+ }
+ }
+ WP2Free(items_);
+ items_ = new_items;
+ capacity_ = new_cap;
+ }
};
} // namespace Internal
diff --git a/tests/test_vector.cc b/tests/test_vector.cc
index c2e2015..c40141b 100644
--- a/tests/test_vector.cc
+++ b/tests/test_vector.cc
@@ -26,6 +26,7 @@
class Foo {
public:
Foo(int x = kMagic) : x_(x) {} // NOLINT (explicitly no 'explicit')
+ ~Foo() { x_ = -1; }
operator int() const { return x_; } // NOLINT (explicitly no 'explicit')
private:
@@ -68,38 +69,71 @@
DoTestCtor(0.);
}
-template<typename T> void DoTestPushBack(const T& vector_type) {
- (void)vector_type;
+template <typename VectorType>
+void DoTestPushBack() {
+ // Avoid compilers complaining about comparison of integer expressions of
+ // different signedness.
+ const auto magic = static_cast<typename VectorType::value_type>(kMagic);
+ const auto magic2 = static_cast<typename VectorType::value_type>(kMagic2);
- T v;
+ VectorType v;
EXPECT_TRUE(v.reserve(8));
EXPECT_EQ(v.size(), 0u);
EXPECT_EQ(v.capacity(), 8u);
EXPECT_TRUE(v.push_back(kMagic));
EXPECT_EQ(v.size(), 1u);
- EXPECT_EQ((int)v[0], kMagic);
+ EXPECT_EQ(v[0], magic);
EXPECT_TRUE(v.push_back(kMagic2));
EXPECT_EQ(v.size(), 2u);
- EXPECT_EQ((int)v[0], kMagic);
- EXPECT_EQ((int)v[1], kMagic2);
+ EXPECT_EQ(v[0], magic);
+ EXPECT_EQ(v[1], magic2);
- EXPECT_TRUE(v.resize(1)); // down-size back to 1
+ EXPECT_TRUE(v.resize(1)); // down-size back to 1
EXPECT_EQ(v.size(), 1u);
- EXPECT_EQ((int)v[0], kMagic);
+ EXPECT_EQ(v[0], magic);
- v.resize_no_check(7); // up-size quietly
+ v.resize_no_check(7); // up-size quietly
EXPECT_EQ(v.size(), 7u);
- EXPECT_EQ((int)v[1], kMagic2);
+
+ v.clear();
+ EXPECT_TRUE(v.push_back(kMagic2));
+ for (int i = 0; i < 100; ++i) {
+ EXPECT_TRUE(v.push_back(v[0]));
+ EXPECT_EQ(v[i + 1], magic2);
+ }
+
+ v.clear();
+ EXPECT_TRUE(v.shrink_to_fit());
+ EXPECT_TRUE(v.push_back(kMagic));
+ EXPECT_TRUE(v.push_back(kMagic2));
+ for (int i = 0; i < 100; ++i) {
+ EXPECT_TRUE(v.append(&v[0], 2));
+ EXPECT_EQ(v[v.size() - 2], v[0]);
+ EXPECT_EQ(v[v.size() - 1], v[1]);
+ }
+
+ v.clear();
+ EXPECT_TRUE(v.shrink_to_fit());
+ for (int i = 0; i < 100; ++i) {
+ EXPECT_TRUE(v.push_back(kMagic));
+ EXPECT_TRUE(v.push_back(kMagic2));
+ }
+ const size_t size = v.size();
+ EXPECT_TRUE(v.copy_from(v));
+ for (size_t i = 0; i < size; i += 2) {
+ EXPECT_EQ(v[i], magic);
+ EXPECT_EQ(v[i + 1], magic2);
+ }
}
TEST(VectorTest, PushBack) {
- DoTestPushBack(Vector_u8());
- DoTestPushBack(Vector_s16());
- DoTestPushBack(Vector_u32());
- DoTestPushBack(VectorNoCtor<float>());
- DoTestPushBack(Vector<Foo>());
+ DoTestPushBack<Vector_u8>();
+ DoTestPushBack<Vector_s16>();
+ DoTestPushBack<Vector_u32>();
+ DoTestPushBack<VectorNoCtor<float>>();
+ DoTestPushBack<Vector<Foo>>();
}
// Copy constructor and assignment are deleted, but move constructor and