我们目前正在捕获我们知道的异常并记录所有通配符。这确实很有效; 但是,我只是希望有人使用过或知道一种可以发现此信息的工具。
针对之前的回答,我已经成功创建了一个基本的异常查找器。它利用了一个基于反射的ILReader
类,可在Haibo Luo的MSDN博客这里找到。(只需添加对项目的引用即可)
更新:
以下是完整的代码。您只需要使用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>
对象来抛出。此外,它还维护本地变量数组和堆栈,以便跟踪不会立即在创建后抛出的异常。
当然,目前这段代码并不是万无一失的。我需要进行一些改进才能使其更加健壮,具体包括:
rethrow
指令。除此之外,我认为这段代码已经足够完整了。在我找到如何进行流控制检测之前,可能需要进行更多的调查(尽管我现在相信我已经看到了它在IL级别上是如何运作的)。
如果有人想要创建一个全功能的“异常分析器”,这些函数可能可以转化为整个库,但希望即使在当前状态下也提供了一个良好的起点。
无论如何,希望这有所帮助!
这并不是非常困难。您可以像这样获取由方法创建的异常列表:
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共享库的Lokad.Quality.dll(它使用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();
这个答案是在你提到的另一个问题中发布的,我知道我之前在另一个类似的问题中也推荐过它。你应该试试Exception Hunter。它列出了可能抛出的每个异常。当我第一次在我的代码上运行它时,即使是简单的函数,我也对这个列表的长度感到惊讶。有30天的免费试用,所以没有理由不尝试一下。
对于这种情况,我的方法是处理我想要的所有异常,然后覆盖应用程序的UnhandledException事件以记录我不知道的任何其他异常。然后,如果我遇到任何我认为可能可以解决的异常,我就会相应地进行更新。
希望这能帮到你!
我非常怀疑在C#中有任何(至少是直接的)方法可以做到这一点。不过,我有一个可能会起作用的想法,所以请继续阅读...
首先,值得注意的是,使用大量参数排列进行暴力搜索显然是不可行的。即使具有参数类型的先前知识(我认为在您的情况下并不理想),任务在一般情况下本质上缩小为停机问题,因为您不知道函数是否会在给定某些参数时终止。理想情况下,异常应该会停止这个过程,但这当然并不总是发生。
现在,也许最可靠的方法是分析源代码(或更现实的 CIL 代码)本身,以查看可能抛出的异常。我相信这实际上可能是可行的。一个简单的算法可能是:
我为反编译工具Reflector编写了一个名为ExceptionFinder的插件,可以处理这个问题。你可以在以下获取:
http://exfinderreflector.codeplex.com/
敬礼, Jason
与Java不同,C#没有检查异常的概念。
在宏观层面上,您应该捕获所有错误并记录或通知用户。当您了解方法可能引发的特定异常时,一定要适当地处理它们,但一定要让任何其他异常冒泡(最好)或记录它们,否则您将无法找到错误并且通常会使雇用人员的生活变得痛苦,以帮助减少错误列表 - 我曾经在那里,不好玩! :)