Previously, lldb mistook fields in anonymous union in a struct as the direct
field of the struct, which causes lldb crashes due to multiple fields sharing
the same offset in a struct. This patch fixes it.
MSVC generated pdb doesn't have the debug info entity representing a anonymous
union in a struct. It looks like the following:
```
struct S {
union {
char c;
int i;
};
};
0x1004 | LF_FIELDLIST [size = 40]
- LF_MEMBER [name = `c`, Type = 0x0070 (char), offset = 0, attrs = public]
- LF_MEMBER [name = `i`, Type = 0x0074 (int), offset = 0, attrs = public]
0x1005 | LF_STRUCTURE [size = 32] `S`
unique name: `.?AUS@@`
vtable: <no type>, base list: <no type>, field list: 0x1004
```
Clang generated pdb is similar, though due to the [[ https://github.com/llvm/llvm-project/issues/57999 | bug ]],
it's not more useful than the debug info above. But that's not very relavent,
lldb should still be able to understand MSVC geneerated pdb.
```
0x1003 | LF_UNION [size = 60] `S::<unnamed-tag>`
unique name: `.?AT<unnamed-type-$S1>@S@@`
field list: <no type>
options: forward ref (= 0x1003) | has unique name | is nested, sizeof 0
0x1004 | LF_FIELDLIST [size = 40]
- LF_MEMBER [name = `c`, Type = 0x0070 (char), offset = 0, attrs = public]
- LF_MEMBER [name = `i`, Type = 0x0074 (int), offset = 0, attrs = public]
- LF_NESTTYPE [name = ``, parent = 0x1003]
0x1005 | LF_STRUCTURE [size = 32] `S`
unique name: `.?AUS@@`
vtable: <no type>, base list: <no type>, field list: 0x1004
options: contains nested class | has unique name, sizeof 4
0x1006 | LF_FIELDLIST [size = 28]
- LF_MEMBER [name = `c`, Type = 0x0070 (char), offset = 0, attrs = public]
- LF_MEMBER [name = `i`, Type = 0x0074 (int), offset = 0, attrs = public]
0x1007 | LF_UNION [size = 60] `S::<unnamed-tag>`
unique name: `.?AT<unnamed-type-$S1>@S@@`
field list: 0x1006
options: has unique name | is nested | sealed, sizeof
```
This patch delays the FieldDecl creation when travesing LF_FIELDLIST so we know
if there are multiple fields are in the same offsets and are able to group them
into different anonymous unions based on offsets. Nested anonymous union will
be flatten into one anonymous union, because we simply don't have that info, but
they are equivalent in terms of union layout.
Differential Revision: https://reviews.llvm.org/D134849
245 lines
7.5 KiB
C++
245 lines
7.5 KiB
C++
//===-- 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 "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<Member>;
|
|
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<Member>(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));
|
|
}
|