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

Jam*_*een 32 c# error-handling exception

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

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

Nol*_*rin 46

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

更新:

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

这是完整的代码.您只想将该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;
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  1. 检测未使用异常构造函数直接抛出的异常.(即从局部变量或方法调用中检索异常.)
  2. 支持异常弹出堆栈然后再推回.
  3. 添加流量控制检测.除非rethrow检测到指令,否则处理任何抛出异常的try-catch块应从列表中删除适当的异常.

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

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

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

  • 这是一些非常棒的工作。同时我想要一个独立的工具,我可以指向一个程序集并找出特定方法会抛出哪些异常。如果这对你们中的任何人有用,我在 github 上分享了源代码... https://github.com/stevesheldon/ExceptionReflector。它建立在上面的代码之上。 (2认同)
  • @Noldorin - 谢谢,很高兴!有关该工具的更多信息,我在这里写了一篇关于它的快速博客... http://steves-rv-travels.com/archives/167。 (2认同)

Rin*_*lin 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();
}
Run Code Online (Sandbox Code Playgroud)

片段使用来自开源Lokad共享库的 Lokad.Quality.dll(它使用Mono.Cecil来完成代码反射的繁重工作).我实际上将此代码放入trunk中的一个测试用例中.

说,我们有这样一个类:

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

    void InnerCall()
    {
        throw new NotImplementedException();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,为了从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);
Run Code Online (Sandbox Code Playgroud)

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

var references = method.GetReferencedMethods();
Run Code Online (Sandbox Code Playgroud)

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


Mar*_*ark 9

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