Comment puis-je déterminer quelles exceptions peuvent être levées par une méthode donnée?

Ma question est vraiment la même que celle-ci “Trouver les exceptions qu’une méthode peut lancer en C #” . Cependant, j’aimerais vraiment savoir si quelqu’un connaît un moyen de déterminer la stack de toutes les exceptions pouvant être levées par une méthode donnée. J’espère un outil ou un utilitaire que je peux parsingr le code lors de la compilation ou par reflection, comme FxCop, StyleCop ou NCover. Je n’ai pas besoin de ces informations au moment de l’exécution. Je veux simplement m’assurer que nous capturons les exceptions et les enregistrons correctement dans notre code.

Nous capturons actuellement les exceptions que nous connaissons et enregistrons tous les jokers. Cela fonctionne bien. Cependant, j’espérais simplement que quelqu’un a utilisé ou connaît un outil permettant de découvrir cette information.

Suite à ma réponse précédente, j’ai réussi à créer un outil de recherche d’exception de base. Il utilise une classe ILReader basée sur la ILReader , disponible ici sur le blog MSDN de Haibo Luo. (Ajoutez simplement une référence au projet.)

Mises à jour:

  1. Gère maintenant les variables locales et la stack.
    • Détecte correctement les exceptions renvoyées par des appels de méthode ou des champs et levées ultérieurement.
    • Gère maintenant la stack pousse / saute complètement et correctement.

Voici le code, en entier. Vous souhaitez simplement utiliser la GetAllExceptions(MethodBase) en tant qu’extension ou méthode statique.

 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 GetAllExceptions(this MethodBase method) { var exceptionTypes = new HashSet(); var visitedMethods = new HashSet(); var localVars = new Type[ushort.MaxValue]; var stack = new Stack(); GetAllExceptions(method, exceptionTypes, visitedMethods, localVars, stack, 0); return exceptionTypes.ToList().AsReadOnly(); } public static void GetAllExceptions(MethodBase method, HashSet exceptionTypes, HashSet visitedMethods, Type[] localVars, Stack 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; } } } } } 

Pour résumer, cet algorithme énumère de manière récursive (en premier lieu) toutes les méthodes appelées dans la méthode spécifiée, en lisant les instructions CIL (et en gardant une trace des méthodes déjà visitées). Il gère une liste unique de collections pouvant être HashSet aide d’un object HashSet , qui est renvoyé à la fin. En outre, il gère un tableau de variables locales et une stack, afin de garder une trace des exceptions qui ne sont pas levées immédiatement après leur création.

Bien sûr, ce code n’est pas infaillible dans son état actuel. Il y a quelques améliorations que je dois faire pour qu’il soit robuste, à savoir:

  1. Détectez les exceptions qui ne sont pas levées directement à l’aide d’un constructeur d’exception. (c’est-à-dire que l’exception est extraite d’une variable locale ou d’un appel de méthode.)
  2. Les exceptions de support sont sorties de la stack, puis repoussées.
  3. Ajouter une détection de contrôle de stream. Les blocs try-catch qui gèrent toute exception levée doivent supprimer l’exception appropriée de la liste, sauf si une instruction de rethrow est détectée.

En dehors de cela, je pense que le code est assez complet. Il faudra peut-être un peu plus d’investigation avant de savoir exactement comment procéder à la détection de contrôle de stream (bien que je puisse comprendre comment cela fonctionne maintenant au niveau IL).

Ces fonctions pourraient probablement être transformées en une bibliothèque complète si l’on créait un “parsingur d’exceptions” complet, mais nous espérons que cela fournira au moins un bon sharepoint départ pour un tel outil, si ce n’est déjà assez bon dans son état actuel.

Quoi qu’il en soit, espérons que cela aide!

Cela ne devrait pas être extrêmement difficile. Vous pouvez obtenir la liste des exceptions créées par une méthode comme celle-ci:

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

Les extraits de code utilisent Lokad.Quality.dll à partir des bibliothèques partagées Open Source Lokad (qui utilisent Mono.Cecil pour effectuer le travail fastidieux autour de la reflection de code). En fait, j’ai mis ce code dans l’un des cas de test du coffre .

Disons que nous avons un cours comme celui-ci:

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

puis pour obtenir toutes les exceptions de la méthode Run:

 var codebase = new Codebase("Lokad.Quality.Test.dll"); var type = codebase.Find(); 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); 

Maintenant, il ne rest plus qu’à parcourir la stack d’appels de méthodes jusqu’à une certaine profondeur. Vous pouvez obtenir une liste de méthodes référencées par une méthode comme celle-ci:

 var references = method.GetReferencedMethods(); 

Maintenant, avant de pouvoir appeler GetCreatedExceptions sur n’importe quelle méthode de la stack, nous devons simplement rechercher dans la base de code et résoudre toutes les instances de MethodReference en instances de MethodDefinition contenant réellement le code octet (avec une mise en cache pour éviter d’parsingr les twigs existantes). C’est la partie du code qui prend le plus de temps (puisque l’object Codebase n’implémente aucune recherche de méthode sur Cecil), mais cela devrait être faisable.

Cette réponse a été postée dans l’autre question que vous avez mentionnée et je sais que je l’avais déjà recommandée dans une autre question similaire. Vous devriez essayer Exception Hunter . Il répertorie chaque exception qui peut éventuellement être levée. Lorsque je l’ai exécuté pour la première fois sur mon code, j’ai été assez surpris par la taille de cette liste, même pour des fonctions simples. Il y a un essai gratuit de 30 jours, il n’y a donc aucune raison de ne pas l’essayer.

Ma méthodologie pour ce type de situation consiste à gérer toutes les exceptions souhaitées, puis à remplacer l’événement UnhandledException de l’application pour consigner toute autre information inconnue de ma part. Ensuite, si je rencontre des problèmes que je pense pouvoir résoudre, je les actualiserai en conséquence.

J’espère que cela pourra aider!

Je doute fort qu’il existe un moyen (au moins simple) de faire cela en C #. Cela dit, j’ai une idée qui peut fonctionner, alors poursuivez votre lecture …

Tout d’abord, il convient de noter qu’une recherche en force brute utilisant un grand nombre de permutations d’arguments n’est manifestement pas réalisable. Même en ayant une connaissance préalable des types de parameters (ce qui, à mon avis, n’est pas souhaitable dans votre cas), la tâche se réduit essentiellement au problème stoppant dans le cas général, puisque vous ne savez pas que la fonction mettra fin aux parameters certian. Idéalement, des exceptions devraient mettre fin à cela, mais ce n’est pas toujours le cas, bien sûr.

Maintenant, peut-être que la méthode la plus fiable consiste à parsingr le code source (ou le code CIL de manière plus réaliste) pour voir quelles exceptions pourraient être levées. Je pense que cela peut effectivement être réalisable. Un algorithme simple pourrait aller quelque chose comme:

  1. Effectuez une recherche en profondeur ou en largeur d’abord de la méthode donnée. Recherchez toutes les méthodes / propriétés appelées n’importe où dans le corps de la méthode et effectuez une récurrence sur celles-ci.
  2. Pour chaque bloc de code CIL dans l’arborescence méthodes / propriétés, examinez le code pour voir si des exceptions sont générées et ajoutez-le à une liste.

Cela vous permettrait même d’obtenir des informations détaillées sur les exceptions, par exemple si elles sont levées directement par la méthode appelée, ou plus profondément dans la stack d’appels, ou même les messages d’exception eux-mêmes. Quoi qu’il en soit, je vais essayer de mettre en œuvre cet essai plus tard cet après-midi, donc je vous ferai savoir à quel point l’idée est réalisable à ce moment-là.

Contrairement à Java, C # n’a pas le concept d’exceptions vérifiées.

Au niveau macro, vous devez tout prendre et vous connecter ou informer l’utilisateur de l’erreur. Lorsque vous connaissez les exceptions spécifiques qu’une méthode peut déclencher, puis les gérer définitivement, mais assurez-vous de laisser les autres exceptions bouillonner (de préférence) ou les consigner, sinon vous aurez des bugs que vous ne pourrez pas trouver et génèrent généralement la vie. misérable pour quiconque embauché pour aider à réduire la liste des bugs – ont été là, pas amusant! 🙂

John Robbins a publié une série d’articles sur la création de règles FxCop, notamment un article MSDN qui indiquerait les exceptions lancées. C’était pour mettre en garde sur la documentation XML manquante pour les exceptions, mais l’idée serait la même.

J’ai écrit un complément pour Reflector appelé ExceptionFinder qui gère cela. Vous pouvez l’obtenir à:

http://exfinderreflector.codeplex.com/

Cordialement, Jason

Ce n’est pas tant une réponse que de bâtir sur l’excellent travail réalisé par @Noldorin ci-dessus. J’ai utilisé le code ci-dessus et j’ai pensé qu’il serait vraiment utile de disposer d’un outil qu’un développeur pourrait pointer sur un assembly / dll arbitraire et voir la liste des exceptions levées.

En construisant au-dessus du travail ci-dessus, j’ai construit un outil qui fait exactement cela. J’ai partagé la source sur GitHub pour toute personne intéressée. C’est super simple, je n’avais que quelques heures libres pour l’écrire, mais n’hésitez pas à apporter des modifications et à les mettre à jour si vous le souhaitez …

Exception Reflector sur Github