#include #include "core.h" #include "imports/import_source.h" using namespace rcx; class TestImportSource : public QObject { Q_OBJECT private slots: // Basic type tests void emptyInput(); void noStructs(); void singleEmptyStruct(); void stdintTypes(); void windowsTypes(); void platformPointerTypes(); void standardCTypes(); void multiWordTypes(); void floatDouble(); void boolType(); // Pointer tests void voidPointer(); void typedPointer(); void selfReferencingPointer(); void doublePointer(); // Array tests void primitiveArray(); void charArrayToUtf8(); void wcharArrayToUtf16(); void floatArrayToVec2(); void floatArrayToVec3(); void floatArrayToVec4(); void floatArray4x4ToMat4x4(); void genericFloatArray(); void structArray(); // Comment offset tests void commentOffsets(); void computedOffsets(); void mixedOffsetsAutoDetect(); // Multi-struct tests void multiStruct(); void pointerCrossRef(); // Forward declarations void forwardDeclaration(); // Union handling void unionContainer(); void unionWithCommentOffsets(); void namedUnion(); // Padding fields void paddingFieldExpansion(); // static_assert void staticAssertTailPadding(); // Embedded struct void embeddedStruct(); // Typedef void typedefBasic(); // Qualifiers void constVolatileQualifiers(); void structPrefixOnType(); // Edge cases void bitfieldSkipped(); void bitfieldWithOffsetsEmitsHex(); void hexArraySizes(); void windowsStylePEB(); void classKeyword(); void inheritanceSkipped(); // Enum tests void enumBasic(); void enumAutoValues(); void enumHexValues(); void enumInStruct(); void enumClass(); // Round-trip test (requires generator.h) void basicRoundTrip(); }; // ── Helper ── static int countRoots(const NodeTree& tree) { int n = 0; for (const auto& node : tree.nodes) if (node.parentId == 0 && node.kind == NodeKind::Struct) n++; return n; } static QVector childrenOf(const NodeTree& tree, uint64_t parentId) { QVector result; for (int i = 0; i < tree.nodes.size(); i++) if (tree.nodes[i].parentId == parentId) result.append(i); return result; } // ── Tests ── void TestImportSource::emptyInput() { QString err; NodeTree tree = importFromSource(QString(), &err); QVERIFY(tree.nodes.isEmpty()); QVERIFY(!err.isEmpty()); } void TestImportSource::noStructs() { QString err; NodeTree tree = importFromSource(QStringLiteral("int x = 42;"), &err); QVERIFY(tree.nodes.isEmpty()); QVERIFY(!err.isEmpty()); } void TestImportSource::singleEmptyStruct() { NodeTree tree = importFromSource(QStringLiteral( "struct Empty {};\n" )); QCOMPARE(countRoots(tree), 1); QCOMPARE(tree.nodes[0].name, QStringLiteral("Empty")); QCOMPARE(tree.nodes[0].kind, NodeKind::Struct); } void TestImportSource::stdintTypes() { NodeTree tree = importFromSource(QStringLiteral( "struct Test {\n" " uint8_t a;\n" " int8_t b;\n" " uint16_t c;\n" " int16_t d;\n" " uint32_t e;\n" " int32_t f;\n" " uint64_t g;\n" " int64_t h;\n" "};\n" )); QCOMPARE(countRoots(tree), 1); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 8); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UInt8); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Int8); QCOMPARE(tree.nodes[kids[2]].kind, NodeKind::UInt16); QCOMPARE(tree.nodes[kids[3]].kind, NodeKind::Int16); QCOMPARE(tree.nodes[kids[4]].kind, NodeKind::UInt32); QCOMPARE(tree.nodes[kids[5]].kind, NodeKind::Int32); QCOMPARE(tree.nodes[kids[6]].kind, NodeKind::UInt64); QCOMPARE(tree.nodes[kids[7]].kind, NodeKind::Int64); } void TestImportSource::windowsTypes() { NodeTree tree = importFromSource(QStringLiteral( "struct WinTypes {\n" " BYTE a;\n" " WORD b;\n" " DWORD c;\n" " QWORD d;\n" " ULONG e;\n" " LONG f;\n" " USHORT g;\n" " UCHAR h;\n" " BOOLEAN i;\n" " BOOL j;\n" " CHAR k;\n" " WCHAR l;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 12); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UInt8); // BYTE QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::UInt16); // WORD QCOMPARE(tree.nodes[kids[2]].kind, NodeKind::UInt32); // DWORD QCOMPARE(tree.nodes[kids[3]].kind, NodeKind::UInt64); // QWORD QCOMPARE(tree.nodes[kids[4]].kind, NodeKind::UInt32); // ULONG QCOMPARE(tree.nodes[kids[5]].kind, NodeKind::Int32); // LONG QCOMPARE(tree.nodes[kids[6]].kind, NodeKind::UInt16); // USHORT QCOMPARE(tree.nodes[kids[7]].kind, NodeKind::UInt8); // UCHAR QCOMPARE(tree.nodes[kids[8]].kind, NodeKind::UInt8); // BOOLEAN QCOMPARE(tree.nodes[kids[9]].kind, NodeKind::Int32); // BOOL QCOMPARE(tree.nodes[kids[10]].kind, NodeKind::Int8); // CHAR QCOMPARE(tree.nodes[kids[11]].kind, NodeKind::UInt16); // WCHAR } void TestImportSource::platformPointerTypes() { NodeTree tree = importFromSource(QStringLiteral( "struct PtrTypes {\n" " PVOID a;\n" " HANDLE b;\n" " SIZE_T c;\n" " ULONG_PTR d;\n" " uintptr_t e;\n" " size_t f;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 6); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Pointer64); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Pointer64); QCOMPARE(tree.nodes[kids[2]].kind, NodeKind::UInt64); QCOMPARE(tree.nodes[kids[3]].kind, NodeKind::UInt64); QCOMPARE(tree.nodes[kids[4]].kind, NodeKind::UInt64); QCOMPARE(tree.nodes[kids[5]].kind, NodeKind::UInt64); } void TestImportSource::standardCTypes() { NodeTree tree = importFromSource(QStringLiteral( "struct CTypes {\n" " char a;\n" " short b;\n" " int c;\n" " long d;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 4); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Int8); // char QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Int16); // short QCOMPARE(tree.nodes[kids[2]].kind, NodeKind::Int32); // int QCOMPARE(tree.nodes[kids[3]].kind, NodeKind::Int32); // long } void TestImportSource::multiWordTypes() { NodeTree tree = importFromSource(QStringLiteral( "struct MultiWord {\n" " unsigned char a;\n" " unsigned short b;\n" " unsigned int c;\n" " unsigned long d;\n" " long long e;\n" " unsigned long long f;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 6); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UInt8); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::UInt16); QCOMPARE(tree.nodes[kids[2]].kind, NodeKind::UInt32); QCOMPARE(tree.nodes[kids[3]].kind, NodeKind::UInt32); QCOMPARE(tree.nodes[kids[4]].kind, NodeKind::Int64); QCOMPARE(tree.nodes[kids[5]].kind, NodeKind::UInt64); } void TestImportSource::floatDouble() { NodeTree tree = importFromSource(QStringLiteral( "struct FD {\n" " float a;\n" " double b;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 2); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Float); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Double); } void TestImportSource::boolType() { NodeTree tree = importFromSource(QStringLiteral( "struct B {\n" " bool a;\n" " _Bool b;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 2); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Bool); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Bool); } void TestImportSource::voidPointer() { NodeTree tree = importFromSource(QStringLiteral( "struct VP {\n" " void* ptr;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Pointer64); QCOMPARE(tree.nodes[kids[0]].name, QStringLiteral("ptr")); QCOMPARE(tree.nodes[kids[0]].refId, uint64_t(0)); // void* has no target } void TestImportSource::typedPointer() { NodeTree tree = importFromSource(QStringLiteral( "struct Target {\n" " int x;\n" "};\n" "struct HasPtr {\n" " Target* pTarget;\n" "};\n" )); QCOMPARE(countRoots(tree), 2); // Find HasPtr int hasPtrIdx = -1; for (int i = 0; i < tree.nodes.size(); i++) { if (tree.nodes[i].name == QStringLiteral("HasPtr") && tree.nodes[i].parentId == 0) { hasPtrIdx = i; break; } } QVERIFY(hasPtrIdx >= 0); auto kids = childrenOf(tree, tree.nodes[hasPtrIdx].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Pointer64); QVERIFY(tree.nodes[kids[0]].refId != 0); // refId should point to Target struct int targetIdx = tree.indexOfId(tree.nodes[kids[0]].refId); QVERIFY(targetIdx >= 0); QCOMPARE(tree.nodes[targetIdx].name, QStringLiteral("Target")); } void TestImportSource::selfReferencingPointer() { NodeTree tree = importFromSource(QStringLiteral( "struct Node {\n" " int value;\n" " Node* next;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 2); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Pointer64); QCOMPARE(tree.nodes[kids[1]].refId, tree.nodes[0].id); } void TestImportSource::doublePointer() { NodeTree tree = importFromSource(QStringLiteral( "struct DP {\n" " void** ppData;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Pointer64); } void TestImportSource::primitiveArray() { NodeTree tree = importFromSource(QStringLiteral( "struct PA {\n" " int32_t values[10];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Array); QCOMPARE(tree.nodes[kids[0]].arrayLen, 10); QCOMPARE(tree.nodes[kids[0]].elementKind, NodeKind::Int32); } void TestImportSource::charArrayToUtf8() { NodeTree tree = importFromSource(QStringLiteral( "struct CA {\n" " char name[64];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UTF8); QCOMPARE(tree.nodes[kids[0]].strLen, 64); } void TestImportSource::wcharArrayToUtf16() { NodeTree tree = importFromSource(QStringLiteral( "struct WC {\n" " wchar_t name[32];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UTF16); QCOMPARE(tree.nodes[kids[0]].strLen, 32); } void TestImportSource::floatArrayToVec2() { NodeTree tree = importFromSource(QStringLiteral( "struct V {\n" " float pos[2];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Vec2); } void TestImportSource::floatArrayToVec3() { NodeTree tree = importFromSource(QStringLiteral( "struct V {\n" " float pos[3];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Vec3); } void TestImportSource::floatArrayToVec4() { NodeTree tree = importFromSource(QStringLiteral( "struct V {\n" " float rot[4];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Vec4); } void TestImportSource::floatArray4x4ToMat4x4() { NodeTree tree = importFromSource(QStringLiteral( "struct M {\n" " float matrix[4][4];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Mat4x4); } void TestImportSource::genericFloatArray() { NodeTree tree = importFromSource(QStringLiteral( "struct GF {\n" " float values[8];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Array); QCOMPARE(tree.nodes[kids[0]].arrayLen, 8); QCOMPARE(tree.nodes[kids[0]].elementKind, NodeKind::Float); } void TestImportSource::structArray() { NodeTree tree = importFromSource(QStringLiteral( "struct Item {\n" " int id;\n" "};\n" "struct Container {\n" " Item items[5];\n" "};\n" )); QCOMPARE(countRoots(tree), 2); // Find Container int contIdx = -1; for (int i = 0; i < tree.nodes.size(); i++) { if (tree.nodes[i].name == QStringLiteral("Container") && tree.nodes[i].parentId == 0) { contIdx = i; break; } } QVERIFY(contIdx >= 0); auto kids = childrenOf(tree, tree.nodes[contIdx].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Array); QCOMPARE(tree.nodes[kids[0]].arrayLen, 5); QCOMPARE(tree.nodes[kids[0]].elementKind, NodeKind::Struct); } void TestImportSource::commentOffsets() { NodeTree tree = importFromSource(QStringLiteral( "struct Offsets {\n" " uint64_t vtable; // 0x0\n" " float health; // 0x8\n" " uint8_t _pad000C[0x4]; // 0xC\n" " double score; // 0x10\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); // vtable at 0x0 QCOMPARE(tree.nodes[kids[0]].offset, 0); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UInt64); // health at 0x8 QCOMPARE(tree.nodes[kids[1]].offset, 8); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Float); // _pad at 0xC -> hex nodes // score at 0x10 // Find the double bool foundDouble = false; for (int k : kids) { if (tree.nodes[k].kind == NodeKind::Double) { QCOMPARE(tree.nodes[k].offset, 0x10); foundDouble = true; } } QVERIFY(foundDouble); } void TestImportSource::computedOffsets() { NodeTree tree = importFromSource(QStringLiteral( "struct Computed {\n" " uint8_t a;\n" " uint16_t b;\n" " uint32_t c;\n" " uint64_t d;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 4); QCOMPARE(tree.nodes[kids[0]].offset, 0); // uint8_t at 0 QCOMPARE(tree.nodes[kids[1]].offset, 1); // uint16_t at 1 QCOMPARE(tree.nodes[kids[2]].offset, 3); // uint32_t at 3 QCOMPARE(tree.nodes[kids[3]].offset, 7); // uint64_t at 7 } void TestImportSource::mixedOffsetsAutoDetect() { // If any field has a comment offset, all should use comment mode NodeTree tree = importFromSource(QStringLiteral( "struct Mixed {\n" " uint32_t a; // 0x0\n" " uint32_t b;\n" " uint32_t c; // 0x10\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(tree.nodes[kids[0]].offset, 0); // b has no comment offset, in comment mode it gets computed offset 4 QCOMPARE(tree.nodes[kids[1]].offset, 4); // c has comment offset 0x10 QCOMPARE(tree.nodes[kids[2]].offset, 0x10); } void TestImportSource::multiStruct() { NodeTree tree = importFromSource(QStringLiteral( "struct A {\n" " int x;\n" "};\n" "struct B {\n" " float y;\n" "};\n" "struct C {\n" " double z;\n" "};\n" )); QCOMPARE(countRoots(tree), 3); } void TestImportSource::pointerCrossRef() { NodeTree tree = importFromSource(QStringLiteral( "struct A {\n" " int value;\n" "};\n" "struct B {\n" " A* ref;\n" "};\n" )); // Find B's pointer field int bIdx = -1; for (int i = 0; i < tree.nodes.size(); i++) { if (tree.nodes[i].name == QStringLiteral("B") && tree.nodes[i].parentId == 0) { bIdx = i; break; } } QVERIFY(bIdx >= 0); auto kids = childrenOf(tree, tree.nodes[bIdx].id); QCOMPARE(kids.size(), 1); QVERIFY(tree.nodes[kids[0]].refId != 0); // Should point to A int aIdx = tree.indexOfId(tree.nodes[kids[0]].refId); QVERIFY(aIdx >= 0); QCOMPARE(tree.nodes[aIdx].name, QStringLiteral("A")); } void TestImportSource::forwardDeclaration() { NodeTree tree = importFromSource(QStringLiteral( "struct Bar;\n" "struct Foo {\n" " Bar* pBar;\n" "};\n" "struct Bar {\n" " int val;\n" "};\n" )); QCOMPARE(countRoots(tree), 2); // Foo's pBar should resolve to Bar int fooIdx = -1; for (int i = 0; i < tree.nodes.size(); i++) { if (tree.nodes[i].name == QStringLiteral("Foo") && tree.nodes[i].parentId == 0) { fooIdx = i; break; } } QVERIFY(fooIdx >= 0); auto kids = childrenOf(tree, tree.nodes[fooIdx].id); QCOMPARE(kids.size(), 1); QVERIFY(tree.nodes[kids[0]].refId != 0); } void TestImportSource::unionContainer() { NodeTree tree = importFromSource(QStringLiteral( "struct WithUnion {\n" " union {\n" " float asFloat;\n" " uint32_t asInt;\n" " };\n" " int after;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); // Should have 2 direct children: union container + after QCOMPARE(kids.size(), 2); // First child is the union container const auto& unionNode = tree.nodes[kids[0]]; QCOMPARE(unionNode.kind, NodeKind::Struct); QCOMPARE(unionNode.classKeyword, QStringLiteral("union")); QCOMPARE(unionNode.offset, 0); // Union has 2 children, both at offset 0 auto unionKids = childrenOf(tree, unionNode.id); QCOMPARE(unionKids.size(), 2); QCOMPARE(tree.nodes[unionKids[0]].kind, NodeKind::Float); QCOMPARE(tree.nodes[unionKids[0]].name, QStringLiteral("asFloat")); QCOMPARE(tree.nodes[unionKids[0]].offset, 0); QCOMPARE(tree.nodes[unionKids[1]].kind, NodeKind::UInt32); QCOMPARE(tree.nodes[unionKids[1]].name, QStringLiteral("asInt")); QCOMPARE(tree.nodes[unionKids[1]].offset, 0); // structSpan of union = max member size = 4 QCOMPARE(tree.structSpan(unionNode.id), 4); // after field follows the union at offset 4 QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Int32); QCOMPARE(tree.nodes[kids[1]].name, QStringLiteral("after")); QCOMPARE(tree.nodes[kids[1]].offset, 4); } void TestImportSource::unionWithCommentOffsets() { NodeTree tree = importFromSource(QStringLiteral( "struct S {\n" " uint64_t a; // 0x0\n" " union {\n" " uint32_t x; // 0x8\n" " float y; // 0x8\n" " };\n" " uint32_t b; // 0xC\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 3); // a + union + b // Union at offset 0x8 const auto& unionNode = tree.nodes[kids[1]]; QCOMPARE(unionNode.kind, NodeKind::Struct); QCOMPARE(unionNode.classKeyword, QStringLiteral("union")); QCOMPARE(unionNode.offset, 0x8); // Union members at offset 0 (relative to union) auto unionKids = childrenOf(tree, unionNode.id); QCOMPARE(unionKids.size(), 2); QCOMPARE(tree.nodes[unionKids[0]].offset, 0); QCOMPARE(tree.nodes[unionKids[1]].offset, 0); // b at 0xC QCOMPARE(tree.nodes[kids[2]].offset, 0xC); } void TestImportSource::namedUnion() { NodeTree tree = importFromSource(QStringLiteral( "struct S {\n" " union {\n" " uint16_t shortVal;\n" " uint64_t longVal;\n" " } u3;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); const auto& unionNode = tree.nodes[kids[0]]; QCOMPARE(unionNode.kind, NodeKind::Struct); QCOMPARE(unionNode.classKeyword, QStringLiteral("union")); QCOMPARE(unionNode.name, QStringLiteral("u3")); auto unionKids = childrenOf(tree, unionNode.id); QCOMPARE(unionKids.size(), 2); // structSpan = max(2, 8) = 8 QCOMPARE(tree.structSpan(unionNode.id), 8); } void TestImportSource::paddingFieldExpansion() { NodeTree tree = importFromSource(QStringLiteral( "struct Padded {\n" " uint8_t _pad0000[0x10];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); // 0x10 = 16 bytes, should be 2x Hex64 (best fit) QCOMPARE(kids.size(), 2); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Hex64); QCOMPARE(tree.nodes[kids[0]].offset, 0); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Hex64); QCOMPARE(tree.nodes[kids[1]].offset, 8); } void TestImportSource::staticAssertTailPadding() { NodeTree tree = importFromSource(QStringLiteral( "struct Sized {\n" " uint32_t x;\n" "};\n" "static_assert(sizeof(Sized) == 0x10, \"Size check\");\n" )); // x is 4 bytes, static_assert says 0x10 = 16 // Should have tail padding from offset 4 to 16 (12 bytes) int span = tree.structSpan(tree.nodes[0].id); QCOMPARE(span, 0x10); } void TestImportSource::embeddedStruct() { NodeTree tree = importFromSource(QStringLiteral( "struct Inner {\n" " int a;\n" "};\n" "struct Outer {\n" " Inner embedded;\n" " float after;\n" "};\n" )); int outerIdx = -1; for (int i = 0; i < tree.nodes.size(); i++) { if (tree.nodes[i].name == QStringLiteral("Outer") && tree.nodes[i].parentId == 0) { outerIdx = i; break; } } QVERIFY(outerIdx >= 0); auto kids = childrenOf(tree, tree.nodes[outerIdx].id); QCOMPARE(kids.size(), 2); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Struct); QCOMPARE(tree.nodes[kids[0]].structTypeName, QStringLiteral("Inner")); QVERIFY(tree.nodes[kids[0]].refId != 0); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Float); } void TestImportSource::typedefBasic() { NodeTree tree = importFromSource(QStringLiteral( "typedef uint32_t MyInt;\n" "struct TD {\n" " MyInt value;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UInt32); } void TestImportSource::constVolatileQualifiers() { NodeTree tree = importFromSource(QStringLiteral( "struct Quals {\n" " const uint32_t a;\n" " volatile int32_t b;\n" " const volatile uint8_t c;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 3); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UInt32); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Int32); QCOMPARE(tree.nodes[kids[2]].kind, NodeKind::UInt8); } void TestImportSource::structPrefixOnType() { NodeTree tree = importFromSource(QStringLiteral( "struct Inner {\n" " int val;\n" "};\n" "struct Outer {\n" " struct Inner member;\n" "};\n" )); int outerIdx = -1; for (int i = 0; i < tree.nodes.size(); i++) { if (tree.nodes[i].name == QStringLiteral("Outer") && tree.nodes[i].parentId == 0) { outerIdx = i; break; } } QVERIFY(outerIdx >= 0); auto kids = childrenOf(tree, tree.nodes[outerIdx].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Struct); QCOMPARE(tree.nodes[kids[0]].structTypeName, QStringLiteral("Inner")); } void TestImportSource::bitfieldSkipped() { // Bitfields emit a hex placeholder covering the group NodeTree tree = importFromSource(QStringLiteral( "struct BF {\n" " uint32_t normal;\n" " uint32_t bitA : 4;\n" " uint32_t bitB : 12;\n" " uint32_t after;\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); // normal + Hex16 (16 bits → 2 bytes) + after QCOMPARE(kids.size(), 3); QCOMPARE(tree.nodes[kids[0]].name, QStringLiteral("normal")); QCOMPARE(tree.nodes[kids[0]].offset, 0); QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Hex16); QCOMPARE(tree.nodes[kids[1]].offset, 4); QCOMPARE(tree.nodes[kids[2]].name, QStringLiteral("after")); QCOMPARE(tree.nodes[kids[2]].offset, 6); } void TestImportSource::bitfieldWithOffsetsEmitsHex() { NodeTree tree = importFromSource(QStringLiteral( "struct BF2 {\n" " uint32_t normal; // 0x0\n" " ULONGLONG Valid : 1; // 0x4\n" " ULONGLONG Dirty : 1; // 0x4\n" " ULONGLONG PageFrameNumber : 36; // 0x4\n" " ULONGLONG Reserved : 26; // 0x4\n" " uint32_t after; // 0xC\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); // normal + hex64 (bitfield group: 64 bits) + after = 3 QCOMPARE(kids.size(), 3); QCOMPARE(tree.nodes[kids[0]].name, QStringLiteral("normal")); QCOMPARE(tree.nodes[kids[0]].offset, 0); // Bitfield group emitted as Hex64 at offset 4 QCOMPARE(tree.nodes[kids[1]].kind, NodeKind::Hex64); QCOMPARE(tree.nodes[kids[1]].offset, 4); // after at 0xC QCOMPARE(tree.nodes[kids[2]].name, QStringLiteral("after")); QCOMPARE(tree.nodes[kids[2]].offset, 0xC); } void TestImportSource::hexArraySizes() { NodeTree tree = importFromSource(QStringLiteral( "struct HexArr {\n" " uint8_t data[0x20];\n" "};\n" )); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Array); QCOMPARE(tree.nodes[kids[0]].arrayLen, 0x20); } void TestImportSource::windowsStylePEB() { // Test with Windows PEB-style struct (no comment offsets) NodeTree tree = importFromSource(QStringLiteral( "struct PEB64 {\n" " BOOLEAN InheritedAddressSpace;\n" " BOOLEAN ReadImageFileExecOptions;\n" " BOOLEAN BeingDebugged;\n" " BOOLEAN BitField;\n" " PVOID Mutant;\n" " PVOID ImageBaseAddress;\n" "};\n" )); QCOMPARE(countRoots(tree), 1); QCOMPARE(tree.nodes[0].name, QStringLiteral("PEB64")); auto kids = childrenOf(tree, tree.nodes[0].id); QCOMPARE(kids.size(), 6); // First 4 are BOOLEAN (UInt8) for (int i = 0; i < 4; i++) QCOMPARE(tree.nodes[kids[i]].kind, NodeKind::UInt8); // Last 2 are PVOID (Pointer64) QCOMPARE(tree.nodes[kids[4]].kind, NodeKind::Pointer64); QCOMPARE(tree.nodes[kids[5]].kind, NodeKind::Pointer64); } void TestImportSource::classKeyword() { NodeTree tree = importFromSource(QStringLiteral( "class MyClass {\n" " int value;\n" "};\n" )); QCOMPARE(countRoots(tree), 1); QCOMPARE(tree.nodes[0].classKeyword, QStringLiteral("class")); } void TestImportSource::inheritanceSkipped() { NodeTree tree = importFromSource(QStringLiteral( "struct Base {\n" " int a;\n" "};\n" "struct Derived : public Base {\n" " float b;\n" "};\n" )); QCOMPARE(countRoots(tree), 2); int derivedIdx = -1; for (int i = 0; i < tree.nodes.size(); i++) { if (tree.nodes[i].name == QStringLiteral("Derived") && tree.nodes[i].parentId == 0) { derivedIdx = i; break; } } QVERIFY(derivedIdx >= 0); auto kids = childrenOf(tree, tree.nodes[derivedIdx].id); QCOMPARE(kids.size(), 1); QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::Float); } void TestImportSource::basicRoundTrip() { // Build a simple tree manually, export it, then re-import and compare NodeTree original; { Node s; s.kind = NodeKind::Struct; s.name = QStringLiteral("RoundTrip"); s.structTypeName = QStringLiteral("RoundTrip"); s.parentId = 0; s.offset = 0; int sIdx = original.addNode(s); uint64_t sId = original.nodes[sIdx].id; Node f1; f1.kind = NodeKind::UInt32; f1.name = QStringLiteral("field_a"); f1.parentId = sId; f1.offset = 0; original.addNode(f1); Node f2; f2.kind = NodeKind::Float; f2.name = QStringLiteral("field_b"); f2.parentId = sId; f2.offset = 4; original.addNode(f2); Node f3; f3.kind = NodeKind::UInt64; f3.name = QStringLiteral("field_c"); f3.parentId = sId; f3.offset = 8; original.addNode(f3); } // Create source text that matches what generator would produce QString source = QStringLiteral( "struct RoundTrip {\n" " uint32_t field_a; // 0x0\n" " float field_b; // 0x4\n" " uint64_t field_c; // 0x8\n" "};\n" "static_assert(sizeof(RoundTrip) == 0x10, \"Size mismatch\");\n" ); NodeTree reimported = importFromSource(source); QCOMPARE(countRoots(reimported), 1); QCOMPARE(reimported.nodes[0].name, QStringLiteral("RoundTrip")); auto origKids = childrenOf(original, original.nodes[0].id); auto reimpKids = childrenOf(reimported, reimported.nodes[0].id); // Compare field count (reimported may have extra padding nodes from static_assert) // Check that the first 3 fields match QVERIFY(reimpKids.size() >= 3); for (int i = 0; i < 3; i++) { QCOMPARE(reimported.nodes[reimpKids[i]].kind, original.nodes[origKids[i]].kind); QCOMPARE(reimported.nodes[reimpKids[i]].name, original.nodes[origKids[i]].name); QCOMPARE(reimported.nodes[reimpKids[i]].offset, original.nodes[origKids[i]].offset); } } // ── Enum tests ── void TestImportSource::enumBasic() { auto tree = importFromSource(QStringLiteral( "enum Color { Red = 0, Green = 1, Blue = 2 };")); QCOMPARE(countRoots(tree), 1); QCOMPARE(tree.nodes[0].classKeyword, QStringLiteral("enum")); QCOMPARE(tree.nodes[0].structTypeName, QStringLiteral("Color")); QCOMPARE(tree.nodes[0].enumMembers.size(), 3); QCOMPARE(tree.nodes[0].enumMembers[0].first, QStringLiteral("Red")); QCOMPARE(tree.nodes[0].enumMembers[0].second, 0LL); QCOMPARE(tree.nodes[0].enumMembers[1].first, QStringLiteral("Green")); QCOMPARE(tree.nodes[0].enumMembers[1].second, 1LL); QCOMPARE(tree.nodes[0].enumMembers[2].first, QStringLiteral("Blue")); QCOMPARE(tree.nodes[0].enumMembers[2].second, 2LL); } void TestImportSource::enumAutoValues() { auto tree = importFromSource(QStringLiteral( "enum Flags { A, B, C };")); QCOMPARE(tree.nodes[0].enumMembers.size(), 3); QCOMPARE(tree.nodes[0].enumMembers[0].second, 0LL); QCOMPARE(tree.nodes[0].enumMembers[1].second, 1LL); QCOMPARE(tree.nodes[0].enumMembers[2].second, 2LL); } void TestImportSource::enumHexValues() { auto tree = importFromSource(QStringLiteral( "enum { X = 0x10, Y = 0x20 };")); // Anonymous enum has no name — parser skips it (unnamed enums are not added) // Actually, let's use a named enum with hex values tree = importFromSource(QStringLiteral( "enum Hex { X = 0x10, Y = 0x20 };")); QCOMPARE(tree.nodes[0].enumMembers.size(), 2); QCOMPARE(tree.nodes[0].enumMembers[0].second, 0x10LL); QCOMPARE(tree.nodes[0].enumMembers[1].second, 0x20LL); } void TestImportSource::enumInStruct() { auto tree = importFromSource(QStringLiteral( "enum PoolType { NonPaged = 0, Paged = 1 };\n" "struct Foo {\n" " PoolType pool; //0x0\n" " uint32_t size; //0x4\n" "};")); // Should have 2 roots: PoolType enum + Foo struct QCOMPARE(countRoots(tree), 2); // Find Foo struct int fooIdx = -1; for (int i = 0; i < tree.nodes.size(); i++) { if (tree.nodes[i].name == QStringLiteral("Foo")) { fooIdx = i; break; } } QVERIFY(fooIdx >= 0); auto kids = childrenOf(tree, tree.nodes[fooIdx].id); QCOMPARE(kids.size(), 2); // First child should be UInt32 (enum mapped to int) with refId to PoolType QCOMPARE(tree.nodes[kids[0]].kind, NodeKind::UInt32); QCOMPARE(tree.nodes[kids[0]].name, QStringLiteral("pool")); QVERIFY(tree.nodes[kids[0]].refId != 0); // linked to enum definition } void TestImportSource::enumClass() { auto tree = importFromSource(QStringLiteral( "enum class Scope : uint8_t { A = 1, B = 2 };")); QCOMPARE(countRoots(tree), 1); QCOMPARE(tree.nodes[0].classKeyword, QStringLiteral("enum")); QCOMPARE(tree.nodes[0].structTypeName, QStringLiteral("Scope")); QCOMPARE(tree.nodes[0].enumMembers.size(), 2); QCOMPARE(tree.nodes[0].enumMembers[0].first, QStringLiteral("A")); QCOMPARE(tree.nodes[0].enumMembers[0].second, 1LL); } QTEST_MAIN(TestImportSource) #include "test_import_source.moc"