diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdFieldInfo.cs index fcc1845bdf90a6..8f241f4d8b6b3e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/MdFieldInfo.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; +using System.Reflection.Metadata; using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache; namespace System.Reflection @@ -33,8 +34,7 @@ internal override bool CacheEquals(object? o) return o is MdFieldInfo m && m.m_tkField == m_tkField && - m_declaringType.TypeHandle.GetModuleHandle().Equals( - m.m_declaringType.TypeHandle.GetModuleHandle()); + ReferenceEquals(m_declaringType, m.m_declaringType); } #endregion @@ -43,6 +43,13 @@ o is MdFieldInfo m && public override int MetadataToken => m_tkField; internal override RuntimeModule GetRuntimeModule() { return m_declaringType.GetRuntimeModule(); } + + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || + (MetadataUpdater.IsSupported && CacheEquals(obj)); + + public override int GetHashCode() => + HashCode.Combine(m_tkField.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); #endregion #region FieldInfo Overrides diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs index 5831eca455adc1..ac322bdf20c002 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RtFieldInfo.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache; @@ -183,6 +184,13 @@ internal override RuntimeModule GetRuntimeModule() return RuntimeTypeHandle.GetModule(RuntimeFieldHandle.GetApproxDeclaringType(this)); } + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || + (MetadataUpdater.IsSupported && CacheEquals(obj)); + + public override int GetHashCode() => + HashCode.Combine(m_fieldHandle.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); + #endregion #region FieldInfo Overrides 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 51ee0061420e0f..98a98161208f41 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 @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Text; using System.Threading; @@ -72,7 +73,8 @@ internal RuntimeConstructorInfo( RuntimeMethodHandleInternal IRuntimeMethodInfo.Value => new RuntimeMethodHandleInternal(m_handle); internal override bool CacheEquals(object? o) => - o is RuntimeConstructorInfo m && m.m_handle == m_handle; + o is RuntimeConstructorInfo m && m.m_handle == m_handle && + ReferenceEquals(m_declaringType, m.m_declaringType); internal Signature Signature { @@ -118,6 +120,13 @@ public override string ToString() return m_toString; } + + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || + (MetadataUpdater.IsSupported && CacheEquals(obj)); + + public override int GetHashCode() => + HashCode.Combine(m_handle.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); #endregion #region ICustomAttributeProvider 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 366c9288662bbf..09adb05fc289c3 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeEventInfo.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Reflection.Metadata; using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache; namespace System.Reflection @@ -53,8 +54,7 @@ internal override bool CacheEquals(object? o) return o is RuntimeEventInfo m && m.m_token == m_token && - RuntimeTypeHandle.GetModule(m_declaringType).Equals( - RuntimeTypeHandle.GetModule(m.m_declaringType)); + ReferenceEquals(m_declaringType, m.m_declaringType); } internal BindingFlags BindingFlags => m_bindingFlags; @@ -68,6 +68,13 @@ public override string ToString() return m_addMethod.GetParametersNoCopy()[0].ParameterType.FormatTypeName() + " " + Name; } + + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || + (MetadataUpdater.IsSupported && CacheEquals(obj)); + + public override int GetHashCode() => + HashCode.Combine(m_token.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); #endregion #region ICustomAttributeProvider 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 cf06b03d661a96..5cbf1cb1463c0f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeFieldInfo.cs @@ -46,6 +46,7 @@ internal RuntimeType GetDeclaringTypeInternal() public override Module Module => GetRuntimeModule(); public override bool IsCollectible => m_declaringType.IsCollectible; + #endregion #region Object Overrides 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 8878b27db60dde..c55ee607da7738 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 @@ -157,56 +157,18 @@ public override string ToString() return m_toString; } - public override int GetHashCode() - { - // See RuntimeMethodInfo.Equals() below. - if (IsGenericMethod) - return RuntimeHelpers.GetHashCodeOfPtr(m_handle); - else - return base.GetHashCode(); - } - - public override bool Equals(object? obj) - { - if (!IsGenericMethod) - return obj == (object)this; - - // We cannot do simple object identity comparisons for generic methods. - // Equals will be called in CerHashTable when RuntimeType+RuntimeTypeCache.GetGenericMethodInfo() - // retrieve items from and insert items into s_methodInstantiations which is a CerHashtable. - - RuntimeMethodInfo? mi = obj as RuntimeMethodInfo; - - if (mi == null || !mi.IsGenericMethod) - return false; - - // now we know that both operands are generic methods - - IRuntimeMethodInfo handle1 = RuntimeMethodHandle.StripMethodInstantiation(this); - IRuntimeMethodInfo handle2 = RuntimeMethodHandle.StripMethodInstantiation(mi); - if (handle1.Value.Value != handle2.Value.Value) - return false; - - Type[] lhs = GetGenericArguments(); - Type[] rhs = mi.GetGenericArguments(); + // We cannot do simple object identity comparisons due to generic methods. + // Equals and GetHashCode will be called in CerHashTable when RuntimeType+RuntimeTypeCache.GetGenericMethodInfo() + // retrieve items from and insert items into s_methodInstantiations. - if (lhs.Length != rhs.Length) - return false; + public override int GetHashCode() => + HashCode.Combine(m_handle.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); - for (int i = 0; i < lhs.Length; i++) - { - if (lhs[i] != rhs[i]) - return false; - } + public override bool Equals(object? obj) => + obj is RuntimeMethodInfo m && m_handle == m.m_handle && + ReferenceEquals(m_declaringType, m.m_declaringType) && + ReferenceEquals(m_reflectedTypeCache.GetRuntimeType(), m.m_reflectedTypeCache.GetRuntimeType()); - if (DeclaringType != mi.DeclaringType) - return false; - - if (ReflectedType != mi.ReflectedType) - return false; - - return true; - } #endregion #region ICustomAttributeProvider 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 82c582a3386659..b844b6eaa0d752 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimePropertyInfo.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Reflection.Metadata; using System.Text; using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache; @@ -55,8 +56,7 @@ internal override bool CacheEquals(object? o) return o is RuntimePropertyInfo m && m.m_token == m_token && - RuntimeTypeHandle.GetModule(m_declaringType).Equals( - RuntimeTypeHandle.GetModule(m.m_declaringType)); + ReferenceEquals(m_declaringType, m.m_declaringType); } internal Signature Signature @@ -179,6 +179,13 @@ 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 Equals(object? obj) => + ReferenceEquals(this, obj) || + (MetadataUpdater.IsSupported && CacheEquals(obj)); + + public override int GetHashCode() => + HashCode.Combine(m_token.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode()); #endregion #region PropertyInfo Overrides diff --git a/src/libraries/System.Runtime/tests/System/DelegateTests.cs b/src/libraries/System.Runtime/tests/System/DelegateTests.cs index 6675d90ccd258e..e24d5478fddc17 100644 --- a/src/libraries/System.Runtime/tests/System/DelegateTests.cs +++ b/src/libraries/System.Runtime/tests/System/DelegateTests.cs @@ -458,7 +458,7 @@ public static void SameGenericMethodObtainedViaDelegateAndReflectionAreSameForCl var m1 = ((MethodCallExpression)((Expression)(() => new ClassG().M())).Body).Method; var m2 = new Action(new ClassG().M).Method; Assert.True(m1.Equals(m2)); - Assert.True(m1.GetHashCode().Equals(m2.GetHashCode())); + Assert.Equal(m1.GetHashCode(), m2.GetHashCode()); Assert.Equal(m1.MethodHandle.Value, m2.MethodHandle.Value); } @@ -468,7 +468,7 @@ public static void SameGenericMethodObtainedViaDelegateAndReflectionAreSameForSt var m1 = ((MethodCallExpression)((Expression)(() => new StructG().M())).Body).Method; var m2 = new Action(new StructG().M).Method; Assert.True(m1.Equals(m2)); - Assert.True(m1.GetHashCode().Equals(m2.GetHashCode())); + Assert.Equal(m1.GetHashCode(), m2.GetHashCode()); Assert.Equal(m1.MethodHandle.Value, m2.MethodHandle.Value); } diff --git a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs index 4833893eb3dfe6..40ce236003222a 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Tests; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Reflection.Tests @@ -9,16 +10,53 @@ namespace System.Reflection.Tests [Collection(nameof(DisableParallelization))] public class ReflectionCacheTests { + private static bool IsMetadataUpdateAndRemoteExecutorSupported => PlatformDetection.IsMetadataUpdateSupported && RemoteExecutor.IsSupported; + + private static readonly Type s_type = typeof(ReflectionCacheTests); + + public string Property { get; set; } + + public int Field1; + +#pragma warning disable xUnit1013 // Public method should be marked as test + public void Method(bool param) +#pragma warning restore xUnit1013 // Public method should be marked as test + { + Event1(null, EventArgs.Empty); + } + + public event EventHandler Event1; + [Fact] - public void GetMethod_MultipleCalls_SameObjects() + public void GetMembers_MultipleCalls_SameObjects() { - MethodInfo mi1 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_SameObjects)); - Assert.NotNull(mi1); + MethodInfo mi1 = s_type.GetMethod(nameof(Method)); + PropertyInfo pi1 = s_type.GetProperty(nameof(Property)); + FieldInfo fi1 = s_type.GetField(nameof(Field1)); + EventInfo ei1 = s_type.GetEvent(nameof(Event1)); + ConstructorInfo ci1 = s_type.GetConstructor(Type.EmptyTypes); + + MethodInfo mi2 = s_type.GetMethod(nameof(Method)); + PropertyInfo pi2 = s_type.GetProperty(nameof(Property)); + FieldInfo fi2 = s_type.GetField(nameof(Field1)); + EventInfo ei2 = s_type.GetEvent(nameof(Event1)); + ConstructorInfo ci2 = s_type.GetConstructor(Type.EmptyTypes); - MethodInfo mi2 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_SameObjects)); - Assert.NotNull(mi2); + AssertSameEqualAndHashCodeEqual(mi1, mi2); + AssertSameEqualAndHashCodeEqual(pi1, pi2); + AssertSameEqualAndHashCodeEqual(fi1, fi2); + AssertSameEqualAndHashCodeEqual(ei1, ei2); + AssertSameEqualAndHashCodeEqual(ci1, ci2); + } - Assert.Same(mi1, mi2); + void AssertSameEqualAndHashCodeEqual(object o1, object o2) + { + // When cache not cleared the references of the same members are Same and Equal, and Hashcodes Equal. + Assert.NotNull(o1); + Assert.NotNull(o2); + Assert.Same(o1, o2); + Assert.Equal(o1, o2); + Assert.Equal(o1.GetHashCode(), o2.GetHashCode()); } [ActiveIssue("https://github.com/dotnet/runtime/issues/50978", TestRuntimes.Mono)] @@ -33,24 +71,82 @@ public void InvokeClearCache_NoExceptions() } [ActiveIssue("https://github.com/dotnet/runtime/issues/50978", TestRuntimes.Mono)] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsMetadataUpdateSupported))] - [InlineData(false)] - [InlineData(true)] - public void GetMethod_MultipleCalls_ClearCache_DifferentObjects(bool justSpecificType) + [ConditionalFact(typeof(ReflectionCacheTests), nameof(IsMetadataUpdateAndRemoteExecutorSupported))] + public void GetMembers_MultipleCalls_ClearCache_ReflectionCacheTestsType() { - Action clearCache = GetClearCacheMethod(); + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables.Add("DOTNET_MODIFIABLE_ASSEMBLIES", "debug"); + + using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(() => + { + Action clearCache = GetClearCacheMethod(); + + MethodInfo mi1 = s_type.GetMethod(nameof(Method)); + PropertyInfo pi1 = s_type.GetProperty(nameof(Property)); + FieldInfo fi1 = s_type.GetField(nameof(Field1)); + EventInfo ei1 = s_type.GetEvent(nameof(Event1)); + ConstructorInfo ci1 = s_type.GetConstructor(Type.EmptyTypes); + + clearCache(new[] { typeof(ReflectionCacheTests) }); + + MethodInfo mi2 = s_type.GetMethod(nameof(Method)); + PropertyInfo pi2 = s_type.GetProperty(nameof(Property)); + FieldInfo fi2 = s_type.GetField(nameof(Field1)); + EventInfo ei2 = s_type.GetEvent(nameof(Event1)); + ConstructorInfo ci2 = s_type.GetConstructor(Type.EmptyTypes); + + AssertNotSameSameButEqualAndHashCodeEqual(mi1, mi2); + AssertNotSameSameButEqualAndHashCodeEqual(pi1, pi2); + AssertNotSameSameButEqualAndHashCodeEqual(fi1, fi2); + AssertNotSameSameButEqualAndHashCodeEqual(ci1, ci2); + AssertNotSameSameButEqualAndHashCodeEqual(ei1, ei2); + }, options); + } + + private static void AssertNotSameSameButEqualAndHashCodeEqual(object o1, object o2) + { + // After the cache cleared the references of the same members will be Not Same. + // But they should be evaluated as Equal so that there were no issue using the same member after hot reload. + // And the member HashCode before and after hot reload should produce the same result. + + Assert.NotNull(o1); + Assert.NotNull(o2); + Assert.NotSame(o1, o2); + Assert.Equal(o1, o2); + Assert.Equal(o1.GetHashCode(), o2.GetHashCode()); + } + + [ActiveIssue("https://github.com/dotnet/runtime/issues/50978", TestRuntimes.Mono)] + [ConditionalFact(typeof(ReflectionCacheTests), nameof(IsMetadataUpdateAndRemoteExecutorSupported))] + public void GetMembers_MultipleCalls_ClearCache_All() + { + RemoteInvokeOptions options = new RemoteInvokeOptions(); + options.StartInfo.EnvironmentVariables.Add("DOTNET_MODIFIABLE_ASSEMBLIES", "debug"); + + using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(() => + { + Action clearCache = GetClearCacheMethod(); - MethodInfo mi1 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects)); - Assert.NotNull(mi1); - Assert.Equal(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects), mi1.Name); + MethodInfo mi1 = s_type.GetMethod(nameof(Method)); + PropertyInfo pi1 = s_type.GetProperty(nameof(Property)); + FieldInfo fi1 = s_type.GetField(nameof(Field1)); + EventInfo ei1 = s_type.GetEvent(nameof(Event1)); + ConstructorInfo ci1 = s_type.GetConstructor(Type.EmptyTypes); - clearCache(justSpecificType ? new[] { typeof(ReflectionCacheTests) } : null); + clearCache(null); - MethodInfo mi2 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects)); - Assert.NotNull(mi2); - Assert.Equal(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects), mi2.Name); + MethodInfo mi2 = s_type.GetMethod(nameof(Method)); + PropertyInfo pi2 = s_type.GetProperty(nameof(Property)); + FieldInfo fi2 = s_type.GetField(nameof(Field1)); + EventInfo ei2 = s_type.GetEvent(nameof(Event1)); + ConstructorInfo ci2 = s_type.GetConstructor(Type.EmptyTypes); - Assert.NotSame(mi1, mi2); + AssertNotSameSameButEqualAndHashCodeEqual(mi1, mi2); + AssertNotSameSameButEqualAndHashCodeEqual(pi1, pi2); + AssertNotSameSameButEqualAndHashCodeEqual(fi1, fi2); + AssertNotSameSameButEqualAndHashCodeEqual(ci1, ci2); + AssertNotSameSameButEqualAndHashCodeEqual(ei1, ei2); + }, options); } private static Action GetClearCacheMethod()