From 62d0c8478487eb7971a2ce3ceab4cd19aa7463f2 Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Thu, 18 Sep 2025 18:48:32 -0700 Subject: [PATCH] Consider all MemberInfo references for IsCollectible Use ReflectedType instead DeclaringType since ReflectedType can be colletible even when DeclaringType is not. These fixes support typical IsCollectible use case where the property is used to determine whether or how the reflection objects can be cached. Fix #119697 --- .../RuntimeConstructorInfo.CoreCLR.cs | 2 +- .../src/System/Reflection/RuntimeEventInfo.cs | 2 +- .../src/System/Reflection/RuntimeFieldInfo.cs | 2 +- .../Reflection/RuntimeMethodInfo.CoreCLR.cs | 3 + .../System/Reflection/RuntimePropertyInfo.cs | 2 +- .../System/Reflection/IsCollectibleTests.cs | 103 +++++++----------- 6 files changed, 49 insertions(+), 65 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs index d47d19050c4d64..576283e807529a 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.CoreCLR.cs @@ -173,7 +173,7 @@ public override IList GetCustomAttributesData() internal RuntimeType GetRuntimeType() { return m_declaringType; } internal RuntimeModule GetRuntimeModule() { return RuntimeTypeHandle.GetModule(m_declaringType); } internal RuntimeAssembly GetRuntimeAssembly() { return GetRuntimeModule().GetRuntimeAssembly(); } - public override bool IsCollectible => m_declaringType.IsCollectible; + public override bool IsCollectible => ReflectedTypeInternal.IsCollectible; #endregion #region MethodBase Overrides diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs index e09a405a4144c3..cdef2ad754f7a8 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs @@ -129,7 +129,7 @@ public override IList GetCustomAttributesData() public override int MetadataToken => m_token; public override Module Module => GetRuntimeModule(); internal RuntimeModule GetRuntimeModule() { return m_declaringType.GetRuntimeModule(); } - public override bool IsCollectible => m_declaringType.IsCollectible; + public override bool IsCollectible => ReflectedTypeInternal.IsCollectible; #endregion #region EventInfo Overrides diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs index a314edaab2a15f..665fbd2076fe54 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -45,7 +45,7 @@ internal RuntimeType GetDeclaringTypeInternal() public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore(other); public override Module Module => GetRuntimeModule(); - public override bool IsCollectible => m_declaringType.IsCollectible; + public override bool IsCollectible => ReflectedTypeInternal.IsCollectible; #endregion diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs index 51e7575642826a..5386568d617e99 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs @@ -322,6 +322,9 @@ public override bool IsCollectible { get { + if (ReflectedTypeInternal.IsCollectible) + return true; + bool isCollectible = RuntimeMethodHandle.IsCollectible(new RuntimeMethodHandleInternal(m_handle)); GC.KeepAlive(this); // We directly pass the native handle above - make sure this object stays alive for the call return isCollectible; diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs index 8666910e37318b..bca5b8f506c0e9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs @@ -180,7 +180,7 @@ public override IList GetCustomAttributesData() public override Module Module => GetRuntimeModule(); internal RuntimeModule GetRuntimeModule() { return m_declaringType.GetRuntimeModule(); } - public override bool IsCollectible => m_declaringType.IsCollectible; + public override bool IsCollectible => ReflectedTypeInternal.IsCollectible; public override bool Equals(object? obj) => ReferenceEquals(this, obj) || diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/IsCollectibleTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/IsCollectibleTests.cs index ae02eef2537ba1..eac1dda524f2a1 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/IsCollectibleTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/IsCollectibleTests.cs @@ -132,17 +132,10 @@ public void Assembly_IsCollectibleTrue_WhenUsingTestAssemblyLoadContext() }).Dispose(); } - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData("MyField")] - [InlineData("MyProperty")] - [InlineData("MyMethod")] - [InlineData("MyGenericMethod")] - [InlineData("MyStaticMethod")] - [InlineData("MyStaticField")] - [InlineData("MyStaticGenericMethod")] - public void MemberInfo_IsCollectibleFalse_WhenUsingAssemblyLoad(string memberName) + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void MemberInfo_IsCollectibleFalse_WhenUsingAssemblyLoad() { - RemoteExecutor.Invoke((marshalledName) => + RemoteExecutor.Invoke(() => { Type t1 = Type.GetType( "TestCollectibleAssembly.MyTestClass, TestCollectibleAssembly, Version=1.0.0.0", @@ -153,25 +146,18 @@ public void MemberInfo_IsCollectibleFalse_WhenUsingAssemblyLoad(string memberNam Assert.NotNull(t1); - var member = t1.GetMember(marshalledName).FirstOrDefault(); - - Assert.NotNull(member); + foreach (var member in t1.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + Assert.False(member.IsCollectible, member.ToString()); + } - Assert.False(member.IsCollectible); - }, memberName).Dispose(); + }).Dispose(); } - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData("MyStaticGenericField")] - [InlineData("MyStaticField")] - [InlineData("MyStaticGenericMethod")] - [InlineData("MyStaticMethod")] - [InlineData("MyGenericField")] - [InlineData("MyGenericProperty")] - [InlineData("MyGenericMethod")] - public void MemberInfoGeneric_IsCollectibleFalse_WhenUsingAssemblyLoad(string memberName) + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void MemberInfoGeneric_IsCollectibleFalse_WhenUsingAssemblyLoad() { - RemoteExecutor.Invoke((marshalledName) => + RemoteExecutor.Invoke(() => { Type t1 = Type.GetType( "TestCollectibleAssembly.MyGenericTestClass`1[System.Int32], TestCollectibleAssembly, Version=1.0.0.0", @@ -182,25 +168,18 @@ public void MemberInfoGeneric_IsCollectibleFalse_WhenUsingAssemblyLoad(string me Assert.NotNull(t1); - var member = t1.GetMember(marshalledName).FirstOrDefault(); - - Assert.NotNull(member); + foreach (var member in t1.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + Assert.False(member.IsCollectible, member.ToString()); + } - Assert.False(member.IsCollectible); - }, memberName).Dispose(); + }).Dispose(); } - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData("MyField")] - [InlineData("MyProperty")] - [InlineData("MyMethod")] - [InlineData("MyGenericMethod")] - [InlineData("MyStaticMethod")] - [InlineData("MyStaticField")] - [InlineData("MyStaticGenericMethod")] - public void MemberInfo_IsCollectibleTrue_WhenUsingAssemblyLoadContext(string memberName) + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void MemberInfo_IsCollectibleTrue_WhenUsingAssemblyLoadContext() { - RemoteExecutor.Invoke((marshalledName) => + RemoteExecutor.Invoke(() => { AssemblyLoadContext alc = new TestAssemblyLoadContext(); @@ -213,25 +192,18 @@ public void MemberInfo_IsCollectibleTrue_WhenUsingAssemblyLoadContext(string mem Assert.NotNull(t1); - var member = t1.GetMember(marshalledName).FirstOrDefault(); - - Assert.NotNull(member); + foreach (var member in t1.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + Assert.True(member.IsCollectible, member.ToString()); + } - Assert.True(member.IsCollectible); - }, memberName).Dispose(); + }).Dispose(); } - [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] - [InlineData("MyStaticGenericField")] - [InlineData("MyStaticField")] - [InlineData("MyStaticGenericMethod")] - [InlineData("MyStaticMethod")] - [InlineData("MyGenericField")] - [InlineData("MyGenericProperty")] - [InlineData("MyGenericMethod")] - public void MemberInfoGeneric_IsCollectibleTrue_WhenUsingAssemblyLoadContext(string memberName) + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void MemberInfoGeneric_IsCollectibleTrue_WhenUsingAssemblyLoadContext() { - RemoteExecutor.Invoke((marshalledName) => + RemoteExecutor.Invoke(() => { AssemblyLoadContext alc = new TestAssemblyLoadContext(); @@ -244,12 +216,12 @@ public void MemberInfoGeneric_IsCollectibleTrue_WhenUsingAssemblyLoadContext(str Assert.NotNull(t1); - var member = t1.GetMember(marshalledName).FirstOrDefault(); + foreach (var member in t1.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + Assert.True(member.IsCollectible, member.ToString()); + } - Assert.NotNull(member); - - Assert.True(member.IsCollectible); - }, memberName).Dispose(); + }).Dispose(); } [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] @@ -268,7 +240,16 @@ public void GenericWithCollectibleTypeParameter_IsCollectibleTrue_WhenUsingAssem Assert.NotNull(t1); - Assert.True(t1.IsCollectible); + foreach (var member in t1.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (member is Type) + { + continue; + } + + Assert.True(member.IsCollectible, member.ToString()); + } + }).Dispose(); } }