Bring SpdyIntrusiveList back to life as QuicheIntrusiveList.

I need it for the QuicBufferedPacketStore change in cl/650350765.

PiperOrigin-RevId: 651457521
diff --git a/build/source_list.bzl b/build/source_list.bzl
index d65346b..c93905d 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -47,6 +47,7 @@
     "common/quiche_data_writer.h",
     "common/quiche_endian.h",
     "common/quiche_feature_flags_list.h",
+    "common/quiche_intrusive_list.h",
     "common/quiche_ip_address.h",
     "common/quiche_ip_address_family.h",
     "common/quiche_linked_hash_map.h",
@@ -1094,6 +1095,7 @@
     "common/quiche_data_reader_test.cc",
     "common/quiche_data_writer_test.cc",
     "common/quiche_endian_test.cc",
+    "common/quiche_intrusive_list_test.cc",
     "common/quiche_ip_address_test.cc",
     "common/quiche_linked_hash_map_test.cc",
     "common/quiche_mem_slice_storage_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 1692b9a..634b199 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -47,6 +47,7 @@
     "src/quiche/common/quiche_data_writer.h",
     "src/quiche/common/quiche_endian.h",
     "src/quiche/common/quiche_feature_flags_list.h",
+    "src/quiche/common/quiche_intrusive_list.h",
     "src/quiche/common/quiche_ip_address.h",
     "src/quiche/common/quiche_ip_address_family.h",
     "src/quiche/common/quiche_linked_hash_map.h",
@@ -1095,6 +1096,7 @@
     "src/quiche/common/quiche_data_reader_test.cc",
     "src/quiche/common/quiche_data_writer_test.cc",
     "src/quiche/common/quiche_endian_test.cc",
+    "src/quiche/common/quiche_intrusive_list_test.cc",
     "src/quiche/common/quiche_ip_address_test.cc",
     "src/quiche/common/quiche_linked_hash_map_test.cc",
     "src/quiche/common/quiche_mem_slice_storage_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 9bb5d09..dc343b0 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -46,6 +46,7 @@
     "quiche/common/quiche_data_writer.h",
     "quiche/common/quiche_endian.h",
     "quiche/common/quiche_feature_flags_list.h",
+    "quiche/common/quiche_intrusive_list.h",
     "quiche/common/quiche_ip_address.h",
     "quiche/common/quiche_ip_address_family.h",
     "quiche/common/quiche_linked_hash_map.h",
@@ -1094,6 +1095,7 @@
     "quiche/common/quiche_data_reader_test.cc",
     "quiche/common/quiche_data_writer_test.cc",
     "quiche/common/quiche_endian_test.cc",
+    "quiche/common/quiche_intrusive_list_test.cc",
     "quiche/common/quiche_ip_address_test.cc",
     "quiche/common/quiche_linked_hash_map_test.cc",
     "quiche/common/quiche_mem_slice_storage_test.cc",
diff --git a/quiche/common/quiche_intrusive_list.h b/quiche/common/quiche_intrusive_list.h
new file mode 100644
index 0000000..a00bc76
--- /dev/null
+++ b/quiche/common/quiche_intrusive_list.h
@@ -0,0 +1,343 @@
+// Copyright (c) 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_COMMON_QUICHE_INTRUSIVE_LIST_H_
+#define QUICHE_COMMON_QUICHE_INTRUSIVE_LIST_H_
+
+// A QuicheIntrusiveList<> is a doubly-linked list where the link pointers are
+// embedded in the elements. They are circularly linked making insertion and
+// removal into a known position constant time and branch-free operations.
+//
+// Usage is similar to an STL list<> where feasible, but there are important
+// differences. First and foremost, the elements must derive from the
+// QuicheIntrusiveLink<> base class:
+//
+//   struct Foo : public QuicheIntrusiveLink<Foo> {
+//     // ...
+//   }
+//
+//   QuicheIntrusiveList<Foo> l;
+//   l.push_back(new Foo);
+//   l.push_front(new Foo);
+//   l.erase(&l.front());
+//   l.erase(&l.back());
+//
+// Intrusive lists are primarily useful when you would have considered embedding
+// link pointers in your class directly for space or performance reasons. An
+// QuicheIntrusiveLink<> is the size of 2 pointers, usually 16 bytes on 64-bit
+// systems. Intrusive lists do not perform memory allocation (unlike the STL
+// list<> class) and thus may use less memory than list<>. In particular, if the
+// list elements are pointers to objects, using a list<> would perform an extra
+// memory allocation for each list node structure, while an
+// QuicheIntrusiveList<> would not.
+//
+// Note that QuicheIntrusiveLink is exempt from the C++ style guide's
+// limitations on multiple inheritance, so it's fine to inherit from both
+// QuicheIntrusiveLink and a base class, even if the base class is not a pure
+// interface.
+//
+// Because the list pointers are embedded in the objects stored in an
+// QuicheIntrusiveList<>, erasing an item from a list is constant time. Consider
+// the following:
+//
+//   map<string,Foo> foo_map;
+//   list<Foo*> foo_list;
+//
+//   foo_list.push_back(&foo_map["bar"]);
+//   foo_list.erase(&foo_map["bar"]); // Compile error!
+//
+// The problem here is that a Foo* doesn't know where on foo_list it resides,
+// so removal requires iteration over the list. Various tricks can be performed
+// to overcome this. For example, a foo_list::iterator can be stored inside of
+// the Foo object. But at that point you'd be better off using an
+// QuicheIntrusiveList<>:
+//
+//   map<string,Foo> foo_map;
+//   QuicheIntrusiveList<Foo> foo_list;
+//
+//   foo_list.push_back(&foo_map["bar"]);
+//   foo_list.erase(&foo_map["bar"]); // Yeah!
+//
+// Note that QuicheIntrusiveLists come with a few limitations. The primary
+// limitation is that the QuicheIntrusiveLink<> base class is not copyable or
+// assignable. The result is that STL algorithms which mutate the order of
+// iterators, such as reverse() and unique(), will not work by default with
+// QuicheIntrusiveLists. In order to allow these algorithms to work you'll need
+// to define swap() and/or operator= for your class.
+//
+// Another limitation is that the QuicheIntrusiveList<> structure itself is not
+// copyable or assignable since an item/link combination can only exist on one
+// QuicheIntrusiveList<> at a time. This limitation is a result of the link
+// pointers for an item being intrusive in the item itself. For example, the
+// following will not compile:
+//
+//   FooList a;
+//   FooList b(a); // no copy constructor
+//   b = a;        // no assignment operator
+//
+// The similar STL code does work since the link pointers are external to the
+// item:
+//
+//   list<int*> a;
+//   a.push_back(new int);
+//   list<int*> b(a);
+//   QUICHE_CHECK(a.front() == b.front());
+//
+// Note that QuicheIntrusiveList::size() runs in O(N) time.
+
+#include <stddef.h>
+
+#include <cstddef>
+#include <iterator>
+
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche {
+
+template <typename T, typename ListID>
+class QuicheIntrusiveList;
+
+template <typename T, typename ListID = void>
+class QUICHE_EXPORT QuicheIntrusiveLink {
+ protected:
+  // We declare the constructor protected so that only derived types and the
+  // befriended list can construct this.
+  QuicheIntrusiveLink() : next_(nullptr), prev_(nullptr) {}
+
+#ifndef SWIG
+  QuicheIntrusiveLink(const QuicheIntrusiveLink&) = delete;
+  QuicheIntrusiveLink& operator=(const QuicheIntrusiveLink&) = delete;
+#endif  // SWIG
+
+ private:
+  // We befriend the matching list type so that it can manipulate the links
+  // while they are kept private from others.
+  friend class QuicheIntrusiveList<T, ListID>;
+
+  // Encapsulates the logic to convert from a link to its derived type.
+  T* cast_to_derived() { return static_cast<T*>(this); }
+  const T* cast_to_derived() const { return static_cast<const T*>(this); }
+
+  QuicheIntrusiveLink* next_;
+  QuicheIntrusiveLink* prev_;
+};
+
+template <typename T, typename ListID = void>
+class QUICHE_EXPORT QuicheIntrusiveList {
+  template <typename QualifiedT, typename QualifiedLinkT>
+  class iterator_impl;
+
+ public:
+  typedef T value_type;
+  typedef value_type* pointer;
+  typedef const value_type* const_pointer;
+  typedef value_type& reference;
+  typedef const value_type& const_reference;
+  typedef size_t size_type;
+  typedef ptrdiff_t difference_type;
+
+  typedef QuicheIntrusiveLink<T, ListID> link_type;
+  typedef iterator_impl<T, link_type> iterator;
+  typedef iterator_impl<const T, const link_type> const_iterator;
+  typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+  typedef std::reverse_iterator<iterator> reverse_iterator;
+
+  QuicheIntrusiveList() { clear(); }
+  // After the move constructor the moved-from list will be empty.
+  //
+  // NOTE: There is no move assign operator (for now).
+  // The reason is that at the moment 'clear()' does not unlink the nodes.
+  // It makes is_linked() return true when it should return false.
+  // If such node is removed from the list (e.g. from its destructor), or is
+  // added to another list - a memory corruption will occur.
+  // Admitedly the destructor does not unlink the nodes either, but move-assign
+  // will likely make the problem more prominent.
+#ifndef SWIG
+  QuicheIntrusiveList(QuicheIntrusiveList&& src) noexcept {
+    clear();
+    if (src.empty()) return;
+    sentinel_link_.next_ = src.sentinel_link_.next_;
+    sentinel_link_.prev_ = src.sentinel_link_.prev_;
+    // Fix head and tail nodes of the list.
+    sentinel_link_.prev_->next_ = &sentinel_link_;
+    sentinel_link_.next_->prev_ = &sentinel_link_;
+    src.clear();
+  }
+#endif  // SWIG
+
+  iterator begin() { return iterator(sentinel_link_.next_); }
+  const_iterator begin() const { return const_iterator(sentinel_link_.next_); }
+  iterator end() { return iterator(&sentinel_link_); }
+  const_iterator end() const { return const_iterator(&sentinel_link_); }
+  reverse_iterator rbegin() { return reverse_iterator(end()); }
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(end());
+  }
+  reverse_iterator rend() { return reverse_iterator(begin()); }
+  const_reverse_iterator rend() const {
+    return const_reverse_iterator(begin());
+  }
+
+  bool empty() const { return (sentinel_link_.next_ == &sentinel_link_); }
+  // This runs in O(N) time.
+  size_type size() const { return std::distance(begin(), end()); }
+  size_type max_size() const { return size_type(-1); }
+
+  reference front() { return *begin(); }
+  const_reference front() const { return *begin(); }
+  reference back() { return *(--end()); }
+  const_reference back() const { return *(--end()); }
+
+  static iterator insert(iterator position, T* obj) {
+    return insert_link(position.link(), obj);
+  }
+  void push_front(T* obj) { insert(begin(), obj); }
+  void push_back(T* obj) { insert(end(), obj); }
+
+  static iterator erase(T* obj) {
+    link_type* obj_link = obj;
+    // Fix up the next and previous links for the previous and next objects.
+    obj_link->next_->prev_ = obj_link->prev_;
+    obj_link->prev_->next_ = obj_link->next_;
+    // Zero out the next and previous links for the removed item. This will
+    // cause any future attempt to remove the item from the list to cause a
+    // crash instead of possibly corrupting the list structure.
+    link_type* next_link = obj_link->next_;
+    obj_link->next_ = nullptr;
+    obj_link->prev_ = nullptr;
+    return iterator(next_link);
+  }
+
+  static iterator erase(iterator position) {
+    return erase(position.operator->());
+  }
+  void pop_front() { erase(begin()); }
+  void pop_back() { erase(--end()); }
+
+  // Check whether the given element is linked into some list. Note that this
+  // does *not* check whether it is linked into a particular list.
+  // Also, if clear() is used to clear the containing list, is_linked() will
+  // still return true even though obj is no longer in any list.
+  static bool is_linked(const T* obj) {
+    return obj->link_type::next_ != nullptr;
+  }
+
+  void clear() {
+    sentinel_link_.next_ = sentinel_link_.prev_ = &sentinel_link_;
+  }
+  void swap(QuicheIntrusiveList& x) {
+    QuicheIntrusiveList tmp;
+    tmp.splice(tmp.begin(), *this);
+    this->splice(this->begin(), x);
+    x.splice(x.begin(), tmp);
+  }
+
+  void splice(iterator pos, QuicheIntrusiveList& src) {
+    splice(pos, src.begin(), src.end());
+  }
+
+  void splice(iterator pos, iterator i) { splice(pos, i, std::next(i)); }
+
+  void splice(iterator pos, iterator first, iterator last) {
+    if (first == last) return;
+
+    link_type* const last_prev = last.link()->prev_;
+
+    // Remove from the source.
+    first.link()->prev_->next_ = last.operator->();
+    last.link()->prev_ = first.link()->prev_;
+
+    // Attach to the destination.
+    first.link()->prev_ = pos.link()->prev_;
+    pos.link()->prev_->next_ = first.operator->();
+    last_prev->next_ = pos.operator->();
+    pos.link()->prev_ = last_prev;
+  }
+
+ private:
+  static iterator insert_link(link_type* next_link, T* obj) {
+    link_type* obj_link = obj;
+    obj_link->next_ = next_link;
+    link_type* const initial_next_prev = next_link->prev_;
+    obj_link->prev_ = initial_next_prev;
+    initial_next_prev->next_ = obj_link;
+    next_link->prev_ = obj_link;
+    return iterator(obj_link);
+  }
+
+  // The iterator implementation is parameterized on a potentially qualified
+  // variant of T and the matching qualified link type. Essentially, QualifiedT
+  // will either be 'T' or 'const T', the latter for a const_iterator.
+  template <typename QualifiedT, typename QualifiedLinkT>
+  class QUICHE_EXPORT iterator_impl {
+   public:
+    using iterator_category = std::bidirectional_iterator_tag;
+    using value_type = QualifiedT;
+    using difference_type = std::ptrdiff_t;
+    using pointer = QualifiedT*;
+    using reference = QualifiedT&;
+
+    iterator_impl() = default;
+    iterator_impl(QualifiedLinkT* link) : link_(link) {}
+    iterator_impl(const iterator_impl& x) = default;
+    iterator_impl& operator=(const iterator_impl& x) = default;
+
+    // Allow converting and comparing across iterators where the pointer
+    // assignment and comparisons (respectively) are allowed.
+    template <typename U, typename V>
+    iterator_impl(const iterator_impl<U, V>& x) : link_(x.link_) {}
+    template <typename U, typename V>
+    bool operator==(const iterator_impl<U, V>& x) const {
+      return link_ == x.link_;
+    }
+    template <typename U, typename V>
+    bool operator!=(const iterator_impl<U, V>& x) const {
+      return link_ != x.link_;
+    }
+
+    reference operator*() const { return *operator->(); }
+    pointer operator->() const { return link_->cast_to_derived(); }
+
+    QualifiedLinkT* link() const { return link_; }
+
+#ifndef SWIG  // SWIG can't wrap these operator overloads.
+    iterator_impl& operator++() {
+      link_ = link_->next_;
+      return *this;
+    }
+    iterator_impl operator++(int /*unused*/) {
+      iterator_impl tmp = *this;
+      ++*this;
+      return tmp;
+    }
+    iterator_impl& operator--() {
+      link_ = link_->prev_;
+      return *this;
+    }
+    iterator_impl operator--(int /*unused*/) {
+      iterator_impl tmp = *this;
+      --*this;
+      return tmp;
+    }
+#endif  // SWIG
+
+   private:
+    // Ensure iterators can access other iterators node directly.
+    template <typename U, typename V>
+    friend class iterator_impl;
+
+    QualifiedLinkT* link_ = nullptr;
+  };
+
+  // This bare link acts as the sentinel node.
+  link_type sentinel_link_;
+
+  // These are private and undefined to prevent copying and assigning.
+  QuicheIntrusiveList(const QuicheIntrusiveList&);
+  void operator=(const QuicheIntrusiveList&);
+};
+
+}  // namespace quiche
+
+#endif  // QUICHE_COMMON_QUICHE_INTRUSIVE_LIST_H_
diff --git a/quiche/common/quiche_intrusive_list_test.cc b/quiche/common/quiche_intrusive_list_test.cc
new file mode 100644
index 0000000..26b4731
--- /dev/null
+++ b/quiche/common/quiche_intrusive_list_test.cc
@@ -0,0 +1,420 @@
+// Copyright (c) 2024 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/quiche_intrusive_list.h"
+
+#include <algorithm>
+#include <iterator>
+#include <list>
+#include <string>
+#include <utility>
+
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace quiche {
+namespace test {
+
+struct ListId2 {};
+
+struct TestItem : public QuicheIntrusiveLink<TestItem>,
+                  public QuicheIntrusiveLink<TestItem, ListId2> {
+  int n;
+};
+typedef QuicheIntrusiveList<TestItem> TestList;
+typedef std::list<TestItem *> CanonicalList;
+
+void swap(TestItem &a, TestItem &b) {
+  using std::swap;
+  swap(a.n, b.n);
+}
+
+class IntrusiveListTest : public quiche::test::QuicheTest {
+ protected:
+  void CheckLists() {
+    CheckLists(l1, ll1);
+    if (quiche::test::QuicheTest::HasFailure()) return;
+    CheckLists(l2, ll2);
+  }
+
+  void CheckLists(const TestList &list_a, const CanonicalList &list_b) {
+    ASSERT_EQ(list_a.size(), list_b.size());
+    TestList::const_iterator it_a = list_a.begin();
+    CanonicalList::const_iterator it_b = list_b.begin();
+    while (it_a != list_a.end()) {
+      EXPECT_EQ(&*it_a++, *it_b++);
+    }
+    EXPECT_EQ(list_a.end(), it_a);
+    EXPECT_EQ(list_b.end(), it_b);
+  }
+
+  void PrepareLists(int num_elems_1, int num_elems_2 = 0) {
+    FillLists(&l1, &ll1, e, num_elems_1);
+    FillLists(&l2, &ll2, e + num_elems_1, num_elems_2);
+  }
+
+  void FillLists(TestList *list_a, CanonicalList *list_b, TestItem *elems,
+                 int num_elems) {
+    list_a->clear();
+    list_b->clear();
+    for (int i = 0; i < num_elems; ++i) {
+      list_a->push_back(elems + i);
+      list_b->push_back(elems + i);
+    }
+    CheckLists(*list_a, *list_b);
+  }
+
+  TestItem e[10];
+  TestList l1, l2;
+  CanonicalList ll1, ll2;
+};
+
+TEST(NewIntrusiveListTest, Basic) {
+  TestList list1;
+
+  EXPECT_EQ(sizeof(QuicheIntrusiveLink<TestItem>), sizeof(void *) * 2);
+
+  for (int i = 0; i < 10; ++i) {
+    TestItem *e = new TestItem;
+    e->n = i;
+    list1.push_front(e);
+  }
+  EXPECT_EQ(list1.size(), 10u);
+
+  // Verify we can reverse a list because we defined swap for TestItem.
+  std::reverse(list1.begin(), list1.end());
+  EXPECT_EQ(list1.size(), 10u);
+
+  // Check both const and non-const forward iteration.
+  const TestList &clist1 = list1;
+  int i = 0;
+  TestList::iterator iter = list1.begin();
+  for (; iter != list1.end(); ++iter, ++i) {
+    EXPECT_EQ(iter->n, i);
+  }
+  EXPECT_EQ(iter, clist1.end());
+  EXPECT_NE(iter, clist1.begin());
+  i = 0;
+  iter = list1.begin();
+  for (; iter != list1.end(); ++iter, ++i) {
+    EXPECT_EQ(iter->n, i);
+  }
+  EXPECT_EQ(iter, clist1.end());
+  EXPECT_NE(iter, clist1.begin());
+
+  EXPECT_EQ(list1.front().n, 0);
+  EXPECT_EQ(list1.back().n, 9);
+
+  // Verify we can swap 2 lists.
+  TestList list2;
+  list2.swap(list1);
+  EXPECT_EQ(list1.size(), 0u);
+  EXPECT_EQ(list2.size(), 10u);
+
+  // Check both const and non-const reverse iteration.
+  const TestList &clist2 = list2;
+  TestList::reverse_iterator riter = list2.rbegin();
+  i = 9;
+  for (; riter != list2.rend(); ++riter, --i) {
+    EXPECT_EQ(riter->n, i);
+  }
+  EXPECT_EQ(riter, clist2.rend());
+  EXPECT_NE(riter, clist2.rbegin());
+
+  riter = list2.rbegin();
+  i = 9;
+  for (; riter != list2.rend(); ++riter, --i) {
+    EXPECT_EQ(riter->n, i);
+  }
+  EXPECT_EQ(riter, clist2.rend());
+  EXPECT_NE(riter, clist2.rbegin());
+
+  while (!list2.empty()) {
+    TestItem *e = &list2.front();
+    list2.pop_front();
+    delete e;
+  }
+}
+
+TEST(NewIntrusiveListTest, Erase) {
+  TestList l;
+  TestItem *e[10];
+
+  // Create a list with 10 items.
+  for (int i = 0; i < 10; ++i) {
+    e[i] = new TestItem;
+    l.push_front(e[i]);
+  }
+
+  // Test that erase works.
+  for (int i = 0; i < 10; ++i) {
+    EXPECT_EQ(l.size(), (10u - i));
+
+    TestList::iterator iter = l.erase(e[i]);
+    EXPECT_NE(iter, TestList::iterator(e[i]));
+
+    EXPECT_EQ(l.size(), (10u - i - 1));
+    delete e[i];
+  }
+}
+
+TEST(NewIntrusiveListTest, Insert) {
+  TestList l;
+  TestList::iterator iter = l.end();
+  TestItem *e[10];
+
+  // Create a list with 10 items.
+  for (int i = 9; i >= 0; --i) {
+    e[i] = new TestItem;
+    iter = l.insert(iter, e[i]);
+    EXPECT_EQ(&(*iter), e[i]);
+  }
+
+  EXPECT_EQ(l.size(), 10u);
+
+  // Verify insertion order.
+  iter = l.begin();
+  for (TestItem *item : e) {
+    EXPECT_EQ(&(*iter), item);
+    iter = l.erase(item);
+    delete item;
+  }
+}
+
+TEST(NewIntrusiveListTest, Move) {
+  // Move contructible.
+
+  {  // Move-construct from an empty list.
+    TestList src;
+    TestList dest(std::move(src));
+    EXPECT_TRUE(dest.empty());
+  }
+
+  {  // Move-construct from a single item list.
+    TestItem e;
+    TestList src;
+    src.push_front(&e);
+
+    TestList dest(std::move(src));
+    EXPECT_TRUE(src.empty());  // NOLINT bugprone-use-after-move
+    ASSERT_THAT(dest.size(), 1);
+    EXPECT_THAT(&dest.front(), &e);
+    EXPECT_THAT(&dest.back(), &e);
+  }
+
+  {  // Move-construct from a list with multiple items.
+    TestItem items[10];
+    TestList src;
+    for (TestItem &e : items) src.push_back(&e);
+
+    TestList dest(std::move(src));
+    EXPECT_TRUE(src.empty());  // NOLINT bugprone-use-after-move
+    // Verify the items on the destination list.
+    ASSERT_THAT(dest.size(), 10);
+    int i = 0;
+    for (TestItem &e : dest) {
+      EXPECT_THAT(&e, &items[i++]) << " for index " << i;
+    }
+  }
+}
+
+TEST(NewIntrusiveListTest, StaticInsertErase) {
+  TestList l;
+  TestItem e[2];
+  TestList::iterator i = l.begin();
+  TestList::insert(i, &e[0]);
+  TestList::insert(&e[0], &e[1]);
+  TestList::erase(&e[0]);
+  TestList::erase(TestList::iterator(&e[1]));
+  EXPECT_TRUE(l.empty());
+}
+
+TEST_F(IntrusiveListTest, Splice) {
+  // We verify that the contents of this secondary list aren't affected by any
+  // of the splices.
+  QuicheIntrusiveList<TestItem, ListId2> secondary_list;
+  for (int i = 0; i < 3; ++i) {
+    secondary_list.push_back(&e[i]);
+  }
+
+  // Test the basic cases:
+  // - The lists range from 0 to 2 elements.
+  // - The insertion point ranges from begin() to end()
+  // - The transfered range has multiple sizes and locations in the source.
+  for (int l1_count = 0; l1_count < 3; ++l1_count) {
+    for (int l2_count = 0; l2_count < 3; ++l2_count) {
+      for (int pos = 0; pos <= l1_count; ++pos) {
+        for (int first = 0; first <= l2_count; ++first) {
+          for (int last = first; last <= l2_count; ++last) {
+            PrepareLists(l1_count, l2_count);
+
+            l1.splice(std::next(l1.begin(), pos), std::next(l2.begin(), first),
+                      std::next(l2.begin(), last));
+            ll1.splice(std::next(ll1.begin(), pos), ll2,
+                       std::next(ll2.begin(), first),
+                       std::next(ll2.begin(), last));
+
+            CheckLists();
+
+            ASSERT_EQ(3u, secondary_list.size());
+            for (int i = 0; i < 3; ++i) {
+              EXPECT_EQ(&e[i], &*std::next(secondary_list.begin(), i));
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// Build up a set of classes which form "challenging" type hierarchies to use
+// with an QuicheIntrusiveList.
+struct BaseLinkId {};
+struct DerivedLinkId {};
+
+struct AbstractBase : public QuicheIntrusiveLink<AbstractBase, BaseLinkId> {
+  virtual ~AbstractBase() = 0;
+  virtual std::string name() { return "AbstractBase"; }
+};
+AbstractBase::~AbstractBase() {}
+struct DerivedClass : public QuicheIntrusiveLink<DerivedClass, DerivedLinkId>,
+                      public AbstractBase {
+  ~DerivedClass() override {}
+  std::string name() override { return "DerivedClass"; }
+};
+struct VirtuallyDerivedBaseClass : public virtual AbstractBase {
+  ~VirtuallyDerivedBaseClass() override = 0;
+  std::string name() override { return "VirtuallyDerivedBaseClass"; }
+};
+VirtuallyDerivedBaseClass::~VirtuallyDerivedBaseClass() {}
+struct VirtuallyDerivedClassA
+    : public QuicheIntrusiveLink<VirtuallyDerivedClassA, DerivedLinkId>,
+      public virtual VirtuallyDerivedBaseClass {
+  ~VirtuallyDerivedClassA() override {}
+  std::string name() override { return "VirtuallyDerivedClassA"; }
+};
+struct NonceClass {
+  virtual ~NonceClass() {}
+  int data_;
+};
+struct VirtuallyDerivedClassB
+    : public QuicheIntrusiveLink<VirtuallyDerivedClassB, DerivedLinkId>,
+      public virtual NonceClass,
+      public virtual VirtuallyDerivedBaseClass {
+  ~VirtuallyDerivedClassB() override {}
+  std::string name() override { return "VirtuallyDerivedClassB"; }
+};
+struct VirtuallyDerivedClassC
+    : public QuicheIntrusiveLink<VirtuallyDerivedClassC, DerivedLinkId>,
+      public virtual AbstractBase,
+      public virtual NonceClass,
+      public virtual VirtuallyDerivedBaseClass {
+  ~VirtuallyDerivedClassC() override {}
+  std::string name() override { return "VirtuallyDerivedClassC"; }
+};
+
+// Test for multiple layers between the element type and the link.
+namespace templated_base_link {
+template <typename T>
+struct AbstractBase : public QuicheIntrusiveLink<T> {
+  virtual ~AbstractBase() = 0;
+};
+template <typename T>
+AbstractBase<T>::~AbstractBase() {}
+struct DerivedClass : public AbstractBase<DerivedClass> {
+  int n;
+};
+}  // namespace templated_base_link
+
+TEST(NewIntrusiveListTest, HandleInheritanceHierarchies) {
+  {
+    QuicheIntrusiveList<DerivedClass, DerivedLinkId> list;
+    DerivedClass elements[2];
+    EXPECT_TRUE(list.empty());
+    list.push_back(&elements[0]);
+    EXPECT_EQ(1u, list.size());
+    list.push_back(&elements[1]);
+    EXPECT_EQ(2u, list.size());
+    list.pop_back();
+    EXPECT_EQ(1u, list.size());
+    list.pop_back();
+    EXPECT_TRUE(list.empty());
+  }
+  {
+    QuicheIntrusiveList<VirtuallyDerivedClassA, DerivedLinkId> list;
+    VirtuallyDerivedClassA elements[2];
+    EXPECT_TRUE(list.empty());
+    list.push_back(&elements[0]);
+    EXPECT_EQ(1u, list.size());
+    list.push_back(&elements[1]);
+    EXPECT_EQ(2u, list.size());
+    list.pop_back();
+    EXPECT_EQ(1u, list.size());
+    list.pop_back();
+    EXPECT_TRUE(list.empty());
+  }
+  {
+    QuicheIntrusiveList<VirtuallyDerivedClassC, DerivedLinkId> list;
+    VirtuallyDerivedClassC elements[2];
+    EXPECT_TRUE(list.empty());
+    list.push_back(&elements[0]);
+    EXPECT_EQ(1u, list.size());
+    list.push_back(&elements[1]);
+    EXPECT_EQ(2u, list.size());
+    list.pop_back();
+    EXPECT_EQ(1u, list.size());
+    list.pop_back();
+    EXPECT_TRUE(list.empty());
+  }
+  {
+    QuicheIntrusiveList<AbstractBase, BaseLinkId> list;
+    DerivedClass d1;
+    VirtuallyDerivedClassA d2;
+    VirtuallyDerivedClassB d3;
+    VirtuallyDerivedClassC d4;
+    EXPECT_TRUE(list.empty());
+    list.push_back(&d1);
+    EXPECT_EQ(1u, list.size());
+    list.push_back(&d2);
+    EXPECT_EQ(2u, list.size());
+    list.push_back(&d3);
+    EXPECT_EQ(3u, list.size());
+    list.push_back(&d4);
+    EXPECT_EQ(4u, list.size());
+    QuicheIntrusiveList<AbstractBase, BaseLinkId>::iterator it = list.begin();
+    EXPECT_EQ("DerivedClass", (it++)->name());
+    EXPECT_EQ("VirtuallyDerivedClassA", (it++)->name());
+    EXPECT_EQ("VirtuallyDerivedClassB", (it++)->name());
+    EXPECT_EQ("VirtuallyDerivedClassC", (it++)->name());
+  }
+  {
+    QuicheIntrusiveList<templated_base_link::DerivedClass> list;
+    templated_base_link::DerivedClass elements[2];
+    EXPECT_TRUE(list.empty());
+    list.push_back(&elements[0]);
+    EXPECT_EQ(1u, list.size());
+    list.push_back(&elements[1]);
+    EXPECT_EQ(2u, list.size());
+    list.pop_back();
+    EXPECT_EQ(1u, list.size());
+    list.pop_back();
+    EXPECT_TRUE(list.empty());
+  }
+}
+
+class IntrusiveListTagTypeTest : public quiche::test::QuicheTest {
+ protected:
+  struct Tag {};
+  class Element : public QuicheIntrusiveLink<Element, Tag> {};
+};
+
+TEST_F(IntrusiveListTagTypeTest, TagTypeListID) {
+  QuicheIntrusiveList<Element, Tag> list;
+  {
+    Element e;
+    list.push_back(&e);
+  }
+}
+
+}  // namespace test
+}  // namespace quiche