diff --git a/src/CppAst.Tests/TestFunctions.cs b/src/CppAst.Tests/TestFunctions.cs index 55a3599..e740052 100644 --- a/src/CppAst.Tests/TestFunctions.cs +++ b/src/CppAst.Tests/TestFunctions.cs @@ -1,4 +1,7 @@ using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; namespace CppAst.Tests { @@ -224,7 +227,7 @@ public void TestFunctionExport() Assert.True(cppFunction.IsPublicExport()); } }, - new CppParserOptions() { } + new CppParserOptions() { } ); ParseAssert(text, @@ -353,7 +356,356 @@ 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 TestFunctionOverloadBodySpan() + { + var options = new CppParserOptions(); + options.ParseFunctionBodies = true; + var headerFilename = "test_function_overload_body.h"; + + var text = @" +int process(int x) +{ + return x * 2; +} +int process(float y) +{ + return (int)(y * 3.0f); +} + +int process(int x, int y) +{ + return x + y; +} +"; + + 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("process", cppFunction.Name); + Assert.AreEqual(1, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "process(int) 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); + } + + { + var cppFunction = compilation.Functions[1]; + Assert.AreEqual("process", cppFunction.Name); + Assert.AreEqual(1, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "process(float) 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); + } + + { + var cppFunction = compilation.Functions[2]; + Assert.AreEqual("process", cppFunction.Name); + Assert.AreEqual(2, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "process(int, int) 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); + } + HashSet startLines = new HashSet(); + foreach (var function in compilation.Functions) + { + Assert.True(startLines.Add(function.BodySpan.Value.Start.Line)); + } + } + + [Test] + public void TestMethodOverloadBodySpan() + { + var options = new CppParserOptions(); + options.ParseFunctionBodies = true; + var headerFilename = "test_method_overload_body.h"; + + var text = @" +class Calculator { +public: + int calculate(int x) { + return x * 2; + } + + int calculate(float y) { + return (int)(y * 3.0f); + } + + int calculate(int x, int y) { + return x + y; + } +}; +"; + + 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("Calculator", cls.Name); + Assert.AreEqual(3, cls.Functions.Count); + + { + var cppFunction = cls.Functions[0]; + Assert.AreEqual("calculate", cppFunction.Name); + Assert.AreEqual(1, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "calculate(int) 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); + } + + { + var cppFunction = cls.Functions[1]; + Assert.AreEqual("calculate", cppFunction.Name); + Assert.AreEqual(1, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "calculate(float) 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); + } + + { + var cppFunction = cls.Functions[2]; + Assert.AreEqual("calculate", cppFunction.Name); + Assert.AreEqual(2, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "calculate(int, int) 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); + } + + HashSet startLines = new HashSet(); + foreach (var function in cls.Functions) + { + Assert.True(startLines.Add(function.BodySpan.Value.Start.Line)); + } + } + + [Test] + public void TestMethodOverloadDefinitionOutsideClassBodySpan() + { + var options = new CppParserOptions(); + options.ParseFunctionBodies = true; + var headerFilename = "test_method_overload_outside_class_body.h"; + + var text = @" +class Calculator { +public: + int calculate(int x); + int calculate(float y); + int calculate(int x, int y); +}; + +int Calculator::calculate(int x) +{ + return x * 2; +} + +int Calculator::calculate(float y) +{ + return (int)(y * 3.0f); +} + +int Calculator::calculate(int x, int y) +{ + return x + y; +} + +class Calculator2 { +public: + int calculate(int x); + int calculate(float y); + int calculate(int x, int y); +}; + +int Calculator2::calculate(int x) +{ + return x * 2; +} + +int Calculator2::calculate(float y) +{ + return (int)(y * 3.0f); +} + +int Calculator2::calculate(int x, int y) +{ + return x + y; +} +"; + + 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(2, compilation.Classes.Count); + + var cls = compilation.Classes[0]; + Assert.AreEqual("Calculator", cls.Name); + Assert.AreEqual(3, cls.Functions.Count); + + { + var cppFunction = cls.Functions[0]; + Assert.AreEqual("calculate", cppFunction.Name); + Assert.AreEqual(1, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "calculate(int) 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); + } + + { + var cppFunction = cls.Functions[1]; + Assert.AreEqual("calculate", cppFunction.Name); + Assert.AreEqual(1, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "calculate(float) 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); + } + + { + var cppFunction = cls.Functions[2]; + Assert.AreEqual("calculate", cppFunction.Name); + Assert.AreEqual(2, cppFunction.Parameters.Count); + Assert.IsNotNull(cppFunction.BodySpan, "calculate(int, int) 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); + } + + HashSet startLines = new HashSet(); + foreach (var cls2 in compilation.Classes) + { + foreach (var function in cls2.Functions) + { + Assert.True(startLines.Add(function.BodySpan.Value.Start.Line)); + } + } + } } } \ No newline at end of file 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..731a8c1 100644 --- a/src/CppAst/CppModelBuilder.cs +++ b/src/CppAst/CppModelBuilder.cs @@ -1549,11 +1549,81 @@ 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; + + // Collect parameter types from the current cursor for signature matching + var currentParameterTypes = new List(); + cursor.VisitChildren((argCursor, functionCursor, clientData) => + { + if (argCursor.Kind == CXCursorKind.CXCursor_ParmDecl) + { + var paramType = GetCppType(argCursor.Type.Declaration, argCursor.Type, argCursor, clientData); + currentParameterTypes.Add(paramType); + } + return CXChildVisitResult.CXChildVisit_Continue; + }, new CXClientData((IntPtr)data)); + + // Search in Functions, Constructors, and Destructors + foreach (var func in cppClass.Functions) + { + if (func.Name == functionName && CompareParameterSignatures(func.Parameters, currentParameterTypes)) + { + existingFunction = func; + break; + } + } + + if (existingFunction == null) + { + foreach (var ctor in cppClass.Constructors) + { + if (ctor.Name == functionName && CompareParameterSignatures(ctor.Parameters, currentParameterTypes)) + { + existingFunction = ctor; + break; + } + } + } + + if (existingFunction == null) + { + foreach (var dtor in cppClass.Destructors) + { + if (dtor.Name == functionName && CompareParameterSignatures(dtor.Parameters, currentParameterTypes)) + { + 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 +1719,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) => { @@ -2481,6 +2566,27 @@ private string GetCursorKey(CXCursor cursor) return $"{_rootContainerContext.NameContext}/{typeAsCString}{(cursor.IsAnonymous ? "/" + cursor.Hash : string.Empty)}"; } + private static bool CompareParameterSignatures(IList parameters, IList paramTypes) + { + if (parameters.Count != paramTypes.Count) + { + return false; + } + + for (int i = 0; i < parameters.Count; i++) + { + var paramType = parameters[i].Type; + var typeFromCursor = paramTypes[i]; + + if (!paramType.Equals(typeFromCursor)) + { + return false; + } + } + + return true; + } + private class CppContainerContext { public CppContainerContext(ICppContainer container) 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. ///