public void Test_CanGetInstructionsWithNoILGenerator() { var method = typeof(Class12).GetMethod(nameof(Class12.FizzBuzz)); var newGenerator = PatchProcessor.CreateILGenerator(method); var instrsNoGen = MethodBodyReader.GetInstructions(generator: null, method); var instrsHasGen = MethodBodyReader.GetInstructions(newGenerator, method); Assert.AreEqual(instrsNoGen.Count, instrsHasGen.Count); for (var i = 0; i < instrsNoGen.Count; i++) { var instrNoGen = instrsNoGen[i]; var instrHasGen = instrsHasGen[i]; Assert.AreEqual(instrNoGen.offset, instrHasGen.offset, "offset @ {0} ({1})", i, instrNoGen); Assert.AreEqual(instrNoGen.opcode, instrHasGen.opcode, "opcode @ {0} ({1})", i, instrNoGen); AssertAreEqual(instrNoGen.operand, instrHasGen.operand, "operand", i, instrNoGen); CollectionAssert.AreEqual(instrNoGen.labels, instrHasGen.labels, "labels @ {0}", i); CollectionAssert.AreEqual(instrNoGen.blocks, instrHasGen.blocks, "blocks @ {0}", i); AssertAreEqual(instrNoGen.argument, instrHasGen.argument, "argument", i, instrNoGen); // The only difference between w/o gen and w/ gen is this: var operandType = instrNoGen.opcode.OperandType; if ((operandType == OperandType.ShortInlineVar || operandType == OperandType.InlineVar) && instrNoGen.argument is object) { #if NETCOREAPP3_0 || NETCOREAPP3_1 || NET5_0 Assert.AreEqual("System.Reflection.RuntimeLocalVariableInfo", instrNoGen.argument.GetType().FullName, "w/o generator argument type @ {0} ({1})", i, instrNoGen); #else Assert.AreEqual("System.Reflection.LocalVariableInfo", instrNoGen.argument.GetType().FullName, "w/o generator argument type @ {0} ({1})", i, instrNoGen); #endif Assert.AreEqual(typeof(LocalBuilder), instrHasGen.argument.GetType(), "w/ generator argument type @ {0} ({1})", i, instrNoGen); } } }
/// <summary> /// Compiles a method into IL instructions exactly the same as the Harmony transpiler. /// </summary> /// <param name="method">Method to compile.</param> /// <returns> /// List of ILInstructions. /// </returns> /// <remarks> /// Utilizes harmony library. /// </remarks> public static List <CodeInstruction> MethodToILInstructions(MethodBase method) { // Get il generator ILGenerator il_gen = PatchProcessor.CreateILGenerator(method); List <Label> labels = new List <Label>(); List <MethodInfo> transpilers = new List <MethodInfo>(); bool hasReturn = false; // internal Emitter(ILGenerator il, bool debug) object emitter = Activator.CreateInstance( t_Emitter, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { il_gen, true }, null); // internal MethodBodyReader(MethodBase method, ILGenerator generator) object methodBodyReader = Activator.CreateInstance( t_MethodBodyReader, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { method, il_gen }, null); // internal void DeclareVariables(LocalBuilder[] existingVariables) t_MethodBodyReader.GetMethod("DeclareVariables", BindingFlags.NonPublic | BindingFlags.Instance) .Invoke(methodBodyReader, new object[] { null }); // internal void ReadInstructions() t_MethodBodyReader.GetMethod("ReadInstructions", BindingFlags.NonPublic | BindingFlags.Instance) .Invoke(methodBodyReader, new object[0]); // internal List<CodeInstruction> FinalizeILCodes(Emitter emitter, List<MethodInfo> transpilers, List<Label> endLabels, out bool hasReturnCode) List <CodeInstruction> generated_instructions = (List <CodeInstruction>)t_MethodBodyReader .GetMethod("FinalizeILCodes", BindingFlags.NonPublic | BindingFlags.Instance) .Invoke(methodBodyReader, new object[] { emitter, transpilers, labels, hasReturn }); // Add final return statement as harmony adds it manually after??? generated_instructions.Add(new CodeInstruction(OpCodes.Ret, null)); return(generated_instructions); }