[libc-commits] [libc] [libc][CPP] Add vector container and unit tests (PR #222972)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Fri Sep 11 11:57:46 PDT 2026


https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/222972

>From c1239cf45c96df7b1b31d3deec0a3750b0f83287 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 11 Sep 2026 07:39:45 +0100
Subject: [PATCH 1/2] [libc][CPP] Add vector container and unit tests

Sequential group database entrypoints require dynamically resizable
buffers to parse arbitrary-length records and member lists reliably.
Rather than managing raw heap reallocations manually within subsystem
parsers, dynamic growth is encapsulated in a dedicated cpp::vector
container.

Add cpp::vector in libc/src/__support/CPP/vector.h with move semantics,
safe fallible allocation (returning bool for out-of-memory handling
without C++ exceptions), and RAII clean-up for trivially and
non-trivially destructible types.

* Add vector header library in libc/src/__support/CPP/CMakeLists.txt
* Implement vector container in libc/src/__support/CPP/vector.h
* Add comprehensive unit tests in libc/test/src/__support/CPP/vector_test.cpp
* Register vector_test in libc/test/src/__support/CPP/CMakeLists.txt

Assisted-by: Automated tooling, human reviewed.
---
 libc/src/__support/CPP/CMakeLists.txt       |  16 +
 libc/src/__support/CPP/vector.h             | 321 ++++++++++++++++++++
 libc/test/src/__support/CPP/CMakeLists.txt  |  12 +
 libc/test/src/__support/CPP/vector_test.cpp | 302 ++++++++++++++++++
 4 files changed, 651 insertions(+)
 create mode 100644 libc/src/__support/CPP/vector.h
 create mode 100644 libc/test/src/__support/CPP/vector_test.cpp

diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index 6777c6a27c0cc..3a68411e39392 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -253,3 +253,19 @@ add_header_library(
     .utility
     .tuple
 )
+
+add_header_library(
+  vector
+  HDRS
+    vector.h
+  DEPENDS
+    .new
+    .type_traits
+    .utility
+    libc.hdr.func.free
+    libc.hdr.func.realloc
+    libc.hdr.types.size_t
+    libc.src.__support.common
+    libc.src.__support.libc_assert
+    libc.src.__support.macros.config
+)
diff --git a/libc/src/__support/CPP/vector.h b/libc/src/__support/CPP/vector.h
new file mode 100644
index 0000000000000..ceffe208342a1
--- /dev/null
+++ b/libc/src/__support/CPP/vector.h
@@ -0,0 +1,321 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of vector container.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_CPP_VECTOR_H
+#define LLVM_LIBC_SRC___SUPPORT_CPP_VECTOR_H
+
+#include "hdr/func/free.h"
+#include "hdr/func/realloc.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/CPP/type_traits.h"
+#include "src/__support/CPP/utility.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/config.h"
+
+#include <stddef.h> // For max_align_t, ptrdiff_t
+
+namespace LIBC_NAMESPACE_DECL {
+namespace cpp {
+
+template <typename T> class vector {
+  static_assert(alignof(T) <= alignof(max_align_t),
+                "Overaligned types are not supported by cpp::vector");
+
+public:
+  using value_type = T;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
+  using reference = T &;
+  using const_reference = const T &;
+  using pointer = T *;
+  using const_pointer = const T *;
+  using iterator = T *;
+  using const_iterator = const T *;
+
+  [[nodiscard]] LIBC_INLINE constexpr size_type max_size() const noexcept {
+    return static_cast<size_type>(-1) / sizeof(T);
+  }
+
+private:
+  T *data_ = nullptr;
+  size_t size_ = 0;
+  size_t capacity_ = 0;
+
+  static constexpr size_t DEFAULT_INITIAL_CAPACITY = 16;
+
+  LIBC_INLINE void destroy_elements(size_t from, size_t to) {
+    if constexpr (!is_trivially_destructible_v<T>) {
+      while (to > from) {
+        --to;
+        data_[to].~T();
+      }
+    }
+  }
+
+  LIBC_INLINE void deallocate() {
+    if (data_) {
+      destroy_elements(0, size_);
+      ::free(data_);
+      data_ = nullptr;
+      size_ = 0;
+      capacity_ = 0;
+    }
+  }
+
+public:
+  LIBC_INLINE constexpr vector() = default;
+
+  LIBC_INLINE ~vector() { deallocate(); }
+
+  // Move constructor
+  LIBC_INLINE vector(vector &&other) noexcept
+      : data_(other.data_), size_(other.size_), capacity_(other.capacity_) {
+    other.data_ = nullptr;
+    other.size_ = 0;
+    other.capacity_ = 0;
+  }
+
+  // Move assignment
+  LIBC_INLINE vector &operator=(vector &&other) noexcept {
+    if (this != &other) {
+      deallocate();
+      data_ = other.data_;
+      size_ = other.size_;
+      capacity_ = other.capacity_;
+      other.data_ = nullptr;
+      other.size_ = 0;
+      other.capacity_ = 0;
+    }
+    return *this;
+  }
+
+  // Copy operations are deleted to prevent accidental implicit allocations.
+  vector(const vector &) = delete;
+  vector &operator=(const vector &) = delete;
+
+  // Element access
+  LIBC_INLINE reference operator[](size_t i) {
+    LIBC_ASSERT(i < size_);
+    return data_[i];
+  }
+
+  LIBC_INLINE const_reference operator[](size_t i) const {
+    LIBC_ASSERT(i < size_);
+    return data_[i];
+  }
+
+  LIBC_INLINE pointer data() { return data_; }
+  LIBC_INLINE const_pointer data() const { return data_; }
+
+  LIBC_INLINE reference front() {
+    LIBC_ASSERT(size_ > 0);
+    return data_[0];
+  }
+
+  LIBC_INLINE const_reference front() const {
+    LIBC_ASSERT(size_ > 0);
+    return data_[0];
+  }
+
+  LIBC_INLINE reference back() {
+    LIBC_ASSERT(size_ > 0);
+    return data_[size_ - 1];
+  }
+
+  LIBC_INLINE const_reference back() const {
+    LIBC_ASSERT(size_ > 0);
+    return data_[size_ - 1];
+  }
+
+  // Iterators
+  LIBC_INLINE iterator begin() { return data_; }
+  LIBC_INLINE const_iterator begin() const { return data_; }
+  LIBC_INLINE const_iterator cbegin() const { return data_; }
+  LIBC_INLINE iterator end() { return data_ + size_; }
+  LIBC_INLINE const_iterator end() const { return data_ + size_; }
+  LIBC_INLINE const_iterator cend() const { return data_ + size_; }
+
+  // Capacity
+  [[nodiscard]] LIBC_INLINE bool empty() const { return size_ == 0; }
+  LIBC_INLINE size_t size() const { return size_; }
+  LIBC_INLINE size_t capacity() const { return capacity_; }
+
+  // Reserves at least new_cap elements. Returns true on success, false on OOM.
+  [[nodiscard]] LIBC_INLINE bool reserve(size_t new_cap) {
+    if (new_cap <= capacity_)
+      return true;
+    if (new_cap > max_size())
+      return false;
+
+    if constexpr (is_trivially_copyable_v<T>) {
+      void *new_data = ::realloc(data_, new_cap * sizeof(T));
+      if (!new_data)
+        return false;
+      data_ = static_cast<T *>(new_data);
+      capacity_ = new_cap;
+      return true;
+    } else {
+      void *new_raw = ::realloc(nullptr, new_cap * sizeof(T));
+      if (!new_raw)
+        return false;
+      T *new_data = static_cast<T *>(new_raw);
+      for (size_t i = 0; i < size_; ++i) {
+        new (new_data + i) T(cpp::move(data_[i]));
+        data_[i].~T();
+      }
+      ::free(data_);
+      data_ = new_data;
+      capacity_ = new_cap;
+      return true;
+    }
+  }
+
+  LIBC_INLINE void shrink_to_fit() {
+    if (size_ == capacity_)
+      return;
+    if (size_ == 0) {
+      reset();
+      return;
+    }
+    if constexpr (is_trivially_copyable_v<T>) {
+      void *new_data = ::realloc(data_, size_ * sizeof(T));
+      if (new_data) {
+        data_ = static_cast<T *>(new_data);
+        capacity_ = size_;
+      }
+    } else {
+      void *new_raw = ::realloc(nullptr, size_ * sizeof(T));
+      if (!new_raw)
+        return;
+      T *new_data = static_cast<T *>(new_raw);
+      for (size_t i = 0; i < size_; ++i) {
+        new (new_data + i) T(cpp::move(data_[i]));
+        data_[i].~T();
+      }
+      ::free(data_);
+      data_ = new_data;
+      capacity_ = size_;
+    }
+  }
+
+  // Modifiers
+  template <typename... Args>
+  [[nodiscard]] LIBC_INLINE bool emplace_back(Args &&...args) {
+    if (size_ < capacity_) {
+      new (data_ + size_) T(cpp::forward<Args>(args)...);
+      ++size_;
+      return true;
+    }
+
+    size_t new_cap = DEFAULT_INITIAL_CAPACITY;
+    if (capacity_ > 0) {
+      if (capacity_ > max_size() / 2)
+        return false;
+      new_cap = capacity_ * 2;
+    }
+
+    void *new_raw = ::realloc(nullptr, new_cap * sizeof(T));
+    if (!new_raw)
+      return false;
+    T *new_data = static_cast<T *>(new_raw);
+
+    // Construct the new element before moving/freeing old storage so that
+    // arguments referencing the vector's own buffer remain valid.
+    new (new_data + size_) T(cpp::forward<Args>(args)...);
+
+    for (size_t i = 0; i < size_; ++i) {
+      new (new_data + i) T(cpp::move(data_[i]));
+      data_[i].~T();
+    }
+    ::free(data_);
+    data_ = new_data;
+    capacity_ = new_cap;
+    ++size_;
+    return true;
+  }
+
+  [[nodiscard]] LIBC_INLINE bool push_back(const T &val) {
+    return emplace_back(val);
+  }
+
+  [[nodiscard]] LIBC_INLINE bool push_back(T &&val) {
+    return emplace_back(cpp::move(val));
+  }
+
+  LIBC_INLINE void pop_back() {
+    LIBC_ASSERT(size_ > 0);
+    --size_;
+    if constexpr (!is_trivially_destructible_v<T>)
+      data_[size_].~T();
+  }
+
+  LIBC_INLINE void clear() {
+    destroy_elements(0, size_);
+    size_ = 0;
+  }
+
+  // Deallocates backing memory and resets size and capacity to 0.
+  LIBC_INLINE void reset() { deallocate(); }
+
+  [[nodiscard]] LIBC_INLINE bool resize(size_t new_size) {
+    if (new_size < size_) {
+      destroy_elements(new_size, size_);
+      size_ = new_size;
+      return true;
+    }
+    if (new_size > size_) {
+      if (!reserve(new_size))
+        return false;
+      for (size_t i = size_; i < new_size; ++i)
+        new (data_ + i) T();
+      size_ = new_size;
+    }
+    return true;
+  }
+
+  [[nodiscard]] LIBC_INLINE bool resize(size_t new_size, const T &value) {
+    if (new_size < size_) {
+      destroy_elements(new_size, size_);
+      size_ = new_size;
+      return true;
+    }
+    if (new_size > size_) {
+      // Copy value before reserve in case value references this vector's data.
+      T val_copy(value);
+      if (!reserve(new_size))
+        return false;
+      for (size_t i = size_; i < new_size; ++i)
+        new (data_ + i) T(val_copy);
+      size_ = new_size;
+    }
+    return true;
+  }
+
+  LIBC_INLINE void swap(vector &other) noexcept {
+    cpp::swap(data_, other.data_);
+    cpp::swap(size_, other.size_);
+    cpp::swap(capacity_, other.capacity_);
+  }
+};
+
+template <typename T>
+LIBC_INLINE void swap(vector<T> &lhs, vector<T> &rhs) noexcept {
+  lhs.swap(rhs);
+}
+
+} // namespace cpp
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_CPP_VECTOR_H
diff --git a/libc/test/src/__support/CPP/CMakeLists.txt b/libc/test/src/__support/CPP/CMakeLists.txt
index e962487a87a43..7931da87fca14 100644
--- a/libc/test/src/__support/CPP/CMakeLists.txt
+++ b/libc/test/src/__support/CPP/CMakeLists.txt
@@ -207,6 +207,18 @@ add_libc_test(
     libc.src.__support.CPP.type_traits
 )
 
+add_libc_test(
+  vector_test
+  SUITE
+    libc-cpp-utils-tests
+  SRCS
+    vector_test.cpp
+  DEPENDS
+    libc.src.__support.CPP.type_traits
+    libc.src.__support.CPP.utility
+    libc.src.__support.CPP.vector
+)
+
 if(LIBC_COMPILER_HAS_EXT_VECTOR_TYPE)
   add_libc_test(
     simd_test
diff --git a/libc/test/src/__support/CPP/vector_test.cpp b/libc/test/src/__support/CPP/vector_test.cpp
new file mode 100644
index 0000000000000..036e64b9156ea
--- /dev/null
+++ b/libc/test/src/__support/CPP/vector_test.cpp
@@ -0,0 +1,302 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unit tests for vector.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/vector.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::cpp::vector;
+
+TEST(LlvmLibcVectorTest, InitializeEmpty) {
+  vector<int> v;
+  EXPECT_TRUE(v.empty());
+  EXPECT_EQ(v.size(), size_t(0));
+  EXPECT_EQ(v.capacity(), size_t(0));
+  EXPECT_EQ(v.data(), static_cast<int *>(nullptr));
+}
+
+TEST(LlvmLibcVectorTest, PushBackAndAccess) {
+  vector<int> v;
+  ASSERT_TRUE(v.push_back(10));
+  ASSERT_TRUE(v.push_back(20));
+  ASSERT_TRUE(v.push_back(30));
+
+  EXPECT_FALSE(v.empty());
+  EXPECT_EQ(v.size(), size_t(3));
+  EXPECT_GE(v.capacity(), size_t(3));
+
+  EXPECT_EQ(v[0], 10);
+  EXPECT_EQ(v[1], 20);
+  EXPECT_EQ(v[2], 30);
+  EXPECT_EQ(v.front(), 10);
+  EXPECT_EQ(v.back(), 30);
+  ASSERT_NE(v.data(), nullptr);
+  EXPECT_EQ(v.data()[1], 20);
+}
+
+TEST(LlvmLibcVectorTest, RangeBasedForLoop) {
+  vector<int> v;
+  for (int i = 0; i < 5; ++i)
+    ASSERT_TRUE(v.push_back(i * 10));
+
+  int expected = 0;
+  for (int val : v) {
+    EXPECT_EQ(val, expected);
+    expected += 10;
+  }
+  EXPECT_EQ(expected, 50);
+
+  // Test cbegin / cend
+  int const_expected = 0;
+  for (auto it = v.cbegin(); it != v.cend(); ++it) {
+    EXPECT_EQ(*it, const_expected);
+    const_expected += 10;
+  }
+  EXPECT_EQ(const_expected, 50);
+}
+
+TEST(LlvmLibcVectorTest, MemberTypesAndMaxSize) {
+  static_assert(LIBC_NAMESPACE::cpp::is_same_v<vector<int>::value_type, int>);
+  static_assert(LIBC_NAMESPACE::cpp::is_same_v<vector<int>::size_type, size_t>);
+  static_assert(
+      LIBC_NAMESPACE::cpp::is_same_v<vector<int>::difference_type, ptrdiff_t>);
+  static_assert(LIBC_NAMESPACE::cpp::is_same_v<vector<int>::pointer, int *>);
+  static_assert(
+      LIBC_NAMESPACE::cpp::is_same_v<vector<int>::const_pointer, const int *>);
+
+  vector<int> v;
+  EXPECT_GT(v.max_size(), size_t(0));
+  EXPECT_EQ(v.max_size(), static_cast<size_t>(-1) / sizeof(int));
+}
+
+TEST(LlvmLibcVectorTest, PopBack) {
+  vector<int> v;
+  ASSERT_TRUE(v.push_back(1));
+  ASSERT_TRUE(v.push_back(2));
+  EXPECT_EQ(v.size(), size_t(2));
+
+  v.pop_back();
+  EXPECT_EQ(v.size(), size_t(1));
+  EXPECT_EQ(v.back(), 1);
+
+  v.pop_back();
+  EXPECT_TRUE(v.empty());
+  EXPECT_EQ(v.size(), size_t(0));
+}
+
+TEST(LlvmLibcVectorTest, ReserveAndShrinkToFit) {
+  vector<int> v;
+  ASSERT_TRUE(v.reserve(100));
+  EXPECT_GE(v.capacity(), size_t(100));
+  EXPECT_EQ(v.size(), size_t(0));
+
+  ASSERT_TRUE(v.push_back(42));
+  EXPECT_EQ(v.size(), size_t(1));
+  EXPECT_GE(v.capacity(), size_t(100));
+
+  v.shrink_to_fit();
+  EXPECT_EQ(v.size(), size_t(1));
+  EXPECT_EQ(v.capacity(), size_t(1));
+  EXPECT_EQ(v[0], 42);
+}
+
+TEST(LlvmLibcVectorTest, ClearAndReset) {
+  vector<int> v;
+  for (int i = 0; i < 10; ++i)
+    ASSERT_TRUE(v.push_back(i));
+
+  size_t old_cap = v.capacity();
+  v.clear();
+  EXPECT_TRUE(v.empty());
+  EXPECT_EQ(v.size(), size_t(0));
+  EXPECT_EQ(v.capacity(), old_cap);
+
+  // Can reuse buffer after clear
+  ASSERT_TRUE(v.push_back(99));
+  EXPECT_EQ(v.size(), size_t(1));
+  EXPECT_EQ(v[0], 99);
+
+  // Reset frees buffer
+  v.reset();
+  EXPECT_TRUE(v.empty());
+  EXPECT_EQ(v.size(), size_t(0));
+  EXPECT_EQ(v.capacity(), size_t(0));
+  EXPECT_EQ(v.data(), static_cast<int *>(nullptr));
+}
+
+TEST(LlvmLibcVectorTest, Resize) {
+  vector<int> v;
+  ASSERT_TRUE(v.resize(5, 7));
+  EXPECT_EQ(v.size(), size_t(5));
+  for (size_t i = 0; i < 5; ++i)
+    EXPECT_EQ(v[i], 7);
+
+  ASSERT_TRUE(v.resize(2));
+  EXPECT_EQ(v.size(), size_t(2));
+  EXPECT_EQ(v[0], 7);
+  EXPECT_EQ(v[1], 7);
+
+  ASSERT_TRUE(v.resize(4, 9));
+  EXPECT_EQ(v.size(), size_t(4));
+  EXPECT_EQ(v[0], 7);
+  EXPECT_EQ(v[1], 7);
+  EXPECT_EQ(v[2], 9);
+  EXPECT_EQ(v[3], 9);
+}
+
+TEST(LlvmLibcVectorTest, MoveOperations) {
+  vector<int> v1;
+  ASSERT_TRUE(v1.push_back(100));
+  ASSERT_TRUE(v1.push_back(200));
+
+  // Move constructor
+  vector<int> v2(LIBC_NAMESPACE::cpp::move(v1));
+  EXPECT_EQ(v2.size(), size_t(2));
+  EXPECT_EQ(v2[0], 100);
+  EXPECT_EQ(v2[1], 200);
+  EXPECT_TRUE(v1.empty());
+  EXPECT_EQ(v1.data(), static_cast<int *>(nullptr));
+
+  // Move assignment
+  vector<int> v3;
+  v3 = LIBC_NAMESPACE::cpp::move(v2);
+  EXPECT_EQ(v3.size(), size_t(2));
+  EXPECT_EQ(v3[0], 100);
+  EXPECT_EQ(v3[1], 200);
+  EXPECT_TRUE(v2.empty());
+  EXPECT_EQ(v2.data(), static_cast<int *>(nullptr));
+}
+
+TEST(LlvmLibcVectorTest, SwapOperations) {
+  vector<int> a;
+  ASSERT_TRUE(a.push_back(1));
+  ASSERT_TRUE(a.push_back(2));
+
+  vector<int> b;
+  ASSERT_TRUE(b.push_back(30));
+  ASSERT_TRUE(b.push_back(40));
+  ASSERT_TRUE(b.push_back(50));
+
+  // Member swap
+  a.swap(b);
+  EXPECT_EQ(a.size(), size_t(3));
+  EXPECT_EQ(a[0], 30);
+  EXPECT_EQ(a[1], 40);
+  EXPECT_EQ(a[2], 50);
+  EXPECT_EQ(b.size(), size_t(2));
+  EXPECT_EQ(b[0], 1);
+  EXPECT_EQ(b[1], 2);
+
+  // Free swap
+  LIBC_NAMESPACE::cpp::swap(a, b);
+  EXPECT_EQ(a.size(), size_t(2));
+  EXPECT_EQ(a[0], 1);
+  EXPECT_EQ(a[1], 2);
+  EXPECT_EQ(b.size(), size_t(3));
+  EXPECT_EQ(b[0], 30);
+  EXPECT_EQ(b[1], 40);
+  EXPECT_EQ(b[2], 50);
+}
+
+struct LifetimeTracker {
+  static int constructed;
+  static int destructed;
+  static int destroy_log[64];
+  static int destroy_log_len;
+  int value = 0;
+  bool moved_from = false;
+
+  LifetimeTracker() : value(0) { ++constructed; }
+  explicit LifetimeTracker(int val) : value(val) { ++constructed; }
+  LifetimeTracker(const LifetimeTracker &other) : value(other.value) {
+    ++constructed;
+  }
+  LifetimeTracker(LifetimeTracker &&other) noexcept : value(other.value) {
+    other.moved_from = true;
+    ++constructed;
+  }
+  ~LifetimeTracker() {
+    ++destructed;
+    if (!moved_from && destroy_log_len < 64)
+      destroy_log[destroy_log_len++] = value;
+  }
+};
+
+int LifetimeTracker::constructed = 0;
+int LifetimeTracker::destructed = 0;
+int LifetimeTracker::destroy_log[64] = {};
+int LifetimeTracker::destroy_log_len = 0;
+
+TEST(LlvmLibcVectorTest, NonTrivialGrowthAndDestructionOrder) {
+  LifetimeTracker::constructed = 0;
+  LifetimeTracker::destructed = 0;
+  LifetimeTracker::destroy_log_len = 0;
+
+  {
+    vector<LifetimeTracker> v;
+    // Insert 20 elements to force reallocation past DEFAULT_INITIAL_CAPACITY
+    // (16)
+    for (int i = 0; i < 20; ++i)
+      ASSERT_TRUE(v.emplace_back(i));
+
+    EXPECT_EQ(v.size(), size_t(20));
+    EXPECT_GE(v.capacity(), size_t(20));
+    for (size_t i = 0; i < 20; ++i)
+      EXPECT_EQ(v[i].value, int(i));
+
+    v.shrink_to_fit();
+    EXPECT_EQ(v.capacity(), size_t(20));
+    for (size_t i = 0; i < 20; ++i)
+      EXPECT_EQ(v[i].value, int(i));
+
+    v.pop_back();
+    EXPECT_EQ(v.size(), size_t(19));
+  }
+
+  EXPECT_EQ(LifetimeTracker::constructed, LifetimeTracker::destructed);
+  // First destroyed was element 19 (via pop_back), then 18 down to 0 (LIFO)
+  ASSERT_EQ(LifetimeTracker::destroy_log_len, 20);
+  EXPECT_EQ(LifetimeTracker::destroy_log[0], 19);
+  for (int i = 1; i < 20; ++i)
+    EXPECT_EQ(LifetimeTracker::destroy_log[i], 19 - i);
+}
+
+TEST(LlvmLibcVectorTest, SelfReferentialPushBackAndResize) {
+  vector<int> v;
+  // Fill up to initial capacity (16) so the next push_back reallocates.
+  for (int i = 0; i < 16; ++i)
+    ASSERT_TRUE(v.push_back(i + 100));
+
+  EXPECT_EQ(v.size(), v.capacity());
+  // Self-referential push_back across reallocation boundary
+  ASSERT_TRUE(v.push_back(v[0]));
+  EXPECT_EQ(v.size(), size_t(17));
+  EXPECT_EQ(v.back(), 100);
+
+  // Self-referential resize across reallocation boundary
+  ASSERT_TRUE(v.resize(40, v[1]));
+  EXPECT_EQ(v.size(), size_t(40));
+  EXPECT_EQ(v[39], 101);
+}
+
+TEST(LlvmLibcVectorTest, StringPointerVector) {
+  vector<const char *> ptrs;
+  ASSERT_TRUE(ptrs.push_back("first"));
+  ASSERT_TRUE(ptrs.push_back("second"));
+  ASSERT_TRUE(ptrs.push_back(nullptr));
+
+  EXPECT_EQ(ptrs.size(), size_t(3));
+  EXPECT_STREQ(ptrs[0], "first");
+  EXPECT_STREQ(ptrs[1], "second");
+  EXPECT_EQ(ptrs[2], nullptr);
+  EXPECT_EQ(ptrs.data()[2], nullptr);
+}

>From f680235f7ab1452bbfad8102586012e9e5a1ee91 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 11 Sep 2026 19:57:34 +0100
Subject: [PATCH 2/2] [libc][CPP] Address review feedback on vector

* Factor out reallocate() private helper shared by reserve() and shrink_to_fit()
* Use ::malloc instead of ::realloc(nullptr, ...) for initial/non-trivial allocations
* Add [[nodiscard]] to pure accessors/observers matching libc++

Assisted-by: Automated tooling, human reviewed.
---
 libc/src/__support/CPP/CMakeLists.txt |   1 +
 libc/src/__support/CPP/vector.h       | 104 ++++++++++++--------------
 2 files changed, 47 insertions(+), 58 deletions(-)

diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index 3a68411e39392..16d9db567abf4 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -263,6 +263,7 @@ add_header_library(
     .type_traits
     .utility
     libc.hdr.func.free
+    libc.hdr.func.malloc
     libc.hdr.func.realloc
     libc.hdr.types.size_t
     libc.src.__support.common
diff --git a/libc/src/__support/CPP/vector.h b/libc/src/__support/CPP/vector.h
index ceffe208342a1..839a937505671 100644
--- a/libc/src/__support/CPP/vector.h
+++ b/libc/src/__support/CPP/vector.h
@@ -15,6 +15,7 @@
 #define LLVM_LIBC_SRC___SUPPORT_CPP_VECTOR_H
 
 #include "hdr/func/free.h"
+#include "hdr/func/malloc.h"
 #include "hdr/func/realloc.h"
 #include "hdr/types/size_t.h"
 #include "src/__support/CPP/new.h"
@@ -74,6 +75,30 @@ template <typename T> class vector {
     }
   }
 
+  [[nodiscard]] LIBC_INLINE bool reallocate(size_t new_cap) {
+    if constexpr (is_trivially_copyable_v<T>) {
+      void *new_data = ::realloc(data_, new_cap * sizeof(T));
+      if (!new_data)
+        return false;
+      data_ = static_cast<T *>(new_data);
+      capacity_ = new_cap;
+      return true;
+    } else {
+      void *new_raw = ::malloc(new_cap * sizeof(T));
+      if (!new_raw)
+        return false;
+      T *new_data = static_cast<T *>(new_raw);
+      for (size_t i = 0; i < size_; ++i) {
+        new (new_data + i) T(cpp::move(data_[i]));
+        data_[i].~T();
+      }
+      ::free(data_);
+      data_ = new_data;
+      capacity_ = new_cap;
+      return true;
+    }
+  }
+
 public:
   LIBC_INLINE constexpr vector() = default;
 
@@ -106,51 +131,53 @@ template <typename T> class vector {
   vector &operator=(const vector &) = delete;
 
   // Element access
-  LIBC_INLINE reference operator[](size_t i) {
+  [[nodiscard]] LIBC_INLINE reference operator[](size_t i) {
     LIBC_ASSERT(i < size_);
     return data_[i];
   }
 
-  LIBC_INLINE const_reference operator[](size_t i) const {
+  [[nodiscard]] LIBC_INLINE const_reference operator[](size_t i) const {
     LIBC_ASSERT(i < size_);
     return data_[i];
   }
 
-  LIBC_INLINE pointer data() { return data_; }
-  LIBC_INLINE const_pointer data() const { return data_; }
+  [[nodiscard]] LIBC_INLINE pointer data() { return data_; }
+  [[nodiscard]] LIBC_INLINE const_pointer data() const { return data_; }
 
-  LIBC_INLINE reference front() {
+  [[nodiscard]] LIBC_INLINE reference front() {
     LIBC_ASSERT(size_ > 0);
     return data_[0];
   }
 
-  LIBC_INLINE const_reference front() const {
+  [[nodiscard]] LIBC_INLINE const_reference front() const {
     LIBC_ASSERT(size_ > 0);
     return data_[0];
   }
 
-  LIBC_INLINE reference back() {
+  [[nodiscard]] LIBC_INLINE reference back() {
     LIBC_ASSERT(size_ > 0);
     return data_[size_ - 1];
   }
 
-  LIBC_INLINE const_reference back() const {
+  [[nodiscard]] LIBC_INLINE const_reference back() const {
     LIBC_ASSERT(size_ > 0);
     return data_[size_ - 1];
   }
 
   // Iterators
-  LIBC_INLINE iterator begin() { return data_; }
-  LIBC_INLINE const_iterator begin() const { return data_; }
-  LIBC_INLINE const_iterator cbegin() const { return data_; }
-  LIBC_INLINE iterator end() { return data_ + size_; }
-  LIBC_INLINE const_iterator end() const { return data_ + size_; }
-  LIBC_INLINE const_iterator cend() const { return data_ + size_; }
+  [[nodiscard]] LIBC_INLINE iterator begin() { return data_; }
+  [[nodiscard]] LIBC_INLINE const_iterator begin() const { return data_; }
+  [[nodiscard]] LIBC_INLINE const_iterator cbegin() const { return data_; }
+  [[nodiscard]] LIBC_INLINE iterator end() { return data_ + size_; }
+  [[nodiscard]] LIBC_INLINE const_iterator end() const { return data_ + size_; }
+  [[nodiscard]] LIBC_INLINE const_iterator cend() const {
+    return data_ + size_;
+  }
 
   // Capacity
   [[nodiscard]] LIBC_INLINE bool empty() const { return size_ == 0; }
-  LIBC_INLINE size_t size() const { return size_; }
-  LIBC_INLINE size_t capacity() const { return capacity_; }
+  [[nodiscard]] LIBC_INLINE size_t size() const { return size_; }
+  [[nodiscard]] LIBC_INLINE size_t capacity() const { return capacity_; }
 
   // Reserves at least new_cap elements. Returns true on success, false on OOM.
   [[nodiscard]] LIBC_INLINE bool reserve(size_t new_cap) {
@@ -158,28 +185,7 @@ template <typename T> class vector {
       return true;
     if (new_cap > max_size())
       return false;
-
-    if constexpr (is_trivially_copyable_v<T>) {
-      void *new_data = ::realloc(data_, new_cap * sizeof(T));
-      if (!new_data)
-        return false;
-      data_ = static_cast<T *>(new_data);
-      capacity_ = new_cap;
-      return true;
-    } else {
-      void *new_raw = ::realloc(nullptr, new_cap * sizeof(T));
-      if (!new_raw)
-        return false;
-      T *new_data = static_cast<T *>(new_raw);
-      for (size_t i = 0; i < size_; ++i) {
-        new (new_data + i) T(cpp::move(data_[i]));
-        data_[i].~T();
-      }
-      ::free(data_);
-      data_ = new_data;
-      capacity_ = new_cap;
-      return true;
-    }
+    return reallocate(new_cap);
   }
 
   LIBC_INLINE void shrink_to_fit() {
@@ -189,25 +195,7 @@ template <typename T> class vector {
       reset();
       return;
     }
-    if constexpr (is_trivially_copyable_v<T>) {
-      void *new_data = ::realloc(data_, size_ * sizeof(T));
-      if (new_data) {
-        data_ = static_cast<T *>(new_data);
-        capacity_ = size_;
-      }
-    } else {
-      void *new_raw = ::realloc(nullptr, size_ * sizeof(T));
-      if (!new_raw)
-        return;
-      T *new_data = static_cast<T *>(new_raw);
-      for (size_t i = 0; i < size_; ++i) {
-        new (new_data + i) T(cpp::move(data_[i]));
-        data_[i].~T();
-      }
-      ::free(data_);
-      data_ = new_data;
-      capacity_ = size_;
-    }
+    static_cast<void>(reallocate(size_));
   }
 
   // Modifiers
@@ -226,7 +214,7 @@ template <typename T> class vector {
       new_cap = capacity_ * 2;
     }
 
-    void *new_raw = ::realloc(nullptr, new_cap * sizeof(T));
+    void *new_raw = ::malloc(new_cap * sizeof(T));
     if (!new_raw)
       return false;
     T *new_data = static_cast<T *>(new_raw);



More information about the libc-commits mailing list