From d3c5fe0764806684cc71508abf009473b9d7bc0a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 26 Aug 2020 22:14:25 -0400 Subject: rename new heuristic rewriters for clarity --- .../Rewriters/FieldToPropertyRewriter.cs | 69 ----------- .../ModLoading/Rewriters/HeuristicFieldRewriter.cs | 69 +++++++++++ .../Rewriters/HeuristicMethodRewriter.cs | 127 +++++++++++++++++++++ .../MethodWithMissingOptionalParameterRewriter.cs | 127 --------------------- 4 files changed, 196 insertions(+), 196 deletions(-) delete mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs delete mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs deleted file mode 100644 index aaf04b79..00000000 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.Framework.ModLoading.Framework; - -namespace StardewModdingAPI.Framework.ModLoading.Rewriters -{ - /// Rewrites references to fields which no longer exist, but which have an equivalent property with the exact same name. - internal class FieldToPropertyRewriter : BaseInstructionHandler - { - /********* - ** Fields - *********/ - /// The assembly names to which to rewrite broken references. - private readonly HashSet RewriteReferencesToAssemblies; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The assembly names to which to rewrite broken references. - public FieldToPropertyRewriter(string[] rewriteReferencesToAssemblies) - : base(defaultPhrase: "field changed to property") // ignored since we specify phrases - { - this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); - } - - /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) - { - // get field ref - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - if (fieldRef == null || !this.ShouldValidate(fieldRef.DeclaringType)) - return false; - - // skip if not broken - if (fieldRef.Resolve() != null) - return false; - - // get equivalent property - PropertyDefinition property = fieldRef.DeclaringType.Resolve().Properties.FirstOrDefault(p => p.Name == fieldRef.Name); - MethodDefinition method = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld - ? property?.GetMethod - : property?.SetMethod; - if (method == null) - return false; - - // rewrite field to property - instruction.OpCode = OpCodes.Call; - instruction.Operand = module.ImportReference(method); - - this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)"); - return this.MarkRewritten(); - } - - - /********* - ** Private methods - *********/ - /// Whether references to the given type should be validated. - /// The type reference. - private bool ShouldValidate(TypeReference type) - { - return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); - } - } -} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs new file mode 100644 index 00000000..5a088ed8 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; + +namespace StardewModdingAPI.Framework.ModLoading.Rewriters +{ + /// Automatically fix references to fields that have been replaced by a property. + internal class HeuristicFieldRewriter : BaseInstructionHandler + { + /********* + ** Fields + *********/ + /// The assembly names to which to rewrite broken references. + private readonly HashSet RewriteReferencesToAssemblies; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The assembly names to which to rewrite broken references. + public HeuristicFieldRewriter(string[] rewriteReferencesToAssemblies) + : base(defaultPhrase: "field changed to property") // ignored since we specify phrases + { + this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); + } + + /// + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) + { + // get field ref + FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + if (fieldRef == null || !this.ShouldValidate(fieldRef.DeclaringType)) + return false; + + // skip if not broken + if (fieldRef.Resolve() != null) + return false; + + // get equivalent property + PropertyDefinition property = fieldRef.DeclaringType.Resolve().Properties.FirstOrDefault(p => p.Name == fieldRef.Name); + MethodDefinition method = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld + ? property?.GetMethod + : property?.SetMethod; + if (method == null) + return false; + + // rewrite field to property + instruction.OpCode = OpCodes.Call; + instruction.Operand = module.ImportReference(method); + + this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)"); + return this.MarkRewritten(); + } + + + /********* + ** Private methods + *********/ + /// Whether references to the given type should be validated. + /// The type reference. + private bool ShouldValidate(TypeReference type) + { + return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs new file mode 100644 index 00000000..21b42e12 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; + +namespace StardewModdingAPI.Framework.ModLoading.Rewriters +{ + /// Automatically fix references to methods that had extra optional parameters added. + internal class HeuristicMethodRewriter : BaseInstructionHandler + { + /********* + ** Fields + *********/ + /// The assembly names to which to rewrite broken references. + private readonly HashSet RewriteReferencesToAssemblies; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The assembly names to which to rewrite broken references. + public HeuristicMethodRewriter(string[] rewriteReferencesToAssemblies) + : base(defaultPhrase: "methods with missing parameters") // ignored since we specify phrases + { + this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); + } + + /// + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) + { + // get method ref + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) + return false; + + // skip if not broken + if (methodRef.Resolve() != null) + return false; + + // get type + var type = methodRef.DeclaringType.Resolve(); + if (type == null) + return false; + + // get method definition + MethodDefinition method = null; + foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) + { + // reference matches initial parameters of definition + if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) + continue; + + // all remaining parameters in definition are optional + if (!match.Parameters.Skip(methodRef.Parameters.Count).All(p => p.IsOptional)) + continue; + + method = match; + break; + } + if (method == null) + return false; + + // get instructions to inject parameter values + var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) + .Select(p => this.GetLoadValueInstruction(p.Constant)) + .ToArray(); + if (loadInstructions.Any(p => p == null)) + return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized + + // rewrite method reference + foreach (Instruction loadInstruction in loadInstructions) + cil.InsertBefore(instruction, loadInstruction); + instruction.Operand = module.ImportReference(method); + + this.Phrases.Add($"{methodRef.DeclaringType.Name}.{methodRef.Name} (added missing optional parameters)"); + return this.MarkRewritten(); + } + + + /********* + ** Private methods + *********/ + /// Whether references to the given type should be validated. + /// The type reference. + private bool ShouldValidate(TypeReference type) + { + return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); + } + + /// Get whether every parameter in the method reference matches the exact order and type of the parameters in the method definition. This ignores extra parameters in the definition. + /// The method reference whose parameters to check. + /// The method definition whose parameters to check against. + private bool InitialParametersMatch(MethodReference methodRef, MethodDefinition method) + { + if (methodRef.Parameters.Count > method.Parameters.Count) + return false; + + for (int i = 0; i < methodRef.Parameters.Count; i++) + { + if (!RewriteHelper.IsSameType(methodRef.Parameters[i].ParameterType, method.Parameters[i].ParameterType)) + return false; + } + + return true; + } + + /// Get the CIL instruction to load a value onto the stack. + /// The constant value to inject. + /// Returns the instruction, or null if the value type isn't supported. + private Instruction GetLoadValueInstruction(object rawValue) + { + return rawValue switch + { + null => Instruction.Create(OpCodes.Ldnull), + bool value => Instruction.Create(value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0), + int value => Instruction.Create(OpCodes.Ldc_I4, value), // int32 + long value => Instruction.Create(OpCodes.Ldc_I8, value), // int64 + float value => Instruction.Create(OpCodes.Ldc_R4, value), // float32 + double value => Instruction.Create(OpCodes.Ldc_R8, value), // float64 + string value => Instruction.Create(OpCodes.Ldstr, value), + _ => null + }; + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs deleted file mode 100644 index 89c8ede7..00000000 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.Framework.ModLoading.Framework; - -namespace StardewModdingAPI.Framework.ModLoading.Rewriters -{ - /// Rewrites references to methods which only broke because the definition has new optional parameters. - internal class MethodWithMissingOptionalParameterRewriter : BaseInstructionHandler - { - /********* - ** Fields - *********/ - /// The assembly names to which to rewrite broken references. - private readonly HashSet RewriteReferencesToAssemblies; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The assembly names to which to rewrite broken references. - public MethodWithMissingOptionalParameterRewriter(string[] rewriteReferencesToAssemblies) - : base(defaultPhrase: "methods with missing parameters") // ignored since we specify phrases - { - this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); - } - - /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) - { - // get method ref - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); - if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) - return false; - - // skip if not broken - if (methodRef.Resolve() != null) - return false; - - // get type - var type = methodRef.DeclaringType.Resolve(); - if (type == null) - return false; - - // get method definition - MethodDefinition method = null; - foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) - { - // reference matches initial parameters of definition - if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) - continue; - - // all remaining parameters in definition are optional - if (!match.Parameters.Skip(methodRef.Parameters.Count).All(p => p.IsOptional)) - continue; - - method = match; - break; - } - if (method == null) - return false; - - // get instructions to inject parameter values - var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) - .Select(p => this.GetLoadValueInstruction(p.Constant)) - .ToArray(); - if (loadInstructions.Any(p => p == null)) - return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized - - // rewrite method reference - foreach (Instruction loadInstruction in loadInstructions) - cil.InsertBefore(instruction, loadInstruction); - instruction.Operand = module.ImportReference(method); - - this.Phrases.Add($"{methodRef.DeclaringType.Name}.{methodRef.Name} (added missing optional parameters)"); - return this.MarkRewritten(); - } - - - /********* - ** Private methods - *********/ - /// Whether references to the given type should be validated. - /// The type reference. - private bool ShouldValidate(TypeReference type) - { - return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); - } - - /// Get whether every parameter in the method reference matches the exact order and type of the parameters in the method definition. This ignores extra parameters in the definition. - /// The method reference whose parameters to check. - /// The method definition whose parameters to check against. - private bool InitialParametersMatch(MethodReference methodRef, MethodDefinition method) - { - if (methodRef.Parameters.Count > method.Parameters.Count) - return false; - - for (int i = 0; i < methodRef.Parameters.Count; i++) - { - if (!RewriteHelper.IsSameType(methodRef.Parameters[i].ParameterType, method.Parameters[i].ParameterType)) - return false; - } - - return true; - } - - /// Get the CIL instruction to load a value onto the stack. - /// The constant value to inject. - /// Returns the instruction, or null if the value type isn't supported. - private Instruction GetLoadValueInstruction(object rawValue) - { - return rawValue switch - { - null => Instruction.Create(OpCodes.Ldnull), - bool value => Instruction.Create(value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0), - int value => Instruction.Create(OpCodes.Ldc_I4, value), // int32 - long value => Instruction.Create(OpCodes.Ldc_I8, value), // int64 - float value => Instruction.Create(OpCodes.Ldc_R4, value), // float32 - double value => Instruction.Create(OpCodes.Ldc_R8, value), // float64 - string value => Instruction.Create(OpCodes.Ldstr, value), - _ => null - }; - } - } -} -- cgit