From ea9ff9fac3a6ea77b488081dd9faabc8fe334b46 Mon Sep 17 00:00:00 2001 From: Adrian Prantl Date: Mon, 13 Jun 2022 10:51:01 -0700 Subject: [PATCH] [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) $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>' 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 --- .../Plugins/Language/CPlusPlus/CMakeLists.txt | 1 + .../Language/CPlusPlus/CPlusPlusLanguage.cpp | 11 ++ lldb/source/Plugins/Language/CPlusPlus/LibCxx.h | 8 ++ .../Plugins/Language/CPlusPlus/LibCxxSpan.cpp | 151 ++++++++++++++++++++ .../data-formatter-stl/libcxx/span/Makefile | 7 + .../libcxx/span/TestDataFormatterLibcxxSpan.py | 153 +++++++++++++++++++++ .../data-formatter-stl/libcxx/span/main.cpp | 56 ++++++++ 7 files changed, 387 insertions(+) create mode 100644 lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py create mode 100644 lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt index 125c12c..80135da 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 08b6d89..82f8258 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 0f166ae..b4e789e6 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 0000000..0379ba2 --- /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): + // + // (std::__1::span) s = { + // __data = 0x000000016fdff494 + // } + // + // For dynamic extents, e.g., std::span, the layout is: + // + // (std::__1::span) 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 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 0000000..20c9cf0 --- /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 0000000..207e745 --- /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', + 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', + 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', + 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', + 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 *', + 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 0000000..de93949 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include + +template +void by_ref_and_ptr(std::span &ref, std::span *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; + + // 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 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 static_zero_span; + std::span dynamic_zero_span; + + // Multiple spans + std::array span_arr{strings_span, strings_span}; + std::span, 2> nested = span_arr; + + printf("break here"); + + return 0; // break here +} -- 2.7.4