Skip to content

Fix handling of TypeSpec fields #184

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 2 commits into from
Apr 30, 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
18 changes: 16 additions & 2 deletions MetadataProcessor.Shared/Tables/nanoTablesContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}");
Expand Down
15 changes: 15 additions & 0 deletions MetadataProcessor.Shared/Tables/nanoTypeSpecificationsTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ public TypeReference TryGetTypeSpecification(MetadataToken token)
return _idByTypeSpecifications.FirstOrDefault(typeEntry => typeEntry.Key.MetadataToken == token).Key;
}

/// <summary>
/// Tries to find type reference by the index on the TypeSpec list.
/// </summary>
/// <param name="index">Index of the type reference in the list.</param>
/// <returns>Returns the type reference if found, otherwise returns <c>null</c>.</returns>
public TypeReference TryGetTypeReferenceByIndex(ushort index)
{
if (index >= _idByTypeSpecifications.Count)
{
return null;
}

return _idByTypeSpecifications.ElementAt(index).Key;
}

/// <inheritdoc/>
public void Write(
nanoBinaryWriter writer)
Expand Down
47 changes: 41 additions & 6 deletions MetadataProcessor.Shared/nanoAssemblyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,18 +350,20 @@ private HashSet<MetadataToken> BuildDependencyList(MetadataToken token)
if (mr != null &&
mr.ReturnType != null)
{
parameters = mr.Parameters;

if (mr.DeclaringType != null)
{
if (mr.DeclaringType is TypeSpecification)
{
// 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
{
Expand Down Expand Up @@ -460,9 +462,13 @@ private HashSet<MetadataToken> 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
{
Expand Down Expand Up @@ -518,14 +524,33 @@ private HashSet<MetadataToken> 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;

Expand Down Expand Up @@ -807,10 +832,12 @@ private HashSet<MetadataToken> 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
Expand Down Expand Up @@ -844,9 +871,13 @@ private HashSet<MetadataToken> 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
{
Expand Down Expand Up @@ -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
{
Expand Down
19 changes: 19 additions & 0 deletions MetadataProcessor.Shared/nanoDumperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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}");
}

Expand Down
2 changes: 1 addition & 1 deletion MetadataProcessor.Tests/Core/Utility/DumperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions MetadataProcessor.Tests/TestNFApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public static void Main()
///////////////////////////////////////////////////////////////////
// Generics Tests
_ = new GenericClassTests();
_ = new StatckTests();

// null attributes tests
Console.WriteLine("Null attributes tests");
Expand Down
43 changes: 43 additions & 0 deletions MetadataProcessor.Tests/TestNFApp/StackClass.cs
Original file line number Diff line number Diff line change
@@ -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<T>
{
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<int> intStack = new Stack<int>(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<string> stringStack = new Stack<string>(5);

stringStack.Push("Hello");
stringStack.Push("World");

Console.WriteLine($"First value is {stringStack.Pop()}");
Console.WriteLine($"Second value is {stringStack.Pop()}");
}
}
}
1 change: 1 addition & 0 deletions MetadataProcessor.Tests/TestNFApp/TestNFApp.nfproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="DataRowAttribute.cs" />
<Compile Include="DummyCustomAttribute1.cs" />
<Compile Include="DummyCustomAttribute2.cs" />
<Compile Include="StackClass.cs" />
<Compile Include="IgnoreAttribute.cs" />
<Compile Include="IOneClassOverAll.cs" />
<Compile Include="ComplexAttribute.cs" />
Expand Down
Loading