aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/Makefile.in1
-rw-r--r--gdb/gdb-gdb.py.in98
-rw-r--r--gdb/gdbthread.h13
-rw-r--r--gdb/inferior.c24
-rw-r--r--gdb/inferior.h14
-rw-r--r--gdb/scoped-mock-context.h4
-rw-r--r--gdb/thread-iter.c53
-rw-r--r--gdb/thread-iter.h5
-rw-r--r--gdb/thread.c61
-rw-r--r--gdb/unittests/intrusive_list-selftests.c734
-rw-r--r--gdbsupport/intrusive_list.h559
-rw-r--r--gdbsupport/reference-to-pointer-iterator.h79
12 files changed, 1561 insertions, 84 deletions
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 53beb3a..2274b9b 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -449,6 +449,7 @@ SELFTESTS_SRCS = \
unittests/function-view-selftests.c \
unittests/gdb_tilde_expand-selftests.c \
unittests/gmp-utils-selftests.c \
+ unittests/intrusive_list-selftests.c \
unittests/lookup_name_info-selftests.c \
unittests/memory-map-selftests.c \
unittests/memrange-selftests.c \
diff --git a/gdb/gdb-gdb.py.in b/gdb/gdb-gdb.py.in
index af9fcfe..15dbf38 100644
--- a/gdb/gdb-gdb.py.in
+++ b/gdb/gdb-gdb.py.in
@@ -276,16 +276,108 @@ class CoreAddrPrettyPrinter:
return hex(int(self._val))
+class IntrusiveListPrinter:
+ """Print a struct intrusive_list."""
+
+ def __init__(self, val):
+ self._val = val
+
+ # Type of linked items.
+ self._item_type = self._val.type.template_argument(0)
+ self._node_ptr_type = gdb.lookup_type(
+ "intrusive_list_node<{}>".format(self._item_type.tag)
+ ).pointer()
+
+ # Type of value -> node converter.
+ self._conv_type = self._val.type.template_argument(1)
+
+ if self._uses_member_node():
+ # The second template argument of intrusive_member_node is a member
+ # pointer value. Its value is the offset of the node member in the
+ # enclosing type.
+ member_node_ptr = self._conv_type.template_argument(1)
+ member_node_ptr = member_node_ptr.cast(gdb.lookup_type("int"))
+ self._member_node_offset = int(member_node_ptr)
+
+ # This is only needed in _as_node_ptr if using a member node. Look it
+ # up here so we only do it once.
+ self._char_ptr_type = gdb.lookup_type("char").pointer()
+
+ def display_hint(self):
+ return "array"
+
+ def _uses_member_node(self):
+ """Return True if the list items use a node as a member, False if
+ they use a node as a base class.
+ """
+
+ if self._conv_type.name.startswith("intrusive_member_node<"):
+ return True
+ elif self._conv_type.name.startswith("intrusive_base_node<"):
+ return False
+ else:
+ raise RuntimeError(
+ "Unexpected intrusive_list value -> node converter type: {}".format(
+ self._conv_type.name
+ )
+ )
+
+ def to_string(self):
+ s = "intrusive list of {}".format(self._item_type)
+
+ if self._uses_member_node():
+ node_member = self._conv_type.template_argument(1)
+ s += ", linked through {}".format(node_member)
+
+ return s
+
+ def _as_node_ptr(self, elem_ptr):
+ """Given ELEM_PTR, a pointer to a list element, return a pointer to the
+ corresponding intrusive_list_node.
+ """
+
+ assert elem_ptr.type.code == gdb.TYPE_CODE_PTR
+
+ if self._uses_member_node():
+ # Node as a member: add the member node offset from to the element's
+ # address to get the member node's address.
+ elem_char_ptr = elem_ptr.cast(self._char_ptr_type)
+ node_char_ptr = elem_char_ptr + self._member_node_offset
+ return node_char_ptr.cast(self._node_ptr_type)
+ else:
+ # Node as a base: just casting from node pointer to item pointer
+ # will adjust the pointer value.
+ return elem_ptr.cast(self._node_ptr_type)
+
+ def _children_generator(self):
+ """Generator that yields one tuple per list item."""
+
+ elem_ptr = self._val["m_front"]
+ idx = 0
+ while elem_ptr != 0:
+ yield (str(idx), elem_ptr.dereference())
+ node_ptr = self._as_node_ptr(elem_ptr)
+ elem_ptr = node_ptr["next"]
+ idx += 1
+
+ def children(self):
+ return self._children_generator()
+
+
def type_lookup_function(val):
"""A routine that returns the correct pretty printer for VAL
if appropriate. Returns None otherwise.
"""
- if val.type.tag == "type":
+ tag = val.type.tag
+ name = val.type.name
+ if tag == "type":
return StructTypePrettyPrinter(val)
- elif val.type.tag == "main_type":
+ elif tag == "main_type":
return StructMainTypePrettyPrinter(val)
- elif val.type.name == "CORE_ADDR":
+ elif name == "CORE_ADDR":
return CoreAddrPrettyPrinter(val)
+ elif tag is not None and tag.startswith("intrusive_list<"):
+ return IntrusiveListPrinter(val)
return None
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index cb3dcc3..1305b20 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -33,6 +33,7 @@ struct symtab;
#include "gdbsupport/common-gdbthread.h"
#include "gdbsupport/forward-scope-exit.h"
#include "displaced-stepping.h"
+#include "gdbsupport/intrusive_list.h"
struct inferior;
struct process_stratum_target;
@@ -222,9 +223,12 @@ struct private_thread_info
delete_thread). All other thread references are considered weak
references. Placing a thread in the thread list is an implicit
strong reference, and is thus not accounted for in the thread's
- refcount. */
+ refcount.
-class thread_info : public refcounted_object
+ The intrusive_list_node base links threads in a per-inferior list. */
+
+class thread_info : public refcounted_object,
+ public intrusive_list_node<thread_info>
{
public:
explicit thread_info (inferior *inf, ptid_t ptid);
@@ -235,7 +239,6 @@ public:
/* Mark this thread as running and notify observers. */
void set_running (bool running);
- struct thread_info *next = NULL;
ptid_t ptid; /* "Actual process id";
In fact, this may be overloaded with
kernel thread id, etc. */
@@ -435,6 +438,10 @@ extern void delete_thread (struct thread_info *thread);
this thread belonged to has already exited, for example. */
extern void delete_thread_silent (struct thread_info *thread);
+/* Mark the thread exited, but don't delete it or remove it from the
+ inferior thread list. */
+extern void set_thread_exited (thread_info *tp, bool silent);
+
/* Delete a step_resume_breakpoint from the thread database. */
extern void delete_step_resume_breakpoint (struct thread_info *);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index e1c70d5..3accf97 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -163,6 +163,19 @@ add_inferior (int pid)
return inf;
}
+/* See inferior.h. */
+
+void
+inferior::clear_thread_list (bool silent)
+{
+ thread_list.clear_and_dispose ([=] (thread_info *thr)
+ {
+ set_thread_exited (thr, silent);
+ if (thr->deletable ())
+ delete thr;
+ });
+}
+
void
delete_inferior (struct inferior *todel)
{
@@ -177,8 +190,7 @@ delete_inferior (struct inferior *todel)
if (!inf)
return;
- for (thread_info *tp : inf->threads_safe ())
- delete_thread_silent (tp);
+ inf->clear_thread_list (true);
if (infprev)
infprev->next = inf->next;
@@ -209,13 +221,7 @@ exit_inferior_1 (struct inferior *inftoex, int silent)
if (!inf)
return;
- for (thread_info *tp : inf->threads_safe ())
- {
- if (silent)
- delete_thread_silent (tp);
- else
- delete_thread (tp);
- }
+ inf->clear_thread_list (silent);
gdb::observers::inferior_exit.notify (inf);
diff --git a/gdb/inferior.h b/gdb/inferior.h
index c63990a..2ae9f9a 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -390,8 +390,8 @@ public:
/* Pointer to next inferior in singly-linked list of inferiors. */
struct inferior *next = NULL;
- /* This inferior's thread list. */
- thread_info *thread_list = nullptr;
+ /* This inferior's thread list, sorted by creation order. */
+ intrusive_list<thread_info> thread_list;
/* Returns a range adapter covering the inferior's threads,
including exited threads. Used like this:
@@ -400,7 +400,7 @@ public:
{ .... }
*/
inf_threads_range threads ()
- { return inf_threads_range (this->thread_list); }
+ { return inf_threads_range (this->thread_list.begin ()); }
/* Returns a range adapter covering the inferior's non-exited
threads. Used like this:
@@ -409,7 +409,7 @@ public:
{ .... }
*/
inf_non_exited_threads_range non_exited_threads ()
- { return inf_non_exited_threads_range (this->thread_list); }
+ { return inf_non_exited_threads_range (this->thread_list.begin ()); }
/* Like inferior::threads(), but returns a range adapter that can be
used with range-for, safely. I.e., it is safe to delete the
@@ -420,7 +420,11 @@ public:
delete f;
*/
inline safe_inf_threads_range threads_safe ()
- { return safe_inf_threads_range (this->thread_list); }
+ { return safe_inf_threads_range (this->thread_list.begin ()); }
+
+ /* Delete all threads in the thread list. If SILENT, exit threads
+ silently. */
+ void clear_thread_list (bool silent);
/* Continuations-related methods. A continuation is an std::function
to be called to finish the execution of a command when running
diff --git a/gdb/scoped-mock-context.h b/gdb/scoped-mock-context.h
index 8d295ba..37ffe51 100644
--- a/gdb/scoped-mock-context.h
+++ b/gdb/scoped-mock-context.h
@@ -44,9 +44,6 @@ struct scoped_mock_context
scoped_restore_current_pspace_and_thread restore_pspace_thread;
- scoped_restore_tmpl<thread_info *> restore_thread_list
- {&mock_inferior.thread_list, &mock_thread};
-
/* Add the mock inferior to the inferior list so that look ups by
target+ptid can find it. */
scoped_restore_tmpl<inferior *> restore_inferior_list
@@ -54,6 +51,7 @@ struct scoped_mock_context
explicit scoped_mock_context (gdbarch *gdbarch)
{
+ mock_inferior.thread_list.push_back (mock_thread);
mock_inferior.gdbarch = gdbarch;
mock_inferior.aspace = mock_pspace.aspace;
mock_inferior.pspace = &mock_pspace;
diff --git a/gdb/thread-iter.c b/gdb/thread-iter.c
index 012ca5f..a1cdd02 100644
--- a/gdb/thread-iter.c
+++ b/gdb/thread-iter.c
@@ -27,8 +27,15 @@ all_threads_iterator::all_threads_iterator (begin_t)
{
/* Advance M_INF/M_THR to the first thread's position. */
for (m_inf = inferior_list; m_inf != NULL; m_inf = m_inf->next)
- if ((m_thr = m_inf->thread_list) != NULL)
- return;
+ {
+ auto thr_iter = m_inf->thread_list.begin ();
+ if (thr_iter != m_inf->thread_list.end ())
+ {
+ m_thr = &*thr_iter;
+ return;
+ }
+ }
+ m_thr = nullptr;
}
/* See thread-iter.h. */
@@ -36,6 +43,8 @@ all_threads_iterator::all_threads_iterator (begin_t)
void
all_threads_iterator::advance ()
{
+ intrusive_list<thread_info>::iterator thr_iter (m_thr);
+
/* The loop below is written in the natural way as-if we'd always
start at the beginning of the inferior list. This fast forwards
the algorithm to the actual current position. */
@@ -43,14 +52,17 @@ all_threads_iterator::advance ()
for (; m_inf != NULL; m_inf = m_inf->next)
{
- m_thr = m_inf->thread_list;
- while (m_thr != NULL)
+ thr_iter = m_inf->thread_list.begin ();
+ while (thr_iter != m_inf->thread_list.end ())
{
+ m_thr = &*thr_iter;
return;
start:
- m_thr = m_thr->next;
+ ++thr_iter;
}
}
+
+ m_thr = nullptr;
}
/* See thread-iter.h. */
@@ -74,12 +86,18 @@ all_matching_threads_iterator::all_matching_threads_iterator
gdb_assert ((filter_target == nullptr && filter_ptid == minus_one_ptid)
|| filter_target->stratum () == process_stratum);
- m_thr = nullptr;
for (m_inf = inferior_list; m_inf != NULL; m_inf = m_inf->next)
if (m_inf_matches ())
- for (m_thr = m_inf->thread_list; m_thr != NULL; m_thr = m_thr->next)
- if (m_thr->ptid.matches (m_filter_ptid))
- return;
+ for (auto thr_iter = m_inf->thread_list.begin ();
+ thr_iter != m_inf->thread_list.end ();
+ ++thr_iter)
+ if (thr_iter->ptid.matches (m_filter_ptid))
+ {
+ m_thr = &*thr_iter;
+ return;
+ }
+
+ m_thr = nullptr;
}
/* See thread-iter.h. */
@@ -87,6 +105,8 @@ all_matching_threads_iterator::all_matching_threads_iterator
void
all_matching_threads_iterator::advance ()
{
+ intrusive_list<thread_info>::iterator thr_iter (m_thr);
+
/* The loop below is written in the natural way as-if we'd always
start at the beginning of the inferior list. This fast forwards
the algorithm to the actual current position. */
@@ -95,13 +115,18 @@ all_matching_threads_iterator::advance ()
for (; m_inf != NULL; m_inf = m_inf->next)
if (m_inf_matches ())
{
- m_thr = m_inf->thread_list;
- while (m_thr != NULL)
+ thr_iter = m_inf->thread_list.begin ();
+ while (thr_iter != m_inf->thread_list.end ())
{
- if (m_thr->ptid.matches (m_filter_ptid))
- return;
+ if (thr_iter->ptid.matches (m_filter_ptid))
+ {
+ m_thr = &*thr_iter;
+ return;
+ }
start:
- m_thr = m_thr->next;
+ ++thr_iter;
}
}
+
+ m_thr = nullptr;
}
diff --git a/gdb/thread-iter.h b/gdb/thread-iter.h
index 098af0f..2e43034 100644
--- a/gdb/thread-iter.h
+++ b/gdb/thread-iter.h
@@ -20,13 +20,16 @@
#define THREAD_ITER_H
#include "gdbsupport/filtered-iterator.h"
+#include "gdbsupport/iterator-range.h"
#include "gdbsupport/next-iterator.h"
+#include "gdbsupport/reference-to-pointer-iterator.h"
#include "gdbsupport/safe-iterator.h"
/* A forward iterator that iterates over a given inferior's
threads. */
-using inf_threads_iterator = next_iterator<thread_info>;
+using inf_threads_iterator
+ = reference_to_pointer_iterator<intrusive_list<thread_info>::iterator>;
/* A forward iterator that iterates over all threads of all
inferiors. */
diff --git a/gdb/thread.c b/gdb/thread.c
index f850f05..89f51c0 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -177,9 +177,9 @@ clear_thread_inferior_resources (struct thread_info *tp)
clear_inline_frame_state (tp);
}
-/* Set the TP's state as exited. */
+/* See gdbthread.h. */
-static void
+void
set_thread_exited (thread_info *tp, bool silent)
{
/* Dead threads don't need to step-over. Remove from chain. */
@@ -203,17 +203,8 @@ init_thread_list (void)
{
highest_thread_num = 0;
- for (thread_info *tp : all_threads_safe ())
- {
- inferior *inf = tp->inf;
-
- if (tp->deletable ())
- delete tp;
- else
- set_thread_exited (tp, 1);
-
- inf->thread_list = NULL;
- }
+ for (inferior *inf : all_inferiors ())
+ inf->clear_thread_list (true);
}
/* Allocate a new thread of inferior INF with target id PTID and add
@@ -224,21 +215,7 @@ new_thread (struct inferior *inf, ptid_t ptid)
{
thread_info *tp = new thread_info (inf, ptid);
- if (inf->thread_list == NULL)
- inf->thread_list = tp;
- else
- {
- struct thread_info *last;
-
- for (last = inf->thread_list; last->next != NULL; last = last->next)
- gdb_assert (ptid != last->ptid
- || last->state == THREAD_EXITED);
-
- gdb_assert (ptid != last->ptid
- || last->state == THREAD_EXITED);
-
- last->next = tp;
- }
+ inf->thread_list.push_back (*tp);
return tp;
}
@@ -462,29 +439,18 @@ delete_thread_1 (thread_info *thr, bool silent)
{
gdb_assert (thr != nullptr);
- struct thread_info *tp, *tpprev = NULL;
-
- for (tp = thr->inf->thread_list; tp; tpprev = tp, tp = tp->next)
- if (tp == thr)
- break;
+ set_thread_exited (thr, silent);
- if (!tp)
- return;
-
- set_thread_exited (tp, silent);
-
- if (!tp->deletable ())
+ if (!thr->deletable ())
{
/* Will be really deleted some other time. */
return;
}
- if (tpprev)
- tpprev->next = tp->next;
- else
- tp->inf->thread_list = tp->next;
+ auto it = thr->inf->thread_list.iterator_to (*thr);
+ thr->inf->thread_list.erase (it);
- delete tp;
+ delete thr;
}
/* See gdbthread.h. */
@@ -629,7 +595,10 @@ in_thread_list (process_stratum_target *targ, ptid_t ptid)
thread_info *
first_thread_of_inferior (inferior *inf)
{
- return inf->thread_list;
+ if (inf->thread_list.empty ())
+ return nullptr;
+
+ return &inf->thread_list.front ();
}
thread_info *
@@ -2018,7 +1987,7 @@ update_threads_executing (void)
/* If the process has no threads, then it must be we have a
process-exit event pending. */
- if (inf->thread_list == NULL)
+ if (inf->thread_list.empty ())
{
targ->threads_executing = true;
return;
diff --git a/gdb/unittests/intrusive_list-selftests.c b/gdb/unittests/intrusive_list-selftests.c
new file mode 100644
index 0000000..5497a01
--- /dev/null
+++ b/gdb/unittests/intrusive_list-selftests.c
@@ -0,0 +1,734 @@
+/* Tests fpr intrusive double linked list for GDB, the GNU debugger.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "defs.h"
+
+#include "gdbsupport/intrusive_list.h"
+#include "gdbsupport/selftest.h"
+#include <unordered_set>
+
+/* An item type using intrusive_list_node by inheriting from it and its
+ corresponding list type. Put another base before intrusive_list_node
+ so that a pointer to the node != a pointer to the item. */
+
+struct other_base
+{
+ int n = 1;
+};
+
+struct item_with_base : public other_base,
+ public intrusive_list_node<item_with_base>
+{
+ explicit item_with_base (const char *name)
+ : name (name)
+ {}
+
+ const char *const name;
+};
+
+using item_with_base_list = intrusive_list<item_with_base>;
+
+/* An item type using intrusive_list_node as a field and its corresponding
+ list type. Put the other field before the node, so that a pointer to the
+ node != a pointer to the item. */
+
+struct item_with_member
+{
+ explicit item_with_member (const char *name)
+ : name (name)
+ {}
+
+ const char *const name;
+ intrusive_list_node<item_with_member> node;
+};
+
+using item_with_member_node
+ = intrusive_member_node<item_with_member, &item_with_member::node>;
+using item_with_member_list
+ = intrusive_list<item_with_member, item_with_member_node>;
+
+/* To run all tests using both the base and member methods, all tests are
+ declared in this templated class, which is instantiated once for each
+ list type. */
+
+template <typename ListType>
+struct intrusive_list_test
+{
+ using item_type = typename ListType::value_type;
+
+ /* Verify that LIST contains exactly the items in EXPECTED.
+
+ Traverse the list forward and backwards to exercise all links. */
+
+ static void
+ verify_items (const ListType &list,
+ gdb::array_view<const typename ListType::value_type *> expected)
+ {
+ int i = 0;
+
+ for (typename ListType::iterator it = list.begin ();
+ it != list.end ();
+ ++it)
+ {
+ const item_type &item = *it;
+
+ gdb_assert (i < expected.size ());
+ gdb_assert (&item == expected[i]);
+
+ ++i;
+ }
+
+ gdb_assert (i == expected.size ());
+
+ for (typename ListType::reverse_iterator it = list.rbegin ();
+ it != list.rend ();
+ ++it)
+ {
+ const item_type &item = *it;
+
+ --i;
+
+ gdb_assert (i >= 0);
+ gdb_assert (&item == expected[i]);
+ }
+
+ gdb_assert (i == 0);
+ }
+
+ static void
+ test_move_constructor ()
+ {
+ {
+ /* Other list is not empty. */
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list1;
+ std::vector<const item_type *> expected;
+
+ list1.push_back (a);
+ list1.push_back (b);
+ list1.push_back (c);
+
+ ListType list2 (std::move (list1));
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {&a, &b, &c};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* Other list contains 1 element. */
+ item_type a ("a");
+ ListType list1;
+ std::vector<const item_type *> expected;
+
+ list1.push_back (a);
+
+ ListType list2 (std::move (list1));
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {&a};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* Other list is empty. */
+ ListType list1;
+ std::vector<const item_type *> expected;
+
+ ListType list2 (std::move (list1));
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {};
+ verify_items (list2, expected);
+ }
+ }
+
+ static void
+ test_move_assignment ()
+ {
+ {
+ /* Both lists are not empty. */
+ item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e");
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list1.push_back (a);
+ list1.push_back (b);
+ list1.push_back (c);
+
+ list2.push_back (d);
+ list2.push_back (e);
+
+ list2 = std::move (list1);
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {&a, &b, &c};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* rhs list is empty. */
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list2.push_back (a);
+ list2.push_back (b);
+ list2.push_back (c);
+
+ list2 = std::move (list1);
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* lhs list is empty. */
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list1.push_back (a);
+ list1.push_back (b);
+ list1.push_back (c);
+
+ list2 = std::move (list1);
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {&a, &b, &c};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* Both lists contain 1 item. */
+ item_type a ("a"), b ("b");
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list1.push_back (a);
+ list2.push_back (b);
+
+ list2 = std::move (list1);
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {&a};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* Both lists are empty. */
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list2 = std::move (list1);
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {};
+ verify_items (list2, expected);
+ }
+ }
+
+ static void
+ test_swap ()
+ {
+ {
+ /* Two non-empty lists. */
+ item_type a ("a"), b ("b"), c ("c"), d ("d"), e ("e");
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list1.push_back (a);
+ list1.push_back (b);
+ list1.push_back (c);
+
+ list2.push_back (d);
+ list2.push_back (e);
+
+ std::swap (list1, list2);
+
+ expected = {&d, &e};
+ verify_items (list1, expected);
+
+ expected = {&a, &b, &c};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* Other is empty. */
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list1.push_back (a);
+ list1.push_back (b);
+ list1.push_back (c);
+
+ std::swap (list1, list2);
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {&a, &b, &c};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* *this is empty. */
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list2.push_back (a);
+ list2.push_back (b);
+ list2.push_back (c);
+
+ std::swap (list1, list2);
+
+ expected = {&a, &b, &c};
+ verify_items (list1, expected);
+
+ expected = {};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* Both lists empty. */
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ std::swap (list1, list2);
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {};
+ verify_items (list2, expected);
+ }
+
+ {
+ /* Swap one element twice. */
+ item_type a ("a");
+ ListType list1;
+ ListType list2;
+ std::vector<const item_type *> expected;
+
+ list1.push_back (a);
+
+ std::swap (list1, list2);
+
+ expected = {};
+ verify_items (list1, expected);
+
+ expected = {&a};
+ verify_items (list2, expected);
+
+ std::swap (list1, list2);
+
+ expected = {&a};
+ verify_items (list1, expected);
+
+ expected = {};
+ verify_items (list2, expected);
+ }
+ }
+
+ static void
+ test_front_back ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ const ListType &clist = list;
+
+ list.push_back (a);
+ list.push_back (b);
+ list.push_back (c);
+
+ gdb_assert (&list.front () == &a);
+ gdb_assert (&clist.front () == &a);
+ gdb_assert (&list.back () == &c);
+ gdb_assert (&clist.back () == &c);
+ }
+
+ static void
+ test_push_front ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ std::vector<const item_type *> expected;
+
+ expected = {};
+ verify_items (list, expected);
+
+ list.push_front (a);
+ expected = {&a};
+ verify_items (list, expected);
+
+ list.push_front (b);
+ expected = {&b, &a};
+ verify_items (list, expected);
+
+ list.push_front (c);
+ expected = {&c, &b, &a};
+ verify_items (list, expected);
+ }
+
+ static void
+ test_push_back ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ std::vector<const item_type *> expected;
+
+ expected = {};
+ verify_items (list, expected);
+
+ list.push_back (a);
+ expected = {&a};
+ verify_items (list, expected);
+
+ list.push_back (b);
+ expected = {&a, &b};
+ verify_items (list, expected);
+
+ list.push_back (c);
+ expected = {&a, &b, &c};
+ verify_items (list, expected);
+ }
+
+ static void
+ test_insert ()
+ {
+ std::vector<const item_type *> expected;
+
+ {
+ /* Insert at beginning. */
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+
+
+ list.insert (list.begin (), a);
+ expected = {&a};
+ verify_items (list, expected);
+
+ list.insert (list.begin (), b);
+ expected = {&b, &a};
+ verify_items (list, expected);
+
+ list.insert (list.begin (), c);
+ expected = {&c, &b, &a};
+ verify_items (list, expected);
+ }
+
+ {
+ /* Insert at end. */
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+
+
+ list.insert (list.end (), a);
+ expected = {&a};
+ verify_items (list, expected);
+
+ list.insert (list.end (), b);
+ expected = {&a, &b};
+ verify_items (list, expected);
+
+ list.insert (list.end (), c);
+ expected = {&a, &b, &c};
+ verify_items (list, expected);
+ }
+
+ {
+ /* Insert in the middle. */
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+
+ list.push_back (a);
+ list.push_back (b);
+
+ list.insert (list.iterator_to (b), c);
+ expected = {&a, &c, &b};
+ verify_items (list, expected);
+ }
+
+ {
+ /* Insert in empty list. */
+ item_type a ("a");
+ ListType list;
+
+ list.insert (list.end (), a);
+ expected = {&a};
+ verify_items (list, expected);
+ }
+ }
+
+ static void
+ test_pop_front ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ std::vector<const item_type *> expected;
+
+ list.push_back (a);
+ list.push_back (b);
+ list.push_back (c);
+
+ list.pop_front ();
+ expected = {&b, &c};
+ verify_items (list, expected);
+
+ list.pop_front ();
+ expected = {&c};
+ verify_items (list, expected);
+
+ list.pop_front ();
+ expected = {};
+ verify_items (list, expected);
+ }
+
+ static void
+ test_pop_back ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ std::vector<const item_type *> expected;
+
+ list.push_back (a);
+ list.push_back (b);
+ list.push_back (c);
+
+ list.pop_back();
+ expected = {&a, &b};
+ verify_items (list, expected);
+
+ list.pop_back ();
+ expected = {&a};
+ verify_items (list, expected);
+
+ list.pop_back ();
+ expected = {};
+ verify_items (list, expected);
+ }
+
+ static void
+ test_erase ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ std::vector<const item_type *> expected;
+
+ list.push_back (a);
+ list.push_back (b);
+ list.push_back (c);
+
+ list.erase (list.iterator_to (b));
+ expected = {&a, &c};
+ verify_items (list, expected);
+
+ list.erase (list.iterator_to (c));
+ expected = {&a};
+ verify_items (list, expected);
+
+ list.erase (list.iterator_to (a));
+ expected = {};
+ verify_items (list, expected);
+ }
+
+ static void
+ test_clear ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ std::vector<const item_type *> expected;
+
+ list.push_back (a);
+ list.push_back (b);
+ list.push_back (c);
+
+ list.clear ();
+ expected = {};
+ verify_items (list, expected);
+
+ /* Verify idempotency. */
+ list.clear ();
+ expected = {};
+ verify_items (list, expected);
+ }
+
+ static void
+ test_clear_and_dispose ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ std::vector<const item_type *> expected;
+ std::unordered_set<const item_type *> disposer_seen;
+ int disposer_calls = 0;
+
+ list.push_back (a);
+ list.push_back (b);
+ list.push_back (c);
+
+ auto disposer = [&] (const item_type *item)
+ {
+ disposer_seen.insert (item);
+ disposer_calls++;
+ };
+ list.clear_and_dispose (disposer);
+
+ expected = {};
+ verify_items (list, expected);
+ gdb_assert (disposer_calls == 3);
+ gdb_assert (disposer_seen.find (&a) != disposer_seen.end ());
+ gdb_assert (disposer_seen.find (&b) != disposer_seen.end ());
+ gdb_assert (disposer_seen.find (&c) != disposer_seen.end ());
+
+ /* Verify idempotency. */
+ list.clear_and_dispose (disposer);
+ gdb_assert (disposer_calls == 3);
+ }
+
+ static void
+ test_empty ()
+ {
+ item_type a ("a");
+ ListType list;
+
+ gdb_assert (list.empty ());
+ list.push_back (a);
+ gdb_assert (!list.empty ());
+ list.erase (list.iterator_to (a));
+ gdb_assert (list.empty ());
+ }
+
+ static void
+ test_begin_end ()
+ {
+ item_type a ("a"), b ("b"), c ("c");
+ ListType list;
+ const ListType &clist = list;
+
+ list.push_back (a);
+ list.push_back (b);
+ list.push_back (c);
+
+ gdb_assert (&*list.begin () == &a);
+ gdb_assert (&*list.cbegin () == &a);
+ gdb_assert (&*clist.begin () == &a);
+ gdb_assert (&*list.rbegin () == &c);
+ gdb_assert (&*list.crbegin () == &c);
+ gdb_assert (&*clist.rbegin () == &c);
+
+ /* At least check that they compile. */
+ list.end ();
+ list.cend ();
+ clist.end ();
+ list.rend ();
+ list.crend ();
+ clist.end ();
+ }
+};
+
+template <typename ListType>
+static void
+test_intrusive_list ()
+{
+ intrusive_list_test<ListType> tests;
+
+ tests.test_move_constructor ();
+ tests.test_move_assignment ();
+ tests.test_swap ();
+ tests.test_front_back ();
+ tests.test_push_front ();
+ tests.test_push_back ();
+ tests.test_insert ();
+ tests.test_pop_front ();
+ tests.test_pop_back ();
+ tests.test_erase ();
+ tests.test_clear ();
+ tests.test_clear_and_dispose ();
+ tests.test_empty ();
+ tests.test_begin_end ();
+}
+
+static void
+test_node_is_linked ()
+{
+ {
+ item_with_base a ("a");
+ item_with_base_list list;
+
+ gdb_assert (!a.is_linked ());
+ list.push_back (a);
+ gdb_assert (a.is_linked ());
+ list.pop_back ();
+ gdb_assert (!a.is_linked ());
+ }
+
+ {
+ item_with_member a ("a");
+ item_with_member_list list;
+
+ gdb_assert (!a.node.is_linked ());
+ list.push_back (a);
+ gdb_assert (a.node.is_linked ());
+ list.pop_back ();
+ gdb_assert (!a.node.is_linked ());
+ }
+}
+
+static void
+test_intrusive_list ()
+{
+ test_intrusive_list<item_with_base_list> ();
+ test_intrusive_list<item_with_member_list> ();
+ test_node_is_linked ();
+}
+
+void _initialize_intrusive_list_selftests ();
+void
+_initialize_intrusive_list_selftests ()
+{
+ selftests::register_test
+ ("intrusive_list", test_intrusive_list);
+}
diff --git a/gdbsupport/intrusive_list.h b/gdbsupport/intrusive_list.h
new file mode 100644
index 0000000..8d49ce4
--- /dev/null
+++ b/gdbsupport/intrusive_list.h
@@ -0,0 +1,559 @@
+/* Intrusive double linked list for GDB, the GNU debugger.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef GDBSUPPORT_INTRUSIVE_LIST_H
+#define GDBSUPPORT_INTRUSIVE_LIST_H
+
+#define INTRUSIVE_LIST_UNLINKED_VALUE ((T *) -1)
+
+/* A list node. The elements put in an intrusive_list either inherit
+ from this, or have a field of this type. */
+template<typename T>
+struct intrusive_list_node
+{
+ bool is_linked () const
+ {
+ return next != INTRUSIVE_LIST_UNLINKED_VALUE;
+ }
+
+ T *next = INTRUSIVE_LIST_UNLINKED_VALUE;
+ T *prev = INTRUSIVE_LIST_UNLINKED_VALUE;
+};
+
+/* Follows a couple types used by intrusive_list as template parameter to find
+ the intrusive_list_node for a given element. One for lists where the
+ elements inherit intrusive_list_node, and another for elements that keep the
+ node as member field. */
+
+/* For element types that inherit from intrusive_list_node. */
+
+template<typename T>
+struct intrusive_base_node
+{
+ static intrusive_list_node<T> *as_node (T *elem)
+ { return elem; }
+};
+
+/* For element types that keep the node as member field. */
+
+template<typename T, intrusive_list_node<T> T::*MemberNode>
+struct intrusive_member_node
+{
+ static intrusive_list_node<T> *as_node (T *elem)
+ { return &(elem->*MemberNode); }
+};
+
+/* Common code for forward and reverse iterators. */
+
+template<typename T, typename AsNode, typename SelfType>
+struct intrusive_list_base_iterator
+{
+ using self_type = SelfType;
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type = T;
+ using pointer = T *;
+ using const_pointer = const T *;
+ using reference = T &;
+ using const_reference = const T &;
+ using difference_type = ptrdiff_t;
+ using size_type = size_t;
+ using node_type = intrusive_list_node<T>;
+
+ /* Create an iterator pointing to ELEM. */
+ explicit intrusive_list_base_iterator (T *elem)
+ : m_elem (elem)
+ {}
+
+ /* Create a past-the-end iterator. */
+ intrusive_list_base_iterator ()
+ : m_elem (nullptr)
+ {}
+
+ reference operator* () const
+ { return *m_elem; }
+
+ pointer operator-> () const
+ { return m_elem; }
+
+ bool operator== (const self_type &other) const
+ { return m_elem == other.m_elem; }
+
+ bool operator!= (const self_type &other) const
+ { return m_elem != other.m_elem; }
+
+protected:
+ static node_type *as_node (T *elem)
+ { return AsNode::as_node (elem); }
+
+ /* A past-end-the iterator points to the list's head. */
+ pointer m_elem;
+};
+
+/* Forward iterator for an intrusive_list. */
+
+template<typename T, typename AsNode = intrusive_base_node<T>>
+struct intrusive_list_iterator
+ : public intrusive_list_base_iterator
+ <T, AsNode, intrusive_list_iterator<T, AsNode>>
+{
+ using base = intrusive_list_base_iterator
+ <T, AsNode, intrusive_list_iterator<T, AsNode>>;
+ using self_type = typename base::self_type;
+ using node_type = typename base::node_type;
+
+ /* Inherit constructor and M_NODE visibility from base. */
+ using base::base;
+ using base::m_elem;
+
+ self_type &operator++ ()
+ {
+ node_type *node = this->as_node (m_elem);
+ m_elem = node->next;
+ return *this;
+ }
+
+ self_type operator++ (int)
+ {
+ self_type temp = *this;
+ node_type *node = this->as_node (m_elem);
+ m_elem = node->next;
+ return temp;
+ }
+
+ self_type &operator-- ()
+ {
+ node_type *node = this->as_node (m_elem);
+ m_elem = node->prev;
+ return *this;
+ }
+
+ self_type operator-- (int)
+ {
+ self_type temp = *this;
+ node_type *node = this->as_node (m_elem);
+ m_elem = node->prev;
+ return temp;
+ }
+};
+
+/* Reverse iterator for an intrusive_list. */
+
+template<typename T, typename AsNode = intrusive_base_node<T>>
+struct intrusive_list_reverse_iterator
+ : public intrusive_list_base_iterator
+ <T, AsNode, intrusive_list_reverse_iterator<T, AsNode>>
+{
+ using base = intrusive_list_base_iterator
+ <T, AsNode, intrusive_list_reverse_iterator<T, AsNode>>;
+ using self_type = typename base::self_type;
+
+ /* Inherit constructor and M_NODE visibility from base. */
+ using base::base;
+ using base::m_elem;
+ using node_type = typename base::node_type;
+
+ self_type &operator++ ()
+ {
+ node_type *node = this->as_node (m_elem);
+ m_elem = node->prev;
+ return *this;
+ }
+
+ self_type operator++ (int)
+ {
+ self_type temp = *this;
+ node_type *node = this->as_node (m_elem);
+ m_elem = node->prev;
+ return temp;
+ }
+
+ self_type &operator-- ()
+ {
+ node_type *node = this->as_node (m_elem);
+ m_elem = node->next;
+ return *this;
+ }
+
+ self_type operator-- (int)
+ {
+ self_type temp = *this;
+ node_type *node = this->as_node (m_elem);
+ m_elem = node->next;
+ return temp;
+ }
+};
+
+/* An intrusive double-linked list.
+
+ T is the type of the elements to link. The type T must either:
+
+ - inherit from intrusive_list_node<T>
+ - have an intrusive_list_node<T> member
+
+ AsNode is a type with an as_node static method used to get a node from an
+ element. If elements inherit from intrusive_list_node<T>, use the default
+ intrusive_base_node<T>. If elements have an intrusive_list_node<T> member,
+ use:
+
+ intrusive_member_node<T, &T::member>
+
+ where `member` is the name of the member. */
+
+template <typename T, typename AsNode = intrusive_base_node<T>>
+class intrusive_list
+{
+public:
+ using value_type = T;
+ using pointer = T *;
+ using const_pointer = const T *;
+ using reference = T &;
+ using const_reference = const T &;
+ using difference_type = ptrdiff_t;
+ using size_type = size_t;
+ using iterator = intrusive_list_iterator<T, AsNode>;
+ using reverse_iterator = intrusive_list_reverse_iterator<T, AsNode>;
+ using const_iterator = const intrusive_list_iterator<T, AsNode>;
+ using const_reverse_iterator
+ = const intrusive_list_reverse_iterator<T, AsNode>;
+ using node_type = intrusive_list_node<T>;
+
+ intrusive_list () = default;
+
+ ~intrusive_list ()
+ {
+ clear ();
+ }
+
+ intrusive_list (intrusive_list &&other)
+ : m_front (other.m_front),
+ m_back (other.m_back)
+ {
+ other.m_front = nullptr;
+ other.m_back = nullptr;
+ }
+
+ intrusive_list &operator= (intrusive_list &&other)
+ {
+ m_front = other.m_front;
+ m_back = other.m_back;
+ other.m_front = nullptr;
+ other.m_back = nullptr;
+
+ return *this;
+ }
+
+ void swap (intrusive_list &other)
+ {
+ std::swap (m_front, other.m_front);
+ std::swap (m_back, other.m_back);
+ }
+
+ iterator iterator_to (reference value)
+ {
+ return iterator (&value);
+ }
+
+ const_iterator iterator_to (const_reference value)
+ {
+ return const_iterator (&value);
+ }
+
+ reference front ()
+ {
+ gdb_assert (!this->empty ());
+ return *m_front;
+ }
+
+ const_reference front () const
+ {
+ gdb_assert (!this->empty ());
+ return *m_front;
+ }
+
+ reference back ()
+ {
+ gdb_assert (!this->empty ());
+ return *m_back;
+ }
+
+ const_reference back () const
+ {
+ gdb_assert (!this->empty ());
+ return *m_back;
+ }
+
+ void push_front (reference elem)
+ {
+ intrusive_list_node<T> *elem_node = as_node (&elem);
+
+ gdb_assert (elem_node->next == INTRUSIVE_LIST_UNLINKED_VALUE);
+ gdb_assert (elem_node->prev == INTRUSIVE_LIST_UNLINKED_VALUE);
+
+ if (this->empty ())
+ this->push_empty (elem);
+ else
+ this->push_front_non_empty (elem);
+ }
+
+ void push_back (reference elem)
+ {
+ intrusive_list_node<T> *elem_node = as_node (&elem);
+
+ gdb_assert (elem_node->next == INTRUSIVE_LIST_UNLINKED_VALUE);
+ gdb_assert (elem_node->prev == INTRUSIVE_LIST_UNLINKED_VALUE);
+
+ if (this->empty ())
+ this->push_empty (elem);
+ else
+ this->push_back_non_empty (elem);
+ }
+
+ /* Inserts ELEM before POS. */
+ void insert (const_iterator pos, reference elem)
+ {
+ if (this->empty ())
+ return this->push_empty (elem);
+
+ if (pos == this->begin ())
+ return this->push_front_non_empty (elem);
+
+ if (pos == this->end ())
+ return this->push_back_non_empty (elem);
+
+ intrusive_list_node<T> *elem_node = as_node (&elem);
+ T *pos_elem = &*pos;
+ intrusive_list_node<T> *pos_node = as_node (pos_elem);
+ T *prev_elem = pos_node->prev;
+ intrusive_list_node<T> *prev_node = as_node (prev_elem);
+
+ gdb_assert (elem_node->next == INTRUSIVE_LIST_UNLINKED_VALUE);
+ gdb_assert (elem_node->prev == INTRUSIVE_LIST_UNLINKED_VALUE);
+
+ elem_node->prev = prev_elem;
+ prev_node->next = &elem;
+ elem_node->next = pos_elem;
+ pos_node->prev = &elem;
+ }
+
+ void pop_front ()
+ {
+ gdb_assert (!this->empty ());
+ erase_element (*m_front);
+ }
+
+ void pop_back ()
+ {
+ gdb_assert (!this->empty ());
+ erase_element (*m_back);
+ }
+
+private:
+ /* Push ELEM in the list, knowing the list is empty. */
+ void push_empty (T &elem)
+ {
+ gdb_assert (this->empty ());
+
+ intrusive_list_node<T> *elem_node = as_node (&elem);
+
+ gdb_assert (elem_node->next == INTRUSIVE_LIST_UNLINKED_VALUE);
+ gdb_assert (elem_node->prev == INTRUSIVE_LIST_UNLINKED_VALUE);
+
+ m_front = &elem;
+ m_back = &elem;
+ elem_node->prev = nullptr;
+ elem_node->next = nullptr;
+ }
+
+ /* Push ELEM at the front of the list, knowing the list is not empty. */
+ void push_front_non_empty (T &elem)
+ {
+ gdb_assert (!this->empty ());
+
+ intrusive_list_node<T> *elem_node = as_node (&elem);
+ intrusive_list_node<T> *front_node = as_node (m_front);
+
+ gdb_assert (elem_node->next == INTRUSIVE_LIST_UNLINKED_VALUE);
+ gdb_assert (elem_node->prev == INTRUSIVE_LIST_UNLINKED_VALUE);
+
+ elem_node->next = m_front;
+ front_node->prev = &elem;
+ elem_node->prev = nullptr;
+ m_front = &elem;
+ }
+
+ /* Push ELEM at the back of the list, knowing the list is not empty. */
+ void push_back_non_empty (T &elem)
+ {
+ gdb_assert (!this->empty ());
+
+ intrusive_list_node<T> *elem_node = as_node (&elem);
+ intrusive_list_node<T> *back_node = as_node (m_back);
+
+ gdb_assert (elem_node->next == INTRUSIVE_LIST_UNLINKED_VALUE);
+ gdb_assert (elem_node->prev == INTRUSIVE_LIST_UNLINKED_VALUE);
+
+ elem_node->prev = m_back;
+ back_node->next = &elem;
+ elem_node->next = nullptr;
+ m_back = &elem;
+ }
+
+ void erase_element (T &elem)
+ {
+ intrusive_list_node<T> *elem_node = as_node (&elem);
+
+ gdb_assert (elem_node->prev != INTRUSIVE_LIST_UNLINKED_VALUE);
+ gdb_assert (elem_node->next != INTRUSIVE_LIST_UNLINKED_VALUE);
+
+ if (m_front == &elem)
+ {
+ gdb_assert (elem_node->prev == nullptr);
+ m_front = elem_node->next;
+ }
+ else
+ {
+ gdb_assert (elem_node->prev != nullptr);
+ intrusive_list_node<T> *prev_node = as_node (elem_node->prev);
+ prev_node->next = elem_node->next;
+ }
+
+ if (m_back == &elem)
+ {
+ gdb_assert (elem_node->next == nullptr);
+ m_back = elem_node->prev;
+ }
+ else
+ {
+ gdb_assert (elem_node->next != nullptr);
+ intrusive_list_node<T> *next_node = as_node (elem_node->next);
+ next_node->prev = elem_node->prev;
+ }
+
+ elem_node->next = INTRUSIVE_LIST_UNLINKED_VALUE;
+ elem_node->prev = INTRUSIVE_LIST_UNLINKED_VALUE;
+ }
+
+public:
+ /* Remove the element pointed by I from the list. The element
+ pointed by I is not destroyed. */
+ iterator erase (const_iterator i)
+ {
+ iterator ret = i;
+ ++ret;
+
+ erase_element (*i);
+
+ return ret;
+ }
+
+ /* Erase all the elements. The elements are not destroyed. */
+ void clear ()
+ {
+ while (!this->empty ())
+ pop_front ();
+ }
+
+ /* Erase all the elements. Disposer::operator()(pointer) is called
+ for each of the removed elements. */
+ template<typename Disposer>
+ void clear_and_dispose (Disposer disposer)
+ {
+ while (!this->empty ())
+ {
+ pointer p = &front ();
+ pop_front ();
+ disposer (p);
+ }
+ }
+
+ bool empty () const
+ {
+ return m_front == nullptr;
+ }
+
+ iterator begin () noexcept
+ {
+ return iterator (m_front);
+ }
+
+ const_iterator begin () const noexcept
+ {
+ return const_iterator (m_front);
+ }
+
+ const_iterator cbegin () const noexcept
+ {
+ return const_iterator (m_front);
+ }
+
+ iterator end () noexcept
+ {
+ return {};
+ }
+
+ const_iterator end () const noexcept
+ {
+ return {};
+ }
+
+ const_iterator cend () const noexcept
+ {
+ return {};
+ }
+
+ reverse_iterator rbegin () noexcept
+ {
+ return reverse_iterator (m_back);
+ }
+
+ const_reverse_iterator rbegin () const noexcept
+ {
+ return const_reverse_iterator (m_back);
+ }
+
+ const_reverse_iterator crbegin () const noexcept
+ {
+ return const_reverse_iterator (m_back);
+ }
+
+ reverse_iterator rend () noexcept
+ {
+ return {};
+ }
+
+ const_reverse_iterator rend () const noexcept
+ {
+ return {};
+ }
+
+ const_reverse_iterator crend () const noexcept
+ {
+ return {};
+ }
+
+private:
+ static node_type *as_node (T *elem)
+ {
+ return AsNode::as_node (elem);
+ }
+
+ T *m_front = nullptr;
+ T *m_back = nullptr;
+};
+
+#endif /* GDBSUPPORT_INTRUSIVE_LIST_H */
diff --git a/gdbsupport/reference-to-pointer-iterator.h b/gdbsupport/reference-to-pointer-iterator.h
new file mode 100644
index 0000000..7303fa4
--- /dev/null
+++ b/gdbsupport/reference-to-pointer-iterator.h
@@ -0,0 +1,79 @@
+/* An iterator wrapper that yields pointers instead of references.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H
+#define GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H
+
+/* Wrap an iterator that yields references to objects so that it yields
+ pointers to objects instead.
+
+ This is useful for example to bridge the gap between iterators on intrusive
+ lists, which yield references, and the rest of GDB, which for legacy reasons
+ expects to iterate on pointers. */
+
+template <typename IteratorType>
+struct reference_to_pointer_iterator
+{
+ using self_type = reference_to_pointer_iterator;
+ using value_type = typename IteratorType::value_type *;
+ using reference = typename IteratorType::value_type *&;
+ using pointer = typename IteratorType::value_type **;
+ using iterator_category = typename IteratorType::iterator_category;
+ using difference_type = typename IteratorType::difference_type;
+
+ /* Construct a reference_to_pointer_iterator, passing args to the underyling
+ iterator. */
+ template <typename... Args>
+ reference_to_pointer_iterator (Args &&...args)
+ : m_it (std::forward<Args> (args)...)
+ {}
+
+ /* Create a past-the-end iterator.
+
+ Assumes that default-constructing an underlying iterator creates a
+ past-the-end iterator. */
+ reference_to_pointer_iterator ()
+ {}
+
+ /* Need these as the variadic constructor would be a better match
+ otherwise. */
+ reference_to_pointer_iterator (reference_to_pointer_iterator &) = default;
+ reference_to_pointer_iterator (const reference_to_pointer_iterator &) = default;
+ reference_to_pointer_iterator (reference_to_pointer_iterator &&) = default;
+
+ value_type operator* () const
+ { return &*m_it; }
+
+ self_type &operator++ ()
+ {
+ ++m_it;
+ return *this;
+ }
+
+ bool operator== (const self_type &other) const
+ { return m_it == other.m_it; }
+
+ bool operator!= (const self_type &other) const
+ { return m_it != other.m_it; }
+
+private:
+ /* The underlying iterator. */
+ IteratorType m_it;
+};
+
+#endif /* GDBSUPPORT_REFERENCE_TO_POINTER_ITERATOR_H */