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

时间:2009-06-12 11:29:44

标签: c# exception error-handling

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

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

9 个答案:

答案 0 :(得分:46)

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

更新:

  1. 现在处理局部变量和堆栈。
    • 正确检测从方法调用或字段返回的异常,然后抛出。
    • 现在可以完全适当地处理堆栈推送/弹出。
  2. 以下是完整的代码。您只想将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;
                    }
                }
            }
        }
    }
    

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

    当然,这个代码在当前状态下并不是绝对可靠的。我需要做一些改进才能使其健壮,即:

    1. 检测未使用异常构造函数直接抛出的异常。 (即从局部变量或方法调用中检索异常。)
    2. 支持异常弹出堆栈然后再推回。
    3. 添加流量控制检测。处理任何抛出异常的try-catch块应该从列表中删除适当的异常,除非检测到rethrow指令。
    4. 除此之外,我相信代码合理地完成。在我弄清楚如何进行流量控制检测之前,可能需要进行一些调查(尽管我相信我现在可以看到它在IL级别的运行方式)。

      如果要创建一个功能齐全的“异常分析器”,这些函数可能会变成一个完整的库,但希望这至少可以为这样一个工具提供一个良好的起点,如果还不够好的话。现状。

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

答案 1 :(得分:12)

这应该不是很难。您可以获取由以下方法创建的例外列表:

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使用来自开源Lokad Shared Libraries的Lokad.Quality.dll(它使用Mono.Cecil来完成代码反射的繁重工作)。我实际上是put this code into one of the test cases in trunk

说,我们有这样一个类:

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

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

然后为了从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);

现在剩下的就是将方法调用堆栈向下移动到一定深度。您可以获取由以下方法引用的方法列表:

var references = method.GetReferencedMethods();

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

答案 2 :(得分:9)

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

答案 3 :(得分:2)

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

希望有所帮助!

答案 4 :(得分:2)

我很怀疑在C#中有任何(至少是直截了当的)方法。说,我确实知道可以工作,所以请继续阅读...

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

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

  1. 对给定方法执行深度优先或广度优先搜索。查找在方法体内任何位置调用的所有方法/属性,并对它们进行递归。
  2. 对于方法/ propreties树中的每个CIL代码块,检查可能抛出的任何异常的代码,并添加到列表中。
  3. 这甚至可以让您获得有关异常的详细信息,例如它们是由被调用方法直接抛出,还是在调用堆栈中更深层次,甚至是异常消息本身。无论如何,我会考虑在今天下午晚些时候尝试实现这个,所以我会告诉你这个想法是多么可行。

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

获得它

http://exfinderreflector.codeplex.com/

此致 杰森

答案 8 :(得分:0)

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

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

Exception Reflector on Github