From 911c101bad575f318eb31bc41c0364eab40482c1 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Mon, 2 Feb 2026 02:45:06 +0200 Subject: [PATCH 01/14] Include custom modifiers when importing method reference signatures. --- .../System/Reflection/Emit/SignatureHelper.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs index 2ec00b90743525..d211d181dc769c 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs @@ -85,7 +85,8 @@ internal static BlobBuilder GetMethodSignature(ModuleBuilderImpl module, Type[]? new BlobEncoder(methodSignature).MethodSignature(convention, genParamCount, isInstance). Parameters(paramsLength, out ReturnTypeEncoder retEncoder, out ParametersEncoder parEncoder); - WriteReturnTypeCustomModifiers(retEncoder.CustomModifiers(), returnTypeRequiredModifiers, returnTypeOptionalModifiers, module); + WriteReturnTypeCustomModifiers(retEncoder.CustomModifiers(), returnTypeRequiredModifiers ?? returnType?.GetRequiredCustomModifiers(), + returnTypeOptionalModifiers ?? returnType?.GetOptionalCustomModifiers(), module); if (returnType != null && returnType != module.GetTypeFromCoreAssembly(CoreTypeId.Void)) { @@ -156,15 +157,15 @@ private static void WriteParametersSignature(ModuleBuilderImpl module, Type[]? p { ParameterTypeEncoder encoder = parameterEncoder.AddParameter(); - if (requiredModifiers != null && requiredModifiers.Length > i && requiredModifiers[i] != null) - { - WriteCustomModifiers(encoder.CustomModifiers(), requiredModifiers[i], isOptional: false, module); - } + Type[] modreqs = requiredModifiers != null && requiredModifiers.Length > i && requiredModifiers[i] is { } mr + ? mr + : parameters[i].GetRequiredCustomModifiers(); + WriteCustomModifiers(encoder.CustomModifiers(), modreqs, isOptional: false, module); - if (optionalModifiers != null && optionalModifiers.Length > i && optionalModifiers[i] != null) - { - WriteCustomModifiers(encoder.CustomModifiers(), optionalModifiers[i], isOptional: true, module); - } + Type[] modopts = optionalModifiers != null && optionalModifiers.Length > i && optionalModifiers[i] is { } mo + ? mo + : parameters[i].GetOptionalCustomModifiers(); + WriteCustomModifiers(encoder.CustomModifiers(), modopts, isOptional: true, module); WriteSignatureForType(encoder.Type(), parameters[i], module); } From 9981ae2e39be473eff51d89a2a33779b020bddd9 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Tue, 3 Feb 2026 01:49:24 +0200 Subject: [PATCH 02/14] Add test. --- .../AssemblySaveTypeBuilderTests.cs | 42 +++++++++++++++++++ .../tests/System.Reflection.Emit.Tests.csproj | 2 + 2 files changed, 44 insertions(+) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs index 5b7911bed07f88..b4f3b23417e556 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs @@ -10,6 +10,7 @@ using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Loader; using Xunit; namespace System.Reflection.Emit.Tests @@ -626,6 +627,42 @@ public void SaveGenericTypeSignatureWithGenericParameter() } } + [Fact] + public void SaveInterfaceOverrideWithCustomModifier() + { + using (TempDirectory dir = new()) + { + AssemblyName name = new("TestAssembly"); + PersistedAssemblyBuilder assemblyBuilder = AssemblySaveTools.PopulateAssemblyBuilder(name); + ModuleBuilder mb = assemblyBuilder.DefineDynamicModule("My Module"); + TypeBuilder tb = mb.DefineType("IMethodWithModifiersImpl", TypeAttributes.Class | TypeAttributes.Public); + tb.AddInterfaceImplementation(typeof(IMethodWithModifiers)); + MethodBuilder m = tb.DefineMethod("IMethodWithModifiers.Run", + MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + CallingConventions.Standard, + returnType: typeof(void), + returnTypeRequiredCustomModifiers: [], + returnTypeOptionalCustomModifiers: [], + parameterTypes: [typeof(int).MakeByRefType()], + parameterTypeRequiredCustomModifiers: [[typeof(InAttribute)]], + parameterTypeOptionalCustomModifiers: [[]]); + // The method's parameter has a modreq; make sure it is added to the override's signature. + // See https://github.com/dotnet/runtime/issues/123857 + tb.DefineMethodOverride(m, typeof(IMethodWithModifiers).GetMethod(nameof(IMethodWithModifiers.Run))); + var fooMethodParameter = m.DefineParameter(1, ParameterAttributes.In, "x"); + fooMethodParameter.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, [])); + m.GetILGenerator().Emit(OpCodes.Ret); + tb.CreateType(); + string assemblyPath = Path.Combine(dir.Path, $"{name.Name}.dll"); + assemblyBuilder.Save(assemblyPath); + + // Load the assembly and check that loading the type does not throw. + AssemblyLoadContext alc = new(nameof(SaveInterfaceOverrideWithCustomModifier), isCollectible: true); + Assembly loadedAsm = alc.LoadFromAssemblyPath(assemblyPath); + _ = loadedAsm.GetType(tb.Name, throwOnError: true); + } + } + [Fact] public void SaveMultipleGenericTypeParametersToEnsureSortingWorks() { @@ -983,6 +1020,11 @@ public interface IOneMethod object Func(string a, short b); } + public interface IMethodWithModifiers + { + void Run(in int x); + } + public struct EmptyStruct { } diff --git a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj index 0552a0ae8f6473..e3f5faa3ed1e9e 100644 --- a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj +++ b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj @@ -132,9 +132,11 @@ + + From dba646f86b28757c71a5872b6bbce0dd341bd5d3 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Tue, 3 Feb 2026 02:23:59 +0200 Subject: [PATCH 03/14] Address Copilot feedback. --- .../AssemblySaveTypeBuilderTests.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs index b4f3b23417e556..a756ace80e2184 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs @@ -649,17 +649,24 @@ public void SaveInterfaceOverrideWithCustomModifier() // The method's parameter has a modreq; make sure it is added to the override's signature. // See https://github.com/dotnet/runtime/issues/123857 tb.DefineMethodOverride(m, typeof(IMethodWithModifiers).GetMethod(nameof(IMethodWithModifiers.Run))); - var fooMethodParameter = m.DefineParameter(1, ParameterAttributes.In, "x"); - fooMethodParameter.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, [])); + ParameterBuilder pb = m.DefineParameter(1, ParameterAttributes.In, "x"); + pb.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, [])); m.GetILGenerator().Emit(OpCodes.Ret); tb.CreateType(); string assemblyPath = Path.Combine(dir.Path, $"{name.Name}.dll"); assemblyBuilder.Save(assemblyPath); - // Load the assembly and check that loading the type does not throw. AssemblyLoadContext alc = new(nameof(SaveInterfaceOverrideWithCustomModifier), isCollectible: true); - Assembly loadedAsm = alc.LoadFromAssemblyPath(assemblyPath); - _ = loadedAsm.GetType(tb.Name, throwOnError: true); + try + { + // Load the assembly and check that loading the type does not throw. + Assembly loadedAsm = alc.LoadFromAssemblyPath(assemblyPath); + _ = loadedAsm.GetType(tb.Name, throwOnError: true); + } + finally + { + alc.Unload(); + } } } From 829914c6051d549f6f28f98232bf504101d03388 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Wed, 4 Feb 2026 04:04:24 +0200 Subject: [PATCH 04/14] Make the interface method's signature more complex. --- .../AssemblySaveTypeBuilderTests.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs index a756ace80e2184..ec4d26b53475a0 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs @@ -637,18 +637,20 @@ public void SaveInterfaceOverrideWithCustomModifier() ModuleBuilder mb = assemblyBuilder.DefineDynamicModule("My Module"); TypeBuilder tb = mb.DefineType("IMethodWithModifiersImpl", TypeAttributes.Class | TypeAttributes.Public); tb.AddInterfaceImplementation(typeof(IMethodWithModifiers)); + MethodInfo mRun = typeof(IMethodWithModifiers).GetMethod(nameof(IMethodWithModifiers.Run))!; MethodBuilder m = tb.DefineMethod("IMethodWithModifiers.Run", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, CallingConventions.Standard, returnType: typeof(void), returnTypeRequiredCustomModifiers: [], returnTypeOptionalCustomModifiers: [], - parameterTypes: [typeof(int).MakeByRefType()], - parameterTypeRequiredCustomModifiers: [[typeof(InAttribute)]], - parameterTypeOptionalCustomModifiers: [[]]); + // typeof(delegate*) does not copy the modreq, so get the modified type from the interface method. + parameterTypes: [typeof(int).MakeByRefType(), mRun.GetParameters()[1].GetModifiedParameterType()], + parameterTypeRequiredCustomModifiers: [[typeof(InAttribute)], []], + parameterTypeOptionalCustomModifiers: [[], []]); // The method's parameter has a modreq; make sure it is added to the override's signature. // See https://github.com/dotnet/runtime/issues/123857 - tb.DefineMethodOverride(m, typeof(IMethodWithModifiers).GetMethod(nameof(IMethodWithModifiers.Run))); + tb.DefineMethodOverride(m, mRun); ParameterBuilder pb = m.DefineParameter(1, ParameterAttributes.In, "x"); pb.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, [])); m.GetILGenerator().Emit(OpCodes.Ret); @@ -1029,7 +1031,7 @@ public interface IOneMethod public interface IMethodWithModifiers { - void Run(in int x); + unsafe void Run(in int x, delegate* f); } public struct EmptyStruct From df3ae75bf9a07818d8aa0651f1676083c723a83c Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Sat, 7 Feb 2026 23:22:24 +0200 Subject: [PATCH 05/14] Simplify test. --- .../AssemblySaveTypeBuilderTests.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs index ec4d26b53475a0..aaabeda44dcab0 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs @@ -641,13 +641,12 @@ public void SaveInterfaceOverrideWithCustomModifier() MethodBuilder m = tb.DefineMethod("IMethodWithModifiers.Run", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, CallingConventions.Standard, - returnType: typeof(void), - returnTypeRequiredCustomModifiers: [], - returnTypeOptionalCustomModifiers: [], - // typeof(delegate*) does not copy the modreq, so get the modified type from the interface method. - parameterTypes: [typeof(int).MakeByRefType(), mRun.GetParameters()[1].GetModifiedParameterType()], - parameterTypeRequiredCustomModifiers: [[typeof(InAttribute)], []], - parameterTypeOptionalCustomModifiers: [[], []]); + returnType: mRun.ReturnParameter.GetModifiedParameterType(), + returnTypeRequiredCustomModifiers: null, + returnTypeOptionalCustomModifiers: null, + parameterTypes: mRun.GetParameters().Select(x => x.GetModifiedParameterType()).ToArray(), + parameterTypeRequiredCustomModifiers: null, + parameterTypeOptionalCustomModifiers: null); // The method's parameter has a modreq; make sure it is added to the override's signature. // See https://github.com/dotnet/runtime/issues/123857 tb.DefineMethodOverride(m, mRun); From 85c837ac9f01315b653dea8c24d7e6a1e4574ea8 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Mon, 9 Feb 2026 02:59:37 +0200 Subject: [PATCH 06/14] Fix test failures in Mono. The logic of `GetTypeParameter` follows the comment above more closely. Also make `TypeSignature` readonly. --- .../AssemblySaveTypeBuilderTests.cs | 2 +- .../System/Reflection/ModifiedType.Mono.cs | 21 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs index aaabeda44dcab0..b2068251c8f1a9 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs @@ -915,7 +915,7 @@ public void ConsumeFunctionPointerMembers() // public unsafe class Container // { // public static delegate* Method; - // + // // public static int Add(int a, int b) => a + b; // public static void Init() => Method = &Add; // } diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs index 81a0d098d04682..294b5c316c8cf3 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; + namespace System.Reflection { internal partial class ModifiedType @@ -19,11 +21,11 @@ internal partial class ModifiedType /// volatile delegate* unmanaged[Cdecl]<int> fptrField2; /// NOTE: In scenario 3) the SignatureHolderInfo has higher priority for retrieving field data (like custom modifiers) /// - internal struct TypeSignature + internal readonly struct TypeSignature { internal readonly RuntimeType? SignatureHolderType; internal readonly object? SignatureHolderInfo; - internal int ParameterIndex; + internal readonly int ParameterIndex; internal TypeSignature(RuntimeType signatureHolderType, int parameterIndex) { @@ -39,8 +41,9 @@ internal TypeSignature(object signatureHolderInfo, int parameterIndex) ParameterIndex = parameterIndex; } - internal TypeSignature(RuntimeType signatureHolderType, object signatureHolderInfo, int parameterIndex) + internal TypeSignature(RuntimeType? signatureHolderType, object? signatureHolderInfo, int parameterIndex) { + Debug.Assert(signatureHolderType is not null || signatureHolderInfo is not null); SignatureHolderType = signatureHolderType; SignatureHolderInfo = signatureHolderInfo; ParameterIndex = parameterIndex; @@ -118,18 +121,12 @@ internal Type GetTypeParameter(Type unmodifiedType, int index) } else { + var parentSignatureHolderInfo = _typeSignature.SignatureHolderInfo; if (parentUnmodifiedType.IsFunctionPointer) { - var parentSignatureHolderType = _typeSignature.SignatureHolderType ?? - throw new Exception($"Parent's {nameof(_typeSignature.SignatureHolderType)} cannot be null"); - childTypeSignature = new TypeSignature(parentSignatureHolderType, index); - } - else - { - var parentSignatureHolderInfo = _typeSignature.SignatureHolderInfo ?? - throw new Exception($"Parent's {nameof(_typeSignature.SignatureHolderInfo)} cannot be null"); - childTypeSignature = new TypeSignature(parentSignatureHolderInfo, index); + parentSignatureHolderInfo = null; } + childTypeSignature = new TypeSignature(_typeSignature.SignatureHolderType, parentSignatureHolderInfo, index); } return Create(childUnmodifiedType, childTypeSignature); From 47f81130a5c19b045e15eaea0e86d68941a6b746 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Mon, 9 Feb 2026 21:41:06 +0200 Subject: [PATCH 07/14] Fix another test failure. The `ParameterInfo` object contains the index on its own; we shouldn't pass it to the `TypeSignature`'s `ParameterIndex`, because it gets used as index to generic parameter. --- .../AssemblySaveILGeneratorTests.cs | 6 +++--- .../src/System/Reflection/ModifiedType.Mono.cs | 14 ++++++++------ .../src/System/Reflection/RuntimeParameterInfo.cs | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs index 9c7f21ed239990..c4a4cf0c4ed2d8 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers.Binary; using System.Collections.Generic; @@ -2638,9 +2638,9 @@ public void ReferenceNestedGenericTypeWithConstructedTypeBuilderParameterInIL() TypeBuilder nestedItem = type.DefineNestedType("ItemInfo", TypeAttributes.NestedPublic); GenericTypeParameterBuilder itemParam = nestedItem.DefineGenericParameters(genParams)[0]; TypeBuilder nestedSector = type.DefineNestedType("Sector", TypeAttributes.NestedPublic); - GenericTypeParameterBuilder nestedParam = nestedSector.DefineGenericParameters(genParams)[0]; + GenericTypeParameterBuilder sectorParam = nestedSector.DefineGenericParameters(genParams)[0]; - Type nestedOfT = nestedItem.MakeGenericType(nestedParam); + Type nestedOfT = nestedItem.MakeGenericType(sectorParam); Type parent = typeof(HashSet<>).MakeGenericType(nestedOfT); nestedSector.SetParent(parent); diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs index 294b5c316c8cf3..334e768098f9f3 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs @@ -23,9 +23,11 @@ internal partial class ModifiedType /// internal readonly struct TypeSignature { - internal readonly RuntimeType? SignatureHolderType; - internal readonly object? SignatureHolderInfo; - internal readonly int ParameterIndex; + public readonly RuntimeType? SignatureHolderType; + public readonly object? SignatureHolderInfo; + // The index of the function pointer (starting from 1, 0 being the return type) + // or generic parameter for which we are retrieving modifiers. + public readonly int ParameterIndex; internal TypeSignature(RuntimeType signatureHolderType, int parameterIndex) { @@ -91,15 +93,15 @@ internal bool TryGetCustomModifiersFromSignatureHolderType(bool required, out Ty } } - internal static Type Create(Type sourceType, object sourceTypeInfo, int parameterIndex = 0) + internal static Type Create(Type sourceType, object sourceTypeInfo) { var unmodifiedType = (RuntimeType)sourceType; TypeSignature typeSignature; if (unmodifiedType.IsFunctionPointer) - typeSignature = new TypeSignature(unmodifiedType, sourceTypeInfo, parameterIndex); + typeSignature = new TypeSignature(unmodifiedType, sourceTypeInfo, -1); else - typeSignature = new TypeSignature(sourceTypeInfo, parameterIndex); + typeSignature = new TypeSignature(sourceTypeInfo, -1); return Create(unmodifiedType, typeSignature); } diff --git a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeParameterInfo.cs b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeParameterInfo.cs index d28cdfc3469c1b..3d5bbec0bcf732 100644 --- a/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeParameterInfo.cs +++ b/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeParameterInfo.cs @@ -425,6 +425,6 @@ internal void SetAttributes(ParameterAttributes attributes) internal Type[] GetCustomModifiersFromModifiedType(bool optional, int genericArgumentPosition) => GetTypeModifiers(ParameterType, Member, Position, optional, genericArgumentPosition) ?? Type.EmptyTypes; - public override Type GetModifiedParameterType() => ModifiedType.Create(ParameterType, this, PositionImpl + 1); + public override Type GetModifiedParameterType() => ModifiedType.Create(ParameterType, this); } } From 2d86cbbffed159ec0362e181ca6bdf98765c9dbc Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Tue, 10 Feb 2026 03:00:45 +0200 Subject: [PATCH 08/14] Clean-up test; use `TestAssemblyLoadContext` like other tests, and add back BOM. --- .../AssemblySaveILGeneratorTests.cs | 2 +- .../AssemblySaveTypeBuilderTests.cs | 22 ++++++------------- .../tests/System.Reflection.Emit.Tests.csproj | 1 - 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs index c4a4cf0c4ed2d8..1fbe07a55785c7 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveILGeneratorTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers.Binary; using System.Collections.Generic; diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs index b2068251c8f1a9..78d99ede4066eb 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs @@ -10,7 +10,6 @@ using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Loader; using Xunit; namespace System.Reflection.Emit.Tests @@ -630,7 +629,7 @@ public void SaveGenericTypeSignatureWithGenericParameter() [Fact] public void SaveInterfaceOverrideWithCustomModifier() { - using (TempDirectory dir = new()) + using (TempFile file = TempFile.Create()) { AssemblyName name = new("TestAssembly"); PersistedAssemblyBuilder assemblyBuilder = AssemblySaveTools.PopulateAssemblyBuilder(name); @@ -654,20 +653,13 @@ public void SaveInterfaceOverrideWithCustomModifier() pb.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, [])); m.GetILGenerator().Emit(OpCodes.Ret); tb.CreateType(); - string assemblyPath = Path.Combine(dir.Path, $"{name.Name}.dll"); - assemblyBuilder.Save(assemblyPath); + assemblyBuilder.Save(file.Path); - AssemblyLoadContext alc = new(nameof(SaveInterfaceOverrideWithCustomModifier), isCollectible: true); - try - { - // Load the assembly and check that loading the type does not throw. - Assembly loadedAsm = alc.LoadFromAssemblyPath(assemblyPath); - _ = loadedAsm.GetType(tb.Name, throwOnError: true); - } - finally - { - alc.Unload(); - } + TestAssemblyLoadContext context = new(); + // Load the assembly and check that loading the type does not throw. + Assembly loadedAsm = context.LoadFromAssemblyPath(file.Path); + _ = loadedAsm.GetType(tb.Name, throwOnError: true); + context.Unload(); } } diff --git a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj index e3f5faa3ed1e9e..39e12cd5e07f24 100644 --- a/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj +++ b/src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj @@ -132,7 +132,6 @@ - From 058e44c54825862d08d0544cafc5d56a40fc9c5e Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Wed, 11 Feb 2026 02:58:27 +0200 Subject: [PATCH 09/14] Update src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs Co-authored-by: Jan Kotas --- .../PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs index 78d99ede4066eb..264e36452bdeec 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs @@ -646,8 +646,6 @@ public void SaveInterfaceOverrideWithCustomModifier() parameterTypes: mRun.GetParameters().Select(x => x.GetModifiedParameterType()).ToArray(), parameterTypeRequiredCustomModifiers: null, parameterTypeOptionalCustomModifiers: null); - // The method's parameter has a modreq; make sure it is added to the override's signature. - // See https://github.com/dotnet/runtime/issues/123857 tb.DefineMethodOverride(m, mRun); ParameterBuilder pb = m.DefineParameter(1, ParameterAttributes.In, "x"); pb.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, [])); From b6218c12f9fa1825f26d8e1a2ebb5c3e1533397a Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Mon, 16 Feb 2026 02:27:17 +0200 Subject: [PATCH 10/14] Test more scenarios of passing custom modifiers to `DefineMethod`. --- .../AssemblySaveTypeBuilderTests.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs index 264e36452bdeec..fd20ead73a8f11 100644 --- a/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs +++ b/src/libraries/System.Reflection.Emit/tests/PersistedAssemblyBuilder/AssemblySaveTypeBuilderTests.cs @@ -634,22 +634,40 @@ public void SaveInterfaceOverrideWithCustomModifier() AssemblyName name = new("TestAssembly"); PersistedAssemblyBuilder assemblyBuilder = AssemblySaveTools.PopulateAssemblyBuilder(name); ModuleBuilder mb = assemblyBuilder.DefineDynamicModule("My Module"); + TypeBuilder tb = mb.DefineType("IMethodWithModifiersImpl", TypeAttributes.Class | TypeAttributes.Public); tb.AddInterfaceImplementation(typeof(IMethodWithModifiers)); - MethodInfo mRun = typeof(IMethodWithModifiers).GetMethod(nameof(IMethodWithModifiers.Run))!; + MethodInfo mRun = typeof(IMethodWithModifiers).GetMethod(nameof(IMethodWithModifiers.Run)); MethodBuilder m = tb.DefineMethod("IMethodWithModifiers.Run", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, CallingConventions.Standard, returnType: mRun.ReturnParameter.GetModifiedParameterType(), returnTypeRequiredCustomModifiers: null, returnTypeOptionalCustomModifiers: null, - parameterTypes: mRun.GetParameters().Select(x => x.GetModifiedParameterType()).ToArray(), - parameterTypeRequiredCustomModifiers: null, + // The first parameter will have modreqs specified from parameterTypeRequiredCustomModifiers, and the second from parameterTypes. + parameterTypes: mRun.GetParameters().Select((x, i) => i == 0 ? x.ParameterType : x.GetModifiedParameterType()).ToArray(), + parameterTypeRequiredCustomModifiers: [[typeof(InAttribute)], null], parameterTypeOptionalCustomModifiers: null); tb.DefineMethodOverride(m, mRun); ParameterBuilder pb = m.DefineParameter(1, ParameterAttributes.In, "x"); - pb.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: [])!, [])); + pb.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: []), [])); m.GetILGenerator().Emit(OpCodes.Ret); + MethodInfo mRun2 = typeof(IMethodWithModifiers).GetMethod(nameof(IMethodWithModifiers.Run2)); + MethodBuilder m2 = tb.DefineMethod("IMethodWithModifiers.Run2", + MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + CallingConventions.Standard, + returnType: mRun2.ReturnParameter.GetModifiedParameterType(), + returnTypeRequiredCustomModifiers: null, + returnTypeOptionalCustomModifiers: null, + parameterTypes: mRun2.GetParameters().Select(x => x.GetModifiedParameterType()).ToArray(), + // Test that passing null gets modreqs from the parameter types. + parameterTypeRequiredCustomModifiers: null, + parameterTypeOptionalCustomModifiers: null); + tb.DefineMethodOverride(m2, mRun2); + ParameterBuilder pb2 = m2.DefineParameter(1, ParameterAttributes.In, "x"); + pb2.SetCustomAttribute(new CustomAttributeBuilder(typeof(IsReadOnlyAttribute).GetConstructor(types: []), [])); + m2.GetILGenerator().Emit(OpCodes.Ret); + tb.CreateType(); assemblyBuilder.Save(file.Path); @@ -1021,6 +1039,7 @@ public interface IOneMethod public interface IMethodWithModifiers { unsafe void Run(in int x, delegate* f); + void Run2(in int x); } public struct EmptyStruct From d2259c7f01e0c5a78fa6a1a17909f48a70560b6e Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Tue, 17 Feb 2026 09:25:44 +0200 Subject: [PATCH 11/14] Generalize encoding of custom modifiers. --- .../System/Reflection/Emit/SignatureHelper.cs | 58 +++---------------- 1 file changed, 9 insertions(+), 49 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs index d211d181dc769c..6df8689f443258 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs @@ -30,8 +30,7 @@ internal static BlobBuilder GetFieldSignature(Type fieldType, Type[] requiredCus { BlobBuilder fieldSignature = new(); FieldTypeEncoder encoder = new BlobEncoder(fieldSignature).Field(); - WriteReturnTypeCustomModifiers(encoder.CustomModifiers(), requiredCustomModifiers, optionalCustomModifiers, module); - WriteSignatureForType(encoder.Type(), fieldType, module); + WriteSignatureForType(encoder.Type(), fieldType, module, requiredCustomModifiers, optionalCustomModifiers); return fieldSignature; } @@ -85,17 +84,8 @@ internal static BlobBuilder GetMethodSignature(ModuleBuilderImpl module, Type[]? new BlobEncoder(methodSignature).MethodSignature(convention, genParamCount, isInstance). Parameters(paramsLength, out ReturnTypeEncoder retEncoder, out ParametersEncoder parEncoder); - WriteReturnTypeCustomModifiers(retEncoder.CustomModifiers(), returnTypeRequiredModifiers ?? returnType?.GetRequiredCustomModifiers(), - returnTypeOptionalModifiers ?? returnType?.GetOptionalCustomModifiers(), module); - - if (returnType != null && returnType != module.GetTypeFromCoreAssembly(CoreTypeId.Void)) - { - WriteSignatureForType(retEncoder.Type(), returnType, module); - } - else - { - retEncoder.Void(); - } + returnType ??= module.GetTypeFromCoreAssembly(CoreTypeId.Void); + WriteSignatureForType(retEncoder.Type(), returnType, module, returnTypeRequiredModifiers, returnTypeOptionalModifiers); WriteParametersSignature(module, parameters, parEncoder, parameterRequiredModifiers, parameterOptionalModifiers); @@ -124,20 +114,6 @@ internal static Type[] GetParameterTypes(ParameterInfo[] parameterInfos) return parameterTypes; } - private static void WriteReturnTypeCustomModifiers(CustomModifiersEncoder encoder, - Type[]? requiredModifiers, Type[]? optionalModifiers, ModuleBuilderImpl module) - { - if (requiredModifiers != null) - { - WriteCustomModifiers(encoder, requiredModifiers, isOptional: false, module); - } - - if (optionalModifiers != null) - { - WriteCustomModifiers(encoder, optionalModifiers, isOptional: true, module); - } - } - private static void WriteCustomModifiers(CustomModifiersEncoder encoder, Type[] customModifiers, bool isOptional, ModuleBuilderImpl module) { // GetOptionalCustomModifiers and GetRequiredCustomModifiers return modifiers in reverse order @@ -160,14 +136,12 @@ private static void WriteParametersSignature(ModuleBuilderImpl module, Type[]? p Type[] modreqs = requiredModifiers != null && requiredModifiers.Length > i && requiredModifiers[i] is { } mr ? mr : parameters[i].GetRequiredCustomModifiers(); - WriteCustomModifiers(encoder.CustomModifiers(), modreqs, isOptional: false, module); Type[] modopts = optionalModifiers != null && optionalModifiers.Length > i && optionalModifiers[i] is { } mo ? mo : parameters[i].GetOptionalCustomModifiers(); - WriteCustomModifiers(encoder.CustomModifiers(), modopts, isOptional: true, module); - WriteSignatureForType(encoder.Type(), parameters[i], module); + WriteSignatureForType(encoder.Type(), parameters[i], module, modreqs, modopts); } } } @@ -180,15 +154,16 @@ internal static BlobBuilder GetPropertySignature(PropertyBuilderImpl property, M PropertySignature(isInstanceProperty: property.CallingConventions.HasFlag(CallingConventions.HasThis)). Parameters(property.ParameterTypes == null ? 0 : property.ParameterTypes.Length, out ReturnTypeEncoder retType, out ParametersEncoder paramEncoder); - WriteReturnTypeCustomModifiers(retType.CustomModifiers(), property._returnTypeRequiredCustomModifiers, property._returnTypeOptionalCustomModifiers, module); - WriteSignatureForType(retType.Type(), property.PropertyType, module); + WriteSignatureForType(retType.Type(), property.PropertyType, module, property._returnTypeRequiredCustomModifiers, property._returnTypeOptionalCustomModifiers); WriteParametersSignature(module, property.ParameterTypes, paramEncoder, property._parameterTypeRequiredCustomModifiers, property._parameterTypeOptionalCustomModifiers); return propertySignature; } - private static void WriteSignatureForType(SignatureTypeEncoder signature, Type type, ModuleBuilderImpl module) + private static void WriteSignatureForType(SignatureTypeEncoder signature, Type type, ModuleBuilderImpl module, Type[]? requiredModifiers = null, Type[]? optionalModifiers = null) { + WriteCustomModifiers(signature.CustomModifiers(), requiredModifiers ?? type.GetRequiredCustomModifiers(), isOptional: false, module); + WriteCustomModifiers(signature.CustomModifiers(), optionalModifiers ?? type.GetOptionalCustomModifiers(), isOptional: true, module); if (type.IsArray) { Type elementType = type.GetElementType()!; @@ -201,7 +176,7 @@ private static void WriteSignatureForType(SignatureTypeEncoder signature, Type t { signature.Array(out SignatureTypeEncoder elTypeSignature, out ArrayShapeEncoder arrayEncoder); WriteSimpleSignature(elTypeSignature, elementType, module); - arrayEncoder.Shape(type.GetArrayRank(), ImmutableArray.Create(), ImmutableArray.Create(new int[rank])); + arrayEncoder.Shape(type.GetArrayRank(), [], default); } } else if (type.IsPointer) @@ -288,26 +263,11 @@ private static void WriteSignatureForFunctionPointerType(SignatureTypeEncoder si MethodSignatureEncoder sigEncoder = signature.FunctionPointer(callConv, attribs); sigEncoder.Parameters(paramTypes.Length, out ReturnTypeEncoder retTypeEncoder, out ParametersEncoder paramsEncoder); - CustomModifiersEncoder retModifiersEncoder = retTypeEncoder.CustomModifiers(); - - if (returnType.GetOptionalCustomModifiers() is Type[] retModOpts) - WriteCustomModifiers(retModifiersEncoder, retModOpts, isOptional: true, module); - - if (returnType.GetRequiredCustomModifiers() is Type[] retModReqs) - WriteCustomModifiers(retModifiersEncoder, retModReqs, isOptional: false, module); - WriteSignatureForType(retTypeEncoder.Type(), returnType, module); foreach (Type paramType in paramTypes) { ParameterTypeEncoder paramEncoder = paramsEncoder.AddParameter(); - CustomModifiersEncoder paramModifiersEncoder = paramEncoder.CustomModifiers(); - - if (paramType.GetOptionalCustomModifiers() is Type[] paramModOpts) - WriteCustomModifiers(paramModifiersEncoder, paramModOpts, isOptional: true, module); - - if (paramType.GetRequiredCustomModifiers() is Type[] paramModReqs) - WriteCustomModifiers(paramModifiersEncoder, paramModReqs, isOptional: false, module); WriteSignatureForType(paramEncoder.Type(), paramType, module); } From f30fd17aca89e8a56175421a483a250110ee574a Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Tue, 17 Feb 2026 09:32:35 +0200 Subject: [PATCH 12/14] Allow custom modifiers in the types of complex array signatures. They are allowed per the amendments to ECMA-335. --- .../src/System/Reflection/Emit/SignatureHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs index 6df8689f443258..1540e9b69bd899 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs @@ -175,7 +175,7 @@ private static void WriteSignatureForType(SignatureTypeEncoder signature, Type t else { signature.Array(out SignatureTypeEncoder elTypeSignature, out ArrayShapeEncoder arrayEncoder); - WriteSimpleSignature(elTypeSignature, elementType, module); + WriteSignatureForType(elTypeSignature, elementType, module); arrayEncoder.Shape(type.GetArrayRank(), [], default); } } From 971512fa99fdbade1f898f2ff723f5b32d06a8a4 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 17 Feb 2026 09:10:38 -0800 Subject: [PATCH 13/14] Apply suggestions from code review --- .../src/System/Reflection/Emit/SignatureHelper.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs index 1540e9b69bd899..8d25e78ec8e78e 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs @@ -133,13 +133,9 @@ private static void WriteParametersSignature(ModuleBuilderImpl module, Type[]? p { ParameterTypeEncoder encoder = parameterEncoder.AddParameter(); - Type[] modreqs = requiredModifiers != null && requiredModifiers.Length > i && requiredModifiers[i] is { } mr - ? mr - : parameters[i].GetRequiredCustomModifiers(); + Type[]? modreqs = (requiredModifiers != null && requiredModifiers.Length > i) ? requiredModifiers[i] : null; - Type[] modopts = optionalModifiers != null && optionalModifiers.Length > i && optionalModifiers[i] is { } mo - ? mo - : parameters[i].GetOptionalCustomModifiers(); + Type[]? modopts = (optionalModifiers != null && optionalModifiers.Length > i) ? optionalModifiers[i] : null; WriteSignatureForType(encoder.Type(), parameters[i], module, modreqs, modopts); } From 5da2ab599cef48a683f7edd2ccac4f191f191317 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 17 Feb 2026 09:11:03 -0800 Subject: [PATCH 14/14] Update src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs --- .../src/System/Reflection/Emit/SignatureHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs index 8d25e78ec8e78e..b6f465eecd4374 100644 --- a/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs +++ b/src/libraries/System.Reflection.Emit/src/System/Reflection/Emit/SignatureHelper.cs @@ -134,7 +134,6 @@ private static void WriteParametersSignature(ModuleBuilderImpl module, Type[]? p ParameterTypeEncoder encoder = parameterEncoder.AddParameter(); Type[]? modreqs = (requiredModifiers != null && requiredModifiers.Length > i) ? requiredModifiers[i] : null; - Type[]? modopts = (optionalModifiers != null && optionalModifiers.Length > i) ? optionalModifiers[i] : null; WriteSignatureForType(encoder.Type(), parameters[i], module, modreqs, modopts);