问题描述
例如,考虑以下 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
检查 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;
}
}
}
其中 locals
是 methodDef.Body.Variables
但这当然还不够,因为函数的参数可以是对其他函数的调用,例如 f(g("hello"))
。看起来像上面的情况,当它实际执行代码时,我检查以前的指令必须重复虚拟机的操作。当然,我不执行它,但我需要识别函数调用并将它们及其参数替换为各自的返回值(即使是占位符)。看起来很痛苦。
有没有更简单的方法?也许已经内置了一些东西?
解决方法
我不知道实现这一目标的简单方法。
我能想到的“最简单”的方法是遍历堆栈并找到用作调用目标的引用被压入的位置。
基本上,考虑到每条指令如何影响堆栈,从调用指令开始,一次返回一条指令;通过这种方式,您可以找到推送用作调用目标的引用的确切指令(很久以前我写过类似的内容;您可以使用 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<T>()
方法并发现它返回 new T()
并且知道在这种情况下 T
是 SomeType
,这就是您要查找的类型。
所以 Vagaus 的回答帮助我想出了一个可行的实现。
我在 github 上发布了它 - https://github.com/MarkKharitonov/MonoCecilExtensions
包括许多单元测试,但我确定我错过了一些案例。