From 76e9c90e2598adc4960028b5acab541926ad08ca Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Thu, 11 Sep 2025 10:08:09 -0400 Subject: [PATCH 01/11] Add MetadataUpdateDeletedAttribute This attribute is intended to be emitted only by Roslyn. Its intent is to be used by reflection as a filter to "remove" types and members that have been deleted during a hot reload session. Implements https://github.com/dotnet/runtime/issues/118903 --- .../CompatibilitySuppressions.xml | 34 ++++++++++ .../src/CompatibilitySuppressions.xml | 64 ++++++++++++++----- .../src/CompatibilitySuppressions.xml | 1 - .../System.Private.CoreLib.Shared.projitems | 1 + .../MetadataUpdateDeletedAttribute.cs | 12 ++++ .../ref/System.Runtime.Loader.cs | 4 ++ 6 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MetadataUpdateDeletedAttribute.cs diff --git a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml new file mode 100644 index 00000000000000..e68afe1915d899 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml @@ -0,0 +1,34 @@ + + + + + CP0001 + T:Internal.Console + + + CP0002 + F:System.Resources.ResourceManager.BaseNameField + + + CP0002 + F:System.Resources.ResourceSet.Reader + + + CP0002 + M:System.String.Trim(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) + + + CP0008 + T:System.Collections.BitArray + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + + \ No newline at end of file diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index b85912e6cf1ff0..14c102ad3c7d33 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -1,6 +1,10 @@  + + CP0001 + T:Internal.Console + CP0001 T:Internal.Metadata.NativeFormat.ArraySignature @@ -53,14 +57,6 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantBooleanValueHandle - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValue - - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle - CP0001 T:Internal.Metadata.NativeFormat.ConstantByteArray @@ -117,6 +113,14 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantEnumArrayHandle + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValue + + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle + CP0001 T:Internal.Metadata.NativeFormat.ConstantHandleArray @@ -653,6 +657,10 @@ CP0001 T:Internal.Metadata.NativeFormat.UInt64Collection + + CP0001 + T:Internal.NativeFormat.TypeHashingAlgorithms + CP0001 T:Internal.Reflection.Core.AssemblyBinder @@ -725,6 +733,10 @@ CP0001 T:Internal.TypeSystem.LockFreeReaderHashtable`2 + + CP0001 + T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 + CP0001 T:System.Diagnostics.DebugAnnotations @@ -735,11 +747,11 @@ CP0001 - T:System.MDArray + T:System.FieldHandleInfo CP0001 - T:System.FieldHandleInfo + T:System.MDArray CP0001 @@ -806,8 +818,16 @@ T:System.Runtime.CompilerServices.StaticClassConstructionContext - CP0001 - T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 + CP0002 + F:System.Resources.ResourceManager.BaseNameField + + + CP0002 + F:System.Resources.ResourceSet.Reader + + + CP0002 + M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) CP0002 @@ -815,14 +835,24 @@ CP0002 - M:System.Threading.Lock.#ctor(System.Boolean) + M:System.String.Trim(System.ReadOnlySpan{System.Char}) CP0002 - M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) + M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) - CP0001 - T:Internal.NativeFormat.TypeHashingAlgorithms + CP0002 + M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.Threading.Lock.#ctor(System.Boolean) + + + CP0008 + T:System.Collections.BitArray + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll - + \ No newline at end of file diff --git a/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml b/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml index d6c64b01d2cd7a..67ada8e90c3092 100644 --- a/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml +++ b/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml @@ -1,7 +1,6 @@  - CP0001 T:System.Collections.Specialized.ListDictionary.DictionaryNode diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index a25bcdfc984f28..6183664c49d229 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -886,6 +886,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MetadataUpdateDeletedAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MetadataUpdateDeletedAttribute.cs new file mode 100644 index 00000000000000..37ec2c4df563db --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MetadataUpdateDeletedAttribute.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices; + +/// +/// This attribute is emitted by Roslyn when a type is deleted during a +/// hot reload session. The intent is to use it as a filter in places we cannot +/// delete the type outright. +/// +[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] +public sealed class MetadataUpdateDeletedAttribute : Attribute; diff --git a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs index a272e40c33db39..7be705ed5fb5ff 100644 --- a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs +++ b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs @@ -33,6 +33,10 @@ namespace System.Runtime.CompilerServices public sealed class CreateNewOnMetadataUpdateAttribute : System.Attribute { } + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + public sealed class MetadataUpdateDeletedAttribute : Attribute + { + } [AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple=false, Inherited=false)] public class MetadataUpdateOriginalTypeAttribute : Attribute From 49f2f0cc8965d55f3fb439c81a9b6cee9fca1964 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Thu, 11 Sep 2025 15:39:36 -0400 Subject: [PATCH 02/11] Add ability to filter members in reflection and test --- .../CompatibilitySuppressions.xml | 34 ---------- .../RuntimeTypeMetadataUpdateHandler.cs | 2 + .../src/System/RuntimeType.CoreCLR.cs | 21 +++++- .../src/CompatibilitySuppressions.xml | 64 +++++-------------- .../src/CompatibilitySuppressions.xml | 1 + .../src/System/RuntimeType.cs | 2 + .../ReflectionDeleteMethod.cs | 14 ++++ .../ReflectionDeleteMethod_v1.cs | 15 +++++ ...yUpdate.Test.ReflectionDeleteMethod.csproj | 11 ++++ .../deltascript.json | 6 ++ .../tests/ApplyUpdateTest.cs | 22 +++++++ .../tests/System.Runtime.Loader.Tests.csproj | 1 + 12 files changed, 111 insertions(+), 82 deletions(-) delete mode 100644 src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod.cs create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod_v1.cs create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod.csproj create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/deltascript.json diff --git a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml deleted file mode 100644 index e68afe1915d899..00000000000000 --- a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - CP0001 - T:Internal.Console - - - CP0002 - F:System.Resources.ResourceManager.BaseNameField - - - CP0002 - F:System.Resources.ResourceSet.Reader - - - CP0002 - M:System.String.Trim(System.ReadOnlySpan{System.Char}) - - - CP0002 - M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) - - - CP0002 - M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) - - - CP0008 - T:System.Collections.BitArray - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - \ No newline at end of file diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs index e45e84efb60f01..4d7204ef0357a1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs @@ -16,6 +16,8 @@ internal static class RuntimeTypeMetadataUpdateHandler [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Clearing the caches on a Type isn't affected if a Type is trimmed, or has any of its members trimmed.")] public static void ClearCache(Type[]? types) { + RuntimeType.FilterDeletedMembers = true; + if (RequiresClearingAllTypes(types)) { // TODO: This should ideally be in a QCall in the runtime. As written here: diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index c4de3a72051b92..68ce100a5e861a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -382,7 +382,26 @@ private unsafe T[] GetListByName(string name, Span utf8Name, MemberListTyp Debug.Fail("Invalid CacheType"); break; } - return (T[])list; + + if (RuntimeType.FilterDeletedMembers) + { + T[] sourceItems = (T[])list; + ListBuilder nonDeletedList = default; + + for (int i = 0; i < sourceItems.Length; i++) + { + if (!sourceItems[i].IsDefined(typeof(System.Runtime.CompilerServices.MetadataUpdateDeletedAttribute), true)) + { + nonDeletedList.Add(sourceItems[i]); + } + } + + return nonDeletedList.ToArray(); + } + else + { + return (T[])list; + } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 14c102ad3c7d33..b85912e6cf1ff0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -1,10 +1,6 @@  - - CP0001 - T:Internal.Console - CP0001 T:Internal.Metadata.NativeFormat.ArraySignature @@ -57,6 +53,14 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantBooleanValueHandle + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValue + + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle + CP0001 T:Internal.Metadata.NativeFormat.ConstantByteArray @@ -113,14 +117,6 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantEnumArrayHandle - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValue - - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle - CP0001 T:Internal.Metadata.NativeFormat.ConstantHandleArray @@ -657,10 +653,6 @@ CP0001 T:Internal.Metadata.NativeFormat.UInt64Collection - - CP0001 - T:Internal.NativeFormat.TypeHashingAlgorithms - CP0001 T:Internal.Reflection.Core.AssemblyBinder @@ -733,10 +725,6 @@ CP0001 T:Internal.TypeSystem.LockFreeReaderHashtable`2 - - CP0001 - T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 - CP0001 T:System.Diagnostics.DebugAnnotations @@ -747,11 +735,11 @@ CP0001 - T:System.FieldHandleInfo + T:System.MDArray CP0001 - T:System.MDArray + T:System.FieldHandleInfo CP0001 @@ -818,16 +806,8 @@ T:System.Runtime.CompilerServices.StaticClassConstructionContext - CP0002 - F:System.Resources.ResourceManager.BaseNameField - - - CP0002 - F:System.Resources.ResourceSet.Reader - - - CP0002 - M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) + CP0001 + T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 CP0002 @@ -835,24 +815,14 @@ CP0002 - M:System.String.Trim(System.ReadOnlySpan{System.Char}) - - - CP0002 - M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) - - - CP0002 - M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) + M:System.Threading.Lock.#ctor(System.Boolean) CP0002 - M:System.Threading.Lock.#ctor(System.Boolean) + M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) - CP0008 - T:System.Collections.BitArray - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll + CP0001 + T:Internal.NativeFormat.TypeHashingAlgorithms - \ No newline at end of file + diff --git a/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml b/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml index 67ada8e90c3092..d6c64b01d2cd7a 100644 --- a/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml +++ b/src/libraries/System.Collections.Specialized/src/CompatibilitySuppressions.xml @@ -1,6 +1,7 @@  + CP0001 T:System.Collections.Specialized.ListDictionary.DictionaryNode diff --git a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs index 933e84836c4ce1..4ba6d9afe1f7f3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs @@ -1024,5 +1024,7 @@ private CheckValueStatus TryChangeType(ref object? value, ref bool copyBack) return CheckValueStatus.ArgumentException; } + + internal static bool FilterDeletedMembers { get; set; } } } diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod.cs new file mode 100644 index 00000000000000..e8521b873091de --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class ReflectionDeleteMethod + { + public string Id { get; set;} + + public string Name { get; set; } + + public string FullName { get; set; } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod_v1.cs new file mode 100644 index 00000000000000..e105244321c705 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod_v1.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class ReflectionDeleteMethod + { + public string Id { get; set;} + + public string Name { get; set; } + + [System.Runtime.CompilerServices.MetadataUpdateDeleted] + public string FullName { get; set; } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod.csproj new file mode 100644 index 00000000000000..905174f0c7184c --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod.csproj @@ -0,0 +1,11 @@ + + + System.Runtime.Loader.Tests + $(NetCoreAppCurrent) + true + deltascript.json + + + + + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/deltascript.json new file mode 100644 index 00000000000000..51d1fd75472e2c --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/deltascript.json @@ -0,0 +1,6 @@ +{ + "changes": [ + {"document": "ReflectionDeleteMethod.cs", "update": "ReflectionDeleteMethod_v1.cs"} + ] +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index 98475ed8a5e21e..621aa1c9339e74 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -19,6 +19,28 @@ namespace System.Reflection.Metadata [Collection(nameof(DisableParallelization))] public class ApplyUpdateTest { + [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] + void DeleteMethodTest() + { + ApplyUpdateUtil.TestCase(static () => + { + var assm = typeof (ApplyUpdate.Test.ReflectionDeleteMethod).Assembly; + + var t = assm.GetType("System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod"); + Assert.NotNull(t); + + var mi = t.GetProperty("FullName"); + Assert.NotNull(mi); + + ApplyUpdateUtil.ApplyUpdate(assm); + ApplyUpdateUtil.ClearAllReflectionCaches(); + + // FullName should be filtered out now + mi = t.GetProperty("FullName"); + Assert.Null(mi); + }); + } + [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] [ActiveIssue("https://github.com/dotnet/runtime/issues/54617", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))] void StaticMethodBodyUpdate() diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj index f9f54c521f0240..8c9b97d81ac548 100644 --- a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj @@ -74,6 +74,7 @@ + From da6eb20f2cb41c93d3052fbfaeec0c730a5871fa Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Mon, 29 Sep 2025 10:29:54 -0700 Subject: [PATCH 03/11] Feedback and tests --- .../RuntimeTypeMetadataUpdateHandler.cs | 7 +- .../Reflection/RuntimeCustomAttributeData.cs | 2 +- .../src/System/RuntimeHandles.cs | 4 +- .../src/System/RuntimeType.CoreCLR.cs | 94 +++++++++++---- .../MetadataUpdateDeletedAttribute.cs | 5 +- .../src/System/RuntimeType.cs | 2 - .../ReflectionDeleteMember.cs | 58 +++++++++ .../ReflectionDeleteMember_v1.cs | 57 +++++++++ ...Update.Test.ReflectionDeleteMember.csproj} | 2 +- .../deltascript.json | 6 + .../ReflectionDeleteMethod.cs | 14 --- .../ReflectionDeleteMethod_v1.cs | 15 --- .../deltascript.json | 6 - .../tests/ApplyUpdateTest.cs | 112 ++++++++++++++++-- .../tests/System.Runtime.Loader.Tests.csproj | 7 +- 15 files changed, 309 insertions(+), 82 deletions(-) create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/ReflectionDeleteMember.cs create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/ReflectionDeleteMember_v1.cs rename src/libraries/System.Runtime.Loader/tests/ApplyUpdate/{System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod.csproj => System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember.csproj} (85%) create mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/deltascript.json delete mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod.cs delete mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod_v1.cs delete mode 100644 src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/deltascript.json diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs index 4d7204ef0357a1..dfa29f1bd315bc 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs @@ -11,12 +11,17 @@ namespace System.Reflection.Metadata /// Metadata update handler used to clear a Type's reflection cache in response to a metadata update notification. internal static class RuntimeTypeMetadataUpdateHandler { + /// + /// True to enable filtering deleted members from Reflection results. Set after the first metadata update. + /// + internal static bool FilterDeletedMembers { get; private set; } + /// Clear type caches in response to an update notification. /// The specific types to be cleared, or null to clear everything. [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Clearing the caches on a Type isn't affected if a Type is trimmed, or has any of its members trimmed.")] public static void ClearCache(Type[]? types) { - RuntimeType.FilterDeletedMembers = true; + FilterDeletedMembers = true; if (RequiresClearingAllTypes(types)) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs index 96ecf9cf0cdcbb..c3d3fef4031a69 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs @@ -1407,7 +1407,7 @@ internal static bool IsAttributeDefined(RuntimeModule decoratedModule, int decor return IsCustomAttributeDefined(decoratedModule, decoratedMetadataToken, null, attributeCtorToken, false); } - private static bool IsCustomAttributeDefined( + internal static bool IsCustomAttributeDefined( RuntimeModule decoratedModule, int decoratedMetadataToken, RuntimeType? attributeFilterType) { return IsCustomAttributeDefined(decoratedModule, decoratedMetadataToken, attributeFilterType, 0, false); diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index e59be872265d3b..3eb6015001c706 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1118,7 +1118,7 @@ internal static int GetSlot(IRuntimeMethodInfo method) } [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int GetMethodDef(RuntimeMethodHandleInternal method); + internal static extern int GetMethodDef(RuntimeMethodHandleInternal method); internal static int GetMethodDef(IRuntimeMethodInfo method) { @@ -1569,7 +1569,7 @@ internal static ref byte GetFieldDataReference(ref byte target, RuntimeFieldInfo private static unsafe partial void GetFieldDataReference(IntPtr fieldDesc, ObjectHandleOnStack target, ByteRefOnStack fieldDataRef); [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int GetToken(IntPtr fieldDesc); + internal static extern int GetToken(IntPtr fieldDesc); internal static int GetToken(RtFieldInfo field) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 68ce100a5e861a..a39d1dc8196cca 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -6,7 +6,9 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using System.Reflection; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -148,10 +150,18 @@ private readonly struct Filter private readonly MdUtf8String m_name; private readonly MemberListType m_listType; - public unsafe Filter(byte* pUtf8Name, int cUtf8Name, MemberListType listType) + // If non-null, filter out members that have this attribute defined on them. + private readonly RuntimeType? m_metadataUpdateDeletedAttribute; + + // The module that defines the members being filtered, or null if not needed. + private readonly RuntimeModule? m_declaringModule; + + public unsafe Filter(byte* pUtf8Name, int cUtf8Name, MemberListType listType, RuntimeType? metadataUpdateDeletedAttribute, RuntimeModule? declaringModule) { m_name = new MdUtf8String(pUtf8Name, cUtf8Name); m_listType = listType; + m_metadataUpdateDeletedAttribute = metadataUpdateDeletedAttribute; + m_declaringModule = declaringModule; } public bool Match(MdUtf8String name) @@ -179,6 +189,16 @@ public bool RequiresStringComparison() } public bool CaseSensitive() => m_listType == MemberListType.CaseSensitive; + + public bool FilterMetadataUpdateDeletedMembers + => MetadataUpdater.IsSupported && m_metadataUpdateDeletedAttribute != null; + + public bool IsMetadataUpdateDeleted(int memberToken) + { + Debug.Assert(m_metadataUpdateDeletedAttribute != null); + Debug.Assert(m_declaringModule != null); + return CustomAttribute.IsCustomAttributeDefined(m_declaringModule, memberToken, m_metadataUpdateDeletedAttribute); + } } private sealed class MemberInfoCache where T : MemberInfo @@ -350,9 +370,13 @@ private unsafe T[] GetListByName(string name, Span utf8Name, MemberListTyp if (name.Length != 0) Encoding.UTF8.GetBytes(name, utf8Name); + var (metadataUpdateDeletedAttributeRuntimeType, declaringModule) = MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers + ? (typeof(System.Runtime.CompilerServices.MetadataUpdateDeletedAttribute).UnderlyingSystemType as RuntimeType, ReflectedType.GetRuntimeModule()) + : default; + fixed (byte* pUtf8Name = utf8Name) { - Filter filter = new Filter(pUtf8Name, utf8Name.Length, listType); + Filter filter = new Filter(pUtf8Name, utf8Name.Length, listType, metadataUpdateDeletedAttributeRuntimeType, declaringModule); object list = null!; switch (cacheType) @@ -382,26 +406,7 @@ private unsafe T[] GetListByName(string name, Span utf8Name, MemberListTyp Debug.Fail("Invalid CacheType"); break; } - - if (RuntimeType.FilterDeletedMembers) - { - T[] sourceItems = (T[])list; - ListBuilder nonDeletedList = default; - - for (int i = 0; i < sourceItems.Length; i++) - { - if (!sourceItems[i].IsDefined(typeof(System.Runtime.CompilerServices.MetadataUpdateDeletedAttribute), true)) - { - nonDeletedList.Add(sourceItems[i]); - } - } - - return nonDeletedList.ToArray(); - } - else - { - return (T[])list; - } + return (T[])list; } } @@ -624,6 +629,11 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) continue; #endregion + if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(RuntimeMethodHandle.GetMethodDef(methodHandle))) + { + continue; + } + #region Calculate Binding Flags bool isPublic = (methodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public; bool isStatic = (methodAttributes & MethodAttributes.Static) != 0; @@ -634,7 +644,7 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) RuntimeMethodHandleInternal instantiatedHandle = RuntimeMethodHandle.GetStubIfNeeded(methodHandle, declaringType, null); RuntimeMethodInfo runtimeMethodInfo = new RuntimeMethodInfo( - instantiatedHandle, declaringType, m_runtimeTypeCache, methodAttributes, bindingFlags, null); + instantiatedHandle, declaringType, m_runtimeTypeCache, methodAttributes, bindingFlags, null); list.Add(runtimeMethodInfo); #endregion @@ -704,6 +714,12 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) #endregion + // Filter out deleted method before setting override state, so that a deleted override in a subclass does not hide override in an ancestor. + if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(RuntimeMethodHandle.GetMethodDef(methodHandle))) + { + continue; + } + #region Continue if this is a virtual and is already overridden if (isVirtual) { @@ -738,7 +754,7 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) RuntimeMethodHandleInternal instantiatedHandle = RuntimeMethodHandle.GetStubIfNeeded(methodHandle, declaringType, null); RuntimeMethodInfo runtimeMethodInfo = new RuntimeMethodInfo( - instantiatedHandle, declaringType, m_runtimeTypeCache, methodAttributes, bindingFlags, null); + instantiatedHandle, declaringType, m_runtimeTypeCache, methodAttributes, bindingFlags, null); list.Add(runtimeMethodInfo); #endregion @@ -783,6 +799,11 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) (methodAttributes & MethodAttributes.Abstract) == 0 && (methodAttributes & MethodAttributes.Virtual) == 0); + if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(RuntimeMethodHandle.GetMethodDef(methodHandle))) + { + continue; + } + #region Calculate Binding Flags bool isPublic = (methodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public; bool isStatic = (methodAttributes & MethodAttributes.Static) != 0; @@ -894,6 +915,11 @@ private void PopulateRtFields(Filter filter, continue; } + if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(RuntimeFieldHandle.GetToken(handle))) + { + continue; + } + #region Calculate Binding Flags bool isPublic = fieldAccess == FieldAttributes.Public; bool isStatic = (fieldAttributes & FieldAttributes.Static) != 0; @@ -904,8 +930,7 @@ private void PopulateRtFields(Filter filter, if (needsStaticFieldForGeneric && isStatic) runtimeFieldHandle = RuntimeFieldHandle.GetStaticFieldForGenericType(runtimeFieldHandle, declaringType); - RuntimeFieldInfo runtimeFieldInfo = - new RtFieldInfo(runtimeFieldHandle, declaringType, m_runtimeTypeCache, bindingFlags); + var runtimeFieldInfo = new RtFieldInfo(runtimeFieldHandle, declaringType, m_runtimeTypeCache, bindingFlags); list.Add(runtimeFieldInfo); } @@ -955,6 +980,11 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref continue; } + if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(tkField)) + { + continue; + } + #region Calculate Binding Flags bool isPublic = fieldAccess == FieldAttributes.Public; bool isStatic = (fieldAttributes & FieldAttributes.Static) != 0; @@ -1183,6 +1213,11 @@ private void PopulateEvents( continue; } + if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(tkEvent)) + { + continue; + } + RuntimeEventInfo eventInfo = new RuntimeEventInfo( tkEvent, declaringType, m_runtimeTypeCache, out bool isPrivate); @@ -1295,6 +1330,12 @@ private void PopulateProperties( continue; } + // Filter out deleted property before updating usedSlots, so that a deleted override in a subclass does not hide override in an ancestor. + if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(tkProperty)) + { + continue; + } + RuntimePropertyInfo propertyInfo = new RuntimePropertyInfo( tkProperty, declaringType, m_runtimeTypeCache, out bool isPrivate); @@ -1477,6 +1518,7 @@ private MemberInfoCache GetMemberCache(ref MemberInfoCache? m_cache) return existingCache; } + #endregion #region Internal Members diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MetadataUpdateDeletedAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MetadataUpdateDeletedAttribute.cs index 37ec2c4df563db..32690b0fbece53 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MetadataUpdateDeletedAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MetadataUpdateDeletedAttribute.cs @@ -4,9 +4,8 @@ namespace System.Runtime.CompilerServices; /// -/// This attribute is emitted by Roslyn when a type is deleted during a -/// hot reload session. The intent is to use it as a filter in places we cannot -/// delete the type outright. +/// This attribute is emitted by the compiler when a metadata entity is deleted during a +/// Hot Reload session. /// [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] public sealed class MetadataUpdateDeletedAttribute : Attribute; diff --git a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs index 4ba6d9afe1f7f3..933e84836c4ce1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs @@ -1024,7 +1024,5 @@ private CheckValueStatus TryChangeType(ref object? value, ref bool copyBack) return CheckValueStatus.ArgumentException; } - - internal static bool FilterDeletedMembers { get; set; } } } diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/ReflectionDeleteMember.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/ReflectionDeleteMember.cs new file mode 100644 index 00000000000000..8b858b8d109357 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/ReflectionDeleteMember.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class ReflectionDeleteMember + { + public int F1; + + public ReflectionDeleteMember() { } + public ReflectionDeleteMember(int arg) { F1 = arg; } + + public virtual int P1 { get; set; } + public virtual void M1() { } + + public virtual int P2 { get; } + public virtual void M2() { } + + public int P3 { get; set; } + public void M3() { } + public event Action E1 { add { } remove { } } + } + + public class ReflectionDeleteMember_Derived : ReflectionDeleteMember + { + public new int F1; + + public override int P1 { get; set; } + public override void M1() { } + + public override int P2 { get; } + public override void M2() { } + + public new int P3 { get; set; } + public new void M3() { } + public new event Action E1 { add { } remove { } } + } + + public class ReflectionDeleteMember_Derived2 : ReflectionDeleteMember_Derived + { + public override int P1 { get; set; } + public override void M1() { } + + public override int P2 { get; } + public override void M2() { } + } + + public interface IReflectionDeleteMember + { + int P1 { get; set; } + int P2 { get; } + void M1(); + void M2(); + event Action E1; + event Action E2; + } +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/ReflectionDeleteMember_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/ReflectionDeleteMember_v1.cs new file mode 100644 index 00000000000000..6d47382c684fef --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/ReflectionDeleteMember_v1.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Reflection.Metadata.ApplyUpdate.Test +{ + public class ReflectionDeleteMember + { + public int F1; // delete: not supported by Roslyn + + public ReflectionDeleteMember() { } + // delete: public ReflectionDeleteMember(int arg) { F1 = arg; } + + public virtual int P1 { get; set; } + public virtual void M1() { } + + public virtual int P2 { get; } + public virtual void M2() { } + + public int P3 { get; set; } + public void M3() { } + public event Action E1 { add { } remove { } } + } + + public class ReflectionDeleteMember_Derived : ReflectionDeleteMember + { + public new int F1; + + public override int P1 { get; set; } // delete: not supported by Roslyn + public override void M1() { } // delete: not supported by Roslyn + + public override int P2 { get; } + public override void M2() { } + + // delete: public new int P3 { get; set; } + // delete: public new void M3() { } + // delete: public new event Action E1 { add { } remove { } } + } + + public class ReflectionDeleteMember_Derived2 : ReflectionDeleteMember_Derived + { + public override int P1 { get; set; } + public override void M1() { } + + public override int P2 { get; } // delete: not supported by Roslyn + public override void M2() { } // delete: not supported by Roslyn + } + + public interface IReflectionDeleteMember + { + int P1 { get; set; } + int P2 { get; } // delete: not supported by Roslyn + void M1(); + void M2(); // delete: not supported by Roslyn + event Action E1; + event Action E2; // delete: not supported by Roslyn + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod.csproj b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember.csproj similarity index 85% rename from src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod.csproj rename to src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember.csproj index 905174f0c7184c..fbd7d2323ed2b5 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod.csproj +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember.csproj @@ -6,6 +6,6 @@ deltascript.json - + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/deltascript.json new file mode 100644 index 00000000000000..acd60b7974dfe8 --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember/deltascript.json @@ -0,0 +1,6 @@ +{ + "changes": [ + {"document": "ReflectionDeleteMember.cs", "update": "ReflectionDeleteMember_v1.cs"} + ] +} + diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod.cs deleted file mode 100644 index e8521b873091de..00000000000000 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Reflection.Metadata.ApplyUpdate.Test -{ - public class ReflectionDeleteMethod - { - public string Id { get; set;} - - public string Name { get; set; } - - public string FullName { get; set; } - } -} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod_v1.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod_v1.cs deleted file mode 100644 index e105244321c705..00000000000000 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/ReflectionDeleteMethod_v1.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Reflection.Metadata.ApplyUpdate.Test -{ - public class ReflectionDeleteMethod - { - public string Id { get; set;} - - public string Name { get; set; } - - [System.Runtime.CompilerServices.MetadataUpdateDeleted] - public string FullName { get; set; } - } -} diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/deltascript.json b/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/deltascript.json deleted file mode 100644 index 51d1fd75472e2c..00000000000000 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdate/System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod/deltascript.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "changes": [ - {"document": "ReflectionDeleteMethod.cs", "update": "ReflectionDeleteMethod_v1.cs"} - ] -} - diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index 621aa1c9339e74..53997cbdf4ee69 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.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.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -19,26 +21,116 @@ namespace System.Reflection.Metadata [Collection(nameof(DisableParallelization))] public class ApplyUpdateTest { + [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] void DeleteMethodTest() { ApplyUpdateUtil.TestCase(static () => { - var assm = typeof (ApplyUpdate.Test.ReflectionDeleteMethod).Assembly; - - var t = assm.GetType("System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMethod"); - Assert.NotNull(t); + var assm = typeof (ApplyUpdate.Test.ReflectionDeleteMember).Assembly; - var mi = t.GetProperty("FullName"); - Assert.NotNull(mi); + var type_ReflectionDeleteMember = assm.GetType("System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember"); + var type_ReflectionDeleteMember_Derived = assm.GetType("System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember_Derived"); + var type_ReflectionDeleteMember_Derived2 = assm.GetType("System.Reflection.Metadata.ApplyUpdate.Test.ReflectionDeleteMember_Derived2"); + var type_IReflectionDeleteMember = assm.GetType("System.Reflection.Metadata.ApplyUpdate.Test.IReflectionDeleteMember"); ApplyUpdateUtil.ApplyUpdate(assm); ApplyUpdateUtil.ClearAllReflectionCaches(); - // FullName should be filtered out now - mi = t.GetProperty("FullName"); - Assert.Null(mi); + Assert.Equal( + [ + "get_P1 : ReflectionDeleteMember", + "set_P1 : ReflectionDeleteMember", + "M1 : ReflectionDeleteMember", + "get_P2 : ReflectionDeleteMember", + "M2 : ReflectionDeleteMember", + "get_P3 : ReflectionDeleteMember", + "set_P3 : ReflectionDeleteMember", + "M3 : ReflectionDeleteMember", + "add_E1 : ReflectionDeleteMember", + "remove_E1 : ReflectionDeleteMember", + "GetType : Object", + "ToString : Object", + "Equals : Object", + "GetHashCode : Object", + ".ctor(0) : ReflectionDeleteMember", + "P1 : ReflectionDeleteMember", + "P2 : ReflectionDeleteMember", + "P3 : ReflectionDeleteMember", + "E1 : ReflectionDeleteMember", + "F1 : ReflectionDeleteMember", + ], Inspect(type_ReflectionDeleteMember_Derived2)); + + Assert.Equal( + [ + "get_P1 : ReflectionDeleteMember_Derived", + "set_P1 : ReflectionDeleteMember_Derived", + "M1 : ReflectionDeleteMember_Derived", + "get_P2 : ReflectionDeleteMember_Derived", + "M2 : ReflectionDeleteMember_Derived", + "get_P3 : ReflectionDeleteMember", + "set_P3 : ReflectionDeleteMember", + "M3 : ReflectionDeleteMember", + "add_E1 : ReflectionDeleteMember", + "remove_E1 : ReflectionDeleteMember", + "GetType : Object", + "ToString : Object", + "Equals : Object", + "GetHashCode : Object", + ".ctor(0) : ReflectionDeleteMember_Derived", + "P1 : ReflectionDeleteMember_Derived", + "P2 : ReflectionDeleteMember_Derived", + "P3 : ReflectionDeleteMember", + "E1 : ReflectionDeleteMember", + "F1 : ReflectionDeleteMember_Derived", + "F1 : ReflectionDeleteMember", + ], Inspect(type_ReflectionDeleteMember_Derived2)); + + Assert.Equal( + [ + "get_P1 : ReflectionDeleteMember_Derived2", + "set_P1 : ReflectionDeleteMember_Derived2", + "M1 : ReflectionDeleteMember_Derived2", + "get_P2 : ReflectionDeleteMember_Derived2", + "M2 : ReflectionDeleteMember_Derived2", + "get_P3 : ReflectionDeleteMember", + "set_P3 : ReflectionDeleteMember", + "M3 : ReflectionDeleteMember", + "add_E1 : ReflectionDeleteMember", + "remove_E1 : ReflectionDeleteMember", + "GetType : Object", + "ToString : Object", + "Equals : Object", + "GetHashCode : Object", + ".ctor(0) : ReflectionDeleteMember_Derived2", + "P1 : ReflectionDeleteMember_Derived2", + "P2 : ReflectionDeleteMember_Derived2", + "P3 : ReflectionDeleteMember", + "E1 : ReflectionDeleteMember", + "F1 : ReflectionDeleteMember_Derived", + "F1 : ReflectionDeleteMember", + ], Inspect(type_ReflectionDeleteMember_Derived2)); + + Assert.Equal( + [ + "get_P1 : IReflectionDeleteMember", + "set_P1 : IReflectionDeleteMember", + "get_P2 : IReflectionDeleteMember", + "M1 : IReflectionDeleteMember", + "M2 : IReflectionDeleteMember", + "add_E1 : IReflectionDeleteMember", + "remove_E1 : IReflectionDeleteMember", + "add_E2 : IReflectionDeleteMember", + "remove_E2 : IReflectionDeleteMember", + "P1 : IReflectionDeleteMember", + "P2 : IReflectionDeleteMember", + "E1 : IReflectionDeleteMember", + "E2 : IReflectionDeleteMember", + ], Inspect(type_ReflectionDeleteMember_Derived2)); }); + + static IEnumerable Inspect(Type type) + => type.GetMembers().Select(m => m is ConstructorInfo ctor ? $"{ctor.Name}({ctor.GetParameters().Length}) : {m.DeclaringType!.Name}" : m.Name); } [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] @@ -648,7 +740,7 @@ internal static Type CheckReflectedType(Assembly assm, Type[] allTypes, string n if (nestedIdx != -1) comparisonName = typeName.Substring(nestedIdx+1); Assert.True(comparisonName == ty.Name, $"{callerFilePath}:{callerLineNumber}: returned type has unexpected name '{ty.Name}' (expected: '{comparisonName}') in {callerMemberName}"); - Assert.True(ContainsTypeWithName (allTypes, fullName), $"{callerFilePath}:{callerLineNumber}: expected Assembly.GetTypes to contain '{fullName}', but it didn't in {callerMemberName}"); + Assert.True(ContainsTypeWithName (allTypes, fullName), $"{callerFilePath}:{callerLineNumber}: expected Assembly.GetTypes to contain '{fullName}', but it didn'type_ReflectionDeleteMember in {callerMemberName}"); if (moreChecks != null) moreChecks(ty); return ty; diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj index 8c9b97d81ac548..c557f7e3b4a05b 100644 --- a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj @@ -40,6 +40,11 @@ System.Reflection.Metadata.ApplyUpdate.Test. + + + + + @@ -74,7 +79,7 @@ - + From 3a6e60201c1733528cf75b9c7d7c3d1a5070e887 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Mon, 29 Sep 2025 11:22:46 -0700 Subject: [PATCH 04/11] More feedback --- .../RuntimeTypeMetadataUpdateHandler.cs | 4 + .../src/System/RuntimeType.CoreCLR.cs | 80 +++++++++---------- .../System.Private.CoreLib.Shared.projitems | 2 +- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs index dfa29f1bd315bc..9c54c27f28b41a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; +using System.Runtime.CompilerServices; [assembly: MetadataUpdateHandler(typeof(RuntimeTypeMetadataUpdateHandler))] @@ -16,6 +17,9 @@ internal static class RuntimeTypeMetadataUpdateHandler /// internal static bool FilterDeletedMembers { get; private set; } + internal static bool IsMetadataUpdateDeleted(RuntimeModule module, int memberToken) + => CustomAttribute.IsCustomAttributeDefined(module, memberToken, (RuntimeType)typeof(MetadataUpdateDeletedAttribute)); + /// Clear type caches in response to an update notification. /// The specific types to be cleared, or null to clear everything. [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Clearing the caches on a Type isn't affected if a Type is trimmed, or has any of its members trimmed.")] diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 0208cae71a5e5a..d14a7f0667d06a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -150,18 +150,10 @@ private readonly struct Filter private readonly MdUtf8String m_name; private readonly MemberListType m_listType; - // If non-null, filter out members that have this attribute defined on them. - private readonly RuntimeType? m_metadataUpdateDeletedAttribute; - - // The module that defines the members being filtered, or null if not needed. - private readonly RuntimeModule? m_declaringModule; - - public unsafe Filter(byte* pUtf8Name, int cUtf8Name, MemberListType listType, RuntimeType? metadataUpdateDeletedAttribute, RuntimeModule? declaringModule) + public unsafe Filter(byte* pUtf8Name, int cUtf8Name, MemberListType listType) { m_name = new MdUtf8String(pUtf8Name, cUtf8Name); m_listType = listType; - m_metadataUpdateDeletedAttribute = metadataUpdateDeletedAttribute; - m_declaringModule = declaringModule; } public bool Match(MdUtf8String name) @@ -189,16 +181,6 @@ public bool RequiresStringComparison() } public bool CaseSensitive() => m_listType == MemberListType.CaseSensitive; - - public bool FilterMetadataUpdateDeletedMembers - => MetadataUpdater.IsSupported && m_metadataUpdateDeletedAttribute != null; - - public bool IsMetadataUpdateDeleted(int memberToken) - { - Debug.Assert(m_metadataUpdateDeletedAttribute != null); - Debug.Assert(m_declaringModule != null); - return CustomAttribute.IsCustomAttributeDefined(m_declaringModule, memberToken, m_metadataUpdateDeletedAttribute); - } } private sealed class MemberInfoCache where T : MemberInfo @@ -370,13 +352,9 @@ private unsafe T[] GetListByName(string name, Span utf8Name, MemberListTyp if (name.Length != 0) Encoding.UTF8.GetBytes(name, utf8Name); - var (metadataUpdateDeletedAttributeRuntimeType, declaringModule) = MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers - ? (typeof(System.Runtime.CompilerServices.MetadataUpdateDeletedAttribute).UnderlyingSystemType as RuntimeType, ReflectedType.GetRuntimeModule()) - : default; - fixed (byte* pUtf8Name = utf8Name) { - Filter filter = new Filter(pUtf8Name, utf8Name.Length, listType, metadataUpdateDeletedAttributeRuntimeType, declaringModule); + Filter filter = new Filter(pUtf8Name, utf8Name.Length, listType); object list = null!; switch (cacheType) @@ -603,6 +581,8 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) RuntimeType declaringType = ReflectedType; Debug.Assert(declaringType != null); + RuntimeModule declaringModule = declaringType.GetRuntimeModule(); + if (declaringType.IsActualInterface) { #region IsInterface @@ -629,7 +609,9 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) continue; #endregion - if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(RuntimeMethodHandle.GetMethodDef(methodHandle))) + if (MetadataUpdater.IsSupported && + RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeMethodHandle.GetMethodDef(methodHandle))) { continue; } @@ -715,7 +697,9 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) #endregion // Filter out deleted method before setting override state, so that a deleted override in a subclass does not hide override in an ancestor. - if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(RuntimeMethodHandle.GetMethodDef(methodHandle))) + if (MetadataUpdater.IsSupported && + RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeMethodHandle.GetMethodDef(methodHandle))) { continue; } @@ -765,6 +749,7 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) #endregion } + GC.KeepAlive(declaringModule); return list.ToArray(); } @@ -778,6 +763,7 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) ListBuilder list = default; RuntimeType declaringType = ReflectedType; + RuntimeModule declaringModule = declaringType.GetRuntimeModule(); foreach (RuntimeMethodHandleInternal methodHandle in RuntimeTypeHandle.GetIntroducedMethods(declaringType)) { @@ -799,7 +785,9 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) (methodAttributes & MethodAttributes.Abstract) == 0 && (methodAttributes & MethodAttributes.Virtual) == 0); - if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(RuntimeMethodHandle.GetMethodDef(methodHandle))) + if (MetadataUpdater.IsSupported && + RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeMethodHandle.GetMethodDef(methodHandle))) { continue; } @@ -819,6 +807,7 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) list.Add(runtimeConstructorInfo); } + GC.KeepAlive(declaringModule); return list.ToArray(); } @@ -893,6 +882,7 @@ private void PopulateRtFields(Filter filter, bool needsStaticFieldForGeneric = declaringType.IsGenericType && !RuntimeTypeHandle.ContainsGenericVariables(declaringType); bool isInherited = declaringType != ReflectedType; + RuntimeModule declaringModule = declaringType.GetRuntimeModule(); foreach (IntPtr handle in fieldHandles) { @@ -915,7 +905,9 @@ private void PopulateRtFields(Filter filter, continue; } - if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(RuntimeFieldHandle.GetToken(handle))) + if (MetadataUpdater.IsSupported && + RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeFieldHandle.GetToken(handle))) { continue; } @@ -934,6 +926,8 @@ private void PopulateRtFields(Filter filter, list.Add(runtimeFieldInfo); } + + GC.KeepAlive(declaringModule); } private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref ListBuilder list) @@ -947,8 +941,8 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref if (MdToken.IsNullToken(tkDeclaringType)) return; - RuntimeModule module = declaringType.GetRuntimeModule(); - MetadataImport scope = module.MetadataImport; + RuntimeModule declaringModule = declaringType.GetRuntimeModule(); + MetadataImport scope = declaringModule.MetadataImport; scope.EnumFields(tkDeclaringType, out MetadataEnumResult tkFields); @@ -980,7 +974,9 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref continue; } - if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(tkField)) + if (MetadataUpdater.IsSupported && + RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeFieldHandle.GetToken(tkField))) { continue; } @@ -997,7 +993,7 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref list.Add(runtimeFieldInfo); } } - GC.KeepAlive(module); + GC.KeepAlive(declaringModule); } private void AddSpecialInterface( @@ -1193,8 +1189,8 @@ private void PopulateEvents( if (MdToken.IsNullToken(tkDeclaringType)) return; - RuntimeModule module = declaringType.GetRuntimeModule(); - MetadataImport scope = module.MetadataImport; + RuntimeModule declaringModule = declaringType.GetRuntimeModule(); + MetadataImport scope = declaringModule.MetadataImport; scope.EnumEvents(tkDeclaringType, out MetadataEnumResult tkEvents); @@ -1213,7 +1209,9 @@ private void PopulateEvents( continue; } - if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(tkEvent)) + if (MetadataUpdater.IsSupported && + RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeFieldHandle.GetToken(tkEvent))) { continue; } @@ -1245,7 +1243,7 @@ private void PopulateEvents( list.Add(eventInfo); } - GC.KeepAlive(module); + GC.KeepAlive(declaringModule); } private RuntimePropertyInfo[] PopulateProperties(Filter filter) @@ -1305,8 +1303,8 @@ private void PopulateProperties( if (MdToken.IsNullToken(tkDeclaringType)) return; - RuntimeModule module = declaringType.GetRuntimeModule(); - MetadataImport scope = module.MetadataImport; + RuntimeModule declaringModule = declaringType.GetRuntimeModule(); + MetadataImport scope = declaringModule.MetadataImport; scope.EnumProperties(tkDeclaringType, out MetadataEnumResult tkProperties); @@ -1331,7 +1329,9 @@ private void PopulateProperties( } // Filter out deleted property before updating usedSlots, so that a deleted override in a subclass does not hide override in an ancestor. - if (filter.FilterMetadataUpdateDeletedMembers && filter.IsMetadataUpdateDeleted(tkProperty)) + if (MetadataUpdater.IsSupported && + RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeFieldHandle.GetToken(tkProperty))) { continue; } @@ -1425,7 +1425,7 @@ private void PopulateProperties( list.Add(propertyInfo); } - GC.KeepAlive(module); + GC.KeepAlive(declaringModule); } #endregion diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 8e8568d865cb16..f3f528efd1b258 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2916,4 +2916,4 @@ - + \ No newline at end of file From 71a90f9a309a2e83e782b0486a4ed92e7cd8b439 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Mon, 29 Sep 2025 11:35:13 -0700 Subject: [PATCH 05/11] Fix typo --- .../System.Runtime.Loader/tests/ApplyUpdateTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index 53997cbdf4ee69..000366d65ba5a5 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -59,7 +59,7 @@ void DeleteMethodTest() "P3 : ReflectionDeleteMember", "E1 : ReflectionDeleteMember", "F1 : ReflectionDeleteMember", - ], Inspect(type_ReflectionDeleteMember_Derived2)); + ], Inspect(type_ReflectionDeleteMember)); Assert.Equal( [ @@ -84,7 +84,7 @@ void DeleteMethodTest() "E1 : ReflectionDeleteMember", "F1 : ReflectionDeleteMember_Derived", "F1 : ReflectionDeleteMember", - ], Inspect(type_ReflectionDeleteMember_Derived2)); + ], Inspect(type_ReflectionDeleteMember_Derived)); Assert.Equal( [ @@ -126,7 +126,7 @@ void DeleteMethodTest() "P2 : IReflectionDeleteMember", "E1 : IReflectionDeleteMember", "E2 : IReflectionDeleteMember", - ], Inspect(type_ReflectionDeleteMember_Derived2)); + ], Inspect(type_IReflectionDeleteMember)); }); static IEnumerable Inspect(Type type) From 985f9ead035cee5f153ef09510bdd0cec8616787 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Mon, 29 Sep 2025 11:55:39 -0700 Subject: [PATCH 06/11] Cleanup --- .../System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index d14a7f0667d06a..3b8c49e7170f28 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -807,7 +807,6 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) list.Add(runtimeConstructorInfo); } - GC.KeepAlive(declaringModule); return list.ToArray(); } @@ -926,8 +925,6 @@ private void PopulateRtFields(Filter filter, list.Add(runtimeFieldInfo); } - - GC.KeepAlive(declaringModule); } private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref ListBuilder list) From d408768f5c73d49f86fb7e963bf417b32c8918ba Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Mon, 29 Sep 2025 12:35:26 -0700 Subject: [PATCH 07/11] Cleanup --- .../src/System/RuntimeType.CoreCLR.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 3b8c49e7170f28..723256f7684bb7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -581,8 +581,6 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) RuntimeType declaringType = ReflectedType; Debug.Assert(declaringType != null); - RuntimeModule declaringModule = declaringType.GetRuntimeModule(); - if (declaringType.IsActualInterface) { #region IsInterface @@ -611,7 +609,7 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) if (MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && - RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeMethodHandle.GetMethodDef(methodHandle))) + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle))) { continue; } @@ -699,7 +697,7 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) // Filter out deleted method before setting override state, so that a deleted override in a subclass does not hide override in an ancestor. if (MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && - RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeMethodHandle.GetMethodDef(methodHandle))) + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle))) { continue; } @@ -749,7 +747,6 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter) #endregion } - GC.KeepAlive(declaringModule); return list.ToArray(); } @@ -763,7 +760,6 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) ListBuilder list = default; RuntimeType declaringType = ReflectedType; - RuntimeModule declaringModule = declaringType.GetRuntimeModule(); foreach (RuntimeMethodHandleInternal methodHandle in RuntimeTypeHandle.GetIntroducedMethods(declaringType)) { @@ -787,7 +783,7 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter) if (MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && - RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeMethodHandle.GetMethodDef(methodHandle))) + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle))) { continue; } @@ -881,7 +877,6 @@ private void PopulateRtFields(Filter filter, bool needsStaticFieldForGeneric = declaringType.IsGenericType && !RuntimeTypeHandle.ContainsGenericVariables(declaringType); bool isInherited = declaringType != ReflectedType; - RuntimeModule declaringModule = declaringType.GetRuntimeModule(); foreach (IntPtr handle in fieldHandles) { @@ -906,7 +901,7 @@ private void PopulateRtFields(Filter filter, if (MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && - RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeFieldHandle.GetToken(handle))) + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeFieldHandle.GetToken(handle))) { continue; } From c6c314feb111ec2ac672d6197f6a2f04dd0b917d Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Mon, 29 Sep 2025 22:23:24 -0400 Subject: [PATCH 08/11] Fix ref assembly formatting --- .../System.Runtime.Loader/ref/System.Runtime.Loader.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs index a5c1e1293ad168..8f7555ac5b599d 100644 --- a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs +++ b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs @@ -32,12 +32,11 @@ public sealed partial class CreateNewOnMetadataUpdateAttribute : System.Attribut { public CreateNewOnMetadataUpdateAttribute() { } } - - [System.AttributeUsageAttribute(System.AttributeTargets.All, AllowMultiple = false, Inherited = false)] - public sealed partial class MetadataUpdateDeletedAttribute : Attribute + [System.AttributeUsageAttribute(System.AttributeTargets.All, AllowMultiple=false, Inherited=false)] + public sealed partial class MetadataUpdateDeletedAttribute : System.Attribute { + public MetadataUpdateDeletedAttribute() { } } - [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple=false, Inherited=false)] public partial class MetadataUpdateOriginalTypeAttribute : System.Attribute { From 0f4eeb05096bf528bd439c23104e5ee48c194af5 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Fri, 3 Oct 2025 09:00:48 -0700 Subject: [PATCH 09/11] Fix --- .../src/System/RuntimeType.CoreCLR.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 723256f7684bb7..e60729f2925b05 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -968,7 +968,7 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref if (MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && - RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeFieldHandle.GetToken(tkField))) + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkField)) { continue; } @@ -1203,7 +1203,7 @@ private void PopulateEvents( if (MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && - RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeFieldHandle.GetToken(tkEvent))) + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkEvent)) { continue; } @@ -1323,7 +1323,7 @@ private void PopulateProperties( // Filter out deleted property before updating usedSlots, so that a deleted override in a subclass does not hide override in an ancestor. if (MetadataUpdater.IsSupported && RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers && - RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, RuntimeFieldHandle.GetToken(tkProperty))) + RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkProperty)) { continue; } From b15b9bb40fd7b348cc1c607aa44477e77b233ad7 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Fri, 3 Oct 2025 12:39:19 -0700 Subject: [PATCH 10/11] Fix test --- src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index 000366d65ba5a5..4ff9b0d9451466 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -130,7 +130,7 @@ void DeleteMethodTest() }); static IEnumerable Inspect(Type type) - => type.GetMembers().Select(m => m is ConstructorInfo ctor ? $"{ctor.Name}({ctor.GetParameters().Length}) : {m.DeclaringType!.Name}" : m.Name); + => type.GetMembers().Select(m => (m is ConstructorInfo ctor ? $"{ctor.Name}({ctor.GetParameters().Length})" : m.Name) + " : " + m.DeclaringType!.Name); } [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] From 7755689208a7568a3fac0859f1c9437a77ae55de Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 8 Oct 2025 12:51:04 -0700 Subject: [PATCH 11/11] Disable test on Mono --- src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs index 4ff9b0d9451466..d5986e07cc3eb9 100644 --- a/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs +++ b/src/libraries/System.Runtime.Loader/tests/ApplyUpdateTest.cs @@ -23,7 +23,8 @@ public class ApplyUpdateTest { [ConditionalFact(typeof(ApplyUpdateUtil), nameof (ApplyUpdateUtil.IsSupported))] - void DeleteMethodTest() + [ActiveIssue("https://github.com/dotnet/runtime/issues/120547", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoRuntime))] + void DeleteMemberTest() { ApplyUpdateUtil.TestCase(static () => {