An application which I’m currently developing has quite complicated authorization system. That is why, we can not use role based authorization, and basically every developer is obliged to call appropriate security check method in every controller action he or she writes. As You probably know it is quite easy to forget about that, therefore I decided to write a test which would check whether all controller’s action invokes this security critical function. After hours of searching for some anchor point, I came across this article http://blogs.msdn.com/b/haibo_luo/archive/2006/11/06/system-reflection-based-ilreader.aspx in which author creates so called “reflection based ILReader”. ILReader is a class which allows You to iterate over IL instructions of every object of type RuntimeMethodInfo or RuntimeConstructorInfo .One of these instructions is InlineMethodInstruction which indicates method call. That is why, in order to get all methods which are called by given method, we have to simply select all instructions of type InlineMethodInstruction
1 2 3 4 5 6 7 8 |
public static class MethodInvokerCrawler { public static IEnumerable<MethodBase> GetMethods(MethodBase caller) { ILReader reader = new ILReader(caller); return reader.OfType<InlineMethodInstruction>().Select(method => method.Method); } } |
We can go a bit further and create a recursive version of this function to iterate over entire function execution chain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public static class MethodInvokerCrawler { public const string DefaultPrefix = "GeneralLearning"; public static IEnumerable<MethodBase> GetMethods(MethodBase caller, bool recursive = false, string namespacePrefix = DefaultPrefix) { ILReader reader = new ILReader(caller); foreach (InlineMethodInstruction method in reader.OfType<InlineMethodInstruction>()) { yield return method.Method; if (recursive && method.Method.DeclaringType != null && method.Method.DeclaringType.FullName.StartsWith(namespacePrefix, StringComparison.InvariantCultureIgnoreCase)) { foreach (var innerMethod in GetMethods(method.Method)) { yield return innerMethod; } } } } } |
Please notice, that I limited recursion depth to methods from user defined namespace, otherwise we would end up iterating over .NET’s native functions.
Now it is time to show some results. Here is a sample program which demonstrate possibilities of MethodInvokerCrawler class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
internal class Program { public class FooClass { public FooClass() { this.X(); this.Foo(); this.Bar(); } public void Foo(){} public void Bar() { this.Z(); this.Foo(); } public string X() { return null; } public void Y(){ } public void Z() { this.Y(); } } public static void Main(string[] args) { Console.WriteLine("=== execution chain of Bar function no recursion ==="); foreach (var s in MethodInvokerCrawler.GetMethods(typeof(FooClass).GetMethod("Bar"))) { Console.WriteLine("{0}.{1}", s.DeclaringType.FullName, s.Name); } Console.WriteLine(); Console.WriteLine("=== execution chain of Bar function with recursion ==="); foreach (var s in MethodInvokerCrawler.GetMethods(typeof(FooClass).GetMethod("Bar"), true)) { Console.WriteLine("{0}.{1}", s.DeclaringType.FullName, s.Name); } Console.WriteLine(); Console.WriteLine("execution chain of FooClass default constructor no recursion"); foreach (var s in MethodInvokerCrawler.GetMethods(typeof(FooClass).GetConstructors().Single())) { Console.WriteLine("{0}.{1}", s.DeclaringType.FullName, s.Name); } Console.WriteLine(); Console.WriteLine("execution chain of FooClass default constructor with recursion"); foreach (var s in MethodInvokerCrawler.GetMethods(typeof(FooClass).GetConstructors().Single(), true)) { Console.WriteLine("{0}.{1}", s.DeclaringType.FullName, s.Name); } Console.ReadKey(); } } |
At this point checking whether function or constructor calls another function is limited to simple LINQ query
1 |
MethodInvokerCrawler.GetMethods(typeof(FooClass).GetMethod("Bar")).Any(val => val.Name == "Foo"); |
Source code for this post can be found here
That's a nice find. I remember when I write some code by hand to get out call chain of the method. This has very limited usages however as we simply get the method implementation and it's not possible to get out of that scope (Well it is possible but the code get's funky as it's pointer magic now). So if you have some simple or complex branching logic then this way will fail fast.
Another thing is that if you need to force some method call on a developer then why not create a tool that simply generates that code in pre-compile time it's much simpler that way, less error prone and less governing work which is a chore for everyone.
Regards
Bartosz Adamczewski
I would go similar way Bartosz suggests. I would create a MSBuild task and inject the security logic into every method when code is compiled. You can do this using Mono.Cecil for example. Examples can be found in my blog post about Cecil – http://blog.octal.pl/2011/09/mono-cecil-prezentacja.html
Relaying on above test is better than nothing but still not the best approach.
Paweł