Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions src/CppAst.Tests/TestFunctions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using NUnit.Framework;
using System;
using System.IO;

namespace CppAst.Tests
{
Expand Down Expand Up @@ -353,6 +355,154 @@ public void TestFunctionPointersByParam()
);
}

[Test]
public void TestFunctionBody()
{
var options = new CppParserOptions();
options.ParseFunctionBodies = true;
var headerFilename = "test_function_body.h";

var text = @"
void function0();
int function1(int a, float b) {
return a + (int)b;
}
float function2(int x);
";

var currentDirectory = Environment.CurrentDirectory;
var headerFile = Path.Combine(currentDirectory, headerFilename);
File.WriteAllText(headerFile, text);

var compilation = CppParser.ParseFile(headerFile, options);

Assert.False(compilation.HasErrors);
Assert.AreEqual(3, compilation.Functions.Count);

{
var cppFunction = compilation.Functions[0];
Assert.AreEqual("function0", cppFunction.Name);
Assert.IsNull(cppFunction.BodySpan);
}

{
var cppFunction = compilation.Functions[1];
Assert.AreEqual("function1", cppFunction.Name);
Assert.IsNotNull(cppFunction.BodySpan);
Assert.Greater(cppFunction.BodySpan.Value.Start.Line, 0);
Assert.Greater(cppFunction.BodySpan.Value.End.Line, 0);
Assert.GreaterOrEqual(cppFunction.BodySpan.Value.End.Offset, cppFunction.BodySpan.Value.Start.Offset);
}

{
var cppFunction = compilation.Functions[2];
Assert.AreEqual("function2", cppFunction.Name);
Assert.IsNull(cppFunction.BodySpan);
}
}

[Test]
public void TestInlineMethodBody()
{
var options = new CppParserOptions();
options.ParseFunctionBodies = true;
var headerFilename = "test_inline_method_body.h";

var text = @"
typedef unsigned int ImWchar;

class ImFont {
public:
bool IsGlyphInFont(ImWchar c)
{
return false;
}

bool AnotherMethod(ImWchar c) {
if (c == 0) return true;
return false;
}
};
";

var currentDirectory = Environment.CurrentDirectory;
var headerFile = Path.Combine(currentDirectory, headerFilename);
File.WriteAllText(headerFile, text);

var compilation = CppParser.ParseFile(headerFile, options);

Assert.False(compilation.HasErrors);
Assert.AreEqual(1, compilation.Classes.Count);

var cls = compilation.Classes[0];
Assert.AreEqual("ImFont", cls.Name);
Assert.AreEqual(2, cls.Functions.Count);

{
var cppFunction = cls.Functions[0];
Assert.AreEqual("IsGlyphInFont", cppFunction.Name);
Assert.IsNotNull(cppFunction.BodySpan, "IsGlyphInFont should have BodySpan - this is the bug reported");
Assert.Greater(cppFunction.BodySpan.Value.Start.Line, 0);
Assert.Greater(cppFunction.BodySpan.Value.End.Line, 0);
Assert.GreaterOrEqual(cppFunction.BodySpan.Value.End.Offset, cppFunction.BodySpan.Value.Start.Offset);
}

{
var cppFunction = cls.Functions[1];
Assert.AreEqual("AnotherMethod", cppFunction.Name);
Assert.IsNotNull(cppFunction.BodySpan, "AnotherMethod should have BodySpan");
Assert.Greater(cppFunction.BodySpan.Value.Start.Line, 0);
Assert.Greater(cppFunction.BodySpan.Value.End.Line, 0);
Assert.GreaterOrEqual(cppFunction.BodySpan.Value.End.Offset, cppFunction.BodySpan.Value.Start.Offset);
}
}

[Test]
public void TestMethodDefinitionOutsideClass()
{
var options = new CppParserOptions();
options.ParseFunctionBodies = true;
var headerFilename = "test_method_outside_class.h";

var text = @"
typedef unsigned int ImWchar;

class ImFont {
public:
bool IsGlyphInFont(ImWchar c);
};

bool ImFont::IsGlyphInFont(ImWchar c)
{
return false;
}
";

var currentDirectory = Environment.CurrentDirectory;
var headerFile = Path.Combine(currentDirectory, headerFilename);
File.WriteAllText(headerFile, text);

var compilation = CppParser.ParseFile(headerFile, options);

Assert.False(compilation.HasErrors);
Assert.AreEqual(1, compilation.Classes.Count);

var cls = compilation.Classes[0];
Assert.AreEqual("ImFont", cls.Name);


Assert.AreEqual(1, cls.Functions.Count);

{
var cppFunction = cls.Functions[0];
Assert.AreEqual("IsGlyphInFont", cppFunction.Name);
Assert.IsNotNull(cppFunction.BodySpan, "IsGlyphInFont should have BodySpan - this is the bug reported (method defined outside class)");
Assert.Greater(cppFunction.BodySpan.Value.Start.Line, 0);
Assert.Greater(cppFunction.BodySpan.Value.End.Line, 0);
Assert.GreaterOrEqual(cppFunction.BodySpan.Value.End.Offset, cppFunction.BodySpan.Value.Start.Offset);
}
}



}
Expand Down
5 changes: 5 additions & 0 deletions src/CppAst/CppFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ public int DefaultParamCount
}
}

/// <summary>
/// Gets or sets the source span of the function body implementation.
/// </summary>
public CppSourceSpan? BodySpan { get; set; }

/// <summary>
/// Gets or sets the flags of this function.
/// </summary>
Expand Down
77 changes: 75 additions & 2 deletions src/CppAst/CppModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1549,11 +1549,69 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da

//We need ignore the function define out in the class definition here(Otherwise it will has two same functions here~)!
var semKind = cursor.SemanticParent.Kind;
if ((semKind == CXCursorKind.CXCursor_StructDecl ||
bool isMethodDefinedOutsideClass = (semKind == CXCursorKind.CXCursor_StructDecl ||
semKind == CXCursorKind.CXCursor_ClassDecl ||
semKind == CXCursorKind.CXCursor_ClassTemplate)
&& cursor.LexicalParent != cursor.SemanticParent)
&& cursor.LexicalParent != cursor.SemanticParent;

if (isMethodDefinedOutsideClass)
{
// Find the previously created function in the class and update its BodySpan
if (cppClass != null)
{
CppFunction existingFunction = null;

// Search in Functions, Constructors, and Destructors
foreach (var func in cppClass.Functions)
{
if (func.Name == functionName)
{
existingFunction = func;
break;
}
}

if (existingFunction == null)
{
foreach (var ctor in cppClass.Constructors)
{
if (ctor.Name == functionName)
{
existingFunction = ctor;
break;
}
}
}

if (existingFunction == null)
{
foreach (var dtor in cppClass.Destructors)
{
if (dtor.Name == functionName)
{
existingFunction = dtor;
break;
}
}
}

// If we found the existing function, update its BodySpan
if (existingFunction != null && cursor.IsDefinition)
{
cursor.VisitChildren((childCursor, functionCursor, clientData) =>
{
if (childCursor.Kind == CXCursorKind.CXCursor_CompoundStmt)
{
var bodyStart = GetSourceLocation(childCursor.Extent.Start);
var bodyEnd = GetSourceLocation(childCursor.Extent.End);
existingFunction.BodySpan = new CppSourceSpan(bodyStart, bodyEnd);
return CXChildVisitResult.CXChildVisit_Break;
}
return CXChildVisitResult.CXChildVisit_Continue;
}, new CXClientData((IntPtr)data));
}
}

return null;
}

Expand Down Expand Up @@ -1649,6 +1707,21 @@ private CppFunction VisitFunctionDecl(CXCursor cursor, CXCursor parent, void* da
ParseAttributes(cursor, cppFunction, true);
cppFunction.CallingConvention = GetCallingConvention(cursor.Type);

if (cursor.IsDefinition)
{
cursor.VisitChildren((childCursor, functionCursor, clientData) =>
{
if (childCursor.Kind == CXCursorKind.CXCursor_CompoundStmt)
{
var bodyStart = GetSourceLocation(childCursor.Extent.Start);
var bodyEnd = GetSourceLocation(childCursor.Extent.End);
cppFunction.BodySpan = new CppSourceSpan(bodyStart, bodyEnd);
return CXChildVisitResult.CXChildVisit_Break;
}
return CXChildVisitResult.CXChildVisit_Continue;
}, new CXClientData((IntPtr)data));
}

int i = 0;
cursor.VisitChildren((argCursor, functionCursor, clientData) =>
{
Expand Down
5 changes: 4 additions & 1 deletion src/CppAst/CppParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ private static unsafe CppCompilation ParseInternal(List<CppFileOrString> cppFile
}

var translationFlags = CXTranslationUnit_Flags.CXTranslationUnit_None;
translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_SkipFunctionBodies; // Don't traverse function bodies
if (!options.ParseFunctionBodies)
{
translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_SkipFunctionBodies; // Don't traverse function bodies
}
translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_IncludeAttributedTypes; // Include attributed types in CXType
translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_VisitImplicitAttributes; // Implicit attributes should be visited

Expand Down
6 changes: 6 additions & 0 deletions src/CppAst/CppParserOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public CppParserOptions()
ParseSystemIncludes = true;
ParseTokenAttributes = false;
ParseCommentAttribute = false;
ParseFunctionBodies = false;

// Default triple targets
TargetCpu = IntPtr.Size == 8 ? CppTargetCpu.X86_64 : CppTargetCpu.X86;
Expand Down Expand Up @@ -101,6 +102,11 @@ public CppParserOptions()
/// </summary>
public bool ParseCommentAttribute { get; set; }

/// <summary>
/// Gets or sets a boolean indicating whether to parse function bodies. Default is <c>false</c>
/// </summary>
public bool ParseFunctionBodies { get; set; }

/// <summary>
/// Sets <see cref="ParseMacros"/> to <c>true</c> and return this instance.
/// </summary>
Expand Down