Skip to content

Fix processing of auto backing fields #180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 26, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class StaticField
{
public string Name;
public int ReferenceIndex;
public string FieldWarning;
}

public class InstanceField
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
Expand Down Expand Up @@ -62,6 +62,9 @@ struct Library_{{AssemblyName}}_{{Name}}{{#newline}}
{{{#newline}}

{{#each StaticFields}}
{{#if FieldWarning}}
{{FieldWarning}}{{#newline}}
{{/if}}
static const int FIELD_STATIC__{{Name}} = {{ReferenceIndex}};{{#newline}}
{{/each}}
{{#if StaticFields}}{{#newline}}{{/if}}
Expand Down
42 changes: 29 additions & 13 deletions MetadataProcessor.Shared/nanoSkeletonGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,14 +465,17 @@ private void GenerateAssemblyHeader()

// static fields
int fieldCount = 0;
var staticFields = c.Fields.Where(f => f.IsStatic && !f.IsLiteral);
IEnumerable<FieldDefinition> staticFields = c.Fields.Where(f => f.IsStatic && !f.IsLiteral);

foreach (var f in staticFields)
foreach (FieldDefinition f in staticFields)
{
FixFieldName(f, out string fixedFieldName, out string fieldWarning);

classData.StaticFields.Add(new StaticField()
{
Name = f.Name,
ReferenceIndex = staticFieldCount + fieldCount++
Name = string.IsNullOrEmpty(fixedFieldName) ? f.Name : fixedFieldName,
ReferenceIndex = staticFieldCount + fieldCount++,
FieldWarning = fieldWarning
});
}

Expand All @@ -488,15 +491,7 @@ private void GenerateAssemblyHeader()
fieldCount = 0;
foreach (var f in c.Fields.Where(f => !f.IsStatic && !f.IsLiteral))
{
// rename auto-properties backing field to a valid C++ identifier
string fixedFieldName = string.Empty;
string fieldWarning = string.Empty;

if (Regex.IsMatch(f.Name, @"<\w+>k__BackingField"))
{
fixedFieldName = $"{f.Name.Replace("<", "").Replace(">", "_")}";
fieldWarning = $"// auto-property backing field renamed to '{fixedFieldName}'";
}
FixFieldName(f, out string fixedFieldName, out string fieldWarning);

if (_tablesContext.FieldsTable.TryGetFieldReferenceId(f, false, out ushort fieldRefId))
{
Expand Down Expand Up @@ -566,6 +561,27 @@ private void GenerateAssemblyHeader()
}
}

/// <summary>
/// Fix field name to a valid C++ identifier.
/// </summary>
/// <param name="field">The field definition to work on.</param>
/// <param name="fixedFieldName">The fixed field name, or an <see cref="Empty"/> string if no fix is needed.</param>
/// <param name="fieldWarning">The warning message to be added to the field declaration, or empty if no warning is needed.</param>
private static void FixFieldName(
FieldDefinition field,
out string fixedFieldName,
out string fieldWarning)
{
fixedFieldName = string.Empty;
fieldWarning = string.Empty;

if (Regex.IsMatch(field.Name, @"<\w+>k__BackingField"))
{
fixedFieldName = $"{field.Name.Replace("<", "").Replace(">k__BackingField", "")}";
fieldWarning = $"// renamed backing field '{field.Name}'";
}
}

private int GetInstanceFieldsOffset(TypeDefinition c)
{
// check if this type has a base type different from System.Object
Expand Down
106 changes: 81 additions & 25 deletions MetadataProcessor.Tests/Core/StubsGenerationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Mono.Cecil;
using nanoFramework.Tools.MetadataProcessor.Core;
Expand Down Expand Up @@ -58,15 +59,15 @@ public class StubsGenerationTests
private const string NativeHeaderMethodGenerationDeclaration =
"static void NativeMethodWithReferenceParameters( uint8_t& param0, uint16_t& param1, HRESULT &hr );";

private string stubPath;
private string _stubsPath;

[TestMethod]
public void GeneratingStubsFromNFAppTest()
{
// read generated stub file and look for the function declaration
var generatedFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp");

Assert.IsTrue(generatedFile.Contains(NativeMethodGenerationDeclaration));
}
Expand All @@ -76,7 +77,7 @@ public void GeneratingMarshallingStubsFromNFAppTest()
{
var generatedFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp");

Assert.IsTrue(generatedFile.Contains(NativeMarshallingMethodGenerationDeclaration));
}
Expand All @@ -86,7 +87,7 @@ public void GeneratingHeaderStubsFromNFAppTest()
{
var generatedFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h");

Assert.IsTrue(generatedFile.Contains(NativeHeaderMethodGenerationDeclaration));
}
Expand Down Expand Up @@ -127,15 +128,15 @@ public void GeneratingStaticMethodWithoutParams()
{
var generatedHeaderFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h");

var generatedMarshallFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp");

var generatedImplementationFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp");

Assert.IsTrue(generatedHeaderFile.Contains(StaticMethodWithoutParameterHeaderGeneration));
Assert.IsTrue(generatedMarshallFile.Contains(StaticMethodWithoutParameterMarshallGeneration));
Expand Down Expand Up @@ -182,27 +183,41 @@ public void GeneratingStaticMethod()
{
var generatedHeaderFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.h");

var generatedMarshallFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration_mshl.cpp");

var generatedImplementationFile =
File.ReadAllText(
$"{stubPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp");
$"{_stubsPath}\\StubsGenerationTestNFApp_StubsGenerationTestNFApp_NativeMethodGeneration.cpp");

Assert.IsTrue(generatedHeaderFile.Contains(StaticMethodHeaderGeneration));
Assert.IsTrue(generatedMarshallFile.Contains(StaticMethodMarshallGeneration));
Assert.IsTrue(generatedImplementationFile.Contains(StaticMethodImplementationGeneration));
}

[TestMethod]
public void BackingFieldsAbsentTests()
{
string generatedAssemblyHeaderFile =
File.ReadAllText(
$"{_stubsPath}\\TestNFClassLibrary.h");

// check for property with backing field patter in the name
Assert.IsFalse(generatedAssemblyHeaderFile.Contains("k__BackingField ="), "Found a name with BackingField pattern, when it shouldn't");

// deep check for backing field name pattern (except for entry patter in comments)
Assert.IsFalse(Regex.IsMatch(generatedAssemblyHeaderFile, @"(?<!')<\w+>k__BackingField(?!')"), "Found a name with BackingField pattern, when it shouldn't");
}

[TestInitialize]
public void GenerateStubs()
{
var loadHints = new Dictionary<string, string>(StringComparer.Ordinal)
{
["mscorlib"] = Path.Combine(Directory.GetParent(TestObjectHelper.GenerationNFAppFullPath).FullName,
["mscorlib"] = Path.Combine(Directory.GetParent(TestObjectHelper.StubsGenerationNFAppFullPath).FullName,
"mscorlib.dll")
};

Expand All @@ -212,51 +227,92 @@ public void GenerateStubs()
"THIS_NAME_DOES_NOT_EXIST_IN_THE_PROJECT"
};

var fileToParse = TestObjectHelper.GenerationNFAppFullPath;
var fileToCompile = Path.ChangeExtension(fileToParse, "pe");
// Conpile StubsGenerationNFApp
string stubsGenerationFileToParse = TestObjectHelper.StubsGenerationNFAppFullPath;
string stubsGenerationFileToCompile = Path.ChangeExtension(stubsGenerationFileToParse, "pe");

// get path where stubs will be generated
stubPath = Path.Combine(
_stubsPath = Path.Combine(
TestObjectHelper.TestExecutionLocation,
"Stubs");

var assemblyDefinition = AssemblyDefinition.ReadAssembly(
fileToParse,
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(
stubsGenerationFileToParse,
new ReaderParameters { AssemblyResolver = new LoadHintsAssemblyResolver(loadHints) });

var assemblyBuilder = new nanoAssemblyBuilder(assemblyDefinition, classNamesToExclude, false);
nanoAssemblyBuilder assemblyBuilder = new nanoAssemblyBuilder(assemblyDefinition, classNamesToExclude, false);

using (var stream = File.Open(
Path.ChangeExtension(fileToCompile, "tmp"),
using (FileStream stream = File.Open(
Path.ChangeExtension(stubsGenerationFileToCompile, "tmp"),
FileMode.Create,
FileAccess.ReadWrite))
using (var writer = new BinaryWriter(stream))
using (BinaryWriter writer = new BinaryWriter(stream))
{
assemblyBuilder.Write(GetBinaryWriter(writer));
}

// OK to delete tmp PE file
File.Delete(Path.ChangeExtension(fileToCompile, "tmp"));
File.Delete(Path.ChangeExtension(stubsGenerationFileToCompile, "tmp"));

assemblyBuilder.Minimize();

var tablesContext = assemblyBuilder.TablesContext;
nanoTablesContext tablesContext = assemblyBuilder.TablesContext;

var skeletonGenerator = new nanoSkeletonGenerator(
tablesContext,
stubPath,
_stubsPath,
"testStubs",
"StubsGenerationTestNFApp",
false,
false);

skeletonGenerator.GenerateSkeleton();

// Compile the TestNFClassLibrary
string nfLibFileToParse = TestObjectHelper.TestNFClassLibFullPath;
string nfLibFileToCompile = Path.ChangeExtension(nfLibFileToParse, "pe");

assemblyDefinition = AssemblyDefinition.ReadAssembly(
nfLibFileToParse,
new ReaderParameters { AssemblyResolver = new LoadHintsAssemblyResolver(loadHints) });

assemblyBuilder = new nanoAssemblyBuilder(
assemblyDefinition,
new List<string>(),
false);

using (FileStream stream = File.Open(
Path.ChangeExtension(nfLibFileToCompile, "tmp"),
FileMode.Create,
FileAccess.ReadWrite))

using (BinaryWriter writer = new BinaryWriter(stream))
{
assemblyBuilder.Write(GetBinaryWriter(writer));
}

// OK to delete tmp PE file
File.Delete(Path.ChangeExtension(nfLibFileToCompile, "tmp"));

assemblyBuilder.Minimize();

tablesContext = assemblyBuilder.TablesContext;

skeletonGenerator = new nanoSkeletonGenerator(
tablesContext,
_stubsPath,
"testStubs",
"TestNFClassLibrary",
true,
true);

skeletonGenerator.GenerateSkeleton();
}

[TestCleanup]
public void DeleteStubs()
{
Directory.Delete(stubPath, true);
Directory.Delete(_stubsPath, true);
}

private nanoBinaryWriter GetBinaryWriter(
Expand All @@ -265,4 +321,4 @@ private nanoBinaryWriter GetBinaryWriter(
return nanoBinaryWriter.CreateLittleEndianBinaryWriter(writer);
}
}
}
}
4 changes: 2 additions & 2 deletions MetadataProcessor.Tests/Core/Utility/DumperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public void DumpAssemblyTest()
Assert.IsTrue(dumpFileContent.Contains("TypeRefProps [01000001]: Scope: 23000001 'System.Diagnostics.DebuggableAttribute'"), "Wrong entry for System.Diagnostics.DebuggableAttribute in type ref");
Assert.IsTrue(dumpFileContent.Contains(": Scope: 23000002 'TestNFClassLibrary.ClassOnAnotherAssembly'"), "Wrong entry for TestNFClassLibrary.ClassOnAnotherAssembly in type ref");

Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001001 Extends: 0100000f Enclosed: 02000000 'TestNFApp.DummyCustomAttribute1'"), "Wrong entry for TestNFApp.DummyCustomAttribute1 in type ref");
Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001001 Extends: 01000010 Enclosed: 02000000 'TestNFApp.DummyCustomAttribute1'"), "Wrong entry for TestNFApp.DummyCustomAttribute1 in type ref");

Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001001 Extends: 0100000f Enclosed: 02000000 'TestNFApp.DummyCustomAttribute2'"), "Wrong entry for TestNFApp.DummyCustomAttribute2 in type ref");
Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001001 Extends: 01000010 Enclosed: 02000000 'TestNFApp.DummyCustomAttribute2'"), "Wrong entry for TestNFApp.DummyCustomAttribute2 in type ref");

Assert.IsTrue(dumpFileContent.Contains(": Flags: 00001061 Extends: 01000000 Enclosed: 02000000 'TestNFApp.IOneClassOverAll'"), "Wrong entry for TestNFApp.IOneClassOverAll in type ref");
Assert.IsTrue(dumpFileContent.Contains(": Flags: 000007c6 Impl: 00000000 RVA: 00000000 'get_DummyProperty' [I4( )]"), "Wrong entry for get_DummyProperty in type ref");
Expand Down
4 changes: 3 additions & 1 deletion MetadataProcessor.Tests/MetadataProcessor.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,11 @@
"$(MSBuildBinPath)\msbuild" "$(ProjectDir)TestNFApp\TestNFApp.nfproj" -t:Build -nr:False -p:Configuration=$(Configuration) -p:NF_MDP_MSBUILDTASK_PATH="$(ProjectDir)..\MetadataProcessor.MsBuildTask\bin\$(Configuration)\net472"
"$(MSBuildBinPath)\msbuild" "$(ProjectDir)StubsGenerationTestNFApp\StubsGenerationTestNFApp.nfproj" -t:Build -nr:False -p:Configuration=$(Configuration) -p:NF_MDP_MSBUILDTASK_PATH="$(ProjectDir)..\MetadataProcessor.MsBuildTask\bin\$(Configuration)\net472"
mkdir "$(TargetDir)\TestNFApp"
mkdir "$(TargetDir)\TestNFClassLibrary"
mkdir "$(TargetDir)\StubsGenerationTestNFApp"
copy /y "$(ProjectDir)TestNFApp\$(OutDir)\*" "$(TargetDir)\TestNFApp"
copy /y "$(ProjectDir)TestNFApp\$(OutDir)\TestNFClassLibrary.*" "$(TargetDir)\TestNFClassLibrary"
copy /y "$(ProjectDir)StubsGenerationTestNFApp\$(OutDir)\*" "$(TargetDir)\StubsGenerationTestNFApp"
</PreBuildEvent>
</PropertyGroup>
</Project>
</Project>
6 changes: 6 additions & 0 deletions MetadataProcessor.Tests/TestNFApp/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

/////////////////////////////////////////////////////////////////
// This attribute is mandatory when building Interop libraries //
// update this whenever the native assembly signature changes //
[assembly: AssemblyNativeVersion("0.0.0.0")]
/////////////////////////////////////////////////////////////////
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//

using System.Runtime.CompilerServices;

namespace TestNFClassLibrary
{
public class ClassWithNativeImplementation
{
private static int _staticField;
private int _field;

public static int StaticProperty1 { get; set; }

public int Property1 { get; set; }

public void ManagedMethod1()
{
NativeMethod1();
}

public void ManagedMethod2()
{
NativeMethod2();
}

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void NativeMethod1();

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void NativeMethod2();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ItemGroup>
<Compile Include="IAmAClassWithAnEnum.cs" />
<Compile Include="ClassOnAnotherAssembly.cs" />
<Compile Include="ClassWithNativeImplementation.cs" />
<Compile Include="IAmAnEnumToExclude.cs" />
<Compile Include="IAmAlsoATypeToExclude.cs" />
<Compile Include="IAmATypeToExclude.cs" />
Expand All @@ -39,4 +40,4 @@
<ProjectConfigurationsDeclaredAsItems />
</ProjectCapabilities>
</ProjectExtensions>
</Project>
</Project>
Loading