Skip to content

Commit d0e90c7

Browse files
committed
Fix bug with multiple anonymous unions and named unions fields (#66)
1 parent 0894624 commit d0e90c7

File tree

2 files changed

+120
-23
lines changed

2 files changed

+120
-23
lines changed

src/CppAst.Tests/TestStructs.cs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public void TestAnonymous()
111111

112112

113113
[Test]
114-
public void TestUnion()
114+
public void TestAnonymousUnion()
115115
{
116116
ParseAssert(@"
117117
struct HelloWorld
@@ -122,6 +122,10 @@ struct HelloWorld
122122
int d;
123123
};
124124
int b;
125+
union {
126+
int e;
127+
int f;
128+
};
125129
};
126130
",
127131
compilation =>
@@ -132,7 +136,61 @@ struct HelloWorld
132136

133137
{
134138
var cppStruct = compilation.Classes[0];
135-
Assert.AreEqual(3, cppStruct.Fields.Count);
139+
Assert.AreEqual(4, cppStruct.Fields.Count);
140+
141+
for (int i = 0; i < 4; i++)
142+
{
143+
Assert.AreEqual(i * 4, cppStruct.Fields[i].Offset);
144+
Assert.AreEqual(4, cppStruct.Fields[i].Type.SizeOf);
145+
}
146+
147+
// Check first union
148+
Assert.AreEqual(string.Empty, cppStruct.Fields[1].Name);
149+
Assert.IsInstanceOf<CppClass>(cppStruct.Fields[1].Type);
150+
var cppUnion = ((CppClass)cppStruct.Fields[1].Type);
151+
Assert.AreEqual(CppClassKind.Union, ((CppClass)cppStruct.Fields[1].Type).ClassKind);
152+
Assert.AreEqual(2, cppUnion.Fields.Count);
153+
154+
// Check 2nd union
155+
Assert.AreEqual(string.Empty, cppStruct.Fields[3].Name);
156+
Assert.IsInstanceOf<CppClass>(cppStruct.Fields[3].Type);
157+
cppUnion = ((CppClass)cppStruct.Fields[3].Type);
158+
Assert.AreEqual(CppClassKind.Union, ((CppClass)cppStruct.Fields[3].Type).ClassKind);
159+
Assert.AreEqual(2, cppUnion.Fields.Count);
160+
}
161+
}
162+
);
163+
}
164+
165+
[Test]
166+
public void TestAnonymousUnionWithField()
167+
{
168+
ParseAssert(@"
169+
struct HelloWorld
170+
{
171+
int a;
172+
union {
173+
int c;
174+
int d;
175+
} e;
176+
};
177+
",
178+
compilation =>
179+
{
180+
Assert.False(compilation.HasErrors);
181+
182+
Assert.AreEqual(1, compilation.Classes.Count);
183+
184+
{
185+
var cppStruct = compilation.Classes[0];
186+
187+
// Only one union
188+
Assert.AreEqual(1, cppStruct.Classes.Count);
189+
190+
// Only 2 fields
191+
Assert.AreEqual(2, cppStruct.Fields.Count);
192+
193+
// Check the union
136194
Assert.AreEqual(string.Empty, cppStruct.Fields[1].Name);
137195
Assert.IsInstanceOf<CppClass>(cppStruct.Fields[1].Type);
138196
var cppUnion = ((CppClass)cppStruct.Fields[1].Type);

src/CppAst/CppModelBuilder.cs

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ public CXChildVisitResult VisitTranslationUnit(CXCursor cursor, CXCursor parent,
5151

5252
private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, void* data)
5353
{
54-
var fullName = clang.getCursorUSR(cursor).CString;
55-
if (_containers.TryGetValue(fullName, out var containerContext))
54+
var typeAsCString = clang.getCursorUSR(cursor).CString.ToString();
55+
if (string.IsNullOrEmpty(typeAsCString))
56+
{
57+
typeAsCString = clang.getCursorDisplayName(cursor).ToString();
58+
}
59+
// Try to workaround anonymous types
60+
var typeKey = $"{cursor.Kind}:{typeAsCString}{(cursor.IsAnonymous ? "/" + cursor.Hash : string.Empty)}";
61+
if (_containers.TryGetValue(typeKey, out var containerContext))
5662
{
5763
return containerContext;
5864
}
@@ -153,9 +159,9 @@ private CppContainerContext GetOrCreateDeclarationContainer(CXCursor cursor, voi
153159
containerContext = new CppContainerContext(symbol) { CurrentVisibility = defaultContainerVisibility };
154160

155161
// The type could have been added separately as part of the GetCppType above TemplateParameters
156-
if (!_containers.ContainsKey(fullName))
162+
if (!_containers.ContainsKey(typeKey))
157163
{
158-
_containers.Add(fullName, containerContext);
164+
_containers.Add(typeKey, containerContext);
159165
}
160166
return containerContext;
161167
}
@@ -196,7 +202,6 @@ private CppClass VisitClassDecl(CXCursor cursor, void* data)
196202
private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* data)
197203
{
198204
CppElement element = null;
199-
200205
switch (cursor.Kind)
201206
{
202207
case CXCursorKind.CXCursor_FieldDecl:
@@ -230,12 +235,34 @@ private CXChildVisitResult VisitMember(CXCursor cursor, CXCursor parent, void* d
230235
case CXCursorKind.CXCursor_StructDecl:
231236
case CXCursorKind.CXCursor_UnionDecl:
232237
{
238+
bool isAnonymous = cursor.IsAnonymous;
233239
var cppClass = VisitClassDecl(cursor, data);
234240
// Empty struct/class/union declaration are considered as fields
235-
if (string.IsNullOrEmpty(cppClass.Name))
241+
if (isAnonymous)
236242
{
243+
Debug.Assert(string.IsNullOrEmpty(cppClass.Name));
237244
var containerContext = GetOrCreateDeclarationContainer(parent, data);
238-
var cppField = new CppField(cppClass, string.Empty);
245+
246+
// We try to recover the offset from the previous field
247+
// Might not be always correct (with alignment rules),
248+
// but not sure how to recover the offset without recalculating the entire offsets
249+
var offset = 0;
250+
var cppClassContainer = containerContext.Container as CppClass;
251+
if (cppClassContainer is object && cppClassContainer.Fields.Count > 0)
252+
{
253+
var lastField = cppClassContainer.Fields[cppClassContainer.Fields.Count - 1];
254+
offset = (int)lastField.Offset + lastField.Type.SizeOf;
255+
}
256+
257+
// Create an anonymous field for the type
258+
var cppField = new CppField(cppClass, string.Empty)
259+
{
260+
Visibility = containerContext.CurrentVisibility,
261+
StorageQualifier = GetStorageQualifier(cursor),
262+
IsAnonymous = true,
263+
Offset = offset,
264+
Attributes = ParseAttributes(cursor)
265+
};
239266
containerContext.DeclarationContainer.Fields.Add(cppField);
240267
element = cppField;
241268
}
@@ -735,22 +762,34 @@ private CppField VisitFieldOrVariable(CppContainerContext containerContext, CXCu
735762
var fieldName = GetCursorSpelling(cursor);
736763
var type = GetCppType(cursor.Type.Declaration, cursor.Type, cursor, data);
737764

738-
var cppField = new CppField(type, fieldName)
765+
var previousField = containerContext.DeclarationContainer.Fields.Count > 0 ? containerContext.DeclarationContainer.Fields[containerContext.DeclarationContainer.Fields.Count - 1] : null;
766+
CppField cppField;
767+
// This happen in the type is anonymous, we create implicitly a field for it, but if type is the same
768+
// we should reuse the anonymous field we created just before
769+
if (previousField != null && previousField.IsAnonymous && ReferenceEquals(previousField.Type, type))
739770
{
740-
Visibility = containerContext.CurrentVisibility,
741-
StorageQualifier = GetStorageQualifier(cursor),
742-
IsBitField = cursor.IsBitField,
743-
BitFieldWidth = cursor.FieldDeclBitWidth,
744-
Offset = cursor.OffsetOfField / 8,
745-
};
746-
containerContext.DeclarationContainer.Fields.Add(cppField);
747-
cppField.Attributes = ParseAttributes(cursor);
748-
749-
if (cursor.Kind == CXCursorKind.CXCursor_VarDecl)
771+
cppField = previousField;
772+
cppField.Offset = cursor.OffsetOfField / 8;
773+
}
774+
else
750775
{
751-
VisitInitValue(cursor, data, out var fieldExpr, out var fieldValue);
752-
cppField.InitValue = fieldValue;
753-
cppField.InitExpression = fieldExpr;
776+
cppField = new CppField(type, fieldName)
777+
{
778+
Visibility = containerContext.CurrentVisibility,
779+
StorageQualifier = GetStorageQualifier(cursor),
780+
IsBitField = cursor.IsBitField,
781+
BitFieldWidth = cursor.FieldDeclBitWidth,
782+
Offset = cursor.OffsetOfField / 8,
783+
};
784+
containerContext.DeclarationContainer.Fields.Add(cppField);
785+
cppField.Attributes = ParseAttributes(cursor);
786+
787+
if (cursor.Kind == CXCursorKind.CXCursor_VarDecl)
788+
{
789+
VisitInitValue(cursor, data, out var fieldExpr, out var fieldValue);
790+
cppField.InitValue = fieldValue;
791+
cppField.InitExpression = fieldExpr;
792+
}
754793
}
755794

756795
return cppField;

0 commit comments

Comments
 (0)