2009-06-12 9 views
31

La mia domanda è proprio la stessa di questa "Finding out what exceptions a method might throw in C#". Tuttavia, mi piacerebbe davvero sapere se qualcuno conosce un modo per determinare lo stack di tutte le eccezioni che possono essere generate da un determinato metodo. Sto sperando in uno strumento o un'utilità che possa analizzare il codice in fase di compilazione o attraverso la riflessione come FxCop, StyleCop o NCover. Non ho bisogno di queste informazioni in fase di esecuzione, voglio solo assicurarmi di intercettare le eccezioni e registrarle correttamente nel codice.Come posso determinare quali eccezioni possono essere generate da un determinato metodo?

Attualmente stiamo intrappolando le eccezioni che conosciamo e registrando tutte le wild card. Questo funziona bene; tuttavia, stavo solo sperando che qualcuno abbia usato o conosca uno strumento in grado di scoprire queste informazioni.

+0

Sì, sto ottenendo lo stesso errore, purtroppo. Risulta che la logica richiesta è di nuovo leggermente più complessa di quanto pensassi. Qualche manciata spinta/trazione per lo stack dovrebbe fare il lavoro comunque - che è esavamente che sto provando ora. – Noldorin

+0

Punto di chiarimento, ma intendi tutte le eccezioni lanciate da una funzione così come sono state scritte da uno sviluppatore (ad esempio, lanciare comandi nella funzione) o che potrebbero essere generate da altre routine chiamate dalla funzione (ad esempio un'operazione non valida expcetion generata da una chiamata alla libreria .NET)? – rjzii

+0

Devo solo chiedermi perché stai cercando di reinventare la ruota? Se è stato creato uno strumento che fa esattamente questo e molto bene, perché scrivere codice che si spera possa farlo? – Mark

risposta

44

Seguendo la mia risposta precedente, sono riuscito a creare un cercatore di eccezioni di base. Utilizza una classe ILReader basata su riflessione, disponibile here sul blog MSDN di Haibo Luo. (Basta aggiungere un riferimento al progetto.)

Aggiornamenti:

  1. ora gestisce le variabili locali e la pila.
    • Rileva correttamente le eccezioni restituite da chiamate di metodo o campi e successivamente generate.
    • Ora gestisce lo stack push/pop in modo completo e appropriato.

Ecco il codice, in pieno. Si desidera semplicemente utilizzare il metodo GetAllExceptions(MethodBase) come un'estensione o metodo statico.

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

In sintesi, questo algoritmo ricorsivo enumera (in profondità) eventuali metodi chiamati all'interno della specificato uno, leggendo le istruzioni CIL (così come tenere traccia dei metodi già visitato). Mantiene un unico elenco di raccolte che possono essere generate utilizzando un oggetto HashSet<T>, che viene restituito alla fine.Mantiene inoltre una serie di variabili locali e uno stack, al fine di tenere traccia delle eccezioni che non vengono lanciate immediatamente dopo la loro creazione.

Ovviamente, questo codice non è infallibile nel suo stato attuale. Ci sono alcuni miglioramenti che ho bisogno di fare per essere robusta, vale a dire:

  1. Detect eccezioni che non vengono gettati direttamente utilizzando un costruttore di un'eccezione. (Ad esempio, l'eccezione viene recuperata da una variabile locale o da una chiamata al metodo.)
  2. Le eccezioni di supporto sono saltate fuori dallo stack e poi reinserite.
  3. Aggiungere il rilevamento del controllo del flusso. I blocchi try-catch che gestiscono qualsiasi eccezione generata dovrebbero rimuovere l'eccezione appropriata dall'elenco, a meno che non venga rilevata un'istruzione rethrow.

A parte questo, credo che il codice sia ragionevolmente completo. Potrebbero essere necessarie ulteriori indagini prima di capire esattamente come eseguire il rilevamento del controllo di flusso (anche se credo di poter vedere come funziona ora a livello di IL).

Probabilmente queste funzioni potrebbero essere trasformate in un'intera libreria se si dovesse creare un "analizzatore di eccezioni" completo, ma si spera che questo fornisca almeno un buon punto di partenza per tale strumento, se non già abbastanza buono in il suo stato attuale.

In ogni caso, spero che questo aiuti!

+0

Aveva alcuni problemi con questo codice che ho spiegato sopra in questione. Non ho potuto formattare correttamente nel commento: - |. –

+0

@Jamey: In effetti, ci sono alcuni problemi relativi a certi metodi nel BCL, come quello che hai indicato. Avrei dovuto provare con metodi più che relativamente semplici, davvero! Comunque, ho apportato molte modifiche al codice, quindi prova la nuova versione e fammi sapere come funziona per te. Nota: per qualche motivo emette 'String' e' Assembly' nell'elenco dei tipi di eccezione. Non riesco a rintracciarlo su un bug nel mio codice, quindi * potrebbe * essere effettivamente parte del BCL, poiché IL non ha restrizioni sui tipi di eccezione. Puoi comunque filtrare l'elenco ai sottotipi di Eccezione, se lo desideri. – Noldorin

+1

+1, +100 se potessi - questo è un pezzetto di codice assolutamente fantastico. L'ho appena portato su Mono.Cecil, grazie. – briantyler

2

La mia metodologia per questo tipo di situazione è di gestire tutte le eccezioni che desidero e quindi sovrascrivere l'evento UnhandledException dell'applicazione per registrare qualsiasi altro di cui non sono a conoscenza. Quindi se mi imbatto in qualcosa che penso di poter risolvere, aggiorno di conseguenza.

Spero che questo aiuti!

+0

Questo aiuta ed è quello che sto facendo ora. È tuttavia, di solito, un cliente che scopre quello che non ho intrappolato ed è per questo che voglio conoscerli tutti in anticipo. Grazie! –

+0

Suppongo che l'opzione migliore sarebbe indagare su ciascun metodo che si sta utilizzando tramite MSDN. – James

+0

Sì, questo è quello che mi aspetto di dover fare. Volevo solo controllare qui prima.Grazie! –

1

A differenza di java C# non ha il concetto di eccezioni controllate.

A livello macro è necessario catturare tutto e registrare o informare l'utente dell'errore. Quando conosci le specifiche eccezioni che un metodo può sollevare, gestiscilo in modo appropriato, ma assicurati di lasciare che altre eccezioni esplodano (preferibilmente) o di registrarle, altrimenti avrai bug che non sarai in grado di trovare e che in genere creerai vita infelice per chiunque sia stato ingaggiato per aiutare a ridurre l'elenco dei bug - ci sono stato, non è divertente! :)

+0

Sono d'accordo al 100% è per questo che voglio essere in grado di visualizzare l'elenco delle eccezioni possibili prima mano. Voglio testare e prendere in giro tutte le possibilità. –

+0

beh se riesci a scrivere test che coprano tutti gli scenari possibili, lo otterrai :) –

1

John Robbins aveva una serie di articoli sulla creazione di regole FxCop incluso uno MSDN article che indicava quali eccezioni venivano lanciate. Questo per avvertire della documentazione XML mancante per le eccezioni, ma l'idea sarebbe stata la stessa.

2

Sono molto dubbioso che ci sia un modo (almeno immediato) per farlo in C#. Detto questo, io ho un'idea che può lavoro, quindi continuate a leggere ... si prega di

In primo luogo, vale la pena notare che una ricerca di forza bruta con un enorme numero di permutazioni di argomenti chiaramente non è fattibile. Pur avendo una conoscenza preliminare dei tipi di parametro (che non credo sia auspicabile nella tua situazione), l'attività si riduce essenzialmente allo halting problem nel caso generale, poiché non si sa che la funzione terminerà i determinati parametri certian. Idealmente, le eccezioni dovrebbero fermarlo, ma non è sempre il caso, naturalmente.

Ora, forse il metodo più affidabile è quello di analizzare il codice sorgente (o più realisticamente il codice CIL) per vedere quali eccezioni potrebbero essere lanciate. Questo credo possa essere effettivamente praticabile. Un semplice algoritmo potrebbe essere simile a:

  1. Eseguire una ricerca in profondità o in larghezza del metodo specificato. Trova tutti i metodi/proprietà che sono chiamati ovunque all'interno del corpo del metodo e recali su di essi.
  2. Per ogni blocco di codice CIL nell'albero dei metodi/proprietà, esaminare il codice per eventuali eccezioni che potrebbero essere generate e aggiungere a un elenco.

Ciò consentirebbe anche di ottenere informazioni dettagliate sulle eccezioni, ad esempio se vengono lanciate direttamente dal metodo chiamato o più in profondità nello stack di chiamate o persino dai messaggi di eccezione.Ad ogni modo, prenderò in considerazione la possibilità di dare una implementazione di questo in un prossimo pomeriggio, quindi ti farò sapere quanto sia fattibile l'idea.

+0

Inoltre, posso supporre che tu voglia qualcosa di più della semplice lettura della documentazione XML per i metodi? – Noldorin

9

Questa risposta è stata postata nell'altra domanda a cui si fa riferimento e so che l'ho già consigliata in un'altra domanda simile. Si consiglia di provare Exception Hunter. Elenca ogni singola eccezione che può essere generata. Quando l'ho eseguito per la prima volta sul mio codice, sono rimasto piuttosto sorpreso dalle dimensioni di questo elenco anche per funzioni semplici. C'è una prova di 30 giorni gratis quindi non c'è ragione per non provarci.

+0

Sì, questa è probabilmente la soluzione più affidabile/più semplice che tu abbia i soldi da spendere. I metodi più semplici e abbastanza efficaci sono comunque possibili usando non una lunghezza assurda del codice, come mostro. – Noldorin

+0

L'unica volta in cui ho potuto vedere dove dovresti vedere ogni possibile eccezione è in un ambiente in cui hai i soldi per acquistare questo tipo di software. Se stai scrivendo codice libero, usare l'occasionale blocco di tutti i blocchi e poi rilanciare l'errore è probabilmente una soluzione migliore. Se stai scrivendo un codice critico che richiede una stabilità eccezionale, venderai questo codice e poi dovresti calcolare il costo di strumenti come Exception Hunter per aumentare la tua produttività. – Mark

12

Questo non dovrebbe essere estremamente difficile. È possibile ottenere l'elenco di eccezioni create da un metodo come questo:

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 utilizzare Lokad.Quality.dll dalla Open Source Lokad Shared Libraries (che utilizza Mono.Cecil per fare il sollevamento pesante intorno al codice di riflessione). In realtà sono put this code into one of the test cases in trunk.

Say, abbiamo una classe come questa:

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

    void InnerCall() 
    { 
     throw new NotImplementedException(); 
    } 
} 

poi al fine di ottenere tutte le eccezioni a partire da soli il metodo 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); 

Ora tutto ciò che rimane è quello di camminare per il metodo chiama lo stack fino a una certa profondità. È possibile ottenere un elenco dei metodi di riferimento da un metodo come questo:

var references = method.GetReferencedMethods(); 

Ora, prima di essere in grado di chiamare GetCreatedExceptions su qualsiasi metodo verso il basso la pila abbiamo solo bisogno di guardare in realtà su nel codice di base e risolvere tutto MethodReference istanze alle istanze MethodDefinition che contengono effettivamente il codice byte (con alcune cache per evitare la scansione dei rami esistenti). Questa è la parte del codice che richiede più tempo (dal momento che l'oggetto Codebase non implementa alcuna ricerca di metodi su Cecil), ma dovrebbe essere fattibile.

+0

Questa è vagamente la strada giusta da percorrere per il compito, ma è lontana dall'essere completa o robusta (come probabilmente ti rendi conto, ma non hai indicato la tua risposta). Si noti che si controlla solo per * oggetti eccezione creati direttamente nel metodo *. Ignori se vengono lanciati o meno, se si accede ad eccezioni tramite un campo o un metodo e se il push/popping coinvolge lo stack. – Noldorin

+0

+1 per lo sforzo! –

+0

Grazie a tutti i tuoi sforzi è difficile scegliere una sola risposta a volte. –

0

Questa non è tanto una risposta come la costruzione in cima al grande lavoro fatto da @Noldorin sopra. Ho usato il codice qui sopra e ho pensato che sarebbe stato davvero utile avere uno strumento che uno sviluppatore potesse indirizzare a un assembly/dll arbitrario e vedere l'elenco di eccezioni generate.

Costruendo sopra il lavoro di cui sopra, ho costruito uno strumento che fa esattamente questo. Ho condiviso la fonte su GitHub per chiunque fosse interessato. E 'super semplice che ho avuto solo un paio di ore libere per scriverlo ma sentitevi liberi di forcella e fare gli aggiornamenti, se si vede in forma ...

Exception Reflector on Github

Problemi correlati