From 3a890408760d0d38a418d9830374262043e2ba13 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 24 Aug 2020 22:16:48 -0400 Subject: add rewriter for method references with missing optional parameters --- .../MethodWithMissingOptionalParameterRewriter.cs | 113 +++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs new file mode 100644 index 00000000..9db3c3fd --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs @@ -0,0 +1,113 @@ +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.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); + } + + /// Rewrite a CIL instruction reference if needed. + /// The assembly module containing the instruction. + /// The CIL processor. + /// The CIL instruction to handle. + /// 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) + { + // 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; + + // add extra parameters + foreach (ParameterDefinition parameter in method.Parameters.Skip(methodRef.Parameters.Count)) + { + methodRef.Parameters.Add(new ParameterDefinition( + name: parameter.Name, + attributes: parameter.Attributes, + parameterType: module.ImportReference(parameter.ParameterType) + )); + } + + 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; + } + } +} -- cgit