本文章是由機器翻譯。

深究 CLR

處理損毀狀態的例外狀況

Andrew Pardoe

本專欄根據的 Visual Studio 2010 搶鮮版所撰寫。 所有的資訊有變更。

內容

什麼是完全? 例外狀況
Win32 SEH 例外狀況和 System.Exception
Managed 程式碼和 SEH
損毀的狀態的例外狀況
是仍然使用 Catch (Exception e) 錯誤
程式碼好好

您有不完全正確,但足以關閉曾經撰寫程式碼嗎? 您必須撰寫程式碼運作正常,當所有項目也,進入,但您不太確定發生錯誤時就會發生什麼事? 還有一個簡單、 不正確的陳述式的可能坐在您撰寫或有以維護某些程式碼中,: catch (Exception e)。 似乎無辜及簡單,但此小的陳述式可以會造成許多問題,無法執行您所預期時。

如果您見過使用例外狀況的程式碼,方法為下列程式碼不會,您就必須讀取此資料行:

public void FileSave(String name)
{
    try
    {
      FileStream fs = new FileStream(name, FileMode.Create);
    }
    catch (Exception)
    {
      throw new System.IO.IOException("File Open Error!");
    }
}

這個程式碼中的錯誤訊息是常見: 很容易撰寫程式碼來攔截所有例外狀況,比為攔截完全無法藉由在您的 try 區塊中執行的程式碼會引發,例外狀況。 但藉由攔截例外狀況階層架構的基底,您會得到抑制任何型別的例外狀況,並將它以 IOException 中轉換。

例外處理會是哪些大多數的人是在使用知識庫 」,而不是 intimate 瞭解這些區域之一。 我將開頭的背景資訊,說明例外狀況的人可能很熟悉例外處理從原生程式設計或一個舊的大學教科書更,處理從 CLR 的觀點。 如果您的舊 Pro 在受管理的例外處理中,隨意跳到不同的例外狀況 > 一節中繼續進行,或一個在 Managed 程式碼,並結構化例外處理 (Structured Exception Handling,SEH)。 請確定不過讀取最後幾節。

什麼是完全? 例外狀況

例外狀況會是訊號偵測到條件時所引發,並非預期在程式執行緒的正常執行。 許多代理程式可以偵測到不正確的條件,並引發例外狀況中。 程式的程式碼] (或 [使用程式庫程式碼) 可以擲回衍生自 System.Exception 的型別,CLR 的執行引擎可以引發例外狀況,Unmanaged 程式碼可以引發以及例外狀況。 在執行的執行緒上引發的例外狀況跨 AppDomain,依照透過原生和 Managed 程式碼的執行緒,如果未處理程式,會視為未處理的例外狀況由作業系統。

例外狀況會指示的錯誤發生。 雖然每一個 Managed 例外狀況會有一個類型,(例如 System.ArgumentException 或 System.ArithmeticException),類型是只有意義的內容中則的例外狀況時引發。 如果它瞭解造成例外狀況發生的條件,程式可以處理例外狀況。 但如果程式無法處理例外狀況,它表示任何數目的不正確的項目。 一旦例外狀況,離開程式,並且只具有一個相當常見的意義: 錯誤發生。

當 Windows 會看到程式不會處理例外狀況,它會嘗試保護程式就的由終止處理序保存資料 (檔案磁碟、 登錄設定,等等)。 如果即使例外狀況原始表示某些良性的未預期的程式狀態,(例如,無法從空的堆疊取出) 就會出現是嚴重的問題,當 Windows 看到它,因為作業系統沒有正確地解譯例外狀況內容。 單一執行緒在一個 AppDomain 中的可以關閉整個 CLR 執行個體將未處理例外狀況 (請參閱 [圖 1 )。

fig01.gif

[圖 1 未執行緒 ’s 處理例外狀況造成整個處理程序,以終止一

例外狀況因此危險時為何會他們因此常見? motorcycles 和鏈結 saws,未經處理的例外狀況功能讓它們非常有用。 在程式執行緒上的一般資料流程會通過呼叫的函式 function 並傳回。 每個呼叫函式會建立執行的框架,在堆疊上 ; 每個傳回會終結該框架 除了變更全域狀態,從只在程式的資料流是藉由為函式參數或傳回值的連續框架之間傳遞資料來達成。 例外處理缺乏每一個呼叫端需要檢查它所呼叫之函式的成功 (或只是假設所有項目一定是 [確定]。

大部分的 Win32 API 會傳回非零值,表示失敗,因為 Windows 無法使用例外處理。 程式設計人員必須換行程式碼會檢查呼叫的函式的傳回值的每個函式呼叫。 例如,目錄中的清單檔案如何的 MSDN 文件從這個程式碼明確地會檢查成功的每個呼叫。 在呼叫 FindNextFile(...) 包裝在檢查,查看是否傳回非零。 如果不成功呼叫一個函數呼叫-GetLastError () — 提供例外狀況的詳細資料。 請注意,成功的下一個框架必須檢查每個呼叫,因為傳回的值一定限於本機的函式範圍:

// FindNextFile requires checking for success of each call 
while (FindNextFile(hFind, &ffd) != 0); 
dwError = GetLastError(); 
if (dwError != ERROR_NO_MORE_FILES) 
{ 
  ErrorHandler(TEXT("FindFirstFile")); 
} 
FindClose(hFind); 
return dwError; 

錯誤狀況,只能從函式包含未預期的條件,該函式的呼叫端傳遞。 例外狀況會有能力將傳遞的函式的執行結果的目前的函式的範圍內至堆疊的每個框架一直框架,知道如何處理未預期的狀況。 CLR 的例外系統 (稱為兩次的例外狀況的系統) 會將例外狀況傳遞至每個前身執行緒的呼叫堆疊上, 從開始呼叫端,並將繼續,直到某些函式指出它將會處理例外狀況 (這稱為在第一階段) 中。

例外狀況的系統將會再回溯則的例外狀況,會引發之間,它會處理 (稱為第二階段) 呼叫堆疊上的每個框架的狀態。 當堆疊回溯,CLR 會同時執行 finally 子句及 fault 子句在每個框架中因為它是回溯。 然後,會執行在處理框架的 catch 子句。

因為,CLR 會檢查呼叫堆疊上的每個前置任務的 Catch 區塊,將呼叫端不需要-例外狀況可以任何地方攔截堆疊。 與其立即檢查每個函式呼叫的結果的程式碼,程式設計人員就可以處理在目前離開,從其中,例外狀況的地方的錯誤。 使用錯誤碼,需要程式設計人員檢查,並傳遞的錯誤程式碼,在每個堆疊框架上,直到它到達,可以處理錯誤條件的位置為止。 例外處理會釋放從檢查例外狀況在堆疊上的每個框架程式設計人員。

如需有關擲回自訂例外狀況型別的詳細資訊,請參閱 < 錯誤處理: 擲回自訂例外狀況型別,從 Managed COM + 伺服器應用程式".

Win32 SEH 例外狀況和 System.Exception

還有一個有趣的副作用來自能夠攔截最立即從其中,例外狀況的例外狀況的。 程式執行緒可以從任何使用中框架,其呼叫堆疊上接收程式例外狀況,而不知道,在例外狀況。 例外狀況不一定表示程式偵測到的錯誤狀況,但是: 程式的執行緒也可能造成程式以外的例外狀況。

如果執行緒的執行會導致錯誤的處理器,然後會將控制項) 轉移到作業系統核心,呈現到為 SEH 例外狀況的執行緒錯誤。 就像您的 Catch 區塊不知道,引發了例外狀況的執行緒的堆疊上, 它不需要知道完全在哪個點,作業系統核心引發 SEH 例外狀況。

Windows 會通知程式關於作業系統使用 SEH 的例外狀況的執行緒。 Managed 程式碼的程式設計人員會很少看到這些因為 CLR 通常可防止的 SEH 例外狀況所指出的錯誤類型。 但是,如果 Windows 會引發的 SEH 例外狀況,CLR 會傳遞至 Managed 程式碼。 雖然很少 SEH 例外狀況,在 Managed 程式碼,Unsafe Managed 程式碼就可以產生一個 STATUS_­ACCESS_VIOLATION 表示該程式嘗試存取無效的記憶體。

如需 SEH 的詳細資訊,請參閱 Matt Pietrek 文件 (英文) > Win32 的上一個損毀課程結構化例外處理「 1 月) 1997 年發行的 Microsoft Systems Journal .

SEH 例外狀況會是一個不同的類別,從您的程式所引發的例外狀況。 程式可能引發例外狀況,因為它會嘗試顯示從空的堆疊項目,或嘗試開啟不存在的檔案。 所有這些例外狀況的意義的程式的執行內容中。 SEH 例外狀況,請參閱程式以外的內容。 存取違規 (AV) 例如,會指出一個嘗試的寫入無效的記憶體。 與程式的錯誤不同的 SEH 例外狀況會指示執行階段的處理序的完整性可能已遭入侵的。 但即使是不同的時,CLR 則會將 SEH 例外狀況傳遞至 Managed 執行緒,衍生自 System.Exception,例外狀況的 SEH 例外狀況可以使用 catch (Exception e) 陳述式攔截。

有些系統會嘗試不同的例外狀況這兩種。 Microsoft Visual C ++ 編譯器會區別引發由 C ++ 的 throw 陳述式和 Win32 SEH 例外狀況,如果您編譯您的程式與 / EH 參數的例外狀況。 此分隔是很有用的因為一般的程式都不知道如何處理錯誤,並未引發中。 如果 C ++ 程式會嘗試將項目加入至一個 std::vector,它應該的作業可能會因為失敗,缺乏記憶體,但不應該預期使用撰寫完善的程式庫的正確程式處理存取違規。

此分隔可用來使用程式設計人員的。 AV 會是一個嚴重的問題: 重要的系統記憶體的未預期的寫入會正常影響處理序的任何一部分。 但某些等,所產生的錯誤 (和未核取) 使用者的輸入,一個除以零 (Divide-by-0) 的錯誤的 SEH 錯誤是較不嚴重。 在除數為零的程式不正確時這是不太可能這會影響系統的任何其他一部分。 事實上,很可能在 C ++ 程式無法處理除以零 (Divide-by-0) 的錯誤,而不用不穩定的其餘的系統。 因此這個分隔有用時它不會很 Express 語意 (Semantics) Managed 程式設計人員需要。

Managed 程式碼和 SEH

送 CLR 有 SEH 例外狀況來將送使用相同的機制,以程式本身所引發的例外狀況 Managed 程式碼至。 這不是問題,只要程式碼不會嘗試處理合理地無法處理的例外狀況。 大部分的程式無法安全地繼續執行的存取違規之後。 不幸的是,CLR 的例外處理模型永遠都有建議來攔截這些嚴重錯誤,讓程式以攔截任何例外狀況,在 System.Exception 的階層架構的頂端的使用者。 但這是不正確的做法。

因為未處理的例外狀況有嚴重的後果時,就撰寫 catch (Exception e) 是一個常見的程式設計錯誤。 但是,您可能認為如果您不知道函式,會引發哪些錯誤,您應該保護對所有可能的錯誤當您的程式會呼叫該函式。 直到您思考這表示可能是損毀的狀態您的處理序時,繼續執行,這似乎合理課程的動作。 有時候,正在中止,並嘗試再次是最佳的選項: 沒有人喜歡看到 Watson 對話方塊中,但是,重新啟動您的程式比若要有損毀您的資料。

程式的內容,他們不瞭解是嚴重的問題所引起的攔截例外狀況。 但是,您無法解決問題,使用例外狀況規格或一些其他合約的機制。 而且很重要的 Managed 的程式可以接收通知的 SEH 例外狀況,因為 CLR 是許多種類的應用程式和主機平台。 如 SQL Server 的某些主機需要總控制其應用程式的處理序。 Managed 程式碼相互操作原生程式碼,有時候必須處理原生 C ++ 例外狀況] 或 [SEH 例外狀況。

但大部分撰寫 catch (Exception e) 設計人員沒有真正要攔截存取違規。 它們會希望發生嚴重錯誤時,其程式會停止,而不是讓程式處於未知的狀態,以及 limp 執行。 這特別是對程式的主機管理,例如 Visual Studio 或 Microsoft Office 增益集。 如果增益集造成存取違規,並再 swallows 例外狀況,主應用程式可能進行傷害自己的狀態 (或使用者檔案) 沒有曾經實現項目發生錯誤。

在 CLR 4 版中,產品團隊進行表示已損毀的處理序狀態不同於其他所有的例外狀況的例外狀況。 我們會指定關於十幾個 SEH 例外狀況,表示已損毀的處理序的狀態。 指定與相關的內容相對於例外狀況型別本身則的例外狀況時引發。 這表示從 Windows 收到存取違規將會標示為一個損毀的狀態例外狀況 (CSE),但寫入擲回新 System.AccessViolation­exception 不會被標示為一個 CSE 在使用者程式碼中引發的一個。 如果您參加 PDC 2008,您會收到一個社群技術預覽的 Visual Studio 2010 包含這些變更。

有一點要注意的例外狀況不會損毀處理序: 則例外狀況在處理序狀態中偵測到損毀之後,會引發。 例如,當透過不安全的程式碼中的指標寫入會參考不屬於該程式的記憶體,會引發存取違規。 不合法的寫入未實際發生 — 作業系統會檢查記憶體的擁有權,並無法從製作位置動作。 存取違規表示指標本身已損毀在的舊版執行緒的執行階段中。

損毀的狀態的例外狀況

在 4 (含) 以後版本,在 CLR 例外狀況系統將無法傳遞 CSEs 到 Managed 程式碼除非程式碼已明確指出它可以處理損毀的處理序狀態的例外狀況。 這表示在 Managed 程式碼中,catch (Exception e) 的執行個體不會有提供給它,CSE。 藉由內部的 CLR 例外狀況的系統變更您不必變更例外狀況階層架構中,或變更處理語意 (Semantics) 的任何 Managed 的語言的例外。

基於相容性的理由 CLR 小組會提供一些方法可讓您執行您的舊程式碼,在舊的行為:

  • 如果要重新編譯您在 Microsoft.NET Framework 3.5 中建立的程式碼,並將,.NET Framework 4.0 中執行而不需要更新來源,您可以加入的項目在應用程式組態檔: legacyCorruptedState­­ExceptionsPolicy = true。
  • 針對.NET Framework 3.5 或舊版的執行階段編譯的組件將能夠處理損毀的狀態的例外狀況 (也就是說,維護舊的行為) 是.NET Framework 4.0 上執行時。

如果要讓程式碼以處理 CSEs,您必須以標記包含具有新的屬性,例外狀況子句 (最後,catch 或 fault) 函式指示您的目的: System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions。 如果引發一個 CSE,CLR 會執行其搜尋相符的 catch 子句的但只會搜尋以 HandleProcessCorruptedStateExceptions 屬性 (Attribute) 標示的函式 (請參閱 [圖 2 )。

[圖 2 使用 HandleProcessCorruptedStateExceptions

// This program runs as part of an automated test system so you need
// to prevent the normal Unhandled Exception behavior (Watson dialog).
// Instead, print out any exceptions and exit with an error code.
[HandledProcessCorruptedStateExceptions]
public static int Main()
{
    try
    {
        // Catch any exceptions leaking out of the program
        CallMainProgramLoop();
    }
    catch (Exception e) // We could be catching anything here
    {
        // The exception we caught could have been a program error
        // or something much more serious. Regardless, we know that
        // something is not right. We'll just output the exception
        // and exit with an error. We won't try to do any work when 
        // the program or process is in an unknown state!
        System.Console.WriteLine(e.Message);
        return 1;
    }
    return 0;
  } 

如果找不到適合的 catch 子句則 CLR 會回溯堆疊正常,但只會執行最後和 fault 區塊 (並在 C# 中,隱含最後封鎖的使用陳述式) 以屬性 (Attribute) 標示的函式。 發生在部分信任或透明程式碼中,因為受信任的主應用程式不希望的不受信任增益集來攔截,並忽略這些嚴重的例外狀況時,則會忽略,HandleProcessCorruptedStateExceptions] 屬性。

是仍然使用 Catch (Exception e) 錯誤

即使在 CLR 例外狀況的系統會將最差的例外狀況標示 CSE,它最好還是不以您的程式碼撰寫 catch (Exception e)。 例外狀況,表示發生未預期的情況下在整個範圍。 CLR 可以偵測到最差的例外狀況 (SEH 例外狀況,表示可能已損毀的處理序狀態。 但其他未預期的狀況仍可能有害如果它們是被忽略或一般處理。

在 [處理序損毀缺乏 CLR 會提供一些相當強式的保證,有關程式的正確性和記憶體安全。 時執行程式,以安全的 Microsoft Intermediate Language (MSIL) 程式碼撰寫就可以確定您程式中的所有指示將會正確地都執行。 但執行程式指示来說是通常不同進行程式設計人員的需要。 完全正確,根據在 CLR 的程式可以損毀保存的狀態例如寫入磁碟的程式檔案。

採用做為簡單的範例中管理的高學校測驗分數的資料庫的程式。 程式會使用物件導向設計原則來封裝資料,並引發 Managed 例外狀況,表示發生未預期的事件。 有一天,學校 Secretary 按下 Enter 鍵一太許多時間產生成績檔案時。 程式嘗試從一個空的佇列取出值,並引發一個 QueueEmptyException 它會處理透過框架在呼叫堆疊上。

某個地方堆疊頂端附近是函式,GenerateGrades(),使用 try / catch 子句攔截例外狀況。 不幸的是,GenerateGrades() 都有學生會儲存在佇列中,且沒有任何的概念如何使用一個 QueueEmpty­exception 不知道。 但程式設計人員撰寫 GenerateGrades() 不希望程式損毀,而不儲存目前為止的已計算的資料。 所有會安全地寫入磁碟,並且程式結束。

這個程式問題是,它會數可能不正確的假設。 說學生佇列中遺失的項目是在結尾是什麼? 也許是第一個的學生的資料錄你略過,或在第十。 例外狀況只告訴程式設計人員程式不正確。 採取任何動作,將資料儲存到磁碟或修復 > 和繼續執行 — 是只是一般的錯誤。 您不不知情內容中,例外狀況的可能任何正確的動作。

如果程式必須攔截接近到其中,例外狀況的例外狀況,它可能已經能夠採取適當動作。 程式會知道在 QueueEmptyException 函式,嘗試從佇列移除的學生中表示。 如果函式會攔截例外狀況型別 — 而攔截例外狀況型別的整個類別 — 它就會嘗試進行正確的程式狀態更好位置。

一般來說,攔截特定例外狀況是要做,因為它提供大部分的內容至您的例外狀況處理常式,正確的件事。 如果您的程式碼可能可以攔截兩個例外狀況,然後它必須能夠同時處理。 撰寫程式碼指出 catch (Exception e),都必須能夠處理常值的任何例外狀況。 這是很難讓的承諾。

某些語言會嘗試攔截例外狀況的主要類別時,防止程式設計人員。 舉例來說,C ++ 會有例外狀況規格,一種機制,允許程式設計人員指定哪些例外狀況可能會引發該函式中。 Java 會採用此進一步檢查的例外編譯器強制執行需求來指定特定的例外狀況類別。 在兩種的程式語言請您列出超出這個函式宣告函式可以傳送例外狀況,並呼叫者,才能處理這些例外狀況]。 例外狀況規格會是個好的主意,但它們有混合練習中的結果。

還有狀態辯論,做為要任何 Managed 程式碼應該能夠處理 CSEs。 這些例外狀況通常代表一個層級的系統錯誤,而且只能由程式碼,瞭解系統層級的內容。 大多數的人不需要有能力處理 CSEs 時,有幾個案例,其中的必要。

一種情況是當您是非常接近的例外狀況發生。 舉例來說,假設呼叫的已知 buggy 的原生程式碼的程式。 偵錯程式碼,您學習它有時候零存取它之前的指標,會造成存取違規。 您可以使用 HandleProcessCorruptedStateExceptions 屬性函式會呼叫使用 P / Invoke,因為您知道指標損毀的原因,是習慣的處理序的完整性維護的原生程式碼。

使用這個屬性 (Attribute) 的屬性 (Property) 可能會呼叫的其他案例時,您就可以從錯誤。 事實上,您準備幾乎結束您的處理序。 假設您完成撰寫主機或架構,要執行一些自訂的記錄在錯誤的情況下。 您可能會包裝您的 Main 函式使用 try / catch / finally 區塊,並將它標記與 HandleProcessCorruptedStateExceptions。 如果錯誤意外地讓它最設定程式的主函式,您某些資料寫入您的記錄檔為一些工作,您對,和結束處理序。 您執行任何工作處理序的完整性有問題時可能是危險,但是如果自訂的記錄有時候會失敗的可接受。

請看一下圖表,如 [圖 3 ] 所示。 在此函式 1 (fn1()) 使用屬性化 HandleProcess­CorruptedStateExceptions] 使其 catch 子句會攔截存取違規。 在最後區塊中 3 的函式不會執行即使函式 1 中攔截到例外狀況。 函式 4 (在堆疊的底端,會引發存取違規。

fig03.gif

[圖 3 例外狀況和存取違規

沒有這些案例中您正在做是完全安全但其中只會終止處理序是無法接受的案例中,保證。 不過,如果您要處理一個 CSE 龐大的負擔上沒有您以程式設計人員正確地執行。 請記得 CLR 例外狀況的系統無法將一個 CSE 甚至傳遞至任何未標記以新屬性是在搜尋相符的 catch 子句) 時,第一個傳遞或 (當它回溯每個框架的狀態與最後執行與 fault 區塊),第二次傳遞的函式。

在最後區塊可在順序保證一定,會執行該程式碼是否有例外狀況。 (錯誤的區塊執行例外狀況發生時,但它們具有類似保證的一律執行時,只)。 這些建構用來清除重要的資源,例如釋放檔案控制代碼,或復原模擬內容。

甚至會寫入使用可靠的程式碼會限制的執行,除非它是以 HandleProcessCorruptedStateExceptions 屬性標示函式中,已經引發一個 CSE 時,不會執行區域 (CER)。 是很難撰寫正確的程式碼,會處理在 CSE,並繼續安全地執行處理序。

仔細查看程式碼以查看哪些可能出錯的 [圖 4 ] 中。 如果這個程式碼不在函式可以處理 CSEs,然後在 finally 區塊將不會執行發生存取違規的情況時。 沒如果處理序終止-開啟的檔案控制代碼將會發行。 但如果其他的程式碼會攔截存取違規,並且嘗試還原狀態,需要知道它已有關閉這個檔案,以及還原此程式已變更任何其他外部狀態。

[圖 4: Finally 區塊可能會無法執行

void ReadFile(int index)
    {
      System.IO.StreamReader file = 
        new System.IO.StreamReader(filepath);
          try
          {
            file.ReadBlock(buffer, index, buffer.Length);
          }
          catch (System.IO.IOException e)
          {
            Console.WriteLine("File Read Error!");
          }
          finally
          {
            if (file != null)
                {
                    file.Close()
                }
          }
    }

如果決定您要處理一個 CSE 您程式碼必須預期沒有已卸載的嚴重狀態的資料堆。 最後,且 fault 區塊不具有已執行。 已執行限制的執行區域。 程式 (和處理序 — 未知狀態中。

如果知道您的程式碼會執行正確的動作再您知道該怎麼做。 但是,如果您不確定狀態的程式正在執行中,] 然後 [最好只可讓您的處理序結束。 或者,如果您的應用程式裝載,叫用您的主機已指定,擴大原則。 請參閱 Alessandro Catorcini 和 Brian Grunkemeyer CLR 深究從 12 月 2007 的資料行 如需撰寫可靠的程式碼和 CER 的詳細資訊。

程式碼好好

雖然 CLR 阻止您 naively 攔截 CSEs,它最好仍無法攔截的例外狀況有過度廣泛的類別。 但 catch (Exception e) 會出現在許多程式碼,而且很不太可能這將會變更。 不提供例外狀況表示 naively 取得所有例外狀況的程式碼已損毀的處理序狀態,您防止此程式碼在嚴重的情況更糟。

下次您寫入或維護攔截例外狀況的程式碼會考慮例外狀況的表示。 您攔截到擲回記錄您的程式 (和它使用的程式庫) 的相符項目之型別嗎? 您知道如何處理例外狀況,例如,您的程式可以正確並安全地繼續執行嗎?

例外處理會是一個功能強大的工具,應該仔細和 thoughtfully 使用。 如果您真的需要使用這項功能,如果您真的需要處理例外狀況,可能會指出損毀的處理序 — CLR 將您的信任和可讓您執行這項操作。 只要小心,並正確執行。

您問題或意見寄至 clrinout@Microsoft.com.

Andrew Pardoe 都是在 Microsoft 的 CLR 的程式管理員。 他負責桌上型電腦和 Silverlight 執行階段執行引擎的許多方面。 在達到他 Andrew.Pardoe@Microsoft.com.