Skip to content

Improve type comparison for declarations and stubs #181

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 28, 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
34 changes: 23 additions & 11 deletions MetadataProcessor.Shared/nanoSkeletonGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public sealed class nanoSkeletonGenerator

private string _safeProjectName => _project.Replace('.', '_');

public string SafeProjectName => _safeProjectName;

public nanoSkeletonGenerator(
nanoTablesContext tablesContext,
string path,
Expand Down Expand Up @@ -87,11 +89,9 @@ private void GenerateStubs()
IsInterop = !_withoutInteropCode
};

foreach (var c in _tablesContext.TypeDefinitionTable.TypeDefinitions)
foreach (TypeDefinition c in _tablesContext.TypeDefinitionTable.TypeDefinitions)
{
if (c.IncludeInStub() &&
!c.IsToExclude() &&
!nanoTablesContext.IgnoringAttributes.Contains(c.FullName))
if (ShouldIncludeType(c))
{
var className = NativeMethodsCrc.GetClassName(c);

Expand Down Expand Up @@ -370,12 +370,12 @@ private void GenerateAssemblyLookup()
};


foreach (var c in _tablesContext.TypeDefinitionTable.Items)
foreach (TypeDefinition c in _tablesContext.TypeDefinitionTable.Items)
{
// only care about types that have methods
if (c.HasMethods)
{
if (c.IncludeInStub())
if (ShouldIncludeType(c))
{
var className = NativeMethodsCrc.GetClassName(c);

Expand All @@ -399,8 +399,11 @@ private void GenerateAssemblyLookup()
// need to add a NULL entry for it
assemblyLookup.LookupTable.Add(new MethodStub()
{
#if DEBUG
Declaration = $"NULL, // <<<<< Library_{_safeProjectName}_{NativeMethodsCrc.GetClassName(c)}::{NativeMethodsCrc.GetMethodName(m)}",
#else
Declaration = "NULL"
//Declaration = $"**Library_{_safeProjectName}_{NativeMethodsCrc.GetClassName(c)}::{NativeMethodsCrc.GetMethodName(m)}"
#endif
});
}
}
Expand All @@ -413,8 +416,11 @@ private void GenerateAssemblyLookup()
{
assemblyLookup.LookupTable.Add(new MethodStub()
{
#if DEBUG
Declaration = $"NULL, // <<<<< Library_{_safeProjectName}_{NativeMethodsCrc.GetClassName(c)}::{NativeMethodsCrc.GetMethodName(m)}",
#else
Declaration = "NULL"
//Declaration = $"**Library_{_safeProjectName}_{NativeMethodsCrc.GetClassName(c)}::{NativeMethodsCrc.GetMethodName(m)}"
#endif
});
}
}
Expand Down Expand Up @@ -443,10 +449,9 @@ private void GenerateAssemblyHeader()
IsCoreLib = _isCoreLib
};

foreach (var c in _tablesContext.TypeDefinitionTable.Items)
foreach (TypeDefinition c in _tablesContext.TypeDefinitionTable.Items)
{
if (c.IncludeInStub() &&
!c.IsToExclude())
if (ShouldIncludeType(c))
{
var classData = new Class()
{
Expand Down Expand Up @@ -619,5 +624,12 @@ private int GetNestedFieldsCount(TypeDefinition c)
return c.Fields.Count(f => !f.IsStatic && !f.IsLiteral);
}
}

internal static bool ShouldIncludeType(TypeDefinition type)
{
return type.IncludeInStub()
&& !type.IsToExclude()
&& !nanoTablesContext.IgnoringAttributes.Contains(type.FullName);
}
}
}
100 changes: 100 additions & 0 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.Linq;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Mono.Cecil;
Expand Down Expand Up @@ -60,6 +61,8 @@ public class StubsGenerationTests
"static void NativeMethodWithReferenceParameters( uint8_t& param0, uint16_t& param1, HRESULT &hr );";

private string _stubsPath;
private List<string> _nfTestLibTypeToIncludeInLookupTable = new List<string>();
private List<string> _nfTestLibTypeToIncludeInHeader = new List<string>();

[TestMethod]
public void GeneratingStubsFromNFAppTest()
Expand Down Expand Up @@ -212,6 +215,49 @@ public void BackingFieldsAbsentTests()
Assert.IsFalse(Regex.IsMatch(generatedAssemblyHeaderFile, @"(?<!')<\w+>k__BackingField(?!')"), "Found a name with BackingField pattern, when it shouldn't");
}

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

// extract all type definitions from the header file
MatchCollection typeDefinitionsInHeader = Regex.Matches(generatedAssemblyHeaderFile, @"struct\s{1}(\w+_\w+_\w+_\w+)\b", RegexOptions.IgnoreCase | RegexOptions.Multiline);

// extract all type definitions from the lookup file
List<Match> typeDefinitionsInLookupTable = Regex.Matches(generatedAssemblyLookupFile, @"^\s{4}(\w+_\w+_\w+_\w+)::", RegexOptions.IgnoreCase | RegexOptions.Multiline)
.Cast<Match>()
.GroupBy(m => m.Groups[1].Value)
.Select(g => g.First())
.ToList();

// check if all entries in lookup table are present in the header
foreach (Match typeDefinition in typeDefinitionsInLookupTable)
{
string typeName = typeDefinition.Groups[1].Value;
bool found = typeDefinitionsInHeader.Cast<Match>().Any(md => md.Groups[1].Value == typeName);
Assert.IsTrue(found, $"Type definition {typeName} not found in header file");
}

// check if all expected types are present in the lookup table
Assert.AreEqual(_nfTestLibTypeToIncludeInLookupTable.Count, typeDefinitionsInLookupTable.Count, "Number of type definitions don't match");

foreach (string typeName in _nfTestLibTypeToIncludeInLookupTable)
{
bool found = typeDefinitionsInLookupTable.Any(md => md.Groups[1].Value == typeName);
Assert.IsTrue(found, $"Type definition {typeName} not found in lookup table");
}

// check if all expected types are present in the header file
Assert.AreEqual(_nfTestLibTypeToIncludeInHeader.Count, typeDefinitionsInHeader.Count, "Number of type definitions don't match");

foreach (string typeName in _nfTestLibTypeToIncludeInHeader)
{
bool found = typeDefinitionsInHeader.Cast<Match>().Any(md => md.Groups[1].Value == typeName);
Assert.IsTrue(found, $"Type definition {typeName} not found in header file");
}
}

[TestInitialize]
public void GenerateStubs()
{
Expand Down Expand Up @@ -256,6 +302,16 @@ public void GenerateStubs()

assemblyBuilder.Minimize();

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

nanoTablesContext tablesContext = assemblyBuilder.TablesContext;

var skeletonGenerator = new nanoSkeletonGenerator(
Expand Down Expand Up @@ -296,6 +352,17 @@ public void GenerateStubs()

assemblyBuilder.Minimize();

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

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

tablesContext = assemblyBuilder.TablesContext;

skeletonGenerator = new nanoSkeletonGenerator(
Expand All @@ -307,6 +374,39 @@ public void GenerateStubs()
true);

skeletonGenerator.GenerateSkeleton();

// save types that are to be included from assembly lookup declaration
foreach (TypeDefinition c in tablesContext.TypeDefinitionTable.Items)
{
if (c.HasMethods && nanoSkeletonGenerator.ShouldIncludeType(c))
{
foreach (MethodDefinition m in nanoTablesContext.GetOrderedMethods(c.Methods))
{
ushort rva = tablesContext.ByteCodeTable.GetMethodRva(m);

// check method inclusion
// method is not a native implementation (RVA 0xFFFF) and is not abstract
if (rva == 0xFFFF && !m.IsAbstract)
{
_nfTestLibTypeToIncludeInLookupTable.Add($"Library_{skeletonGenerator.SafeProjectName}_{NativeMethodsCrc.GetClassName(c)}");

// only need to add the type once
break;
}
}
}
}

// save types that are to be included in assembly header
foreach (TypeDefinition c in tablesContext.TypeDefinitionTable.Items)
{
if (nanoSkeletonGenerator.ShouldIncludeType(c)
&& c.HasMethods
&& c.HasFields)
{
_nfTestLibTypeToIncludeInHeader.Add($"Library_{skeletonGenerator.SafeProjectName}_{NativeMethodsCrc.GetClassName(c)}");
}
}
}

[TestCleanup]
Expand Down