diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt --- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt @@ -11,6 +11,7 @@ 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 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -756,6 +756,12 @@ 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 @@ "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 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h @@ -74,6 +74,10 @@ bool LibcxxContainerSummaryProvider(ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options); +// 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 @@ 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 --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp @@ -0,0 +1,159 @@ +//===-- 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; + + size_t CalculateNumChildren() override; + + lldb::ValueObjectSP GetChildAtIndex(size_t idx) override; + + bool Update() override; + + bool MightHaveChildren() override; + + size_t GetIndexOfChildWithName(ConstString name) override; + +private: + ValueObject *m_start = nullptr; ///< First element of span + 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(); +} + +lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd:: + ~LibcxxStdSpanSyntheticFrontEnd() { + // this needs to stay around because it's a child object who will follow + // its parent's life cycle + // delete m_start; +} + +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); +} + +/* + * std::span can either be instantiated with a compile-time known + * extent or a std::dynaic_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 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 size + auto size_sp = + m_backend.GetChildMemberWithName(ConstString("__size"), true); + if (size_sp) { + 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 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main.cpp + +USE_LIBCPP := 1 + +CXXFLAGS_EXTRAS := -std=c++20 -O0 +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 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py @@ -0,0 +1,164 @@ +""" +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 check_numbers(self, var_name): + """Helper to check that data formatter sees contents of std::span correctly""" + self.expect("frame variable " + var_name, + substrs=[var_name + ' = size=5', + '[0] = 1', + '[1] = 12', + '[2] = 123', + '[3] = 1234', + '[4] = 12345', + '}']) + + self.expect("p " + var_name, + substrs=['$', 'size=5', + '[0] = 1', + '[1] = 12', + '[2] = 123', + '[3] = 1234', + '[4] = 12345', + '}']) + + self.expect_expr(var_name, result_summary="size=5", result_children=[ + ValueCheck(value="1"), + ValueCheck(value="12"), + ValueCheck(value="123"), + ValueCheck(value="1234"), + ValueCheck(value="12345"), + ]) + + # check access-by-index + self.expect("frame variable " + var_name + "[0]", + substrs=['1']) + self.expect("frame variable " + var_name + "[1]", + substrs=['12']) + self.expect("frame variable " + var_name + "[2]", + substrs=['123']) + self.expect("frame variable " + var_name + "[3]", + substrs=['1234']) + + @add_test_categories(["libc++"]) + 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)) + + # This is the function to remove the custom formats in order to have a + # clean slate for the next test case. + def cleanup(): + self.runCmd('type format clear', check=False) + self.runCmd('type summary clear', check=False) + self.runCmd('type filter clear', check=False) + self.runCmd('type synth clear', check=False) + self.runCmd( + "settings set target.max-children-count 256", + check=False) + + # Execute the cleanup function during test case tear down. + self.addTearDownHook(cleanup) + + 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('frame variable numbers_span', substrs=['item 0 is 1']) + + self.runCmd("type summary add --summary-string \"item 0 is ${svar[0]}\" -x \"std::span<\" span") + self.expect('frame variable numbers_span', substrs=['item 0 is 1']) + + self.runCmd("type summary delete span") + + # New span with strings + lldbutil.continue_to_breakpoint(process, bkpt) + + self.expect("frame variable strings_span", + substrs=['smart', + '!!!']) + + self.expect("p strings_span", + substrs=['smart', + '!!!']) + + self.expect("p strings_span_it", + substrs=['item = "smart"']) + + # 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('frame variable strings_span', substrs=['item 0 is "smart"']) + + self.runCmd("type summary add --summary-string \"item 0 is ${svar[0]}\" dynamic_string_span") + self.expect('frame variable strings_span', substrs=['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("frame variable strings_span", + substrs=['span has 2 items']) + + self.expect("p strings_span", + substrs=['span has 2 items', + 'smart', + '!!!']) + + # check access-by-index + self.expect("frame variable strings_span[0]", + substrs=['smart']) + self.expect("frame variable strings_span[1]", + substrs=['!!!']) + + # Newly inserted value not visible to span + lldbutil.continue_to_breakpoint(process, bkpt) + + self.expect("p strings_span", + substrs=['smart', + '!!!']) + + lldbutil.continue_to_breakpoint(process, bkpt) + + # Empty spans + self.expect("frame variable static_zero_span", + substrs=['static_zero_span = size=0']) + + self.expect("frame variable dynamic_zero_span", + substrs=['dynamic_zero_span = size=0']) + + # Nested spans + self.expect("frame variable nested", + substrs=['nested = size=2', + '[0] = size=2', + '[1] = size=2']) + + @add_test_categories(["libc++"]) + 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: + + self.expect("frame variable ptr", substrs=['ptr =', ' size=5']) + + self.expect("p ptr", substrs=['$', '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 --- /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) { + // 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 +}