//===-- UdtRecordCompleterTests.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 "Plugins/SymbolFile/NativePDB/UdtRecordCompleter.h" #include "llvm/ADT/StringExtras.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using namespace lldb_private::npdb; using namespace llvm; namespace { using Member = UdtRecordCompleter::Member; using MemberUP = std::unique_ptr; using Record = UdtRecordCompleter::Record; class WrappedMember { public: WrappedMember(const Member &obj) : m_obj(obj) {} private: const Member &m_obj; friend bool operator==(const WrappedMember &lhs, const WrappedMember &rhs) { return lhs.m_obj.kind == rhs.m_obj.kind && lhs.m_obj.name == rhs.m_obj.name && lhs.m_obj.bit_offset == rhs.m_obj.bit_offset && lhs.m_obj.bit_size == rhs.m_obj.bit_size && lhs.m_obj.base_offset == rhs.m_obj.base_offset && std::equal(lhs.m_obj.fields.begin(), lhs.m_obj.fields.end(), rhs.m_obj.fields.begin(), rhs.m_obj.fields.end(), [](const MemberUP &lhs, const MemberUP &rhs) { return WrappedMember(*lhs) == WrappedMember(*rhs); }); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const WrappedMember &w) { os << llvm::formatv("Member{.kind={0}, .name=\"{1}\", .bit_offset={2}, " ".bit_size={3}, .base_offset={4}, .fields=[", w.m_obj.kind, w.m_obj.name, w.m_obj.bit_offset, w.m_obj.bit_size, w.m_obj.base_offset); llvm::ListSeparator sep; for (auto &f : w.m_obj.fields) os << sep << WrappedMember(*f); return os << "]}"; } }; class WrappedRecord { public: WrappedRecord(const Record &obj) : m_obj(obj) {} private: const Record &m_obj; friend bool operator==(const WrappedRecord &lhs, const WrappedRecord &rhs) { return lhs.m_obj.start_offset == rhs.m_obj.start_offset && std::equal( lhs.m_obj.record.fields.begin(), lhs.m_obj.record.fields.end(), rhs.m_obj.record.fields.begin(), rhs.m_obj.record.fields.end(), [](const MemberUP &lhs, const MemberUP &rhs) { return WrappedMember(*lhs) == WrappedMember(*rhs); }); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const WrappedRecord &w) { os << llvm::formatv("Record{.start_offset={0}, .record.fields=[", w.m_obj.start_offset); llvm::ListSeparator sep; for (const MemberUP &f : w.m_obj.record.fields) os << sep << WrappedMember(*f); return os << "]}"; } }; class UdtRecordCompleterRecordTests : public testing::Test { protected: Record record; public: void SetKind(Member::Kind kind) { record.record.kind = kind; } void CollectMember(StringRef name, uint64_t byte_offset, uint64_t byte_size) { record.CollectMember(name, byte_offset * 8, byte_size * 8, clang::QualType(), lldb::eAccessPublic, 0); } void ConstructRecord() { record.ConstructRecord(); } }; Member *AddField(Member *member, StringRef name, uint64_t byte_offset, uint64_t byte_size, Member::Kind kind, uint64_t base_offset = 0) { auto field = std::make_unique(name, byte_offset * 8, byte_size * 8, clang::QualType(), lldb::eAccessPublic, 0); field->kind = kind; field->base_offset = base_offset; member->fields.push_back(std::move(field)); return member->fields.back().get(); } } // namespace TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) { SetKind(Member::Kind::Struct); CollectMember("m1", 0, 4); CollectMember("m2", 0, 4); CollectMember("m3", 0, 1); CollectMember("m4", 0, 8); ConstructRecord(); // struct { // union { // m1; // m2; // m3; // m4; // }; // }; Record record; record.start_offset = 0; Member *u = AddField(&record.record, "", 0, 0, Member::Union); AddField(u, "m1", 0, 4, Member::Field); AddField(u, "m2", 0, 4, Member::Field); AddField(u, "m3", 0, 1, Member::Field); AddField(u, "m4", 0, 8, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); } TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInUnion) { SetKind(Member::Kind::Union); CollectMember("m1", 0, 4); CollectMember("m2", 0, 4); CollectMember("m3", 0, 1); CollectMember("m4", 0, 8); ConstructRecord(); // union { // m1; // m2; // m3; // m4; // }; Record record; record.start_offset = 0; AddField(&record.record, "m1", 0, 4, Member::Field); AddField(&record.record, "m2", 0, 4, Member::Field); AddField(&record.record, "m3", 0, 1, Member::Field); AddField(&record.record, "m4", 0, 8, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); } TEST_F(UdtRecordCompleterRecordTests, TestAnonymousStructInUnion) { SetKind(Member::Kind::Union); CollectMember("m1", 0, 4); CollectMember("m2", 4, 4); CollectMember("m3", 8, 1); ConstructRecord(); // union { // struct { // m1; // m2; // m3; // }; // }; Record record; record.start_offset = 0; Member *s = AddField(&record.record, "", 0, 0, Member::Kind::Struct); AddField(s, "m1", 0, 4, Member::Field); AddField(s, "m2", 4, 4, Member::Field); AddField(s, "m3", 8, 1, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); } TEST_F(UdtRecordCompleterRecordTests, TestNestedUnionStructInStruct) { SetKind(Member::Kind::Struct); CollectMember("m1", 0, 4); CollectMember("m2", 0, 2); CollectMember("m3", 0, 2); CollectMember("m4", 2, 4); CollectMember("m5", 3, 2); ConstructRecord(); // struct { // union { // m1; // struct { // m2; // m5; // }; // struct { // m3; // m4; // }; // }; // }; Record record; record.start_offset = 0; Member *u = AddField(&record.record, "", 0, 0, Member::Union); AddField(u, "m1", 0, 4, Member::Field); Member *s1 = AddField(u, "", 0, 0, Member::Struct); Member *s2 = AddField(u, "", 0, 0, Member::Struct); AddField(s1, "m2", 0, 2, Member::Field); AddField(s1, "m5", 3, 2, Member::Field); AddField(s2, "m3", 0, 2, Member::Field); AddField(s2, "m4", 2, 4, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); } TEST_F(UdtRecordCompleterRecordTests, TestNestedUnionStructInUnion) { SetKind(Member::Kind::Union); CollectMember("m1", 0, 4); CollectMember("m2", 0, 2); CollectMember("m3", 0, 2); CollectMember("m4", 2, 4); CollectMember("m5", 3, 2); ConstructRecord(); // union { // m1; // struct { // m2; // m5; // }; // struct { // m3; // m4; // }; // }; Record record; record.start_offset = 0; AddField(&record.record, "m1", 0, 4, Member::Field); Member *s1 = AddField(&record.record, "", 0, 0, Member::Struct); Member *s2 = AddField(&record.record, "", 0, 0, Member::Struct); AddField(s1, "m2", 0, 2, Member::Field); AddField(s1, "m5", 3, 2, Member::Field); AddField(s2, "m3", 0, 2, Member::Field); AddField(s2, "m4", 2, 4, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); }