David Spickett f23d2ee5d5
[lldb] Fix libstdc++ std::string formatter after #147835 (#152993)
https://github.com/llvm/llvm-project/pull/147835 made changes that
caused libstdc++ std::string tests to fail:
```
======================================================================
FAIL: test_unavailable_summary_libstdcxx_dwo (TestDataFormatterStdString.StdStringDataFormatterTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 1805, in test_method
    return attrvalue(self)
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string/TestDataFormatterStdString.py", line 223, in test_unavailable_summary_libstdcxx
    self.do_test_summary_unavailable()
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string/TestDataFormatterStdString.py", line 213, in do_test_summary_unavailable
    self.assertEqual(summary, "Summary Unavailable", "No summary for bad value")
AssertionError: '(null)' != 'Summary Unavailable'
- (null)
+ Summary Unavailable
 : No summary for bad value
Config=aarch64-/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang
----------------------------------------------------------------------
```

This test constructs an invalid std::string by starting with a null
pointer.

Somehow this improvement in Clang uncovered a bug in the formatter.
Perhaps because we now know the type of the field we tried to access is
char *, so fall back to a C string formatter that produces `(null)`.

The formatter tries to access `_M_p` and checked whether the resulting
ValueObjectSP was null, but not that it did not contain an error value.
I think that error value can be there if you are able to access one part
of the path, `_M_dataplus`, but another part fails. Since the layout
looks like this:
```
      struct _Alloc_hider :
      {
	      pointer _M_p; // The actual data.
      };

      _Alloc_hider	_M_dataplus;

      void
      _M_data(pointer __p)
      { _M_dataplus._M_p = __p; }
```
So I think we were able to read `_M_dataplus` just by offset, but then
failed because it contains, or points to something containing nulls or a
null pointer.

Or perhaps an error value means we know what the class member is, but
could not read from it.

I found this by comparing with the libcxx formatter, so I've copied the
same handling from there to fix the issue.
2025-08-11 12:01:32 +01:00

416 lines
12 KiB
C++

//===-- LibStdcpp.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 "LibStdcpp.h"
#include "LibCxx.h"
#include "Plugins/Language/CPlusPlus/Generic.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/DataFormatters/StringPrinter.h"
#include "lldb/DataFormatters/VectorIterator.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/Endian.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/Stream.h"
#include "lldb/ValueObject/ValueObject.h"
#include "lldb/ValueObject/ValueObjectConstResult.h"
#include <optional>
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::formatters;
namespace {
class LibstdcppMapIteratorSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
/*
(std::_Rb_tree_iterator<std::pair<const int, std::basic_string<char,
std::char_traits<char>, std::allocator<char> > > >) ibeg = {
(_Base_ptr) _M_node = 0x0000000100103910 {
(std::_Rb_tree_color) _M_color = _S_black
(std::_Rb_tree_node_base::_Base_ptr) _M_parent = 0x00000001001038c0
(std::_Rb_tree_node_base::_Base_ptr) _M_left = 0x0000000000000000
(std::_Rb_tree_node_base::_Base_ptr) _M_right = 0x0000000000000000
}
}
*/
public:
explicit LibstdcppMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
llvm::Expected<uint32_t> CalculateNumChildren() override;
lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override;
lldb::ChildCacheState Update() override;
llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override;
private:
ExecutionContextRef m_exe_ctx_ref;
lldb::addr_t m_pair_address = 0;
CompilerType m_pair_type;
lldb::ValueObjectSP m_pair_sp;
};
class LibStdcppSharedPtrSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
public:
explicit LibStdcppSharedPtrSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
llvm::Expected<uint32_t> CalculateNumChildren() override;
lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override;
lldb::ChildCacheState Update() override;
llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override;
private:
// The lifetime of a ValueObject and all its derivative ValueObjects
// (children, clones, etc.) is managed by a ClusterManager. These
// objects are only destroyed when every shared pointer to any of them
// is destroyed, so we must not store a shared pointer to any ValueObject
// derived from our backend ValueObject (since we're in the same cluster).
ValueObject *m_ptr_obj = nullptr; // Underlying pointer (held, not owned)
};
} // end of anonymous namespace
LibstdcppMapIteratorSyntheticFrontEnd::LibstdcppMapIteratorSyntheticFrontEnd(
lldb::ValueObjectSP valobj_sp)
: SyntheticChildrenFrontEnd(*valobj_sp), m_exe_ctx_ref(), m_pair_type(),
m_pair_sp() {
if (valobj_sp)
Update();
}
lldb::ChildCacheState LibstdcppMapIteratorSyntheticFrontEnd::Update() {
ValueObjectSP valobj_sp = m_backend.GetSP();
if (!valobj_sp)
return lldb::ChildCacheState::eRefetch;
TargetSP target_sp(valobj_sp->GetTargetSP());
if (!target_sp)
return lldb::ChildCacheState::eRefetch;
bool is_64bit = (target_sp->GetArchitecture().GetAddressByteSize() == 8);
if (!valobj_sp)
return lldb::ChildCacheState::eRefetch;
m_exe_ctx_ref = valobj_sp->GetExecutionContextRef();
ValueObjectSP _M_node_sp(valobj_sp->GetChildMemberWithName("_M_node"));
if (!_M_node_sp)
return lldb::ChildCacheState::eRefetch;
m_pair_address = _M_node_sp->GetValueAsUnsigned(0);
if (m_pair_address == 0)
return lldb::ChildCacheState::eRefetch;
m_pair_address += (is_64bit ? 32 : 16);
CompilerType my_type(valobj_sp->GetCompilerType());
if (my_type.GetNumTemplateArguments() >= 1) {
CompilerType pair_type = my_type.GetTypeTemplateArgument(0);
if (!pair_type)
return lldb::ChildCacheState::eRefetch;
m_pair_type = pair_type;
} else
return lldb::ChildCacheState::eRefetch;
return lldb::ChildCacheState::eReuse;
}
llvm::Expected<uint32_t>
LibstdcppMapIteratorSyntheticFrontEnd::CalculateNumChildren() {
return 2;
}
lldb::ValueObjectSP
LibstdcppMapIteratorSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) {
if (m_pair_address != 0 && m_pair_type) {
if (!m_pair_sp)
m_pair_sp = CreateValueObjectFromAddress("pair", m_pair_address,
m_exe_ctx_ref, m_pair_type);
if (m_pair_sp)
return m_pair_sp->GetChildAtIndex(idx);
}
return lldb::ValueObjectSP();
}
llvm::Expected<size_t>
LibstdcppMapIteratorSyntheticFrontEnd::GetIndexOfChildWithName(
ConstString name) {
if (name == "first")
return 0;
if (name == "second")
return 1;
return llvm::createStringError("Type has no child named '%s'",
name.AsCString());
}
SyntheticChildrenFrontEnd *
lldb_private::formatters::LibstdcppMapIteratorSyntheticFrontEndCreator(
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
return (valobj_sp ? new LibstdcppMapIteratorSyntheticFrontEnd(valobj_sp)
: nullptr);
}
/*
(lldb) fr var ibeg --ptr-depth 1
(__gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > >)
ibeg = {
_M_current = 0x00000001001037a0 {
*_M_current = 1
}
}
*/
SyntheticChildrenFrontEnd *
lldb_private::formatters::LibStdcppVectorIteratorSyntheticFrontEndCreator(
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
return (valobj_sp ? new VectorIteratorSyntheticFrontEnd(
valobj_sp, {ConstString("_M_current")})
: nullptr);
}
lldb_private::formatters::VectorIteratorSyntheticFrontEnd::
VectorIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp,
llvm::ArrayRef<ConstString> item_names)
: SyntheticChildrenFrontEnd(*valobj_sp), m_exe_ctx_ref(),
m_item_names(item_names), m_item_sp() {
if (valobj_sp)
Update();
}
lldb::ChildCacheState VectorIteratorSyntheticFrontEnd::Update() {
m_item_sp.reset();
ValueObjectSP valobj_sp = m_backend.GetSP();
if (!valobj_sp)
return lldb::ChildCacheState::eRefetch;
if (!valobj_sp)
return lldb::ChildCacheState::eRefetch;
ValueObjectSP item_ptr =
formatters::GetChildMemberWithName(*valobj_sp, m_item_names);
if (!item_ptr)
return lldb::ChildCacheState::eRefetch;
if (item_ptr->GetValueAsUnsigned(0) == 0)
return lldb::ChildCacheState::eRefetch;
Status err;
m_exe_ctx_ref = valobj_sp->GetExecutionContextRef();
m_item_sp = CreateValueObjectFromAddress(
"item", item_ptr->GetValueAsUnsigned(0), m_exe_ctx_ref,
item_ptr->GetCompilerType().GetPointeeType());
if (err.Fail())
m_item_sp.reset();
return lldb::ChildCacheState::eRefetch;
}
llvm::Expected<uint32_t>
VectorIteratorSyntheticFrontEnd::CalculateNumChildren() {
return 1;
}
lldb::ValueObjectSP
VectorIteratorSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) {
if (idx == 0)
return m_item_sp;
return lldb::ValueObjectSP();
}
llvm::Expected<size_t>
VectorIteratorSyntheticFrontEnd::GetIndexOfChildWithName(ConstString name) {
if (name == "item")
return 0;
return llvm::createStringError("Type has no child named '%s'",
name.AsCString());
}
bool lldb_private::formatters::LibStdcppStringSummaryProvider(
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
ValueObjectSP ptr = valobj.GetChildAtNamePath({"_M_dataplus", "_M_p"});
if (!ptr || !ptr->GetError().Success())
stream << "Summary Unavailable";
else
stream << ptr->GetSummaryAsCString();
return true;
}
LibStdcppSharedPtrSyntheticFrontEnd::LibStdcppSharedPtrSyntheticFrontEnd(
lldb::ValueObjectSP valobj_sp)
: SyntheticChildrenFrontEnd(*valobj_sp) {
if (valobj_sp)
Update();
}
llvm::Expected<uint32_t>
LibStdcppSharedPtrSyntheticFrontEnd::CalculateNumChildren() {
return 1;
}
lldb::ValueObjectSP
LibStdcppSharedPtrSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) {
if (!m_ptr_obj)
return nullptr;
if (idx == 0)
return m_ptr_obj->GetSP();
if (idx == 1) {
ValueObjectSP valobj_sp = m_backend.GetSP();
if (!valobj_sp)
return nullptr;
Status status;
ValueObjectSP value_sp = m_ptr_obj->Dereference(status);
if (status.Success())
return value_sp;
}
return lldb::ValueObjectSP();
}
lldb::ChildCacheState LibStdcppSharedPtrSyntheticFrontEnd::Update() {
auto backend = m_backend.GetSP();
if (!backend)
return lldb::ChildCacheState::eRefetch;
auto valobj_sp = backend->GetNonSyntheticValue();
if (!valobj_sp)
return lldb::ChildCacheState::eRefetch;
auto ptr_obj_sp = valobj_sp->GetChildMemberWithName("_M_ptr");
if (!ptr_obj_sp)
return lldb::ChildCacheState::eRefetch;
auto cast_ptr_sp = GetDesugaredSmartPointerValue(*ptr_obj_sp, *valobj_sp);
if (!cast_ptr_sp)
return lldb::ChildCacheState::eRefetch;
m_ptr_obj = cast_ptr_sp->Clone(ConstString("pointer")).get();
return lldb::ChildCacheState::eRefetch;
}
llvm::Expected<size_t>
LibStdcppSharedPtrSyntheticFrontEnd::GetIndexOfChildWithName(ConstString name) {
if (name == "pointer")
return 0;
if (name == "object" || name == "$$dereference$$")
return 1;
return llvm::createStringError("Type has no child named '%s'",
name.AsCString());
}
SyntheticChildrenFrontEnd *
lldb_private::formatters::LibStdcppSharedPtrSyntheticFrontEndCreator(
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
return (valobj_sp ? new LibStdcppSharedPtrSyntheticFrontEnd(valobj_sp)
: nullptr);
}
bool lldb_private::formatters::LibStdcppSmartPointerSummaryProvider(
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue());
if (!valobj_sp)
return false;
ValueObjectSP ptr_sp(valobj_sp->GetChildMemberWithName("_M_ptr"));
if (!ptr_sp)
return false;
DumpCxxSmartPtrPointerSummary(stream, *ptr_sp, options);
ValueObjectSP pi_sp = valobj_sp->GetChildAtNamePath({"_M_refcount", "_M_pi"});
if (!pi_sp)
return false;
bool success;
uint64_t pi_addr = pi_sp->GetValueAsUnsigned(0, &success);
// Empty control field. We're done.
if (!success || pi_addr == 0)
return true;
int64_t shared_count = 0;
if (auto count_sp = pi_sp->GetChildMemberWithName("_M_use_count")) {
bool success;
shared_count = count_sp->GetValueAsSigned(0, &success);
if (!success)
return false;
stream.Printf(" strong=%" PRId64, shared_count);
}
// _M_weak_count is the number of weak references + (_M_use_count != 0).
if (auto weak_count_sp = pi_sp->GetChildMemberWithName("_M_weak_count")) {
bool success;
int64_t count = weak_count_sp->GetValueAsUnsigned(0, &success);
if (!success)
return false;
stream.Printf(" weak=%" PRId64, count - (shared_count != 0));
}
return true;
}
static uint64_t LibStdcppVariantNposValue(size_t index_byte_size) {
switch (index_byte_size) {
case 1:
return 0xff;
case 2:
return 0xffff;
default:
return 0xffff'ffff;
}
}
bool formatters::LibStdcppVariantSummaryProvider(
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
ValueObjectSP valobj_sp = valobj.GetNonSyntheticValue();
if (!valobj_sp)
return false;
ValueObjectSP index_obj = valobj_sp->GetChildMemberWithName("_M_index");
ValueObjectSP data_obj = valobj_sp->GetChildMemberWithName("_M_u");
if (!index_obj || !data_obj)
return false;
auto index_bytes = index_obj->GetByteSize();
if (!index_bytes)
return false;
auto npos_value = LibStdcppVariantNposValue(*index_bytes);
auto index = index_obj->GetValueAsUnsigned(0);
if (index == npos_value) {
stream.Printf(" No Value");
return true;
}
auto variant_type =
valobj_sp->GetCompilerType().GetCanonicalType().GetNonReferenceType();
if (!variant_type)
return false;
if (index >= variant_type.GetNumTemplateArguments(true)) {
stream.Printf(" <Invalid>");
return true;
}
auto active_type = variant_type.GetTypeTemplateArgument(index, true);
stream << " Active Type = " << active_type.GetDisplayTypeName() << " ";
return true;
}