Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 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 @@ -33,8 +33,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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -118,6 +119,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.Metadata;
using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache;

namespace System.Reflection
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Reflection.Metadata;
using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache;

namespace System.Reflection
Expand Down Expand Up @@ -46,6 +47,13 @@ internal RuntimeType GetDeclaringTypeInternal()

public override Module Module => 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(MetadataToken.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode());
#endregion

#region Object Overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
Expand Down Expand Up @@ -157,56 +158,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -179,6 +179,13 @@ public override IList<CustomAttributeData> 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
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Runtime/tests/System/DelegateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ public static void SameGenericMethodObtainedViaDelegateAndReflectionAreSameForCl
var m1 = ((MethodCallExpression)((Expression<Action>)(() => new ClassG().M<string, object>())).Body).Method;
var m2 = new Action(new ClassG().M<string, object>).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);
}

Expand All @@ -468,7 +468,7 @@ public static void SameGenericMethodObtainedViaDelegateAndReflectionAreSameForSt
var m1 = ((MethodCallExpression)((Expression<Action>)(() => new StructG().M<string, object>())).Body).Method;
var m2 = new Action(new StructG().M<string, object>).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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,61 @@
// 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
{
[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)]
Expand All @@ -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<Type[]> clearCache = GetClearCacheMethod();
RemoteInvokeOptions options = new RemoteInvokeOptions();
options.StartInfo.EnvironmentVariables.Add("DOTNET_MODIFIABLE_ASSEMBLIES", "debug");

using RemoteInvokeHandle remoteHandle = RemoteExecutor.Invoke(() =>
{
Action<Type[]> 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<Type[]> 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<Type[]> GetClearCacheMethod()
Expand Down