How can I determine which exceptions can be thrown by a given method?
Asked Answered
T

9

41

My question is really the same as this one "Finding out what exceptions a method might throw in C#". However, I would really like to know if anyone knows of a way to determine the stack of all the exceptions that may be thrown by a given method. I am hoping for a tool or utility that I can analyze code at compile time or through reflection like FxCop, StyleCop, or NCover. I do not need this information at run time I just want to make sure we are trapping exceptions and logging them correctly in out code.

We are currently trapping the exceptions that we know about and logging all the wild cards. This does work well; however, i was just hoping someone has used or knows of a tool that can discover this information.

Thromboplastic answered 12/6, 2009 at 11:29 Comment(3)
Yeah, I'm getting the same error unfortunately. It turns out the required logic is again slightly more complex than I thought. Some dummy push/pull handling for the stack should do the job however - which is exavtly that I'm trying now.Onanism
Point of clarification, but do you mean all exceptions that are thrown by a function as they were written by a developer (e.g. throw commands in the function) or that could also be thrown by other routines called by the function (e.g. an invalid operation expcetion thrown by a .NET library call)?Santamaria
I just have to wonder why are you trying to reinvent the wheel? If a tool has been created that does exactly this and very well, why write code that hopefully does this?Trustbuster
O
52

Following up to my previous answer, I've managed to create a basic exception finder. It utilises a reflection-based ILReader class, available here on Haibo Luo's MSDN blog. (Just add a reference to the project.)

Updates:

  1. Now handles local variables and the stack.
    • Correctly detects exceptions returned from method calls or fields and later thrown.
    • Now handles stack pushes/pops fully and appropiately.

Here is the code, in full. You simply want to use the GetAllExceptions(MethodBase) method either as an extension or static method.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using ClrTest.Reflection;

public static class ExceptionAnalyser
{
    public static ReadOnlyCollection<Type> GetAllExceptions(this MethodBase method)
    {
        var exceptionTypes = new HashSet<Type>();
        var visitedMethods = new HashSet<MethodBase>();
        var localVars = new Type[ushort.MaxValue];
        var stack = new Stack<Type>();
        GetAllExceptions(method, exceptionTypes, visitedMethods, localVars, stack, 0);

        return exceptionTypes.ToList().AsReadOnly();
    }

    public static void GetAllExceptions(MethodBase method, HashSet<Type> exceptionTypes,
        HashSet<MethodBase> visitedMethods, Type[] localVars, Stack<Type> stack, int depth)
    {
        var ilReader = new ILReader(method);
        var allInstructions = ilReader.ToArray();

        ILInstruction instruction;
        for (int i = 0; i < allInstructions.Length; i++)
        {
            instruction = allInstructions[i];

            if (instruction is InlineMethodInstruction)
            {
                var methodInstruction = (InlineMethodInstruction)instruction;

                if (!visitedMethods.Contains(methodInstruction.Method))
                {
                    visitedMethods.Add(methodInstruction.Method);
                    GetAllExceptions(methodInstruction.Method, exceptionTypes, visitedMethods,
                        localVars, stack, depth + 1);
                }

                var curMethod = methodInstruction.Method;
                if (curMethod is ConstructorInfo)
                    stack.Push(((ConstructorInfo)curMethod).DeclaringType);
                else if (method is MethodInfo)
                    stack.Push(((MethodInfo)curMethod).ReturnParameter.ParameterType);
            }
            else if (instruction is InlineFieldInstruction)
            {
                var fieldInstruction = (InlineFieldInstruction)instruction;
                stack.Push(fieldInstruction.Field.FieldType);
            }
            else if (instruction is ShortInlineBrTargetInstruction)
            {
            }
            else if (instruction is InlineBrTargetInstruction)
            {
            }
            else
            {
                switch (instruction.OpCode.Value)
                {
                    // ld*
                    case 0x06:
                        stack.Push(localVars[0]);
                        break;
                    case 0x07:
                        stack.Push(localVars[1]);
                        break;
                    case 0x08:
                        stack.Push(localVars[2]);
                        break;
                    case 0x09:
                        stack.Push(localVars[3]);
                        break;
                    case 0x11:
                        {
                            var index = (ushort)allInstructions[i + 1].OpCode.Value;
                            stack.Push(localVars[index]);
                            break;
                        }
                    // st*
                    case 0x0A:
                        localVars[0] = stack.Pop();
                        break;
                    case 0x0B:
                        localVars[1] = stack.Pop();
                        break;
                    case 0x0C:
                        localVars[2] = stack.Pop();
                        break;
                    case 0x0D:
                        localVars[3] = stack.Pop();
                        break;
                    case 0x13:
                        {
                            var index = (ushort)allInstructions[i + 1].OpCode.Value;
                            localVars[index] = stack.Pop();
                            break;
                        }
                    // throw
                    case 0x7A:
                        if (stack.Peek() == null)
                            break;
                        if (!typeof(Exception).IsAssignableFrom(stack.Peek()))
                        {
                            //var ops = allInstructions.Select(f => f.OpCode).ToArray();
                            //break;
                        }
                        exceptionTypes.Add(stack.Pop());
                        break;
                    default:
                        switch (instruction.OpCode.StackBehaviourPop)
                        {
                            case StackBehaviour.Pop0:
                                break;
                            case StackBehaviour.Pop1:
                            case StackBehaviour.Popi:
                            case StackBehaviour.Popref:
                            case StackBehaviour.Varpop:
                                stack.Pop();
                                break;
                            case StackBehaviour.Pop1_pop1:
                            case StackBehaviour.Popi_pop1:
                            case StackBehaviour.Popi_popi:
                            case StackBehaviour.Popi_popi8:
                            case StackBehaviour.Popi_popr4:
                            case StackBehaviour.Popi_popr8:
                            case StackBehaviour.Popref_pop1:
                            case StackBehaviour.Popref_popi:
                                stack.Pop();
                                stack.Pop();
                                break;
                            case StackBehaviour.Popref_popi_pop1:
                            case StackBehaviour.Popref_popi_popi:
                            case StackBehaviour.Popref_popi_popi8:
                            case StackBehaviour.Popref_popi_popr4:
                            case StackBehaviour.Popref_popi_popr8:
                            case StackBehaviour.Popref_popi_popref:
                                stack.Pop();
                                stack.Pop();
                                stack.Pop();
                                break;
                        }

                        switch (instruction.OpCode.StackBehaviourPush)
                        {
                            case StackBehaviour.Push0:
                                break;
                            case StackBehaviour.Push1:
                            case StackBehaviour.Pushi:
                            case StackBehaviour.Pushi8:
                            case StackBehaviour.Pushr4:
                            case StackBehaviour.Pushr8:
                            case StackBehaviour.Pushref:
                            case StackBehaviour.Varpush:
                                stack.Push(null);
                                break;
                            case StackBehaviour.Push1_push1:
                                stack.Push(null);
                                stack.Push(null);
                                break;
                        }

                        break;
                }
            }
        }
    }
}

To summarise, this algorithm recursively enumerates (depth-first) any methods called within the specified one, by reading the CIL instructions (as well as keeping track of methods already visited). It maintains a single list of collections that can be thrown using a HashSet<T> object, which is returned at the end. It additionally maintains an array of local variables and a stack, in order to keep track of exceptions that aren't thrown immediately after they are created.

Of course, this code isn't infallible in it's current state. There are a few improvements that I need to make for it to be robust, namely:

  1. Detect exceptions that aren't thrown directly using an exception constructor. (i.e. The exception is retrieved from a local variable or a method call.)
  2. Support exceptions popped off the stack then later pushed back on.
  3. Add flow-control detection. Try-catch blocks that handle any thrown exception should remove the appropiate exception from the list, unless a rethrow instruction is detected.

Apart from that, I believe the code is reasonably complete. It may take a bit more investigation before I figure out exactly how to do the flow-control detection (though I believe I can see how it operates at the IL-level now).

These functions could probably be turned into an entire library if one was to create a full-featured "exception analyser", but hopefully this will at least provide a sound starting point for such a tool, if not already good enough in its current state.

Anyway, hope that helps!

Onanism answered 15/6, 2009 at 19:31 Comment(11)
Had some issues with this code I explained above in question. I could not format properly in the comment :-|.Thromboplastic
@Jamey: Indeed, there are some issues relating to certain methods in the BCL, such as the one you pointed out. I should have tested with more than relatively simple methods, really! Anyway, I've made a lot of changes to the code, so try the new version and let me know how it works for you. Note: for some reason it outputs String and Assembly among the list of exception types. I can't seem to track this down to a bug in my code, so it may actually be part of the BCL, since IL has no restrictions on exception types. You can however just filter the list to subtypes of Exception if you like.Onanism
+1, +100 if I could - that is an absolutely awesome bit of code. I've just ported it to Mono.Cecil, thanks.Delate
@B Tyler: Thanks! :-) A Mono.Cecil port sounds great... I don't think it was around (or at least not prominent) when I wrote this. Have you publicised the code anywhere?Onanism
This was some really great work. At the same time I wanted a stand alone tool I could point at an assembly and find out out which exceptions a particular method would throw. If that would be useful to any of you, I shared the source code on github... github.com/stevesheldon/ExceptionReflector. It builds on the code from above.Jacquelyn
@SteveSheldon: Gosh, it's been a long time since I touched this stuff... But I'm glad someone finally transformed this approach/code into a proper tool. Thanks for sharing your work!Onanism
@Onanism - Thanks, happy to! For more info on the tool, I wrote a quick blog on it here... steves-rv-travels.com/archives/167.Jacquelyn
That's great; I shall link anyone who asks about the matter in the future to your relevant blog post. :)Onanism
I am a bit frustrated now. C# outdoes Java in so many respects, yet in order to know what Exceptions you need to account for in a try/catch block, you must use reflection to find the exceptions. In Java, this would be as simple as going to the definition of the method because any exceptions you throw must be declared. I really don't have time to grab someone else's code and run it just for the sake of finding out what exceptions are thrown, so I guess I am just tossing my try/catch block instead.Pancho
Just for future reference, because Steve's original link doesn't work anymore: web.archive.org/web/20131209043117/http://steves-rv-travels.com/…Complaisant
Note on MethodBase: Get it via Type.GetMethods(), e.g. typeof(Newtonsoft.Json.JsonConvert).GetMethods().FirstOrDefault(method => method.Name == "DeserializeObject" && !method.IsGenericMethod)Belleslettres
P
13

This should not be extremely hard. You can get list of exceptions created by a method like this:

IEnumerable<TypeReference> GetCreatedExceptions(MethodDefinition method)
{
    return method.GetInstructions()
        .Where(i => i.OpCode == OpCodes.Newobj)
        .Select(i => ((MemberReference) i.Operand).DeclaringType)
        .Where(tr => tr.Name.EndsWith("Exception"))
        .Distinct();
}

Snippets use Lokad.Quality.dll from the Open Source Lokad Shared Libraries (which uses Mono.Cecil to do the heavy-lifting around code reflection). I actually put this code into one of the test cases in trunk.

Say, we have a class like this:

class ExceptionClass
{
    public void Run()
    {
        InnerCall();
        throw new NotSupportedException();
    }

    void InnerCall()
    {
        throw new NotImplementedException();
    }
}

then in order to get all exceptions from just the Run method:

var codebase = new Codebase("Lokad.Quality.Test.dll");
var type = codebase.Find<ExceptionClass>();
var method = type.GetMethods().First(md => md.Name == "Run");

var exceptions = GetCreatedExceptions(method)
    .ToArray();

Assert.AreEqual(1, exceptions.Length);
Assert.AreEqual("NotSupportedException", exceptions[0].Name);

Now all that remains is to walk down the method call stack down to a certain depth. You can get a list of methods referenced by a method like this:

var references = method.GetReferencedMethods();

Now before being able to call GetCreatedExceptions upon any method down the stack we just need to actually look up into the codebase and resolve all MethodReference instances to MethodDefinition instances actually containing the byte code (with some caching to avoid scanning existing branches). That's the most time-consuming part of the code (since Codebase object does not implement any method lookups on top of Cecil), but that should be doable.

Pneumatograph answered 15/6, 2009 at 18:49 Comment(2)
This is vaguely the right way to go about the task, but it is far from complete or robust (as you probably realise, but didn't point out in your answer). Notice that you only check for exception objects created directly in the method. You ignore whether they are thrown or not, if any exceptions are accessed via a field or method, and the pushing/popping involving the stack.Onanism
Thanks for all your effort it is hard to have to pick just one answer sometimes.Thromboplastic
T
9

This answer was posted in the other question you reference and I know I have recommended it before in another similar question. You should give Exception Hunter a try. It lists out every single exception that can possibly be thrown. When I ran it for the first time on my code I was quite surprised by the size of this list for even simple functions. There is a 30 day trial for free so there is no reason not to give it a try.

Trustbuster answered 15/6, 2009 at 13:31 Comment(2)
Yeah, this is probably the most reliable/easiest solution of you have the money to dish out. Simpler and quite effective methods are however possible using not an absurd length of code, as I show.Onanism
The only time where I could see where you would NEED to see every possible exception is in an environment where you have the money to purchase this kind of software. If you are writing free code, using the occasional catch all block and then rethrowing the error is probably a better solution. If you are writing critical code that requires exceptional stability you are going to sell this code and then should factor in the cost of tools such as Exception Hunter to increase your productivity.Trustbuster
C
2

My methodology for this type of situation is to handle all the exceptions that I want to and then override the UnhandledException event of the application to log any other's that I don't know about. Then if I come up against any that I think I could possibly resolve then I would just update accordingly.

Hope that helps!

Cioffred answered 12/6, 2009 at 11:33 Comment(3)
This does help and that is what I am doing now. It is however, usually a client that discovers the one I did not trap and that is why I want to know them all up front. Thanks!Thromboplastic
I suppose the best option would be to investigate each method you are using via MSDN.Cioffred
Yep, that is what I expect to have to do. I just wanted to check here first. Thanks!Thromboplastic
O
2

I'm very doubtful there is any (at least straightforward) way to do this in C#. Saying that, I do have an idea that may work, so read on please...

Firstly, it's worth noting that doing a brute-force search using a huge number of permutations of arguments clearly is not feasible. Even having prior knowledge of parameter types (which I don't believe is desirable in your situation), the task essentially reduces to the halting problem in the general case, since you don't know the function will terminate given certian parameters. Ideally, exceptions should stop this, but it isn't always the case of course.

Now, perhaps the most reliable method is to analyse the source code (or more realistically CIL code) itself to see which exceptions might be thrown. This I believe may actually be workable. A simple algorithm might go something like:

  1. Do a depth-first or breadth-first search of the given method. Find all methods/properties that are called anywhere within the method body, and recurse on them.
  2. For each block of CIL code in tree of methods/propreties, examine code for any exceptions that might be thrown, and add to a list.

This would even allow you to get detailed information about the exceptions, such as whether they are thrown directly by the called method, or deeper in the call stack, or even the exception messages themselves. Anyway, I'll consider giving an implementation of this a try later this afternoon, so I'll let you know how feasible the idea is then.

Onanism answered 15/6, 2009 at 13:30 Comment(1)
Also, can I assume that you want something more than just reading the XML documentation for methods?Onanism
P
1

Unlike java C# does not have the concept of checked exceptions.

On a macro level you should catch everything and log or inform the user of the error. When you know about the specific exceptions a method may raise then definetly handle that appropriately but be sure to let any other exceptions bubble up (preferably) or log them, otherwise you will have bugs that you will not be able to find and generally make life miserable for anyone hired to help reduce the bug list - have been there, not fun! :)

Precipitin answered 14/6, 2009 at 21:54 Comment(2)
I agree 100% that is why I want to be able to figgure out the list of exceptions possible before hand. I want to test and mock all possibilities.Thromboplastic
well if you manage to write tests that cover every possible scenario you will achieve that :)Precipitin
S
1

John Robbins had a series of articles on creating FxCop rules including an MSDN article that would indicate which exceptions were thrown. This was to warn about missing XML documentation for the exceptions but the idea would be the same.

Scornik answered 15/6, 2009 at 13:23 Comment(0)
N
1

I wrote an add-in for Reflector called ExceptionFinder that handles this. You can get it at:

http://exfinderreflector.codeplex.com/

Regards, Jason

Nicotine answered 12/11, 2009 at 19:37 Comment(0)
J
0

This isn't so much an answer as building on top of the great work done by @Noldorin above. I used the code above and figured it would be really useful to have a tool that a developer could point at an arbitrary assembly/dll and see the list of exceptions thrown.

By building on top of the work above, I built a tool which does exactly that. I shared the source on GitHub for anybody interested. It's super simple I only had a couple of hours free to write it but feel free to fork and make updates if you see fit...

Exception Reflector on Github

Jacquelyn answered 31/12, 2012 at 19:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.