| 1 | //===-- UdtRecordCompleterTests.cpp ---------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "Plugins/SymbolFile/NativePDB/UdtRecordCompleter.h" |
| 10 | #include "llvm/ADT/StringExtras.h" |
| 11 | #include "gmock/gmock.h" |
| 12 | #include "gtest/gtest.h" |
| 13 | |
| 14 | using namespace lldb_private::npdb; |
| 15 | using namespace llvm; |
| 16 | |
| 17 | namespace { |
| 18 | using Member = UdtRecordCompleter::Member; |
| 19 | using MemberUP = std::unique_ptr<Member>; |
| 20 | using Record = UdtRecordCompleter::Record; |
| 21 | |
| 22 | class WrappedMember { |
| 23 | public: |
| 24 | WrappedMember(const Member &obj) : m_obj(obj) {} |
| 25 | |
| 26 | private: |
| 27 | const Member &m_obj; |
| 28 | |
| 29 | friend bool operator==(const WrappedMember &lhs, const WrappedMember &rhs) { |
| 30 | return lhs.m_obj.kind == rhs.m_obj.kind && |
| 31 | lhs.m_obj.name == rhs.m_obj.name && |
| 32 | lhs.m_obj.bit_offset == rhs.m_obj.bit_offset && |
| 33 | lhs.m_obj.bit_size == rhs.m_obj.bit_size && |
| 34 | lhs.m_obj.base_offset == rhs.m_obj.base_offset && |
| 35 | std::equal(first1: lhs.m_obj.fields.begin(), last1: lhs.m_obj.fields.end(), |
| 36 | first2: rhs.m_obj.fields.begin(), last2: rhs.m_obj.fields.end(), |
| 37 | binary_pred: [](const MemberUP &lhs, const MemberUP &rhs) { |
| 38 | return WrappedMember(*lhs) == WrappedMember(*rhs); |
| 39 | }); |
| 40 | } |
| 41 | |
| 42 | friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, |
| 43 | const WrappedMember &w) { |
| 44 | os << llvm::formatv(Fmt: "Member{.kind={0}, .name=\"{1}\", .bit_offset={2}, " |
| 45 | ".bit_size={3}, .base_offset={4}, .fields=[" , |
| 46 | Vals: w.m_obj.kind, Vals: w.m_obj.name, Vals: w.m_obj.bit_offset, |
| 47 | Vals: w.m_obj.bit_size, Vals: w.m_obj.base_offset); |
| 48 | llvm::ListSeparator sep; |
| 49 | for (auto &f : w.m_obj.fields) |
| 50 | os << sep << WrappedMember(*f); |
| 51 | return os << "]}" ; |
| 52 | } |
| 53 | }; |
| 54 | |
| 55 | class WrappedRecord { |
| 56 | public: |
| 57 | WrappedRecord(const Record &obj) : m_obj(obj) {} |
| 58 | |
| 59 | private: |
| 60 | const Record &m_obj; |
| 61 | |
| 62 | friend bool operator==(const WrappedRecord &lhs, const WrappedRecord &rhs) { |
| 63 | return lhs.m_obj.start_offset == rhs.m_obj.start_offset && |
| 64 | std::equal( |
| 65 | lhs.m_obj.record.fields.begin(), lhs.m_obj.record.fields.end(), |
| 66 | rhs.m_obj.record.fields.begin(), rhs.m_obj.record.fields.end(), |
| 67 | [](const MemberUP &lhs, const MemberUP &rhs) { |
| 68 | return WrappedMember(*lhs) == WrappedMember(*rhs); |
| 69 | }); |
| 70 | } |
| 71 | |
| 72 | friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, |
| 73 | const WrappedRecord &w) { |
| 74 | os << llvm::formatv(Fmt: "Record{.start_offset={0}, .record.fields=[" , |
| 75 | Vals: w.m_obj.start_offset); |
| 76 | llvm::ListSeparator sep; |
| 77 | for (const MemberUP &f : w.m_obj.record.fields) |
| 78 | os << sep << WrappedMember(*f); |
| 79 | return os << "]}" ; |
| 80 | } |
| 81 | }; |
| 82 | |
| 83 | class UdtRecordCompleterRecordTests : public testing::Test { |
| 84 | protected: |
| 85 | Record record; |
| 86 | |
| 87 | public: |
| 88 | void SetKind(Member::Kind kind) { record.record.kind = kind; } |
| 89 | void CollectMember(StringRef name, uint64_t byte_offset, uint64_t byte_size) { |
| 90 | record.CollectMember(name, byte_offset * 8, byte_size * 8, |
| 91 | clang::QualType(), lldb::eAccessPublic, 0); |
| 92 | } |
| 93 | void ConstructRecord() { record.ConstructRecord(); } |
| 94 | }; |
| 95 | Member *AddField(Member *member, StringRef name, uint64_t byte_offset, |
| 96 | uint64_t byte_size, Member::Kind kind, |
| 97 | uint64_t base_offset = 0) { |
| 98 | auto field = |
| 99 | std::make_unique<Member>(args&: name, args: byte_offset * 8, args: byte_size * 8, |
| 100 | args: clang::QualType(), args: lldb::eAccessPublic, args: 0); |
| 101 | field->kind = kind; |
| 102 | field->base_offset = base_offset; |
| 103 | member->fields.push_back(Elt: std::move(field)); |
| 104 | return member->fields.back().get(); |
| 105 | } |
| 106 | } // namespace |
| 107 | |
| 108 | TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) { |
| 109 | SetKind(Member::Kind::Struct); |
| 110 | CollectMember("m1" , 0, 4); |
| 111 | CollectMember("m2" , 0, 4); |
| 112 | CollectMember("m3" , 0, 1); |
| 113 | CollectMember("m4" , 0, 8); |
| 114 | ConstructRecord(); |
| 115 | |
| 116 | // struct { |
| 117 | // union { |
| 118 | // m1; |
| 119 | // m2; |
| 120 | // m3; |
| 121 | // m4; |
| 122 | // }; |
| 123 | // }; |
| 124 | Record record; |
| 125 | record.start_offset = 0; |
| 126 | Member *u = AddField(&record.record, "" , 0, 0, Member::Union); |
| 127 | AddField(member: u, name: "m1" , byte_offset: 0, byte_size: 4, kind: Member::Field); |
| 128 | AddField(member: u, name: "m2" , byte_offset: 0, byte_size: 4, kind: Member::Field); |
| 129 | AddField(member: u, name: "m3" , byte_offset: 0, byte_size: 1, kind: Member::Field); |
| 130 | AddField(member: u, name: "m4" , byte_offset: 0, byte_size: 8, kind: Member::Field); |
| 131 | EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); |
| 132 | } |
| 133 | |
| 134 | TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInUnion) { |
| 135 | SetKind(Member::Kind::Union); |
| 136 | CollectMember("m1" , 0, 4); |
| 137 | CollectMember("m2" , 0, 4); |
| 138 | CollectMember("m3" , 0, 1); |
| 139 | CollectMember("m4" , 0, 8); |
| 140 | ConstructRecord(); |
| 141 | |
| 142 | // union { |
| 143 | // m1; |
| 144 | // m2; |
| 145 | // m3; |
| 146 | // m4; |
| 147 | // }; |
| 148 | Record record; |
| 149 | record.start_offset = 0; |
| 150 | AddField(&record.record, "m1" , 0, 4, Member::Field); |
| 151 | AddField(&record.record, "m2" , 0, 4, Member::Field); |
| 152 | AddField(&record.record, "m3" , 0, 1, Member::Field); |
| 153 | AddField(&record.record, "m4" , 0, 8, Member::Field); |
| 154 | EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); |
| 155 | } |
| 156 | |
| 157 | TEST_F(UdtRecordCompleterRecordTests, TestAnonymousStructInUnion) { |
| 158 | SetKind(Member::Kind::Union); |
| 159 | CollectMember("m1" , 0, 4); |
| 160 | CollectMember("m2" , 4, 4); |
| 161 | CollectMember("m3" , 8, 1); |
| 162 | ConstructRecord(); |
| 163 | |
| 164 | // union { |
| 165 | // struct { |
| 166 | // m1; |
| 167 | // m2; |
| 168 | // m3; |
| 169 | // }; |
| 170 | // }; |
| 171 | Record record; |
| 172 | record.start_offset = 0; |
| 173 | Member *s = AddField(&record.record, "" , 0, 0, Member::Kind::Struct); |
| 174 | AddField(member: s, name: "m1" , byte_offset: 0, byte_size: 4, kind: Member::Field); |
| 175 | AddField(member: s, name: "m2" , byte_offset: 4, byte_size: 4, kind: Member::Field); |
| 176 | AddField(member: s, name: "m3" , byte_offset: 8, byte_size: 1, kind: Member::Field); |
| 177 | EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); |
| 178 | } |
| 179 | |
| 180 | TEST_F(UdtRecordCompleterRecordTests, TestNestedUnionStructInStruct) { |
| 181 | SetKind(Member::Kind::Struct); |
| 182 | CollectMember("m1" , 0, 4); |
| 183 | CollectMember("m2" , 0, 2); |
| 184 | CollectMember("m3" , 0, 2); |
| 185 | CollectMember("m4" , 2, 4); |
| 186 | CollectMember("m5" , 3, 2); |
| 187 | ConstructRecord(); |
| 188 | |
| 189 | // struct { |
| 190 | // union { |
| 191 | // m1; |
| 192 | // struct { |
| 193 | // m2; |
| 194 | // m5; |
| 195 | // }; |
| 196 | // struct { |
| 197 | // m3; |
| 198 | // m4; |
| 199 | // }; |
| 200 | // }; |
| 201 | // }; |
| 202 | Record record; |
| 203 | record.start_offset = 0; |
| 204 | Member *u = AddField(&record.record, "" , 0, 0, Member::Union); |
| 205 | AddField(member: u, name: "m1" , byte_offset: 0, byte_size: 4, kind: Member::Field); |
| 206 | Member *s1 = AddField(member: u, name: "" , byte_offset: 0, byte_size: 0, kind: Member::Struct); |
| 207 | Member *s2 = AddField(member: u, name: "" , byte_offset: 0, byte_size: 0, kind: Member::Struct); |
| 208 | AddField(member: s1, name: "m2" , byte_offset: 0, byte_size: 2, kind: Member::Field); |
| 209 | AddField(member: s1, name: "m5" , byte_offset: 3, byte_size: 2, kind: Member::Field); |
| 210 | AddField(member: s2, name: "m3" , byte_offset: 0, byte_size: 2, kind: Member::Field); |
| 211 | AddField(member: s2, name: "m4" , byte_offset: 2, byte_size: 4, kind: Member::Field); |
| 212 | EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); |
| 213 | } |
| 214 | |
| 215 | TEST_F(UdtRecordCompleterRecordTests, TestNestedUnionStructInUnion) { |
| 216 | SetKind(Member::Kind::Union); |
| 217 | CollectMember("m1" , 0, 4); |
| 218 | CollectMember("m2" , 0, 2); |
| 219 | CollectMember("m3" , 0, 2); |
| 220 | CollectMember("m4" , 2, 4); |
| 221 | CollectMember("m5" , 3, 2); |
| 222 | ConstructRecord(); |
| 223 | |
| 224 | // union { |
| 225 | // m1; |
| 226 | // struct { |
| 227 | // m2; |
| 228 | // m5; |
| 229 | // }; |
| 230 | // struct { |
| 231 | // m3; |
| 232 | // m4; |
| 233 | // }; |
| 234 | // }; |
| 235 | Record record; |
| 236 | record.start_offset = 0; |
| 237 | AddField(&record.record, "m1" , 0, 4, Member::Field); |
| 238 | Member *s1 = AddField(&record.record, "" , 0, 0, Member::Struct); |
| 239 | Member *s2 = AddField(&record.record, "" , 0, 0, Member::Struct); |
| 240 | AddField(member: s1, name: "m2" , byte_offset: 0, byte_size: 2, kind: Member::Field); |
| 241 | AddField(member: s1, name: "m5" , byte_offset: 3, byte_size: 2, kind: Member::Field); |
| 242 | AddField(member: s2, name: "m3" , byte_offset: 0, byte_size: 2, kind: Member::Field); |
| 243 | AddField(member: s2, name: "m4" , byte_offset: 2, byte_size: 4, kind: Member::Field); |
| 244 | EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); |
| 245 | } |
| 246 | |