How to detect source code changes in async method body

丶灬走出姿态 提交于 2020-06-18 11:56:10

问题


I'm trying to detect during runtime if the source code of a method of a class has been changed. Basically I retrieve the method body (IL), hash it with md5 and store it in the database. Next time I check the method, I can compare the hashes.

public class Changed
{
    public string SomeValue { get; set; }

    public string GetSomeValue()
    {
        return SomeValue + "add something";
    }

    public async Task<string> GetSomeValueAsync()
    {
        return await Task.FromResult(SomeValue + "add something");
    }
}

I'm using Mono.Cecil to retrieve the method bodies:

var module = ModuleDefinition.ReadModule("MethodBodyChangeDetector.exe");

var typeDefinition = module.Types.First(t => t.FullName == typeof(Changed).FullName);

// Retrieve all method bodies (IL instructions as string)
var methodInstructions = typeDefinition.Methods
    .Where(m => m.HasBody)
    .SelectMany(x => x.Body.Instructions)
    .Select(i => i.ToString());

var hash = Md5(string.Join("", methodInstructions));

This works great, except for methods marked as async. Whenever I add some code to the SomeValue method, the hash changes. Whenever I add some code to GetSomeValueAsync method, the hash does not change. Does anyone know how to detect if the method body of an async method has changed?


回答1:


Async methods, like iterator methods, are mostly compiled into a nested helper class that represents a state machine. That entire helper class (use ILSpy with deactivated option to decompile async methods to see the result for your example) will be used only for that async method. Changes to the method will likely happen in a generated method of that helper class instead of the original method.




回答2:


I've found a solution, thanks to @xanatos and @Wormbo who put me in the right direction.

In the case of an async method, the C# compiler generates a helper class which contain the method body. These helper classes can be found in the NestedTypes property of the main type. So, if we include the method bodies of the nested types, we can create the correct hash:

var module = ModuleDefinition.ReadModule("MethodBodyChangeDetector.exe");

var typeDefinition = module.Types.First(t => t.FullName == typeof(Changed).FullName);

// Retrieve all method bodies (IL instructions as string)
var methodInstructions = typeDefinition.Methods
    .Where(m => m.HasBody)
    .SelectMany(x => x.Body.Instructions)
    .Select(i => i.ToString());

var nestedMethodInstructions = typeDefinition.NestedTypes
    .SelectMany(x=>x.Methods)
    .Where(m => m.HasBody)
    .SelectMany(x => x.Body.Instructions)
    .Select(i => i.ToString());


Md5(string.Join("", methodInstructions) + string.Join("", nestedMethodInstructions));



回答3:


For your second question, without using Cecil (because I don't have it):

var method2 = typeof(Program).GetMethod("MyMethodX", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var body = method2.GetMethodBody();
Type[] compilerGeneratedVariables = body.LocalVariables.Select(x => x.LocalType).Where(x => x.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0).ToArray();
byte[] ilInstructions = body.GetILAsByteArray(); // You can hash these

if (compilerGeneratedVariables.Length != 0)
{
    // There are some local variables of types that are compiler generated
    // This is a good sign that the compiler has changed the code
}

If you look at the generated code, you'll see that clearly it needs a local variable of the "hidden" type that has been generated by the compiler. We use this :-) Note that this is compatible with both yield and async



来源:https://stackoverflow.com/questions/29180494/how-to-detect-source-code-changes-in-async-method-body

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!