使用反射查看方法内部是否调用了方法

发布于 2024-11-02 12:25:52 字数 354 浏览 0 评论 0原文

我正在使用反射,目前有一个 MethodBody。如何检查 MethodBody 内部是否调用了特定方法?

Assembly assembly = Assembly.Load("Module1");
Type type = assembly.GetType("Module1.ModuleInit");
MethodInfo mi = type.GetMethod("Initialize");
MethodBody mb = mi.GetMethodBody();

I'm working with reflection and currently have a MethodBody. How do I check if a specific method is called inside the MethodBody?

Assembly assembly = Assembly.Load("Module1");
Type type = assembly.GetType("Module1.ModuleInit");
MethodInfo mi = type.GetMethod("Initialize");
MethodBody mb = mi.GetMethodBody();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

零崎曲识 2024-11-09 12:25:52

使用Mono.Cecil。它是一个独立的程序集,可以在 Microsoft .NET 和 Mono 上运行。 (我想当我编写下面的代码时,我使用了 0.6 版或大约版本)

假设您有许多程序集

IEnumerable<AssemblyDefinition> assemblies;

使用 AssemblyFactory 获取这些程序集(加载一个?)

以下代码片段将枚举这些程序集的所有类型中方法的所有用法

methodUsages = assemblies
            .SelectMany(assembly => assembly.MainModule.Types.Cast<TypeDefinition>())
            .SelectMany(type => type.Methods.Cast<MethodDefinition>())
            .Where(method => null != method.Body) // allow abstracts and generics
            .SelectMany(method => method.Body.Instructions.Cast<Instruction>())
            .Select(instr => instr.Operand)
            .OfType<MethodReference>();

这将返回对方法的所有引用(因此包括在反射中使用,或构造可能执行或不执行的表达式)。因此,这可能不是很有用,只是向您展示无需太多努力即可使用 Cecil API 完成的操作 :)

请注意,此示例假定 Cecil 版本稍旧(主流单声道版本中的版本)。新版本

  • 更简洁(通过使用强类型泛型集合)
  • 更快

当然,在您的情况下,您可以将单个方法引用作为起点。假设您想检测何时可以在“startingpoint”内直接调用“mytargetmethod”实际上

MethodReference startingpoint; // get it somewhere using Cecil
MethodReference mytargetmethod; // what you are looking for

bool isCalled = startingpoint    
    .GetOriginalMethod() // jump to original (for generics e.g.)
    .Resolve()           // get the definition from the IL image
    .Body.Instructions.Cast<Instruction>()
    .Any(i => i.OpCode == OpCodes.Callvirt && i.Operand == (mytargetmethod));

调用树搜索

这是一个工作片段,允许您递归搜索到相互调用的(选定的)方法(间接)。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace StackOverflow
{
    /*
     * breadth-first lazy search across a subset of the call tree rooting in startingPoint
     * 
     * methodSelect selects the methods to recurse into
     * resultGen generates the result objects to be returned by the enumerator
     * 
     */
    class CallTreeSearch<T> : BaseCodeVisitor, IEnumerable<T> where T : class
    {
        private readonly Func<MethodReference, bool> _methodSelect;
        private readonly Func<Instruction, Stack<MethodReference>, T> _transform;

        private readonly IEnumerable<MethodDefinition> _startingPoints;
        private readonly IDictionary<MethodDefinition, Stack<MethodReference>> _chain = new Dictionary<MethodDefinition, Stack<MethodReference>>();
        private readonly ICollection<MethodDefinition> _seen = new HashSet<MethodDefinition>(new CompareMembers<MethodDefinition>());
        private readonly ICollection<T> _results = new HashSet<T>();
        private Stack<MethodReference> _currentStack;

        private const int InfiniteRecursion = -1;
        private readonly int _maxrecursiondepth;
        private bool _busy;

        public CallTreeSearch(IEnumerable<MethodDefinition> startingPoints,
                              Func<MethodReference, bool> methodSelect,
                              Func<Instruction, Stack<MethodReference>, T> resultGen)
            : this(startingPoints, methodSelect, resultGen, InfiniteRecursion)
        {

        }

        public CallTreeSearch(IEnumerable<MethodDefinition> startingPoints,
                              Func<MethodReference, bool> methodSelect,
                              Func<Instruction, Stack<MethodReference>, T> resultGen,
                              int maxrecursiondepth)
        {
            _startingPoints = startingPoints.ToList();

            _methodSelect = methodSelect;
            _maxrecursiondepth = maxrecursiondepth;
            _transform = resultGen;
        }

        public override void VisitMethodBody(MethodBody body)
        {
            _seen.Add(body.Method); // avoid infinite recursion
            base.VisitMethodBody(body);
        }

        public override void VisitInstructionCollection(InstructionCollection instructions)
        {
            foreach (Instruction instr in instructions)
                VisitInstruction(instr);

            base.VisitInstructionCollection(instructions);
        }

        public override void VisitInstruction(Instruction instr)
        {
            T result = _transform(instr, _currentStack);
            if (result != null)
                _results.Add(result);

            var methodRef = instr.Operand as MethodReference; // TODO select calls only?
            if (methodRef != null && _methodSelect(methodRef))
            {
                var resolve = methodRef.Resolve();
                if (null != resolve && !(_chain.ContainsKey(resolve) || _seen.Contains(resolve)))
                    _chain.Add(resolve, new Stack<MethodReference>(_currentStack.Reverse()));
            }

            base.VisitInstruction(instr);
        }

        public IEnumerator<T> GetEnumerator()
        {
            lock (this) // not multithread safe
            {
                if (_busy)
                    throw new InvalidOperationException("CallTreeSearch enumerator is not reentrant");
                _busy = true;

                try
                {
                    int recursionLevel = 0;
                    ResetToStartingPoints();

                    while (_chain.Count > 0 &&
                           ((InfiniteRecursion == _maxrecursiondepth) || recursionLevel++ <= _maxrecursiondepth))
                    {

                        // swapout the collection because Visitor will modify
                        var clone = new Dictionary<MethodDefinition, Stack<MethodReference>>(_chain);
                        _chain.Clear();

                        foreach (var call in clone.Where(call => HasBody(call.Key)))
                        {
//                          Console.Error.Write("\rCallTreeSearch: level #{0}, scanning {1,-20}\r", recursionLevel, call.Key.Name + new string(' ',21));
                            _currentStack = call.Value;
                            _currentStack.Push(call.Key);
                            try
                            {
                                _results.Clear();
                                call.Key.Body.Accept(this); // grows _chain and _results
                            }
                            finally
                            {
                                _currentStack.Pop();
                            }
                            _currentStack = null;

                            foreach (var result in _results)
                                yield return result;
                        }
                    }
                }
                finally
                {
                    _busy = false;
                }
            }
        }

        private void ResetToStartingPoints()
        {
            _chain.Clear();
            _seen.Clear();
            foreach (var startingPoint in _startingPoints)
            {
                _chain.Add(startingPoint, new Stack<MethodReference>());
                _seen.Add(startingPoint);
            }
        }

        private static bool HasBody(MethodDefinition methodDefinition)
        {
            return !(methodDefinition.IsAbstract || methodDefinition.Body == null);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    internal class CompareMembers<T> : IComparer<T>, IEqualityComparer<T>
        where T: class, IMemberReference
    {
        public int Compare(T x, T y)
        { return StringComparer.InvariantCultureIgnoreCase.Compare(KeyFor(x), KeyFor(y)); }

        public bool Equals(T x, T y)
        { return KeyFor(x).Equals(KeyFor(y)); }

        private static string KeyFor(T mr)
        { return null == mr ? "" : String.Format("{0}::{1}", mr.DeclaringType.FullName, mr.Name); }

        public int GetHashCode(T obj)
        { return KeyFor(obj).GetHashCode(); }
    }
}

注释

  • Resolve() 进行一些错误处理(我有一个扩展方法 TryResolve() 用于此目的),
  • 可以选择在 a 中选择 MethodReferences 的用法仅调用操作(call、calli、callvirt ...)(参见 //TODO

典型用法:

public static IEnumerable<T> SearchCallTree<T>(this TypeDefinition startingClass,
                                               Func<MethodReference, bool> methodSelect,
                                               Func<Instruction, Stack<MethodReference>, T> resultFunc,
                                               int maxdepth)
    where T : class
{
    return new CallTreeSearch<T>(startingClass.Methods.Cast<MethodDefinition>(), methodSelect, resultFunc, maxdepth);
}

public static IEnumerable<T> SearchCallTree<T>(this MethodDefinition startingMethod,
                                               Func<MethodReference, bool> methodSelect,
                                               Func<Instruction, Stack<MethodReference>, T> resultFunc,
                                               int maxdepth)
    where T : class
{
    return new CallTreeSearch<T>(new[] { startingMethod }, methodSelect, resultFunc, maxdepth); 
}

// Actual usage:
private static IEnumerable<TypeUsage> SearchMessages(TypeDefinition uiType, bool onlyConstructions)
{
    return uiType.SearchCallTree(IsBusinessCall,
           (instruction, stack) => DetectRequestUsage(instruction, stack, onlyConstructions));
}

注意函数的完成,例如检测请求使用情况满足您的需求完全取决于您(编辑:

Use Mono.Cecil. It is a single standalone assembly that will work on Microsoft .NET as well as Mono. (I think I used version 0.6 or thereabouts back when I wrote the code below)

Say you have a number of assemblies

IEnumerable<AssemblyDefinition> assemblies;

Get these using AssemblyFactory (load one?)

The following snippet would enumerate all usages of methods in all types of these assemblies

methodUsages = assemblies
            .SelectMany(assembly => assembly.MainModule.Types.Cast<TypeDefinition>())
            .SelectMany(type => type.Methods.Cast<MethodDefinition>())
            .Where(method => null != method.Body) // allow abstracts and generics
            .SelectMany(method => method.Body.Instructions.Cast<Instruction>())
            .Select(instr => instr.Operand)
            .OfType<MethodReference>();

This will return all references to methods (so including use in reflection, or to construct expressions which may or may not be executed). As such, this is probably not very useful, except to show you what can be done with the Cecil API without too much of an effort :)

Note that this sample assumes a somewhat older version of Cecil (the one in mainstream mono versions). Newer versions are

  • more succinct (by using strong typed generic collections)
  • faster

Of course in your case you could have a single method reference as starting point. Say you want to detect when 'mytargetmethod' can actually be called directly inside 'startingpoint':

MethodReference startingpoint; // get it somewhere using Cecil
MethodReference mytargetmethod; // what you are looking for

bool isCalled = startingpoint    
    .GetOriginalMethod() // jump to original (for generics e.g.)
    .Resolve()           // get the definition from the IL image
    .Body.Instructions.Cast<Instruction>()
    .Any(i => i.OpCode == OpCodes.Callvirt && i.Operand == (mytargetmethod));

Call Tree Search

Here is a working snippet that allows you to recursively search to (selected) methods that call each other (indirectly).

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace StackOverflow
{
    /*
     * breadth-first lazy search across a subset of the call tree rooting in startingPoint
     * 
     * methodSelect selects the methods to recurse into
     * resultGen generates the result objects to be returned by the enumerator
     * 
     */
    class CallTreeSearch<T> : BaseCodeVisitor, IEnumerable<T> where T : class
    {
        private readonly Func<MethodReference, bool> _methodSelect;
        private readonly Func<Instruction, Stack<MethodReference>, T> _transform;

        private readonly IEnumerable<MethodDefinition> _startingPoints;
        private readonly IDictionary<MethodDefinition, Stack<MethodReference>> _chain = new Dictionary<MethodDefinition, Stack<MethodReference>>();
        private readonly ICollection<MethodDefinition> _seen = new HashSet<MethodDefinition>(new CompareMembers<MethodDefinition>());
        private readonly ICollection<T> _results = new HashSet<T>();
        private Stack<MethodReference> _currentStack;

        private const int InfiniteRecursion = -1;
        private readonly int _maxrecursiondepth;
        private bool _busy;

        public CallTreeSearch(IEnumerable<MethodDefinition> startingPoints,
                              Func<MethodReference, bool> methodSelect,
                              Func<Instruction, Stack<MethodReference>, T> resultGen)
            : this(startingPoints, methodSelect, resultGen, InfiniteRecursion)
        {

        }

        public CallTreeSearch(IEnumerable<MethodDefinition> startingPoints,
                              Func<MethodReference, bool> methodSelect,
                              Func<Instruction, Stack<MethodReference>, T> resultGen,
                              int maxrecursiondepth)
        {
            _startingPoints = startingPoints.ToList();

            _methodSelect = methodSelect;
            _maxrecursiondepth = maxrecursiondepth;
            _transform = resultGen;
        }

        public override void VisitMethodBody(MethodBody body)
        {
            _seen.Add(body.Method); // avoid infinite recursion
            base.VisitMethodBody(body);
        }

        public override void VisitInstructionCollection(InstructionCollection instructions)
        {
            foreach (Instruction instr in instructions)
                VisitInstruction(instr);

            base.VisitInstructionCollection(instructions);
        }

        public override void VisitInstruction(Instruction instr)
        {
            T result = _transform(instr, _currentStack);
            if (result != null)
                _results.Add(result);

            var methodRef = instr.Operand as MethodReference; // TODO select calls only?
            if (methodRef != null && _methodSelect(methodRef))
            {
                var resolve = methodRef.Resolve();
                if (null != resolve && !(_chain.ContainsKey(resolve) || _seen.Contains(resolve)))
                    _chain.Add(resolve, new Stack<MethodReference>(_currentStack.Reverse()));
            }

            base.VisitInstruction(instr);
        }

        public IEnumerator<T> GetEnumerator()
        {
            lock (this) // not multithread safe
            {
                if (_busy)
                    throw new InvalidOperationException("CallTreeSearch enumerator is not reentrant");
                _busy = true;

                try
                {
                    int recursionLevel = 0;
                    ResetToStartingPoints();

                    while (_chain.Count > 0 &&
                           ((InfiniteRecursion == _maxrecursiondepth) || recursionLevel++ <= _maxrecursiondepth))
                    {

                        // swapout the collection because Visitor will modify
                        var clone = new Dictionary<MethodDefinition, Stack<MethodReference>>(_chain);
                        _chain.Clear();

                        foreach (var call in clone.Where(call => HasBody(call.Key)))
                        {
//                          Console.Error.Write("\rCallTreeSearch: level #{0}, scanning {1,-20}\r", recursionLevel, call.Key.Name + new string(' ',21));
                            _currentStack = call.Value;
                            _currentStack.Push(call.Key);
                            try
                            {
                                _results.Clear();
                                call.Key.Body.Accept(this); // grows _chain and _results
                            }
                            finally
                            {
                                _currentStack.Pop();
                            }
                            _currentStack = null;

                            foreach (var result in _results)
                                yield return result;
                        }
                    }
                }
                finally
                {
                    _busy = false;
                }
            }
        }

        private void ResetToStartingPoints()
        {
            _chain.Clear();
            _seen.Clear();
            foreach (var startingPoint in _startingPoints)
            {
                _chain.Add(startingPoint, new Stack<MethodReference>());
                _seen.Add(startingPoint);
            }
        }

        private static bool HasBody(MethodDefinition methodDefinition)
        {
            return !(methodDefinition.IsAbstract || methodDefinition.Body == null);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    internal class CompareMembers<T> : IComparer<T>, IEqualityComparer<T>
        where T: class, IMemberReference
    {
        public int Compare(T x, T y)
        { return StringComparer.InvariantCultureIgnoreCase.Compare(KeyFor(x), KeyFor(y)); }

        public bool Equals(T x, T y)
        { return KeyFor(x).Equals(KeyFor(y)); }

        private static string KeyFor(T mr)
        { return null == mr ? "" : String.Format("{0}::{1}", mr.DeclaringType.FullName, mr.Name); }

        public int GetHashCode(T obj)
        { return KeyFor(obj).GetHashCode(); }
    }
}

Notes

  • do some error handling a Resolve() (I have an extension method TryResolve() for the purpose)
  • optionally select usages of MethodReferences in a call operation (call, calli, callvirt ...) only (see //TODO)

Typical usage:

public static IEnumerable<T> SearchCallTree<T>(this TypeDefinition startingClass,
                                               Func<MethodReference, bool> methodSelect,
                                               Func<Instruction, Stack<MethodReference>, T> resultFunc,
                                               int maxdepth)
    where T : class
{
    return new CallTreeSearch<T>(startingClass.Methods.Cast<MethodDefinition>(), methodSelect, resultFunc, maxdepth);
}

public static IEnumerable<T> SearchCallTree<T>(this MethodDefinition startingMethod,
                                               Func<MethodReference, bool> methodSelect,
                                               Func<Instruction, Stack<MethodReference>, T> resultFunc,
                                               int maxdepth)
    where T : class
{
    return new CallTreeSearch<T>(new[] { startingMethod }, methodSelect, resultFunc, maxdepth); 
}

// Actual usage:
private static IEnumerable<TypeUsage> SearchMessages(TypeDefinition uiType, bool onlyConstructions)
{
    return uiType.SearchCallTree(IsBusinessCall,
           (instruction, stack) => DetectRequestUsage(instruction, stack, onlyConstructions));
}

Note the completiion of a function like DetectRequestUsage to suite your needs is completely and entirely up to you (edit: but see here). You can do whatever you want, and don't forget: you'll have the complete statically analyzed call stack at your disposal, so you actually can do pretty neat things with all that information!

柠檬 2024-11-09 12:25:52

在生成代码之前,必须检查它是否已经存在

。在某些情况下,捕获异常比阻止生成异常要便宜得多。这是一个很好的例子。您可以获得方法主体的 IL,但 Reflection 不是反汇编程序。反汇编程序也不是真正的解决方案,您可以反汇编整个调用树以实现您想要的行为。毕竟,主体中的方法调用本身可以调用方法等等。捕获编译 IL 时抖动抛出的异常要简单得多。

Before it generates code, it must check if it already exists

There are a few cases where catching an exception is way cheaper than preventing it from being generated. This is a prime example. You can get the IL for the method body but Reflection is not a disassembler. Nor is a disassembler a real fix, you'd have the disassemble the entire call tree to implement your desired behavior. After all, a method call in the body could itself call a method, etcetera. It is just much simpler to catch the exception that the jitter will throw when it compiles the IL.

奶茶白久 2024-11-09 12:25:52

可以使用 StackTrace 类:

System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace();
System.Diagnostics.StackFrame sf = st.GetFrame(1); 
Console.Out.Write(sf.GetMethod().ReflectedType.Name + "." + sf.GetMethod().Name); 

可以调整 1 并确定您感兴趣的帧数。

One can use the StackTrace class:

System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace();
System.Diagnostics.StackFrame sf = st.GetFrame(1); 
Console.Out.Write(sf.GetMethod().ReflectedType.Name + "." + sf.GetMethod().Name); 

The 1 can be adjusted and determines the number of frame you are interested in.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文