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:
- 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:
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.)
Le eccezioni di supporto sono saltate fuori dallo stack e poi reinserite.
- 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!
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
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
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