diff --git a/src/CppAst.Tests/TestFunctions.cs b/src/CppAst.Tests/TestFunctions.cs index 55a3599..89ee0ea 100644 --- a/src/CppAst.Tests/TestFunctions.cs +++ b/src/CppAst.Tests/TestFunctions.cs @@ -1,4 +1,6 @@ using NUnit.Framework; +using System; +using System.IO; namespace CppAst.Tests { @@ -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); + } + } + } diff --git a/src/CppAst/CppFunction.cs b/src/CppAst/CppFunction.cs index 6cee273..7667643 100644 --- a/src/CppAst/CppFunction.cs +++ b/src/CppAst/CppFunction.cs @@ -93,6 +93,11 @@ public int DefaultParamCount } } + /// + /// Gets or sets the source span of the function body implementation. + /// + public CppSourceSpan? BodySpan { get; set; } + /// /// Gets or sets the flags of this function. /// diff --git a/src/CppAst/CppModelBuilder.cs b/src/CppAst/CppModelBuilder.cs index 517e6ad..0cae389 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -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; } @@ -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) => { diff --git a/src/CppAst/CppParser.cs b/src/CppAst/CppParser.cs index f1fdd86..2dd717c 100644 --- a/src/CppAst/CppParser.cs +++ b/src/CppAst/CppParser.cs @@ -122,7 +122,10 @@ private static unsafe CppCompilation ParseInternal(List 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 diff --git a/src/CppAst/CppParserOptions.cs b/src/CppAst/CppParserOptions.cs index 1de9bb0..4f23954 100644 --- a/src/CppAst/CppParserOptions.cs +++ b/src/CppAst/CppParserOptions.cs @@ -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; @@ -101,6 +102,11 @@ public CppParserOptions() /// public bool ParseCommentAttribute { get; set; } + /// + /// Gets or sets a boolean indicating whether to parse function bodies. Default is false + /// + public bool ParseFunctionBodies { get; set; } + /// /// Sets to true and return this instance. ///