Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14598] ファイルの概要

このコミットは、Go言語の標準ライブラリである bufio パッケージにおけるいくつかのマイナーな修正と改善を目的としています。具体的には、bufio.ReaderRead メソッドが負の値を返した場合の診断機能の追加と、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点です。

  1. io.Reader の不正な実装への対応: Go言語の io.Reader インターフェースの Read メソッドは、読み込んだバイト数を示す int とエラーを返すことが期待されています。仕様上、読み込んだバイト数は負の値であってはなりません。しかし、不正な実装の io.Reader が負の値を返す可能性があり、その場合に bufio.Reader が予期せぬ動作をする可能性がありました。このコミットは、このような不正な Reader を早期に診断し、パニックを発生させることで、問題の特定を容易にすることを目的としています。これは、Fixes #4276 に関連していると考えられます。
  2. ReadLine メソッドの振る舞いの明確化: bufio.ReaderReadLine メソッドは、行の読み込みに特化した便利なメソッドですが、その振る舞い、特に改行コードの扱いとファイルの終端 (EOF) での挙動について、ドキュメントが不明瞭な点がありました。このコミットは、ReadLine が返却するテキストに改行コードが含まれないこと、および入力が最終的な改行コードなしで終了した場合の挙動について、ドキュメントに明記することで、ユーザーの誤解を防ぎ、APIの使いやすさを向上させることを目的としています。これは、Fixes #3825 に関連していると考えられます。

これらの修正は、Go言語の標準ライブラリの品質と信頼性を維持するための継続的な取り組みの一環です。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と bufio パッケージに関する知識が必要です。

  1. io.Reader インターフェース:

    • Go言語におけるデータの読み込み操作を抽象化する基本的なインターフェースです。
    • Read(p []byte) (n int, err error) という単一のメソッドを持ちます。
    • np に読み込まれたバイト数を示し、0 <= n <= len(p) である必要があります。負の値を返すことは仕様違反です。
    • err は読み込み中に発生したエラーを示します。ファイルの終端に達した場合は io.EOF が返されます。
    • ファイル、ネットワーク接続、メモリ上のバッファなど、様々なデータソースがこのインターフェースを実装します。
  2. bufio パッケージ:

    • bufio パッケージは、io.Reader および io.Writer インターフェースをラップし、バッファリング機能を提供することで、I/O操作の効率を向上させます。
    • ディスクI/OやネットワークI/Oはコストが高い操作であるため、少量のデータを頻繁に読み書きするよりも、ある程度のデータをまとめてバッファに読み込み/書き出すことで、システムコールやディスクアクセス回数を減らし、パフォーマンスを向上させます。
    • bufio.Reader は、内部にバッファを持ち、基となる io.Reader からデータをまとめて読み込み、アプリケーションからの要求に応じてバッファからデータを供給します。バッファが空になると、再度基となる io.Reader からデータを読み込みます。
  3. bufio.Reader.ReadLine() メソッド:

    • このメソッドは、バッファリングされた入力から単一の論理行を読み込みます。
    • 行は改行文字 (\n) またはキャリッジリターンと改行の組み合わせ (\r\n) で区切られます。
    • 返される行には、末尾の改行文字は含まれません。
    • 行がバッファに収まらない場合、isPrefixtrue となり、行のプレフィックスが返されます。この場合、残りの行を読み込むために ReadLine を再度呼び出す必要があります。
    • エラーが発生した場合、またはファイルの終端に達した場合は、エラーが返されます。
  4. パニック (Panic) とリカバリ (Recover):

    • Go言語におけるパニックは、プログラムの通常の実行フローを中断させるランタイムエラーの一種です。通常、回復不可能なエラーやプログラマの論理的誤りを示すために使用されます。
    • パニックが発生すると、現在のゴルーチンの実行が停止し、遅延関数 (deferred functions) が実行され、コールスタックを遡っていきます。
    • recover() 関数は、パニックが発生したゴルーチン内で遅延関数から呼び出された場合にのみ、パニックを捕捉し、パニック値を返すことができます。これにより、パニックから回復し、プログラムの実行を継続することが可能になります。
    • このコミットでは、不正な io.Reader が負の値を返した場合にパニックを発生させ、テストコードでそのパニックを捕捉して検証しています。

技術的詳細

このコミットにおける技術的な変更点は大きく分けて2つあります。

1. io.Reader が負の値を返した場合の診断

src/pkg/bufio/bufio.goReader.fill() メソッドに修正が加えられました。

  • errNegativeRead の導入: var errNegativeRead = errors.New("bufio: reader returned negative count from Read") という新しいエラー変数が定義されました。これは、io.ReaderRead メソッドが不正な負の値を返した場合に発生するパニックのメッセージとして使用されます。

  • 負の読み込みバイト数に対するパニック: fill() メソッド内で、基となる b.rd.Read(b.buf[b.w:]) の呼び出し結果 n (読み込んだバイト数) が負の値であるかどうかをチェックする if n < 0 { panic(errNegativeRead) } という行が追加されました。 io.ReaderRead メソッドは、読み込んだバイト数として負の値を返すことは仕様上許されていません。もし負の値を返した場合、それは io.Reader の実装が壊れていることを意味します。このような不正な状態を早期に検出し、パニックを発生させることで、デバッグを容易にし、後続の予期せぬ動作を防ぎます。

2. ReadLine メソッドのドキュメントの明確化

src/pkg/bufio/bufio.goReader.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.goTestNegativeRead という新しいテストケースが追加されました。

  • negativeReader 型の定義: type negativeReader intfunc (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 の変更点

  1. errNegativeRead 変数の追加: var errNegativeRead = errors.New("bufio: reader returned negative count from Read") この行は、io.ReaderRead メソッドから負のバイト数を返した場合に発生させるパニックのエラーメッセージを定義しています。これにより、問題の原因が bufio パッケージ自体ではなく、基となる io.Reader の不正な実装にあることを明確に示します。

  2. 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 の使用が早期に検出され、プログラムの不安定化を防ぎます。

  3. 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 の変更点

  1. negativeReader 型の定義:

    type negativeReader int
    func (r *negativeReader) Read([]byte) (int, error) { return -1, nil }
    

    このコードは、io.Reader インターフェースを実装するカスタム型 negativeReader を定義しています。この型の Read メソッドは、常に n = -1err = nil を返します。これは、io.Reader の仕様に違反する不正な実装を意図的にシミュレートするためのものです。

  2. 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.Readerio.Reader の不正な実装に対して堅牢であり、問題を早期に診断できることが保証されます。

これらの変更は、bufio パッケージの信頼性を向上させ、開発者がより安全にI/O操作を扱えるようにするための重要な改善です。

関連リンク

参考にした情報源リンク