[インデックス 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