From e8ad5d0a2404720ec4f8cc75117e62bcc72cd068 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 28 Jul 2021 18:03:49 -0400 Subject: [PATCH] fix Data\Movies error regression when patching dictionary (#711) --- .../TemporaryHacks/MiniMonoModHotfix.cs | 101 ++++++++++++++---- 1 file changed, 82 insertions(+), 19 deletions(-) diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs index 9f5819d7..9d63ab2c 100644 --- a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -8,12 +8,12 @@ // Special thanks to 0x0ade for submitting this worokaround! Copy/pasted and adapted from MonoMod. using System; +using System.Reflection; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Reflection.Emit; using System.Runtime.CompilerServices; using HarmonyLib; +using System.Reflection.Emit; // ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author. namespace MonoMod.Utils @@ -24,38 +24,38 @@ namespace MonoMod.Utils { // .NET Framework can break member ordering if using Module.Resolve* on certain members. - private static readonly object[] _NoArgs = new object[0]; - private static readonly object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null }; + private static object[] _NoArgs = new object[0]; + private static object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null }; - private static readonly Type t_RuntimeModule = + private static Type t_RuntimeModule = typeof(Module).Assembly .GetType("System.Reflection.RuntimeModule"); - private static readonly PropertyInfo p_RuntimeModule_RuntimeType = + private static PropertyInfo p_RuntimeModule_RuntimeType = typeof(Module).Assembly .GetType("System.Reflection.RuntimeModule") ?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly Type t_RuntimeType = + private static Type t_RuntimeType = typeof(Type).Assembly .GetType("System.RuntimeType"); - private static readonly PropertyInfo p_RuntimeType_Cache = + private static PropertyInfo p_RuntimeType_Cache = typeof(Type).Assembly .GetType("System.RuntimeType") ?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly MethodInfo m_RuntimeTypeCache_GetFieldList = + private static MethodInfo m_RuntimeTypeCache_GetFieldList = typeof(Type).Assembly .GetType("System.RuntimeType+RuntimeTypeCache") ?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly MethodInfo m_RuntimeTypeCache_GetPropertyList = + private static MethodInfo m_RuntimeTypeCache_GetPropertyList = typeof(Type).Assembly .GetType("System.RuntimeType+RuntimeTypeCache") ?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly ConditionalWeakTable _CacheFixed = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable _CacheFixed = new ConditionalWeakTable(); public static void Apply() { @@ -143,24 +143,80 @@ namespace MonoMod.Utils if (!t_RuntimeType.IsInstanceOfType(type)) continue; - _CacheFixed.GetValue(type, rt => - { + CacheFixEntry entry = _CacheFixed.GetValue(type, rt => { + CacheFixEntry entryNew = new CacheFixEntry(); + object cache; + Array properties, fields; - object cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs); - _FixReflectionCacheOrder(cache, m_RuntimeTypeCache_GetPropertyList); - _FixReflectionCacheOrder(cache, m_RuntimeTypeCache_GetFieldList); + // All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null. + entryNew.Cache = cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs); + entryNew.Properties = properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList); + entryNew.Fields = fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); - return new object(); + _FixReflectionCacheOrder(properties); + _FixReflectionCacheOrder(fields); + + entryNew.NeedsVerify = false; + return entryNew; }); + + if (entry.NeedsVerify && !_Verify(entry, type)) + { + lock (entry) + { + _FixReflectionCacheOrder(entry.Properties); + _FixReflectionCacheOrder(entry.Fields); + } + } + + entry.NeedsVerify = true; } } - private static void _FixReflectionCacheOrder(object cache, MethodInfo getter) where T : MemberInfo + private static bool _Verify(CacheFixEntry entry, Type type) + { + object cache; + Array properties, fields; + + // The cache can sometimes be invalidated. + // TODO: Figure out if only the arrays get replaced or if the entire cache object gets replaced! + if (entry.Cache != (cache = p_RuntimeType_Cache.GetValue(type, _NoArgs))) + { + entry.Cache = cache; + entry.Properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList); + entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); + return false; + + } + else if (entry.Properties != (properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList))) + { + entry.Properties = properties; + entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); + return false; + + } + else if (entry.Fields != (fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList))) + { + entry.Fields = fields; + return false; + + } + else + { + // Cache should still be the same, no re-fix necessary. + return true; + } + } + + private static Array _GetArray(object cache, MethodInfo getter) { // Get and discard once, otherwise we might not be getting the actual backing array. getter.Invoke(cache, _CacheGetterArgs); - Array orig = (Array)getter.Invoke(cache, _CacheGetterArgs); + return (Array)getter.Invoke(cache, _CacheGetterArgs); + } + private static void _FixReflectionCacheOrder(Array orig) where T : MemberInfo + { // Sort using a short-lived list. List list = new List(orig.Length); for (int i = 0; i < orig.Length; i++) @@ -172,5 +228,12 @@ namespace MonoMod.Utils orig.SetValue(list[i], i); } + private class CacheFixEntry + { + public object Cache; + public Array Properties; + public Array Fields; + public bool NeedsVerify; + } } }