Repository navigation
Write custom modifiers in PersistedAssemblyBuilder's order in CoreCLR's AssemblyBuilder - #136699
Conversation
CoreCLR's RuntimeAssemblyBuilder took each custom modifier array in signature order and wrote the optional modifiers first. PersistedAssemblyBuilder takes each array in the order GetRequiredCustomModifiers and GetOptionalCustomModifiers return it, the reverse of signature order, and writes the required modifiers first. CoreCLR's SignatureHelper now writes PersistedAssemblyBuilder's order, for module signatures and for DynamicMethod signatures alike. DispatchProxy and MEF's MetadataViewGenerator no longer reverse the arrays they read from reflection. Mono's AssemblyBuilder is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @steveisok, @dotnet/area-system-reflection-emit |
Name the flag that selects required or optional custom modifiers isOptional, as CustomModifiersEncoder.AddModifier does: in the SignatureHelper writers, in ModifiedType.GetCustomModifiers on each runtime, and in CoreCLR's Signature.GetCustomModifiers. The Signature_GetCustomModifiersAtOffset QCall keeps its required flag, which it compares with the required byte of internal modifiers. State the modifier order in the writers' loops, and inline the property's custom modifiers in MetadataViewGenerator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Mono's |
There was a problem hiding this comment.
🟡 Changes recommended
Unconditionally removing modifier reversals breaks metadata-view creation on older targets such as .NET 10 by causing getter signatures to mismatch their interfaces.
1 open issue
What changed in this PR
This PR aligns CoreCLR’s custom-modifier emission with PersistedAssemblyBuilder, so reflection-provided modifier arrays can be passed directly to the emitter.
Changes:
- Emit required modifiers first and reverse each modifier array in regular and dynamic signatures.
- Remove modifier-array reversals from DispatchProxy and MEF, and standardize internal boolean parameters as
isOptional. - Add ordering and integration tests, update validation expectations, and mark affected tests as skipped on Mono.
| File | Description |
|---|---|
| src/mono/System.Private.CoreLib/src/System/Reflection/ModifiedType.Mono.cs | Adopt isOptional semantics while preserving modifier selection. |
| src/libraries/System.Reflection.Emit/tests/TypeBuilder/TypeBuilderCustomModifierOrder.cs | Test runtime and persisted signature ordering. |
| src/libraries/System.Reflection.Emit/tests/System.Reflection.Emit.Tests.csproj | Include the new ordering tests. |
| src/libraries/System.Reflection.Emit.ILGeneration/tests/SignatureHelper/SignatureHelperGetPropertySigHelper.cs | Expect required-modifier validation first. |
| src/libraries/System.Reflection.Emit.ILGeneration/tests/ILGenerator/Emit4Tests.cs | Test dynamic-signature modifier validation order. |
| src/libraries/System.Reflection.DispatchProxy/tests/DispatchProxyTests.cs | Add persisted-interface coverage and share assertions. |
| src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs | Pass reflected modifier arrays without reversal. |
| src/libraries/System.Private.CoreLib/src/System/Reflection/ModifiedType.cs | Use named isOptional arguments. |
| src/libraries/System.ComponentModel.Composition/tests/System/ComponentModel/Composition/MetadataViewProviderTests.cs | Test metadata views with persisted custom modifiers. |
| src/libraries/System.ComponentModel.Composition/src/System/ComponentModel/Composition/MetadataViewGenerator.cs | Remove getter modifier-array reversals. |
| src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs | Translate isOptional to the native required-modifier flag. |
| src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs | Update property modifier-selection calls. |
| src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeParameterInfo.CoreCLR.cs | Update parameter modifier-selection calls. |
| src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs | Update field modifier-selection calls. |
| src/coreclr/System.Private.CoreLib/src/System/Reflection/ModifiedType.CoreCLR.cs | Forward isOptional through signature lookup. |
| src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs | Implement the new ordering in both emission paths. |
| src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/ModifiedType.NativeAot.cs | Preserve modifier filtering with isOptional semantics. |
Review effort: Balanced
MEF also ships net10.0 and net11.0 builds, which run on runtimes whose AssemblyBuilder still takes custom modifiers of one kind in the reverse of the order reflection returns them in. MetadataViewGenerator reverses them again when Environment.Version is below 12; the runtime, not the target framework, decides, since those builds also run on .NET 12. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Environment.Version.Major < 12 is always false where the net12.0 build runs, so put the field and its use under !NET12_0_OR_GREATER. The reversal compiles out of net12.0 once the SDK defines that symbol, and can be deleted once net12.0 is the oldest target framework. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pcshrosbree
left a comment
There was a problem hiding this comment.
This PR also changes the custom-modifier order in the MemberRefs that RuntimeModuleBuilder emits for calls to generic methods and to methods of generic types. I have a test request and a breaking-change case inline.
On CoreCLR, a call to a loaded generic method, or to a method of a loaded generic type, in another module gets a member reference whose signature the runtime builder writes from the target's reflected custom modifiers. With the old order, such a call to a PersistedAssemblyBuilder method with two required modifiers threw MissingMethodException. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Added When you commit this breaking change:
Tagging @dotnet/compat for awareness of the breaking change. |
|
Breaking change doc created dotnet/docs#56739 |
Conflict in SignatureHelper.cs with dotnet#136699, resolved by keeping its AddCustomModifiers and dropping this PR's AddReflectedCustomModifiers and AddCustomModifier, which did the same. A modified type's own modifiers now go through AddCustomModifiers, whose messages name the modifier and whose paramName may be null for them. The comments in the resolved code no longer give PersistedAssemblyBuilder as the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DefineMethod_ModifiedParameterTypes_CopiesSignature defines its source method with modifier arrays. Since dotnet#136699, DefineMethod takes each array in the order reflection returns it, so the test now expects the source's and the copy's modifiers in the arrays' order. A modified List<int> from Type.MakeModifiedSignatureType was written without its modifiers on .NET 11 RC1. A test of its own now checks that its modifier comes back, and fails without this PR. The constructed-generic ldtoken test now says what its first-byte check shows. The comment on the worker call now says that the other overload would also write modifiers an array replaced, and a comment sentence that restated the code is gone. The worker calls name lastWasGenericInst, and the module check uses "is not null", as the repository's C# guidelines ask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

As asked in #136450 (r4414670923, r4418172093): CoreCLR's
AssemblyBuilderandPersistedAssemblyBuilderwrote custom modifiers in different orders. With required[IsConst, IsVolatile]and optional[IsLong, IsBoxed]passed toDefineFieldorDefineMethodon .NET 10.0.14:PersistedAssemblyBuildertakes each array in the orderGetRequiredCustomModifiersandGetOptionalCustomModifiersreturn it, the reverse of signature order, and writes the required modifiers first. CoreCLR'sSignatureHelpertook each array in signature order and wrote the optional modifiers first.SignatureHelpernow writesPersistedAssemblyBuilder's order. That includes member references to a loaded target in another module, built from its reflected modifiers.DispatchProxyand MEF'sMetadataViewGeneratorreversed the arrays they read from reflection to fit the old order; they no longer do, except that MEF'snet10.0andnet11.0builds still reverse them when running on .NET 10 or 11.ComImportInteropInterfaceDetailsStrategyalready passed the arrays it reads from reflection unreversed, so its forwarders now write custom modifiers in the same order asDispatchProxy; that path isn't tested here.This is a breaking change for callers relying on the old modifier order. Member references built from reflected modifiers now match only signatures listing required modifiers first. On CoreCLR, for targets whose parameter is
modopt(IsLong) modreq(IsVolatile) int32, calls to a generic method, a vararg method, or a method or constructor of a generic type, andldftnto a generic method, bound on .NET 10.0.14 and now throwMissingMethodException, and MEF'snet12.0build can't create a metadata view whose property has that type. As r4418172093 notes, the C# compiler does not generate signatures that would be affected. This needs thebreaking-changelabel.Mono's
AssemblyBuilderis a separate implementation that this change does not touch, so on Mono .NET 12,DispatchProxyand MEF now pass it two modifiers of one kind in the reverse of their old order. As r4407294021 suggests for Mono, theDispatchProxytests this affects are markedSkipOnMono. So are the two signature tests inTypeBuilderCustomModifierOrder.cs: Mono's builder writes each array in the order given, and itsModule.ResolveSignatureresolves only standalone signature tokens.New tests:
TypeBuilderCustomModifierOrder.cs(both builders writePersistedAssemblyBuilder's signature above, andAssemblyBuildercalls a generic method and a method of a generic type written byPersistedAssemblyBuilder),DispatchProxyTests.csandMetadataViewProviderTests.cs(a proxy and a metadata view for interfaces written byPersistedAssemblyBuilder). On CoreCLR, they fail without the change, exceptPersistedAssemblyBuilder_CustomModifierOrder.SignatureHelperGetPropertySigHelper.cschanges because required modifiers are now validated first.Note
AI-generated, written at my direction and reviewed by me before posting.
Tests ran on Windows 11 x64, Release; the signatures above are from .NET 10.0.14 on Windows.