}
void assertSafeToEmplace() {}
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ template <class U>
+ static const T *reserveForParamAndGetAddressImpl(U *This, const T &Elt,
+ size_t N) {
+ size_t NewSize = This->size() + N;
+ if (LLVM_LIKELY(NewSize <= This->capacity()))
+ return &Elt;
+
+ bool ReferencesStorage = false;
+ int64_t Index = -1;
+ if (!U::TakesParamByValue) {
+ if (LLVM_UNLIKELY(This->isReferenceToStorage(&Elt))) {
+ ReferencesStorage = true;
+ Index = &Elt - This->begin();
+ }
+ }
+ This->grow(NewSize);
+ return ReferencesStorage ? This->begin() + Index : &Elt;
+ }
+
public:
using size_type = size_t;
using difference_type = ptrdiff_t;
(is_trivially_move_constructible<T>::value) &&
std::is_trivially_destructible<T>::value>
class SmallVectorTemplateBase : public SmallVectorTemplateCommon<T> {
+ friend class SmallVectorTemplateCommon<T>;
+
protected:
+ static constexpr bool TakesParamByValue = false;
+ using ValueParamT = const T &;
+
SmallVectorTemplateBase(size_t Size) : SmallVectorTemplateCommon<T>(Size) {}
static void destroy_range(T *S, T *E) {
/// element, or MinSize more elements if specified.
void grow(size_t MinSize = 0);
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ const T *reserveForParamAndGetAddress(const T &Elt, size_t N = 1) {
+ return this->reserveForParamAndGetAddressImpl(this, Elt, N);
+ }
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ T *reserveForParamAndGetAddress(T &Elt, size_t N = 1) {
+ return const_cast<T *>(
+ this->reserveForParamAndGetAddressImpl(this, Elt, N));
+ }
+
+ static T &&forward_value_param(T &&V) { return std::move(V); }
+ static const T &forward_value_param(const T &V) { return V; }
+
public:
void push_back(const T &Elt) {
- this->assertSafeToAdd(&Elt);
- if (LLVM_UNLIKELY(this->size() >= this->capacity()))
- this->grow();
- ::new ((void*) this->end()) T(Elt);
+ const T *EltPtr = reserveForParamAndGetAddress(Elt);
+ ::new ((void *)this->end()) T(*EltPtr);
this->set_size(this->size() + 1);
}
void push_back(T &&Elt) {
- this->assertSafeToAdd(&Elt);
- if (LLVM_UNLIKELY(this->size() >= this->capacity()))
- this->grow();
- ::new ((void*) this->end()) T(::std::move(Elt));
+ T *EltPtr = reserveForParamAndGetAddress(Elt);
+ ::new ((void *)this->end()) T(::std::move(*EltPtr));
this->set_size(this->size() + 1);
}
/// skipping destruction.
template <typename T>
class SmallVectorTemplateBase<T, true> : public SmallVectorTemplateCommon<T> {
+ friend class SmallVectorTemplateCommon<T>;
+
protected:
+ /// True if it's cheap enough to take parameters by value. Doing so avoids
+ /// overhead related to mitigations for reference invalidation.
+ static constexpr bool TakesParamByValue = sizeof(T) <= 2 * sizeof(void *);
+
+ /// Either const T& or T, depending on whether it's cheap enough to take
+ /// parameters by value.
+ using ValueParamT =
+ typename std::conditional<TakesParamByValue, T, const T &>::type;
+
SmallVectorTemplateBase(size_t Size) : SmallVectorTemplateCommon<T>(Size) {}
// No need to do a destroy loop for POD's.
/// least one more element or MinSize if specified.
void grow(size_t MinSize = 0) { this->grow_pod(MinSize, sizeof(T)); }
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ const T *reserveForParamAndGetAddress(const T &Elt, size_t N = 1) {
+ return this->reserveForParamAndGetAddressImpl(this, Elt, N);
+ }
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ T *reserveForParamAndGetAddress(T &Elt, size_t N = 1) {
+ return const_cast<T *>(
+ this->reserveForParamAndGetAddressImpl(this, Elt, N));
+ }
+
+ /// Copy \p V or return a reference, depending on \a ValueParamT.
+ static ValueParamT forward_value_param(ValueParamT V) { return V; }
+
public:
- void push_back(const T &Elt) {
- this->assertSafeToAdd(&Elt);
- if (LLVM_UNLIKELY(this->size() >= this->capacity()))
- this->grow();
- memcpy(reinterpret_cast<void *>(this->end()), &Elt, sizeof(T));
+ void push_back(ValueParamT Elt) {
+ const T *EltPtr = reserveForParamAndGetAddress(Elt);
+ memcpy(reinterpret_cast<void *>(this->end()), EltPtr, sizeof(T));
this->set_size(this->size() + 1);
}
using size_type = typename SuperClass::size_type;
protected:
+ using SmallVectorTemplateBase<T>::TakesParamByValue;
+ using ValueParamT = typename SuperClass::ValueParamT;
+
// Default ctor - Initialize to empty.
explicit SmallVectorImpl(unsigned N)
: SmallVectorTemplateBase<T>(N) {}
/// Like resize, but \ref T is POD, the new values won't be initialized.
void resize_for_overwrite(size_type N) { resizeImpl<true>(N); }
- void resize(size_type N, const T &NV) {
+ void resize(size_type N, ValueParamT NV) {
if (N == this->size())
return;
return;
}
- this->assertSafeToReferenceAfterResize(&NV, N);
- if (this->capacity() < N)
- this->grow(N);
- std::uninitialized_fill(this->end(), this->begin() + N, NV);
- this->set_size(N);
+ // N > this->size(). Defer to append.
+ this->append(N - this->size(), NV);
}
void reserve(size_type N) {
}
/// Append \p NumInputs copies of \p Elt to the end.
- void append(size_type NumInputs, const T &Elt) {
- this->assertSafeToAdd(&Elt, NumInputs);
- if (NumInputs > this->capacity() - this->size())
- this->grow(this->size()+NumInputs);
-
- std::uninitialized_fill_n(this->end(), NumInputs, Elt);
+ void append(size_type NumInputs, ValueParamT Elt) {
+ const T *EltPtr = this->reserveForParamAndGetAddress(Elt, NumInputs);
+ std::uninitialized_fill_n(this->end(), NumInputs, *EltPtr);
this->set_size(this->size() + NumInputs);
}
private:
template <class ArgType> iterator insert_one_impl(iterator I, ArgType &&Elt) {
+ // Callers ensure that ArgType is derived from T.
+ static_assert(
+ std::is_same<std::remove_const_t<std::remove_reference_t<ArgType>>,
+ T>::value,
+ "ArgType must be derived from T!");
+
if (I == this->end()) { // Important special case for empty vector.
this->push_back(::std::forward<ArgType>(Elt));
return this->end()-1;
assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds.");
- // Check that adding an element won't invalidate Elt.
- this->assertSafeToAdd(&Elt);
-
- if (this->size() >= this->capacity()) {
- size_t EltNo = I-this->begin();
- this->grow();
- I = this->begin()+EltNo;
- }
+ // Grow if necessary.
+ size_t Index = I - this->begin();
+ std::remove_reference_t<ArgType> *EltPtr =
+ this->reserveForParamAndGetAddress(Elt);
+ I = this->begin() + Index;
::new ((void*) this->end()) T(::std::move(this->back()));
// Push everything else over.
this->set_size(this->size() + 1);
// If we just moved the element we're inserting, be sure to update
- // the reference.
- std::remove_reference_t<ArgType> *EltPtr = &Elt;
- if (this->isReferenceToRange(EltPtr, I, this->end()))
+ // the reference (never happens if TakesParamByValue).
+ static_assert(!TakesParamByValue || std::is_same<ArgType, T>::value,
+ "ArgType must be 'T' when taking by value!");
+ if (!TakesParamByValue && this->isReferenceToRange(EltPtr, I, this->end()))
++EltPtr;
*I = ::std::forward<ArgType>(*EltPtr);
public:
iterator insert(iterator I, T &&Elt) {
- return insert_one_impl(I, std::move(Elt));
+ return insert_one_impl(I, this->forward_value_param(std::move(Elt)));
}
- iterator insert(iterator I, const T &Elt) { return insert_one_impl(I, Elt); }
+ iterator insert(iterator I, const T &Elt) {
+ return insert_one_impl(I, this->forward_value_param(Elt));
+ }
- iterator insert(iterator I, size_type NumToInsert, const T &Elt) {
+ iterator insert(iterator I, size_type NumToInsert, ValueParamT Elt) {
// Convert iterator to elt# to avoid invalidating iterator when we reserve()
size_t InsertElt = I - this->begin();
assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds.");
- // Check that adding NumToInsert elements won't invalidate Elt.
- this->assertSafeToAdd(&Elt, NumToInsert);
-
- // Ensure there is enough space.
- reserve(this->size() + NumToInsert);
+ // Ensure there is enough space, and get the (maybe updated) address of
+ // Elt.
+ const T *EltPtr = this->reserveForParamAndGetAddress(Elt, NumToInsert);
// Uninvalidate the iterator.
I = this->begin()+InsertElt;
// Copy the existing elements that get replaced.
std::move_backward(I, OldEnd-NumToInsert, OldEnd);
- std::fill_n(I, NumToInsert, Elt);
+ // If we just moved the element we're inserting, be sure to update
+ // the reference (never happens if TakesParamByValue).
+ if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end())
+ EltPtr += NumToInsert;
+
+ std::fill_n(I, NumToInsert, *EltPtr);
return I;
}
size_t NumOverwritten = OldEnd-I;
this->uninitialized_move(I, OldEnd, this->end()-NumOverwritten);
+ // If we just moved the element we're inserting, be sure to update
+ // the reference (never happens if TakesParamByValue).
+ if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end())
+ EltPtr += NumToInsert;
+
// Replace the overwritten part.
- std::fill_n(I, NumOverwritten, Elt);
+ std::fill_n(I, NumOverwritten, *EltPtr);
// Insert the non-overwritten middle part.
- std::uninitialized_fill_n(OldEnd, NumToInsert-NumOverwritten, Elt);
+ std::uninitialized_fill_n(OldEnd, NumToInsert - NumOverwritten, *EltPtr);
return I;
}
Constructable(Constructable && src) : constructed(true) {
value = src.value;
+ src.value = 0;
++numConstructorCalls;
++numMoveConstructorCalls;
}
Constructable & operator=(Constructable && src) {
EXPECT_TRUE(constructed);
value = src.value;
+ src.value = 0;
++numAssignmentCalls;
++numMoveAssignmentCalls;
return *this;
return N;
}
+ template <class T> static bool isValueType() {
+ return std::is_same<T, typename VectorT::value_type>::value;
+ }
+
void SetUp() override {
SmallVectorTestBase::SetUp();
// Fill up the small size so that insertions move the elements.
- V.append({0, 0, 0});
+ for (int I = 0, E = NumBuiltinElts(V); I != E; ++I)
+ V.emplace_back(I + 1);
}
};
SmallVectorReferenceInvalidationTestTypes);
TYPED_TEST(SmallVectorReferenceInvalidationTest, PushBack) {
+ // Note: setup adds [1, 2, ...] to V until it's at capacity in small mode.
auto &V = this->V;
- (void)V;
-#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
- EXPECT_DEATH(V.push_back(V.back()), this->AssertionMessage);
-#endif
+ int N = this->NumBuiltinElts(V);
+
+ // Push back a reference to last element when growing from small storage.
+ V.push_back(V.back());
+ EXPECT_EQ(N, V.back());
+
+ // Check that the old value is still there (not moved away).
+ EXPECT_EQ(N, V[V.size() - 2]);
+
+ // Fill storage again.
+ V.back() = V.size();
+ while (V.size() < V.capacity())
+ V.push_back(V.size() + 1);
+
+ // Push back a reference to last element when growing from large storage.
+ V.push_back(V.back());
+ EXPECT_EQ(int(V.size()) - 1, V.back());
}
TYPED_TEST(SmallVectorReferenceInvalidationTest, PushBackMoved) {
+ // Note: setup adds [1, 2, ...] to V until it's at capacity in small mode.
auto &V = this->V;
- (void)V;
-#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
- EXPECT_DEATH(V.push_back(std::move(V.back())), this->AssertionMessage);
-#endif
+ int N = this->NumBuiltinElts(V);
+
+ // Push back a reference to last element when growing from small storage.
+ V.push_back(std::move(V.back()));
+ EXPECT_EQ(N, V.back());
+ if (this->template isValueType<Constructable>()) {
+ // Check that the value was moved (not copied).
+ EXPECT_EQ(0, V[V.size() - 2]);
+ }
+
+ // Fill storage again.
+ V.back() = V.size();
+ while (V.size() < V.capacity())
+ V.push_back(V.size() + 1);
+
+ // Push back a reference to last element when growing from large storage.
+ V.push_back(std::move(V.back()));
+
+ // Check the values.
+ EXPECT_EQ(int(V.size()) - 1, V.back());
+ if (this->template isValueType<Constructable>()) {
+ // Check the value got moved out.
+ EXPECT_EQ(0, V[V.size() - 2]);
+ }
}
TYPED_TEST(SmallVectorReferenceInvalidationTest, Resize) {
auto &V = this->V;
(void)V;
int N = this->NumBuiltinElts(V);
-#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
- EXPECT_DEATH(V.resize(N + 1, V.back()), this->AssertionMessage);
-#endif
-
- // No assertion when shrinking, since the parameter isn't accessed.
- V.resize(N - 1, V.back());
+ V.resize(N + 1, V.back());
+ EXPECT_EQ(N, V.back());
+
+ // Resize to add enough elements that V will grow again. If reference
+ // invalidation breaks in the future, sanitizers should be able to catch a
+ // use-after-free here.
+ V.resize(V.capacity() + 1, V.front());
+ EXPECT_EQ(1, V.back());
}
TYPED_TEST(SmallVectorReferenceInvalidationTest, Append) {
auto &V = this->V;
(void)V;
-#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
- EXPECT_DEATH(V.append(1, V.back()), this->AssertionMessage);
-#endif
+ V.append(1, V.back());
+ int N = this->NumBuiltinElts(V);
+ EXPECT_EQ(N, V[N - 1]);
+
+ // Append enough more elements that V will grow again. This tests growing
+ // when already in large mode.
+ //
+ // If reference invalidation breaks in the future, sanitizers should be able
+ // to catch a use-after-free here.
+ V.append(V.capacity() - V.size() + 1, V.front());
+ EXPECT_EQ(1, V.back());
}
TYPED_TEST(SmallVectorReferenceInvalidationTest, AppendRange) {
}
TYPED_TEST(SmallVectorReferenceInvalidationTest, Insert) {
+ // Note: setup adds [1, 2, ...] to V until it's at capacity in small mode.
auto &V = this->V;
(void)V;
-#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
- EXPECT_DEATH(V.insert(V.begin(), V.back()), this->AssertionMessage);
-#endif
+
+ // Insert a reference to the back (not at end() or else insert delegates to
+ // push_back()), growing out of small mode. Confirm the value was copied out
+ // (moving out Constructable sets it to 0).
+ V.insert(V.begin(), V.back());
+ EXPECT_EQ(int(V.size() - 1), V.front());
+ EXPECT_EQ(int(V.size() - 1), V.back());
+
+ // Fill up the vector again.
+ while (V.size() < V.capacity())
+ V.push_back(V.size() + 1);
+
+ // Grow again from large storage to large storage.
+ V.insert(V.begin(), V.back());
+ EXPECT_EQ(int(V.size() - 1), V.front());
+ EXPECT_EQ(int(V.size() - 1), V.back());
}
TYPED_TEST(SmallVectorReferenceInvalidationTest, InsertMoved) {
+ // Note: setup adds [1, 2, ...] to V until it's at capacity in small mode.
auto &V = this->V;
(void)V;
-#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
- EXPECT_DEATH(V.insert(V.begin(), std::move(V.back())),
- this->AssertionMessage);
-#endif
+
+ // Insert a reference to the back (not at end() or else insert delegates to
+ // push_back()), growing out of small mode. Confirm the value was copied out
+ // (moving out Constructable sets it to 0).
+ V.insert(V.begin(), std::move(V.back()));
+ EXPECT_EQ(int(V.size() - 1), V.front());
+ if (this->template isValueType<Constructable>()) {
+ // Check the value got moved out.
+ EXPECT_EQ(0, V.back());
+ }
+
+ // Fill up the vector again.
+ while (V.size() < V.capacity())
+ V.push_back(V.size() + 1);
+
+ // Grow again from large storage to large storage.
+ V.insert(V.begin(), std::move(V.back()));
+ EXPECT_EQ(int(V.size() - 1), V.front());
+ if (this->template isValueType<Constructable>()) {
+ // Check the value got moved out.
+ EXPECT_EQ(0, V.back());
+ }
}
TYPED_TEST(SmallVectorReferenceInvalidationTest, InsertN) {
auto &V = this->V;
(void)V;
-#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
- EXPECT_DEATH(V.insert(V.begin(), 2, V.back()), this->AssertionMessage);
-#endif
+
+ // Cover NumToInsert <= this->end() - I.
+ V.insert(V.begin() + 1, 1, V.back());
+ int N = this->NumBuiltinElts(V);
+ EXPECT_EQ(N, V[1]);
+
+ // Cover NumToInsert > this->end() - I, inserting enough elements that V will
+ // also grow again; V.capacity() will be more elements than necessary but
+ // it's a simple way to cover both conditions.
+ //
+ // If reference invalidation breaks in the future, sanitizers should be able
+ // to catch a use-after-free here.
+ V.insert(V.begin(), V.capacity(), V.front());
+ EXPECT_EQ(1, V.front());
}
TYPED_TEST(SmallVectorReferenceInvalidationTest, InsertRange) {