Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

Следуя моему предыдущему ответу, мне удалось создать базовый искатель исключений. В нем используется класс ILReader на основе отражения, доступный здесь в блоге MSDN Haibo Luo. (Просто добавьте ссылку на проект.)

Обновления:

  • Теперь обрабатывает локальные переменные и стек.
    • Правильно обнаруживает исключения, возвращаемые из вызовов или полей метода, и позже вызывается.
    • Теперь обрабатывает стеки/всплывающие окна полностью и правильно.

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

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

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

Ответ 6

В отличие от java С# не существует понятия проверенных исключений.

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

Ответ 7

У Джона Роббинса была серия статей по созданию правил FxCop, включая статью

Ответ 8

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

http://exfinderreflector.codeplex.com/

С уважением, Джейсон

Ответ 9

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

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

Исключительный рефлектор в Github