Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;

[assembly: MetadataUpdateHandler(typeof(RuntimeTypeMetadataUpdateHandler))]

Expand All @@ -11,11 +12,21 @@ namespace System.Reflection.Metadata
/// <summary>Metadata update handler used to clear a Type's reflection cache in response to a metadata update notification.</summary>
internal static class RuntimeTypeMetadataUpdateHandler
{
/// <summary>
/// True to enable filtering deleted members from Reflection results. Set after the first metadata update.
/// </summary>
internal static bool FilterDeletedMembers { get; private set; }

internal static bool IsMetadataUpdateDeleted(RuntimeModule module, int memberToken)
=> CustomAttribute.IsCustomAttributeDefined(module, memberToken, (RuntimeType)typeof(MetadataUpdateDeletedAttribute));

/// <summary>Clear type caches in response to an update notification.</summary>
/// <param name="types">The specific types to be cleared, or null to clear everything.</param>
[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)
{
FilterDeletedMembers = true;

if (RequiresClearingAllTypes(types))
{
// TODO: This should ideally be in a QCall in the runtime. As written here:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,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)
{
Expand Down Expand Up @@ -1566,7 +1566,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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -605,6 +607,13 @@ private unsafe RuntimeMethodInfo[] PopulateMethods(Filter filter)
continue;
#endregion

if (MetadataUpdater.IsSupported &&
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle)))
{
continue;
}

#region Calculate Binding Flags
bool isPublic = (methodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
bool isStatic = (methodAttributes & MethodAttributes.Static) != 0;
Expand All @@ -615,7 +624,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
Expand Down Expand Up @@ -685,6 +694,14 @@ 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 (MetadataUpdater.IsSupported &&
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle)))
{
continue;
}

#region Continue if this is a virtual and is already overridden
if (isVirtual)
{
Expand Down Expand Up @@ -719,7 +736,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
Expand Down Expand Up @@ -764,6 +781,13 @@ private RuntimeConstructorInfo[] PopulateConstructors(Filter filter)
(methodAttributes & MethodAttributes.Abstract) == 0 &&
(methodAttributes & MethodAttributes.Virtual) == 0);

if (MetadataUpdater.IsSupported &&
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeMethodHandle.GetMethodDef(methodHandle)))
{
continue;
}

#region Calculate Binding Flags
bool isPublic = (methodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
bool isStatic = (methodAttributes & MethodAttributes.Static) != 0;
Expand Down Expand Up @@ -875,6 +899,13 @@ private void PopulateRtFields(Filter filter,
continue;
}

if (MetadataUpdater.IsSupported &&
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringType.GetRuntimeModule(), RuntimeFieldHandle.GetToken(handle)))
{
continue;
}

#region Calculate Binding Flags
bool isPublic = fieldAccess == FieldAttributes.Public;
bool isStatic = (fieldAttributes & FieldAttributes.Static) != 0;
Expand All @@ -885,8 +916,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);
}
Expand All @@ -903,8 +933,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);

Expand Down Expand Up @@ -936,6 +966,13 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref
continue;
}

if (MetadataUpdater.IsSupported &&
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkField))
{
continue;
}

#region Calculate Binding Flags
bool isPublic = fieldAccess == FieldAttributes.Public;
bool isStatic = (fieldAttributes & FieldAttributes.Static) != 0;
Expand All @@ -948,7 +985,7 @@ private void PopulateLiteralFields(Filter filter, RuntimeType declaringType, ref
list.Add(runtimeFieldInfo);
}
}
GC.KeepAlive(module);
GC.KeepAlive(declaringModule);
}

private void AddSpecialInterface(
Expand Down Expand Up @@ -1144,8 +1181,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);

Expand All @@ -1164,6 +1201,13 @@ private void PopulateEvents(
continue;
}

if (MetadataUpdater.IsSupported &&
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkEvent))
{
continue;
}

RuntimeEventInfo eventInfo = new RuntimeEventInfo(
tkEvent, declaringType, m_runtimeTypeCache, out bool isPrivate);

Expand Down Expand Up @@ -1191,7 +1235,7 @@ private void PopulateEvents(

list.Add(eventInfo);
}
GC.KeepAlive(module);
GC.KeepAlive(declaringModule);
}

private RuntimePropertyInfo[] PopulateProperties(Filter filter)
Expand Down Expand Up @@ -1251,8 +1295,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);

Expand All @@ -1276,6 +1320,14 @@ 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 (MetadataUpdater.IsSupported &&
RuntimeTypeMetadataUpdateHandler.FilterDeletedMembers &&
RuntimeTypeMetadataUpdateHandler.IsMetadataUpdateDeleted(declaringModule, tkProperty))
{
continue;
}

RuntimePropertyInfo propertyInfo =
new RuntimePropertyInfo(
tkProperty, declaringType, m_runtimeTypeCache, out bool isPrivate);
Expand Down Expand Up @@ -1365,7 +1417,7 @@ private void PopulateProperties(

list.Add(propertyInfo);
}
GC.KeepAlive(module);
GC.KeepAlive(declaringModule);
}
#endregion

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

return existingCache;
}

#endregion

#region Internal Members
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MethodImplAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MethodImplOptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ModuleInitializerAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MetadataUpdateDeletedAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\MetadataUpdateOriginalTypeAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\NullableAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\NullableContextAttribute.cs" />
Expand Down Expand Up @@ -2915,4 +2916,4 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_0.IPoll.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\Wasi\WasiPollWorld.wit.imports.wasi.io.v0_2_0.PollInterop.cs" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 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;

/// <summary>
/// This attribute is emitted by the compiler when a metadata entity is deleted during a
/// Hot Reload session.
/// </summary>
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)]
public sealed class MetadataUpdateDeletedAttribute : Attribute;
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +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 : System.Attribute
{
public MetadataUpdateDeletedAttribute() { }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Struct, AllowMultiple=false, Inherited=false)]
public partial class MetadataUpdateOriginalTypeAttribute : System.Attribute
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

Loading
Loading