From 88f754e5b134f43ed6c7a833834aaeb92e44a62e Mon Sep 17 00:00:00 2001 From: Evan Behar Date: Sat, 7 Jul 2018 23:45:02 -0700 Subject: Expand validation to respect CIL placeholders --- .../ReferenceToMemberWithUnexpectedTypeFinder.cs | 7 +- src/SMAPI/Framework/ModLoading/RewriteHelper.cs | 163 +++++++++++++++++++++ 2 files changed, 167 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index bd5c97d6..79db6921 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -67,7 +67,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders // validate return type string actualReturnTypeID = this.GetComparableTypeID(targetField.FieldType); string expectedReturnTypeID = this.GetComparableTypeID(fieldRef.FieldType); - if (actualReturnTypeID != expectedReturnTypeID) + + if (!RewriteHelper.LooksLikeSameType(expectedReturnTypeID, actualReturnTypeID)) { 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; @@ -110,8 +111,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The type reference. private bool ShouldValidate(TypeReference type) { - if (type == null) - return false; + if (type != null) + return true; // Extract scope name from type string representation for compatibility // Under Linux, type.Scope.Name sometimes reports incorrectly diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs index 56a60a72..9eb8b3a5 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Mono.Cecil; @@ -90,5 +91,167 @@ namespace StardewModdingAPI.Framework.ModLoading .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); } + + /// Determine whether this type ID has a placeholder such as !0. + /// The type to check. + /// true if the type ID contains a placeholder, false if not. + public static bool HasPlaceholder(string typeID) + { + return typeID.Contains("!0"); + } + + /// returns whether this type ID is a placeholder, i.e., it begins with "!". + /// The symbol to validate. + /// true if the symbol is a placeholder, false if not + public static bool IsPlaceholder(string symbol) + { + return symbol.StartsWith("!"); + } + + /// Determine whether two type IDs look like the same type, accounting for placeholder values such as !0. + /// The type ID to compare. + /// The other type ID to compare. + /// true if the type IDs look like the same type, false if not. + public static bool LooksLikeSameType(string typeA, string typeB) + { + string placeholderType, actualType = ""; + + if (RewriteHelper.HasPlaceholder(typeA)) + { + placeholderType = typeA; + actualType = typeB; + } else if (RewriteHelper.HasPlaceholder(typeB)) + { + placeholderType = typeB; + actualType = typeA; + } else + { + return typeA == typeB; + } + + return RewriteHelper.PlaceholderTypeValidates(placeholderType, actualType); + } + + protected class SymbolLocation + { + public string symbol; + public int depth; + + public SymbolLocation(string symbol, int depth) + { + this.symbol = symbol; + this.depth = depth; + } + } + + private static List symbolBoundaries = new List{'<', '>', ','}; + + /// Traverses and parses out symbols from a type which does not contain placeholder values. + /// The type to traverse. + /// A List in which to store the parsed symbols. + private static void TraverseActualType(string type, List typeSymbols) + { + int depth = 0; + string symbol = ""; + + foreach (char c in type) + { + if (RewriteHelper.symbolBoundaries.Contains(c)) + { + typeSymbols.Add(new SymbolLocation(symbol, depth)); + symbol = ""; + switch (c) { + case '<': + depth++; + break; + case '>': + depth--; + break; + default: + break; + } + } + else + symbol += c; + } + } + + /// Determines whether two symbols in a type ID match, accounting for placeholders such as !0. + /// A symbol in a typename which contains placeholders. + /// A symbol in a typename which does not contain placeholders. + /// A dictionary containing a mapping of placeholders to concrete types. + /// true if the symbols match, false if not. + private static bool SymbolsMatch(SymbolLocation symbolA, SymbolLocation symbolB, Dictionary placeholderMap) + { + System.Console.Write($"comparing {symbolA.symbol} at depth {symbolA.depth} to {symbolB.symbol} at {symbolB.depth}"); + if (symbolA.depth != symbolB.depth) + return false; + + if (!RewriteHelper.IsPlaceholder(symbolA.symbol)) { + return symbolA.symbol == symbolB.symbol; + } + + if (placeholderMap.ContainsKey(symbolA.symbol)) + { + return placeholderMap[symbolA.symbol] == symbolB.symbol; + } + + placeholderMap[symbolA.symbol] = symbolB.symbol; + + return true; + } + + /// Determines whether a type which has placeholders correctly resolves to the concrete type provided. + /// A type containing placeholders such as !0. + /// The list of symbols extracted from the concrete type. + /// true if the type resolves correctly, false if not. + private static bool PlaceholderTypeResolvesToActualType(string type, List typeSymbols) + { + Dictionary placeholderMap = new Dictionary(); + + int depth = 0, symbolCount = 0; + string symbol = ""; + + foreach (char c in type) + { + if (symbolBoundaries.Contains(c)) + { + bool match = RewriteHelper.SymbolsMatch(new SymbolLocation(symbol, depth), typeSymbols[symbolCount], placeholderMap); + System.Console.Write($"match: {match}"); + if (typeSymbols.Count <= symbolCount || + !match) + return false; + + symbolCount++; + symbol = ""; + switch (c) + { + case '<': + depth++; + break; + case '>': + depth--; + break; + default: + break; + } + } + else + symbol += c; + } + + return true; + } + + /// Determines whether a type with placeholders in it matches a type without placeholders. + /// The type with placeholders in it. + /// The type without placeholders. + /// true if the placeholder type can resolve to the actual type, false if not. + private static bool PlaceholderTypeValidates(string placeholderType, string actualType) { + List typeSymbols = new List(); + + RewriteHelper.TraverseActualType(actualType, typeSymbols); + return PlaceholderTypeResolvesToActualType(placeholderType, typeSymbols); + } } } -- cgit