【问题标题】:How to detect source code changes in async method body如何检测异步方法主体中的源代码更改
【发布时间】:2015-03-21 18:39:06
【问题描述】:

我试图在运行时检测某个类的方法的源代码是否已更改。基本上我检索方法体 (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));

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

【问题讨论】:

  • 我建议您使用 IlSpy 查看应用程序,禁用 Display->Options->Decompile async 方法。在async 方法上完成了相当大的魔力(比如yield 方法)
  • 异步方法有所不同。编译器会将其转换为状态机。请参阅this 了解更多信息。另外一点点谷歌搜索将有助于了解异步方法在内部是如何工作的。
  • 你真的不需要 Cecil 来做这么简单的事情......你可以使用GetMethodBody().GetILAsByteArray()/GetMethodBody().LocalVariables
  • @xanatos 那只会给出局部变量,我想知道方法体是否发生了变化,例如是否添加或更改了代码行。
  • IL作为字节数组,只给出方法中使用的操作码,不包括字符串等

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


【解决方案1】:

感谢@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));

【讨论】:

  • 我刚刚发现(在已编译的 Razor 视图中)某些方法可以拆分为多个级别,因此仅搜索第一级 NestedTypes 是行不通的。这对我搜索所有嵌套类型很有用: var typesAndSubTypes = new List() { typeDefinition }; for(int i = 0; i x.Methods) .Where(m => m.HasBody) .SelectMany(x => x.Body.Instructions).ToList();
【解决方案2】:

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

【讨论】:

  • 是的,我也读过。您知道在运行时获取该生成方法的方法吗?或者你建议我检查一下 ILSpy 的源代码,看看他们是怎么做的?
  • 至少 Microsoft C# 编译器似乎在生成的类的名称中包含方法名称,并且生成的类实现了 IAsyncStateMachine。
【解决方案3】:

对于第二个问题,不使用 Cecil(因为我没有):

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 兼容

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-25
    • 2021-12-06
    • 2012-05-18
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    相关资源
    最近更新 更多