Skip to content

Commit

Permalink
Explicitly include an enum by name (#33)
Browse files Browse the repository at this point in the history
* Add ability to explicitly include an enum by name

* Reference libclang if it exists in `lib`

* Keep old behavior of field names for newer versions of Clang
  • Loading branch information
lithiumtoast authored Sep 1, 2024
1 parent 7c7f543 commit b00c09a
Show file tree
Hide file tree
Showing 18 changed files with 276 additions and 59 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ obj/
/artifacts/
src/c/tests/**/ffi/*.json
src/c/tests/**/ffi-x/*.json

# Native library files
lib/*.*
30 changes: 30 additions & 0 deletions src/c/tests/functions/function_implicit_enum/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"inputFilePath": "./main.c",
"userIncludeDirectories": [
"../../../production/ffi_helper/include"
],
"ignoredIncludeFiles": [
"../../../production/ffi_helper/include/ffi_helper.h"
],
"includedNames": [
"enum_implicit"
],
"targetPlatforms": {
"windows": {
"i686-pc-windows-msvc": {},
"x86_64-pc-windows-msvc": {},
"aarch64-pc-windows-msvc": {}
},
"macos": {
"i686-apple-darwin": {},
"aarch64-apple-darwin": {},
"x86_64-apple-darwin": {},
"aarch64-apple-ios": {}
},
"linux": {
"i686-unknown-linux-gnu": {},
"x86_64-unknown-linux-gnu": {},
"aarch64-unknown-linux-gnu": {}
}
}
}
12 changes: 12 additions & 0 deletions src/c/tests/functions/function_implicit_enum/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <stdio.h>
#include "ffi_helper.h"

enum enum_implicit {
ENUM_IMPLICIT_VALUE0 = 0,
ENUM_IMPLICIT_VALUE1 = 255
} enum_implicit;

FFI_API_DECL int function_implicit_enum(int value)
{
return ENUM_IMPLICIT_VALUE1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace c2ffi.Data.Serialization
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "8.0.10.11423")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "8.0.10.31311")]
public partial class JsonSerializerContextCFfiCrossPlatform
{
private readonly static global::System.Text.Json.JsonSerializerOptions s_defaultOptions = new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace c2ffi.Data.Serialization
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "8.0.10.11423")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "8.0.10.31311")]
public partial class JsonSerializerContextCFfiTargetPlatform
{
private readonly static global::System.Text.Json.JsonSerializerOptions s_defaultOptions = new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ public void Dispose()
ParseContext.Dispose();
}

public string GetFieldName(clang.CXCursor clangCursor)
{
if (clangCursor.kind != clang.CXCursorKind.CXCursor_FieldDecl)
{
return string.Empty;
}

var name = clangCursor.Spelling();

// NOTE: In newer versions of Clang (specifically found on 18.1), getting the name of the field has changed if it anonymous. Old behavior was to return empty string.
if (name.Contains("::(anonymous at", StringComparison.OrdinalIgnoreCase))
{
return string.Empty;
}

return name;
}

private CType VisitTypeInternal(
CNodeKind nodeKind,
string typeName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ public sealed class ExploreNodeInfo

public override string ToString()
{
return Name;
if (!string.IsNullOrEmpty(Name))
{
return Name;
}

if (!string.IsNullOrEmpty(TypeName))
{
return TypeName;
}

return "???";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,18 @@ public CFfiTargetPlatform ExtractFfi(
private void VisitTranslationUnit(ExploreContext context, ParseContext parseContext)
{
LogVisitingTranslationUnit(parseContext.FilePath);
VisitIncludes(context, parseContext);

var isMultipleHeaders = !context.ParseContext.ExtractInput.IsSingleHeader;
if (isMultipleHeaders)
{
VisitIncludes(context, parseContext);
}

VisitFunctions(context, parseContext);
VisitVariables(context, parseContext);
VisitMacroObjects(context, parseContext);
VisitExplicitIncludedNames(context, parseContext);

LogVisitedTranslationUnit(parseContext.FilePath);
}

Expand Down Expand Up @@ -118,6 +126,33 @@ private void VisitMacroObject(ExploreContext context, clang.CXCursor clangCursor
context.TryEnqueueNode(info);
}

private void VisitExplicitIncludedNames(ExploreContext context, ParseContext parseContext)
{
var cursors = parseContext.GetExplicitlyIncludedNamedCursors();
foreach (var cursor in cursors)
{
VisitExplicitlyIncludedName(context, cursor);
}
}

private void VisitExplicitlyIncludedName(ExploreContext context, clang.CXCursor clangCursor)
{
var nodeKind = clangCursor.kind switch
{
clang.CXCursorKind.CXCursor_EnumDecl => CNodeKind.Enum,
_ => CNodeKind.Unknown
};

if (nodeKind == CNodeKind.Unknown)
{
// TODO: Add more allowed kinds to explicitly included names
return;
}

var info = context.CreateTopLevelNodeInfo(nodeKind, clangCursor);
context.TryEnqueueNode(info);
}

private void VisitIncludes(ExploreContext context, ParseContext parseContext)
{
var includeCursors = parseContext.GetIncludes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private CRecordField StructField(
ExploreNodeInfo structInfo,
CXCursor clangCursor)
{
var fieldName = clangCursor.Spelling();
var fieldName = context.GetFieldName(clangCursor);
var clangType = clang_getCursorType(clangCursor);
var location = context.ParseContext.Location(clangCursor);
var type = context.VisitType(clangType, structInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private CRecordField UnionField(
CXCursor clangCursor,
ExploreNodeInfo parentInfo)
{
var name = clangCursor.Spelling();
var name = context.GetFieldName(clangCursor);
var clangType = clang_getCursorType(clangCursor);
var location = context.ParseContext.Location(clangCursor);
var type = context.VisitType(clangType, parentInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ private static unsafe bool TryParseTranslationUnit(
ImmutableArray<string> commandLineArgs,
out CXTranslationUnit translationUnit,
bool skipFunctionBodies = true,
bool keepGoing = false)
bool keepGoing = false,
bool isSingleHeader = false)
{
// ReSharper disable BitwiseOperatorOnEnumWithoutFlags
uint options = 0x0 |
Expand All @@ -79,6 +80,11 @@ private static unsafe bool TryParseTranslationUnit(
0x4000 | // CXTranslationUnit_IgnoreNonErrorsFromIncludedFiles
0x0;

if (isSingleHeader)
{
options |= 0x400; // CXTranslationUnit_SingleFileParse
}

if (skipFunctionBodies)
{
options |= 0x40; // CXTranslationUnit_SkipFunctionBodies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,24 @@ static bool IsExternalVariable(clang.CXCursor child, clang.CXCursor parent)
return false;
}

return child.kind == clang.CXCursorKind.CXCursor_VarDecl && IsExternal(child);
var isExternal = IsExternal(child);
var isVariable = child.kind == clang.CXCursorKind.CXCursor_VarDecl;
return isVariable && isExternal;
}
}

public ImmutableArray<clang.CXCursor> GetExplicitlyIncludedNamedCursors()
{
var translationUnitCursor = clang.clang_getTranslationUnitCursor(_translationUnit);
var result = translationUnitCursor.GetDescendents(
(cursor, parent) => IsExplicitlyIncludedName(cursor, ExtractInput.IncludedNames));
return result;

static bool IsExplicitlyIncludedName(clang.CXCursor cursor, ImmutableHashSet<string> names)
{
var name = cursor.Spelling();
var isIncluded = names.Contains(name);
return isIncluded;
}
}

Expand Down Expand Up @@ -278,12 +295,6 @@ private void TryReleaseUnmanagedResources()

private static bool IsExternal(clang.CXCursor cursor)
{
var spelling = cursor.Spelling();
if (spelling == "SDL_malloc")
{
Console.WriteLine();
}

var linkage = clang.clang_getCursorLinkage(cursor);
if (linkage == clang.CXLinkageKind.CXLinkage_Invalid)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ private ExtractTargetPlatformInput SanitizeTargetPlatformInput(
MacroObjectDefines = ClangDefines(input, targetPlatformInput),
AdditionalArguments = ClangArguments(targetPlatformInput),
IsEnabledFindSystemHeaders = input.IsEnabledAutomaticallyFindSystemHeaders ?? true,
IsSingleHeader = input.IsSingleHeader ?? false,
IncludedNames = IncludedNames(input),
IgnoredMacroObjectsRegexes = IgnoredMacroObjects(input),
IgnoredVariableRegexes = IgnoredVariables(input),
IgnoredFunctionRegexes = IgnoredFunctions(input)
Expand Down Expand Up @@ -194,6 +196,11 @@ private ImmutableArray<Regex> IgnoredFunctions(UnsanitizedExtractInput input)
return SanitizeRegexes(input.IgnoredFunctions);
}

private ImmutableHashSet<string> IncludedNames(UnsanitizedExtractInput input)
{
return SanitizeStrings(input.IncludedNames).ToImmutableHashSet();
}

private string SanitizeOutputDirectoryPath(
string? outputDirectoryPath,
string targetPlatformString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Immutable;
using System.Text.RegularExpressions;
using bottlenoselabs;
using c2ffi.Data;

namespace c2ffi.Tool.Commands.Extract.Input.Sanitized;
Expand All @@ -25,6 +26,10 @@ public sealed class ExtractTargetPlatformInput

public bool IsEnabledFindSystemHeaders { get; init; }

public bool IsSingleHeader { get; init; }

public ImmutableHashSet<string> IncludedNames { get; init; } = ImmutableHashSet<string>.Empty;

public ImmutableArray<Regex> IgnoredMacroObjectsRegexes { get; init; } = ImmutableArray<Regex>.Empty;

public ImmutableArray<Regex> IgnoredVariableRegexes { get; init; } = ImmutableArray<Regex>.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,45 +54,6 @@ public sealed class UnsanitizedExtractInput : ToolUnsanitizedInput
[JsonPropertyName("ignoredIncludeFiles")]
public ImmutableArray<string>? IgnoredIncludeFiles { get; set; }

/// <summary>
/// Gets or sets a value that determines whether to show the the path of header code locations with full paths
/// or relative paths.
/// </summary>
/// <remarks>
/// <para>
/// Default is <c>false</c>. Use <c>true</c> to use the full path for header locations. Use <c>false</c> to
/// show only relative file paths.
/// </para>
/// </remarks>
[JsonPropertyName("isEnabledLocationFullPaths")]
public bool? IsEnabledLocationFullPaths { get; set; }

/// <summary>
/// Gets or sets a value that determines whether to include or exclude declarations (functions, enums, structs,
/// typedefs, etc) with a prefixed underscore that are assumed to be 'non public'.
/// </summary>
/// <remarks>
/// <para>
/// Default is <c>false</c>. Use <c>true</c> to include declarations with a prefixed underscore. Use
/// <c>false</c> to exclude declarations with a prefixed underscore.
/// </para>
/// </remarks>
[JsonPropertyName("isEnabledAllowNamesWithPrefixedUnderscore")]
public bool? IsEnabledAllowNamesWithPrefixedUnderscore { get; set; }

/// <summary>
/// Gets or sets a value that determines whether to include or exclude system declarations (functions, enums,
/// typedefs, records, etc).
/// </summary>
/// <remarks>
/// <para>
/// Default is `false`. Use <c>true</c> to include system declarations. Use `false` to exclude system
/// declarations.
/// </para>
/// </remarks>
[JsonPropertyName("isEnabledSystemDeclarations")]
public bool? IsEnabledSystemDeclarations { get; set; }

/// <summary>
/// Gets or sets a value that determines whether to automatically find and append the system headers for the
/// target platform.
Expand All @@ -107,15 +68,14 @@ public sealed class UnsanitizedExtractInput : ToolUnsanitizedInput
public bool? IsEnabledAutomaticallyFindSystemHeaders { get; set; }

/// <summary>
/// Gets or sets determines whether to parse only the top-level cursors which are externally visible, or all
/// top-level cursors.
/// Gets or sets a value that determines whether the C code is parsed as a single header or multiple headers.
/// </summary>
/// <para>
/// Default is <c>true</c>. Use <c>true</c> to parse only top-level cursors which are externally visible. Use
/// <c>false</c> to parse all top-level cursors whether or not they are externally visible.
/// Default is <c>false</c>. Use <c>true</c> to parse the C code as a single header. Use <c>false</c> to parse
/// the C code as multiple headers.
/// </para>
[JsonPropertyName("isEnabledOnlyExternalTopLevelCursors")]
public bool? IsEnabledOnlyExternalTopLevelCursors { get; set; }
[JsonPropertyName("isSingleHeader")]
public bool? IsSingleHeader { get; set; }

/// <summary>
/// Gets or sets the cursor names to be treated as opaque types.
Expand Down Expand Up @@ -153,4 +113,10 @@ public sealed class UnsanitizedExtractInput : ToolUnsanitizedInput
/// </summary>
[JsonPropertyName("appleFrameworks")]
public ImmutableArray<string>? AppleFrameworks { get; set; }

/// <summary>
/// Gets or sets the name of enums that are explicitly allowed.
/// </summary>
[JsonPropertyName("includedNames")]
public ImmutableArray<string>? IncludedNames { get; set; }
}
8 changes: 8 additions & 0 deletions src/cs/production/c2ffi.Tool/c2ffi.Tool.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,13 @@
<Folder Include="Commands\Extract\Infrastructure\" />
<Folder Include="Generated\" />
</ItemGroup>

<!-- libclang -->
<ItemGroup>
<None Include="$(GitRepositoryPath)\lib\libclang.dll" Condition="Exists('$(GitRepositoryPath)\lib\libclang.dll')">
<Link>libclang.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Loading

0 comments on commit b00c09a

Please sign in to comment.