TypeGraph: Support bitfields

- Change member and parent offsets to work in bits, not bytes
- Printer still displays offsets in bytes, with decimals when using
  bitfields
- AddPadding: Don't pad bitfields
- CodeGen: Emit code for bitfields
This commit is contained in:
Alastair Robertson 2023-06-29 01:15:03 -07:00 committed by Alastair Robertson
parent c9bcf5e760
commit e86ebb7aff
14 changed files with 398 additions and 90 deletions

View File

@ -247,7 +247,11 @@ void genDefsClass(const Class& c, std::string& code) {
code += c.name() + " {\n";
for (const auto& mem : c.members) {
code += " " + mem.type->name() + " " + mem.name + ";\n";
code += " " + mem.type->name() + " " + mem.name;
if (mem.bitsize) {
code += " : " + std::to_string(mem.bitsize);
}
code += ";\n";
}
code += "};\n\n";
}
@ -271,8 +275,10 @@ void genStaticAssertsClass(const Class& c, std::string& code) {
") == " + std::to_string(c.size()) +
", \"Unexpected size of struct " + c.name() + "\");\n";
for (const auto& member : c.members) {
if (member.bitsize > 0)
continue;
code += "static_assert(offsetof(" + c.name() + ", " + member.name +
") == " + std::to_string(member.offset) +
") == " + std::to_string(member.bitOffset / 8) +
", \"Unexpected offset of " + c.name() + "::" + member.name +
"\");\n";
}
@ -407,6 +413,7 @@ void CodeGen::getClassSizeFuncConcrete(std::string_view funcName,
}
code += " JLOG(\"" + member.name + " @\");\n";
if (member.bitsize == 0)
code += " JLOGPTR(&t." + member.name + ");\n";
code += " getSizeType(t." + member.name + ", returnArg);\n";
}

View File

@ -65,13 +65,13 @@ void AddPadding::visit(Class& c) {
paddedMembers.reserve(c.members.size());
for (size_t i = 0; i < c.members.size(); i++) {
if (i >= 1) {
addPadding(c.members[i - 1], c.members[i].offset, paddedMembers);
addPadding(c.members[i - 1], c.members[i].bitOffset, paddedMembers);
}
paddedMembers.push_back(c.members[i]);
}
if (!c.members.empty()) {
addPadding(c.members.back(), c.size(), paddedMembers);
addPadding(c.members.back(), c.size() * 8, paddedMembers);
}
c.members = std::move(paddedMembers);
@ -82,16 +82,32 @@ void AddPadding::visit(Class& c) {
}
void AddPadding::addPadding(const Member& prevMember,
uint64_t paddingEnd,
uint64_t paddingEndBits,
std::vector<Member>& paddedMembers) {
uint64_t prevMemberEnd = prevMember.offset + prevMember.type->size();
uint64_t padding = paddingEnd - prevMemberEnd;
if (padding == 0)
uint64_t prevMemberSizeBits;
if (prevMember.bitsize == 0) {
prevMemberSizeBits = prevMember.type->size() * 8;
} else {
prevMemberSizeBits = prevMember.bitsize;
}
uint64_t prevMemberEndBits = prevMember.bitOffset + prevMemberSizeBits;
uint64_t paddingBits = paddingEndBits - prevMemberEndBits;
if (paddingBits == 0)
return;
if (paddingBits % 8 == 0) {
// Pad with an array of bytes
auto* primitive = typeGraph_.make_type<Primitive>(Primitive::Kind::Int8);
auto* paddingArray = typeGraph_.make_type<Array>(primitive, padding);
paddedMembers.emplace_back(paddingArray, MemberPrefix, prevMemberEnd);
auto* paddingArray =
typeGraph_.make_type<Array>(primitive, paddingBits / 8);
paddedMembers.emplace_back(paddingArray, MemberPrefix, prevMemberEndBits);
} else {
// Pad with a bitfield
auto* primitive = typeGraph_.make_type<Primitive>(Primitive::Kind::Int64);
paddedMembers.emplace_back(primitive, MemberPrefix, prevMemberEndBits,
paddingBits);
}
}
} // namespace type_graph

View File

@ -45,7 +45,7 @@ class AddPadding final : public RecursiveVisitor {
void visit(Type& type) override;
void visit(Class& c) override;
static const inline std::string MemberPrefix = "__oid_padding";
static const inline std::string MemberPrefix = "__oi_padding";
private:
std::unordered_set<Type*> visited_;

View File

@ -212,13 +212,14 @@ void DrgnParser::enumerateClassParents(struct drgn_type* type,
}
auto ptype = enumerateType(parent_qual_type.type);
uint64_t poffset = drgn_parents[i].bit_offset / 8;
uint64_t poffset = drgn_parents[i].bit_offset;
Parent p(ptype, poffset);
parents.push_back(p);
}
std::sort(parents.begin(), parents.end(),
[](const auto& a, const auto& b) { return a.offset < b.offset; });
std::sort(parents.begin(), parents.end(), [](const auto& a, const auto& b) {
return a.bitOffset < b.bitOffset;
});
}
void DrgnParser::enumerateClassMembers(struct drgn_type* type,
@ -257,17 +258,16 @@ void DrgnParser::enumerateClassMembers(struct drgn_type* type,
if (drgn_members[i].name)
member_name = drgn_members[i].name;
// TODO bitfields
auto mtype = enumerateType(member_type);
uint64_t moffset = drgn_members[i].bit_offset / 8;
uint64_t moffset = drgn_members[i].bit_offset;
Member m(mtype, member_name, moffset); // TODO
Member m{mtype, member_name, moffset, bit_field_size};
members.push_back(m);
}
std::sort(members.begin(), members.end(),
[](const auto& a, const auto& b) { return a.offset < b.offset; });
std::sort(members.begin(), members.end(), [](const auto& a, const auto& b) {
return a.bitOffset < b.bitOffset;
});
}
void DrgnParser::enumerateTemplateParam(drgn_type_template_parameter* tparams,

View File

@ -62,7 +62,7 @@ void flattenParent(const Parent& parent,
for (size_t i = 0; i < parentClass->members.size(); i++) {
const auto& member = parentClass->members[i];
flattenedMembers.push_back(member);
flattenedMembers.back().offset += parent.offset;
flattenedMembers.back().bitOffset += parent.bitOffset;
if (i == 0) {
flattenedMembers.back().align =
std::max(flattenedMembers.back().align, parentClass->align());
@ -71,7 +71,8 @@ void flattenParent(const Parent& parent,
} else if (Container* parentContainer =
dynamic_cast<Container*>(&parentType)) {
// Create a new member to represent this parent container
flattenedMembers.emplace_back(parentContainer, "__parent", parent.offset);
flattenedMembers.emplace_back(parentContainer, "__parent",
parent.bitOffset);
} else {
throw std::runtime_error("Invalid type for parent");
}
@ -169,8 +170,8 @@ void Flattener::visit(Class& c) {
std::size_t member_idx = 0;
std::size_t parent_idx = 0;
while (member_idx < c.members.size() && parent_idx < c.parents.size()) {
auto member_offset = c.members[member_idx].offset;
auto parent_offset = c.parents[parent_idx].offset;
auto member_offset = c.members[member_idx].bitOffset;
auto parent_offset = c.parents[parent_idx].bitOffset;
if (member_offset < parent_offset) {
// Add our own member
const auto& member = c.members[member_idx++];

View File

@ -166,7 +166,8 @@ void Printer::print_param(const TemplateParam& param) {
void Printer::print_parent(const Parent& parent) {
depth_++;
prefix();
out_ << "Parent (offset: " << parent.offset << ")" << std::endl;
out_ << "Parent (offset: " << static_cast<double>(parent.bitOffset) / 8 << ")"
<< std::endl;
print(*parent.type);
depth_--;
}
@ -174,8 +175,13 @@ void Printer::print_parent(const Parent& parent) {
void Printer::print_member(const Member& member) {
depth_++;
prefix();
out_ << "Member: " << member.name << " (offset: " << member.offset
<< align_str(member.align) << ")" << std::endl;
out_ << "Member: " << member.name
<< " (offset: " << static_cast<double>(member.bitOffset) / 8;
out_ << align_str(member.align);
if (member.bitsize != 0) {
out_ << ", bitsize: " << member.bitsize;
}
out_ << ")" << std::endl;
print(*member.type);
depth_--;
}

View File

@ -47,7 +47,8 @@ void RemoveIgnored::visit(Class& c) {
auto* primitive = typeGraph_.make_type<Primitive>(Primitive::Kind::Int8);
auto* paddingArray =
typeGraph_.make_type<Array>(primitive, c.members[i].type->size());
c.members[i] = Member{paddingArray, c.members[i].name, c.members[i].offset};
c.members[i] =
Member{paddingArray, c.members[i].name, c.members[i].bitOffset};
}
}

View File

@ -69,14 +69,15 @@ class Type {
struct Member {
Member(Type* type,
const std::string& name,
uint64_t offset,
uint64_t align = 0)
: type(type), name(name), offset(offset), align(align) {
uint64_t bitOffset,
uint64_t bitsize = 0)
: type(type), name(name), bitOffset(bitOffset), bitsize(bitsize) {
}
Type* type;
std::string name; // TODO make optional?
uint64_t offset;
std::string name;
uint64_t bitOffset;
uint64_t bitsize;
uint64_t align = 0;
};
@ -91,11 +92,11 @@ struct Function {
class Class;
struct Parent {
Parent(Type* type, uint64_t offset) : type(type), offset(offset) {
Parent(Type* type, uint64_t bitOffset) : type(type), bitOffset(bitOffset) {
}
Type* type;
uint64_t offset;
uint64_t bitOffset;
};
struct TemplateParam {

View File

@ -0,0 +1,125 @@
definitions = '''
struct Single {
int bitfield : 3;
};
struct WithinBytes {
char a : 3;
char b : 5;
char c : 7;
};
struct StraddleBytes {
char a : 7;
char b : 7;
char c : 2;
};
struct Mixed {
int a;
char b : 4;
short c : 12;
char d;
int e : 22;
};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wbitfield-width"
// The bitfield will max out at the size of its type. Extra bits act as padding.
struct MoreBitsThanType {
char a : 29;
};
#pragma clang diagnostic pop
// A zero-sized bitfield adds default padding between neighbouring bitfields
struct ZeroBits {
char b1 : 3;
char : 0;
char b2 : 2;
};
enum class MyEnum {
One,
Two,
Three,
};
struct Enum {
MyEnum e : 2;
MyEnum f : 4;
};
'''
# TODO The sizes do not take bitfields into account. They count each field as
# if they were regular primitives.
[cases]
[cases.single]
cli_options = ["-ftype-graph"]
oil_skip = "not implemented"
param_types = ["Single&"]
setup = "return {};"
expect_json = '''[
{"staticSize":4, "dynamicSize":0, "exclusiveSize":0, "members":[
{"staticSize":4, "dynamicSize":0, "exclusiveSize":4}
]}]'''
[cases.within_bytes]
cli_options = ["-ftype-graph"]
oil_skip = "not implemented"
param_types = ["WithinBytes&"]
setup = "return {};"
expect_json = '''[
{"staticSize":2, "dynamicSize":0, "exclusiveSize":0, "members":[
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1},
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1},
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1}
]}]'''
[cases.straddle_bytes]
cli_options = ["-ftype-graph"]
oil_skip = "not implemented"
param_types = ["StraddleBytes&"]
setup = "return {};"
expect_json = '''[
{"staticSize":3, "dynamicSize":0, "exclusiveSize":0, "members":[
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1},
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1},
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1}
]}]'''
[cases.mixed]
cli_options = ["-ftype-graph"]
oil_skip = "not implemented"
param_types = ["Mixed&"]
setup = "return {};"
expect_json = '''[
{"staticSize":12, "dynamicSize":0, "exclusiveSize":0, "members":[
{"staticSize":4, "dynamicSize":0, "exclusiveSize":4},
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1},
{"staticSize":2, "dynamicSize":0, "exclusiveSize":2},
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1},
{"staticSize":4, "dynamicSize":0, "exclusiveSize":4}
]}]'''
[cases.more_bits_than_type] # TODO member sizes are wrong
skip = "drgn errors out"
cli_options = ["-ftype-graph"]
oil_skip = "not implemented"
param_types = ["MoreBitsThanType&"]
setup = "return {};"
expect_json = '"TODO"'
[cases.zero_bits]
cli_options = ["-ftype-graph"]
oil_skip = "not implemented"
param_types = ["ZeroBits&"]
setup = "return {};"
expect_json = '''[
{"staticSize":2, "dynamicSize":0, "exclusiveSize":0, "members":[
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1},
{"staticSize":1, "dynamicSize":0, "exclusiveSize":1}
]}]'''
[cases.enum]
cli_options = ["-ftype-graph"]
oil_skip = "not implemented"
param_types = ["Enum&"]
setup = "return {};"
expect_json = '''[
{"staticSize":4, "dynamicSize":0, "exclusiveSize":0, "members":[
{"staticSize":4, "dynamicSize":0, "exclusiveSize":4},
{"staticSize":4, "dynamicSize":0, "exclusiveSize":4}
]}]'''

View File

@ -11,13 +11,13 @@ TEST(AddPaddingTest, BetweenMembers) {
auto myint8 = Primitive{Primitive::Kind::Int8};
auto myint64 = Primitive{Primitive::Kind::Int64};
myclass.members.push_back(Member(&myint8, "n1", 0));
myclass.members.push_back(Member(&myint64, "n2", 8));
myclass.members.push_back(Member(&myint64, "n2", 8 * 8));
test(AddPadding::createPass(), {myclass}, R"(
[0] Class: MyClass (size: 16)
Member: n1 (offset: 0)
Primitive: int8_t
Member: __oid_padding (offset: 1)
Member: __oi_padding (offset: 1)
[1] Array: (length: 7)
Primitive: int8_t
Member: n2 (offset: 8)
@ -30,7 +30,7 @@ TEST(AddPaddingTest, AtEnd) {
auto myint8 = Primitive{Primitive::Kind::Int8};
auto myint64 = Primitive{Primitive::Kind::Int64};
myclass.members.push_back(Member(&myint64, "n1", 0));
myclass.members.push_back(Member(&myint8, "n2", 8));
myclass.members.push_back(Member(&myint8, "n2", 8 * 8));
test(AddPadding::createPass(), {myclass}, R"(
[0] Struct: MyStruct (size: 16)
@ -38,7 +38,7 @@ TEST(AddPaddingTest, AtEnd) {
Primitive: int64_t
Member: n2 (offset: 8)
Primitive: int8_t
Member: __oid_padding (offset: 9)
Member: __oi_padding (offset: 9)
[1] Array: (length: 7)
Primitive: int8_t
)");
@ -60,4 +60,55 @@ TEST(AddPaddingTest, UnionNotPadded) {
)");
}
TEST(AddPaddingTest, Bitfields) {
auto myclass = Class{Class::Kind::Class, "MyClass", 16};
auto myint64 = Primitive{Primitive::Kind::Int64};
auto myint16 = Primitive{Primitive::Kind::Int16};
auto myint8 = Primitive{Primitive::Kind::Int8};
Member b1{&myint64, "b1", 0};
b1.bitsize = 3;
Member b2{&myint8, "b2", 3};
b2.bitsize = 2;
// There may be a 0-sized bitfield between these two that does not appear
// in the DWARF. This would add padding and push b3 to the next byte.
Member b3{&myint8, "b3", 8};
b3.bitsize = 1;
Member b4{&myint64, "b4", 8 * 8};
b4.bitsize = 24;
Member n{&myint16, "n", 12 * 8};
myclass.members.push_back(b1);
myclass.members.push_back(b2);
myclass.members.push_back(b3);
myclass.members.push_back(b4);
myclass.members.push_back(n);
test(AddPadding::createPass(), {myclass}, R"(
[0] Class: MyClass (size: 16)
Member: b1 (offset: 0, bitsize: 3)
Primitive: int64_t
Member: b2 (offset: 0.375, bitsize: 2)
Primitive: int8_t
Member: __oi_padding (offset: 0.625, bitsize: 3)
Primitive: int64_t
Member: b3 (offset: 1, bitsize: 1)
Primitive: int8_t
Member: __oi_padding (offset: 1.125, bitsize: 55)
Primitive: int64_t
Member: b4 (offset: 8, bitsize: 24)
Primitive: int64_t
Member: __oi_padding (offset: 11)
[1] Array: (length: 1)
Primitive: int8_t
Member: n (offset: 12)
Primitive: int16_t
Member: __oi_padding (offset: 14)
[2] Array: (length: 2)
Primitive: int8_t
)");
}
// TODO test we follow class members, templates, children

View File

@ -10,7 +10,7 @@ TEST(AlignmentCalcTest, PrimitiveMembers) {
auto myint8 = Primitive{Primitive::Kind::Int8};
auto myint64 = Primitive{Primitive::Kind::Int64};
myclass.members.push_back(Member(&myint8, "n", 0));
myclass.members.push_back(Member(&myint64, "n", 8));
myclass.members.push_back(Member(&myint64, "n", 8 * 8));
test(AlignmentCalc::createPass(), {myclass}, R"(
[0] Class: MyClass (size: 16, align: 8)
@ -25,12 +25,12 @@ TEST(AlignmentCalcTest, StructMembers) {
auto mystruct = Class{Class::Kind::Struct, "MyStruct", 8};
auto myint32 = Primitive{Primitive::Kind::Int32};
mystruct.members.push_back(Member(&myint32, "n1", 0));
mystruct.members.push_back(Member(&myint32, "n2", 4));
mystruct.members.push_back(Member(&myint32, "n2", 4 * 8));
auto myclass = Class{Class::Kind::Class, "MyClass", 12};
auto myint8 = Primitive{Primitive::Kind::Int8};
myclass.members.push_back(Member(&myint8, "n", 0));
myclass.members.push_back(Member(&mystruct, "s", 4));
myclass.members.push_back(Member(&mystruct, "s", 4 * 8));
test(AlignmentCalc::createPass(), {myclass}, R"(
[0] Class: MyClass (size: 12, align: 4)
@ -50,7 +50,7 @@ TEST(AlignmentCalcTest, StructInContainer) {
auto myint8 = Primitive{Primitive::Kind::Int8};
auto myint64 = Primitive{Primitive::Kind::Int64};
myclass.members.push_back(Member(&myint8, "n", 0));
myclass.members.push_back(Member(&myint64, "n", 8));
myclass.members.push_back(Member(&myint64, "n", 8 * 8));
auto mycontainer = Container{ContainerInfo{}, 8};
mycontainer.templateParams.push_back(&myclass);
@ -71,7 +71,7 @@ TEST(AlignmentCalcTest, Packed) {
auto myint8 = Primitive{Primitive::Kind::Int8};
auto myint64 = Primitive{Primitive::Kind::Int64};
mystruct.members.push_back(Member(&myint8, "n1", 0));
mystruct.members.push_back(Member(&myint64, "n2", 1));
mystruct.members.push_back(Member(&myint64, "n2", 1 * 8));
test(AlignmentCalc::createPass(), {mystruct}, R"(
[0] Struct: MyStruct (size: 9, align: 8, packed)

View File

@ -581,4 +581,88 @@ TEST_F(DrgnParserTest, VirtualFunctions) {
)");
}
TEST_F(DrgnParserTest, BitfieldsSingle) {
test("oid_test_case_bitfields_single", R"(
[0] Pointer
[1] Struct: Single (size: 4)
Member: bitfield (offset: 0, bitsize: 3)
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, BitfieldsWithinBytes) {
test("oid_test_case_bitfields_within_bytes", R"(
[0] Pointer
[1] Struct: WithinBytes (size: 2)
Member: a (offset: 0, bitsize: 3)
Primitive: int8_t
Member: b (offset: 0.375, bitsize: 5)
Primitive: int8_t
Member: c (offset: 1, bitsize: 7)
Primitive: int8_t
)");
}
TEST_F(DrgnParserTest, BitfieldsStraddleBytes) {
test("oid_test_case_bitfields_straddle_bytes", R"(
[0] Pointer
[1] Struct: StraddleBytes (size: 3)
Member: a (offset: 0, bitsize: 7)
Primitive: int8_t
Member: b (offset: 1, bitsize: 7)
Primitive: int8_t
Member: c (offset: 2, bitsize: 2)
Primitive: int8_t
)");
}
TEST_F(DrgnParserTest, BitfieldsMixed) {
test("oid_test_case_bitfields_mixed", R"(
[0] Pointer
[1] Struct: Mixed (size: 12)
Member: a (offset: 0)
Primitive: int32_t
Member: b (offset: 4, bitsize: 4)
Primitive: int8_t
Member: c (offset: 4.5, bitsize: 12)
Primitive: int16_t
Member: d (offset: 6)
Primitive: int8_t
Member: e (offset: 8, bitsize: 22)
Primitive: int32_t
)");
}
TEST_F(DrgnParserTest, BitfieldsMoreBitsThanType) {
GTEST_SKIP() << "drgn errors out";
test("oid_test_case_bitfields_more_bits_than_type", R"(
[0] Pointer
[1] Struct: MoreBitsThanType (size: 4)
Member: a (offset: 0, bitsize: 8)
Primitive: int8_t
)");
}
TEST_F(DrgnParserTest, BitfieldsZeroBits) {
test("oid_test_case_bitfields_zero_bits", R"(
[0] Pointer
[1] Struct: ZeroBits (size: 2)
Member: b1 (offset: 0, bitsize: 3)
Primitive: int8_t
Member: b2 (offset: 1, bitsize: 2)
Primitive: int8_t
)");
}
TEST_F(DrgnParserTest, BitfieldsEnum) {
test("oid_test_case_bitfields_enum", R"(
[0] Pointer
[1] Struct: Enum (size: 4)
Member: e (offset: 0, bitsize: 2)
Enum: MyEnum (size: 4)
Member: f (offset: 0.25, bitsize: 4)
Enum: MyEnum (size: 4)
)");
}
// TODO test virtual classes

View File

@ -22,8 +22,8 @@ TEST(FlattenerTest, NoParents) {
mystruct.members.push_back(Member(&myint, "n0", 0));
myclass.members.push_back(Member(&myint, "n", 0));
myclass.members.push_back(Member(&myenum, "e", 4));
myclass.members.push_back(Member(&mystruct, "mystruct", 8));
myclass.members.push_back(Member(&myenum, "e", 4 * 8));
myclass.members.push_back(Member(&mystruct, "mystruct", 8 * 8));
test(Flattener::createPass(), {myclass}, R"(
[0] Class: MyClass (size: 12)
@ -58,7 +58,7 @@ TEST(FlattenerTest, OnlyParents) {
classB.members.push_back(Member(&myint, "b", 0));
classA.parents.push_back(Parent(&classB, 0));
classA.parents.push_back(Parent(&classC, 4));
classA.parents.push_back(Parent(&classC, 4 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 8)
@ -90,8 +90,8 @@ TEST(FlattenerTest, ParentsFirst) {
classB.members.push_back(Member(&myint, "b", 0));
classA.parents.push_back(Parent(&classB, 0));
classA.parents.push_back(Parent(&classC, 4));
classA.members.push_back(Member(&myint, "a", 8));
classA.parents.push_back(Parent(&classC, 4 * 8));
classA.members.push_back(Member(&myint, "a", 8 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 12)
@ -126,8 +126,8 @@ TEST(FlattenerTest, MembersFirst) {
classB.members.push_back(Member(&myint, "b", 0));
classA.members.push_back(Member(&myint, "a", 0));
classA.parents.push_back(Parent(&classB, 4));
classA.parents.push_back(Parent(&classC, 8));
classA.parents.push_back(Parent(&classB, 4 * 8));
classA.parents.push_back(Parent(&classC, 8 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 12)
@ -163,9 +163,9 @@ TEST(FlattenerTest, MixedMembersAndParents) {
classB.members.push_back(Member(&myint, "b", 0));
classA.parents.push_back(Parent(&classB, 0));
classA.members.push_back(Member(&myint, "a1", 4));
classA.members.push_back(Member(&myint, "a2", 8));
classA.parents.push_back(Parent(&classC, 12));
classA.members.push_back(Member(&myint, "a1", 4 * 8));
classA.members.push_back(Member(&myint, "a2", 8 * 8));
classA.parents.push_back(Parent(&classC, 12 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 16)
@ -199,9 +199,9 @@ TEST(FlattenerTest, EmptyParent) {
classC.members.push_back(Member(&myint, "c", 0));
classA.members.push_back(Member(&myint, "a1", 4));
classA.members.push_back(Member(&myint, "a2", 8));
classA.parents.push_back(Parent(&classB, 4));
classA.members.push_back(Member(&myint, "a1", 4 * 8));
classA.members.push_back(Member(&myint, "a2", 8 * 8));
classA.parents.push_back(Parent(&classB, 0));
classA.parents.push_back(Parent(&classC, 0));
test(Flattener::createPass(), {classA}, R"(
@ -240,11 +240,11 @@ TEST(FlattenerTest, TwoDeep) {
classC.members.push_back(Member(&myint, "c", 0));
classB.parents.push_back(Parent(&classD, 0));
classB.members.push_back(Member(&myint, "b", 4));
classB.members.push_back(Member(&myint, "b", 4 * 8));
classA.parents.push_back(Parent(&classB, 0));
classA.parents.push_back(Parent(&classC, 8));
classA.members.push_back(Member(&myint, "a", 12));
classA.parents.push_back(Parent(&classC, 8 * 8));
classA.members.push_back(Member(&myint, "a", 12 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 16)
@ -280,11 +280,11 @@ TEST(FlattenerTest, DiamondInheritance) {
classC.members.push_back(Member(&myint, "c", 0));
classB.parents.push_back(Parent(&classC, 0));
classB.members.push_back(Member(&myint, "b", 4));
classB.members.push_back(Member(&myint, "b", 4 * 8));
classA.parents.push_back(Parent(&classB, 0));
classA.parents.push_back(Parent(&classC, 8));
classA.members.push_back(Member(&myint, "a", 12));
classA.parents.push_back(Parent(&classC, 8 * 8));
classA.members.push_back(Member(&myint, "a", 12 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 16)
@ -316,10 +316,10 @@ TEST(FlattenerTest, Member) {
classC.members.push_back(Member(&myint, "c", 0));
classB.parents.push_back(Parent(&classC, 0));
classB.members.push_back(Member(&myint, "b", 4));
classB.members.push_back(Member(&myint, "b", 4 * 8));
classA.members.push_back(Member(&myint, "a", 0));
classA.members.push_back(Member(&classB, "b", 4));
classA.members.push_back(Member(&classB, "b", 4 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 12)
@ -351,10 +351,10 @@ TEST(FlattenerTest, MemberOfParent) {
classC.members.push_back(Member(&myint, "c", 0));
classB.members.push_back(Member(&myint, "b", 0));
classB.members.push_back(Member(&classC, "c", 4));
classB.members.push_back(Member(&classC, "c", 4 * 8));
classA.parents.push_back(Parent(&classB, 0));
classA.members.push_back(Member(&myint, "a", 8));
classA.members.push_back(Member(&myint, "a", 8 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 12)
@ -386,7 +386,7 @@ TEST(FlattenerTest, ContainerParam) {
classB.members.push_back(Member(&myint, "b", 0));
classA.parents.push_back(Parent(&classB, 0));
classA.members.push_back(Member(&myint, "a", 4));
classA.members.push_back(Member(&myint, "a", 4 * 8));
container.templateParams.push_back(TemplateParam(&classA));
container.templateParams.push_back(TemplateParam(&myint));
@ -416,7 +416,7 @@ TEST(FlattenerTest, Array) {
auto classA = Class{Class::Kind::Class, "ClassA", 8};
classA.parents.push_back(Parent(&classB, 0));
classA.members.push_back(Member(&myint, "a", 4));
classA.members.push_back(Member(&myint, "a", 4 * 8));
auto arrayA = Array{&classA, 5};
@ -441,7 +441,7 @@ TEST(FlattenerTest, Typedef) {
auto classA = Class{Class::Kind::Class, "ClassA", 8};
classA.parents.push_back(Parent(&classB, 0));
classA.members.push_back(Member(&myint, "a", 4));
classA.members.push_back(Member(&myint, "a", 4 * 8));
auto aliasA = Typedef{"aliasA", &classA};
@ -468,7 +468,7 @@ TEST(FlattenerTest, TypedefParent) {
auto classA = Class{Class::Kind::Class, "ClassA", 8};
classA.parents.push_back(Parent(&aliasB, 0));
classA.members.push_back(Member(&myint, "a", 4));
classA.members.push_back(Member(&myint, "a", 4 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 8)
@ -491,7 +491,7 @@ TEST(FlattenerTest, Pointer) {
auto classA = Class{Class::Kind::Class, "ClassA", 8};
classA.parents.push_back(Parent(&classB, 0));
classA.members.push_back(Member(&myint, "a", 4));
classA.members.push_back(Member(&myint, "a", 4 * 8));
auto ptrA = Pointer{&classA};
auto classC = Class{Class::Kind::Class, "ClassC", 8};
@ -545,13 +545,29 @@ TEST(FlattenerTest, Alignment) {
classC.setAlign(16);
classC.members.push_back(Member(&myint, "c", 0));
classB.members.push_back(Member(&myint, "b", 0, 8));
Member memberB{&myint, "b", 0};
memberB.align = 8;
classB.members.push_back(memberB);
classA.parents.push_back(Parent(&classB, 0));
classA.parents.push_back(Parent(&classC, 4));
classA.members.push_back(Member(&myint, "a", 8));
classA.parents.push_back(Parent(&classC, 4 * 8));
classA.members.push_back(Member(&myint, "a", 8 * 8));
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 12)
Parent (offset: 0)
[1] Class: ClassB (size: 4)
Member: b (offset: 0, align: 8)
Primitive: int32_t
Parent (offset: 4)
[2] Class: ClassC (size: 4, align: 16)
Member: c (offset: 0)
Primitive: int32_t
Member: a (offset: 8)
Primitive: int32_t
)",
R"(
[0] Class: ClassA (size: 12)
Member: b (offset: 0, align: 8)
Primitive: int32_t
@ -600,7 +616,7 @@ TEST(FlattenerTest, Children) {
classB.members.push_back(Member(&myint, "b", 0));
classA.parents.push_back(Parent(&classB, 0));
classA.parents.push_back(Parent(&classC, 4));
classA.parents.push_back(Parent(&classC, 4 * 8));
classB.children.push_back(classA);
classC.children.push_back(classA);
@ -635,11 +651,11 @@ TEST(FlattenerTest, ChildrenTwoDeep) {
classC.members.push_back(Member(&myint, "c", 0));
classB.parents.push_back(Parent(&classD, 0));
classB.members.push_back(Member(&myint, "b", 4));
classB.members.push_back(Member(&myint, "b", 4 * 8));
classA.parents.push_back(Parent(&classB, 0));
classA.parents.push_back(Parent(&classC, 8));
classA.members.push_back(Member(&myint, "a", 12));
classA.parents.push_back(Parent(&classC, 8 * 8));
classA.members.push_back(Member(&myint, "a", 12 * 8));
classD.children.push_back(classB);
classB.children.push_back(classA);
@ -676,7 +692,7 @@ TEST(FlattenerTest, ParentContainer) {
auto classA = Class{Class::Kind::Class, "ClassA", 32};
classA.parents.push_back(Parent{&vector, 0});
classA.members.push_back(Member{&myint, "a", 24});
classA.members.push_back(Member{&myint, "a", 24 * 8});
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 32)
@ -706,7 +722,7 @@ TEST(FlattenerTest, ParentTwoContainers) {
auto classA = Class{Class::Kind::Class, "ClassA", 48};
classA.parents.push_back(Parent{&vector, 0});
classA.parents.push_back(Parent{&vector, 24});
classA.parents.push_back(Parent{&vector, 24 * 8});
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 48)
@ -739,7 +755,7 @@ TEST(FlattenerTest, ParentClassAndContainer) {
auto classA = Class{Class::Kind::Class, "ClassA", 32};
classA.parents.push_back(Parent{&classB, 0});
classA.parents.push_back(Parent{&vector, 8});
classA.parents.push_back(Parent{&vector, 8 * 8});
test(Flattener::createPass(), {classA}, R"(
[0] Class: ClassA (size: 32)

View File

@ -11,8 +11,8 @@ TEST(RemoveIgnoredTest, Match) {
auto classA = Class{Class::Kind::Class, "ClassA", 12};
classA.members.push_back(Member(&classB, "a", 0));
classA.members.push_back(Member(&classB, "b", 4));
classA.members.push_back(Member(&classB, "c", 8));
classA.members.push_back(Member(&classB, "b", 4 * 8));
classA.members.push_back(Member(&classB, "c", 8 * 8));
const std::vector<std::pair<std::string, std::string>>& membersToIgnore = {
{"ClassA", "b"},
@ -44,8 +44,8 @@ TEST(RemoveIgnoredTest, TypeMatchMemberMiss) {
auto classA = Class{Class::Kind::Class, "ClassA", 12};
classA.members.push_back(Member(&classB, "a", 0));
classA.members.push_back(Member(&classB, "b", 4));
classA.members.push_back(Member(&classB, "c", 8));
classA.members.push_back(Member(&classB, "b", 4 * 8));
classA.members.push_back(Member(&classB, "c", 8 * 8));
const std::vector<std::pair<std::string, std::string>>& membersToIgnore = {
{"ClassA", "x"},
@ -67,8 +67,8 @@ TEST(RemoveIgnoredTest, MemberMatchWrongType) {
auto classA = Class{Class::Kind::Class, "ClassA", 12};
classA.members.push_back(Member(&classB, "a", 0));
classA.members.push_back(Member(&classB, "b", 4));
classA.members.push_back(Member(&classB, "c", 8));
classA.members.push_back(Member(&classB, "b", 4 * 8));
classA.members.push_back(Member(&classB, "c", 8 * 8));
const std::vector<std::pair<std::string, std::string>>& membersToIgnore = {
{"ClassB", "b"},