From 0bf692addc3e309a8448de9ffb2a41cb701cfddf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 26 Aug 2020 23:11:41 -0400 Subject: add heuristic rewrite for field => const changes --- .../ModLoading/Framework/RewriteHelper.cs | 18 ++++++ .../ModLoading/Rewriters/HeuristicFieldRewriter.cs | 65 +++++++++++++++++----- .../Rewriters/HeuristicMethodRewriter.cs | 20 +------ .../Rewriters/StaticFieldToConstantRewriter.cs | 65 ---------------------- src/SMAPI/Metadata/InstructionMetadata.cs | 3 - 5 files changed, 70 insertions(+), 101 deletions(-) delete mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs (limited to 'src') diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 36058b86..4b88148f 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -59,6 +59,24 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework : null; } + /// 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. + public static 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 + }; + } + /// Get whether a type matches a type reference. /// The defined type. /// The type reference. diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index 5a088ed8..ca04205c 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -6,7 +6,7 @@ using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters { - /// Automatically fix references to fields that have been replaced by a property. + /// Automatically fix references to fields that have been replaced by a property or const field. internal class HeuristicFieldRewriter : BaseInstructionHandler { /********* @@ -36,14 +36,40 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // skip if not broken - if (fieldRef.Resolve() != null) + FieldDefinition fieldDefinition = fieldRef.Resolve(); + if (fieldDefinition != null && !fieldDefinition.HasConstant) return false; + // rewrite if possible + TypeDefinition declaringType = fieldRef.DeclaringType.Resolve(); + bool isRead = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld; + return + this.TryRewriteToProperty(module, instruction, fieldRef, declaringType, isRead) + || this.TryRewriteToConstField(instruction, fieldDefinition); + } + + + /********* + ** 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); + } + + /// Try rewriting the field into a matching property. + /// The assembly module containing the instruction. + /// The CIL instruction to rewrite. + /// The field reference. + /// The type on which the field was defined. + /// Whether the field is being read; else it's being written to. + private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead) + { // 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; + PropertyDefinition property = declaringType.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); + MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod; if (method == null) return false; @@ -55,15 +81,26 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return this.MarkRewritten(); } - - /********* - ** Private methods - *********/ - /// Whether references to the given type should be validated. - /// The type reference. - private bool ShouldValidate(TypeReference type) + /// Try rewriting the field into a matching const field. + /// The CIL instruction to rewrite. + /// The field definition. + private bool TryRewriteToConstField(Instruction instruction, FieldDefinition field) { - return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); + // must have been a static field read, and the new field must be const + if (instruction.OpCode != OpCodes.Ldsfld || field?.HasConstant != true) + return false; + + // get opcode for value type + Instruction loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant); + if (loadInstruction == null) + return false; + + // rewrite to constant + instruction.OpCode = loadInstruction.OpCode; + instruction.Operand = loadInstruction.Operand; + + this.Phrases.Add($"{field.DeclaringType.Name}.{field.Name} (field => const)"); + return this.MarkRewritten(); } } } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index 21b42e12..e133b6fa 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -64,7 +64,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters // get instructions to inject parameter values var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) - .Select(p => this.GetLoadValueInstruction(p.Constant)) + .Select(p => RewriteHelper.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 @@ -105,23 +105,5 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters 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/StaticFieldToConstantRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs deleted file mode 100644 index f34d4943..00000000 --- a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.Framework.ModLoading.Framework; - -namespace StardewModdingAPI.Framework.ModLoading.Rewriters -{ - /// Rewrites static field references into constant values. - /// The constant value type. - internal class StaticFieldToConstantRewriter : BaseInstructionHandler - { - /********* - ** Fields - *********/ - /// The type containing the field to which references should be rewritten. - private readonly Type Type; - - /// The field name to which references should be rewritten. - private readonly string FromFieldName; - - /// The constant value to replace with. - private readonly TValue Value; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type whose field to which references should be rewritten. - /// The field name to rewrite. - /// The constant value to replace with. - public StaticFieldToConstantRewriter(Type type, string fieldName, TValue value) - : base(defaultPhrase: $"{type.FullName}.{fieldName} field") - { - this.Type = type; - this.FromFieldName = fieldName; - this.Value = value; - } - - /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) - { - // get field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - if (!RewriteHelper.IsFieldReferenceTo(fieldRef, this.Type.FullName, this.FromFieldName)) - return false; - - // rewrite to constant - if (typeof(TValue) == typeof(int)) - { - instruction.OpCode = OpCodes.Ldc_I4; - instruction.Operand = this.Value; - } - else if (typeof(TValue) == typeof(string)) - { - instruction.OpCode = OpCodes.Ldstr; - instruction.Operand = this.Value; - } - else - throw new NotSupportedException($"Rewriting to constant values of type {typeof(TValue)} isn't currently supported."); - - return this.MarkRewritten(); - } - } -} diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 86e16e1e..09a199f9 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -35,9 +35,6 @@ namespace StardewModdingAPI.Metadata if (platformChanged) yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchFacade)); - // rewrite for Stardew Valley 1.3 - yield return new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize); - // heuristic rewrites yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies); yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies); -- cgit