[formatters] Add a libstdcpp formatter for bitset and unify tests across stdlibs

This diff adds a data formatter for libstdcpp's bitset. Besides, it unifies the tests for bitset for libcxx and libstdcpp for maintainability.

Reviewed By: wallace

Differential Revision: https://reviews.llvm.org/D112180
This commit is contained in:
Danil Stefaniuc 2021-10-26 14:24:46 -07:00 committed by Walter Erquinigo
parent acabad9ff6
commit 566bfbb740
8 changed files with 156 additions and 81 deletions

View File

@ -3,9 +3,9 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
CPlusPlusLanguage.cpp
CPlusPlusNameParser.cpp
CxxStringTypes.cpp
GenericBitset.cpp
LibCxx.cpp
LibCxxAtomic.cpp
LibCxxBitset.cpp
LibCxxInitializerList.cpp
LibCxxList.cpp
LibCxxMap.cpp

View File

@ -895,6 +895,8 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
SyntheticChildren::Flags stl_synth_flags;
stl_synth_flags.SetCascades(true).SetSkipPointers(false).SetSkipReferences(
false);
SyntheticChildren::Flags stl_deref_flags = stl_synth_flags;
stl_deref_flags.SetFrontEndWantsDereference();
cpp_category_sp->GetRegexTypeSyntheticsContainer()->Add(
RegularExpression("^std::vector<.+>(( )?&)?$"),
@ -913,6 +915,10 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
"lldb.formatters.cpp.gnu_libstdcpp.StdListSynthProvider")));
stl_summary_flags.SetDontShowChildren(false);
stl_summary_flags.SetSkipPointers(true);
cpp_category_sp->GetRegexTypeSummariesContainer()->Add(
RegularExpression("^std::bitset<.+>(( )?&)?$"),
TypeSummaryImplSP(
new StringSummaryFormat(stl_summary_flags, "size=${svar%#}")));
cpp_category_sp->GetRegexTypeSummariesContainer()->Add(
RegularExpression("^std::vector<.+>(( )?&)?$"),
TypeSummaryImplSP(
@ -959,6 +965,12 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
"std::tuple synthetic children", ConstString("^std::tuple<.+>(( )?&)?$"),
stl_synth_flags, true);
AddCXXSynthetic(
cpp_category_sp,
lldb_private::formatters::LibStdcppBitsetSyntheticFrontEndCreator,
"std::bitset synthetic child", ConstString("^std::bitset<.+>(( )?&)?$"),
stl_deref_flags, true);
AddCXXSummary(cpp_category_sp,
lldb_private::formatters::LibStdcppUniquePointerSummaryProvider,
"libstdc++ std::unique_ptr summary provider",

View File

@ -1,4 +1,4 @@
//===-- LibCxxBitset.cpp --------------------------------------------------===//
//===-- GenericBitset.cpp //-----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "LibCxx.h"
#include "LibStdcpp.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/Target/Target.h"
@ -16,9 +17,15 @@ using namespace lldb_private;
namespace {
class BitsetFrontEnd : public SyntheticChildrenFrontEnd {
/// This class can be used for handling bitsets from both libcxx and libstdcpp.
class GenericBitsetFrontEnd : public SyntheticChildrenFrontEnd {
public:
BitsetFrontEnd(ValueObject &valobj);
enum class StdLib {
LibCxx,
LibStdcpp,
};
GenericBitsetFrontEnd(ValueObject &valobj, StdLib stdlib);
size_t GetIndexOfChildWithName(ConstString name) override {
return formatters::ExtractIndexFromString(name.GetCString());
@ -30,6 +37,8 @@ public:
ValueObjectSP GetChildAtIndex(size_t idx) override;
private:
ConstString GetDataContainerMemberName();
// 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
@ -38,15 +47,16 @@ private:
// Value objects created from raw data (i.e. in a different cluster) must
// be referenced via shared pointer to keep them alive, however.
std::vector<ValueObjectSP> m_elements;
ValueObject* m_first = nullptr;
ValueObject *m_first = nullptr;
CompilerType m_bool_type;
ByteOrder m_byte_order = eByteOrderInvalid;
uint8_t m_byte_size = 0;
StdLib m_stdlib;
};
} // namespace
BitsetFrontEnd::BitsetFrontEnd(ValueObject &valobj)
: SyntheticChildrenFrontEnd(valobj) {
GenericBitsetFrontEnd::GenericBitsetFrontEnd(ValueObject &valobj, StdLib stdlib)
: SyntheticChildrenFrontEnd(valobj), m_stdlib(stdlib) {
m_bool_type = valobj.GetCompilerType().GetBasicTypeFromAST(eBasicTypeBool);
if (auto target_sp = m_backend.GetTargetSP()) {
m_byte_order = target_sp->GetArchitecture().GetByteOrder();
@ -55,7 +65,16 @@ BitsetFrontEnd::BitsetFrontEnd(ValueObject &valobj)
}
}
bool BitsetFrontEnd::Update() {
ConstString GenericBitsetFrontEnd::GetDataContainerMemberName() {
switch (m_stdlib) {
case StdLib::LibCxx:
return ConstString("__first_");
case StdLib::LibStdcpp:
return ConstString("_M_w");
}
}
bool GenericBitsetFrontEnd::Update() {
m_elements.clear();
m_first = nullptr;
@ -65,16 +84,17 @@ bool BitsetFrontEnd::Update() {
size_t capping_size = target_sp->GetMaximumNumberOfChildrenToDisplay();
size_t size = 0;
if (auto arg = m_backend.GetCompilerType().GetIntegralTemplateArgument(0))
size = arg->value.getLimitedValue(capping_size);
m_elements.assign(size, ValueObjectSP());
m_first = m_backend.GetChildMemberWithName(ConstString("__first_"), true).get();
m_first = m_backend.GetChildMemberWithName(GetDataContainerMemberName(), true)
.get();
return false;
}
ValueObjectSP BitsetFrontEnd::GetChildAtIndex(size_t idx) {
ValueObjectSP GenericBitsetFrontEnd::GetChildAtIndex(size_t idx) {
if (idx >= m_elements.size() || !m_first)
return ValueObjectSP();
@ -112,9 +132,18 @@ ValueObjectSP BitsetFrontEnd::GetChildAtIndex(size_t idx) {
return m_elements[idx];
}
SyntheticChildrenFrontEnd *formatters::LibStdcppBitsetSyntheticFrontEndCreator(
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
if (valobj_sp)
return new GenericBitsetFrontEnd(*valobj_sp,
GenericBitsetFrontEnd::StdLib::LibStdcpp);
return nullptr;
}
SyntheticChildrenFrontEnd *formatters::LibcxxBitsetSyntheticFrontEndCreator(
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
if (valobj_sp)
return new BitsetFrontEnd(*valobj_sp);
return new GenericBitsetFrontEnd(*valobj_sp,
GenericBitsetFrontEnd::StdLib::LibCxx);
return nullptr;
}

View File

@ -41,6 +41,10 @@ SyntheticChildrenFrontEnd *
LibStdcppTupleSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP);
SyntheticChildrenFrontEnd *
LibStdcppBitsetSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP);
SyntheticChildrenFrontEnd *
LibStdcppVectorIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP);

View File

@ -0,0 +1,93 @@
"""
Test lldb data formatter subsystem for bitset for libcxx and libstdcpp.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
USE_LIBSTDCPP = "USE_LIBSTDCPP"
USE_LIBCPP = "USE_LIBCPP"
VALUE = "VALUE"
REFERENCE = "REFERENCE"
POINTER = "POINTER"
class GenericBitsetDataFormatterTestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
def setUp(self):
TestBase.setUp(self)
primes = [1]*300
primes[0] = primes[1] = 0
for i in range(2, len(primes)):
for j in range(2*i, len(primes), i):
primes[j] = 0
self.primes = primes
def getBitsetVariant(self, size, variant):
if variant == VALUE:
return "std::bitset<" + str(size) + ">"
elif variant == REFERENCE:
return "std::bitset<" + str(size) + "> &"
elif variant == POINTER:
return "std::bitset<" + str(size) + "> *"
return ""
def check(self, name, size, variant):
var = self.frame().FindVariable(name)
self.assertTrue(var.IsValid())
self.assertEqual(var.GetNumChildren(), size)
children = []
for i in range(size):
child = var.GetChildAtIndex(i)
children.append(ValueCheck(value=str(bool(child.GetValueAsUnsigned())).lower()))
self.assertEqual(child.GetValueAsUnsigned(), self.primes[i],
"variable: %s, index: %d"%(name, size))
self.expect_var_path(name,type=self.getBitsetVariant(size,variant),children=children)
def do_test_value(self, stdlib_type):
"""Test that std::bitset is displayed correctly"""
self.build(dictionary={stdlib_type: "1"})
lldbutil.run_to_source_breakpoint(self, '// break here',
lldb.SBFileSpec("main.cpp", False))
self.check("empty", 0, VALUE)
self.check("small", 13, VALUE)
self.check("large", 70, VALUE)
@add_test_categories(["libstdcxx"])
def test_value_libstdcpp(self):
self.do_test_value(USE_LIBSTDCPP)
@add_test_categories(["libc++"])
def test_value_libcpp(self):
self.do_test_value(USE_LIBCPP)
def do_test_ptr_and_ref(self, stdlib_type):
"""Test that ref and ptr to std::bitset is displayed correctly"""
self.build(dictionary={stdlib_type: "1"})
(_, process, _, bkpt) = lldbutil.run_to_source_breakpoint(self,
'Check ref and ptr',
lldb.SBFileSpec("main.cpp", False))
self.check("ref", 13, REFERENCE)
self.check("ptr", 13, POINTER)
lldbutil.continue_to_breakpoint(process, bkpt)
self.check("ref", 70, REFERENCE)
self.check("ptr", 70, POINTER)
@add_test_categories(["libstdcxx"])
def test_ptr_and_ref_libstdcpp(self):
self.do_test_ptr_and_ref(USE_LIBSTDCPP)
@add_test_categories(["libc++"])
def test_ptr_and_ref_libcpp(self):
self.do_test_ptr_and_ref(USE_LIBCPP)

View File

@ -1,27 +1,26 @@
#include <bitset>
#include <stdio.h>
template<std::size_t N>
void fill(std::bitset<N> &b) {
template <std::size_t N> void fill(std::bitset<N> &b) {
b.set();
b[0] = b[1] = false;
for (std::size_t i = 2; i < N; ++i) {
for (std::size_t j = 2*i; j < N; j+=i)
for (std::size_t j = 2 * i; j < N; j += i)
b[j] = false;
}
}
template<std::size_t N>
template <std::size_t N>
void by_ref_and_ptr(std::bitset<N> &ref, std::bitset<N> *ptr) {
// Check ref and ptr
return;
// Check ref and ptr
return;
}
int main() {
std::bitset<0> empty;
std::bitset<13> small;
fill(small);
std::bitset<200> large;
std::bitset<70> large;
fill(large);
by_ref_and_ptr(small, &small); // break here
by_ref_and_ptr(large, &large);

View File

@ -1,61 +0,0 @@
"""
Test lldb data formatter subsystem.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestDataFormatterLibcxxBitset(TestBase):
mydir = TestBase.compute_mydir(__file__)
def setUp(self):
TestBase.setUp(self)
primes = [1]*300
primes[0] = primes[1] = 0
for i in range(2, len(primes)):
for j in range(2*i, len(primes), i):
primes[j] = 0
self.primes = primes
def check(self, name, size):
var = self.frame().FindVariable(name)
self.assertTrue(var.IsValid())
self.assertEqual(var.GetNumChildren(), size)
for i in range(size):
child = var.GetChildAtIndex(i)
self.assertEqual(child.GetValueAsUnsigned(), self.primes[i],
"variable: %s, index: %d"%(name, size))
@add_test_categories(["libc++"])
def test_value(self):
"""Test that std::bitset is displayed correctly"""
self.build()
lldbutil.run_to_source_breakpoint(self, '// break here',
lldb.SBFileSpec("main.cpp", False))
self.check("empty", 0)
self.check("small", 13)
self.check("large", 200)
@add_test_categories(["libc++"])
def test_ptr_and_ref(self):
"""Test that ref and ptr to std::bitset is displayed correctly"""
self.build()
(_, process, _, bkpt) = lldbutil.run_to_source_breakpoint(self,
'Check ref and ptr',
lldb.SBFileSpec("main.cpp", False))
self.check("ref", 13)
self.check("ptr", 13)
lldbutil.continue_to_breakpoint(process, bkpt)
self.check("ref", 200)
self.check("ptr", 200)