diff --git a/src/CppAst.Tests/TestStructs.cs b/src/CppAst.Tests/TestStructs.cs index eac3f7b..3a250b0 100644 --- a/src/CppAst.Tests/TestStructs.cs +++ b/src/CppAst.Tests/TestStructs.cs @@ -111,7 +111,7 @@ public void TestAnonymous() [Test] - public void TestUnion() + public void TestAnonymousUnion() { ParseAssert(@" struct HelloWorld @@ -122,6 +122,10 @@ struct HelloWorld int d; }; int b; + union { + int e; + int f; + }; }; ", compilation => @@ -132,7 +136,61 @@ struct HelloWorld { var cppStruct = compilation.Classes[0]; - Assert.AreEqual(3, cppStruct.Fields.Count); + Assert.AreEqual(4, cppStruct.Fields.Count); + + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(i * 4, cppStruct.Fields[i].Offset); + Assert.AreEqual(4, cppStruct.Fields[i].Type.SizeOf); + } + + // Check first union + Assert.AreEqual(string.Empty, cppStruct.Fields[1].Name); + Assert.IsInstanceOf(cppStruct.Fields[1].Type); + var cppUnion = ((CppClass)cppStruct.Fields[1].Type); + Assert.AreEqual(CppClassKind.Union, ((CppClass)cppStruct.Fields[1].Type).ClassKind); + Assert.AreEqual(2, cppUnion.Fields.Count); + + // Check 2nd union + Assert.AreEqual(string.Empty, cppStruct.Fields[3].Name); + Assert.IsInstanceOf(cppStruct.Fields[3].Type); + cppUnion = ((CppClass)cppStruct.Fields[3].Type); + Assert.AreEqual(CppClassKind.Union, ((CppClass)cppStruct.Fields[3].Type).ClassKind); + Assert.AreEqual(2, cppUnion.Fields.Count); + } + } + ); + } + + [Test] + public void TestAnonymousUnionWithField() + { + ParseAssert(@" +struct HelloWorld +{ + int a; + union { + int c; + int d; + } e; +}; +", + compilation => + { + Assert.False(compilation.HasErrors); + + Assert.AreEqual(1, compilation.Classes.Count); + + { + var cppStruct = compilation.Classes[0]; + + // Only one union + Assert.AreEqual(1, cppStruct.Classes.Count); + + // Only 2 fields + Assert.AreEqual(2, cppStruct.Fields.Count); + + // Check the union Assert.AreEqual(string.Empty, cppStruct.Fields[1].Name); Assert.IsInstanceOf(cppStruct.Fields[1].Type); var cppUnion = ((CppClass)cppStruct.Fields[1].Type); diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index a511733..cadd3ab 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -51,8 +51,14 @@ public CXChildVisitResult VisitTranslationUnit(CXCursor cursor, CXCursor parent, private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, void* data) { - var fullName = clang.getCursorUSR(cursor).CString; - if (_containers.TryGetValue(fullName, out var containerContext)) + var typeAsCString = clang.getCursorUSR(cursor).CString.ToString(); + if (string.IsNullOrEmpty(typeAsCString)) + { + typeAsCString = clang.getCursorDisplayName(cursor).ToString(); + } + // Try to workaround anonymous types + var typeKey = $"{cursor.Kind}:{typeAsCString}{(cursor.IsAnonymous ? "/" + cursor.Hash : string.Empty)}"; + if (_containers.TryGetValue(typeKey, out var containerContext)) { return containerContext; } @@ -153,9 +159,9 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi containerContext = new CppContainerContext(symbol) { CurrentVisibility = defaultContainerVisibility }; // The type could have been added separately as part of the GetCppType above TemplateParameters - if (!_containers.ContainsKey(fullName)) + if (!_containers.ContainsKey(typeKey)) { - _containers.Add(fullName, containerContext); + _containers.Add(typeKey, containerContext); } return containerContext; } @@ -196,7 +202,6 @@ private CppClass VisitClassDecl(CXCursor cursor, void* data) private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* data) { CppElement element = null; - switch (cursor.Kind) { case CXCursorKind.CXCursor_FieldDecl: @@ -230,12 +235,34 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d case CXCursorKind.CXCursor_StructDecl: case CXCursorKind.CXCursor_UnionDecl: { + bool isAnonymous = cursor.IsAnonymous; var cppClass = VisitClassDecl(cursor, data); // Empty struct/class/union declaration are considered as fields - if (string.IsNullOrEmpty(cppClass.Name)) + if (isAnonymous) { + Debug.Assert(string.IsNullOrEmpty(cppClass.Name)); var containerContext = GetOrCreateDeclarationContainer(parent, data); - var cppField = new CppField(cppClass, string.Empty); + + // We try to recover the offset from the previous field + // Might not be always correct (with alignment rules), + // but not sure how to recover the offset without recalculating the entire offsets + var offset = 0; + var cppClassContainer = containerContext.Container as CppClass; + if (cppClassContainer is object && cppClassContainer.Fields.Count > 0) + { + var lastField = cppClassContainer.Fields[cppClassContainer.Fields.Count - 1]; + offset = (int)lastField.Offset + lastField.Type.SizeOf; + } + + // Create an anonymous field for the type + var cppField = new CppField(cppClass, string.Empty) + { + Visibility = containerContext.CurrentVisibility, + StorageQualifier = GetStorageQualifier(cursor), + IsAnonymous = true, + Offset = offset, + Attributes = ParseAttributes(cursor) + }; containerContext.DeclarationContainer.Fields.Add(cppField); element = cppField; } @@ -735,22 +762,34 @@ private CppField VisitFieldOrVariable(CppContainerContext containerContext, CXCu var fieldName = GetCursorSpelling(cursor); var type = GetCppType(cursor.Type.Declaration, cursor.Type, cursor, data); - var cppField = new CppField(type, fieldName) + var previousField = containerContext.DeclarationContainer.Fields.Count > 0 ? containerContext.DeclarationContainer.Fields[containerContext.DeclarationContainer.Fields.Count - 1] : null; + CppField cppField; + // This happen in the type is anonymous, we create implicitly a field for it, but if type is the same + // we should reuse the anonymous field we created just before + if (previousField != null && previousField.IsAnonymous && ReferenceEquals(previousField.Type, type)) { - Visibility = containerContext.CurrentVisibility, - StorageQualifier = GetStorageQualifier(cursor), - IsBitField = cursor.IsBitField, - BitFieldWidth = cursor.FieldDeclBitWidth, - Offset = cursor.OffsetOfField / 8, - }; - containerContext.DeclarationContainer.Fields.Add(cppField); - cppField.Attributes = ParseAttributes(cursor); - - if (cursor.Kind == CXCursorKind.CXCursor_VarDecl) + cppField = previousField; + cppField.Offset = cursor.OffsetOfField / 8; + } + else { - VisitInitValue(cursor, data, out var fieldExpr, out var fieldValue); - cppField.InitValue = fieldValue; - cppField.InitExpression = fieldExpr; + cppField = new CppField(type, fieldName) + { + Visibility = containerContext.CurrentVisibility, + StorageQualifier = GetStorageQualifier(cursor), + IsBitField = cursor.IsBitField, + BitFieldWidth = cursor.FieldDeclBitWidth, + Offset = cursor.OffsetOfField / 8, + }; + containerContext.DeclarationContainer.Fields.Add(cppField); + cppField.Attributes = ParseAttributes(cursor); + + if (cursor.Kind == CXCursorKind.CXCursor_VarDecl) + { + VisitInitValue(cursor, data, out var fieldExpr, out var fieldValue); + cppField.InitValue = fieldValue; + cppField.InitExpression = fieldExpr; + } } return cppField;