From b1e8fe64d92572b42ad270e643fa112190caecbd Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Fri, 19 Sep 2025 14:24:32 -0700 Subject: [PATCH] [release/10.0] Consider all MemberInfo references for IsCollectible (#119869) 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 | 13 ++- .../System/Reflection/RuntimePropertyInfo.cs | 2 +- .../System/Reflection/IsCollectibleTests.cs | 103 +++++++----------- 6 files changed, 58 insertions(+), 66 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 dd855b65fdee93..67180ec563730d 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 3a72a2e1174e4b..8b604ebd0f8797 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 @@ -319,7 +319,18 @@ internal void InvokePropertySetter(object? obj, BindingFlags invokeAttr, Binder? public override ParameterInfo ReturnParameter => FetchReturnParameter(); - public override bool IsCollectible => RuntimeMethodHandle.GetIsCollectible(new RuntimeMethodHandleInternal(m_handle)) != Interop.BOOL.FALSE; + public override bool IsCollectible + { + get + { + if (ReflectedTypeInternal.IsCollectible) + return true; + + bool isCollectible = RuntimeMethodHandle.GetIsCollectible(new RuntimeMethodHandleInternal(m_handle)) != Interop.BOOL.FALSE; + GC.KeepAlive(this); // We directly pass the native handle above - make sure this object stays alive for the call + return isCollectible; + } + } public override MethodInfo GetBaseDefinition() { 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 51b1f84864b1a9..7ebc2b7331b466 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(); } }