Skip to content

Commit 7d43e0f

Browse files
jeffhandleysteveisoktmat
authored
[release/10.0] Add MetadataUpdateDeletedAttribute and Reflection Filtering (#120572)
* Add MetadataUpdateDeletedAttribute (#119584) 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 #118903 * Add ability to filter members in reflection and test --------- Co-authored-by: Tomas Matousek <[email protected]> * Fix assert message with errant text pasted in (#120574) --------- Co-authored-by: Steve Pfister <[email protected]> Co-authored-by: Tomas Matousek <[email protected]>
1 parent c527b09 commit 7d43e0f

File tree

13 files changed

+352
-19
lines changed

13 files changed

+352
-19
lines changed

src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics.CodeAnalysis;
55
using System.Reflection.Metadata;
6+
using System.Runtime.CompilerServices;
67

78
[assembly: MetadataUpdateHandler(typeof(RuntimeTypeMetadataUpdateHandler))]
89

@@ -11,11 +12,21 @@ namespace System.Reflection.Metadata
1112
/// <summary>Metadata update handler used to clear a Type's reflection cache in response to a metadata update notification.</summary>
1213
internal static class RuntimeTypeMetadataUpdateHandler
1314
{
15+
/// <summary>
16+
/// True to enable filtering deleted members from Reflection results. Set after the first metadata update.
17+
/// </summary>
18+
internal static bool FilterDeletedMembers { get; private set; }
19+
20+
internal static bool IsMetadataUpdateDeleted(RuntimeModule module, int memberToken)
21+
=> CustomAttribute.IsCustomAttributeDefined(module, memberToken, (RuntimeType)typeof(MetadataUpdateDeletedAttribute));
22+
1423
/// <summary>Clear type caches in response to an update notification.</summary>
1524
/// <param name="types">The specific types to be cleared, or null to clear everything.</param>
1625
[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.")]
1726
public static void ClearCache(Type[]? types)
1827
{
28+
FilterDeletedMembers = true;
29+
1930
if (RequiresClearingAllTypes(types))
2031
{
2132
// TODO: This should ideally be in a QCall in the runtime. As written here:

src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,7 @@ internal static bool IsAttributeDefined(RuntimeModule decoratedModule, int decor
14071407
return IsCustomAttributeDefined(decoratedModule, decoratedMetadataToken, null, attributeCtorToken, false);
14081408
}
14091409

1410-
private static bool IsCustomAttributeDefined(
1410+
internal static bool IsCustomAttributeDefined(
14111411
RuntimeModule decoratedModule, int decoratedMetadataToken, RuntimeType? attributeFilterType)
14121412
{
14131413
return IsCustomAttributeDefined(decoratedModule, decoratedMetadataToken, attributeFilterType, 0, false);

src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ internal static int GetSlot(IRuntimeMethodInfo method)
11181118
}
11191119

11201120
[MethodImpl(MethodImplOptions.InternalCall)]
1121-
private static extern int GetMethodDef(RuntimeMethodHandleInternal method);
1121+
internal static extern int GetMethodDef(RuntimeMethodHandleInternal method);
11221122

11231123
internal static int GetMethodDef(IRuntimeMethodInfo method)
11241124
{
@@ -1569,7 +1569,7 @@ internal static ref byte GetFieldDataReference(ref byte target, RuntimeFieldInfo
15691569
private static unsafe partial void GetFieldDataReference(IntPtr fieldDesc, ObjectHandleOnStack target, ByteRefOnStack fieldDataRef);
15701570

15711571
[MethodImpl(MethodImplOptions.InternalCall)]
1572-
private static extern int GetToken(IntPtr fieldDesc);
1572+
internal static extern int GetToken(IntPtr fieldDesc);
15731573

15741574
internal static int GetToken(RtFieldInfo field)
15751575
{

src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
using System.Diagnostics;
77
using System.Diagnostics.CodeAnalysis;
88
using System.Globalization;
9+
using System.IO;
910
using System.Reflection;
11+
using System.Reflection.Metadata;
1012
using System.Runtime.CompilerServices;
1113
using System.Runtime.InteropServices;
1214
using System.Text;
@@ -605,6 +607,13 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter)
605607
continue;
606608
#endregion
607609

610+
if (MetadataUpdater.IsSupported &&
611+
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
612+
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle)))
613+
{
614+
continue;
615+
}
616+
608617
#region Calculate Binding Flags
609618
bool isPublic = (methodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
610619
bool isStatic = (methodAttributes & MethodAttributes.Static) != 0;
@@ -615,7 +624,7 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter)
615624
RuntimeMethodHandleInternal instantiatedHandle = RuntimeMethodHandle.GetStubIfNeeded(methodHandle, declaringType, null);
616625

617626
RuntimeMethodInfo runtimeMethodInfo = new RuntimeMethodInfo(
618-
instantiatedHandle, declaringType, m_runtimeTypeCache, methodAttributes, bindingFlags, null);
627+
instantiatedHandle, declaringType, m_runtimeTypeCache, methodAttributes, bindingFlags, null);
619628

620629
list.Add(runtimeMethodInfo);
621630
#endregion
@@ -685,6 +694,14 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter)
685694

686695
#endregion
687696

697+
// Filter out deleted method before setting override state, so that a deleted override in a subclass does not hide override in an ancestor.
698+
if (MetadataUpdater.IsSupported &&
699+
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
700+
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle)))
701+
{
702+
continue;
703+
}
704+
688705
#region Continue if this is a virtual and is already overridden
689706
if (isVirtual)
690707
{
@@ -719,7 +736,7 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter)
719736
RuntimeMethodHandleInternal instantiatedHandle = RuntimeMethodHandle.GetStubIfNeeded(methodHandle, declaringType, null);
720737

721738
RuntimeMethodInfo runtimeMethodInfo = new RuntimeMethodInfo(
722-
instantiatedHandle, declaringType, m_runtimeTypeCache, methodAttributes, bindingFlags, null);
739+
instantiatedHandle, declaringType, m_runtimeTypeCache, methodAttributes, bindingFlags, null);
723740

724741
list.Add(runtimeMethodInfo);
725742
#endregion
@@ -764,6 +781,13 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter)
764781
(methodAttributes & MethodAttributes.Abstract) == 0 &&
765782
(methodAttributes & MethodAttributes.Virtual) == 0);
766783

784+
if (MetadataUpdater.IsSupported &&
785+
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
786+
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle)))
787+
{
788+
continue;
789+
}
790+
767791
#region Calculate Binding Flags
768792
bool isPublic = (methodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
769793
bool isStatic = (methodAttributes & MethodAttributes.Static) != 0;
@@ -875,6 +899,13 @@ private void PopulateRtFields(Filter filter,
875899
continue;
876900
}
877901

902+
if (MetadataUpdater.IsSupported &&
903+
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
904+
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeFieldHandle.GetToken(handle)))
905+
{
906+
continue;
907+
}
908+
878909
#region Calculate Binding Flags
879910
bool isPublic = fieldAccess == FieldAttributes.Public;
880911
bool isStatic = (fieldAttributes & FieldAttributes.Static) != 0;
@@ -885,8 +916,7 @@ private void PopulateRtFields(Filter filter,
885916
if (needsStaticFieldForGeneric && isStatic)
886917
runtimeFieldHandle = RuntimeFieldHandle.GetStaticFieldForGenericType(runtimeFieldHandle, declaringType);
887918

888-
RuntimeFieldInfo runtimeFieldInfo =
889-
new RtFieldInfo(runtimeFieldHandle, declaringType, m_runtimeTypeCache, bindingFlags);
919+
var runtimeFieldInfo = new RtFieldInfo(runtimeFieldHandle, declaringType, m_runtimeTypeCache, bindingFlags);
890920

891921
list.Add(runtimeFieldInfo);
892922
}
@@ -903,8 +933,8 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref
903933
if (MdToken.IsNullToken(tkDeclaringType))
904934
return;
905935

906-
RuntimeModule module = declaringType.GetRuntimeModule();
907-
MetadataImport scope = module.MetadataImport;
936+
RuntimeModule declaringModule = declaringType.GetRuntimeModule();
937+
MetadataImport scope = declaringModule.MetadataImport;
908938

909939
scope.EnumFields(tkDeclaringType, out MetadataEnumResult tkFields);
910940

@@ -936,6 +966,13 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref
936966
continue;
937967
}
938968

969+
if (MetadataUpdater.IsSupported &&
970+
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
971+
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkField))
972+
{
973+
continue;
974+
}
975+
939976
#region Calculate Binding Flags
940977
bool isPublic = fieldAccess == FieldAttributes.Public;
941978
bool isStatic = (fieldAttributes & FieldAttributes.Static) != 0;
@@ -948,7 +985,7 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref
948985
list.Add(runtimeFieldInfo);
949986
}
950987
}
951-
GC.KeepAlive(module);
988+
GC.KeepAlive(declaringModule);
952989
}
953990

954991
private void AddSpecialInterface(
@@ -1144,8 +1181,8 @@ private void PopulateEvents(
11441181
if (MdToken.IsNullToken(tkDeclaringType))
11451182
return;
11461183

1147-
RuntimeModule module = declaringType.GetRuntimeModule();
1148-
MetadataImport scope = module.MetadataImport;
1184+
RuntimeModule declaringModule = declaringType.GetRuntimeModule();
1185+
MetadataImport scope = declaringModule.MetadataImport;
11491186

11501187
scope.EnumEvents(tkDeclaringType, out MetadataEnumResult tkEvents);
11511188

@@ -1164,6 +1201,13 @@ private void PopulateEvents(
11641201
continue;
11651202
}
11661203

1204+
if (MetadataUpdater.IsSupported &&
1205+
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
1206+
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkEvent))
1207+
{
1208+
continue;
1209+
}
1210+
11671211
RuntimeEventInfo eventInfo = new RuntimeEventInfo(
11681212
tkEvent, declaringType, m_runtimeTypeCache, out bool isPrivate);
11691213

@@ -1191,7 +1235,7 @@ private void PopulateEvents(
11911235

11921236
list.Add(eventInfo);
11931237
}
1194-
GC.KeepAlive(module);
1238+
GC.KeepAlive(declaringModule);
11951239
}
11961240

11971241
private RuntimePropertyInfo[] PopulateProperties(Filter filter)
@@ -1251,8 +1295,8 @@ private void PopulateProperties(
12511295
if (MdToken.IsNullToken(tkDeclaringType))
12521296
return;
12531297

1254-
RuntimeModule module = declaringType.GetRuntimeModule();
1255-
MetadataImport scope = module.MetadataImport;
1298+
RuntimeModule declaringModule = declaringType.GetRuntimeModule();
1299+
MetadataImport scope = declaringModule.MetadataImport;
12561300

12571301
scope.EnumProperties(tkDeclaringType, out MetadataEnumResult tkProperties);
12581302

@@ -1276,6 +1320,14 @@ private void PopulateProperties(
12761320
continue;
12771321
}
12781322

1323+
// Filter out deleted property before updating usedSlots, so that a deleted override in a subclass does not hide override in an ancestor.
1324+
if (MetadataUpdater.IsSupported &&
1325+
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
1326+
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkProperty))
1327+
{
1328+
continue;
1329+
}
1330+
12791331
RuntimePropertyInfo propertyInfo =
12801332
new RuntimePropertyInfo(
12811333
tkProperty, declaringType, m_runtimeTypeCache, out bool isPrivate);
@@ -1365,7 +1417,7 @@ private void PopulateProperties(
13651417

13661418
list.Add(propertyInfo);
13671419
}
1368-
GC.KeepAlive(module);
1420+
GC.KeepAlive(declaringModule);
13691421
}
13701422
#endregion
13711423

@@ -1458,6 +1510,7 @@ private MemberInfoCache<T> GetMemberCache<T>(ref MemberInfoCache<T>? m_cache)
14581510

14591511
return existingCache;
14601512
}
1513+
14611514
#endregion
14621515

14631516
#region Internal Members

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,7 @@
881881
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MethodImplAttribute.cs" />
882882
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MethodImplOptions.cs" />
883883
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ModuleInitializerAttribute.cs" />
884+
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MetadataUpdateDeletedAttribute.cs" />
884885
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MetadataUpdateOriginalTypeAttribute.cs" />
885886
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\NullableAttribute.cs" />
886887
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\NullableContextAttribute.cs" />
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Runtime.CompilerServices;
5+
6+
/// <summary>
7+
/// This attribute is emitted by the compiler when a metadata entity is deleted during a
8+
/// Hot Reload session.
9+
/// </summary>
10+
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)]
11+
public sealed class MetadataUpdateDeletedAttribute : Attribute;

src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,13 @@ namespace System.Runtime.CompilerServices
3333
public sealed class CreateNewOnMetadataUpdateAttribute : System.Attribute
3434
{
3535
}
36-
[AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct,
37-
AllowMultiple=false, Inherited=false)]
38-
public class MetadataUpdateOriginalTypeAttribute : Attribute
36+
[System.AttributeUsageAttribute(System.AttributeTargets.All, AllowMultiple=false, Inherited=false)]
37+
public sealed partial class MetadataUpdateDeletedAttribute : System.Attribute
38+
{
39+
public MetadataUpdateDeletedAttribute() { }
40+
}
41+
[System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple=false, Inherited=false)]
42+
public partial class MetadataUpdateOriginalTypeAttribute : System.Attribute
3943
{
4044
public MetadataUpdateOriginalTypeAttribute(Type originalType) { throw null; }
4145
public Type OriginalType { get { throw null; } }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Reflection.Metadata.ApplyUpdate.Test
5+
{
6+
public class ReflectionDeleteMember
7+
{
8+
public int F1;
9+
10+
public ReflectionDeleteMember() { }
11+
public ReflectionDeleteMember(int arg) { F1 = arg; }
12+
13+
public virtual int P1 { get; set; }
14+
public virtual void M1() { }
15+
16+
public virtual int P2 { get; }
17+
public virtual void M2() { }
18+
19+
public int P3 { get; set; }
20+
public void M3() { }
21+
public event Action E1 { add { } remove { } }
22+
}
23+
24+
public class ReflectionDeleteMember_Derived : ReflectionDeleteMember
25+
{
26+
public new int F1;
27+
28+
public override int P1 { get; set; }
29+
public override void M1() { }
30+
31+
public override int P2 { get; }
32+
public override void M2() { }
33+
34+
public new int P3 { get; set; }
35+
public new void M3() { }
36+
public new event Action E1 { add { } remove { } }
37+
}
38+
39+
public class ReflectionDeleteMember_Derived2 : ReflectionDeleteMember_Derived
40+
{
41+
public override int P1 { get; set; }
42+
public override void M1() { }
43+
44+
public override int P2 { get; }
45+
public override void M2() { }
46+
}
47+
48+
public interface IReflectionDeleteMember
49+
{
50+
int P1 { get; set; }
51+
int P2 { get; }
52+
void M1();
53+
void M2();
54+
event Action E1;
55+
event Action E2;
56+
}
57+
}
58+

0 commit comments

Comments
 (0)