From 9de2468bf6aed4d7c3c8c5b7c99a3acd7ca8e052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 30 Apr 2025 11:26:39 +0100 Subject: [PATCH 1/2] Fix handling of TypeSpec fields - GetFieldReferenceId can deal with member references. - Improve handling of TypeSpecs in BuildDependencyList, now it processes the real and fabricated tokens during minimization. - Add more developer notes to explain reasoning and overall functioning for TypeSpecs. - Dumper generator now outputs field references properly. - Add Stack class to test applications. --- .../Tables/nanoTablesContext.cs | 18 ++++++- .../Tables/nanoTypeSpecificationsTable.cs | 15 ++++++ .../nanoAssemblyBuilder.cs | 47 ++++++++++++++++--- .../nanoDumperGenerator.cs | 19 ++++++++ MetadataProcessor.Tests/TestNFApp/Program.cs | 1 + .../TestNFApp/StackClass.cs | 43 +++++++++++++++++ .../TestNFApp/TestNFApp.nfproj | 1 + 7 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 MetadataProcessor.Tests/TestNFApp/StackClass.cs diff --git a/MetadataProcessor.Shared/Tables/nanoTablesContext.cs b/MetadataProcessor.Shared/Tables/nanoTablesContext.cs index fdc927b0..0bfb7d7f 100644 --- a/MetadataProcessor.Shared/Tables/nanoTablesContext.cs +++ b/MetadataProcessor.Shared/Tables/nanoTablesContext.cs @@ -316,12 +316,12 @@ public ushort GetFieldReferenceId( // 0: TBL_FieldDef // 1: TBL_FieldRef - ushort referenceId = 0xFFFF; + ushort referenceId; NanoClrTable ownerTable = NanoClrTable.TBL_EndOfAssembly; if (fieldReference is FieldDefinition) { - // field reference is internal + // field definition is internal if (FieldsTable.TryGetFieldReferenceId(fieldReference as FieldDefinition, false, out referenceId)) { // field reference is internal => field definition @@ -337,6 +337,20 @@ public ushort GetFieldReferenceId( // field reference is external ownerTable = NanoClrTable.TBL_FieldRef; } + else if (fieldReference is MemberReference) + { + // field reference belongs to a TypeSpec + // find FieldDef for this + FieldReference fieldRef = FieldReferencesTable.Items.FirstOrDefault(m => m.DeclaringType.MetadataToken == fieldReference.DeclaringType.MetadataToken && m.Name == fieldReference.Name); + + if (!FieldReferencesTable.TryGetFieldReferenceId(fieldRef, out referenceId)) + { + Debug.Fail($"Can't find FieldRef for {fieldReference}"); + } + + // owner is FieldRef + ownerTable = NanoClrTable.TBL_FieldRef; + } else { Debug.Fail($"Can't find any reference for {fieldReference}"); diff --git a/MetadataProcessor.Shared/Tables/nanoTypeSpecificationsTable.cs b/MetadataProcessor.Shared/Tables/nanoTypeSpecificationsTable.cs index ea27c951..85825940 100644 --- a/MetadataProcessor.Shared/Tables/nanoTypeSpecificationsTable.cs +++ b/MetadataProcessor.Shared/Tables/nanoTypeSpecificationsTable.cs @@ -130,6 +130,21 @@ public TypeReference TryGetTypeSpecification(MetadataToken token) return _idByTypeSpecifications.FirstOrDefault(typeEntry => typeEntry.Key.MetadataToken == token).Key; } + /// + /// Tries to find type reference by the index on the TypeSpec list. + /// + /// Index of the type reference in the list. + /// Returns the type reference if found, otherwise returns null. + public TypeReference TryGetTypeReferenceByIndex(ushort index) + { + if (index >= _idByTypeSpecifications.Count) + { + return null; + } + + return _idByTypeSpecifications.ElementAt(index).Key; + } + /// public void Write( nanoBinaryWriter writer) diff --git a/MetadataProcessor.Shared/nanoAssemblyBuilder.cs b/MetadataProcessor.Shared/nanoAssemblyBuilder.cs index ce3e0320..89a24689 100644 --- a/MetadataProcessor.Shared/nanoAssemblyBuilder.cs +++ b/MetadataProcessor.Shared/nanoAssemblyBuilder.cs @@ -350,8 +350,6 @@ private HashSet BuildDependencyList(MetadataToken token) if (mr != null && mr.ReturnType != null) { - parameters = mr.Parameters; - if (mr.DeclaringType != null) { if (mr.DeclaringType is TypeSpecification) @@ -359,9 +357,13 @@ private HashSet BuildDependencyList(MetadataToken token) // Cecil.Mono has a bug providing TypeSpecs Metadata tokens generic parameters variables, so we need to check against our internal table and build one from it if (_tablesContext.TypeSpecificationsTable.TryGetTypeReferenceId(mr.DeclaringType, out ushort referenceId)) { + // add "fabricated" token for TypeSpec using the referenceId as RID set.Add(new MetadataToken( TokenType.TypeSpec, referenceId)); + + // add the metadata token for the element type + set.Add(mr.DeclaringType.GetElementType().MetadataToken); } else { @@ -460,9 +462,13 @@ private HashSet BuildDependencyList(MetadataToken token) // Cecil.Mono has a bug providing TypeSpecs Metadata tokens generic parameters variables, so we need to check against our internal table and build one from it if (_tablesContext.TypeSpecificationsTable.TryGetTypeReferenceId(fr.DeclaringType, out ushort referenceId)) { + // add "fabricated" token for TypeSpec using the referenceId as RID set.Add(new MetadataToken( TokenType.TypeSpec, referenceId)); + + // add the metadata token for the element type + set.Add(fr.DeclaringType.GetElementType().MetadataToken); } else { @@ -518,14 +524,33 @@ private HashSet BuildDependencyList(MetadataToken token) break; case TokenType.TypeSpec: - var ts = _tablesContext.TypeSpecificationsTable.TryGetTypeSpecification(token); - - if (ts != null) + // Developer notes: + // Because the issue with Mono.Cecil not providing the correct metadata token for TypeSpec, + // need to search the TypeSpec token in the two "formats". + // 1. The original token as provided by Mono.Cecil + // 2. The "fabricated" token, which is the one that is used in the TypeSpec table + // It's OK to add both to the set because they end up referencing to the same type. + // Anyways, as this a minimize operation, it's preferable to have both rather than none. + + // start searching for metadata token + TypeReference ts1 = _tablesContext.TypeSpecificationsTable.TryGetTypeSpecification(token); + + if (ts1 != null) { + // found it, let's add it set.Add(token); } - Debug.Assert(ts != null); + // now try to find the TypeSpec from the "fabricated" token, using the RID + TypeReference ts2 = _tablesContext.TypeSpecificationsTable.TryGetTypeReferenceByIndex((ushort)token.RID); + + if (ts2 != null) + { + set.Add(ts2.MetadataToken); + } + + // sanity check + Debug.Assert(ts1 != null || ts2 != null); break; @@ -807,10 +832,12 @@ private HashSet BuildDependencyList(MetadataToken token) // Cecil.Mono has a bug providing TypeSpecs Metadata tokens generic parameters variables, so we need to check against our internal table and build one from it if (_tablesContext.TypeSpecificationsTable.TryGetTypeReferenceId(v.VariableType, out ushort referenceId)) { + // add "fabricated" token for TypeSpec using the referenceId as RID set.Add(new MetadataToken( TokenType.TypeSpec, referenceId)); + // add the metadata token for the element type set.Add(v.VariableType.GetElementType().MetadataToken); } else @@ -844,9 +871,13 @@ private HashSet BuildDependencyList(MetadataToken token) // Cecil.Mono has a bug providing TypeSpecs Metadata tokens generic parameters variables, so we need to check against our internal table and build one from it if (_tablesContext.TypeSpecificationsTable.TryGetTypeReferenceId(methodReferenceType.DeclaringType, out referenceId)) { + // add "fabricated" token for TypeSpec using the referenceId as RID set.Add(new MetadataToken( TokenType.TypeSpec, referenceId)); + + // add the metadata token for the element type + set.Add(methodReferenceType.DeclaringType.GetElementType().MetadataToken); } else { @@ -877,9 +908,13 @@ i.Operand is GenericInstanceMethod || // Cecil.Mono has a bug providing TypeSpecs Metadata tokens generic parameters variables, so we need to check against our internal table and build one from it if (_tablesContext.TypeSpecificationsTable.TryGetTypeReferenceId(opType, out ushort referenceId)) { + // add "fabricated" token for TypeSpec using the referenceId as RID set.Add(new MetadataToken( TokenType.TypeSpec, referenceId)); + + // add the metadata token for the element type + set.Add(opType.GetElementType().MetadataToken); } else { diff --git a/MetadataProcessor.Shared/nanoDumperGenerator.cs b/MetadataProcessor.Shared/nanoDumperGenerator.cs index d321f3a8..888515c2 100644 --- a/MetadataProcessor.Shared/nanoDumperGenerator.cs +++ b/MetadataProcessor.Shared/nanoDumperGenerator.cs @@ -734,6 +734,25 @@ private void DumpTypeSpecifications( } } + foreach (var fr in _tablesContext.FieldReferencesTable.Items) + { + if (_tablesContext.TypeSpecificationsTable.TryGetTypeReferenceId(fr.DeclaringType, out ushort referenceId) && + referenceId == index) + { + var memberRef = new MemberRef() + { + Name = fr.Name + }; + if (_tablesContext.FieldReferencesTable.TryGetFieldReferenceId(fr, out ushort fieldRefId)) + { + realToken = fr.MetadataToken.ToInt32().ToString("X8"); + memberRef.ReferenceId = $"[{new nanoMetadataToken(NanoClrTable.TBL_FieldRef, fieldRefId)}] /*{realToken}*/"; + memberRef.Signature = fr.FieldType.TypeSignatureAsString(); + } + typeSpec.MemberReferences.Add(memberRef); + } + } + Debug.Assert(typeSpec.MemberReferences.Count > 0, $"Couldn't find any MethodRef for TypeSpec[{typeReference}] {typeReference.FullName}"); } diff --git a/MetadataProcessor.Tests/TestNFApp/Program.cs b/MetadataProcessor.Tests/TestNFApp/Program.cs index a135f8e9..e0ed8b8c 100644 --- a/MetadataProcessor.Tests/TestNFApp/Program.cs +++ b/MetadataProcessor.Tests/TestNFApp/Program.cs @@ -80,6 +80,7 @@ public static void Main() /////////////////////////////////////////////////////////////////// // Generics Tests _ = new GenericClassTests(); + _ = new StatckTests(); // null attributes tests Console.WriteLine("Null attributes tests"); diff --git a/MetadataProcessor.Tests/TestNFApp/StackClass.cs b/MetadataProcessor.Tests/TestNFApp/StackClass.cs new file mode 100644 index 00000000..887d0864 --- /dev/null +++ b/MetadataProcessor.Tests/TestNFApp/StackClass.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace TestNFApp +{ + public class Stack + { + private readonly T[] _items; + private int _count; + + public Stack(int size) => _items = new T[size]; + + public void Push(T item) => _items[_count++] = item; + + public T Pop() => _items[--_count]; + } + + public class StatckTests + { + public StatckTests() + { + // Create a stack of integers + Stack intStack = new Stack(5); + + intStack.Push(1); + intStack.Push(2); + + Console.WriteLine($"First value is{intStack.Pop()}"); + Console.WriteLine($"Second value is{intStack.Pop()}"); + + // Create a stack of strings + Stack stringStack = new Stack(5); + + stringStack.Push("Hello"); + stringStack.Push("World"); + + Console.WriteLine($"First value is {stringStack.Pop()}"); + Console.WriteLine($"Second value is {stringStack.Pop()}"); + } + } +} diff --git a/MetadataProcessor.Tests/TestNFApp/TestNFApp.nfproj b/MetadataProcessor.Tests/TestNFApp/TestNFApp.nfproj index 10f82e28..d2d5a693 100644 --- a/MetadataProcessor.Tests/TestNFApp/TestNFApp.nfproj +++ b/MetadataProcessor.Tests/TestNFApp/TestNFApp.nfproj @@ -23,6 +23,7 @@ + From ee16f5650bd9a73cd9a8bf91be90d97666ceb527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Wed, 30 Apr 2025 14:59:28 +0100 Subject: [PATCH 2/2] Update unit test --- MetadataProcessor.Tests/Core/Utility/DumperTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetadataProcessor.Tests/Core/Utility/DumperTests.cs b/MetadataProcessor.Tests/Core/Utility/DumperTests.cs index 284fdf79..b6f3b274 100644 --- a/MetadataProcessor.Tests/Core/Utility/DumperTests.cs +++ b/MetadataProcessor.Tests/Core/Utility/DumperTests.cs @@ -133,7 +133,7 @@ public void DumpAssemblyTest() nanoTablesContext.TypeReferencesTable.TryGetTypeReferenceId(type1.BaseType, out baseTypeReferenceId); typeFlags = (uint)nanoTypeDefinitionTable.GetFlags(type1, nanoTablesContext.MethodDefinitionTable); - Assert.IsTrue(dumpFileContent.Contains($"TypeDef {nanoDumperGenerator.TypeDefRefIdToString(type1, typeRefId)}\r\n-------------------------------------------------------\r\n '{type1.Name}'\r\n Flags: {typeFlags:X8}\r\n Extends: {nanoDumperGenerator.TypeDefExtendsTypeToString(type1.BaseType, baseTypeReferenceId)}\r\n Enclosed: TestNFApp.OneClassOverAll[04000000] /*0200001B*/")); + Assert.IsTrue(dumpFileContent.Contains($"TypeDef {nanoDumperGenerator.TypeDefRefIdToString(type1, typeRefId)}\r\n-------------------------------------------------------\r\n '{type1.Name}'\r\n Flags: {typeFlags:X8}\r\n Extends: {nanoDumperGenerator.TypeDefExtendsTypeToString(type1.BaseType, baseTypeReferenceId)}\r\n Enclosed: TestNFApp.OneClassOverAll[04000000] /*0200001D*/")); // String heap foreach (string stringKey in nanoTablesContext.StringTable.GetItems().Keys)