[libcxx-commits] [libcxx] [libc++][lldb] Add initial LLDB data-formatters (PR #187677)

Med Ismail Bennani via libcxx-commits libcxx-commits at lists.llvm.org
Wed Oct 21 19:49:29 PDT 2026


https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/187677

>From 37397b3738e69daf8256bae16d5eeb225fd2bbfc Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Thu, 19 Mar 2026 10:04:05 +0000
Subject: [PATCH 01/10] [libcxx][lldb] Add initial LLDB data-formatters

Depends on:
* https://github.com/llvm/llvm-project/pull/165769

Adds synthetic child providers for `std::map` and `std::vector`. These
were translated from C++ into Python (with the help of Claude).
---
 libcxx/test/libcxx/lldb/map_formatter.sh.test |  37 ++
 .../libcxx/lldb/vector_bool_formatter.sh.test |  35 ++
 .../test/libcxx/lldb/vector_formatter.sh.test | 160 +++++++
 libcxx/utils/libcxx/test/features/__init__.py |   3 +-
 libcxx/utils/libcxx/test/features/lldb.py     |  40 ++
 libcxx/utils/lldb/libcxx/libcxx.py            |  17 +
 .../utils/lldb/libcxx/libcxx_map_formatter.py | 420 ++++++++++++++++++
 .../lldb/libcxx/libcxx_vector_formatter.py    | 226 ++++++++++
 8 files changed, 937 insertions(+), 1 deletion(-)
 create mode 100644 libcxx/test/libcxx/lldb/map_formatter.sh.test
 create mode 100644 libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
 create mode 100644 libcxx/test/libcxx/lldb/vector_formatter.sh.test
 create mode 100644 libcxx/utils/libcxx/test/features/lldb.py
 create mode 100644 libcxx/utils/lldb/libcxx/libcxx.py
 create mode 100644 libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
 create mode 100644 libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py

diff --git a/libcxx/test/libcxx/lldb/map_formatter.sh.test b/libcxx/test/libcxx/lldb/map_formatter.sh.test
new file mode 100644
index 00000000000000..a1aefdf405b151
--- /dev/null
+++ b/libcxx/test/libcxx/lldb/map_formatter.sh.test
@@ -0,0 +1,37 @@
+# REQUIRES: host-has-lldb-with-python
+# REQUIRES: has-filecheck
+# REQUIRES: has-splitfile
+# REQUIRES: optimization=none
+
+# RUN: split-file %s %{temp}
+# RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
+
+# RUN: %{lldb} --no-lldbinit --batch -o "settings set target.load-script-from-symbol-file false" \
+# RUN:         -o "settings set interpreter.stop-command-source-on-error false" \
+# RUN:         -o "command script import %S/../../../utils/lldb/libcxx/libcxx.py" \
+# RUN:         -s %{temp}/lldb.commands %t.out | FileCheck %s
+#
+# CHECK:      (lldb) frame var mii
+# CHECK-NEXT: (std::map<int, int>) mii = size=2 {
+# CHECK-NEXT:   [0] = (first = 1, second = 2)
+# CHECK-NEXT:   [1] = (first = 3, second = 4)
+# CHECK-NEXT: }
+
+#--- main.cpp
+
+#include <map>
+
+int main(int, char**) {
+  std::map<int, int> mii = {
+    {1, 2},
+    {3, 4},
+  };
+
+  return 0;
+}
+
+#--- lldb.commands
+
+break set -p return -X main
+run
+frame var mii
diff --git a/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test b/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
new file mode 100644
index 00000000000000..45624ae2262d12
--- /dev/null
+++ b/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
@@ -0,0 +1,35 @@
+# REQUIRES: host-has-lldb-with-python
+# REQUIRES: has-filecheck
+# REQUIRES: has-splitfile
+# REQUIRES: optimization=none
+
+# RUN: split-file %s %{temp}
+# RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
+
+# RUN: %{lldb} --no-lldbinit --batch -o "settings set target.load-script-from-symbol-file false" \
+# RUN:         -o "settings set interpreter.stop-command-source-on-error false" \
+# RUN:         -o "command script import %S/../../../utils/lldb/libcxx/libcxx.py" \
+# RUN:         -s %{temp}/lldb.commands %t.out | FileCheck %s
+
+# CHECK: (lldb) frame var vb
+# CHECK-NEXT: (std::vector<bool>) vb = size=3 {
+# CHECK-NEXT:   [0] = true
+# CHECK-NEXT:   [1] = false
+# CHECK-NEXT:   [2] = false
+# CHECK-NEXT: }
+
+#--- main.cpp
+
+#include <vector>
+
+int main(int, char**) {
+  std::vector<bool> vb = {true, false, false};
+
+  return 0;
+}
+
+#--- lldb.commands
+
+break set -p return -X main
+run
+frame var vb
diff --git a/libcxx/test/libcxx/lldb/vector_formatter.sh.test b/libcxx/test/libcxx/lldb/vector_formatter.sh.test
new file mode 100644
index 00000000000000..518d946c589009
--- /dev/null
+++ b/libcxx/test/libcxx/lldb/vector_formatter.sh.test
@@ -0,0 +1,160 @@
+# REQUIRES: host-has-lldb-with-python
+# REQUIRES: has-filecheck
+# REQUIRES: has-splitfile
+# REQUIRES: optimization=none
+
+# RUN: split-file %s %{temp}
+# RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
+
+# RUN: %{lldb} --no-lldbinit --batch -o "settings set target.load-script-from-symbol-file false" \
+# RUN:         -o "settings set interpreter.stop-command-source-on-error false" \
+# RUN:         -o "command script import %S/../../../utils/lldb/libcxx/libcxx.py" \
+# RUN:         -s %{temp}/lldb.commands %t.out 2>&1 | FileCheck %s
+
+# CHECK: (lldb) frame var empty
+# CHECK-NEXT: (std::vector<float>) empty = size=0 {}
+
+# CHECK: (lldb) frame var vi
+# CHECK-NEXT: (std::vector<int>) vi = size=2 {
+# CHECK-NEXT:   [0] = 1
+# CHECK-NEXT:   [1] = 2
+# CHECK-NEXT: }
+
+# CHECK: (lldb) frame var empty[0]
+# CHECK: array index 0 is not valid
+
+# CHECK: (lldb) continue
+# CHECK: (lldb) frame var vi
+# CHECK-NEXT: (std::vector<int>) vi = size=5 {
+# CHECK-NEXT:   [0] = 1
+# CHECK-NEXT:   [1] = 2
+# CHECK-NEXT:   [2] = 5
+# CHECK-NEXT:   [3] = 4
+# CHECK-NEXT:   [4] = 0
+# CHECK-NEXT: }
+
+# CHECK: (lldb) frame var vi[2]
+# CHECK-NEXT: (int) vi[2] = 5
+
+# CHECK: (lldb) frame var vi[0]
+# CHECK-NEXT: (int) vi[0] = 1
+
+# CHECK: (lldb) continue
+# CHECK: (lldb) frame var vi[0]
+# CHECK-NEXT: array index 0 is not valid
+
+# CHECK: (lldb) frame var vi
+# CHECK-NEXT: (std::vector<int>) vi = size=0 {}
+
+# CHECK: (lldb) continue
+# CHECK: (lldb) frame var vec_of_vec
+# CHECK-NEXT: (std::vector<std::vector<int> >) vec_of_vec = size=1 {
+# CHECK-NEXT:   [0] = size=2 {
+# CHECK-NEXT:     [0] = 10
+# CHECK-NEXT:     [1] = 11
+# CHECK-NEXT:   }
+# CHECK-NEXT: }
+
+# CHECK: (lldb) frame var vec_of_vec[0]
+# CHECK-NEXT: (std::vector<int>) vec_of_vec[0] = size=2 {
+# CHECK-NEXT:   [0] = 10
+# CHECK-NEXT:   [1] = 11
+# CHECK-NEXT: }
+
+# CHECK: (lldb) continue
+# CHECK: (lldb) frame var vs
+# CHECK-NEXT: (std::vector<std::string>) vs = size=2 {
+# CHECK-NEXT:   [0] = "Foo"
+# CHECK-NEXT:   [1] = "Bar"
+# CHECK-NEXT: }
+
+# CHECK: (lldb) frame var vs[1]
+# CHECK-NEXT: (std::string) vs[1] = "Bar"
+
+# CHECK: (lldb) continue
+# CHECK: (lldb) frame var vec_ref
+# CHECK-NEXT: (const std::vector<int> &) vec_ref = 0x{{.*}} size=2: {
+# CHECK-NEXT:   [0] = 10
+# CHECK-NEXT:   [1] = 11
+# CHECK-NEXT: }
+
+# CHECK: (lldb) frame var vec_ref[1]
+# CHECK-NEXT: (int) vec_ref[1] = 11
+
+# CHECK: (lldb) frame var vec_ptr
+# CHECK-NEXT: (const std::vector<int> *) vec_ptr = 0x{{.*}} size=2
+
+# CHECK: (lldb) frame var vec_ptr[1]
+# CHECK-NEXT: (int) vec_ptr[1] = 11
+
+#--- main.cpp
+
+#include <vector>
+#include <string>
+
+void break_here() {}
+
+int main() {
+  std::vector<float> empty;
+  std::vector<int> vi = {1, 2};
+
+  break_here();
+
+  vi.push_back(5);
+  vi.push_back(4);
+  vi.push_back(0);
+
+  break_here();
+
+  vi.clear();
+
+  break_here();
+
+  vi.push_back(10);
+  vi.push_back(11);
+  std::vector<std::vector<int>> vec_of_vec{vi};
+
+  break_here();
+
+  std::vector<std::string>  vs{
+    "Foo",
+    "Bar"
+  };
+
+  break_here();
+
+  [[maybe_unused]] const auto &vec_ref = vi;
+  [[maybe_unused]] const auto *vec_ptr = &vi;
+
+  break_here();
+}
+
+#--- lldb.commands
+break set -p break_here -X main
+run
+frame var empty
+frame var vi
+frame var empty[0]
+
+continue
+frame var vi
+frame var vi[2]
+frame var vi[0]
+
+continue
+frame var vi[0]
+frame var vi
+
+continue
+frame var vec_of_vec
+frame var vec_of_vec[0]
+
+continue
+frame var vs
+frame var vs[1]
+
+continue
+frame var vec_ref
+frame var vec_ref[1]
+frame var vec_ptr
+frame var vec_ptr[1]
diff --git a/libcxx/utils/libcxx/test/features/__init__.py b/libcxx/utils/libcxx/test/features/__init__.py
index 5c0d1f3aaafc6c..f0957a2cc79ef6 100644
--- a/libcxx/utils/libcxx/test/features/__init__.py
+++ b/libcxx/utils/libcxx/test/features/__init__.py
@@ -6,7 +6,7 @@
 #
 # ===----------------------------------------------------------------------===##
 
-from . import availability, compiler, gdb, libcxx_macros, localization, misc, platform
+from . import availability, compiler, gdb, lldb, libcxx_macros, localization, misc, platform
 
 # Lit features are evaluated in order. Some features depend on other features, so
 # we are careful to define them in the correct order. For example, several features
@@ -17,5 +17,6 @@
 DEFAULT_FEATURES += platform.features
 DEFAULT_FEATURES += localization.features
 DEFAULT_FEATURES += gdb.features
+DEFAULT_FEATURES += lldb.features
 DEFAULT_FEATURES += misc.features
 DEFAULT_FEATURES += availability.features
diff --git a/libcxx/utils/libcxx/test/features/lldb.py b/libcxx/utils/libcxx/test/features/lldb.py
new file mode 100644
index 00000000000000..c022c9c358eceb
--- /dev/null
+++ b/libcxx/utils/libcxx/test/features/lldb.py
@@ -0,0 +1,40 @@
+# ===----------------------------------------------------------------------===##
+#
+# 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
+#
+# ===----------------------------------------------------------------------===##
+
+from libcxx.test.dsl import Feature, AddSubstitution
+import shutil
+import subprocess
+
+# Detect whether LLDB is on the system and has Python scripting support.
+# If so add a substitution to access it.
+
+def check_lldb(cfg):
+    lldb_path = shutil.which("lldb")
+    if lldb_path is None:
+        return False
+
+    try:
+        stdout = subprocess.check_output(
+            [lldb_path, "--batch", "-o", "script -l python -- print(\"Has\", \"Python\", \"!\")"],
+            stderr=subprocess.DEVNULL,
+            universal_newlines=True,
+        )
+    except subprocess.CalledProcessError:
+        return False
+
+    # Check we actually ran the Python
+    return "Has Python !" in stdout
+
+
+features = [
+    Feature(
+        name="host-has-lldb-with-python",
+        when=check_lldb,
+        actions=[AddSubstitution("%{lldb}", lambda cfg: shutil.which("lldb"))],
+    )
+]
diff --git a/libcxx/utils/lldb/libcxx/libcxx.py b/libcxx/utils/lldb/libcxx/libcxx.py
new file mode 100644
index 00000000000000..46877527ddebbf
--- /dev/null
+++ b/libcxx/utils/lldb/libcxx/libcxx.py
@@ -0,0 +1,17 @@
+import lldb
+from libcxx_map_formatter import *
+from libcxx_vector_formatter import *
+
+def register_synthetic(debugger: lldb.SBDebugger, regex: str, class_name: str, extra_flags: str = ""):
+    debugger.HandleCommand(f'type synthetic add {extra_flags} -x "{regex}" -l {__name__}.{class_name} -w "cplusplus-py"')
+
+def __lldb_init_module(debugger, dict):
+    register_synthetic(debugger, "^std::__[[:alnum:]]+::map<.+> >$", "LibcxxStdMapSyntheticProvider")
+    register_synthetic(debugger, "^std::__[[:alnum:]]+::set<.+> >$", "LibcxxStdMapSyntheticProvider")
+    register_synthetic(debugger, "^std::__[[:alnum:]]+::multiset<.+> >$", "LibcxxStdMapSyntheticProvider")
+    register_synthetic(debugger, "^std::__[[:alnum:]]+::multimap<.+> >$", "LibcxxStdMapSyntheticProvider")
+    register_synthetic(debugger, "^std::__[[:alnum:]]+::__map_(const_)?iterator<.+>$", "LibCxxMapIteratorSyntheticProvider")
+    register_synthetic(debugger, "^std::__[[:alnum:]]+::vector<.+>$", "LibCxxStdVectorSyntheticFrontendCreator", "--wants-dereference")
+
+    # Enables registered formatters in LLDB.
+    debugger.HandleCommand('type category enable cplusplus-py')
diff --git a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
new file mode 100644
index 00000000000000..93bdddd9a7d2fb
--- /dev/null
+++ b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
@@ -0,0 +1,420 @@
+"""
+Python LLDB synthetic child provider for libc++ std::map
+
+1-to-1 translation from the LLDB builtin std::(multi)map formatter.
+
+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
+"""
+
+import lldb
+
+
+class MapEntry:
+    """Wrapper around an LLDB ValueObject representing a tree node entry."""
+
+    def __init__(self, entry_sp=None):
+        self.m_entry_sp = entry_sp
+
+    def left(self):
+        """Get the left child pointer (offset 0)."""
+        if not self.m_entry_sp:
+            return None
+        return self.m_entry_sp.CreateChildAtOffset("", 0, self.m_entry_sp.GetType())
+
+    def right(self):
+        if not self.m_entry_sp:
+            return None
+        addr_size = self.m_entry_sp.GetProcess().GetAddressByteSize()
+        return self.m_entry_sp.CreateChildAtOffset(
+            "", addr_size, self.m_entry_sp.GetType()
+        )
+
+    def parent(self):
+        if not self.m_entry_sp:
+            return None
+        addr_size = self.m_entry_sp.GetProcess().GetAddressByteSize()
+        return self.m_entry_sp.CreateChildAtOffset(
+            "", 2 * addr_size, self.m_entry_sp.GetType()
+        )
+
+    def value(self):
+        """Get the unsigned integer value of the entry (pointer address)."""
+        if not self.m_entry_sp:
+            return 0
+        return self.m_entry_sp.GetValueAsUnsigned(0)
+
+    def error(self):
+        if not self.m_entry_sp:
+            return True
+        return self.m_entry_sp.GetError().Fail()
+
+    def null(self):
+        return self.value() == 0
+
+    def get_entry(self):
+        return self.m_entry_sp
+
+    def set_entry(self, entry):
+        self.m_entry_sp = entry
+
+    def __eq__(self, other):
+        if not isinstance(other, MapEntry):
+            return False
+        if self.m_entry_sp is None and other.m_entry_sp is None:
+            return True
+        if self.m_entry_sp is None or other.m_entry_sp is None:
+            return False
+        return self.m_entry_sp.GetLoadAddress() == other.m_entry_sp.GetLoadAddress()
+
+
+class MapIterator:
+    """Iterator for traversing the tree backing std::map."""
+
+    def __init__(self, entry=None, depth=0):
+        self.m_entry = MapEntry(entry) if entry else MapEntry()
+        self.m_max_depth = depth
+        self.m_error = False
+
+    def value(self):
+        return self.m_entry.get_entry()
+
+    def advance(self, count):
+        """Advance the iterator by count steps and return the entry."""
+        if self.m_error:
+            return None
+
+        steps = 0
+        while count > 0:
+            self._next()
+            count -= 1
+            steps += 1
+            if self.m_error or self.m_entry.null() or (steps > self.m_max_depth):
+                return None
+
+        return self.m_entry.get_entry()
+
+    def _next(self):
+        """
+        Mimics libc++'s __tree_next algorithm, which libc++ uses
+        in its __tree_iterator::operator++.
+        """
+        if self.m_entry.null():
+            return
+
+        right = MapEntry(self.m_entry.right())
+        if not right.null():
+            self.m_entry = self._tree_min(right)
+            return
+
+        steps = 0
+        while not self._is_left_child(self.m_entry):
+            if self.m_entry.error():
+                self.m_error = True
+                return
+            self.m_entry.set_entry(self.m_entry.parent())
+            steps += 1
+            if steps > self.m_max_depth:
+                self.m_entry = MapEntry()
+                return
+
+        self.m_entry = MapEntry(self.m_entry.parent())
+
+    def _tree_min(self, x):
+        """Mimics libc++'s __tree_min algorithm."""
+        if x.null():
+            return MapEntry()
+
+        left = MapEntry(x.left())
+        steps = 0
+        while not left.null():
+            if left.error():
+                self.m_error = True
+                return MapEntry()
+            x = MapEntry(left.get_entry())
+            left = MapEntry(x.left())
+            steps += 1
+            if steps > self.m_max_depth:
+                return MapEntry()
+
+        return x
+
+    def _is_left_child(self, x):
+        """Check if x is a left child of its parent."""
+        if x.null():
+            return False
+        rhs = MapEntry(x.parent())
+        rhs.set_entry(rhs.left())
+        return x.value() == rhs.value()
+
+
+def _get_first_value_of_libcxx_compressed_pair(pair):
+    """
+    Get the first value from a libc++ compressed pair.
+    Handles both old and new layouts.
+    """
+    # Try __value_ first (newer layout)
+    value_sp = pair.GetChildMemberWithName("__value_")
+    if value_sp and value_sp.IsValid():
+        return value_sp
+
+    # Try __first_ (older layout)
+    first_sp = pair.GetChildMemberWithName("__first_")
+    if first_sp and first_sp.IsValid():
+        return first_sp
+
+    return None
+
+
+def _get_value_or_old_compressed_pair(tree, value_name, pair_name):
+    """
+    Try to get a value member directly, or fall back to compressed pair layout.
+    Returns (value_sp, is_compressed_pair).
+    """
+    # Try new layout first (direct member)
+    value_sp = tree.GetChildMemberWithName(value_name)
+    if value_sp and value_sp.IsValid():
+        return (value_sp, False)
+
+    # Fall back to old compressed pair layout
+    pair_sp = tree.GetChildMemberWithName(pair_name)
+    if pair_sp and pair_sp.IsValid():
+        return (pair_sp, True)
+
+    return (None, False)
+
+
+class LibcxxStdMapSyntheticProvider:
+    """Synthetic children provider for libc++ std::map."""
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.m_tree = None
+        self.m_root_node = None
+        self.m_node_ptr_type = None
+        self.m_count = None
+        self.m_iterators = {}
+
+    def num_children(self):
+        if self.m_count is not None:
+            return self.m_count
+
+        if self.m_tree is None:
+            return 0
+
+        # Try new layout (__size_) or old compressed pair layout (__pair3_)
+        size_sp, is_compressed_pair = _get_value_or_old_compressed_pair(
+            self.m_tree, "__size_", "__pair3_"
+        )
+
+        if not size_sp:
+            return 0
+
+        if is_compressed_pair:
+            return self._calculate_num_children_for_old_compressed_pair_layout(size_sp)
+
+        self.m_count = size_sp.GetValueAsUnsigned(0)
+        return self.m_count
+
+    def _calculate_num_children_for_old_compressed_pair_layout(self, pair):
+        """Handle old libc++ compressed pair layout."""
+        node_sp = _get_first_value_of_libcxx_compressed_pair(pair)
+
+        if not node_sp:
+            return 0
+
+        self.m_count = node_sp.GetValueAsUnsigned(0)
+        return self.m_count
+
+    def get_child_index(self, name):
+        """Get the index of a child with the given name (e.g., "[0]" -> 0)."""
+        try:
+            if name.startswith("[") and name.endswith("]"):
+                return int(name[1:-1])
+        except ValueError:
+            pass
+        return None
+
+    def get_child_at_index(self, index):
+        num_children = self.num_children()
+        if index >= num_children:
+            return None
+
+        if self.m_tree is None or self.m_root_node is None:
+            return None
+
+        key_val_sp = self._get_key_value_pair(index, num_children)
+        if not key_val_sp:
+            # This will stop all future searches until an update() happens
+            self.m_tree = None
+            return None
+
+        name = "[%d]" % index
+        potential_child_sp = key_val_sp.Clone(name)
+
+        if potential_child_sp and potential_child_sp.IsValid():
+            num_child_children = potential_child_sp.GetNumChildren()
+
+            # Handle __cc_ or __cc wrapper (1 child case)
+            if num_child_children == 1:
+                child0_sp = potential_child_sp.GetChildAtIndex(0)
+                if child0_sp:
+                    child_name = child0_sp.GetName()
+                    if child_name in ("__cc_", "__cc"):
+                        potential_child_sp = child0_sp.Clone(name)
+
+            # Handle __cc_ and __nc wrapper (2 children case)
+            elif num_child_children == 2:
+                child0_sp = potential_child_sp.GetChildAtIndex(0)
+                child1_sp = potential_child_sp.GetChildAtIndex(1)
+                if child0_sp and child1_sp:
+                    child0_name = child0_sp.GetName()
+                    child1_name = child1_sp.GetName()
+                    if child0_name in ("__cc_", "__cc") and child1_name == "__nc":
+                        potential_child_sp = child0_sp.Clone(name)
+
+        return potential_child_sp
+
+    def update(self):
+        """Update the cached state. Called when the underlying value changes."""
+        self.m_count = None
+        self.m_tree = None
+        self.m_root_node = None
+        self.m_iterators.clear()
+
+        self.m_tree = self.valobj.GetChildMemberWithName("__tree_")
+        if not self.m_tree or not self.m_tree.IsValid():
+            return False
+
+        self.m_root_node = self.m_tree.GetChildMemberWithName("__begin_node_")
+
+        # Get the __node_pointer type from the tree's type
+        tree_type = self.m_tree.GetType()
+        self.m_node_ptr_type = tree_type.FindDirectNestedType("__node_pointer")
+
+        return False
+
+    def has_children(self):
+        """Check if this object has children."""
+        return True
+
+    def _get_key_value_pair(self, idx, max_depth):
+        """
+        Returns the ValueObject for the __tree_node type that
+        holds the key/value pair of the node at index idx.
+        """
+        iterator = MapIterator(self.m_root_node, max_depth)
+
+        advance_by = idx
+        if idx > 0:
+            # If we have already created the iterator for the previous
+            # index, we can start from there and advance by 1.
+            if idx - 1 in self.m_iterators:
+                iterator = self.m_iterators[idx - 1]
+                advance_by = 1
+
+        iterated_sp = iterator.advance(advance_by)
+        if not iterated_sp:
+            # This tree is garbage - stop
+            return None
+
+        if not self.m_node_ptr_type or not self.m_node_ptr_type.IsValid():
+            return None
+
+        # iterated_sp is a __iter_pointer at this point.
+        # We can cast it to a __node_pointer (which is what libc++ does).
+        value_type_sp = iterated_sp.Cast(self.m_node_ptr_type)
+        if not value_type_sp or not value_type_sp.IsValid():
+            return None
+
+        # Finally, get the key/value pair.
+        value_type_sp = value_type_sp.GetChildMemberWithName("__value_")
+        if not value_type_sp or not value_type_sp.IsValid():
+            return None
+
+        self.m_iterators[idx] = iterator
+
+        return value_type_sp
+
+
+class LibCxxMapIteratorSyntheticProvider:
+    """Synthetic children provider for libc++ std::map::iterator."""
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.m_pair_sp = None
+
+    def num_children(self):
+        """Map iterators always have 2 children (first and second)."""
+        return 2
+
+    def get_child_index(self, name):
+        if not self.m_pair_sp:
+            return None
+        return self.m_pair_sp.GetIndexOfChildWithName(name)
+
+    def get_child_at_index(self, index):
+        if not self.m_pair_sp:
+            return None
+        return self.m_pair_sp.GetChildAtIndex(index)
+
+    def update(self):
+        self.m_pair_sp = None
+
+        if not self.valobj.IsValid():
+            return False
+
+        target = self.valobj.GetTarget()
+        if not target or not target.IsValid():
+            return False
+
+        # valobj is a std::map::iterator
+        # ...which is a __map_iterator<__tree_iterator<..., __node_pointer, ...>>
+        #
+        # Then, __map_iterator::__i_ is a __tree_iterator
+        tree_iter_sp = self.valobj.GetChildMemberWithName("__i_")
+        if not tree_iter_sp or not tree_iter_sp.IsValid():
+            return False
+
+        # Type is __tree_iterator::__node_pointer
+        # (We could alternatively also get this from the template argument)
+        tree_iter_type = tree_iter_sp.GetType()
+        node_pointer_type = tree_iter_type.FindDirectNestedType("__node_pointer")
+        if not node_pointer_type or not node_pointer_type.IsValid():
+            return False
+
+        # __ptr_ is a __tree_iterator::__iter_pointer
+        iter_pointer_sp = tree_iter_sp.GetChildMemberWithName("__ptr_")
+        if not iter_pointer_sp or not iter_pointer_sp.IsValid():
+            return False
+
+        # Cast the __iter_pointer to a __node_pointer (which stores our key/value pair)
+        node_pointer_sp = iter_pointer_sp.Cast(node_pointer_type)
+        if not node_pointer_sp or not node_pointer_sp.IsValid():
+            return False
+
+        key_value_sp = node_pointer_sp.GetChildMemberWithName("__value_")
+        if not key_value_sp or not key_value_sp.IsValid():
+            return False
+
+        # Create the synthetic child, which is a pair where the key and value can be
+        # retrieved by querying the synthetic provider for
+        # get_child_index("first") and get_child_index("second")
+        # respectively.
+        #
+        # std::map stores the actual key/value pair in value_type::__cc_ (or
+        # previously __cc).
+        key_value_sp = key_value_sp.Clone("pair")
+        if key_value_sp.GetNumChildren() == 1:
+            child0_sp = key_value_sp.GetChildAtIndex(0)
+            if child0_sp:
+                child_name = child0_sp.GetName()
+                if child_name in ("__cc_", "__cc"):
+                    key_value_sp = child0_sp.Clone("pair")
+
+        self.m_pair_sp = key_value_sp
+        return False
+
+    def has_children(self):
+        return True
diff --git a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
new file mode 100644
index 00000000000000..f7217a36c67c13
--- /dev/null
+++ b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
@@ -0,0 +1,226 @@
+"""
+Python LLDB data formatter for libc++ std::vector
+
+1-to-1 translation from the LLDB builtin std::vector formatter.
+
+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
+"""
+
+import lldb
+
+
+def get_data_pointer(root):
+    """Get the data pointer from a vector, handling compressed pair layout."""
+    # Try new layout
+    cap_sp = root.GetChildMemberWithName("__cap_")
+    if cap_sp:
+        return cap_sp
+
+    # Try old compressed pair layout
+    end_cap_sp = root.GetChildMemberWithName("__end_cap_")
+    if not end_cap_sp:
+        return None
+
+    # Get first value of compressed pair
+    value_sp = end_cap_sp.GetChildMemberWithName("__value_")
+    if value_sp:
+        return value_sp
+
+    first_sp = end_cap_sp.GetChildMemberWithName("__first_")
+    if first_sp:
+        return first_sp
+
+    return None
+
+
+class LibCxxStdVectorSyntheticFrontEnd:
+    """Synthetic children frontend for libc++ std::vector."""
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.m_start = None
+        self.m_finish = None
+        self.m_element_type = None
+        self.m_element_size = 0
+
+    def num_children(self):
+        if not self.m_start or not self.m_finish:
+            return 0
+
+        start_val = self.m_start.GetValueAsUnsigned(0)
+        finish_val = self.m_finish.GetValueAsUnsigned(0)
+
+        if start_val == 0 or finish_val == 0:
+            return 0
+
+        if start_val > finish_val:
+            return 0
+
+        num_children = finish_val - start_val
+        if num_children % self.m_element_size != 0:
+            return 0
+
+        return num_children // self.m_element_size
+
+    def get_child_at_index(self, index):
+        if not self.m_start or not self.m_finish:
+            return None
+
+        offset = index * self.m_element_size
+        offset = offset + self.m_start.GetValueAsUnsigned(0)
+
+        name = "[%d]" % index
+        target = self.valobj.GetTarget()
+        if not target:
+            return None
+
+        addr = lldb.SBAddress(offset, target)
+        return target.CreateValueFromAddress(name, addr, self.m_element_type)
+
+    def update(self):
+        self.m_start = None
+        self.m_finish = None
+
+        data_sp = get_data_pointer(self.valobj)
+        if not data_sp:
+            return False
+
+        self.m_element_type = data_sp.GetType().GetPointeeType()
+        if not self.m_element_type:
+            return False
+
+        size = self.m_element_type.GetByteSize()
+        if not size or size == 0:
+            return False
+
+        self.m_element_size = size
+
+        begin_sp = self.valobj.GetChildMemberWithName("__begin_")
+        end_sp = self.valobj.GetChildMemberWithName("__end_")
+
+        if not begin_sp:
+            return False
+
+        self.m_start = begin_sp
+        self.m_finish = end_sp
+
+        return True
+
+
+class LibCxxVectorBoolSyntheticFrontEnd:
+    """Synthetic children frontend for libc++ std::vector<bool>."""
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.m_bool_type = None
+        self.m_count = 0
+        self.m_base_data_address = 0
+        self.m_children = {}
+
+        # Get bool type
+        if valobj:
+            target = valobj.GetTarget()
+            if target:
+                self.m_bool_type = target.GetBasicType(lldb.eBasicTypeBool)
+
+    def num_children(self):
+        return self.m_count
+
+    def get_child_at_index(self, index):
+        if index in self.m_children:
+            return self.m_children[index]
+
+        if index >= self.m_count:
+            return None
+
+        if self.m_base_data_address == 0 or self.m_count == 0:
+            return None
+
+        if not self.m_bool_type:
+            return None
+
+        # Calculate byte and bit index
+        byte_idx = index >> 3  # divide by 8
+        bit_index = index & 7  # modulo 8
+
+        byte_location = self.m_base_data_address + byte_idx
+
+        process = self.valobj.GetProcess()
+        if not process:
+            return None
+
+        error = lldb.SBError()
+        byte_data = process.ReadMemory(byte_location, 1, error)
+        if error.Fail() or not byte_data or len(byte_data) == 0:
+            return None
+
+        byte = ord(byte_data[0:1])
+        mask = 1 << bit_index
+        bit_set = (byte & mask) != 0
+
+        bool_size = self.m_bool_type.GetByteSize()
+        if not bool_size:
+            return None
+
+        # Create data for the bool value
+        data = lldb.SBData()
+        data.SetData(
+            error,
+            bytes([1 if bit_set else 0]),
+            process.GetByteOrder(),
+            process.GetAddressByteSize(),
+        )
+
+        name = "[%d]" % index
+        target = self.valobj.GetTarget()
+        if not target:
+            return None
+
+        retval_sp = target.CreateValueFromData(name, data, self.m_bool_type)
+        if retval_sp:
+            self.m_children[index] = retval_sp
+
+        return retval_sp
+
+    def update(self):
+        self.m_children = {}
+
+        if not self.valobj:
+            return False
+
+        size_sp = self.valobj.GetChildMemberWithName("__size_")
+        if not size_sp:
+            return False
+
+        self.m_count = size_sp.GetValueAsUnsigned(0)
+        if not self.m_count:
+            return True
+
+        begin_sp = self.valobj.GetChildMemberWithName("__begin_")
+        if not begin_sp:
+            self.m_count = 0
+            return False
+
+        self.m_base_data_address = begin_sp.GetValueAsUnsigned(0)
+        if not self.m_base_data_address:
+            self.m_count = 0
+            return False
+
+        return True
+
+
+def LibCxxStdVectorSyntheticFrontendCreator(valobj, internal_dict):
+    if not valobj:
+        return None
+
+    compiler_type = valobj.GetType()
+    if not compiler_type or compiler_type.GetNumberOfTemplateArguments() == 0:
+        return None
+
+    arg_type = compiler_type.GetTemplateArgumentType(0)
+    if arg_type.GetName() == "bool":
+        return LibCxxVectorBoolSyntheticFrontEnd(valobj, internal_dict)
+
+    return LibCxxStdVectorSyntheticFrontEnd(valobj, internal_dict)

>From 3a159b0eb24f7c3df533b44ddecd6c4d43d54863 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 17 Apr 2026 12:12:04 +0100
Subject: [PATCH 02/10] fixup! fix copyright headers; add some comments

---
 libcxx/utils/lldb/libcxx/libcxx.py                | 10 +++++++++-
 libcxx/utils/lldb/libcxx/libcxx_map_formatter.py  | 12 ++++--------
 .../utils/lldb/libcxx/libcxx_vector_formatter.py  | 15 +++++++--------
 3 files changed, 20 insertions(+), 17 deletions(-)

diff --git a/libcxx/utils/lldb/libcxx/libcxx.py b/libcxx/utils/lldb/libcxx/libcxx.py
index 46877527ddebbf..3e6a68ac7a110d 100644
--- a/libcxx/utils/lldb/libcxx/libcxx.py
+++ b/libcxx/utils/lldb/libcxx/libcxx.py
@@ -1,3 +1,9 @@
+# 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
+
+# Entry point for LLDB to register data formatters.
+
 import lldb
 from libcxx_map_formatter import *
 from libcxx_vector_formatter import *
@@ -5,6 +11,7 @@
 def register_synthetic(debugger: lldb.SBDebugger, regex: str, class_name: str, extra_flags: str = ""):
     debugger.HandleCommand(f'type synthetic add {extra_flags} -x "{regex}" -l {__name__}.{class_name} -w "cplusplus-py"')
 
+# Entry point for LLDB when importing scripts.
 def __lldb_init_module(debugger, dict):
     register_synthetic(debugger, "^std::__[[:alnum:]]+::map<.+> >$", "LibcxxStdMapSyntheticProvider")
     register_synthetic(debugger, "^std::__[[:alnum:]]+::set<.+> >$", "LibcxxStdMapSyntheticProvider")
@@ -13,5 +20,6 @@ def __lldb_init_module(debugger, dict):
     register_synthetic(debugger, "^std::__[[:alnum:]]+::__map_(const_)?iterator<.+>$", "LibCxxMapIteratorSyntheticProvider")
     register_synthetic(debugger, "^std::__[[:alnum:]]+::vector<.+>$", "LibCxxStdVectorSyntheticFrontendCreator", "--wants-dereference")
 
-    # Enables registered formatters in LLDB.
+    # Enables registered formatters in LLDB. The builtin LLDB formatters are registered in the `cplusplus` category.
+    # We don't want to override/clash with those.
     debugger.HandleCommand('type category enable cplusplus-py')
diff --git a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
index 93bdddd9a7d2fb..22f26ff7830ae1 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
@@ -1,12 +1,8 @@
-"""
-Python LLDB synthetic child provider for libc++ std::map
+# 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
 
-1-to-1 translation from the LLDB builtin std::(multi)map formatter.
-
-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
-"""
+# LLDB data formatters for libc++ std::(multi)map and std::(multi)set
 
 import lldb
 
diff --git a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
index f7217a36c67c13..0f1781bd7705c8 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
@@ -1,12 +1,9 @@
-"""
-Python LLDB data formatter for libc++ std::vector
+# 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
 
-1-to-1 translation from the LLDB builtin std::vector formatter.
+# LLDB data formatter for libc++ std::vector
 
-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
-"""
 
 import lldb
 
@@ -64,6 +61,8 @@ def num_children(self):
 
         return num_children // self.m_element_size
 
+    # FIXME: newer versions of LLDB auto-generate this.
+    # Eventually this can be removed.
     def get_child_at_index(self, index):
         if not self.m_start or not self.m_finish:
             return None
@@ -71,7 +70,7 @@ def get_child_at_index(self, index):
         offset = index * self.m_element_size
         offset = offset + self.m_start.GetValueAsUnsigned(0)
 
-        name = "[%d]" % index
+        name = f'[{index}]'
         target = self.valobj.GetTarget()
         if not target:
             return None

>From 35afdc2a3e13cdeacb4512ac027030f41952fd22 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 17 Apr 2026 12:12:36 +0100
Subject: [PATCH 03/10] fixup! python format

---
 libcxx/utils/lldb/libcxx/libcxx.py            | 45 +++++++++++++++----
 .../lldb/libcxx/libcxx_vector_formatter.py    |  2 +-
 2 files changed, 37 insertions(+), 10 deletions(-)

diff --git a/libcxx/utils/lldb/libcxx/libcxx.py b/libcxx/utils/lldb/libcxx/libcxx.py
index 3e6a68ac7a110d..e78d708b7da2d9 100644
--- a/libcxx/utils/lldb/libcxx/libcxx.py
+++ b/libcxx/utils/lldb/libcxx/libcxx.py
@@ -8,18 +8,45 @@
 from libcxx_map_formatter import *
 from libcxx_vector_formatter import *
 
-def register_synthetic(debugger: lldb.SBDebugger, regex: str, class_name: str, extra_flags: str = ""):
-    debugger.HandleCommand(f'type synthetic add {extra_flags} -x "{regex}" -l {__name__}.{class_name} -w "cplusplus-py"')
+
+def register_synthetic(
+    debugger: lldb.SBDebugger, regex: str, class_name: str, extra_flags: str = ""
+):
+    debugger.HandleCommand(
+        f'type synthetic add {extra_flags} -x "{regex}" -l {__name__}.{class_name} -w "cplusplus-py"'
+    )
+
 
 # Entry point for LLDB when importing scripts.
 def __lldb_init_module(debugger, dict):
-    register_synthetic(debugger, "^std::__[[:alnum:]]+::map<.+> >$", "LibcxxStdMapSyntheticProvider")
-    register_synthetic(debugger, "^std::__[[:alnum:]]+::set<.+> >$", "LibcxxStdMapSyntheticProvider")
-    register_synthetic(debugger, "^std::__[[:alnum:]]+::multiset<.+> >$", "LibcxxStdMapSyntheticProvider")
-    register_synthetic(debugger, "^std::__[[:alnum:]]+::multimap<.+> >$", "LibcxxStdMapSyntheticProvider")
-    register_synthetic(debugger, "^std::__[[:alnum:]]+::__map_(const_)?iterator<.+>$", "LibCxxMapIteratorSyntheticProvider")
-    register_synthetic(debugger, "^std::__[[:alnum:]]+::vector<.+>$", "LibCxxStdVectorSyntheticFrontendCreator", "--wants-dereference")
+    register_synthetic(
+        debugger, "^std::__[[:alnum:]]+::map<.+> >$", "LibcxxStdMapSyntheticProvider"
+    )
+    register_synthetic(
+        debugger, "^std::__[[:alnum:]]+::set<.+> >$", "LibcxxStdMapSyntheticProvider"
+    )
+    register_synthetic(
+        debugger,
+        "^std::__[[:alnum:]]+::multiset<.+> >$",
+        "LibcxxStdMapSyntheticProvider",
+    )
+    register_synthetic(
+        debugger,
+        "^std::__[[:alnum:]]+::multimap<.+> >$",
+        "LibcxxStdMapSyntheticProvider",
+    )
+    register_synthetic(
+        debugger,
+        "^std::__[[:alnum:]]+::__map_(const_)?iterator<.+>$",
+        "LibCxxMapIteratorSyntheticProvider",
+    )
+    register_synthetic(
+        debugger,
+        "^std::__[[:alnum:]]+::vector<.+>$",
+        "LibCxxStdVectorSyntheticFrontendCreator",
+        "--wants-dereference",
+    )
 
     # Enables registered formatters in LLDB. The builtin LLDB formatters are registered in the `cplusplus` category.
     # We don't want to override/clash with those.
-    debugger.HandleCommand('type category enable cplusplus-py')
+    debugger.HandleCommand("type category enable cplusplus-py")
diff --git a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
index 0f1781bd7705c8..6b542af45baf22 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
@@ -70,7 +70,7 @@ def get_child_at_index(self, index):
         offset = index * self.m_element_size
         offset = offset + self.m_start.GetValueAsUnsigned(0)
 
-        name = f'[{index}]'
+        name = f"[{index}]"
         target = self.valobj.GetTarget()
         if not target:
             return None

>From ea0c2541fc03c03a8f6c3bdd21ae6ac8ef3f9092 Mon Sep 17 00:00:00 2001
From: Michael Buch <michaelbuch12 at gmail.com>
Date: Fri, 17 Apr 2026 12:30:00 +0100
Subject: [PATCH 04/10] fixup! python format

---
 libcxx/utils/libcxx/test/features/__init__.py | 11 ++++++++++-
 libcxx/utils/libcxx/test/features/lldb.py     |  8 +++++++-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/libcxx/utils/libcxx/test/features/__init__.py b/libcxx/utils/libcxx/test/features/__init__.py
index f0957a2cc79ef6..cb15a0d04fc8dc 100644
--- a/libcxx/utils/libcxx/test/features/__init__.py
+++ b/libcxx/utils/libcxx/test/features/__init__.py
@@ -6,7 +6,16 @@
 #
 # ===----------------------------------------------------------------------===##
 
-from . import availability, compiler, gdb, lldb, libcxx_macros, localization, misc, platform
+from . import (
+    availability,
+    compiler,
+    gdb,
+    lldb,
+    libcxx_macros,
+    localization,
+    misc,
+    platform,
+)
 
 # Lit features are evaluated in order. Some features depend on other features, so
 # we are careful to define them in the correct order. For example, several features
diff --git a/libcxx/utils/libcxx/test/features/lldb.py b/libcxx/utils/libcxx/test/features/lldb.py
index c022c9c358eceb..5d9c41a31a4e19 100644
--- a/libcxx/utils/libcxx/test/features/lldb.py
+++ b/libcxx/utils/libcxx/test/features/lldb.py
@@ -13,6 +13,7 @@
 # Detect whether LLDB is on the system and has Python scripting support.
 # If so add a substitution to access it.
 
+
 def check_lldb(cfg):
     lldb_path = shutil.which("lldb")
     if lldb_path is None:
@@ -20,7 +21,12 @@ def check_lldb(cfg):
 
     try:
         stdout = subprocess.check_output(
-            [lldb_path, "--batch", "-o", "script -l python -- print(\"Has\", \"Python\", \"!\")"],
+            [
+                lldb_path,
+                "--batch",
+                "-o",
+                'script -l python -- print("Has", "Python", "!")',
+            ],
             stderr=subprocess.DEVNULL,
             universal_newlines=True,
         )

>From 7625dd297dfc00beedace56da62a2ee6bcab5fed Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 18 Jun 2026 12:47:20 -0400
Subject: [PATCH 05/10] Add license headers

---
 libcxx/test/libcxx/lldb/map_formatter.sh.test         | 8 ++++++++
 libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test | 8 ++++++++
 libcxx/test/libcxx/lldb/vector_formatter.sh.test      | 8 ++++++++
 libcxx/utils/lldb/libcxx/libcxx.py                    | 4 ++++
 libcxx/utils/lldb/libcxx/libcxx_map_formatter.py      | 4 ++++
 libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py   | 4 ++++
 6 files changed, 36 insertions(+)

diff --git a/libcxx/test/libcxx/lldb/map_formatter.sh.test b/libcxx/test/libcxx/lldb/map_formatter.sh.test
index a1aefdf405b151..dab2f338780b72 100644
--- a/libcxx/test/libcxx/lldb/map_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/map_formatter.sh.test
@@ -1,3 +1,11 @@
+# ===----------------------------------------------------------------------===##
+#
+# 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
+#
+# ===----------------------------------------------------------------------===##
+
 # REQUIRES: host-has-lldb-with-python
 # REQUIRES: has-filecheck
 # REQUIRES: has-splitfile
diff --git a/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test b/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
index 45624ae2262d12..1729b20b3f7394 100644
--- a/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
@@ -1,3 +1,11 @@
+# ===----------------------------------------------------------------------===##
+#
+# 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
+#
+# ===----------------------------------------------------------------------===##
+
 # REQUIRES: host-has-lldb-with-python
 # REQUIRES: has-filecheck
 # REQUIRES: has-splitfile
diff --git a/libcxx/test/libcxx/lldb/vector_formatter.sh.test b/libcxx/test/libcxx/lldb/vector_formatter.sh.test
index 518d946c589009..375995ac1df449 100644
--- a/libcxx/test/libcxx/lldb/vector_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/vector_formatter.sh.test
@@ -1,3 +1,11 @@
+# ===----------------------------------------------------------------------===##
+#
+# 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
+#
+# ===----------------------------------------------------------------------===##
+
 # REQUIRES: host-has-lldb-with-python
 # REQUIRES: has-filecheck
 # REQUIRES: has-splitfile
diff --git a/libcxx/utils/lldb/libcxx/libcxx.py b/libcxx/utils/lldb/libcxx/libcxx.py
index e78d708b7da2d9..bd23cf42f27394 100644
--- a/libcxx/utils/lldb/libcxx/libcxx.py
+++ b/libcxx/utils/lldb/libcxx/libcxx.py
@@ -1,6 +1,10 @@
+# ===----------------------------------------------------------------------===##
+#
 # 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
+#
+# ===----------------------------------------------------------------------===##
 
 # Entry point for LLDB to register data formatters.
 
diff --git a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
index 22f26ff7830ae1..215804a4f0eb07 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
@@ -1,6 +1,10 @@
+# ===----------------------------------------------------------------------===##
+#
 # 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
+#
+# ===----------------------------------------------------------------------===##
 
 # LLDB data formatters for libc++ std::(multi)map and std::(multi)set
 
diff --git a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
index 6b542af45baf22..6d158999510dbc 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
@@ -1,6 +1,10 @@
+# ===----------------------------------------------------------------------===##
+#
 # 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
+#
+# ===----------------------------------------------------------------------===##
 
 # LLDB data formatter for libc++ std::vector
 

>From b94bce7b6fc6879ee0bf463b5587e461ee2f33bb Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 10 Aug 2026 16:44:48 -0400
Subject: [PATCH 06/10] Try getting the just-built LLDB detected

---
 libcxx/utils/ci/run-buildbot | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot
index d47061f3e1947a..4b830568f143e7 100755
--- a/libcxx/utils/ci/run-buildbot
+++ b/libcxx/utils/ci/run-buildbot
@@ -479,6 +479,12 @@ bootstrapping-build)
           -DLLVM_ENABLE_ASSERTIONS=ON \
           -DLLVM_LIT_ARGS="-sv --xunit-xml-output test-results.xml --timeout=1500 --time-tests"
 
+    step "Install clang and LLDB to a fake location"
+    ${NINJA} -vC "${BUILD_DIR}" install-clang install-lldb
+
+    # TODO: Find a cleaner way to get the libc++ test suite to grab the just-built LLDB
+    export PATH="${PATH}:${INSTALL_DIR}/bin"
+
     step "Running the libc++ and libc++abi tests"
     ${NINJA} -vC "${BUILD_DIR}" check-runtimes
 

>From 981956bf141b02055ba8d1449bfcec7a1242f6e2 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 11 Aug 2026 07:39:56 -0400
Subject: [PATCH 07/10] LLDB_ENABLE_PYTHON

---
 libcxx/utils/ci/run-buildbot | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot
index 4b830568f143e7..7b62ca53b71a3e 100755
--- a/libcxx/utils/ci/run-buildbot
+++ b/libcxx/utils/ci/run-buildbot
@@ -477,6 +477,7 @@ bootstrapping-build)
           -DRUNTIMES_BUILD_ALLOW_DARWIN=ON \
           -DCOMPILER_RT_INCLUDE_TESTS=OFF \
           -DLLVM_ENABLE_ASSERTIONS=ON \
+          -DLLDB_ENABLE_PYTHON=ON \
           -DLLVM_LIT_ARGS="-sv --xunit-xml-output test-results.xml --timeout=1500 --time-tests"
 
     step "Install clang and LLDB to a fake location"

>From d56e116bcaa61e4b4d1d6aa6ab813d6c25901fcb Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 11 Aug 2026 09:57:01 -0400
Subject: [PATCH 08/10] Fix -Wunused-variable errors

---
 libcxx/test/libcxx/lldb/map_formatter.sh.test         |  2 ++
 libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test |  2 ++
 libcxx/test/libcxx/lldb/vector_formatter.sh.test      | 10 ++++++++--
 3 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/libcxx/test/libcxx/lldb/map_formatter.sh.test b/libcxx/test/libcxx/lldb/map_formatter.sh.test
index dab2f338780b72..b63669452d217b 100644
--- a/libcxx/test/libcxx/lldb/map_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/map_formatter.sh.test
@@ -10,6 +10,7 @@
 # REQUIRES: has-filecheck
 # REQUIRES: has-splitfile
 # REQUIRES: optimization=none
+# UNSUPPORTED: c++03
 
 # RUN: split-file %s %{temp}
 # RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
@@ -34,6 +35,7 @@ int main(int, char**) {
     {1, 2},
     {3, 4},
   };
+  (void)mii;
 
   return 0;
 }
diff --git a/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test b/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
index 1729b20b3f7394..f8f06419206a65 100644
--- a/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
@@ -10,6 +10,7 @@
 # REQUIRES: has-filecheck
 # REQUIRES: has-splitfile
 # REQUIRES: optimization=none
+# UNSUPPORTED: c++03
 
 # RUN: split-file %s %{temp}
 # RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
@@ -32,6 +33,7 @@
 
 int main(int, char**) {
   std::vector<bool> vb = {true, false, false};
+  (void)vb;
 
   return 0;
 }
diff --git a/libcxx/test/libcxx/lldb/vector_formatter.sh.test b/libcxx/test/libcxx/lldb/vector_formatter.sh.test
index 375995ac1df449..81edf30f9849f7 100644
--- a/libcxx/test/libcxx/lldb/vector_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/vector_formatter.sh.test
@@ -10,6 +10,7 @@
 # REQUIRES: has-filecheck
 # REQUIRES: has-splitfile
 # REQUIRES: optimization=none
+# UNSUPPORTED: c++03
 
 # RUN: split-file %s %{temp}
 # RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
@@ -105,6 +106,7 @@ void break_here() {}
 int main() {
   std::vector<float> empty;
   std::vector<int> vi = {1, 2};
+  (void)empty;
 
   break_here();
 
@@ -121,6 +123,7 @@ int main() {
   vi.push_back(10);
   vi.push_back(11);
   std::vector<std::vector<int>> vec_of_vec{vi};
+  (void)vec_of_vec;
 
   break_here();
 
@@ -128,11 +131,14 @@ int main() {
     "Foo",
     "Bar"
   };
+  (void)vs;
 
   break_here();
 
-  [[maybe_unused]] const auto &vec_ref = vi;
-  [[maybe_unused]] const auto *vec_ptr = &vi;
+  const auto &vec_ref = vi;
+  const auto *vec_ptr = &vi;
+  (void)vec_ref;
+  (void)vec_ptr;
 
   break_here();
 }

>From 576fe567dfb852425c385e7f081a25e1b6a313a8 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Mon, 21 Sep 2026 00:36:26 -0700
Subject: [PATCH 09/10] [libc++/lldb] Fix data-formatter test setup and cache
 invalidation

Install lldb-server alongside lldb in the bootstrapping build. Without
it LLDB cannot launch a process on Linux, so every formatter test
failed at `run` and never reached a formatter.

Register the vector synthetic with --requires-dereference, the actual
name of the option. The previous spelling was rejected outright, which
left std::vector with no synthetic provider at all.

Return False from the vector update() methods so LLDB refetches
children at each stop, matching the C++ formatters these were
translated from. Returning True freezes the previously reported
children, so a vector that grows or is cleared keeps its stale size.

Copy cached map iterators before advancing them. They are stored by
value in C++; sharing the object meant fetching one index moved the
iterator cached for another.

Drop the map's get_child_index now that ValueObjectSynthetic handles
"[N]" names automatically, and with a bounds check the handwritten
version lacked.

Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
 libcxx/utils/ci/run-buildbot                  |  4 +++-
 libcxx/utils/lldb/libcxx/libcxx.py            |  2 +-
 .../utils/lldb/libcxx/libcxx_map_formatter.py | 23 +++++++++++--------
 .../lldb/libcxx/libcxx_vector_formatter.py    | 11 +++++----
 4 files changed, 24 insertions(+), 16 deletions(-)

diff --git a/libcxx/utils/ci/run-buildbot b/libcxx/utils/ci/run-buildbot
index 58792656e296b9..693e56f509ee4a 100755
--- a/libcxx/utils/ci/run-buildbot
+++ b/libcxx/utils/ci/run-buildbot
@@ -487,7 +487,9 @@ bootstrapping-build)
           -DLLVM_LIT_ARGS="-sv --xunit-xml-output test-results.xml --timeout=1500 --time-tests"
 
     step "Install clang and LLDB to a fake location"
-    ${NINJA} -vC "${BUILD_DIR}" install-clang install-lldb
+    # lldb-server is a separate install component, but LLDB needs it to launch
+    # a process on Linux.
+    ${NINJA} -vC "${BUILD_DIR}" install-clang install-lldb install-lldb-server
 
     # TODO: Find a cleaner way to get the libc++ test suite to grab the just-built LLDB
     export PATH="${PATH}:${INSTALL_DIR}/bin"
diff --git a/libcxx/utils/lldb/libcxx/libcxx.py b/libcxx/utils/lldb/libcxx/libcxx.py
index bd23cf42f27394..21e698806402bd 100644
--- a/libcxx/utils/lldb/libcxx/libcxx.py
+++ b/libcxx/utils/lldb/libcxx/libcxx.py
@@ -48,7 +48,7 @@ def __lldb_init_module(debugger, dict):
         debugger,
         "^std::__[[:alnum:]]+::vector<.+>$",
         "LibCxxStdVectorSyntheticFrontendCreator",
-        "--wants-dereference",
+        "--requires-dereference true",
     )
 
     # Enables registered formatters in LLDB. The builtin LLDB formatters are registered in the `cplusplus` category.
diff --git a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
index 215804a4f0eb07..c8d552e5714847 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
@@ -80,6 +80,18 @@ def __init__(self, entry=None, depth=0):
     def value(self):
         return self.m_entry.get_entry()
 
+    def copy(self):
+        """
+        Returns an independent copy positioned at the same entry.
+
+        Iterators are cached per index and advanced in place, so a caller that
+        resumes from a cached iterator must work on a copy to avoid moving the
+        entry that the cache still describes.
+        """
+        other = MapIterator(self.m_entry.get_entry(), self.m_max_depth)
+        other.m_error = self.m_error
+        return other
+
     def advance(self, count):
         """Advance the iterator by count steps and return the entry."""
         if self.m_error:
@@ -227,15 +239,6 @@ def _calculate_num_children_for_old_compressed_pair_layout(self, pair):
         self.m_count = node_sp.GetValueAsUnsigned(0)
         return self.m_count
 
-    def get_child_index(self, name):
-        """Get the index of a child with the given name (e.g., "[0]" -> 0)."""
-        try:
-            if name.startswith("[") and name.endswith("]"):
-                return int(name[1:-1])
-        except ValueError:
-            pass
-        return None
-
     def get_child_at_index(self, index):
         num_children = self.num_children()
         if index >= num_children:
@@ -311,7 +314,7 @@ def _get_key_value_pair(self, idx, max_depth):
             # If we have already created the iterator for the previous
             # index, we can start from there and advance by 1.
             if idx - 1 in self.m_iterators:
-                iterator = self.m_iterators[idx - 1]
+                iterator = self.m_iterators[idx - 1].copy()
                 advance_by = 1
 
         iterated_sp = iterator.advance(advance_by)
diff --git a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
index 6d158999510dbc..32663a1502f8d5 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
@@ -65,8 +65,6 @@ def num_children(self):
 
         return num_children // self.m_element_size
 
-    # FIXME: newer versions of LLDB auto-generate this.
-    # Eventually this can be removed.
     def get_child_at_index(self, index):
         if not self.m_start or not self.m_finish:
             return None
@@ -109,7 +107,7 @@ def update(self):
         self.m_start = begin_sp
         self.m_finish = end_sp
 
-        return True
+        return False
 
 
 class LibCxxVectorBoolSyntheticFrontEnd:
@@ -188,6 +186,11 @@ def get_child_at_index(self, index):
         return retval_sp
 
     def update(self):
+        """
+        Returning False asks LLDB to re-run this on every stop. Returning True
+        freezes the children we last reported, so it is only safe when there
+        are none to go stale.
+        """
         self.m_children = {}
 
         if not self.valobj:
@@ -211,7 +214,7 @@ def update(self):
             self.m_count = 0
             return False
 
-        return True
+        return False
 
 
 def LibCxxStdVectorSyntheticFrontendCreator(valobj, internal_dict):

>From 1c30fe248f85ac2cbbf834b8bd328484e9cd8107 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Wed, 21 Oct 2026 18:44:45 -0700
Subject: [PATCH 10/10] [libc++/lldb] Fix vector formatter for the
 __vector_layout ABI

libc++ moved std::vector's pointers into a `__layout_` subobject in
#155330. The Python vector formatter still looked for `__cap_` and
`__end_cap_` directly on the vector, gave up, and reported every vector
as empty, which broke vector_formatter.sh.test in the bootstrapping
build. Look the members up through `__layout_` when it is present, take
the element type from `__begin_`, and support the size-based layout,
matching LLDB's builtin formatter.

Create vector children through the vector's SBValue instead of the
SBTarget. LLDB caches synthetic children by raw pointer and only keeps
SBValue-created children alive, so `frame var vi` followed by
`frame var vi[2]` read freed memory.

Fix two test expectations that never matched: the `vi[0]` error is
preceded by a caret line, and `vec_ptr[1]` is pointer arithmetic rather
than an index into the pointee, so check `(*vec_ptr)[1]` instead.

Derive the providers from lldb.plugins' ScriptedSyntheticChildren and
drop the map providers' has_children overrides, which only restated the
base class default. Move the tests' shared REQUIRES and UNSUPPORTED
lines into a lit.local.cfg.

Assisted-by: Claude
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
 libcxx/test/libcxx/lldb/lit.local.cfg         | 17 ++++
 libcxx/test/libcxx/lldb/map_formatter.sh.test |  6 --
 .../libcxx/lldb/vector_bool_formatter.sh.test |  6 --
 .../test/libcxx/lldb/vector_formatter.sh.test | 14 +--
 .../utils/lldb/libcxx/libcxx_map_formatter.py | 16 ++--
 .../lldb/libcxx/libcxx_vector_formatter.py    | 85 ++++++++-----------
 6 files changed, 62 insertions(+), 82 deletions(-)
 create mode 100644 libcxx/test/libcxx/lldb/lit.local.cfg

diff --git a/libcxx/test/libcxx/lldb/lit.local.cfg b/libcxx/test/libcxx/lldb/lit.local.cfg
new file mode 100644
index 00000000000000..d4c8f76dd544f8
--- /dev/null
+++ b/libcxx/test/libcxx/lldb/lit.local.cfg
@@ -0,0 +1,17 @@
+# The tests in this directory drive LLDB to exercise the libc++ data
+# formatters, so they all share the same requirements.
+_required_features = [
+  # The formatters themselves are Python.
+  "host-has-lldb-with-python",
+  "has-filecheck",
+  "has-splitfile",
+  # The formatters read type layouts out of debug info, which optimized
+  # builds are free to drop.
+  "optimization=none",
+]
+
+if any(f not in config.available_features for f in _required_features):
+  config.unsupported = True
+
+if "c++03" in config.available_features:
+  config.unsupported = True
diff --git a/libcxx/test/libcxx/lldb/map_formatter.sh.test b/libcxx/test/libcxx/lldb/map_formatter.sh.test
index b63669452d217b..ed0e79bb923a99 100644
--- a/libcxx/test/libcxx/lldb/map_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/map_formatter.sh.test
@@ -6,12 +6,6 @@
 #
 # ===----------------------------------------------------------------------===##
 
-# REQUIRES: host-has-lldb-with-python
-# REQUIRES: has-filecheck
-# REQUIRES: has-splitfile
-# REQUIRES: optimization=none
-# UNSUPPORTED: c++03
-
 # RUN: split-file %s %{temp}
 # RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
 
diff --git a/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test b/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
index f8f06419206a65..f664bbaa9ce025 100644
--- a/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/vector_bool_formatter.sh.test
@@ -6,12 +6,6 @@
 #
 # ===----------------------------------------------------------------------===##
 
-# REQUIRES: host-has-lldb-with-python
-# REQUIRES: has-filecheck
-# REQUIRES: has-splitfile
-# REQUIRES: optimization=none
-# UNSUPPORTED: c++03
-
 # RUN: split-file %s %{temp}
 # RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
 
diff --git a/libcxx/test/libcxx/lldb/vector_formatter.sh.test b/libcxx/test/libcxx/lldb/vector_formatter.sh.test
index 81edf30f9849f7..9c5221b6053405 100644
--- a/libcxx/test/libcxx/lldb/vector_formatter.sh.test
+++ b/libcxx/test/libcxx/lldb/vector_formatter.sh.test
@@ -6,12 +6,6 @@
 #
 # ===----------------------------------------------------------------------===##
 
-# REQUIRES: host-has-lldb-with-python
-# REQUIRES: has-filecheck
-# REQUIRES: has-splitfile
-# REQUIRES: optimization=none
-# UNSUPPORTED: c++03
-
 # RUN: split-file %s %{temp}
 # RUN: %{cxx} %{flags} %{temp}/main.cpp -o %t.out %{compile_flags} -g %{link_flags}
 
@@ -50,7 +44,7 @@
 
 # CHECK: (lldb) continue
 # CHECK: (lldb) frame var vi[0]
-# CHECK-NEXT: array index 0 is not valid
+# CHECK: array index 0 is not valid
 
 # CHECK: (lldb) frame var vi
 # CHECK-NEXT: (std::vector<int>) vi = size=0 {}
@@ -93,8 +87,8 @@
 # CHECK: (lldb) frame var vec_ptr
 # CHECK-NEXT: (const std::vector<int> *) vec_ptr = 0x{{.*}} size=2
 
-# CHECK: (lldb) frame var vec_ptr[1]
-# CHECK-NEXT: (int) vec_ptr[1] = 11
+# CHECK: (lldb) frame var (*vec_ptr)[1]
+# CHECK-NEXT: (int) (*vec_ptr)[1] = 11
 
 #--- main.cpp
 
@@ -171,4 +165,4 @@ continue
 frame var vec_ref
 frame var vec_ref[1]
 frame var vec_ptr
-frame var vec_ptr[1]
+frame var (*vec_ptr)[1]
diff --git a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
index c8d552e5714847..986099e4ea2b28 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_map_formatter.py
@@ -9,6 +9,7 @@
 # LLDB data formatters for libc++ std::(multi)map and std::(multi)set
 
 import lldb
+from lldb.plugins.scripted_synthetic_children import ScriptedSyntheticChildren
 
 
 class MapEntry:
@@ -197,11 +198,11 @@ def _get_value_or_old_compressed_pair(tree, value_name, pair_name):
     return (None, False)
 
 
-class LibcxxStdMapSyntheticProvider:
+class LibcxxStdMapSyntheticProvider(ScriptedSyntheticChildren):
     """Synthetic children provider for libc++ std::map."""
 
     def __init__(self, valobj, internal_dict):
-        self.valobj = valobj
+        super().__init__(valobj)
         self.m_tree = None
         self.m_root_node = None
         self.m_node_ptr_type = None
@@ -298,10 +299,6 @@ def update(self):
 
         return False
 
-    def has_children(self):
-        """Check if this object has children."""
-        return True
-
     def _get_key_value_pair(self, idx, max_depth):
         """
         Returns the ValueObject for the __tree_node type that
@@ -341,11 +338,11 @@ def _get_key_value_pair(self, idx, max_depth):
         return value_type_sp
 
 
-class LibCxxMapIteratorSyntheticProvider:
+class LibCxxMapIteratorSyntheticProvider(ScriptedSyntheticChildren):
     """Synthetic children provider for libc++ std::map::iterator."""
 
     def __init__(self, valobj, internal_dict):
-        self.valobj = valobj
+        super().__init__(valobj)
         self.m_pair_sp = None
 
     def num_children(self):
@@ -418,6 +415,3 @@ def update(self):
 
         self.m_pair_sp = key_value_sp
         return False
-
-    def has_children(self):
-        return True
diff --git a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
index 32663a1502f8d5..d188e471ced69a 100644
--- a/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
+++ b/libcxx/utils/lldb/libcxx/libcxx_vector_formatter.py
@@ -10,39 +10,19 @@
 
 
 import lldb
+from lldb.plugins.scripted_synthetic_children import ScriptedSyntheticChildren
 
 
-def get_data_pointer(root):
-    """Get the data pointer from a vector, handling compressed pair layout."""
-    # Try new layout
-    cap_sp = root.GetChildMemberWithName("__cap_")
-    if cap_sp:
-        return cap_sp
-
-    # Try old compressed pair layout
-    end_cap_sp = root.GetChildMemberWithName("__end_cap_")
-    if not end_cap_sp:
-        return None
-
-    # Get first value of compressed pair
-    value_sp = end_cap_sp.GetChildMemberWithName("__value_")
-    if value_sp:
-        return value_sp
-
-    first_sp = end_cap_sp.GetChildMemberWithName("__first_")
-    if first_sp:
-        return first_sp
-
-    return None
-
-
-class LibCxxStdVectorSyntheticFrontEnd:
+class LibCxxStdVectorSyntheticFrontEnd(ScriptedSyntheticChildren):
     """Synthetic children frontend for libc++ std::vector."""
 
     def __init__(self, valobj, internal_dict):
-        self.valobj = valobj
+        super().__init__(valobj)
         self.m_start = None
+        # Either the `__end_` pointer or the `__size_` integer, depending on
+        # which layout libc++ was built with. See `m_size_based`.
         self.m_finish = None
+        self.m_size_based = False
         self.m_element_type = None
         self.m_element_size = 0
 
@@ -50,6 +30,9 @@ def num_children(self):
         if not self.m_start or not self.m_finish:
             return 0
 
+        if self.m_size_based:
+            return self.m_finish.GetValueAsUnsigned(0)
+
         start_val = self.m_start.GetValueAsUnsigned(0)
         finish_val = self.m_finish.GetValueAsUnsigned(0)
 
@@ -73,48 +56,56 @@ def get_child_at_index(self, index):
         offset = offset + self.m_start.GetValueAsUnsigned(0)
 
         name = f"[{index}]"
-        target = self.valobj.GetTarget()
-        if not target:
-            return None
-
-        addr = lldb.SBAddress(offset, target)
-        return target.CreateValueFromAddress(name, addr, self.m_element_type)
+        # Create the child from the vector's SBValue rather than the SBTarget:
+        # LLDB caches synthetic children, and only children created this way
+        # are kept alive by the parent once Python drops its reference.
+        return self.valobj.CreateValueFromAddress(name, offset, self.m_element_type)
 
     def update(self):
         self.m_start = None
         self.m_finish = None
 
-        data_sp = get_data_pointer(self.valobj)
-        if not data_sp:
+        # Newer libc++ versions keep the vector's members in a `__layout_`
+        # subobject rather than directly in the vector.
+        members = self.valobj.GetChildMemberWithName("__layout_")
+        if not members:
+            members = self.valobj
+
+        begin_sp = members.GetChildMemberWithName("__begin_")
+        if not begin_sp:
             return False
 
-        self.m_element_type = data_sp.GetType().GetPointeeType()
+        self.m_element_type = begin_sp.GetType().GetPointeeType()
         if not self.m_element_type:
             return False
 
         size = self.m_element_type.GetByteSize()
-        if not size or size == 0:
+        if not size:
             return False
 
         self.m_element_size = size
+        self.m_start = begin_sp
 
-        begin_sp = self.valobj.GetChildMemberWithName("__begin_")
-        end_sp = self.valobj.GetChildMemberWithName("__end_")
-
-        if not begin_sp:
+        end_sp = members.GetChildMemberWithName("__end_")
+        if end_sp:
+            self.m_finish = end_sp
+            self.m_size_based = False
             return False
 
-        self.m_start = begin_sp
-        self.m_finish = end_sp
+        size_sp = members.GetChildMemberWithName("__size_")
+        if not size_sp:
+            return False
 
+        self.m_finish = size_sp
+        self.m_size_based = True
         return False
 
 
-class LibCxxVectorBoolSyntheticFrontEnd:
+class LibCxxVectorBoolSyntheticFrontEnd(ScriptedSyntheticChildren):
     """Synthetic children frontend for libc++ std::vector<bool>."""
 
     def __init__(self, valobj, internal_dict):
-        self.valobj = valobj
+        super().__init__(valobj)
         self.m_bool_type = None
         self.m_count = 0
         self.m_base_data_address = 0
@@ -175,11 +166,7 @@ def get_child_at_index(self, index):
         )
 
         name = "[%d]" % index
-        target = self.valobj.GetTarget()
-        if not target:
-            return None
-
-        retval_sp = target.CreateValueFromData(name, data, self.m_bool_type)
+        retval_sp = self.valobj.CreateValueFromData(name, data, self.m_bool_type)
         if retval_sp:
             self.m_children[index] = retval_sp
 



More information about the libcxx-commits mailing list