Search code examples
c#async-awaitilmono.cecil

How to detect source code changes in async method body


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?


Solution

  • 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));