如何确定给定方法可以抛出哪些异常?

时间:2022-03-24 06:19:50

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.

我的问题与这个问题非常相似“找出方法可能在C#中引发的异常”。但是,我真的想知道是否有人知道一种方法来确定给定方法可能抛出的所有异常的堆栈。我希望有一个工具或实用程序,我可以在编译时或通过像FxCop,StyleCop或NCover这样的反射来分析代码。我在运行时不需要这些信息我只是想确保我们捕获异常并在代码中正确记录它们。

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.

我们目前正在捕获我们所知道的异常并记录所有外卡。这确实很有效;但是,我只是希望有人使用或知道可以发现这些信息的工具。

9 个解决方案

#1


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.)

按照我之前的回答,我设法创建了一个基本的异常查找程序。它使用了一个基于反射的ILReader类,可以在Haibo Luo的MSDN博客上找到。 (只需添加对项目的引用。)

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.
    • 现在完全和适当地处理堆栈推送/弹出。

  2. 现在处理局部变量和堆栈。正确检测从方法调用或字段返回的异常,然后抛出。现在完全和适当地处理堆栈推送/弹出。

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

这是完整的代码。您只想将GetAllExceptions(MethodBase)方法用作扩展或静态方法。

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.

总而言之,该算法通过读取CIL指令(以及跟踪已访问的方法)递归地枚举(深度优先)在指定的方法中调用的任何方法。它维护一个可以使用HashSet 对象抛出的集合列表,该对象在最后返回。它还维护一个局部变量数组和一个堆栈,以便跟踪创建后不会立即抛出的异常。

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. 检测未使用异常构造函数直接抛出的异常。 (即从局部变量或方法调用中检索异常。)

  3. Support exceptions popped off the stack then later pushed back on.
  4. 支持异常弹出堆栈然后再推迟。

  5. 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.
  6. 添加流量控制检测。除非检测到重新抛出指令,否则处理任何抛出异常的try-catch块应从列表中删除适当的异常。

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).

除此之外,我相信代码是完全合理的。在我弄清楚如何进行流量控制检测之前,可能需要进行一些调查(尽管我相信我现在可以看到它在IL级别的运行方式)。

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!

无论如何,希望有所帮助!

#2


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.

片段使用来自开源Lokad共享库的Lokad.Quality.dll(它使用Mono.Cecil来完成代码反射的繁重工作)。我实际上将此代码放入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:

然后,为了从Run方法获取所有异常:

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.

现在,在能够在堆栈上的任何方法上调用GetCreatedExceptions之前,我们只需要实际查找代码库并将所有MethodReference实例解析为实际包含字节代码的MethodDefinition实例(通过一些缓存来避免扫描现有分支)。这是代码中最耗时的部分(因为Codebase对象没有在Cecil上实现任何方法查找),但这应该是可行的。

#3


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.

这个答案发布在你引用的另一个问题中,我知道我之前在另一个类似的问题中推荐过它。你应该试试Exception Hunter。它列出了可能抛出的每个异常。当我第一次在我的代码上运行它时,即使是简单的函数,我对这个列表的大小感到非常惊讶。免费试用30天,所以没有理由不尝试。

#4


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.

我对这种情况的方法是处理我想要的所有异常,然后覆盖应用程序的UnhandledException事件以记录我不知道的任何其他事件。然后,如果我遇到任何我认为我可以解决的问题,那么我会相应更新。

Hope that helps!

希望有所帮助!

#5


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...

我非常怀疑在C#中有任何(至少是直截了当的)方法。说,我确实有一个可能有用的想法,请继续阅读...

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.

首先,值得注意的是,使用大量的参数排列进行蛮力搜索显然是不可行的。即使事先了解参数类型(在我们的情况下我认为不合适),任务本质上也会减少到一般情况下的暂停问题,因为您不知道函数将终止给定的certian参数。理想情况下,例外应该阻止这种情况,但当然并非总是如此。

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:

现在,也许最可靠的方法是分析源代码(或更现实的CIL代码)本身,以查看可能抛出的异常。我相信这可能实际上是可行的。一个简单的算法可能类似于:

  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. 对给定方法执行深度优先或广度优先搜索。查找在方法体内任何位置调用的所有方法/属性,并对它们进行递归。

  3. 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.
  4. 对于方法/ propreties树中的每个CIL代码块,检查可能抛出的任何异常的代码,并添加到列表中。

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.

这甚至可以让您获得有关异常的详细信息,例如它们是由被调用方法直接抛出,还是在调用堆栈中更深层次,甚至是异常消息本身。无论如何,我会考虑在今天下午晚些时候尝试实现这个,所以我会告诉你这个想法是多么可行。

#6


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

与java不同,C#没有检查异常的概念。

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! :)

在宏观层面上,您应该捕获所有内容并记录或通知用户错误。当你知道一个方法可能引发的特定异常时,然后肯定会适当地处理它,但一定要让任何其他异常冒泡(最好)或记录它们,否则你将会遇到一些你将无法找到的错误并且通常会让它们生活对于任何一个被雇用来帮助减少错误列表的人来说都很悲惨 - 一直在那里,没有乐趣! :)

#7


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.

John Robbins有一系列关于创建FxCop规则的文章,其中包括一篇MSDN文章,该文章将指出抛出了哪些异常。这是为了警告缺少异常的XML文档,但这个想法是一样的。

#8


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

我为Reflector编写了一个名为ExceptionFinder的加载项来处理这个问题。你可以在:

http://exfinderreflector.codeplex.com/

Regards, Jason

#9


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.

这不仅仅是建立在@Noldorin上面所做的伟大工作之上的答案。我使用上面的代码,并认为拥有一个开发人员可以指向任意程序集/ dll并查看抛出的异常列表的工具非常有用。

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...

通过构建上面的工作,我构建了一个完全正确的工具。我在GitHub上分享了任何感兴趣的人的来源。它非常简单我只有几个小时的免费写作,但如果你觉得合适,可以随意分叉并进行更新......

Exception Reflector on Github

Github上的异常反射器

#1


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.)

按照我之前的回答,我设法创建了一个基本的异常查找程序。它使用了一个基于反射的ILReader类,可以在Haibo Luo的MSDN博客上找到。 (只需添加对项目的引用。)

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.
    • 现在完全和适当地处理堆栈推送/弹出。

  2. 现在处理局部变量和堆栈。正确检测从方法调用或字段返回的异常,然后抛出。现在完全和适当地处理堆栈推送/弹出。

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

这是完整的代码。您只想将GetAllExceptions(MethodBase)方法用作扩展或静态方法。

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.

总而言之,该算法通过读取CIL指令(以及跟踪已访问的方法)递归地枚举(深度优先)在指定的方法中调用的任何方法。它维护一个可以使用HashSet 对象抛出的集合列表,该对象在最后返回。它还维护一个局部变量数组和一个堆栈,以便跟踪创建后不会立即抛出的异常。

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. 检测未使用异常构造函数直接抛出的异常。 (即从局部变量或方法调用中检索异常。)

  3. Support exceptions popped off the stack then later pushed back on.
  4. 支持异常弹出堆栈然后再推迟。

  5. 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.
  6. 添加流量控制检测。除非检测到重新抛出指令,否则处理任何抛出异常的try-catch块应从列表中删除适当的异常。

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).

除此之外,我相信代码是完全合理的。在我弄清楚如何进行流量控制检测之前,可能需要进行一些调查(尽管我相信我现在可以看到它在IL级别的运行方式)。

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!

无论如何,希望有所帮助!

#2


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.

片段使用来自开源Lokad共享库的Lokad.Quality.dll(它使用Mono.Cecil来完成代码反射的繁重工作)。我实际上将此代码放入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:

然后,为了从Run方法获取所有异常:

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.

现在,在能够在堆栈上的任何方法上调用GetCreatedExceptions之前,我们只需要实际查找代码库并将所有MethodReference实例解析为实际包含字节代码的MethodDefinition实例(通过一些缓存来避免扫描现有分支)。这是代码中最耗时的部分(因为Codebase对象没有在Cecil上实现任何方法查找),但这应该是可行的。

#3


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.

这个答案发布在你引用的另一个问题中,我知道我之前在另一个类似的问题中推荐过它。你应该试试Exception Hunter。它列出了可能抛出的每个异常。当我第一次在我的代码上运行它时,即使是简单的函数,我对这个列表的大小感到非常惊讶。免费试用30天,所以没有理由不尝试。

#4


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.

我对这种情况的方法是处理我想要的所有异常,然后覆盖应用程序的UnhandledException事件以记录我不知道的任何其他事件。然后,如果我遇到任何我认为我可以解决的问题,那么我会相应更新。

Hope that helps!

希望有所帮助!

#5


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...

我非常怀疑在C#中有任何(至少是直截了当的)方法。说,我确实有一个可能有用的想法,请继续阅读...

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.

首先,值得注意的是,使用大量的参数排列进行蛮力搜索显然是不可行的。即使事先了解参数类型(在我们的情况下我认为不合适),任务本质上也会减少到一般情况下的暂停问题,因为您不知道函数将终止给定的certian参数。理想情况下,例外应该阻止这种情况,但当然并非总是如此。

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:

现在,也许最可靠的方法是分析源代码(或更现实的CIL代码)本身,以查看可能抛出的异常。我相信这可能实际上是可行的。一个简单的算法可能类似于:

  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. 对给定方法执行深度优先或广度优先搜索。查找在方法体内任何位置调用的所有方法/属性,并对它们进行递归。

  3. 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.
  4. 对于方法/ propreties树中的每个CIL代码块,检查可能抛出的任何异常的代码,并添加到列表中。

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.

这甚至可以让您获得有关异常的详细信息,例如它们是由被调用方法直接抛出,还是在调用堆栈中更深层次,甚至是异常消息本身。无论如何,我会考虑在今天下午晚些时候尝试实现这个,所以我会告诉你这个想法是多么可行。

#6


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

与java不同,C#没有检查异常的概念。

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! :)

在宏观层面上,您应该捕获所有内容并记录或通知用户错误。当你知道一个方法可能引发的特定异常时,然后肯定会适当地处理它,但一定要让任何其他异常冒泡(最好)或记录它们,否则你将会遇到一些你将无法找到的错误并且通常会让它们生活对于任何一个被雇用来帮助减少错误列表的人来说都很悲惨 - 一直在那里,没有乐趣! :)

#7


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.

John Robbins有一系列关于创建FxCop规则的文章,其中包括一篇MSDN文章,该文章将指出抛出了哪些异常。这是为了警告缺少异常的XML文档,但这个想法是一样的。

#8


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

我为Reflector编写了一个名为ExceptionFinder的加载项来处理这个问题。你可以在:

http://exfinderreflector.codeplex.com/

Regards, Jason

#9


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.

这不仅仅是建立在@Noldorin上面所做的伟大工作之上的答案。我使用上面的代码,并认为拥有一个开发人员可以指向任意程序集/ dll并查看抛出的异常列表的工具非常有用。

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...

通过构建上面的工作,我构建了一个完全正确的工具。我在GitHub上分享了任何感兴趣的人的来源。它非常简单我只有几个小时的免费写作,但如果你觉得合适,可以随意分叉并进行更新......

Exception Reflector on Github

Github上的异常反射器