如何检测异步方法体中的源代码更改

时间:2015-03-21 07:54:33

标签: c# async-await il mono.cecil

我正在尝试在运行时检测是否已更改类方法的源代码。基本上我检索方法体(IL),用md5散列它并将其存储在数据库中。下次我检查方法时,我可以比较哈希值。

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");
    }
}

我正在使用Mono.Cecil来检索方法体:

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

除了标记为async的方法外,这很有用。每当我向SomeValue方法添加一些代码时,哈希都会发生变化。每当我向GetSomeValueAsync方法添加一些代码时,哈希都不会改变。有谁知道如何检测异步方法的方法体是否已更改?

3 个答案:

答案 0 :(得分:3)

异步方法(如迭代器方法)主要编译为表示状态机的嵌套助手类。整个帮助程序类(使用带有停用选项的ILSpy来反编译异步方法以查看示例的结果)将仅用于该异步方法。对该方法的更改可能会在该辅助类的生成方法中发生,而不是原始方法。

答案 1 :(得分:2)

关于你的第二个问题,不使用塞西尔(因为我没有):

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
}

如果你看一下生成的代码,你会发现它显然需要一个由编译器生成的“隐藏”类型的局部变量。我们使用此:-)请注意,这与yieldasync

兼容

答案 2 :(得分:2)

我已经找到了解决方案,感谢@xanatos和@Wormbo让我朝着正确的方向前进。

对于异步方法,C#编译器生成一个包含方法体的辅助类。这些辅助类可以在主类型的NestedTypes属性中找到。因此,如果我们包含嵌套类型的方法体,我们可以创建正确的哈希:

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