[インデックス 14598] ファイルの概要
このコミットは、Go言語の標準ライブラリである bufio
パッケージにおけるいくつかのマイナーな修正と改善を目的としています。具体的には、bufio.Reader
の Read
メソッドが負の値を返した場合の診断機能の追加と、ReadLine
メソッドの振る舞いに関するドキュメントの明確化が行われています。
コミット
commit f0d9ccb8da4c2f0b8f900c970073eb4c980dcc38
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 10 17:25:31 2012 -0500
bufio: minor fixes
* note end-of-line and EOF behavior for ReadLine
* diagnose broken Readers
Fixes #3825.
Fixes #4276.
R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/6907060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f0d9ccb8da4c2f0b8f900c970073eb4c980dcc38
元コミット内容
bufio: minor fixes
* note end-of-line and EOF behavior for ReadLine
* diagnose broken Readers
Fixes #3825.
Fixes #4276.
変更の背景
このコミットは、bufio
パッケージの堅牢性と使いやすさを向上させるために行われました。主な背景は以下の2点です。
io.Reader
の不正な実装への対応: Go言語のio.Reader
インターフェースのRead
メソッドは、読み込んだバイト数を示すint
とエラーを返すことが期待されています。仕様上、読み込んだバイト数は負の値であってはなりません。しかし、不正な実装のio.Reader
が負の値を返す可能性があり、その場合にbufio.Reader
が予期せぬ動作をする可能性がありました。このコミットは、このような不正なReader
を早期に診断し、パニックを発生させることで、問題の特定を容易にすることを目的としています。これは、Fixes #4276
に関連していると考えられます。ReadLine
メソッドの振る舞いの明確化:bufio.Reader
のReadLine
メソッドは、行の読み込みに特化した便利なメソッドですが、その振る舞い、特に改行コードの扱いとファイルの終端 (EOF) での挙動について、ドキュメントが不明瞭な点がありました。このコミットは、ReadLine
が返却するテキストに改行コードが含まれないこと、および入力が最終的な改行コードなしで終了した場合の挙動について、ドキュメントに明記することで、ユーザーの誤解を防ぎ、APIの使いやすさを向上させることを目的としています。これは、Fixes #3825
に関連していると考えられます。
これらの修正は、Go言語の標準ライブラリの品質と信頼性を維持するための継続的な取り組みの一環です。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と bufio
パッケージに関する知識が必要です。
-
io.Reader
インターフェース:- Go言語におけるデータの読み込み操作を抽象化する基本的なインターフェースです。
Read(p []byte) (n int, err error)
という単一のメソッドを持ちます。n
はp
に読み込まれたバイト数を示し、0 <= n <= len(p)
である必要があります。負の値を返すことは仕様違反です。err
は読み込み中に発生したエラーを示します。ファイルの終端に達した場合はio.EOF
が返されます。- ファイル、ネットワーク接続、メモリ上のバッファなど、様々なデータソースがこのインターフェースを実装します。
-
bufio
パッケージ:bufio
パッケージは、io.Reader
およびio.Writer
インターフェースをラップし、バッファリング機能を提供することで、I/O操作の効率を向上させます。- ディスクI/OやネットワークI/Oはコストが高い操作であるため、少量のデータを頻繁に読み書きするよりも、ある程度のデータをまとめてバッファに読み込み/書き出すことで、システムコールやディスクアクセス回数を減らし、パフォーマンスを向上させます。
bufio.Reader
は、内部にバッファを持ち、基となるio.Reader
からデータをまとめて読み込み、アプリケーションからの要求に応じてバッファからデータを供給します。バッファが空になると、再度基となるio.Reader
からデータを読み込みます。
-
bufio.Reader.ReadLine()
メソッド:- このメソッドは、バッファリングされた入力から単一の論理行を読み込みます。
- 行は改行文字 (
\n
) またはキャリッジリターンと改行の組み合わせ (\r\n
) で区切られます。 - 返される行には、末尾の改行文字は含まれません。
- 行がバッファに収まらない場合、
isPrefix
がtrue
となり、行のプレフィックスが返されます。この場合、残りの行を読み込むためにReadLine
を再度呼び出す必要があります。 - エラーが発生した場合、またはファイルの終端に達した場合は、エラーが返されます。
-
パニック (Panic) とリカバリ (Recover):
- Go言語におけるパニックは、プログラムの通常の実行フローを中断させるランタイムエラーの一種です。通常、回復不可能なエラーやプログラマの論理的誤りを示すために使用されます。
- パニックが発生すると、現在のゴルーチンの実行が停止し、遅延関数 (deferred functions) が実行され、コールスタックを遡っていきます。
recover()
関数は、パニックが発生したゴルーチン内で遅延関数から呼び出された場合にのみ、パニックを捕捉し、パニック値を返すことができます。これにより、パニックから回復し、プログラムの実行を継続することが可能になります。- このコミットでは、不正な
io.Reader
が負の値を返した場合にパニックを発生させ、テストコードでそのパニックを捕捉して検証しています。
技術的詳細
このコミットにおける技術的な変更点は大きく分けて2つあります。
1. io.Reader
が負の値を返した場合の診断
src/pkg/bufio/bufio.go
の Reader.fill()
メソッドに修正が加えられました。
-
errNegativeRead
の導入:var errNegativeRead = errors.New("bufio: reader returned negative count from Read")
という新しいエラー変数が定義されました。これは、io.Reader
のRead
メソッドが不正な負の値を返した場合に発生するパニックのメッセージとして使用されます。 -
負の読み込みバイト数に対するパニック:
fill()
メソッド内で、基となるb.rd.Read(b.buf[b.w:])
の呼び出し結果n
(読み込んだバイト数) が負の値であるかどうかをチェックするif n < 0 { panic(errNegativeRead) }
という行が追加されました。io.Reader
のRead
メソッドは、読み込んだバイト数として負の値を返すことは仕様上許されていません。もし負の値を返した場合、それはio.Reader
の実装が壊れていることを意味します。このような不正な状態を早期に検出し、パニックを発生させることで、デバッグを容易にし、後続の予期せぬ動作を防ぎます。
2. ReadLine
メソッドのドキュメントの明確化
src/pkg/bufio/bufio.go
の Reader.ReadLine()
メソッドのコメントに以下の2行が追加されました。
// The text returned from ReadLine does not include the line end ("\r\n" or "\n").
// No indication or error is given if the input ends without a final line end.
これらのコメントは、ReadLine
メソッドの振る舞いに関する重要な詳細を明確にしています。
- 改行コードの非包含:
ReadLine
が返すバイトスライスには、行末の改行コード (\r\n
または\n
) が含まれないことが明示されました。これは、多くのテキスト処理において行の内容のみが必要とされるため、非常に重要な情報です。 - 最終的な改行コードなしのEOF: 入力が最終的な改行コードなしで終了した場合(例: ファイルの最後の行が改行で終わっていない場合)、
ReadLine
はその行を正常に返し、エラーや特別な通知は行われないことが明示されました。これにより、ユーザーはEOF処理をより正確に実装できます。
3. TestNegativeRead
の追加
src/pkg/bufio/bufio_test.go
に TestNegativeRead
という新しいテストケースが追加されました。
-
negativeReader
型の定義:type negativeReader int
とfunc (r *negativeReader) Read([]byte) (int, error) { return -1, nil }
という構造体が定義されました。これは、io.Reader
インターフェースを実装し、Read
メソッドが常に-1
(不正な負の値) を返すように意図的に壊れたReader
をシミュレートするためのものです。 -
パニックの検証:
TestNegativeRead
関数内では、このnegativeReader
を使用してbufio.Reader
を作成し、b.Read(make([]byte, 100))
を呼び出しています。defer
ステートメント内でrecover()
を使用してパニックを捕捉し、パニックが発生したこと、そしてパニックメッセージがerrNegativeRead
で定義された文字列 ("reader returned negative count from Read"
) を含んでいることを検証しています。これにより、bufio.Reader
が不正なio.Reader
を正しく診断し、期待されるパニックを発生させることを保証しています。
これらの変更は、bufio
パッケージの堅牢性を高め、APIのドキュメントを改善することで、開発者がより安全かつ正確に bufio
を利用できるようにすることを目的としています。
コアとなるコードの変更箇所
src/pkg/bufio/bufio.go
@@ -64,6 +64,8 @@ func NewReader(rd io.Reader) *Reader {
return NewReaderSize(rd, defaultBufSize)
}
+var errNegativeRead = errors.New("bufio: reader returned negative count from Read")
+
// fill reads a new chunk into the buffer.
func (b *Reader) fill() {
// Slide existing data to beginning.
@@ -75,6 +77,9 @@ func (b *Reader) fill() {
// Read new data.
n, e := b.rd.Read(b.buf[b.w:])
+ if n < 0 {
+ panic(errNegativeRead)
+ }
b.w += n
if e != nil {
b.err = e
@@ -282,6 +287,9 @@ func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {
// of the line. The returned buffer is only valid until the next call to
// ReadLine. ReadLine either returns a non-nil line or it returns an error,
// never both.
+//
+// The text returned from ReadLine does not include the line end ("\r\n" or "\n").
+// No indication or error is given if the input ends without a final line end.
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {
line, err = b.ReadSlice('\n')
if err == ErrBufferFull {
src/pkg/bufio/bufio_test.go
@@ -939,6 +939,29 @@ func (w *writeCountingDiscard) Write(p []byte) (int, error) {
return len(p), nil
}
+type negativeReader int
+
+func (r *negativeReader) Read([]byte) (int, error) { return -1, nil }
+
+func TestNegativeRead(t *testing.T) {
+ // should panic with a description pointing at the reader, not at itself.
+ // (should NOT panic with slice index error, for example.)
+ b := NewReader(new(negativeReader))
+ defer func() {
+ switch err := recover().(type) {
+ case nil:
+ t.Fatal("read did not panic")
+ case error:
+ if !strings.Contains(err.Error(), "reader returned negative count from Read") {
+ t.Fatal("wrong panic: %v", err)
+ }
+ default:
+ t.Fatalf("unexpected panic value: %T(%v)", err, err)
+ }
+ }()
+ b.Read(make([]byte, 100))
+}
+
// An onlyReader only implements io.Reader, no matter what other methods the underlying implementation may have.
type onlyReader struct {
r io.Reader
コアとなるコードの解説
src/pkg/bufio/bufio.go
の変更点
-
errNegativeRead
変数の追加:var errNegativeRead = errors.New("bufio: reader returned negative count from Read")
この行は、io.Reader
がRead
メソッドから負のバイト数を返した場合に発生させるパニックのエラーメッセージを定義しています。これにより、問題の原因がbufio
パッケージ自体ではなく、基となるio.Reader
の不正な実装にあることを明確に示します。 -
Reader.fill()
メソッド内のパニックロジック:n, e := b.rd.Read(b.buf[b.w:]) if n < 0 { panic(errNegativeRead) }
fill()
メソッドは、bufio.Reader
の内部バッファが空になったときに、基となるio.Reader
からデータを読み込む役割を担います。ここで、b.rd.Read
の戻り値n
(読み込んだバイト数) が負であるかをチェックしています。もし負であれば、それはio.Reader
の契約違反であるため、errNegativeRead
を伴うパニックを発生させます。これにより、不正なReader
の使用が早期に検出され、プログラムの不安定化を防ぎます。 -
Reader.ReadLine()
メソッドのコメント追加:// The text returned from ReadLine does not include the line end ("\r\n" or "\n"). // No indication or error is given if the input ends without a final line end.
これらのコメントは、
ReadLine
メソッドのAPIドキュメントを改善し、その振る舞いをより明確にしています。- 最初の行は、
ReadLine
が返すバイトスライスには、行末の改行文字(\r\n
または\n
)が含まれないことを明示しています。これは、行の内容のみを処理したい場合に便利ですが、改行文字が必要な場合は別途追加する必要があることを示唆しています。 - 2番目の行は、入力ストリームが最終的な改行文字なしで終了した場合(例: ファイルの最後の行が改行で終わっていない場合)でも、
ReadLine
はエラーを返さずにその行を正常に返すことを説明しています。これにより、開発者はEOF処理をより正確に設計できます。
- 最初の行は、
src/pkg/bufio/bufio_test.go
の変更点
-
negativeReader
型の定義:type negativeReader int func (r *negativeReader) Read([]byte) (int, error) { return -1, nil }
このコードは、
io.Reader
インターフェースを実装するカスタム型negativeReader
を定義しています。この型のRead
メソッドは、常にn = -1
とerr = nil
を返します。これは、io.Reader
の仕様に違反する不正な実装を意図的にシミュレートするためのものです。 -
TestNegativeRead
テスト関数の追加:func TestNegativeRead(t *testing.T) { // ... b := NewReader(new(negativeReader)) defer func() { switch err := recover().(type) { case nil: t.Fatal("read did not panic") case error: if !strings.Contains(err.Error(), "reader returned negative count from Read") { t.Fatal("wrong panic: %v", err) } default: t.Fatalf("unexpected panic value: %T(%v)", err, err) } }() b.Read(make([]byte, 100)) }
このテスト関数は、
bufio.Reader
が不正なio.Reader
を検出したときに正しくパニックを発生させることを検証します。NewReader(new(negativeReader))
で、不正なnegativeReader
を基にしたbufio.Reader
を作成します。defer
ステートメント内の匿名関数は、b.Read
の呼び出し中に発生する可能性のあるパニックを捕捉するために使用されます。recover()
を呼び出すことで、パニックが発生したかどうか、そしてそのパニック値が期待されるエラーメッセージ ("reader returned negative count from Read"
) を含んでいるかどうかをチェックします。- これにより、
bufio.Reader
がio.Reader
の不正な実装に対して堅牢であり、問題を早期に診断できることが保証されます。
これらの変更は、bufio
パッケージの信頼性を向上させ、開発者がより安全にI/O操作を扱えるようにするための重要な改善です。
関連リンク
- Go CL 6907060: https://golang.org/cl/6907060
参考にした情報源リンク
- Go言語
bufio
パッケージ公式ドキュメント: https://pkg.go.dev/bufio - Go言語
io
パッケージ公式ドキュメント: https://pkg.go.dev/io - Go言語
errors
パッケージ公式ドキュメント: https://pkg.go.dev/errors - Go言語におけるパニックとリカバリに関するドキュメント (Go言語の公式ブログやEffective Goなど):
- A Tour of Go - Panics: https://go.dev/tour/concurrency/12
- Effective Go - Panics: https://go.dev/doc/effective_go#panics