2009-06-12 86 views
31

我的問題跟這個"Finding out what exceptions a method might throw in C#"真的一樣。但是,我真的很想知道是否有人知道如何確定可能由給定方法拋出的所有異常的堆棧。我希望有一個工具或實用工具,可以在編譯時或通過反射(如FxCop,StyleCop或NCover)分析代碼。我在運行時不需要這些信息,我只是想確保我們捕獲異常並將它們正確記錄在代碼中。如何確定給定方法可拋出哪些異常?

我們目前正在捕獲我們瞭解並記錄所有通配符的例外情況。這確實很好。然而,我只是希望有人使用或知道可以發現這些信息的工具。

+0

是的,我很遺憾得到同樣的錯誤。事實證明,所需的邏輯再次比我想象的要複雜得多。然而,對於堆棧的一些虛擬推/拉處理應該完成這項工作 - 這是我現在正在嘗試的。 – Noldorin 2009-06-17 00:06:43

+0

澄清的要點,但是你是指由開發人員編寫的函數拋出的所有異常(例如,在函數中拋出命令)或者也可能由函數調用的其他例程拋出的所有異常(例如,無效操作.NET庫調用拋出的表達式)? – rjzii 2009-06-18 13:09:34

+0

我只是想知道你爲什麼要重新發明輪子?如果一個工具已經被創建完成,那麼爲什麼要編寫希望這樣做的代碼呢? – Mark 2009-06-18 20:28:04

回答

44

跟着我以前的答案,我設法創建了一個基本的異常查找程序。它使用基於反射的ILReader類,在海博羅的MSDN博客上提供here。 (只需添加到項目的引用。)

更新:

  1. 現在處理局部變量和堆棧。
    • 正確地檢測到方法調用或字段返回的異常,然後拋出。
    • 現在處理堆棧推動/彈出充分和適當。

以下是代碼全部。您只需要使用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>對象拋出的集合的單個列表,最後返回該對象。它另外還維護一個局部變量和一個堆棧的數組,以便跟蹤在創建後不立即拋出的異常。

當然,這個代碼在它的當前狀態中是不可錯誤的。有跡象表明,我需要爲它是強大的,即一些改進:

  1. 檢測未使用異常的構造函數直接拋出的異常。 (即從局部變量或方法調用中檢索異常。)
  2. 支持異常彈出堆棧,然後再推回去。
  3. 添加流量控制檢測。除非檢測到rethrow指令,否則處理任何拋出的異常的Try-catch塊應該從列表中刪除相應的異常。

除此之外,我相信代碼是合理完成。在我弄清楚如何進行流量控制檢測之前,可能需要更多的調查(儘管我相信我可以看到它是如何在IL級別上運行的)。

如果要創建一個功能齊全的「異常分析器」,這些函數可能會變成整個庫,但希望這至少可以爲這樣的工具提供一個良好的起點,如果尚未足夠好的話其當前狀態。

無論如何,希望有所幫助!

2

我的這種類型的情況的方法是處理我想要的所有異常,然後重寫應用程序的UnhandledException事件以記錄其他我不知道的事件。然後,如果我反對任何我認爲可以解決的問題,那麼我會相應地更新。

希望有幫助!

+0

這確實有幫助,而這正是我現在所做的。然而,通常情況下,客戶會發現我沒有陷入困境的客戶,這就是爲什麼我想先了解他們。謝謝! – 2009-06-12 11:38:12

+0

我想最好的選擇是通過MSDN調查你使用的每種方法。 – James 2009-06-12 11:43:44

+0

是的,這是我期望要做的。我只是想先查看這裏。謝謝! – 2009-06-12 12:06:29

1

不像java C#沒有檢查異常的概念。

在宏觀層面上,您應該捕捉所有內容並記錄或通知用戶錯誤。當你知道具體的例外情況時,可能會引發一個方法,然後定義恰當的處理方式,但一定要讓其他例外冒出來(最好)或記錄下來,否則你將會遇到一些你無法找到的錯誤,任何被僱傭來幫助減少錯誤列表的人都會感到痛苦 - 一直在那裏,而不是好玩! :)

+0

我同意100%,這就是爲什麼我希望能夠事先設定可能的例外清單。我想測試和嘲笑所有的可能性。 – 2009-06-15 13:06:28

1

John Robbins有一系列關於創建FxCop規則的文章,其中包括MSDN article,這些文章將指出拋出哪些異常。這是爲了警告缺少XML例外的文檔,但這個想法是一樣的。

2

我很懷疑有什麼(至少直截了當)的方式來做到這一點在C#中。這麼說,我有一個想法,可能工作,因此,閱讀,請...

首先,值得注意的是,這樣做使用參數排列的數量龐大的一個強力搜索顯然是不可行的。即使事先了解了參數類型(我不相信在您的情況下也是可取的),但在一般情況下,任務本質上會簡化爲halting problem,因爲您不知道該函數會終止給定的certian參數。理想情況下,例外應該阻止這一點,但當然並非總是如此。

現在,也許最可靠的方法是分析源代碼(或更現實的CIL代碼)本身,以查看可能拋出哪些異常。我相信這實際上可行。一個簡單的算法可能類似於:

  1. 對給定方法執行深度優先搜索或廣度優先搜索。找到方法體內任何地方調用的所有方法/屬性,並對它們進行遞歸。
  2. 對於方法/ propreties樹中的每個CIL代碼塊,檢查可能拋出的任何異常的代碼,並將其添加到列表中。

這甚至可以讓你獲得關於異常的詳細信息,比如它們是直接被調用方法拋出,還是深入調用堆棧,甚至是異常消息本身。無論如何,我會考慮在今天下午晚些時候給你一個這樣的嘗試,所以我會告訴你這個想法是多麼可行。

9

此答案已發佈在您參考的其他問題中,並且我知道我之前曾在其他類似問題中推薦過它。你應該試試Exception Hunter。它列出了可能拋出的每一個異常。當我第一次在代碼中運行它時,我對這個列表的大小甚至簡單的功能感到非常驚訝。免費試用期爲30天,所以沒有理由不試試。

12

這應該不是很難。你可以通過一個方法是這樣創造的例外列表:

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能做到圍繞代碼反射繁重工作)。我其實put this code into one of the test cases in trunk

說,我們有這樣一個類:

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

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

那麼爲了得到公正的運行方法的所有異常:

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

現在剩下的就是走在方法調用堆棧到一定的深度。能夠在在堆棧中向下,我們只需要真正仰望的代碼庫,並解決所有MethodReference任何方法調用GetCreatedExceptions

var references = method.GetReferencedMethods(); 

現在:你可以通過一個方法是這樣引用的方法列表實例到實際包含字節碼的MethodDefinition實例(通過一些緩存來避免掃描現有分支)。這是代碼中最耗時的部分(因爲Codebase對象沒有在Cecil上實現任何方法查找),但這應該是可行的。

0

這與其說是一個答案,構建由@Noldorin上面做了偉大的工作之上。我使用上面的代碼,並認爲有一個開發人員可以指向任意程序集/ dll並查看拋出的異常列表的工具是非常有用的。

通過建立在上述工作的基礎上,我構建了一個完全符合這一點的工具。我在GitHub上分享了任何感興趣的人的來源。這是超級簡單的我只有一對夫婦的隨意寫,但隨時到餐桌和進行更新,如果你認爲合適的時間...

Exception Reflector on Github