Last active
December 17, 2015 20:19
-
-
Save GeorgeHahn/5667181 to your computer and use it in GitHub Desktop.
Adding dynamic support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using Mono.Cecil; | |
| using Mono.Cecil.Rocks; | |
| using Mono.Cecil.Cil; | |
| public class LogForwardingProcessor | |
| { | |
| public MethodDefinition Method; | |
| public FieldReference Field; | |
| public Action FoundUsageInType; | |
| public Action FoundDynamicInType; | |
| bool foundUsageInMethod; | |
| ILProcessor ilProcessor; | |
| public ModuleWeaver ModuleWeaver; | |
| private List<Instruction> instructions_ldsfld = new List<Instruction>(); | |
| private List<Instruction> instructions_ldfld = new List<Instruction>(); | |
| private List<Instruction> instructions_stsfld = new List<Instruction>(); | |
| private List<Instruction> instructions_call = new List<Instruction>(); | |
| public void ProcessMethod() | |
| { | |
| try | |
| { | |
| ilProcessor = Method.Body.GetILProcessor(); | |
| // Process standard (?) calls | |
| Method.Body.Instructions.Where(x => x.OpCode == OpCodes.Call) | |
| .ToList() | |
| .ForEach(ProcessInstruction); | |
| if (Method.CheckForDynamicUsagesOf("Anotar.NLog.Log")) | |
| { | |
| if (!foundUsageInMethod) | |
| { | |
| Method.Body.InitLocals = true; | |
| Method.Body.SimplifyMacros(); | |
| foundUsageInMethod = true; | |
| } | |
| FoundUsageInType(); // TODO: WARNING: This might not belong here (GH) | |
| FoundDynamicInType(); | |
| // Perform a simple replace and record other instructions that need to be modified | |
| Method.Body.Instructions.Where(x => x.OpCode == OpCodes.Ldtoken) | |
| .ToList() | |
| .ForEach(ProcessDynamic); | |
| Method.Body.Instructions.Where(x => x.Operand as CallSite != null) | |
| .Where(x => NewInvokeCallSites.ContainsKey((x.Operand as CallSite).Name)) | |
| .ToList() | |
| .ForEach(x => ReplaceCallSite(x, NewInvokeCallSites[(x.Operand as CallSite).Name])); | |
| Method.Body.Instructions.Where(x => x.Operand as CallSite != null) | |
| .Where(x => NewCreateCallSites.ContainsKey((x.Operand as CallSite).Name)) | |
| .ToList() | |
| .ForEach(x => ReplaceCallSite(x, NewCreateCallSites[(x.Operand as CallSite).Name])); | |
| Method.Body.Instructions.Where(x => x.Operand as FieldReference != null) | |
| .Where(x => NewSiteFields.ContainsKey((x.Operand as FieldReference).Name)) | |
| .ToList() | |
| .ForEach(x => ReplaceField(x, NewSiteFields[(x.Operand as FieldReference).Name])); | |
| Method.Body.Instructions.Where(x => x.Operand as FieldReference != null) | |
| .Where(x => NewSiteTargetFields.ContainsKey((x.Operand as FieldReference).Name)) | |
| .ToList() | |
| .ForEach(x => ReplaceField(x, NewSiteTargetFields[(x.Operand as FieldReference).Name])); | |
| } | |
| if (foundUsageInMethod) | |
| { | |
| Method.Body.OptimizeMacros(); | |
| } | |
| } | |
| catch (Exception exception) | |
| { | |
| throw new Exception(string.Format("Failed to process '{0}'.", Method.FullName), exception); | |
| } | |
| } | |
| private static Dictionary<string, TypeReference> ReplacedTypes = new Dictionary<string, TypeReference>(); | |
| private static Dictionary<string, FieldReference> NewSiteFields = new Dictionary<string, FieldReference>(); | |
| private static Dictionary<string, FieldReference> NewSiteTargetFields = new Dictionary<string, FieldReference>(); | |
| private static Dictionary<string, CallSite> NewInvokeCallSites = new Dictionary<string, CallSite>(); | |
| private static Dictionary<string, CallSite> NewCreateCallSites = new Dictionary<string, CallSite>(); | |
| void ProcessDynamic(Instruction instruction) | |
| { | |
| var typeReference = instruction.Operand as TypeReference; | |
| if (typeReference == null) | |
| { | |
| return; | |
| } | |
| if (typeReference.FullName != "Anotar.NLog.Log") | |
| { | |
| return; | |
| } | |
| //ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, string, object>> FodyTest.Program/'<Anotar_NLog_NoCast>o__SiteContainer6'::'<>p__Site7' | |
| // |----- This is the type we want -----| | |
| Instruction ldsfld = instruction.Previous; | |
| if (ldsfld.OpCode != OpCodes.Ldsfld) | |
| throw new WeavingException("Expected ldsfield opcode"); | |
| var callsitefield = ldsfld.Operand as FieldReference; | |
| if (callsitefield == null) | |
| throw new WeavingException("Unexpected ldsfld operand"); | |
| var sitecontainer = callsitefield.DeclaringType; | |
| if (!ReplacedTypes.ContainsKey(sitecontainer.Name) && !NewSiteFields.ContainsKey(callsitefield.Name)) | |
| { | |
| // We have not yet replaced this type | |
| var newsitecontainer = new TypeDefinition(sitecontainer.Namespace, | |
| sitecontainer.Name, | |
| TypeAttributes.AnsiClass & | |
| TypeAttributes.AutoClass & | |
| TypeAttributes.Sealed & | |
| TypeAttributes.NestedPrivate & | |
| TypeAttributes.Abstract & | |
| TypeAttributes.BeforeFieldInit); | |
| var msCoreLibDefinition = ModuleWeaver.AssemblyResolver.Resolve("mscorlib"); | |
| //class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, string, object>> | |
| var Scope = msCoreLibDefinition.MainModule; // This is probably wrong | |
| var callsiteType_1 = new TypeReference("System.Runtime.CompilerServices", "CallSite`1", msCoreLibDefinition.MainModule, Scope); | |
| var callsiteType = new TypeReference("System.Runtime.CompilerServices", "CallSite", | |
| msCoreLibDefinition.Modules.First(x => x.Name == "System.Core"), Scope); | |
| TypeDefinition NLogTypeDefinition = ModuleWeaver.NLogReference.MainModule.Types.First(x => x.Name == "Logger"); | |
| TypeReference NLogType = NLogTypeDefinition.MakeByReferenceType(); | |
| var stringType = new TypeReference("", "string", msCoreLibDefinition.MainModule, Scope); | |
| var objectType = new TypeReference("", "object", msCoreLibDefinition.MainModule, Scope); | |
| var actionType = new TypeReference("System", "Action`4", msCoreLibDefinition.MainModule, Scope); | |
| var genericActionType = | |
| actionType.MakeGenericInstanceType(new[] {callsiteType, NLogType, stringType, objectType}); | |
| var fieldType = callsiteType_1.MakeGenericInstanceType(genericActionType); | |
| var newSiteField = new FieldDefinition(callsitefield.Name, | |
| FieldAttributes.Public & | |
| FieldAttributes.Static, | |
| fieldType | |
| ); | |
| newsitecontainer.Fields.Add(newSiteField); | |
| sitecontainer.Module.Types.Add(newsitecontainer); | |
| ReplacedTypes.Add(newsitecontainer.Name, newsitecontainer); | |
| NewSiteFields.Add(newSiteField.Name, newSiteField); | |
| // TODO: | |
| //NewSiteTargetFields CallSite`1::Target ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [NLog]NLog.Logger, string, object>>::Target | |
| //NewInvokeCallSites Action`4::Invoke callvirt instance void class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [NLog]NLog.Logger, string, object>::Invoke(!0, !1, !2, !3) | |
| //NewCreateCallSites CallSite1:Create call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [NLog]NLog.Logger, string, object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) | |
| } | |
| Instruction prev = instruction; | |
| while (prev.OpCode != OpCodes.Call) | |
| { | |
| prev = prev.Previous; // Start with this because we want to capture the first Call opcode | |
| if (prev.OpCode == OpCodes.Ldsfld) | |
| instructions_ldsfld.Add(prev); | |
| else if (prev.OpCode == OpCodes.Ldfld) | |
| instructions_ldfld.Add(prev); | |
| else if (prev.OpCode == OpCodes.Stsfld) | |
| instructions_stsfld.Add(prev); | |
| else if (prev.OpCode == OpCodes.Call) | |
| instructions_call.Add(prev); | |
| } | |
| // Remove typeof(Log) | |
| //IL_006a: ldtoken [System.Core]Anotar.NLog.Log | |
| //IL_006f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) | |
| ilProcessor.Remove(instruction.Next); | |
| ilProcessor.Replace(instruction, Instruction.Create(OpCodes.Ldsfld, Field)); | |
| } | |
| void ReplaceCallSite(Instruction instruction, CallSite newCallSite) | |
| { | |
| var callsite = instruction.Operand as CallSite; | |
| if (callsite == null) | |
| throw new WeavingException("Instruction operand is not a CallSite"); | |
| var newInstruction = Instruction.Create(instruction.OpCode, newCallSite); | |
| ilProcessor.Replace(instruction, newInstruction); | |
| } | |
| void ReplaceField(Instruction instruction, FieldReference newFieldReference) | |
| { | |
| var fieldReference = instruction.Operand as FieldReference; | |
| if (fieldReference == null) | |
| throw new WeavingException("Instruction operand is not a CallSite"); | |
| var newInstruction = Instruction.Create(instruction.OpCode, newFieldReference); | |
| ilProcessor.Replace(instruction, newInstruction); | |
| } | |
| void ProcessInstruction(Instruction instruction) | |
| { | |
| var methodReference = instruction.Operand as MethodReference; | |
| if (methodReference == null) | |
| { | |
| return; | |
| } | |
| if (methodReference.DeclaringType.FullName != "Anotar.NLog.Log") | |
| { | |
| return; | |
| } | |
| if (!foundUsageInMethod) | |
| { | |
| Method.Body.InitLocals = true; | |
| Method.Body.SimplifyMacros(); | |
| } | |
| foundUsageInMethod = true; | |
| FoundUsageInType(); | |
| var parameters = methodReference.Parameters; | |
| instruction.OpCode = OpCodes.Callvirt; | |
| if (parameters.Count == 0) | |
| { | |
| var fieldAssignment = Instruction.Create(OpCodes.Ldsfld, Field); | |
| ilProcessor.Replace(instruction, fieldAssignment); | |
| ilProcessor.InsertAfter(fieldAssignment, | |
| Instruction.Create(OpCodes.Ldstr, GetMessagePrefix(instruction)), | |
| Instruction.Create(OpCodes.Ldc_I4_0), | |
| Instruction.Create(OpCodes.Newarr, ModuleWeaver.ModuleDefinition.TypeSystem.Object), | |
| Instruction.Create(OpCodes.Callvirt, ModuleWeaver.GetNormalOperand(methodReference))); | |
| } | |
| if (methodReference.IsMatch("String", "Exception")) | |
| { | |
| var messageVar = new VariableDefinition(ModuleWeaver.ModuleDefinition.TypeSystem.String); | |
| var exceptionVar = new VariableDefinition(ModuleWeaver.ExceptionType); | |
| Method.Body.Variables.Add(exceptionVar); | |
| Method.Body.Variables.Add(messageVar); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, exceptionVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, messageVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldsfld, Field)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldstr, GetMessagePrefix(instruction))); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, messageVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Call, ModuleWeaver.ConcatMethod)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, messageVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, messageVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, exceptionVar)); | |
| instruction.Operand = ModuleWeaver.GetExceptionOperand(methodReference); | |
| } | |
| if (methodReference.IsMatch("String", "Object[]")) | |
| { | |
| var formatVar = new VariableDefinition(ModuleWeaver.ModuleDefinition.TypeSystem.String); | |
| var paramsVar = new VariableDefinition(ModuleWeaver.ObjectArray); | |
| Method.Body.Variables.Add(formatVar); | |
| Method.Body.Variables.Add(paramsVar); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, paramsVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, formatVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldstr, GetMessagePrefix(instruction))); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, formatVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Call, ModuleWeaver.ConcatMethod)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, formatVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldsfld, Field)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, formatVar)); | |
| ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, paramsVar)); | |
| instruction.Operand = ModuleWeaver.GetNormalOperand(methodReference); | |
| } | |
| } | |
| string GetMessagePrefix(Instruction instruction) | |
| { | |
| //TODO: should prob wrap calls to this method and not concat an empty string. but this will do for now | |
| if (ModuleWeaver.LogMinimalMessage) | |
| { | |
| return string.Empty; | |
| } | |
| var sequencePoint = instruction.GetPreviousSequencePoint(); | |
| if (sequencePoint == null) | |
| { | |
| return string.Format("Method: '{0}'. ", Method.FullName); | |
| } | |
| return string.Format("Method: '{0}'. Line: ~{1}. ", Method.FullName, sequencePoint.StartLine); | |
| } | |
| } |
Author
Author
Three references left to replace, then I need to get tests running and write tests so I can refactor.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Missed some instructions -
callandstsfld. Will probably end up with some combination processing methods because some will be quite similar.