finally ブロックの中で呼び出されるメソッドは例外をスローすることがある。そのような例外をキャッチして処理しないと、try ブロック全体が途中で終了してしまう。try ブロックの中でスローされた例外は失われ、リカバリのための処理を行うメソッドは例外の発生原因となる問題に対処できなくなってしまうのである。さらに、例外の発生によりプログラムの制御の流れが変更されることで、finally ブロック中の、例外が発生した個所以降のコードが実行されなくなってしまう。それゆえ、プログラムは finally ブロックのなかでスローされたチェック例外(checked exception)を適切に処理しなければならない。
チェック例外が finally ブロックの外に伝播することを許すことは「ERR04-J. finally ブロックの処理を途中で終了しない」にも違反する。
違反コード
以下の違反コード例では、finally ブロックで reader オブジェクトをクローズしている。プログラマは、finally ブロック中の文は例外をスローしないと間違って想定しており、finally ブロックで発生する例外を適切に処理していない。
public class Operation { public static void doOperation(String some_file) { // ... 文字エンコーディングのチェックや設定を行うコード ... try { BufferedReader reader = new BufferedReader(new FileReader(some_file)); try { // 何らかの操作を行う } finally { reader.close(); // ... その他のクリーンアップコード ... } } catch (IOException x) { // 実行をハンドラーに移す } } }
close() メソッドは IOException 例外をスローするかもしれない。例外がスローされると、close() 以降にあるクリーンアップを行うコードが実行されなくなってしまう。内側の try 節には IOException をキャッチする catch 節が存在しないが、外側の try 節の catch ブロックでキャッチするようになっているため、コンパイラはこの問題を診断しない。また、close() からスローされた例外は、「何らかの操作を行う」ブロックを実行しているときに発生する例外をマスクしてしまい、正しくリカバーできなくなる。
適合コード (finally ブロックの中で例外に対処する)
以下の適合コードでは、finally ブロックのなかの close() メソッド呼出しを try-catch ブロックで囲んでいる。発生する可能性のある IOException は finally ブロックの外に伝播せずに try-catch ブロックのなかで処理される。
public class Operation { public static void doOperation(String some_file) { // ... 文字エンコーディングのチェックや設定 ... try { BufferedReader reader = new BufferedReader(new FileReader(some_file)); try { // なんらかの操作を行う } finally { try { reader.close(); } catch (IOException ie) { // 実行をハンドラーに移す } // ... その他のクリーンアップコード ... } } catch (IOException x) { // 実行をハンドラーに移す } } }
適合コード (Java SE 7: try-with-resources)
Java SE 7 では新たに try-with-resources が導入された。これを使うことで、エラー発生時にリソースを自動的にクローズすることができる。以下の適合コードは try-with-resources を使ってファイルを適切にクローズしている。
public class Operation { public static void doOperation(String some_file) { // ... 文字エンコーディングのチェックや設定を行うコード ... try ( // try-with-resources BufferedReader reader = new BufferedReader(new FileReader(some_file))) { // 何らかの操作を行う } catch (IOException ex) { System.err.println("thrown exception: " + ex.toString()); Throwable[] suppressed = ex.getSuppressed(); for (int i = 0; i < suppressed.length; i++) { System.err.println("suppressed exception: " + suppressed[i].toString()); } // 実行をハンドラーに移す } } public static void main(String[] args) { if (args.length < 1) { System.out.println("Please supply a path as an argument"); return; } doOperation(args[0]); } }
doOperation メソッドの try ブロックのなかで発生する IOException は catch ブロックでキャッチされ、thrown exception として出力される。キャッチされる例外には、BufferedReader の生成時に発生する例外も含まれる。reader をクローズするときに発生する IOException も、catch ブロックでキャッチされ、同じ出力が得られる。try ブロックと reader のクローズの両方で IOException 例外がスローされる場合、catch 節は両方の例外をキャッチするが、"thrown exception" の出力は try ブロックの例外に対して行われる。reader をクローズする際に発生する例外は抑制され、suppressed exception として出力される。どのような場合にも、reader は安全にクローズされる。
リスク評価
finally ブロックのなかで発生する例外に適切に処理しないと、予期せぬ結果を招く可能性がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
ERR05-J | 低 | 低 | 中 | P2 | L3 |
関連ガイドライン
MITRE CWE | CWE-460. Improper cleanup on thrown exception |
CWE-584. Return inside finally block | |
CWE-248. Uncaught exception | |
CWE-705. Incorrect control flow scoping |
参考文献
[Bloch 2005] | Puzzle 41. Field and Stream |
[Chess 2007] | 8.3, Preventing Resource Leaks (Java) |
[Harold 1999] | |
[J2SE 2011] | The try-with-resources Statement |
翻訳元
これは以下のページを翻訳したものです。
ERR05-J. Do not let checked exceptions escape from a finally block (revision 91)