10

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();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Peter
  • 207
  • 3
  • 10
  • 5
    Can you explain some more about the problem you are trying to solve here? I tend to find that when something seems really hard that I'm trying to solve it the wrong way. Reflection is primarily for discovering the 'shape' of an object, not how it's implemented. – Lazarus Apr 21 '11 at 08:21
  • 1
    It's a complex situation, but i'm building an Blend plugin. This plugin must be able to generate some C# code (code who calls a method). Before it generates code, it must check if it already exists. – Peter Apr 21 '11 at 08:29
  • What are you going to do if it doesn't exist? – Lazarus Apr 21 '11 at 08:32
  • @Peter: Then you actually want to check if the call *doesn't* exist, which you can't reliably do by analysing the code. There are several ways of calling a method other than doing it directly, for example creating a delegate and call the delegate, so you can only tell for certain if a method call exists, but you can't tell if it doesn't exists. – Guffa Apr 21 '11 at 08:36
  • 1
    @Lazurus: when it exists I show the class name in my plugin. – Peter Apr 21 '11 at 08:42
  • 1
    @Guffa: I don't want to call a method. If there is no call in the MethodBody to a specific method, I want to generate C# code inside the MethodBody. – Peter Apr 21 '11 at 08:42
  • @Peter: I'm talking about the code that you want to analyse. The code can contain other ways of calling a method than calling it directly, which makes it nearly impossible to determine that the code doesn't make a specific call. – Guffa Apr 22 '11 at 15:00
  • Possible duplicate of *[Get types used inside C# method body](http://stackoverflow.com/questions/5667816/get-types-used-inside-c-sharp-method-body)*. – Peter Mortensen Sep 17 '13 at 20:05
  • 1
    There is no way to do this directly using reflection. Somebody posted a [similar question](http://stackoverflow.com/questions/5667816/get-types-used-inside-c-method-body/5668079) earlier, you might want to go through those answers see if any will help you do what you want. – Can Gencer Apr 21 '11 at 08:20

3 Answers3

20

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!

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I can't find the BaseCodeVisitor class, can you tell me in wich assembly i can find this one? – Peter Apr 21 '11 at 09:30
  • 1
    You need [Mono.Cecil](http://www.mono-project.com/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 this code_) – sehe Apr 21 '11 at 09:42
  • I know this is an old thread, but I have just tried this code, and also can't find the BaseCodeVisitor class. I have Mono.Cecil referenced, with the namespace imported. I had a look in the DLL itself (using a decompiler) and there isn't a class by that name. I have the latest Momo.Cecil.dll, which is 0.9.5.0. Any ideas? Thanks – Avrohom Yisroel Jan 09 '13 at 19:58
  • 1
    @AvrohomYisroel I imagine the interface has changed. I don't have time to find out how, but I imagine it won't be anything too dramatic. (I know all collection types have gotten an overhaul to use .NET generics) – sehe Jan 09 '13 at 23:18
  • Do you have a copy of the DLL binary that you used? I tried to get a 0.6 release from GitHub, but either my understanding of source control is broken, or that site is lousy. I couldn't work out how to download a specific release. All I want is a binary that will work against the code here. If you have one, please can you email it to me at mryossu@hotmail.com - thanks :) – Avrohom Yisroel Jan 10 '13 at 18:33
1

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
-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.

user492238
  • 4,094
  • 1
  • 20
  • 26
  • 1
    I once got bitten by trying this - the frame number may be different in debug and release build, took me a while to figure that out. (This optimization can be switched off by using an attribute, but that might result in a performance hit). – takrl Apr 21 '11 at 08:52