[Lldb-commits] [lldb] ea9ff9f - [LLDB][formatters] Add formatter for libc++'s std::span

Adrian Prantl via lldb-commits lldb-commits at lists.llvm.org
Mon Jun 13 12:59:45 PDT 2022


Author: Adrian Prantl
Date: 2022-06-13T12:59:38-07:00
New Revision: ea9ff9fac3a6ea77b488081dd9faabc8fe334b46

URL: https://github.com/llvm/llvm-project/commit/ea9ff9fac3a6ea77b488081dd9faabc8fe334b46
DIFF: https://github.com/llvm/llvm-project/commit/ea9ff9fac3a6ea77b488081dd9faabc8fe334b46.diff

LOG: [LLDB][formatters] Add formatter for libc++'s std::span

This patch adds a libcxx formatter for std::span. The
implementation is based on the libcxx formatter for
std::vector. The main difference is the fact that
std::span conditionally has a __size member based
on whether it has a static or dynamic extent.

Example output of formatted span:

(std::span<const int, 18446744073709551615>) $0 = size=6 {
  [0] = 0
  [1] = 1
  [2] = 2
  [3] = 3
  [4] = 4
  [5] = 5
}
The second template parameter here is actually std::dynamic_extent,
but the type declaration we get back from the TypeSystemClang is the
actual value (which in this case is (size_t)-1). This is consistent
with diagnostics from clang, which doesn't desugar this value either.
E.g.,:

span.cpp:30:31: error: implicit instantiation of undefined template
    'Undefined<std::span<int, 18446744073709551615>>'
Testing:

Added API-tests

Confirmed manually using LLDB cli that printing spans works in various scenarios

Patch by Michael Buch!

Differential Revision: https://reviews.llvm.org/D127481

Added: 
    lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py
    lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp

Modified: 
    lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
    lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
    lldb/source/Plugins/Language/CPlusPlus/LibCxx.h

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index 125c12cfe4a5..80135daf4cfd 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -11,6 +11,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
   LibCxxList.cpp
   LibCxxMap.cpp
   LibCxxQueue.cpp
+  LibCxxSpan.cpp
   LibCxxTuple.cpp
   LibCxxUnorderedMap.cpp
   LibCxxVariant.cpp

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index 08b6d89e55f7..82f825871593 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -756,6 +756,12 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
       lldb_private::formatters::LibcxxAtomicSyntheticFrontEndCreator,
       "libc++ std::atomic synthetic children",
       ConstString("^std::__[[:alnum:]]+::atomic<.+>$"), stl_synth_flags, true);
+  AddCXXSynthetic(
+      cpp_category_sp,
+      lldb_private::formatters::LibcxxStdSpanSyntheticFrontEndCreator,
+      "libc++ std::span synthetic children",
+      ConstString("^std::__[[:alnum:]]+::span<.+>(( )?&)?$"), stl_deref_flags,
+      true);
 
   cpp_category_sp->GetRegexTypeSyntheticsContainer()->Add(
       RegularExpression("^(std::__[[:alnum:]]+::)deque<.+>(( )?&)?$"),
@@ -869,6 +875,11 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
                 "libc++ std::variant summary provider",
                 ConstString("^std::__[[:alnum:]]+::variant<.+>(( )?&)?$"),
                 stl_summary_flags, true);
+  AddCXXSummary(cpp_category_sp,
+                lldb_private::formatters::LibcxxContainerSummaryProvider,
+                "libc++ std::span summary provider",
+                ConstString("^std::__[[:alnum:]]+::span<.+>(( )?&)?$"),
+                stl_summary_flags, true);
 
   stl_summary_flags.SetSkipPointers(true);
 

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
index 0f166ae24912..b4e789e65b51 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
@@ -74,6 +74,10 @@ LibcxxVectorBoolSyntheticFrontEndCreator(CXXSyntheticChildren *,
 bool LibcxxContainerSummaryProvider(ValueObject &valobj, Stream &stream,
                                     const TypeSummaryOptions &options);
 
+/// Formatter for libc++ std::span<>.
+bool LibcxxSpanSummaryProvider(ValueObject &valobj, Stream &stream,
+                               const TypeSummaryOptions &options);
+
 class LibCxxMapIteratorSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
 public:
   LibCxxMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
@@ -193,6 +197,10 @@ SyntheticChildrenFrontEnd *
 LibcxxVariantFrontEndCreator(CXXSyntheticChildren *,
                              lldb::ValueObjectSP valobj_sp);
 
+SyntheticChildrenFrontEnd *
+LibcxxStdSpanSyntheticFrontEndCreator(CXXSyntheticChildren *,
+                                      lldb::ValueObjectSP);
+
 } // namespace formatters
 } // namespace lldb_private
 

diff  --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp
new file mode 100644
index 000000000000..0379ba2d85ad
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp
@@ -0,0 +1,151 @@
+//===-- LibCxxSpan.cpp ----------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "LibCxx.h"
+
+#include "lldb/Core/ValueObject.h"
+#include "lldb/DataFormatters/FormattersHelpers.h"
+#include "lldb/Utility/ConstString.h"
+#include "llvm/ADT/APSInt.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::formatters;
+
+namespace lldb_private {
+namespace formatters {
+
+class LibcxxStdSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
+public:
+  LibcxxStdSpanSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
+
+  ~LibcxxStdSpanSyntheticFrontEnd() override = default;
+
+  size_t CalculateNumChildren() override;
+
+  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
+
+  /// Determines properties of the std::span<> associated with this object
+  //
+  // std::span can either be instantiated with a compile-time known
+  // extent or a std::dynamic_extent (this is the default if only the
+  // type template argument is provided). The layout of std::span
+  // depends on whether the extent is dynamic or not. For static
+  // extents (e.g., std::span<int, 9>):
+  //
+  // (std::__1::span<const int, 9>) s = {
+  //   __data = 0x000000016fdff494
+  // }
+  //
+  // For dynamic extents, e.g., std::span<int>, the layout is:
+  //
+  // (std::__1::span<const int, 18446744073709551615>) s = {
+  //   __data = 0x000000016fdff494
+  //   __size = 6
+  // }
+  //
+  // This function checks for a '__size' member to determine the number
+  // of elements in the span. If no such member exists, we get the size
+  // from the only other place it can be: the template argument.
+  bool Update() override;
+
+  bool MightHaveChildren() override;
+
+  size_t GetIndexOfChildWithName(ConstString name) override;
+
+private:
+  ValueObject *m_start = nullptr; ///< First element of span. Held, not owned.
+  CompilerType m_element_type{};  ///< Type of span elements.
+  size_t m_num_elements = 0;      ///< Number of elements in span.
+  uint32_t m_element_size = 0;    ///< Size in bytes of each span element.
+};
+
+lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    LibcxxStdSpanSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
+    : SyntheticChildrenFrontEnd(*valobj_sp) {
+  if (valobj_sp)
+    Update();
+}
+
+size_t lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    CalculateNumChildren() {
+  return m_num_elements;
+}
+
+lldb::ValueObjectSP
+lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::GetChildAtIndex(
+    size_t idx) {
+  if (!m_start)
+    return {};
+
+  uint64_t offset = idx * m_element_size;
+  offset = offset + m_start->GetValueAsUnsigned(0);
+  StreamString name;
+  name.Printf("[%" PRIu64 "]", (uint64_t)idx);
+  return CreateValueObjectFromAddress(name.GetString(), offset,
+                                      m_backend.GetExecutionContextRef(),
+                                      m_element_type);
+}
+
+bool lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::Update() {
+  // Get element type.
+  ValueObjectSP data_type_finder_sp(
+      m_backend.GetChildMemberWithName(ConstString("__data"), true));
+  if (!data_type_finder_sp)
+    return false;
+
+  m_element_type = data_type_finder_sp->GetCompilerType().GetPointeeType();
+
+  // Get element size.
+  if (llvm::Optional<uint64_t> size = m_element_type.GetByteSize(nullptr)) {
+    m_element_size = *size;
+
+    // Get data.
+    if (m_element_size > 0) {
+      m_start = data_type_finder_sp.get();
+    }
+
+    // Get number of elements.
+    if (auto size_sp =
+            m_backend.GetChildMemberWithName(ConstString("__size"), true)) {
+      m_num_elements = size_sp->GetValueAsUnsigned(0);
+    } else if (auto arg =
+                   m_backend.GetCompilerType().GetIntegralTemplateArgument(1)) {
+
+      m_num_elements = arg->value.getLimitedValue();
+    }
+  }
+
+  return true;
+}
+
+bool lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    MightHaveChildren() {
+  return true;
+}
+
+size_t lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    GetIndexOfChildWithName(ConstString name) {
+  if (!m_start)
+    return UINT32_MAX;
+  return ExtractIndexFromString(name.GetCString());
+}
+
+lldb_private::SyntheticChildrenFrontEnd *
+LibcxxStdSpanSyntheticFrontEndCreator(CXXSyntheticChildren *,
+                                      lldb::ValueObjectSP valobj_sp) {
+  if (!valobj_sp)
+    return nullptr;
+  CompilerType type = valobj_sp->GetCompilerType();
+  if (!type.IsValid() || type.GetNumTemplateArguments() != 2)
+    return nullptr;
+  return new LibcxxStdSpanSyntheticFrontEnd(valobj_sp);
+}
+
+} // namespace formatters
+} // namespace lldb_private

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile
new file mode 100644
index 000000000000..20c9cf06b1a6
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile
@@ -0,0 +1,7 @@
+CXX_SOURCES := main.cpp
+
+USE_LIBCPP := 1
+
+CXXFLAGS_EXTRAS := -std=c++20
+
+include Makefile.rules

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py
new file mode 100644
index 000000000000..207e7458c9a8
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py
@@ -0,0 +1,153 @@
+"""
+Test lldb data formatter subsystem for std::span
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class LibcxxSpanDataFormatterTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def findVariable(self, name):
+        var = self.frame().FindVariable(name)
+        self.assertTrue(var.IsValid())
+        return var
+
+    def check_size(self, var_name, size):
+        var = self.findVariable(var_name)
+        self.assertEqual(var.GetNumChildren(), size)
+
+    def check_numbers(self, var_name):
+        """Helper to check that data formatter sees contents of std::span correctly"""
+
+        expectedSize = 5
+        self.check_size(var_name, expectedSize)
+
+        self.expect_expr(
+                var_name, result_type=f'std::span<int, {expectedSize}>',
+                result_summary=f'size={expectedSize}',
+                result_children=[
+                    ValueCheck(name='[0]', value='1'),
+                    ValueCheck(name='[1]', value='12'),
+                    ValueCheck(name='[2]', value='123'),
+                    ValueCheck(name='[3]', value='1234'),
+                    ValueCheck(name='[4]', value='12345')
+        ])
+
+        # check access-by-index
+        self.expect_var_path(f'{var_name}[0]', type='int', value='1')
+        self.expect_var_path(f'{var_name}[1]', type='int', value='12')
+        self.expect_var_path(f'{var_name}[2]', type='int', value='123')
+        self.expect_var_path(f'{var_name}[3]', type='int', value='1234')
+        self.expect_var_path(f'{var_name}[4]', type='int', value='12345')
+
+    @add_test_categories(['libc++'])
+    @skipIf(compiler='clang', compiler_version=['<', '11.0'])
+    def test_with_run_command(self):
+        """Test that std::span variables are formatted correctly when printed."""
+        self.build()
+        (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, 'break here', lldb.SBFileSpec('main.cpp', False))
+
+        lldbutil.continue_to_breakpoint(process, bkpt)
+
+        # std::span of std::array with extents known at compile-time
+        self.check_numbers('numbers_span')
+
+        # check access to synthetic children for static spans
+        self.runCmd('type summary add --summary-string "item 0 is ${var[0]}" -x "std::span<" span')
+        self.expect_expr('numbers_span',
+                         result_type='std::span<int, 5>',
+                         result_summary='item 0 is 1')
+
+        self.runCmd('type summary add --summary-string "item 0 is ${svar[0]}" -x "std::span<" span')
+        self.expect_expr('numbers_span',
+                         result_type='std::span<int, 5>',
+                         result_summary='item 0 is 1')
+
+        self.runCmd('type summary delete span')
+
+        # New span with strings
+        lldbutil.continue_to_breakpoint(process, bkpt)
+
+        expectedStringSpanChildren = [ ValueCheck(name='[0]', summary='"smart"'),
+                                       ValueCheck(name='[1]', summary='"!!!"') ]
+
+        self.expect_var_path('strings_span',
+                             summary='size=2',
+                             children=expectedStringSpanChildren)
+
+        # check access to synthetic children for dynamic spans
+        self.runCmd('type summary add --summary-string "item 0 is ${var[0]}" dynamic_string_span')
+        self.expect_var_path('strings_span', summary='item 0 is "smart"')
+
+        self.runCmd('type summary add --summary-string "item 0 is ${svar[0]}" dynamic_string_span')
+        self.expect_var_path('strings_span', summary='item 0 is "smart"')
+
+        self.runCmd('type summary delete dynamic_string_span')
+
+        # test summaries based on synthetic children
+        self.runCmd(
+                'type summary add --summary-string "span has ${svar%#} items" -e dynamic_string_span')
+
+        self.expect_var_path('strings_span', summary='span has 2 items')
+
+        self.expect_var_path('strings_span',
+                             summary='span has 2 items',
+                             children=expectedStringSpanChildren)
+
+        # check access-by-index
+        self.expect_var_path('strings_span[0]', summary='"smart"');
+        self.expect_var_path('strings_span[1]', summary='"!!!"');
+
+        # Newly inserted value not visible to span
+        lldbutil.continue_to_breakpoint(process, bkpt)
+
+        self.expect_expr('strings_span',
+                         result_summary='span has 2 items',
+                         result_children=expectedStringSpanChildren)
+
+        self.runCmd('type summary delete dynamic_string_span')
+
+        lldbutil.continue_to_breakpoint(process, bkpt)
+
+        # Empty spans
+        self.expect_expr('static_zero_span',
+                         result_type='std::span<int, 0>',
+                         result_summary='size=0')
+        self.check_size('static_zero_span', 0)
+
+        self.expect_expr('dynamic_zero_span',
+                         result_summary='size=0')
+        self.check_size('dynamic_zero_span', 0)
+
+        # Nested spans
+        self.expect_expr('nested',
+                         result_summary='size=2',
+                         result_children=[
+                             ValueCheck(name='[0]', summary='size=2',
+                                        children=expectedStringSpanChildren),
+                             ValueCheck(name='[1]', summary='size=2',
+                                        children=expectedStringSpanChildren)
+        ])
+        self.check_size('nested', 2)
+
+    @add_test_categories(['libc++'])
+    @skipIf(compiler='clang', compiler_version=['<', '11.0'])
+    def test_ref_and_ptr(self):
+        """Test that std::span is correctly formatted when passed by ref and ptr"""
+        self.build()
+        (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, 'Stop here to check by ref', lldb.SBFileSpec('main.cpp', False))
+
+        # The reference should display the same was as the value did
+        self.check_numbers('ref')
+
+        # The pointer should just show the right number of elements:
+
+        ptrAddr = self.findVariable('ptr').GetValue()
+        self.expect_expr('ptr', result_type='std::span<int, 5> *',
+                         result_summary=f'{ptrAddr} size=5')

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp
new file mode 100644
index 000000000000..de93949ac26f
--- /dev/null
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp
@@ -0,0 +1,56 @@
+#include <array>
+#include <span>
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+template <class T, size_t N>
+void by_ref_and_ptr(std::span<T, N> &ref, std::span<T, N> *ptr) {
+  printf("Stop here to check by ref");
+  return;
+}
+
+int main() {
+  std::array numbers = {1, 12, 123, 1234, 12345};
+
+  using dynamic_string_span = std::span<std::string>;
+
+  // Test span of ints
+
+  //   Full view of numbers with static extent
+  std::span numbers_span = numbers;
+
+  printf("break here");
+
+  by_ref_and_ptr(numbers_span, &numbers_span);
+
+  // Test spans of strings
+  std::vector<std::string> strings{"goofy", "is", "smart", "!!!"};
+  strings.reserve(strings.size() + 1);
+
+  //   Partial view of strings with dynamic extent
+  dynamic_string_span strings_span{std::span{strings}.subspan(2)};
+
+  auto strings_span_it = strings_span.begin();
+
+  printf("break here");
+
+  //   Vector size doesn't increase, span should
+  //   print unchanged and the strings_span_it
+  //   remains valid
+  strings.emplace_back("???");
+
+  printf("break here");
+
+  // Now some empty spans
+  std::span<int, 0> static_zero_span;
+  std::span<int> dynamic_zero_span;
+
+  // Multiple spans
+  std::array span_arr{strings_span, strings_span};
+  std::span<std::span<std::string>, 2> nested = span_arr;
+
+  printf("break here");
+
+  return 0; // break here
+}


        


More information about the lldb-commits mailing list