[インデックス 18412] ファイルの概要
このコミットは、Go言語の標準ライブラリ text/tabwriter
パッケージにおけるパニック時のバックトレースの改善を目的としています。具体的には、tabwriter
の内部処理中に発生したパニックが、より分かりやすいメッセージと共に再パニックされるように変更され、デバッグ時の情報が強化されています。
コミット
commit d885aeaaa4263717a18c322da7c9dfc6847aa2b2
Author: Josh Bleecher Snyder <josharian@gmail.com>
Date: Tue Feb 4 10:19:02 2014 -0800
text/tabwriter: improve panic backtraces
Fixes #7117.
LGTM=gri
R=golang-codereviews, gobot, gri
CC=golang-codereviews
https://golang.org/cl/53310044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d885aeaaa4263717a18c322da7c9dfc6847aa2b2
元コミット内容
text/tabwriter: improve panic backtraces
Fixes #7117.
LGTM=gri
R=golang-codereviews, gobot, gri
CC=golang-codereviews
https://golang.org/cl/53310044
変更の背景
text/tabwriter
パッケージは、テキストを整形してタブ区切りの列を揃えるための機能を提供します。このパッケージの内部処理において、例えば基盤となる io.Writer
がエラーを返したり、予期せぬパニックが発生した場合、そのパニックが適切に処理されず、デバッグが困難になる可能性がありました。
このコミットの背景には、おそらく tabwriter
の Flush
メソッドや Write
メソッドの実行中に、下層の io.Writer
からのパニックが捕捉された際に、そのパニックが元のエラーとして適切に伝播されず、デバッグ情報が不足するという問題があったと考えられます。コミットメッセージにある Fixes #7117
は、この特定の挙動に関する課題を指しています。この変更は、パニック発生時に、より明確な情報(どの操作中にパニックが発生したか)をバックトレースに含めることで、問題の特定と解決を容易にすることを目的としています。
前提知識の解説
text/tabwriter
パッケージ: Go言語の標準ライブラリの一つで、テキストデータをタブ区切りで整形し、列を揃えて出力するための機能を提供します。Writer
型が主要なインターフェースであり、Write
メソッドでデータを書き込み、Flush
メソッドでバッファをフラッシュして整形された出力を完了します。- パニック (Panic): Go言語におけるランタイムエラーの一種です。通常、プログラムの回復不可能なエラーや、プログラマの想定外の状況で発生します。パニックが発生すると、通常の実行フローは中断され、
defer
関数が実行された後、プログラムは終了します。 - リカバリ (Recover):
panic
によって中断された実行フローを捕捉し、回復させるための組み込み関数です。defer
関数内でしか呼び出すことができません。recover
がnil
以外の値を返した場合、パニックが発生したことを意味し、その値はpanic
に渡された引数です。 - バックトレース (Backtrace): プログラムがクラッシュまたはパニックした際に、その時点までの関数呼び出しの履歴(スタックトレース)を示すものです。デバッグ時に問題発生箇所を特定するために非常に重要な情報です。
osError
型:text/tabwriter
パッケージ内部で定義されている非公開の構造体で、io.Writer
から返されるエラーをラップするために使用されます。これは、tabwriter
自身が生成したエラーと、下層のio.Writer
から伝播されたエラーを区別するために利用されます。
技術的詳細
このコミットの主要な変更点は、handlePanic
関数と、Writer
型の Flush
および Write
メソッドにおける handlePanic
の呼び出し方にあります。
handlePanic
関数の変更
変更前:
func handlePanic(err *error) {
if e := recover(); e != nil {
*err = e.(osError).err // re-panics if it's not a local osError
}
}
変更後:
func handlePanic(err *error, op string) {
if e := recover(); e != nil {
if nerr, ok := e.(osError); ok {
*err = nerr.err
return
}
panic("tabwriter: panic during " + op)
}
}
op string
パラメータの追加:handlePanic
関数にop
という文字列パラメータが追加されました。このパラメータは、パニックが発生した操作(例: "Flush" や "Write")を示すために使用されます。osError
の型アサーションの改善: 変更前はe.(osError).err
と直接型アサーションを行っていましたが、これはe
がosError
型でない場合にパニックを引き起こす可能性がありました。変更後はnerr, ok := e.(osError)
というパターンで型アサーションを行い、ok
がtrue
の場合のみosError
として処理します。これにより、osError
以外のパニックが捕捉された場合でも、安全に処理を継続できます。- 明確な再パニックメッセージ:
e
がosError
型でない場合(つまり、tabwriter
内部で発生したosError
ではない、予期せぬパニックの場合)、panic("tabwriter: panic during " + op)
という形式で再パニックされるようになりました。これにより、バックトレースに「tabwriter: panic during Flush
」や「tabwriter: panic during Write
」といった、どの操作中にパニックが発生したかを示す明確なメッセージが含まれるようになります。これはデバッグ時に非常に役立ちます。
Flush
および Write
メソッドの変更
Writer
型の Flush
メソッドと Write
メソッドの defer
ステートメントで handlePanic
を呼び出す際に、op
パラメータが追加されました。
Flush
メソッド:defer handlePanic(&err)
からdefer handlePanic(&err, "Flush")
へ変更。Write
メソッド:defer handlePanic(&err)
からdefer handlePanic(&err, "Write")
へ変更。
これにより、Flush
または Write
の実行中にパニックが発生し、それが osError
ではない場合、それぞれ「tabwriter: panic during Flush
」または「tabwriter: panic during Write
」というメッセージで再パニックされるようになります。
テストケースの追加
tabwriter_test.go
に、パニック時の挙動を検証するための新しいテストケースが追加されました。
panicWriter
構造体:io.Writer
インターフェースを満たすダミーの型で、Write
メソッドが常にパニックを発生させるように実装されています。type panicWriter struct{} func (panicWriter) Write([]byte) (int, error) { panic("cannot write") }
wantPanicString
関数:recover
を使用してパニックを捕捉し、期待されるパニックメッセージと一致するかを検証するヘルパー関数です。func wantPanicString(t *testing.T, want string) { if e := recover(); e != nil { got, ok := e.(string) switch { case !ok: t.Errorf("got %v (%T), want panic string", e, e) case got != want: t.Errorf("wrong panic message: got %q, want %q", got, want) } } }
TestPanicDuringFlush
:panicWriter
をtabwriter.Writer
の出力先として設定し、Flush
メソッドを呼び出した際に、期待されるパニックメッセージ「tabwriter: panic during Flush
」が発生するかを検証します。TestPanicDuringWrite
: 同様にpanicWriter
を設定し、WriteString
を呼び出すことでWrite
メソッドが内部的に呼び出され、期待されるパニックメッセージ「tabwriter: panic during Write
」が発生するかを検証します。特に、io.WriteString(w, "a\\n\\n")
のように、2つ目の\n
がw.Write
の呼び出しをトリガーし、パニックを発生させる点が重要です。
これらのテストケースは、handlePanic
の変更が意図通りに機能し、パニック時に適切なメッセージが生成されることを保証します。
コアとなるコードの変更箇所
src/pkg/text/tabwriter/tabwriter.go
--- a/src/pkg/text/tabwriter/tabwriter.go
+++ b/src/pkg/text/tabwriter/tabwriter.go
@@ -434,9 +434,13 @@ func (b *Writer) terminateCell(htab bool) int {
return len(*line)
}
-func handlePanic(err *error) {
+func handlePanic(err *error, op string) {
if e := recover(); e != nil {
- *err = e.(osError).err // re-panics if it's not a local osError
+ if nerr, ok := e.(osError); ok {
+ *err = nerr.err
+ return
+ }
+ panic("tabwriter: panic during " + op)
}
}
@@ -447,7 +451,7 @@ func handlePanic(err *error) {
//
func (b *Writer) Flush() (err error) {
defer b.reset() // even in the presence of errors
- defer handlePanic(&err)
+ defer handlePanic(&err, "Flush")
// add current cell if not empty
if b.cell.size > 0 {
@@ -471,7 +475,7 @@ var hbar = []byte("---\n")
// while writing to the underlying output stream.
//
func (b *Writer) Write(buf []byte) (n int, err error) {
- defer handlePanic(&err)
+ defer handlePanic(&err, "Write")
// split text into cells
n = 0
src/pkg/text/tabwriter/tabwriter_test.go
--- a/src/pkg/text/tabwriter/tabwriter_test.go
+++ b/src/pkg/text/tabwriter/tabwriter_test.go
@@ -613,3 +613,40 @@ func Test(t *testing.T) {
check(t, e.testname, e.minwidth, e.tabwidth, e.padding, e.padchar, e.flags, e.src, e.expected)
}\n}\n+\n+type panicWriter struct{}\n+\n+func (panicWriter) Write([]byte) (int, error) {\n+\tpanic("cannot write")\n+}\n+\n+func wantPanicString(t *testing.T, want string) {\n+\tif e := recover(); e != nil {\n+\t\tgot, ok := e.(string)\n+\t\tswitch {\n+\t\tcase !ok:\n+\t\t\tt.Errorf("got %v (%T), want panic string", e, e)\n+\t\tcase got != want:\n+\t\t\tt.Errorf("wrong panic message: got %q, want %q", got, want)\n+\t\t}\n+\t}\n+}\n+\n+func TestPanicDuringFlush(t *testing.T) {\n+\tdefer wantPanicString(t, "tabwriter: panic during Flush")\n+\tvar p panicWriter\n+\tw := new(Writer)\n+\tw.Init(p, 0, 0, 5, ' ', 0)\n+\tio.WriteString(w, "a")\n+\tw.Flush()\n+\tt.Errorf("failed to panic during Flush")\n+}\n+\n+func TestPanicDuringWrite(t *testing.T) {\n+\tdefer wantPanicString(t, "tabwriter: panic during Write")\n+\tvar p panicWriter\n+\tw := new(Writer)\n+\tw.Init(p, 0, 0, 5, ' ', 0)\n+\tio.WriteString(w, "a\\n\\n") // the second \\n triggers a call to w.Write and thus a panic\n+\tt.Errorf("failed to panic during Write")\n+}\n```
## コアとなるコードの解説
このコミットの核心は、`handlePanic` 関数のロジックの強化と、それを利用する `Flush` および `Write` メソッドの変更です。
1. **`handlePanic` 関数のシグネチャ変更とロジックの改善**:
* `func handlePanic(err *error)` から `func handlePanic(err *error, op string)` へと変更され、パニックが発生した操作を示す `op` 文字列が追加されました。
* `recover()` で捕捉されたパニック `e` が `osError` 型であるかどうかを `if nerr, ok := e.(osError); ok` で安全にチェックするようになりました。
* もし `e` が `osError` 型であれば、その内部のエラー `nerr.err` を `err` ポインタに代入し、関数を正常終了させます。これは、`tabwriter` 自身が意図的に発生させたエラー(例えば、下層の `io.Writer` からのエラーをラップしたもの)を適切に処理するためのものです。
* もし `e` が `osError` 型でなければ(つまり、`tabwriter` の想定外のパニックである場合)、`panic("tabwriter: panic during " + op)` という新しいパニックを発生させます。この新しいパニックメッセージには、どの操作(`Flush` または `Write`)中にパニックが発生したかが明示的に含まれるため、デバッグ時の情報が格段に向上します。
2. **`Flush` および `Write` メソッドでの `handlePanic` の呼び出し変更**:
* `defer handlePanic(&err)` の呼び出しが、それぞれ `defer handlePanic(&err, "Flush")` と `defer handlePanic(&err, "Write")` に変更されました。これにより、`handlePanic` 関数がパニック発生元の操作を識別できるようになり、より詳細なパニックメッセージの生成が可能になります。
3. **新しいテストケースの追加**:
* `panicWriter` は、`Write` メソッドが常にパニックを発生させることで、`tabwriter` が下層の `io.Writer` からのパニックをどのように処理するかをシミュレートします。
* `wantPanicString` は、`recover` を使ってパニックを捕捉し、そのメッセージが期待通りであるかを検証する汎用的なヘルパー関数です。
* `TestPanicDuringFlush` と `TestPanicDuringWrite` は、それぞれ `Flush` と `Write` の実行中に `panicWriter` によってパニックが発生した場合に、`tabwriter` が生成するパニックメッセージが「`tabwriter: panic during Flush`」および「`tabwriter: panic during Write`」となることを確認します。これにより、変更が正しく機能していることが保証されます。
これらの変更により、`text/tabwriter` パッケージは、内部で発生した予期せぬパニックに対して、より情報量の多いバックトレースを提供するようになり、開発者が問題を迅速に特定し、デバッグするのに役立ちます。
## 関連リンク
* Go言語の `text/tabwriter` パッケージのドキュメント: [https://pkg.go.dev/text/tabwriter](https://pkg.go.dev/text/tabwriter)
* Go言語におけるパニックとリカバリに関する公式ブログ記事 (英語): [https://go.dev/blog/defer-panic-and-recover](https://go.dev/blog/defer-panic-and-recover)
## 参考にした情報源リンク
* Go言語の公式リポジトリ: [https://github.com/golang/go](https://github.com/golang/go)
* Go言語のコードレビューシステム (Gerrit): [https://go.dev/cl/53310044](https://go.dev/cl/53310044) (コミットメッセージに記載されている変更リストのURL)
* Go言語のIssueトラッカー: [https://go.dev/issue/7117](https://go.dev/issue/7117) (ただし、この特定のコミットに関連するIssue 7117の公開情報は、一般的なWeb検索では直接見つかりませんでした。これは内部的なIssue管理番号であるか、または後に別のIssueに統合された可能性があります。)