Как я могу определить, какие исключения могут быть сгенерированы данным методом? - PullRequest
32 голосов
/ 12 июня 2009

Мой вопрос действительно такой же, как этот "Выяснение, какие исключения может вызвать метод в C #" . Однако мне бы очень хотелось узнать, знает ли кто-нибудь о способе определения стека всех исключений, которые могут быть сгенерированы данным методом. Я надеюсь на инструмент или утилиту, которые я могу анализировать код во время компиляции или с помощью отражения, как FxCop, StyleCop или NCover. Мне не нужна эта информация во время выполнения, я просто хочу убедиться, что мы перехватываем исключения и правильно записываем их в наш код.

В настоящее время мы отслеживаем исключения, о которых мы знаем, и регистрируем все подстановочные знаки. Это работает хорошо; Однако я просто надеялся, что кто-то использовал или знает инструмент, который может обнаружить эту информацию.

Ответы [ 9 ]

46 голосов
/ 15 июня 2009

Следуя моему предыдущему ответу, мне удалось создать базовый искатель исключений. Он использует класс 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;
                }
            }
        }
    }
}

Подводя итог, этот алгоритм рекурсивно перечисляет (сначала в глубину) любые методы, вызываемые в указанном, путем чтения инструкций CIL (а также отслеживания уже посещенных методов). Он поддерживает единый список коллекций, которые могут быть выброшены с использованием объекта HashSet<T>, который возвращается в конце. Кроме того, он поддерживает массив локальных переменных и стек, чтобы отслеживать исключения, которые не генерируются сразу после их создания.

Конечно, этот код не является безошибочным в его текущем состоянии. Есть несколько улучшений, которые нужно сделать, чтобы он был устойчивым, а именно:

  1. Обнаружение исключений, которые не генерируются напрямую, с помощью конструктора исключений. (т.е. исключение извлекается из локальной переменной или вызова метода.)
  2. Исключения поддержки выталкиваются из стека, а затем снова включаются.
  3. Добавить обнаружение контроля потока. Блоки try-catch, которые обрабатывают любое выброшенное исключение, должны удалить соответствующее исключение из списка, если не обнаружена инструкция rethrow.

Кроме того, я считаю, что код достаточно завершен. Может потребоваться немного больше расследования, прежде чем я пойму, как именно выполнить обнаружение управления потоком (хотя я полагаю, что теперь я вижу, как оно работает на уровне IL).

Эти функции, вероятно, можно было бы превратить в целую библиотеку, если бы нужно было создать полнофункциональный «анализатор исключений», но, надеюсь, это по крайней мере обеспечит хорошую отправную точку для такого инструмента, если он еще недостаточно хорош в его текущее состояние.

В любом случае, надеюсь, это поможет!

12 голосов
/ 15 июня 2009

Это не должно быть чрезвычайно сложно. Вы можете получить список исключений, созданных методом, подобным этому:

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();
}

Фрагменты используют Lokad.Quality.dll из открытого исходного кода Lokad Shared Libraries (который использует Mono.Cecil для выполнения тяжелой работы по отражению кода). Я на самом деле поместил этот код в один из тестовых примеров в транке .

Скажем, у нас есть такой класс:

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), но это должно быть выполнимо.

9 голосов
/ 15 июня 2009

Этот ответ был опубликован в другом вопросе, на который вы ссылаетесь, и я знаю, что рекомендовал его ранее в другом подобном вопросе. Вам следует попробовать Исключительный охотник . В нем перечислены все исключения, которые могут быть выброшены. Когда я впервые запустил его в своем коде, я был очень удивлен размером этого списка даже для простых функций. Существует 30-дневная пробная версия бесплатно, поэтому нет никаких причин, чтобы не попробовать.

2 голосов
/ 15 июня 2009

Я очень сомневаюсь, что есть какой-то (по крайней мере, простой) способ сделать это в C #. Сказав это, у меня есть идея, что может работать, поэтому продолжайте читать, пожалуйста ...

Во-первых, стоит отметить, что выполнение перебора с использованием огромного количества перестановок аргументов явно неосуществимо. Даже имея предварительные знания о типах параметров (которые я не считаю желательными в вашей ситуации), задача существенно сводится к проблеме остановки в общем случае, так как вы не знаете, что функция завершится учитывая определенные параметры. В идеале исключения должны остановить это, но это не всегда так.

Теперь, возможно, самый надежный метод - это анализ самого исходного кода (или более реалистичного кода CIL), чтобы увидеть, какие исключения могут быть выброшены. Я верю, что на самом деле это возможно. Простой алгоритм может выглядеть примерно так:

  1. Выполните поиск данного метода по глубине или по ширине. Найдите все методы / свойства, которые вызываются в любом месте тела метода, и используйте их.
  2. Для каждого блока кода CIL в дереве методов / свойств проверьте код на наличие исключений и добавьте его в список.

Это даже позволит вам получить подробную информацию об исключениях, например, были ли они выброшены напрямую вызываемым методом, или глубже в стеке вызовов, или даже сами сообщения об исключениях. Во всяком случае, я расскажу о том, чтобы попытаться реализовать это позже во второй половине дня, поэтому я дам вам знать, насколько осуществима эта идея.

2 голосов
/ 12 июня 2009

Моя методология для этого типа ситуации - обработать все исключения, которые я хочу, и затем переопределить событие UnhandledException приложения, чтобы регистрировать любые другие, о которых я не знаю. Тогда, если я столкнусь с чем-то, что, по моему мнению, я мог бы решить, то я просто обновил бы соответственно.

Надеюсь, это поможет!

1 голос
/ 12 ноября 2009

Я написал надстройку для Reflector под названием ExceptionFinder, которая обрабатывает это. Вы можете получить его по адресу:

http://exfinderreflector.codeplex.com/

С уважением, Jason

1 голос
/ 15 июня 2009

У Джона Роббинса была серия статей по созданию правил FxCop, в том числе статья MSDN , в которой указывалось бы, какие исключения были выброшены. Это должно было предупредить об отсутствии XML-документации для исключений, но идея была бы такой же.

1 голос
/ 15 июня 2009

В отличие от Java C # не имеет понятия проверенных исключений.

На макроуровне вы должны все поймать и войти или сообщить пользователю об ошибке. Когда вы знаете о конкретных исключениях, метод может быть вызван, а затем определенно обрабатывает это соответствующим образом, но обязательно разрешите всплыть (предпочтительно) любым другим исключениям или зарегистрируйте их, в противном случае у вас будут ошибки, которые вы не сможете найти, и в целом создайте жизнь несчастный для любого нанятого, чтобы помочь уменьшить список ошибок - был там, не весело! :)

0 голосов
/ 31 декабря 2012

Это не столько ответ, сколько построение поверх большой работы, проделанной @Noldorin выше. Я использовал приведенный выше код и подумал, что было бы очень полезно иметь инструмент, который разработчик мог бы указать на произвольную сборку / dll и увидеть список исключений.

Основываясь на вышеупомянутой работе, я создал инструмент, который делает именно это. Я поделился источником на GitHub для всех, кто заинтересован. Это очень просто, у меня было всего пару часов на то, чтобы написать его, но вы можете свободно раскошелиться и делать обновления, если считаете нужным ...

Отражатель исключения на Github

...