【问题标题】:Is it possible in Mono.Cecil to determine the actual type of an object on which a method is called?在 Mono.Cecil 中是否可以确定调用方法的对象的实际类型?
【发布时间】:2021-09-28 10:56:10
【问题描述】:

例如,考虑以下 C# 代码:

interface IBase { void f(int); }
interface IDerived : IBase { /* inherits f from IBase */ }

...
void SomeFunction()
{
    IDerived o = ...;
    o.f(5);
}

我知道如何获取与 SomeFunction 对应的MethodDefinition 对象。 然后我可以遍历MethodDefinition.Instructions

var methodDef = GetMethodDefinitionOfSomeFunction();
foreach (var instruction in methodDef.Body.Instructions)
{
    switch (instruction.Operand)
    {
        case MethodReference mr:
            ...
            break;
    }
    yield return memberRef;
}

这样我可以发现方法SomeFunction调用了函数IBase.f

现在我想知道调用函数f的对象的声明类型,即o的声明类型。

检查mr.DeclaringType 没有帮助,因为它返回IBase

这是我目前所拥有的:

TypeReference typeRef = null;
if (instruction.OpCode == OpCodes.Callvirt)
{
    // Identify the type of the object on which the call is being made.
    var objInstruction = instruction;
    if (instruction.Previous.OpCode == OpCodes.Tail)
    {
        objInstruction = instruction.Previous;
    }
    for (int i = mr.Parameters.Count; i >= 0; --i)
    {
        objInstruction = objInstruction.Previous;
    }
    if (objInstruction.OpCode == OpCodes.Ldloc_0 ||
        objInstruction.OpCode == OpCodes.Ldloc_1 ||
        objInstruction.OpCode == OpCodes.Ldloc_2 ||
        objInstruction.OpCode == OpCodes.Ldloc_3)
    {
        var localIndex = objInstruction.OpCode.Op2 - OpCodes.Ldloc_0.Op2;
        typeRef = locals[localIndex].VariableType;
    }
    else
    {
        switch (objInstruction.Operand)
        {
            case FieldDefinition fd:
                typeRef = fd.DeclaringType;
                break;
            case VariableDefinition vd:
                typeRef = vd.VariableType;
                break;
        }
    }
}

其中localsmethodDef.Body.Variables

但这当然还不够,因为函数的参数可以调用其他函数,例如f(g("hello"))。看起来像上面的情况,我检查以前的指令必须在虚拟机实际执行代码时重复虚拟机的操作。当然,我不执行它,但我需要识别函数调用并将它们及其参数替换为它们各自的返回值(即使是占位符)。看起来很痛苦。

有没有更简单的方法?也许已经内置了一些东西?

【问题讨论】:

  • 我不知道实现这一目标的简单方法。我想“最简单”的方法是遍历堆栈并找到用作调用目标的引用被推送的位置(我认为您可以从github.com/lytico/db4o/blob/master/db4o.net/Db4oTool/Db4oTool/… 获得一些灵感)。无论如何,您需要涵盖更多情况,例如 SomeFunction().f(5)。在这种情况下(如果 SomeFunction() 类型为 IBase),您需要找出返回的实际类型。
  • 我知道。这真的很痛苦。因此我的问题在这里。也许有人已经做过这样的事情了。遍历堆栈是指IL指令堆栈吗?如果是这样 - 是的,似乎是这样。好痛啊……
  • @Vagaus - 请安排您的评论作为答案。虽然不能满足问题,但还是有用的,值得点赞。

标签: mono.cecil


【解决方案1】:

我不知道实现这一目标的简单方法。

我能想到的“最简单”的方法是遍历堆栈并找到用作调用目标的引用被推送到哪里。

基本上,从调用指令开始,一次返回一条指令,同时考虑每条指令如何影响堆栈;通过这种方式,您可以找到推送用作调用目标的引用的确切指令(很久以前我写过类似的东西;您可以使用https://github.com/lytico/db4o/blob/master/db4o.net/Db4oTool/Db4oTool/Core/StackAnalyzer.cs 的代码作为灵感)。

您还需要考虑通过方法/属性生成推送引用的场景;例如,SomeFunction().f(5)。在这种情况下,您可能需要评估该方法以找出返回的实际类型。

请记住,您需要处理很多不同的情况;例如,想象下面的代码:

class Utils
{
   public static T Instantiate<T>() where T : new() => new T();
}

class SomeType
{
    public void F(int i) {}
}

class Usage
{
    static void Main()
    {
       var o = Utils.Instantiate<SomeType>();
       o.F(1);
    }
}

在遍历堆栈时,您会发现o 是方法调用的目标;然后您将评估 Instantiate&lt;T&gt;() 方法并发现它返回 new T() 并知道在这种情况下 TSomeType,这就是您要查找的类型。

【讨论】:

    【解决方案2】:

    所以 Vagaus 的回答帮助我想出了一个可行的实现。

    我在 github 上发布了它-https://github.com/MarkKharitonov/MonoCecilExtensions

    包括许多单元测试,但我确信我错过了一些案例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-28
      • 1970-01-01
      • 2011-12-15
      • 1970-01-01
      相关资源
      最近更新 更多