[インデックス 16159] ファイルの概要
このコミットは、Go言語の標準ライブラリbufio
パッケージ内のScanner
型におけるEOF(End Of File)時のエラーハンドリングの不具合を修正するものです。具体的には、Scanner.Scan()
メソッドが入力の終端に達した際、またはリーダーが0バイトを返した場合の挙動を改善し、予期せぬエラー(io.ErrUnexpectedEOF
など)が正しく伝播されるようにしています。
コミット
commit 082a4a8a47d03c5fd246b4d079391bdb21f2c3ed
Author: Rob Pike <r@golang.org>
Date: Wed Apr 10 20:58:19 2013 -0700
bufio/Scan: fix error handling at EOF
Fixes #5268.
R=golang-dev, dsymonds, bradfitz
CC=golang-dev
https://golang.org/cl/8646045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/082a4a8a47d03c5fd246b4d079391bdb21f2c3ed
元コミット内容
このコミットは、bufio/Scan
パッケージにおけるEOF時のエラーハンドリングを修正します。具体的には、Scanner.Scan()
メソッドが入力の終端に達した際、またはリーダーが0バイトを返した場合に、io.EOF
以外のエラーがScanner.Err()
によって正しく返されるように変更されています。これにより、Issue 5268で報告された問題が解決されます。
変更の背景
Go言語のbufio
パッケージは、I/O操作をバッファリングすることで効率を高めるための機能を提供します。Scanner
型は、入力ストリームをトークン(単語、行など)に分割するための便利なインターフェースを提供します。
このコミットが行われる前、Scanner.Scan()
メソッドには、入力リーダーが0バイトを返した場合の特定のエラーハンドリングに問題がありました。通常、リーダーが0バイトを返し、かつエラーも返さない場合、それはEOF(End Of File)を示します。しかし、リーダーが0バイトを返すと同時にio.EOF
以外のエラー(例えば、io.ErrUnexpectedEOF
など)を返した場合、Scanner
は内部的にそのエラーをio.EOF
で上書きしてしまう可能性がありました。
これにより、Scanner.Err()
メソッドを呼び出した際に、実際に発生したエラーではなく、常にio.EOF
(またはnil
)が返されてしまい、呼び出し元が真のエラー原因を特定できないという問題が発生していました。Issue 5268は、この挙動が予期せぬエラーを隠蔽し、デバッグを困難にすることを指摘していました。
この修正の目的は、Scanner
がリーダーから受け取った実際のエラーを正確に保持し、Scan()
がfalse
を返した後にErr()
メソッドを通じてそのエラーを呼び出し元に適切に報告することです。
前提知識の解説
bufio
パッケージ: Go言語の標準ライブラリの一部で、バッファリングされたI/Oを提供します。これにより、ディスクやネットワークからの読み書きの効率が向上します。bufio.Scanner
:bufio
パッケージ内で提供される型で、io.Reader
からデータを読み込み、それをトークン(行、単語、カスタム区切り文字など)に分割するための高レベルなインターフェースを提供します。Scan()
メソッド:Scanner
を次のトークンに進めます。トークンが見つかればtrue
を返し、入力の終端に達したかエラーが発生した場合はfalse
を返します。Bytes()
/Text()
メソッド:Scan()
がtrue
を返した後に、現在のトークンをバイトスライスまたは文字列として返します。Err()
メソッド:Scan()
がfalse
を返した後に、スキャン中に発生したエラーを返します。io.EOF
の場合はnil
を返します。
io.Reader
インターフェース: Go言語における基本的な読み込みインターフェースです。Read(p []byte) (n int, err error)
メソッドを持ち、データをp
に読み込み、読み込んだバイト数n
とエラーerr
を返します。io.EOF
:io.Reader
のRead
メソッドが、それ以上読み込むデータがないことを示すために返す特別なエラー値です。通常、Read
はn > 0, io.EOF
またはn == 0, io.EOF
を返します。io.ErrUnexpectedEOF
:io
パッケージで定義されているエラーの一つで、予期せぬEOFが発生した場合に返されることがあります。例えば、期待されるバイト数に満たない状態でストリームが終了した場合などです。- エラーハンドリング: Go言語では、関数がエラーを返す場合、通常は戻り値の最後の要素として
error
型の値を返します。呼び出し元は、このエラー値をチェックして適切な処理を行います。
技術的詳細
このコミットの核心は、bufio.Scanner
のScan()
メソッド内のエラー処理ロジックの変更にあります。
Scanner.Scan()
メソッドは、内部的にs.readAhead()
を呼び出して、次のトークンを読み込むためのデータをバッファに確保しようとします。s.readAhead()
は、基となるio.Reader
からデータを読み込みます。
変更前のコードでは、s.readAhead()
がn == 0
(読み込んだバイト数が0)かつerr != nil
の場合、特にerr
がio.EOF
でない場合に問題がありました。元のコードは以下のようになっていました。
// src/pkg/bufio/scan.go (変更前)
if n == 0 { // Don't loop forever if Reader doesn't deliver EOF.
s.err = io.EOF // ここでエラーがio.EOFに上書きされる可能性があった
}
このロジックは、「リーダーがEOFを返さない場合に無限ループに陥るのを防ぐ」という意図がありましたが、副作用として、リーダーが0バイトを返しながらio.EOF
以外の実際のエラー(例: io.ErrUnexpectedEOF
)を返した場合でも、Scanner
の内部エラー状態s.err
が強制的にio.EOF
に設定されてしまうという問題がありました。これにより、Scanner.Err()
を呼び出しても、真のエラーが隠蔽されてしまうことになります。
修正後のコードでは、このs.err = io.EOF
の直接代入がs.setErr(io.EOF)
に置き換えられました。
// src/pkg/bufio/scan.go (変更後)
if n == 0 { // Don't loop forever if Reader doesn't deliver EOF.
s.setErr(io.EOF) // setErrメソッドを通じてエラーが設定される
}
s.setErr()
メソッドは、Scanner
の内部エラー状態を設定するためのヘルパーメソッドです。このメソッドの内部ロジックは、既にエラーが設定されている場合は新しいエラーで上書きしない、という賢い挙動をします。
// setErr records the first error encountered.
func (s *Scanner) setErr(err error) {
if s.err == nil || s.err == io.EOF { // 既存のエラーがないか、io.EOFの場合のみ設定
s.err = err
}
}
この変更により、s.readAhead()
がio.EOF
以外のエラーを返した場合(例えば、io.ErrUnexpectedEOF
)、そのエラーはs.setErr(err)
によって一度s.err
に設定されます。その後、n == 0
の条件が満たされたとしても、s.setErr(io.EOF)
が呼び出された際に、既にs.err
にio.EOF
以外のエラーが設定されているため、そのエラーがio.EOF
で上書きされることはなくなります。結果として、Scanner.Err()
は実際に発生したエラーを正確に報告できるようになります。
また、Scanner.Scan()
メソッドのドキュメントも更新され、Scan
がfalse
を返す条件に「a zero-length read from the input」(入力からのゼロ長読み込み)が追加され、より正確な説明が提供されています。
テストケースTestNonEOFWithEmptyRead
が追加され、この修正が正しく機能することを確認しています。このテストでは、常に0, io.ErrUnexpectedEOF
を返すカスタムio.Reader
を実装し、Scanner
がio.ErrUnexpectedEOF
を正しく報告するかどうかを検証しています。
コアとなるコードの変更箇所
src/pkg/bufio/scan.go
--- a/src/pkg/bufio/scan.go
+++ b/src/pkg/bufio/scan.go
@@ -103,7 +103,8 @@ func (s *Scanner) Text() string {
// Scan advances the Scanner to the next token, which will then be
// available through the Bytes or Text method. It returns false when the
-// scan stops, either by reaching the end of the input or an error.
+// scan stops, either by reaching the end of the input, a zero-length
+// read from the input, or an error.
// After Scan returns false, the Err method will return any error that
// occurred during scanning, except that if it was io.EOF, Err
// will return nil.
@@ -164,7 +165,7 @@ func (s *Scanner) Scan() bool {
s.setErr(err)
}
if n == 0 { // Don't loop forever if Reader doesn't deliver EOF.
- s.err = io.EOF
+ s.setErr(io.EOF)
}
s.end += n
}
src/pkg/bufio/scan_test.go
--- a/src/pkg/bufio/scan_test.go
+++ b/src/pkg/bufio/scan_test.go
@@ -368,3 +368,21 @@ func TestErrAtEOF(t *testing.T) {
t.Fatal("wrong error:", s.Err())
}
}
+
+// Test for issue 5268.
+type alwaysError struct{}
+
+func (alwaysError) Read(p []byte) (int, error) {
+ return 0, io.ErrUnexpectedEOF
+}
+
+func TestNonEOFWithEmptyRead(t *testing.T) {
+ scanner := NewScanner(alwaysError{})
+ for scanner.Scan() {
+ t.Fatal("read should fail")
+ }
+ err := scanner.Err()
+ if err != io.ErrUnexpectedEOF {
+ t.Errorf("unexpected error: %v", err)
+ }
+}
コアとなるコードの解説
src/pkg/bufio/scan.go
の変更
-
Scan()
メソッドのドキュメント更新:Scan()
メソッドのコメントが更新され、false
を返す条件に「a zero-length read from the input」(入力からのゼロ長読み込み)が追加されました。これは、リーダーが0バイトを返した場合もスキャンが停止する可能性があることを明確にしています。 -
エラー設定ロジックの変更:
Scan()
メソッド内のif n == 0 { ... }
ブロックにおいて、s.err = io.EOF
という直接代入がs.setErr(io.EOF)
に変更されました。 この変更が重要です。s.setErr()
メソッドは、既にエラーが設定されている場合(特にio.EOF
以外のエラーが先に発生している場合)には、そのエラーを上書きしないロジックを持っています。これにより、io.Reader
が0, io.ErrUnexpectedEOF
のような値を返した場合、まずs.setErr(err)
によってio.ErrUnexpectedEOF
がs.err
に設定され、その後のs.setErr(io.EOF)
の呼び出しでは、既にエラーが設定されているためio.EOF
で上書きされることがなくなります。結果として、Scanner.Err()
はio.ErrUnexpectedEOF
を正しく返すようになります。
src/pkg/bufio/scan_test.go
の変更
-
alwaysError
構造体の追加:io.Reader
インターフェースを満たすalwaysError
という新しい型が定義されました。この型は、Read
メソッドが常に0, io.ErrUnexpectedEOF
を返すように実装されています。これは、特定のシナリオ(リーダーが0バイトを返しつつ、io.EOF
以外のエラーを返す場合)をシミュレートするために使用されます。 -
TestNonEOFWithEmptyRead
テスト関数の追加: この新しいテスト関数は、Issue 5268で報告された問題を再現し、修正が正しく機能することを確認します。NewScanner(alwaysError{})
で、常にio.ErrUnexpectedEOF
を返すリーダーを持つScanner
を作成します。for scanner.Scan() { ... }
ループは、Scan()
がfalse
を返すまで実行されます。alwaysError
リーダーは常に0, io.ErrUnexpectedEOF
を返すため、Scan()
はすぐにfalse
を返してループは実行されません。err := scanner.Err()
で、Scanner
が保持しているエラーを取得します。if err != io.ErrUnexpectedEOF { ... }
で、取得したエラーが期待されるio.ErrUnexpectedEOF
であるかどうかを検証します。もし修正が適用されていなければ、このテストは失敗し、err
がnil
またはio.EOF
になってしまうでしょう。
これらの変更により、bufio.Scanner
はより堅牢なエラーハンドリングを提供し、基となるリーダーから返されるエラーを正確に伝播できるようになりました。
関連リンク
- Go言語の
bufio
パッケージドキュメント: https://pkg.go.dev/bufio - Go言語の
io
パッケージドキュメント: https://pkg.go.dev/io - Go言語のIssue 5268:
bufio: Scanner.Err() returns nil for non-EOF errors
(このコミットが修正した問題の報告) - GitHubのコミットページからリンクを辿るか、GoのIssueトラッカーで検索することで詳細を確認できます。
参考にした情報源リンク
- GitHub: golang/go commit 082a4a8a47d03c5fd246b4d079391bdb21f2c3ed
- Go言語の公式ドキュメント (pkg.go.dev)
- Go言語のソースコード (特に
src/pkg/bufio/scan.go
とsrc/pkg/bufio/scan_test.go
) - Go言語のIssueトラッカー (Issue 5268に関する情報)
[インデックス 16159] ファイルの概要
このコミットは、Go言語の標準ライブラリbufio
パッケージ内のScanner
型におけるEOF(End Of File)時のエラーハンドリングの不具合を修正するものです。具体的には、Scanner.Scan()
メソッドが入力の終端に達した際、またはリーダーが0バイトを返した場合の挙動を改善し、予期せぬエラー(io.ErrUnexpectedEOF
など)が正しく伝播されるようにしています。
コミット
commit 082a4a8a47d03c5fd246b4d079391bdb21f2c3ed
Author: Rob Pike <r@golang.org>
Date: Wed Apr 10 20:58:19 2013 -0700
bufio/Scan: fix error handling at EOF
Fixes #5268.
R=golang-dev, dsymonds, bradfitz
CC=golang-dev
https://golang.org/cl/8646045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/082a4a8a47d03c5fd246b4d079391bdb21f2c3ed
元コミット内容
このコミットは、bufio/Scan
パッケージにおけるEOF時のエラーハンドリングを修正します。具体的には、Scanner.Scan()
メソッドが入力の終端に達した際、またはリーダーが0バイトを返した場合に、io.EOF
以外のエラーがScanner.Err()
によって正しく返されるように変更されています。これにより、Issue 5268で報告された問題が解決されます。
変更の背景
Go言語のbufio
パッケージは、I/O操作をバッファリングすることで効率を高めるための機能を提供します。Scanner
型は、入力ストリームをトークン(単語、行など)に分割するための便利なインターフェースを提供します。
このコミットが行われる前、Scanner.Scan()
メソッドには、入力リーダーが0バイトを返した場合の特定のエラーハンドリングに問題がありました。通常、リーダーが0バイトを返し、かつエラーも返さない場合、それはEOF(End Of File)を示します。しかし、リーダーが0バイトを返すと同時にio.EOF
以外のエラー(例えば、io.ErrUnexpectedEOF
など)を返した場合、Scanner
は内部的にそのエラーをio.EOF
で上書きしてしまう可能性がありました。
これにより、Scanner.Err()
メソッドを呼び出した際に、実際に発生したエラーではなく、常にio.EOF
(またはnil
)が返されてしまい、呼び出し元が真のエラー原因を特定できないという問題が発生していました。Issue 5268は、この挙動が予期せぬエラーを隠蔽し、デバッグを困難にすることを指摘していました。
この修正の目的は、Scanner
がリーダーから受け取った実際のエラーを正確に保持し、Scan()
がfalse
を返した後にErr()
メソッドを通じてそのエラーを呼び出し元に適切に報告することです。
前提知識の解説
bufio
パッケージ: Go言語の標準ライブラリの一部で、バッファリングされたI/Oを提供します。これにより、ディスクやネットワークからの読み書きの効率が向上します。bufio.Scanner
:bufio
パッケージ内で提供される型で、io.Reader
からデータを読み込み、それをトークン(行、単語、カスタム区切り文字など)に分割するための高レベルなインターフェースを提供します。Scan()
メソッド:Scanner
を次のトークンに進めます。トークンが見つかればtrue
を返し、入力の終端に達したかエラーが発生した場合はfalse
を返します。Bytes()
/Text()
メソッド:Scan()
がtrue
を返した後に、現在のトークンをバイトスライスまたは文字列として返します。Err()
メソッド:Scan()
がfalse
を返した後に、スキャン中に発生したエラーを返します。io.EOF
の場合はnil
を返します。
io.Reader
インターフェース: Go言語における基本的な読み込みインターフェースです。Read(p []byte) (n int, err error)
メソッドを持ち、データをp
に読み込み、読み込んだバイト数n
とエラーerr
を返します。io.EOF
:io.Reader
のRead
メソッドが、それ以上読み込むデータがないことを示すために返す特別なエラー値です。通常、Read
はn > 0, io.EOF
またはn == 0, io.EOF
を返します。io.ErrUnexpectedEOF
:io
パッケージで定義されているエラーの一つで、予期せぬEOFが発生した場合に返されることがあります。例えば、期待されるバイト数に満たない状態でストリームが終了した場合などです。- エラーハンドリング: Go言語では、関数がエラーを返す場合、通常は戻り値の最後の要素として
error
型の値を返します。呼び出し元は、このエラー値をチェックして適切な処理を行います。
技術的詳細
このコミットの核心は、bufio.Scanner
のScan()
メソッド内のエラー処理ロジックの変更にあります。
Scanner.Scan()
メソッドは、内部的にs.readAhead()
を呼び出して、次のトークンを読み込むためのデータをバッファに確保しようとします。s.readAhead()
は、基となるio.Reader
からデータを読み込みます。
変更前のコードでは、s.readAhead()
がn == 0
(読み込んだバイト数が0)かつerr != nil
の場合、特にerr
がio.EOF
でない場合に問題がありました。元のコードは以下のようになっていました。
// src/pkg/bufio/scan.go (変更前)
if n == 0 { // Don't loop forever if Reader doesn't deliver EOF.
s.err = io.EOF // ここでエラーがio.EOFに上書きされる可能性があった
}
このロジックは、「リーダーがEOFを返さない場合に無限ループに陥るのを防ぐ」という意図がありましたが、副作用として、リーダーが0バイトを返しながらio.EOF
以外の実際のエラー(例: io.ErrUnexpectedEOF
)を返した場合でも、Scanner
の内部エラー状態s.err
が強制的にio.EOF
に設定されてしまうという問題がありました。これにより、Scanner.Err()
を呼び出しても、真のエラーが隠蔽されてしまうことになります。
修正後のコードでは、このs.err = io.EOF
の直接代入がs.setErr(io.EOF)
に置き換えられました。
// src/pkg/bufio/scan.go (変更後)
if n == 0 { // Don't loop forever if Reader doesn't deliver EOF.
s.setErr(io.EOF) // setErrメソッドを通じてエラーが設定される
}
s.setErr()
メソッドは、Scanner
の内部エラー状態を設定するためのヘルパーメソッドです。このメソッドの内部ロジックは、既にエラーが設定されている場合は新しいエラーで上書きしない、という賢い挙動をします。
// setErr records the first error encountered.
func (s *Scanner) setErr(err error) {
if s.err == nil || s.err == io.EOF { // 既存のエラーがないか、io.EOFの場合のみ設定
s.err = err
}
}
この変更により、s.readAhead()
がio.EOF
以外のエラーを返した場合(例えば、io.ErrUnexpectedEOF
)、そのエラーはs.setErr(err)
によって一度s.err
に設定されます。その後、n == 0
の条件が満たされたとしても、s.setErr(io.EOF)
が呼び出された際に、既にs.err
にio.EOF
以外のエラーが設定されているため、そのエラーがio.EOF
で上書きされることはなくなります。結果として、Scanner.Err()
は実際に発生したエラーを正確に報告できるようになります。
また、Scanner.Scan()
メソッドのドキュメントも更新され、Scan
がfalse
を返す条件に「a zero-length read from the input」(入力からのゼロ長読み込み)が追加され、より正確な説明が提供されています。
テストケースTestNonEOFWithEmptyRead
が追加され、この修正が正しく機能することを確認しています。このテストでは、常に0, io.ErrUnexpectedEOF
を返すカスタムio.Reader
を実装し、Scanner
がio.ErrUnexpectedEOF
を正しく報告するかどうかを検証しています。
コアとなるコードの変更箇所
src/pkg/bufio/scan.go
--- a/src/pkg/bufio/scan.go
+++ b/src/pkg/bufio/scan.go
@@ -103,7 +103,8 @@ func (s *Scanner) Text() string {
// Scan advances the Scanner to the next token, which will then be
// available through the Bytes or Text method. It returns false when the
-// scan stops, either by reaching the end of the input or an error.
+// scan stops, either by reaching the end of the input, a zero-length
+// read from the input, or an error.
// After Scan returns false, the Err method will return any error that
// occurred during scanning, except that if it was io.EOF, Err
// will return nil.
@@ -164,7 +165,7 @@ func (s *Scanner) Scan() bool {
s.setErr(err)
}
if n == 0 { // Don't loop forever if Reader doesn't deliver EOF.
- s.err = io.EOF
+ s.setErr(io.EOF)
}
s.end += n
}
src/pkg/bufio/scan_test.go
--- a/src/pkg/bufio/scan_test.go
+++ b/src/pkg/bufio/scan_test.go
@@ -368,3 +368,21 @@ func TestErrAtEOF(t *testing.T) {
t.Fatal("wrong error:", s.Err())
}
}
+
+// Test for issue 5268.
+type alwaysError struct{}
+
+func (alwaysError) Read(p []byte) (int, error) {
+ return 0, io.ErrUnexpectedEOF
+}
+
+func TestNonEOFWithEmptyRead(t *testing.T) {
+ scanner := NewScanner(alwaysError{})
+ for scanner.Scan() {
+ t.Fatal("read should fail")
+ }
+ err := scanner.Err()
+ if err != io.ErrUnexpectedEOF {
+ t.Errorf("unexpected error: %v", err)
+ }
+}
コアとなるコードの解説
src/pkg/bufio/scan.go
の変更
-
Scan()
メソッドのドキュメント更新:Scan()
メソッドのコメントが更新され、false
を返す条件に「a zero-length read from the input」(入力からのゼロ長読み込み)が追加されました。これは、リーダーが0バイトを返した場合もスキャンが停止する可能性があることを明確にしています。 -
エラー設定ロジックの変更:
Scan()
メソッド内のif n == 0 { ... }
ブロックにおいて、s.err = io.EOF
という直接代入がs.setErr(io.EOF)
に変更されました。 この変更が重要です。s.setErr()
メソッドは、既にエラーが設定されている場合(特にio.EOF
以外のエラーが先に発生している場合)には、そのエラーを上書きしないロジックを持っています。これにより、io.Reader
が0, io.ErrUnexpectedEOF
のような値を返した場合、まずs.setErr(err)
によってio.ErrUnexpectedEOF
がs.err
に設定され、その後のs.setErr(io.EOF)
の呼び出しでは、既にエラーが設定されているためio.EOF
で上書きされることがなくなります。結果として、Scanner.Err()
はio.ErrUnexpectedEOF
を正しく返すようになります。
src/pkg/bufio/scan_test.go
の変更
-
alwaysError
構造体の追加:io.Reader
インターフェースを満たすalwaysError
という新しい型が定義されました。この型は、Read
メソッドが常に0, io.ErrUnexpectedEOF
を返すように実装されています。これは、特定のシナリオ(リーダーが0バイトを返しつつ、io.EOF
以外のエラーを返す場合)をシミュレートするために使用されます。 -
TestNonEOFWithEmptyRead
テスト関数の追加: この新しいテスト関数は、Issue 5268で報告された問題を再現し、修正が正しく機能することを確認します。NewScanner(alwaysError{})
で、常にio.ErrUnexpectedEOF
を返すリーダーを持つScanner
を作成します。for scanner.Scan() { ... }
ループは、Scan()
がfalse
を返すまで実行されます。alwaysError
リーダーは常に0, io.ErrUnexpectedEOF
を返すため、Scan()
はすぐにfalse
を返してループは実行されません。err := scanner.Err()
で、Scanner
が保持しているエラーを取得します。if err != io.ErrUnexpectedEOF { ... }
で、取得したエラーが期待されるio.ErrUnexpectedEOF
であるかどうかを検証します。もし修正が適用されていなければ、このテストは失敗し、err
がnil
またはio.EOF
になってしまうでしょう。
これらの変更により、bufio.Scanner
はより堅牢なエラーハンドリングを提供し、基となるリーダーから返されるエラーを正確に伝播できるようになりました。
関連リンク
- Go言語の
bufio
パッケージドキュメント: https://pkg.go.dev/bufio - Go言語の
io
パッケージドキュメント: https://pkg.go.dev/io - Go言語のIssue 5268:
bufio: Scanner.Err() returns nil for non-EOF errors
(このコミットが修正した問題の報告) - GitHubのコミットページからリンクを辿るか、GoのIssueトラッカーで検索することで詳細を確認できます。
参考にした情報源リンク
- GitHub: golang/go commit 082a4a8a47d03c5fd246b4d079391bdb21f2c3ed
- Go言語の公式ドキュメント (pkg.go.dev)
- Go言語のソースコード (特に
src/pkg/bufio/scan.go
とsrc/pkg/bufio/scan_test.go
) - Go言語のIssueトラッカー (Issue 5268に関する情報)