diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxxVector.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxxVector.cpp index 95d12e8ee4f06..4966eb1d75b20 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxxVector.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxxVector.cpp @@ -13,6 +13,7 @@ #include "lldb/ValueObject/ValueObject.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" +#include "llvm/Support/Error.h" #include "llvm/Support/ErrorExtras.h" #include @@ -37,8 +38,16 @@ class LibcxxStdVectorSyntheticFrontEnd : public SyntheticChildrenFrontEnd { llvm::Expected GetIndexOfChildWithName(ConstString name) override; private: + lldb::ChildCacheState UpdateVectorWithLayoutSubobject(ValueObject *layout); + ValueObject *m_start = nullptr; + + // m_finish may point to a pointer (`__end_`) or an integer (`__size_`) + // depending on how libc++'s vector is implemented. Interpreting what is + // pointed to is done using `m_layout`. ValueObject *m_finish = nullptr; + enum class VectorLayout : bool { Pointer, Size }; + VectorLayout m_layout; CompilerType m_element_type; uint32_t m_element_size = 0; }; @@ -81,14 +90,11 @@ lldb_private::formatters::LibcxxStdVectorSyntheticFrontEnd:: // delete m_finish; } -llvm::Expected lldb_private::formatters:: - LibcxxStdVectorSyntheticFrontEnd::CalculateNumChildren() { - if (!m_start || !m_finish) - return llvm::createStringError( - "failed to determine start/end of vector data"); - - uint64_t start_val = m_start->GetValueAsUnsigned(0); - uint64_t finish_val = m_finish->GetValueAsUnsigned(0); +static llvm::Expected +CalculateNumChildrenUsingPointerArithmetic(ValueObject *begin, ValueObject *end, + uint64_t value_type_size) { + uint64_t start_val = begin->GetValueAsUnsigned(0); + uint64_t finish_val = end->GetValueAsUnsigned(0); // A default-initialized empty vector. if (start_val == 0 && finish_val == 0) @@ -105,10 +111,32 @@ llvm::Expected lldb_private::formatters:: "start of vector data begins after end pointer"); size_t num_children = (finish_val - start_val); - if (num_children % m_element_size) + if (num_children % value_type_size) return llvm::createStringError("size not multiple of element size"); - return num_children / m_element_size; + return num_children / value_type_size; +} + +static llvm::Expected GetNumChildren(ValueObject *size) { + if (!size->GetCompilerType().IsInteger()) + return llvm::createStringError( + "size data member must be a built-in integer type"); + return size->GetValueAsUnsigned(0); +} + +llvm::Expected lldb_private::formatters:: + LibcxxStdVectorSyntheticFrontEnd::CalculateNumChildren() { + if (!m_start || !m_finish) + return llvm::createStringError( + "failed to determine start/end of vector data"); + + switch (m_layout) { + case VectorLayout::Pointer: + return CalculateNumChildrenUsingPointerArithmetic(m_start, m_finish, + m_element_size); + case VectorLayout::Size: + return GetNumChildren(m_finish); + } } lldb::ValueObjectSP @@ -126,40 +154,46 @@ lldb_private::formatters::LibcxxStdVectorSyntheticFrontEnd::GetChildAtIndex( m_element_type); } -static ValueObjectSP GetDataPointer(ValueObject &root) { - auto [cap_sp, is_compressed_pair] = - GetValueOrOldCompressedPair(root, "__cap_", "__end_cap_"); - if (!cap_sp) - return nullptr; - - if (is_compressed_pair) - return GetFirstValueOfLibCXXCompressedPair(*cap_sp); - - return cap_sp; -} - lldb::ChildCacheState lldb_private::formatters::LibcxxStdVectorSyntheticFrontEnd::Update() { m_start = m_finish = nullptr; - ValueObjectSP data_sp(GetDataPointer(m_backend)); - if (!data_sp) + // Determine if this version of libc++'s `std::vector` uses `__vector_layout`. + ValueObjectSP layout_sp = m_backend.GetChildMemberWithName("__layout_"); + ValueObject *target = layout_sp ? layout_sp.get() : &m_backend; + + ValueObjectSP begin_sp = target->GetChildMemberWithName("__begin_"); + if (!begin_sp) return lldb::ChildCacheState::eRefetch; - m_element_type = data_sp->GetCompilerType().GetPointeeType(); + m_element_type = begin_sp->GetCompilerType().GetPointeeType(); llvm::Expected size_or_err = m_element_type.GetByteSize(nullptr); - if (!size_or_err) + if (!size_or_err) { LLDB_LOG_ERRORV(GetLog(LLDBLog::DataFormatters), size_or_err.takeError(), "{0}"); - else { - m_element_size = *size_or_err; - - if (m_element_size > 0) { - // store raw pointers or end up with a circular dependency - m_start = m_backend.GetChildMemberWithName("__begin_").get(); - m_finish = m_backend.GetChildMemberWithName("__end_").get(); - } + return lldb::ChildCacheState::eRefetch; } + + m_element_size = *size_or_err; + if (m_element_size == 0) { + return lldb::ChildCacheState::eRefetch; + } + + // store raw pointers or end up with a circular dependency + m_start = begin_sp.get(); + + if (ValueObjectSP end_sp = target->GetChildMemberWithName("__end_")) { + m_finish = end_sp.get(); + m_layout = VectorLayout::Pointer; + return lldb::ChildCacheState::eRefetch; + } + + ValueObjectSP size_sp = target->GetChildMemberWithName("__size_"); + if (!size_sp) + return lldb::ChildCacheState::eRefetch; + + m_finish = size_sp.get(); + m_layout = VectorLayout::Size; return lldb::ChildCacheState::eRefetch; } diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/invalid-vector/TestDataFormatterLibcxxInvalidVectorSimulator.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/invalid-vector/TestDataFormatterLibcxxInvalidVectorSimulator.py index c3d51a49c3f5b..e79d0fe13de8c 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/invalid-vector/TestDataFormatterLibcxxInvalidVectorSimulator.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/invalid-vector/TestDataFormatterLibcxxInvalidVectorSimulator.py @@ -13,9 +13,8 @@ class LibcxxInvalidVectorDataFormatterSimulatorTestCase(TestBase): NO_DEBUG_INFO_TESTCASE = True - @skipIf(compiler="clang", compiler_version=['<', '18.0']) - def test(self): + def test_most(self): self.build() lldbutil.run_to_source_breakpoint(self, "return 0", lldb.SBFileSpec("main.cpp")) @@ -37,5 +36,88 @@ def test(self): ) self.expect( "frame variable v5", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v6", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v7", + substrs=["size=error: invalid value for end of vector"], + ) + self.expect( + "frame variable v8", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v9", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v10", + substrs=["size=error: invalid value for end of vector"], + ) + self.expect( + "frame variable v11", + substrs=["size=error: invalid value for start of vector"], + ) + self.expect( + "frame variable v12", + substrs=["size=error: start of vector data begins after end pointer"], + ) + self.expect( + "frame variable v13", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v14", + substrs=["size=error: invalid value for end of vector"], + ) + self.expect( + "frame variable v15", + substrs=["size=1"], + ) + self.expect( + "frame variable v16", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v17", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v18", + substrs=["size=error: size data member must be a built-in integer type"], + ) + self.expect( + "frame variable v19", substrs=["size=error: size not multiple of element size"], ) + self.expect( + "frame variable v20", + substrs=["size=error: size not multiple of element size"], + ) + self.expect( + "frame variable v21", + substrs=["size=1"], + ) + + @skipIf(compiler="clang", compiler_version=["<", "18.0"]) + @skipIfWindows + def test_zero_sized_struct_extension(self): + self.build() + lldbutil.run_to_source_breakpoint(self, "return 0", lldb.SBFileSpec("main.cpp")) + + self.expect( + "frame variable v23", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v24", + substrs=["size=error: failed to determine start/end of vector data"], + ) + self.expect( + "frame variable v25", + substrs=["size=error: failed to determine start/end of vector data"], + ) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/invalid-vector/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/invalid-vector/main.cpp index 5943b35deab8b..cc4a3d4a771f8 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/invalid-vector/main.cpp +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/invalid-vector/main.cpp @@ -1,37 +1,201 @@ #define COMPRESSED_PAIR_REV 4 #include +#include namespace std { -inline namespace __1 { +inline namespace __ValidLegacyVector { template struct vector { T *__begin_; T *__end_; - _LLDB_COMPRESSED_PAIR(T *, __cap_ = nullptr, void *, __alloc_); }; -} // namespace __1 +} // namespace __ValidLegacyVector -inline namespace __2 { -template struct vector {}; -} // namespace __2 +inline namespace __LegacyVectorMissingBegin { +template struct vector { + T *__end_; +}; +} // namespace __LegacyVectorMissingBegin -inline namespace __3 { +inline namespace __LegacyVectorNonPointerBegin { template struct vector { + int __begin_; + T *__end_; +}; +} // namespace __LegacyVectorNonPointerBegin + +inline namespace __LegacyMissingEnd { +template struct vector { + T *__begin_; +}; +} // namespace __LegacyMissingEnd + +inline namespace __LegacyVectorNonPointerEnd { +template struct vector { + T *__begin_; + size_t __end_; +}; +} // namespace __LegacyVectorNonPointerEnd + +inline namespace __LegacyVectorSizeBased { +template struct vector { + T *__begin_; + size_t __end_; +}; +} // namespace __LegacyVectorSizeBased + +inline namespace __ValidPointerLayout { +template struct __vector_layout { T *__begin_; T *__end_; - _LLDB_COMPRESSED_PAIR(short *, __cap_ = nullptr, void *, __alloc_); }; -} // namespace __3 + +template struct vector { + __vector_layout __layout_; +}; +} // namespace __ValidPointerLayout + +inline namespace __PointerLayoutNonPointerBegin { +template struct __vector_layout { + size_t __begin_; + T *__end_; +}; + +template struct vector { + __vector_layout __layout_; +}; +} // namespace __PointerLayoutNonPointerBegin + +inline namespace __PointerLayoutNonPointerEnd { +template struct __vector_layout { + T *__begin_; + size_t __end_; +}; + +template struct vector { + __vector_layout __layout_; +}; +} // namespace __PointerLayoutNonPointerEnd + +inline namespace __LayoutStructMissingBegin { +template struct __vector_layout { + // LLDB short-circuits when it can't find `__begin_`, so other members aren't + // required for this type. +}; + +template struct vector { + __vector_layout __layout_; +}; +} // namespace __LayoutStructMissingBegin + +inline namespace __LayoutStructMissingSecondMember { +template struct __vector_layout { + T *__begin_; +}; + +template struct vector { + __vector_layout __layout_; +}; +} // namespace __LayoutStructMissingSecondMember + +inline namespace __ValidSizeLayout { +template struct __vector_layout { + T *__begin_; + size_t __size_; +}; +template struct vector { + __vector_layout __layout_; +}; +} // namespace __ValidSizeLayout + +inline namespace __SizeLayoutMissingBegin { +template struct __vector_layout { + size_t __size_; +}; +template struct vector { + __vector_layout __layout_; +}; +} // namespace __SizeLayoutMissingBegin + +inline namespace __SizeLayoutNonPointerBegin { +template struct __vector_layout { + size_t __begin_; + size_t __size_; +}; +template struct vector { + __vector_layout __layout_; +}; +} // namespace __SizeLayoutNonPointerBegin + +inline namespace __SizeLayoutNonIntegerSize { +template struct __vector_layout { + T *__begin_; + T *__size_; +}; +template struct vector { + __vector_layout __layout_; +}; +} // namespace __SizeLayoutNonIntegerSize } // namespace std int main() { int arr[] = {1, 2, 3}; - std::__1::vector v1{.__begin_ = arr, .__end_ = nullptr}; - std::__1::vector v2{.__begin_ = nullptr, .__end_ = arr}; - std::__1::vector v3{.__begin_ = &arr[2], .__end_ = arr}; - std::__2::vector v4; + std::__ValidLegacyVector::vector v1{.__begin_ = arr, .__end_ = nullptr}; + std::__ValidLegacyVector::vector v2{.__begin_ = nullptr, .__end_ = arr}; + std::__ValidLegacyVector::vector v3{.__begin_ = &arr[2], .__end_ = arr}; + std::__LegacyVectorMissingBegin::vector v4{.__end_ = arr}; + std::__LegacyMissingEnd::vector v5{.__begin_ = arr}; + std::__LegacyVectorNonPointerBegin::vector v6{.__begin_ = 0, + .__end_ = arr}; + std::__LegacyVectorNonPointerEnd::vector v7{.__begin_ = arr, + .__end_ = 0}; + + std::__LayoutStructMissingBegin::vector v8{.__layout_ = {}}; + std::__LayoutStructMissingSecondMember::vector v9{ + .__layout_ = {.__begin_ = arr}}; + + std::__ValidPointerLayout::vector v10{ + .__layout_ = {.__begin_ = arr, .__end_ = nullptr}}; + std::__ValidPointerLayout::vector v11{ + .__layout_ = {.__begin_ = nullptr, .__end_ = arr}}; + std::__ValidPointerLayout::vector v12{ + .__layout_ = {.__begin_ = &arr[2], .__end_ = arr}}; + + std::__PointerLayoutNonPointerBegin::vector v13{ + .__layout_ = {.__begin_ = 0, .__end_ = arr}}; + std::__PointerLayoutNonPointerEnd::vector v14{ + .__layout_ = {.__begin_ = arr, .__end_ = 0}}; + + std::__ValidSizeLayout::vector v15{ + .__layout_ = {.__begin_ = arr, .__size_ = 1}}; + + std::__SizeLayoutMissingBegin::vector v16{.__layout_ = {.__size_ = 1}}; + std::__SizeLayoutNonPointerBegin::vector v17{ + .__layout_ = {.__begin_ = 0, .__size_ = 0}}; + std::__SizeLayoutNonIntegerSize::vector v18{ + .__layout_ = {.__begin_ = arr, .__size_ = 0}}; char carr[] = {'a'}; - std::__3::vector v5{.__begin_ = carr, .__end_ = carr + 1}; + std::__ValidLegacyVector::vector v19{ + .__begin_ = reinterpret_cast(carr), + .__end_ = reinterpret_cast(carr + 1)}; + std::__ValidPointerLayout::vector v20{ + .__layout_ = {.__begin_ = reinterpret_cast(carr), + .__end_ = reinterpret_cast(carr + 1)}}; + std::__ValidSizeLayout::vector v21{ + .__layout_ = {.__begin_ = reinterpret_cast(carr), .__size_ = 1}}; + +#ifndef _WIN32 + struct ZeroSizeStruct { + int x[0]; + }; + static_assert(sizeof(ZeroSizeStruct) == 0); + std::__ValidLegacyVector::vector v23{.__begin_ = nullptr, + .__end_ = nullptr}; + std::__ValidPointerLayout::vector v24{ + .__layout_ = {.__begin_ = nullptr, .__end_ = nullptr}}; + std::__ValidSizeLayout::vector v25{ + .__layout_ = {.__begin_ = nullptr, .__size_ = 0}}; +#endif return 0; } diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/Makefile new file mode 100644 index 0000000000000..8ce653ffd6871 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp +override CXXFLAGS_EXTRAS += -std=c++11 +include Makefile.rules diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/TestDataFormatterLibcxxVectorSimulator.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/TestDataFormatterLibcxxVectorSimulator.py new file mode 100644 index 0000000000000..7d9aa8b829c0b --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/TestDataFormatterLibcxxVectorSimulator.py @@ -0,0 +1,64 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class LibcxxVectorDataFormatterSimulatorTestCase(TestBase): + SHARED_BUILD_TESTCASE = False + NO_DEBUG_INFO_TESTCASE = True + + def test(self): + self.build() + lldbutil.run_to_source_breakpoint(self, "return 0", lldb.SBFileSpec("main.cpp")) + + self.expect( + "frame variable legacy_layout0", + substrs=["size=0"], + ) + self.expect( + "frame variable legacy_layout1", + substrs=["size=1", "[0] = 10"], + ) + self.expect( + "frame variable legacy_layout2", + substrs=["size=2", "[0] = -10", "[1] = -20"], + ) + self.expect( + "frame variable legacy_layout3", + substrs=["size=3", "[0] = 56", "[1] = 10", "[2] = 87"], + ) + + self.expect( + "frame variable pointer_based_layout0", + substrs=["size=0"], + ) + self.expect( + "frame variable pointer_based_layout1", + substrs=["size=1", "[0] = 10"], + ) + self.expect( + "frame variable pointer_based_layout2", + substrs=["size=2", "[0] = -10", "[1] = -20"], + ) + self.expect( + "frame variable pointer_based_layout3", + substrs=["size=3", "[0] = 56", "[1] = 10", "[2] = 87"], + ) + + self.expect( + "frame variable size_based_layout0", + substrs=["size=0"], + ) + self.expect( + "frame variable size_based_layout1", + substrs=["size=1", "[0] = 10"], + ) + self.expect( + "frame variable size_based_layout2", + substrs=["size=2", "[0] = -10", "[1] = -20"], + ) + self.expect( + "frame variable size_based_layout3", + substrs=["size=3", "[0] = 56", "[1] = 10", "[2] = 87"], + ) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/main.cpp new file mode 100644 index 0000000000000..b46793ce5c9d7 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx-simulators/vector/main.cpp @@ -0,0 +1,98 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// libc++ has changed how `std::vector` is represented over time. This file +// provides approximations of these representations for testing the vector +// pretty-printer. This lets us test that LLDB is able to handle all iterations +// of libc++'s `std::vector`. + +#include + +namespace std { +inline namespace __LegacyLayout { +template class vector { +public: + typedef T *pointer; + + vector(pointer begin, size_t size) : __begin_(begin), __end_(begin + size) {} + +private: + pointer __begin_; + pointer __end_; + + // libc++ changed how the capacity member and allocator were stored in + // 27c8338. LLDB only relies on `__begin_` and `__end_`. Adding the capacity + // and allocator members in their different formats doesn't add test coverage, + // but may convince the reader that it does. As such, we don't provide two + // legacy layouts. + // + // Before 27c83382d83dce0f33ae67abb3bc94977cb3031f: + // __compressed_pair __cap_alloc_; + // + // Since 27c83382d83dce0f33ae67abb3bc94977cb3031f: + // _LIBCPP_COMPRESSED_PAIR(pointer, __cap_ = nullptr, allocator_type, + // __alloc_); +}; +} // namespace __LegacyLayout + +inline namespace __PointerBasedLayout { +// `__PointerBasedLayout::__vector_layout` is structurally equal to +// `__LegacyLayout::vector`. +template struct __vector_layout { + T *__begin_; + T *__end_; +}; + +template class vector { +public: + vector(T *begin, size_t size) : __layout_{begin, begin + size} {} + +private: + __vector_layout __layout_; +}; +} // namespace __PointerBasedLayout + +inline namespace __SizeBasedLayout { +template struct __vector_layout { + T *__begin_; + size_t __size_; +}; + +template class vector { +public: + vector(T *begin, size_t size) : __layout_{begin, size} {} + +private: + __vector_layout __layout_; +}; +} // namespace __SizeBasedLayout +} // namespace std + +int main() { + int a1[] = {10}; + int a2[] = {-10, -20}; + int a3[] = {56, 10, 87}; + + std::__LegacyLayout::vector legacy_layout0(a1, 0); + std::__LegacyLayout::vector legacy_layout1(a1, 1); + std::__LegacyLayout::vector legacy_layout2(a2, 2); + std::__LegacyLayout::vector legacy_layout3(a3, 3); + + std::__PointerBasedLayout::vector pointer_based_layout0(a1, 0); + std::__PointerBasedLayout::vector pointer_based_layout1(a1, 1); + std::__PointerBasedLayout::vector pointer_based_layout2(a2, 2); + std::__PointerBasedLayout::vector pointer_based_layout3(a3, 3); + + std::__SizeBasedLayout::vector size_based_layout0(a1, 0); + std::__SizeBasedLayout::vector size_based_layout1(a1, 1); + std::__SizeBasedLayout::vector size_based_layout2(a2, 2); + std::__SizeBasedLayout::vector size_based_layout3(a3, 3); + + return 0; +}