Посмотрите, вызывается ли метод внутри метода, использующего отражение - PullRequest
9 голосов
/ 21 апреля 2011

Я работаю с отражением и в настоящее время MethodBody . Как проверить, вызывается ли определенный метод внутри MethodBody?

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

Ответы [ 3 ]

19 голосов
/ 21 апреля 2011

Использование 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>();

Это вернет все ссылки на методы (включая использование в отражении или построение выражений, которые могут или могутне быть выполненным).Таким образом, это, вероятно, не очень полезно, за исключением того, чтобы показать вам, что можно сделать с помощью API Cecil без особых усилий:)

Обратите внимание, что в этом примере предполагается несколько более старая версия Cecil (в основных моно версиях).Более новые версии

  • более лаконичны (с использованием строго типизированных обобщенных коллекций)
  • быстрее

Конечно, в вашем случае вы можете использовать ссылку на один методв качестве отправной точки.Скажем, вы хотите определить, когда «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() для этой цели)
  • опционально выбрать использование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));
}

Примечание Завершение функции, подобной DetectRequestUsage, для удовлетворения ваших потребностей полностью и полностью зависит от вас (редактирование: , но см. Здесь ). Вы можете делать все, что захотите, и не забывайте: в вашем распоряжении будет полный статически проанализированный стек вызовов, так что вы действительно сможете делать довольно аккуратные вещи со всей этой информацией !

1 голос
/ 21 апреля 2011

Прежде чем генерировать код, он должен проверить, существует ли он уже

В некоторых случаях перехват исключения в дешевле, чем предотвращение его создания. Это яркий пример. Вы можете получить IL для тела метода, но Reflection не является дизассемблером. И дизассемблер не является реальным исправлением, вам придется разбирать все дерево вызовов для реализации желаемого поведения. В конце концов, вызов метода в теле может сам вызывать метод, и так далее. Просто гораздо проще поймать исключение, которое генерирует джиттер при компиляции IL.

0 голосов
/ 21 апреля 2011

Можно использовать класс 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 можно настроить и определить количество интересующих вас кадров.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...