From 1838842bbc2db2d1049c193b8650bd101ba4858f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 19 May 2020 20:57:50 -0400 Subject: rewrite assembly rewriting, merge Harmony rewriters (#711) This reduces duplication, decouples it from the assembly loader, and makes it more flexible to handle Harmony rewriting. --- .../Framework/ModLoading/Finders/EventFinder.cs | 18 +++++----- .../Framework/ModLoading/Finders/FieldFinder.cs | 31 +++++----------- .../Framework/ModLoading/Finders/MethodFinder.cs | 18 +++++----- .../Framework/ModLoading/Finders/PropertyFinder.cs | 18 +++++----- .../ReferenceToMemberWithUnexpectedTypeFinder.cs | 27 +++++++------- .../Finders/ReferenceToMissingMemberFinder.cs | 28 ++++++++------- .../ModLoading/Finders/TypeAssemblyFinder.cs | 42 +++++++++++++++++----- .../Framework/ModLoading/Finders/TypeFinder.cs | 42 +++++++++++++++++----- 8 files changed, 137 insertions(+), 87 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Finders') diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 1a7ae636..e1476b73 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -1,3 +1,4 @@ +using System; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -28,24 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The event name for which to find references. /// The result to return for matching instructions. public EventFinder(string fullTypeName, string eventName, InstructionHandleResult result) - : base(nounPhrase: $"{fullTypeName}.{eventName} event") + : base(defaultPhrase: $"{fullTypeName}.{eventName} event") { this.FullTypeName = fullTypeName; this.EventName = eventName; this.Result = result; } - /// Perform the predefined logic for an instruction if applicable. + /// Rewrite a CIL instruction reference if needed. /// The assembly module containing the instruction. /// The CIL processor. /// The CIL instruction to handle. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + /// Replaces the CIL instruction with a new one. + /// Returns whether the instruction was changed. + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action 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 9ae07916..c157ed9b 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -1,3 +1,4 @@ +using System; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -28,39 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The field name for which to find references. /// The result to return for matching instructions. public FieldFinder(string fullTypeName, string fieldName, InstructionHandleResult result) - : base(nounPhrase: $"{fullTypeName}.{fieldName} field") + : base(defaultPhrase: $"{fullTypeName}.{fieldName} field") { this.FullTypeName = fullTypeName; this.FieldName = fieldName; this.Result = result; } - /// Perform the predefined logic for an instruction if applicable. + /// Rewrite a CIL instruction reference if needed. /// The assembly module containing the instruction. /// The CIL processor. /// The CIL instruction to handle. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + /// Replaces the CIL instruction with a new one. + /// Returns whether the instruction was changed. + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action 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 - *********/ - /// Get whether a CIL instruction matches. - /// The IL instruction. - 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 75584f1f..82c93a7c 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -1,3 +1,4 @@ +using System; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -28,24 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The method name for which to find references. /// The result to return for matching instructions. public MethodFinder(string fullTypeName, string methodName, InstructionHandleResult result) - : base(nounPhrase: $"{fullTypeName}.{methodName} method") + : base(defaultPhrase: $"{fullTypeName}.{methodName} method") { this.FullTypeName = fullTypeName; this.MethodName = methodName; this.Result = result; } - /// Perform the predefined logic for an instruction if applicable. + /// Rewrite a CIL instruction reference if needed. /// The assembly module containing the instruction. /// The CIL processor. /// The CIL instruction to handle. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + /// Replaces the CIL instruction with a new one. + /// Returns whether the instruction was changed. + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action 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 811420c5..c96d61a2 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -1,3 +1,4 @@ +using System; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -28,24 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The property name for which to find references. /// The result to return for matching instructions. public PropertyFinder(string fullTypeName, string propertyName, InstructionHandleResult result) - : base(nounPhrase: $"{fullTypeName}.{propertyName} property") + : base(defaultPhrase: $"{fullTypeName}.{propertyName} property") { this.FullTypeName = fullTypeName; this.PropertyName = propertyName; this.Result = result; } - /// Perform the predefined logic for an instruction if applicable. + /// Rewrite a CIL instruction reference if needed. /// The assembly module containing the instruction. /// The CIL processor. /// The CIL instruction to handle. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + /// Replaces the CIL instruction with a new one. + /// Returns whether the instruction was changed. + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action 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 1029d350..a67cfa4f 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -23,18 +24,18 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// Construct an instance. /// The assembly names to which to heuristically detect broken references. public ReferenceToMemberWithUnexpectedTypeFinder(string[] validateReferencesToAssemblies) - : base(nounPhrase: "") + : base(defaultPhrase: "") { this.ValidateReferencesToAssemblies = new HashSet(validateReferencesToAssemblies); } - /// Perform the predefined logic for an instruction if applicable. + /// Rewrite a CIL instruction reference if needed. /// The assembly module containing the instruction. /// The CIL processor. /// The CIL instruction to handle. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + /// Replaces the CIL instruction with a new one. + /// Returns whether the instruction was changed. + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); @@ -43,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; } } @@ -60,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 fefa88f4..ebb62948 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -23,18 +24,18 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// Construct an instance. /// The assembly names to which to heuristically detect broken references. public ReferenceToMissingMemberFinder(string[] validateReferencesToAssemblies) - : base(nounPhrase: "") + : base(defaultPhrase: "") { this.ValidateReferencesToAssemblies = new HashSet(validateReferencesToAssemblies); } - /// Perform the predefined logic for an instruction if applicable. + /// Rewrite a CIL instruction reference if needed. /// The assembly module containing the instruction. /// The CIL processor. /// The CIL instruction to handle. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + /// Replaces the CIL instruction with a new one. + /// Returns whether the instruction was changed. + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); @@ -43,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; } } @@ -55,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 index 5301186b..a1ade536 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -5,21 +5,47 @@ using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders { /// Finds incompatible CIL instructions that reference types in a given assembly. - internal class TypeAssemblyFinder : BaseTypeFinder + internal class TypeAssemblyFinder : BaseInstructionHandler { + /********* + ** Fields + *********/ + /// The full assembly name to which to find references. + private readonly string AssemblyName; + + /// The result to return for matching instructions. + private readonly InstructionHandleResult Result; + + /// Get whether a matched type should be ignored. + private readonly Func ShouldIgnore; + + /********* ** Public methods *********/ /// Construct an instance. /// The full assembly name to which to find references. /// The result to return for matching instructions. - /// A lambda which overrides a matched type. + /// Get whether a matched type should be ignored. public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func shouldIgnore = null) - : base( - isMatch: type => type.Scope.Name == assemblyName && (shouldIgnore == null || !shouldIgnore(type)), - result: result, - nounPhrase: $"{assemblyName} assembly" - ) - { } + : base(defaultPhrase: $"{assemblyName} assembly") + { + this.AssemblyName = assemblyName; + this.Result = result; + this.ShouldIgnore = shouldIgnore; + } + + /// Rewrite a type reference if needed. + /// The assembly module containing the instruction. + /// The type definition to handle. + /// Replaces the type reference with a new one. + /// Returns whether the type was changed. + public override bool Handle(ModuleDefinition module, TypeReference type, Action 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 3adc31c7..c285414a 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -5,21 +5,47 @@ using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders { /// Finds incompatible CIL instructions that reference a given type. - internal class TypeFinder : BaseTypeFinder + internal class TypeFinder : BaseInstructionHandler { + /********* + ** Fields + *********/ + /// The full type name to match. + private readonly string FullTypeName; + + /// The result to return for matching instructions. + private readonly InstructionHandleResult Result; + + /// Get whether a matched type should be ignored. + private readonly Func ShouldIgnore; + + /********* ** Public methods *********/ /// Construct an instance. /// The full type name to match. /// The result to return for matching instructions. - /// A lambda which overrides a matched type. + /// Get whether a matched type should be ignored. public TypeFinder(string fullTypeName, InstructionHandleResult result, Func shouldIgnore = null) - : base( - isMatch: type => type.FullName == fullTypeName && (shouldIgnore == null || !shouldIgnore(type)), - result: result, - nounPhrase: $"{fullTypeName} type" - ) - { } + : base(defaultPhrase: $"{fullTypeName} type") + { + this.FullTypeName = fullTypeName; + this.Result = result; + this.ShouldIgnore = shouldIgnore; + } + + /// Rewrite a type reference if needed. + /// The assembly module containing the instruction. + /// The type definition to handle. + /// Replaces the type reference with a new one. + /// Returns whether the type was changed. + public override bool Handle(ModuleDefinition module, TypeReference type, Action replaceWith) + { + if (type.FullName == this.FullTypeName && this.ShouldIgnore?.Invoke(type) != true) + this.MarkFlag(this.Result); + + return false; + } } } -- cgit