diff options
Diffstat (limited to 'src/SMAPI/Framework/ModLoading/Finders')
8 files changed, 154 insertions, 287 deletions
diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 898bafb4..e1476b73 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -1,10 +1,12 @@ +using System; using Mono.Cecil; using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders { /// <summary>Finds incompatible CIL instructions that reference a given event.</summary> - internal class EventFinder : IInstructionHandler + internal class EventFinder : BaseInstructionHandler { /********* ** Fields @@ -20,13 +22,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /********* - ** Accessors - *********/ - /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary> - public string NounPhrase { get; } - - - /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> @@ -34,34 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="eventName">The event name for which to find references.</param> /// <param name="result">The result to return for matching instructions.</param> public EventFinder(string fullTypeName, string eventName, InstructionHandleResult result) + : base(defaultPhrase: $"{fullTypeName}.{eventName} event") { this.FullTypeName = fullTypeName; this.EventName = eventName; this.Result = result; - this.NounPhrase = $"{fullTypeName}.{eventName} event"; } - /// <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> + /// <summary>Rewrite a CIL instruction reference if needed.</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) + /// <param name="instruction">The CIL instruction to handle.</param> + /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param> + /// <returns>Returns whether the instruction was changed.</returns> + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith) { - return this.IsMatch(instruction) - ? this.Result - : InstructionHandleResult.None; + if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) + this.MarkFlag(this.Result); + + return false; } diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index 606ca8b7..c157ed9b 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -1,10 +1,12 @@ +using System; using Mono.Cecil; using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders { /// <summary>Finds incompatible CIL instructions that reference a given field.</summary> - internal class FieldFinder : IInstructionHandler + internal class FieldFinder : BaseInstructionHandler { /********* ** Fields @@ -20,13 +22,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /********* - ** Accessors - *********/ - /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary> - public string NounPhrase { get; } - - - /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> @@ -34,49 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="fieldName">The field name for which to find references.</param> /// <param name="result">The result to return for matching instructions.</param> public FieldFinder(string fullTypeName, string fieldName, InstructionHandleResult result) + : base(defaultPhrase: $"{fullTypeName}.{fieldName} field") { this.FullTypeName = fullTypeName; this.FieldName = fieldName; this.Result = result; - this.NounPhrase = $"{fullTypeName}.{fieldName} field"; } - /// <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> + /// <summary>Rewrite a CIL instruction reference if needed.</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) + /// <param name="instruction">The CIL instruction to handle.</param> + /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param> + /// <returns>Returns whether the instruction was changed.</returns> + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith) { - return this.IsMatch(instruction) - ? this.Result - : InstructionHandleResult.None; - } - + if (!this.Flags.Contains(this.Result) && RewriteHelper.IsFieldReferenceTo(instruction, this.FullTypeName, this.FieldName)) + this.MarkFlag(this.Result); - /********* - ** Protected methods - *********/ - /// <summary>Get whether a CIL instruction matches.</summary> - /// <param name="instruction">The IL instruction.</param> - protected bool IsMatch(Instruction instruction) - { - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - return - fieldRef != null - && fieldRef.DeclaringType.FullName == this.FullTypeName - && fieldRef.Name == this.FieldName; + return false; } } } diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index 9ca246ff..82c93a7c 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -1,10 +1,12 @@ +using System; using Mono.Cecil; using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders { /// <summary>Finds incompatible CIL instructions that reference a given method.</summary> - internal class MethodFinder : IInstructionHandler + internal class MethodFinder : BaseInstructionHandler { /********* ** Fields @@ -20,13 +22,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /********* - ** Accessors - *********/ - /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary> - public string NounPhrase { get; } - - - /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> @@ -34,34 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="methodName">The method name for which to find references.</param> /// <param name="result">The result to return for matching instructions.</param> public MethodFinder(string fullTypeName, string methodName, InstructionHandleResult result) + : base(defaultPhrase: $"{fullTypeName}.{methodName} method") { this.FullTypeName = fullTypeName; this.MethodName = methodName; this.Result = result; - this.NounPhrase = $"{fullTypeName}.{methodName} method"; } - /// <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> + /// <summary>Rewrite a CIL instruction reference if needed.</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 InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + /// <param name="instruction">The CIL instruction to handle.</param> + /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param> + /// <returns>Returns whether the instruction was changed.</returns> + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith) { - return this.IsMatch(instruction) - ? this.Result - : InstructionHandleResult.None; + if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) + this.MarkFlag(this.Result); + + return false; } diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index 0677aa88..c96d61a2 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -1,10 +1,12 @@ +using System; using Mono.Cecil; using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders { /// <summary>Finds incompatible CIL instructions that reference a given property.</summary> - internal class PropertyFinder : IInstructionHandler + internal class PropertyFinder : BaseInstructionHandler { /********* ** Fields @@ -20,13 +22,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /********* - ** Accessors - *********/ - /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary> - public string NounPhrase { get; } - - - /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> @@ -34,34 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="propertyName">The property name for which to find references.</param> /// <param name="result">The result to return for matching instructions.</param> public PropertyFinder(string fullTypeName, string propertyName, InstructionHandleResult result) + : base(defaultPhrase: $"{fullTypeName}.{propertyName} property") { this.FullTypeName = fullTypeName; this.PropertyName = propertyName; this.Result = result; - this.NounPhrase = $"{fullTypeName}.{propertyName} property"; } - /// <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> + /// <summary>Rewrite a CIL instruction reference if needed.</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) + /// <param name="instruction">The CIL instruction to handle.</param> + /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param> + /// <returns>Returns whether the instruction was changed.</returns> + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith) { - return this.IsMatch(instruction) - ? this.Result - : InstructionHandleResult.None; + if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) + this.MarkFlag(this.Result); + + return false; } diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index 459e3210..a67cfa4f 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,13 +1,15 @@ +using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; 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 + internal class ReferenceToMemberWithUnexpectedTypeFinder : BaseInstructionHandler { /********* ** Fields @@ -17,39 +19,23 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /********* - ** Accessors - *********/ - /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary> - public string NounPhrase { get; private set; } = ""; - - - /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="validateReferencesToAssemblies">The assembly names to which to heuristically detect broken references.</param> public ReferenceToMemberWithUnexpectedTypeFinder(string[] validateReferencesToAssemblies) + : base(defaultPhrase: "") { this.ValidateReferencesToAssemblies = new HashSet<string>(validateReferencesToAssemblies); } - /// <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> + /// <summary>Rewrite a CIL instruction reference if needed.</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) + /// <param name="instruction">The CIL instruction to handle.</param> + /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param> + /// <returns>Returns whether the instruction was changed.</returns> + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith) { // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); @@ -58,13 +44,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders // get target field FieldDefinition targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); if (targetField == null) - return InstructionHandleResult.None; + return false; // validate return type if (!RewriteHelper.LooksLikeSameType(fieldRef.FieldType, targetField.FieldType)) { - this.NounPhrase = $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (field returns {this.GetFriendlyTypeName(targetField.FieldType)}, not {this.GetFriendlyTypeName(fieldRef.FieldType)})"; - return InstructionHandleResult.NotCompatible; + this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (field returns {this.GetFriendlyTypeName(targetField.FieldType)}, not {this.GetFriendlyTypeName(fieldRef.FieldType)})"); + return false; } } @@ -75,21 +61,21 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders // get potential targets MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); if (candidateMethods == null || !candidateMethods.Any()) - return InstructionHandleResult.None; + return false; // compare return types MethodDefinition methodDef = methodReference.Resolve(); if (methodDef == null) - return InstructionHandleResult.None; // validated by ReferenceToMissingMemberFinder + return false; // validated by ReferenceToMissingMemberFinder 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)})"; - return InstructionHandleResult.NotCompatible; + this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {methodDef.DeclaringType.FullName}.{methodDef.Name} (no such method returns {this.GetFriendlyTypeName(methodDef.ReturnType)})"); + return false; } } - return InstructionHandleResult.None; + return false; } diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index 44b531a5..ebb62948 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,13 +1,15 @@ +using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders { /// <summary>Finds references to a field, property, or method which no longer exists.</summary> /// <remarks>This implementation is purely heuristic. It should never return a false positive, but won't detect all cases.</remarks> - internal class ReferenceToMissingMemberFinder : IInstructionHandler + internal class ReferenceToMissingMemberFinder : BaseInstructionHandler { /********* ** Fields @@ -17,39 +19,23 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /********* - ** Accessors - *********/ - /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary> - public string NounPhrase { get; private set; } = ""; - - - /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="validateReferencesToAssemblies">The assembly names to which to heuristically detect broken references.</param> public ReferenceToMissingMemberFinder(string[] validateReferencesToAssemblies) + : base(defaultPhrase: "") { this.ValidateReferencesToAssemblies = new HashSet<string>(validateReferencesToAssemblies); } - /// <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> + /// <summary>Rewrite a CIL instruction reference if needed.</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) + /// <param name="instruction">The CIL instruction to handle.</param> + /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param> + /// <returns>Returns whether the instruction was changed.</returns> + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith) { // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); @@ -58,8 +44,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders FieldDefinition target = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); if (target == null) { - this.NounPhrase = $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (no such field)"; - return InstructionHandleResult.NotCompatible; + this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (no such field)"); + return false; } } @@ -70,17 +56,20 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders MethodDefinition target = methodRef.Resolve(); if (target == null) { + string phrase = null; if (this.IsProperty(methodRef)) - this.NounPhrase = $"reference to {methodRef.DeclaringType.FullName}.{methodRef.Name.Substring(4)} (no such property)"; + phrase = $"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)"; + phrase = $"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; + phrase = $"reference to {methodRef.DeclaringType.FullName}.{methodRef.Name} (no such method)"; + + this.MarkFlag(InstructionHandleResult.NotCompatible, phrase); + return false; } } - return InstructionHandleResult.None; + return false; } diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs new file mode 100644 index 00000000..a1ade536 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -0,0 +1,51 @@ +using System; +using Mono.Cecil; +using StardewModdingAPI.Framework.ModLoading.Framework; + +namespace StardewModdingAPI.Framework.ModLoading.Finders +{ + /// <summary>Finds incompatible CIL instructions that reference types in a given assembly.</summary> + internal class TypeAssemblyFinder : BaseInstructionHandler + { + /********* + ** Fields + *********/ + /// <summary>The full assembly name to which to find references.</summary> + private readonly string AssemblyName; + + /// <summary>The result to return for matching instructions.</summary> + private readonly InstructionHandleResult Result; + + /// <summary>Get whether a matched type should be ignored.</summary> + private readonly Func<TypeReference, bool> ShouldIgnore; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="assemblyName">The full assembly name to which to find references.</param> + /// <param name="result">The result to return for matching instructions.</param> + /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> + public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null) + : base(defaultPhrase: $"{assemblyName} assembly") + { + this.AssemblyName = assemblyName; + this.Result = result; + this.ShouldIgnore = shouldIgnore; + } + + /// <summary>Rewrite a type reference if needed.</summary> + /// <param name="module">The assembly module containing the instruction.</param> + /// <param name="type">The type definition to handle.</param> + /// <param name="replaceWith">Replaces the type reference with a new one.</param> + /// <returns>Returns whether the type was changed.</returns> + public override bool Handle(ModuleDefinition module, TypeReference type, Action<TypeReference> replaceWith) + { + if (type.Scope.Name == this.AssemblyName && this.ShouldIgnore?.Invoke(type) != true) + this.MarkFlag(this.Result); + + return false; + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index 701b15f2..c285414a 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -1,31 +1,23 @@ using System; -using System.Linq; using Mono.Cecil; -using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders { /// <summary>Finds incompatible CIL instructions that reference a given type.</summary> - internal class TypeFinder : IInstructionHandler + internal class TypeFinder : BaseInstructionHandler { /********* - ** Accessors + ** Fields *********/ - /// <summary>The full type name for which to find references.</summary> + /// <summary>The full type name to match.</summary> private readonly string FullTypeName; /// <summary>The result to return for matching instructions.</summary> private readonly InstructionHandleResult Result; - /// <summary>A lambda which overrides a matched type.</summary> - protected readonly Func<TypeReference, bool> ShouldIgnore; - - - /********* - ** Accessors - *********/ - /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary> - public string NounPhrase { get; } + /// <summary>Get whether a matched type should be ignored.</summary> + private readonly Func<TypeReference, bool> ShouldIgnore; /********* @@ -34,104 +26,24 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <summary>Construct an instance.</summary> /// <param name="fullTypeName">The full type name to match.</param> /// <param name="result">The result to return for matching instructions.</param> - /// <param name="shouldIgnore">A lambda which overrides a matched type.</param> + /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null) + : base(defaultPhrase: $"{fullTypeName} type") { this.FullTypeName = fullTypeName; this.Result = result; - this.NounPhrase = $"{fullTypeName} type"; - this.ShouldIgnore = shouldIgnore ?? (p => false); - } - - /// <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 this.IsMatch(method) - ? this.Result - : InstructionHandleResult.None; + this.ShouldIgnore = shouldIgnore; } - /// <summary>Perform the predefined logic for an instruction if applicable.</summary> + /// <summary>Rewrite a type reference if needed.</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) - { - return this.IsMatch(instruction) - ? this.Result - : InstructionHandleResult.None; - } - - - /********* - ** Protected methods - *********/ - /// <summary>Get whether a CIL instruction matches.</summary> - /// <param name="method">The method definition.</param> - protected bool IsMatch(MethodDefinition method) + /// <param name="type">The type definition to handle.</param> + /// <param name="replaceWith">Replaces the type reference with a new one.</param> + /// <returns>Returns whether the type was changed.</returns> + public override bool Handle(ModuleDefinition module, TypeReference type, Action<TypeReference> replaceWith) { - if (this.IsMatch(method.ReturnType)) - return true; - - foreach (VariableDefinition variable in method.Body.Variables) - { - if (this.IsMatch(variable.VariableType)) - return true; - } - - return false; - } - - /// <summary>Get whether a CIL instruction matches.</summary> - /// <param name="instruction">The IL instruction.</param> - protected bool IsMatch(Instruction instruction) - { - // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - if (fieldRef != null) - { - return - this.IsMatch(fieldRef.DeclaringType) // field on target class - || this.IsMatch(fieldRef.FieldType); // field value is target class - } - - // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); - if (methodRef != null) - { - return - this.IsMatch(methodRef.DeclaringType) // method on target class - || this.IsMatch(methodRef.ReturnType) // method returns target class - || methodRef.Parameters.Any(p => this.IsMatch(p.ParameterType)); // method parameters - } - - return false; - } - - /// <summary>Get whether a type reference matches the expected type.</summary> - /// <param name="type">The type to check.</param> - protected bool IsMatch(TypeReference type) - { - // root type - if (type.FullName == this.FullTypeName && !this.ShouldIgnore(type)) - return true; - - // generic arguments - if (type is GenericInstanceType genericType) - { - if (genericType.GenericArguments.Any(this.IsMatch)) - return true; - } - - // generic parameters (e.g. constraints) - if (type.GenericParameters.Any(this.IsMatch)) - return true; + if (type.FullName == this.FullTypeName && this.ShouldIgnore?.Invoke(type) != true) + this.MarkFlag(this.Result); return false; } |