From 9a9622702ab310261ebedd5e0310f5f40f6812a1 Mon Sep 17 00:00:00 2001
From: Jesse Plamondon-Willard <github@jplamondonw.com>
Date: Sun, 4 Mar 2018 02:17:44 -0500
Subject: fix misplaced file (#453)

---
 .../ReferenceToMemberWithUnexpectedTypeFinder.cs   | 131 +++++++++++++++++++++
 1 file changed, 131 insertions(+)
 create mode 100644 src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs

(limited to 'src/SMAPI/Framework/ModLoading/Finders')

diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
new file mode 100644
index 00000000..2aee7af4
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
@@ -0,0 +1,131 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace StardewModdingAPI.Framework.ModLoading.Finders
+{
+    /// <summary>Finds references to a field, property, or method which returns a different type than the code expects.</summary>
+    /// <remarks>This implementation is purely heuristic. It should never return a false positive, but won't detect all cases.</remarks>
+    internal class ReferenceToMemberWithUnexpectedTypeFinder : IInstructionHandler
+    {
+        /*********
+        ** Properties
+        *********/
+        /// <summary>A pattern matching type name substrings to strip for display.</summary>
+        private readonly Regex StripTypeNamePattern = new Regex(@"`\d+(?=<)", RegexOptions.Compiled);
+
+
+        /*********
+        ** Accessors
+        *********/
+        /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
+        public string NounPhrase { get; private set; } = "";
+
+
+        /*********
+        ** Public methods
+        *********/
+        /// <summary>Perform the predefined logic for a method if applicable.</summary>
+        /// <param name="module">The assembly module containing the instruction.</param>
+        /// <param name="method">The method definition containing the instruction.</param>
+        /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
+        /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
+        public virtual InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
+        {
+            return InstructionHandleResult.None;
+        }
+
+        /// <summary>Perform the predefined logic for an instruction if applicable.</summary>
+        /// <param name="module">The assembly module containing the instruction.</param>
+        /// <param name="cil">The CIL processor.</param>
+        /// <param name="instruction">The instruction to handle.</param>
+        /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
+        /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
+        public virtual InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
+        {
+            // field reference
+            FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
+            if (fieldRef != null)
+            {
+                // get target field
+                FieldDefinition targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name);
+                if (targetField == null)
+                    return InstructionHandleResult.None;
+
+                // validate return type
+                string actualReturnTypeID = this.GetComparableTypeID(targetField.FieldType);
+                string expectedReturnTypeID = this.GetComparableTypeID(fieldRef.FieldType);
+                if (actualReturnTypeID != expectedReturnTypeID)
+                {
+                    this.NounPhrase = $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (field returns {this.GetFriendlyTypeName(targetField.FieldType, actualReturnTypeID)}, not {this.GetFriendlyTypeName(fieldRef.FieldType, expectedReturnTypeID)})";
+                    return InstructionHandleResult.NotCompatible;
+                }
+            }
+
+            // method reference
+            MethodReference methodReference = RewriteHelper.AsMethodReference(instruction);
+            if (methodReference != null)
+            {
+                // get potential targets
+                MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray();
+                if (candidateMethods == null || !candidateMethods.Any())
+                    return InstructionHandleResult.None;
+
+                // compare return types
+                MethodDefinition methodDef = methodReference.Resolve();
+                if (methodDef == null)
+                {
+                    this.NounPhrase = $"reference to {methodReference.DeclaringType.FullName}.{methodReference.Name} (no such method)";
+                    return InstructionHandleResult.NotCompatible;
+                }
+
+                string expectedReturnType = this.GetComparableTypeID(methodDef.ReturnType);
+                if (candidateMethods.All(method => this.GetComparableTypeID(method.ReturnType) != expectedReturnType))
+                {
+                    this.NounPhrase = $"reference to {methodDef.DeclaringType.FullName}.{methodDef.Name} (no such method returns {this.GetFriendlyTypeName(methodDef.ReturnType, expectedReturnType)})";
+                    return InstructionHandleResult.NotCompatible;
+                }
+            }
+
+            return InstructionHandleResult.None;
+        }
+
+
+        /*********
+        ** Private methods
+        *********/
+        /// <summary>Get a unique string representation of a type.</summary>
+        /// <param name="type">The type reference.</param>
+        private string GetComparableTypeID(TypeReference type)
+        {
+            return this.StripTypeNamePattern.Replace(type.FullName, "");
+        }
+
+        /// <summary>Get a shorter type name for display.</summary>
+        /// <param name="type">The type reference.</param>
+        /// <param name="typeID">The comparable type ID from <see cref="GetComparableTypeID"/>.</param>
+        private string GetFriendlyTypeName(TypeReference type, string typeID)
+        {
+            // most common built-in types
+            switch (type.FullName)
+            {
+                case "System.Boolean":
+                    return "bool";
+                case "System.Int32":
+                    return "int";
+                case "System.String":
+                    return "string";
+            }
+
+            // most common unambiguous namespaces
+            foreach (string @namespace in new[] { "Microsoft.Xna.Framework", "Netcode", "System", "System.Collections.Generic" })
+            {
+                if (type.Namespace == @namespace)
+                    return typeID.Substring(@namespace.Length + 1);
+            }
+
+            return typeID;
+        }
+    }
+}
-- 
cgit