[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