From e80e6564b374b9b5a37748f1a42e10025d92776f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 18 Apr 2018 20:58:25 -0400 Subject: detect broken references to fields which changed generic type (#453) Previously generic types couldn't be compared correctly, since we'd end up with false differences like "Dictionary`1 != Dictionary". That seems to be fixed now, possibly due to the PDB file being included. --- .../ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 4 ---- 1 file changed, 4 deletions(-) (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 index b5e45742..2cbb3a8e 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -59,10 +59,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType)) { - // can't compare generic type parameters between definition and reference - if (fieldRef.FieldType.IsGenericInstance || fieldRef.FieldType.IsGenericParameter) - return InstructionHandleResult.None; - // get target field FieldDefinition targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); if (targetField == null) -- cgit From 1b527f0b2573ba54024cdee80680983030246f4f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 19 Apr 2018 19:11:50 -0400 Subject: detect broken references to methods which changed generic return type (#453) Previously generic types couldn't be compared correctly, since we'd end up with false differences like "Dictionary`1 != Dictionary". That seems to be fixed now, possibly due to the PDB file being included. --- .../ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 4 ---- 1 file changed, 4 deletions(-) (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 index 2cbb3a8e..ecad649a 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -78,10 +78,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders MethodReference methodReference = RewriteHelper.AsMethodReference(instruction); if (methodReference != null && this.ShouldValidate(methodReference.DeclaringType)) { - // can't compare generic type parameters between definition and reference - if (methodReference.ReturnType.IsGenericInstance || methodReference.ReturnType.IsGenericParameter) - return InstructionHandleResult.None; - // get potential targets MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); if (candidateMethods == null || !candidateMethods.Any()) -- cgit From 08b37c70a35edd413e0da0c408e77d255200cf63 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 24 Jun 2018 23:04:23 -0400 Subject: move type match lambda up into TypeFinder (#532) --- src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs | 10 ++++++++-- .../ModLoading/Rewriters/TypeReferenceRewriter.cs | 18 +++++++----------- src/SMAPI/Metadata/InstructionMetadata.cs | 8 ++++---- 3 files changed, 19 insertions(+), 17 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Finders') diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index 45349def..79045241 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -16,6 +17,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The result to return for matching instructions. private readonly InstructionHandleResult Result; + /// A lambda which overrides a matched type. + protected readonly Func ShouldIgnore; + /********* ** Accessors @@ -30,11 +34,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// Construct an instance. /// The full type name to match. /// The result to return for matching instructions. - public TypeFinder(string fullTypeName, InstructionHandleResult result) + /// A lambda which overrides a matched type. + public TypeFinder(string fullTypeName, InstructionHandleResult result, Func shouldIgnore = null) { this.FullTypeName = fullTypeName; this.Result = result; this.NounPhrase = $"{fullTypeName} type"; + this.ShouldIgnore = shouldIgnore ?? (p => false); } /// Perform the predefined logic for a method if applicable. @@ -113,7 +119,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders protected bool IsMatch(TypeReference type) { // root type - if (type.FullName == this.FullTypeName) + if (type.FullName == this.FullTypeName && !this.ShouldIgnore(type)) return true; // generic arguments diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index de9c439a..1bef4df4 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -17,9 +17,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// The new type to reference. private readonly Type ToType; - /// A lambda which indicates whether a matching type reference should be rewritten. - private readonly Func ShouldRewrite; - /********* ** Public methods @@ -27,13 +24,12 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// Construct an instance. /// The full type name to which to find references. /// The new type to reference. - /// A lambda which indicates whether a matching type reference should be rewritten. - public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func shouldRewrite = null) - : base(fromTypeFullName, InstructionHandleResult.None) + /// A lambda which overrides a matched type. + public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func shouldIgnore = null) + : base(fromTypeFullName, InstructionHandleResult.None, shouldIgnore) { this.FromTypeName = fromTypeFullName; this.ToType = toType; - this.ShouldRewrite = shouldRewrite ?? (type => true); } /// Perform the predefined logic for a method if applicable. @@ -138,22 +134,22 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// The type to replace if it matches. private TypeReference RewriteIfNeeded(ModuleDefinition module, TypeReference type) { - // root type + // current type if (type.FullName == this.FromTypeName) { - if (!this.ShouldRewrite(type)) + if (this.ShouldIgnore(type)) return type; return module.ImportReference(this.ToType); } - // generic arguments + // recurse into generic arguments if (type is GenericInstanceType genericType) { for (int i = 0; i < genericType.GenericArguments.Count; i++) genericType.GenericArguments[i] = this.RewriteIfNeeded(module, genericType.GenericArguments[i]); } - // generic parameters (e.g. constraints) + // recurse into generic parameters (e.g. constraints) for (int i = 0; i < type.GenericParameters.Count; i++) type.GenericParameters[i] = new GenericParameter(this.RewriteIfNeeded(module, type.GenericParameters[i])); diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index c5128eb1..aa3e743c 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -38,10 +38,10 @@ namespace StardewModdingAPI.Metadata new VirtualEntryCallRemover(), // rewrite for SMAPI 2.6 (types moved into SMAPI.Toolkit.CoreInterfaces) - new TypeReferenceRewriter("StardewModdingAPI.IManifest", typeof(IManifest), type => type.Scope.Name == "StardewModdingAPI"), - new TypeReferenceRewriter("StardewModdingAPI.IManifestContentPackFor", typeof(IManifestContentPackFor), type => type.Scope.Name == "StardewModdingAPI"), - new TypeReferenceRewriter("StardewModdingAPI.IManifestDependency", typeof(IManifestDependency), type => type.Scope.Name == "StardewModdingAPI"), - new TypeReferenceRewriter("StardewModdingAPI.ISemanticVersion", typeof(ISemanticVersion), type => type.Scope.Name == "StardewModdingAPI"), + new TypeReferenceRewriter("StardewModdingAPI.IManifest", typeof(IManifest), shouldIgnore: type => type.Scope.Name != "StardewModdingAPI"), + new TypeReferenceRewriter("StardewModdingAPI.IManifestContentPackFor", typeof(IManifestContentPackFor), shouldIgnore: type => type.Scope.Name != "StardewModdingAPI"), + new TypeReferenceRewriter("StardewModdingAPI.IManifestDependency", typeof(IManifestDependency), shouldIgnore: type => type.Scope.Name != "StardewModdingAPI"), + new TypeReferenceRewriter("StardewModdingAPI.ISemanticVersion", typeof(ISemanticVersion), shouldIgnore: type => type.Scope.Name != "StardewModdingAPI"), // rewrite for Stardew Valley 1.3 new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize), -- cgit From 2b2860637d36b17d51ce279afaa4d81cefef289d Mon Sep 17 00:00:00 2001 From: Evan Behar Date: Fri, 6 Jul 2018 23:08:09 -0700 Subject: Linux-compatible scope resolution in validator --- .../Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (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 index ecad649a..6364cec8 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -110,7 +110,15 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The type reference. private bool ShouldValidate(TypeReference type) { - return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); + // Extract scope name from type string representation for compatibility + // Under Linux, type.Scope.Name sometimes reports incorrectly + string scopeName = type.ToString(); + if (scopeName[0] != '$') + return false; + + scopeName = scopeName.Substring(0, scopeName.IndexOf(".", System.StringComparison.CurrentCulture)); + + return type != null && this.ValidateReferencesToAssemblies.Contains(scopeName); } /// Get a unique string representation of a type. -- cgit From 829e24b23e23ed44392c07d266107bf4a2f36998 Mon Sep 17 00:00:00 2001 From: "E. Behar" Date: Fri, 6 Jul 2018 23:21:13 -0700 Subject: Fix type==null case --- .../ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (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 index 6364cec8..bd5c97d6 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -110,6 +110,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The type reference. private bool ShouldValidate(TypeReference type) { + if (type == null) + return false; + // Extract scope name from type string representation for compatibility // Under Linux, type.Scope.Name sometimes reports incorrectly string scopeName = type.ToString(); @@ -118,7 +121,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders scopeName = scopeName.Substring(0, scopeName.IndexOf(".", System.StringComparison.CurrentCulture)); - return type != null && this.ValidateReferencesToAssemblies.Contains(scopeName); + return this.ValidateReferencesToAssemblies.Contains(scopeName); } /// Get a unique string representation of a type. -- cgit 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/SMAPI/Framework/ModLoading/Finders') 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 From a30794894bd9bd3e152c882286f0f3600ea41400 Mon Sep 17 00:00:00 2001 From: Evan Behar Date: Sat, 7 Jul 2018 23:46:28 -0700 Subject: Revert ShouldValidate --- .../Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (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 index 79db6921..88ba36ee 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -111,18 +111,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The type reference. private bool ShouldValidate(TypeReference type) { - if (type != null) - return true; - - // Extract scope name from type string representation for compatibility - // Under Linux, type.Scope.Name sometimes reports incorrectly - string scopeName = type.ToString(); - if (scopeName[0] != '$') - return false; - - scopeName = scopeName.Substring(0, scopeName.IndexOf(".", System.StringComparison.CurrentCulture)); - - return this.ValidateReferencesToAssemblies.Contains(scopeName); + return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } /// Get a unique string representation of a type. -- cgit From 40fbafdb73d0501f5239d3b857b6cb3bf2929bab Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 8 Jul 2018 13:26:07 -0400 Subject: fix new logic not applied to method return types --- .../ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (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 index 88ba36ee..47c8b33c 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -93,7 +93,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } string expectedReturnType = this.GetComparableTypeID(methodDef.ReturnType); - if (candidateMethods.All(method => this.GetComparableTypeID(method.ReturnType) != expectedReturnType)) + if (candidateMethods.All(method => !RewriteHelper.LooksLikeSameType(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; -- cgit From befeafd31d7a3351cb138c210b26f126716d05f0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 8 Jul 2018 13:56:46 -0400 Subject: encapsulate GetComparableTypeID --- .../ReferenceToMemberWithUnexpectedTypeFinder.cs | 30 ++++----------- src/SMAPI/Framework/ModLoading/RewriteHelper.cs | 43 ++++++++++++++++------ 2 files changed, 39 insertions(+), 34 deletions(-) (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 index 47c8b33c..cf5a3175 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using Mono.Cecil; using Mono.Cecil.Cil; @@ -16,9 +15,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The assembly names to which to heuristically detect broken references. private readonly HashSet ValidateReferencesToAssemblies; - /// A pattern matching type name substrings to strip for display. - private readonly Regex StripTypeNamePattern = new Regex(@"`\d+(?=<)", RegexOptions.Compiled); - /********* ** Accessors @@ -65,12 +61,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders return InstructionHandleResult.None; // validate return type - string actualReturnTypeID = this.GetComparableTypeID(targetField.FieldType); - string expectedReturnTypeID = this.GetComparableTypeID(fieldRef.FieldType); - - if (!RewriteHelper.LooksLikeSameType(expectedReturnTypeID, actualReturnTypeID)) + if (!RewriteHelper.LooksLikeSameType(fieldRef.FieldType, targetField.FieldType)) { - this.NounPhrase = $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (field returns {this.GetFriendlyTypeName(targetField.FieldType, actualReturnTypeID)}, not {this.GetFriendlyTypeName(fieldRef.FieldType, expectedReturnTypeID)})"; + this.NounPhrase = $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (field returns {this.GetFriendlyTypeName(targetField.FieldType)}, not {this.GetFriendlyTypeName(fieldRef.FieldType)})"; return InstructionHandleResult.NotCompatible; } } @@ -92,10 +85,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders return InstructionHandleResult.NotCompatible; } - string expectedReturnType = this.GetComparableTypeID(methodDef.ReturnType); - if (candidateMethods.All(method => !RewriteHelper.LooksLikeSameType(this.GetComparableTypeID(method.ReturnType), expectedReturnType))) + if (candidateMethods.All(method => !RewriteHelper.LooksLikeSameType(method.ReturnType, methodDef.ReturnType))) { - this.NounPhrase = $"reference to {methodDef.DeclaringType.FullName}.{methodDef.Name} (no such method returns {this.GetFriendlyTypeName(methodDef.ReturnType, expectedReturnType)})"; + this.NounPhrase = $"reference to {methodDef.DeclaringType.FullName}.{methodDef.Name} (no such method returns {this.GetFriendlyTypeName(methodDef.ReturnType)})"; return InstructionHandleResult.NotCompatible; } } @@ -114,17 +106,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } - /// Get a unique string representation of a type. - /// The type reference. - private string GetComparableTypeID(TypeReference type) - { - return this.StripTypeNamePattern.Replace(type.FullName, ""); - } - /// Get a shorter type name for display. /// The type reference. - /// The comparable type ID from . - private string GetFriendlyTypeName(TypeReference type, string typeID) + private string GetFriendlyTypeName(TypeReference type) { // most common built-in types switch (type.FullName) @@ -141,10 +125,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders foreach (string @namespace in new[] { "Microsoft.Xna.Framework", "Netcode", "System", "System.Collections.Generic" }) { if (type.Namespace == @namespace) - return typeID.Substring(@namespace.Length + 1); + return type.Name; } - return typeID; + return type.FullName; } } } diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs index 1600069d..f8684cde 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using Mono.Cecil; using Mono.Cecil.Cil; @@ -10,6 +11,13 @@ namespace StardewModdingAPI.Framework.ModLoading /// Provides helper methods for field rewriters. internal static class RewriteHelper { + /********* + ** Properties + *********/ + /// A pattern matching type name substrings to strip for display. + private static readonly Regex StripTypeNamePattern = new Regex(@"`\d+(?=<)", RegexOptions.Compiled); + + /********* ** Public methods *********/ @@ -109,29 +117,39 @@ namespace StardewModdingAPI.Framework.ModLoading } /// 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. + /// 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) + public static bool LooksLikeSameType(TypeReference a, TypeReference b) { + string typeA = RewriteHelper.GetComparableTypeID(a); + string typeB = RewriteHelper.GetComparableTypeID(b); + string placeholderType = "", actualType = ""; if (RewriteHelper.HasPlaceholder(typeA)) { placeholderType = typeA; actualType = typeB; - } else if (RewriteHelper.HasPlaceholder(typeB)) + } + else if (RewriteHelper.HasPlaceholder(typeB)) { placeholderType = typeB; actualType = typeA; - } else - { - return typeA == typeB; } + else + return typeA == typeB; return RewriteHelper.PlaceholderTypeValidates(placeholderType, actualType); } + /// Get a unique string representation of a type. + /// The type reference. + private static string GetComparableTypeID(TypeReference type) + { + return RewriteHelper.StripTypeNamePattern.Replace(type.FullName, ""); + } + protected class SymbolLocation { public string symbol; @@ -144,7 +162,7 @@ namespace StardewModdingAPI.Framework.ModLoading } } - private static List symbolBoundaries = new List{'<', '>', ','}; + private static List symbolBoundaries = new List { '<', '>', ',' }; /// Traverses and parses out symbols from a type which does not contain placeholder values. /// The type to traverse. @@ -160,7 +178,8 @@ namespace StardewModdingAPI.Framework.ModLoading { typeSymbols.Add(new SymbolLocation(symbol, depth)); symbol = ""; - switch (c) { + switch (c) + { case '<': depth++; break; @@ -186,7 +205,8 @@ namespace StardewModdingAPI.Framework.ModLoading if (symbolA.depth != symbolB.depth) return false; - if (!RewriteHelper.IsPlaceholder(symbolA.symbol)) { + if (!RewriteHelper.IsPlaceholder(symbolA.symbol)) + { return symbolA.symbol == symbolB.symbol; } @@ -245,7 +265,8 @@ namespace StardewModdingAPI.Framework.ModLoading /// 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) { + private static bool PlaceholderTypeValidates(string placeholderType, string actualType) + { List typeSymbols = new List(); RewriteHelper.TraverseActualType(actualType, typeSymbols); -- cgit From 1fd52f8b63d2369de84b7ab8d54596d2cb597abf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 9 Jul 2018 00:58:11 -0400 Subject: detect broken constructor references --- .../ModLoading/Finders/ReferenceToMissingMemberFinder.cs | 11 +++++++---- src/SMAPI/Framework/ModLoading/RewriteHelper.cs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Finders') diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index f5e33313..b95dd79c 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -67,12 +67,15 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null && this.ShouldValidate(methodRef.DeclaringType) && !this.IsUnsupported(methodRef)) { - MethodDefinition target = methodRef.DeclaringType.Resolve()?.Methods.FirstOrDefault(p => p.Name == methodRef.Name); + MethodDefinition target = methodRef.Resolve(); if (target == null) { - this.NounPhrase = this.IsProperty(methodRef) - ? $"reference to {methodRef.DeclaringType.FullName}.{methodRef.Name.Substring(4)} (no such property)" - : $"reference to {methodRef.DeclaringType.FullName}.{methodRef.Name} (no such method)"; + if (this.IsProperty(methodRef)) + this.NounPhrase = $"reference to {methodRef.DeclaringType.FullName}.{methodRef.Name.Substring(4)} (no such property)"; + else if (methodRef.Name == ".ctor") + this.NounPhrase = $"reference to {methodRef.DeclaringType.FullName}.{methodRef.Name} (no matching constructor)"; + else + this.NounPhrase = $"reference to {methodRef.DeclaringType.FullName}.{methodRef.Name} (no such method)"; return InstructionHandleResult.NotCompatible; } } diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs index 2f79809c..9ff43d45 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// The IL instruction. public static MethodReference AsMethodReference(Instruction instruction) { - return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt + return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt || instruction.OpCode == OpCodes.Newobj ? (MethodReference)instruction.Operand : null; } -- cgit