简体   繁体   English

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

[英]How can I determine which exceptions can be thrown by a given method?

My question is really the same as this one "Finding out what exceptions a method might throw in C#" . 我的问题与这个问题非常相似“找出方法可能会在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. 我希望有一个工具或实用程序,我可以在编译时或通过像FxCop,StyleCop或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. 但是,我只是希望有人使用或知道可以发现这些信息的工具。

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. 它采用基于反射的ILReader类,可以在这里上海博罗的MSDN博客。 (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. 您只想将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). 总而言之,该算法通过读取CIL指令(以及跟踪已访问的方法)递归地枚举(深度优先)在指定的方法中调用的任何方法。 It maintains a single list of collections that can be thrown using a HashSet<T> object, which is returned at the end. 它维护一个可以使用HashSet<T>对象抛出的集合列表,该对象在最后返回。 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. 检测未使用异常构造函数直接抛出的异常。 (ie 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. 除非检测到rethrow指令,否则处理任何抛出异常的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! 无论如何,希望有所帮助!

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). 片段使用来自开源Lokad共享库的 Lokad.Quality.dll(它使用Mono.Cecil来完成代码反射的繁重工作)。 I actually put this code into one of the test cases in trunk . 我实际上将此代码放入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). 现在,在能够在堆栈上的任何方法上调用GetCreatedExceptions之前,我们只需要实际查找代码库并将所有MethodReference实例解析为实际包含字节代码的MethodDefinition实例(通过一些缓存来避免扫描现有分支)。 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. 这是代码中最耗时的部分(因为Codebase对象没有在Cecil上实现任何方法查找),但这应该是可行的。

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. 你应该尝试一下Exception Hunter 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. 免费试用30天,所以没有理由不尝试。

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. 我对这种情况的方法是处理我想要的所有异常,然后覆盖应用程序的UnhandledException事件以记录我不知道的任何其他事件。 Then if I come up against any that I think I could possibly resolve then I would just update accordingly. 然后,如果我遇到任何我认为我可以解决的问题,那么我会相应更新。

Hope that helps! 希望有所帮助!

I'm very doubtful there is any (at least straightforward) way to do this in C#. 我非常怀疑在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. 即使事先了解参数类型(在我们的情况下我认为这是不可取的),任务本质上也会减少到一般情况下的暂停问题 ,因为您不知道函数将终止给定的certian参数。 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. 现在,也许最可靠的方法是分析源代码(或更现实的CIL代码)本身,以查看可能抛出的异常。 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. 对于方法/ 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. 无论如何,我会考虑在今天下午稍后试一试,所以我会告诉你这个想法有多可行。

I wrote an add-in for Reflector called ExceptionFinder that handles this. 我为Reflector编写了一个名为ExceptionFinder的加载项来处理这个问题。 You can get it at: 你可以在:

http://exfinderreflector.codeplex.com/ http://exfinderreflector.codeplex.com/

Regards, Jason 问候,杰森

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! 当你知道某个方法可能引发的特定异常时,就可以正确处理它,但一定要让任何其他异常冒出来(最好)或记录它们,否则你将会遇到一些你无法找到的错误并且通常会让它们生活起来对于那些被雇用来帮助减少错误清单的人来说很悲惨 - 一直在那里,没有乐趣! :) :)

John Robbins had a series of articles on creating FxCop rules including an MSDN article that would indicate which exceptions were thrown. John Robbins撰写了一系列关于创建FxCop规则的文章 ,其中包括一篇MSDN文章 ,该文章将指出抛出了哪些异常。 This was to warn about missing XML documentation for the exceptions but the idea would be the same. 这是为了警告缺少异常的XML文档,但这个想法是一样的。

This isn't so much an answer as building on top of the great work done by @Noldorin above. 这不仅仅是建立在@Noldorin上面所做的伟大工作之上的答案。 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. 我使用上面的代码,并认为有一个开发人员可以指向任意程序集/ 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. 我在GitHub上分享了任何感兴趣的人的来源。 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 Github上的异常反射器

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM