[インデックス 14858] ファイルの概要
このコミットは、Go言語の標準ライブラリ bytes
パッケージ内の Buffer
型におけるバグ修正に関するものです。具体的には、ReadBytes
メソッドの後に UnreadByte
メソッドが正しく機能しない問題を解決しています。
コミット
commit 53e342f64805e0a2e750585e361d50d163616c9b
Author: Stéphane Travostino <stephane.travostino@gmail.com>
Date: Fri Jan 11 17:02:21 2013 +1100
bytes: fix UnreadByte failure after ReadBytes
Fixes #4583.
R=golang-dev, minux.ma, bradfitz, rsc, dave
CC=golang-dev
https://golang.org/cl/6976050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/53e342f64805e0a2e750585e361d50d163616c9b
元コミット内容
bytes: fix UnreadByte failure after ReadBytes
このコミットは、bytes
パッケージにおいて、ReadBytes
メソッドの呼び出し後に UnreadByte
メソッドが失敗する問題を修正するものです。
変更の背景
Go言語の bytes.Buffer
は、可変長のバイトシーケンスを扱うためのバッファ実装です。io.Reader
や io.Writer
インターフェースを実装しており、バイトデータの読み書きに広く利用されます。
Buffer
には、読み取り操作を元に戻すための UnreadByte()
メソッドが存在します。これは、直前に読み取った1バイトをバッファに戻し、次回の読み取り操作でそのバイトが再度読み取られるようにする機能です。しかし、ReadBytes()
のような特定の読み取りメソッドの後に UnreadByte()
を呼び出すと、期待通りに動作しないというバグが存在していました。
この問題は、ReadBytes
が内部的に readSlice
メソッドを呼び出す際に、バッファの内部状態を適切に更新していなかったことに起因します。UnreadByte
は、直前の操作が読み取りであったかどうか、そしてその読み取りが何バイトであったかを記録する内部フラグ b.lastRead
に依存しています。ReadBytes
が完了した際にこのフラグが正しく設定されていなかったため、UnreadByte
が「直前の操作が読み取りではない」と判断し、エラーを返していました。
コミットメッセージに Fixes #4583
とありますが、このイシューの詳細は公開されていません。しかし、コミット内容とテストケースから、ReadBytes
の後に UnreadByte
が機能しないという具体的な問題が修正されたことが明確に読み取れます。
前提知識の解説
bytes.Buffer
bytes.Buffer
は、Go言語でバイト列を効率的に操作するための構造体です。メモリ上で動的にサイズが変更されるバッファを提供し、io.Reader
および io.Writer
インターフェースを実装しているため、ストリーム処理において非常に便利です。
ReadBytes(delim byte) ([]byte, error)
このメソッドは、バッファから delim
で指定された区切り文字が出現するまで(またはEOFまで)バイトを読み取り、そのバイト列をスライスとして返します。区切り文字自体も返されるスライスに含まれます。読み取られたバイトはバッファから消費されます。
UnreadByte() error
このメソッドは、直前に読み取られた1バイトをバッファに戻します。これにより、次回の読み取り操作でそのバイトが再度読み取られるようになります。このメソッドは、直前の操作が読み取り操作でなかった場合や、バッファが空の場合などにエラーを返します。
b.lastRead
(内部フィールド)
bytes.Buffer
の内部には、直前の読み取り操作の種類を記録するための lastRead
というフィールドが存在します。UnreadByte
メソッドはこの lastRead
の値を見て、直前の操作が Read
系のものであったか、そして何バイト読み取られたか(UnreadByte
の場合は1バイト)を判断します。このフィールドが正しく設定されていないと、UnreadByte
は期待通りに動作しません。
技術的詳細
問題の核心は、ReadBytes
メソッドが内部的に呼び出す readSlice
メソッドが、読み取り操作の完了時に bytes.Buffer
の内部状態を適切に更新していなかった点にあります。
bytes.Buffer
の UnreadByte
メソッドは、直前の操作が読み取り操作であったことを確認するために、内部の lastRead
フィールドを参照します。このフィールドは、Read
や ReadByte
などの読み取りメソッドが呼び出された際に、その操作の種類を示す値(例えば opRead
や opReadByte
)に設定されます。
しかし、ReadBytes
が内部で利用する readSlice
メソッドでは、読み取りが完了した後に b.lastRead
を opRead
に設定する処理が欠落していました。このため、ReadBytes
の直後に UnreadByte
を呼び出すと、UnreadByte
は lastRead
が適切な読み取り操作の値になっていないことを検出し、エラーを返してしまっていたのです。
修正は、readSlice
メソッドの最後に b.lastRead = opRead
という行を追加することで、この内部状態の不整合を解消しています。これにより、ReadBytes
が完了した後に UnreadByte
が呼び出されても、lastRead
が正しく設定されているため、UnreadByte
は正常に機能するようになります。
コアとなるコードの変更箇所
src/pkg/bytes/buffer.go
の readSlice
メソッドに以下の行が追加されました。
--- a/src/pkg/bytes/buffer.go
+++ b/src/pkg/bytes/buffer.go
@@ -377,6 +377,7 @@ func (b *Buffer) readSlice(delim byte) (line []byte, err error) {
}
line = b.buf[b.off:end]
b.off = end
+ b.lastRead = opRead
return line, err
}
また、src/pkg/bytes/buffer_test.go
には、この修正を検証するための新しいテストケース TestUnreadByte
が追加されました。
--- a/src/pkg/bytes/buffer_test.go
+++ b/src/pkg/bytes/buffer_test.go
@@ -453,3 +453,25 @@ func TestReadEmptyAtEOF(t *testing.T) {
t.Errorf("wrong count; got %d want 0", n)
}
}
+
+func TestUnreadByte(t *testing.T) {
+ b := new(Buffer)
+ b.WriteString("abcdefghijklmnopqrstuvwxyz")
+
+ _, err := b.ReadBytes('m')
+ if err != nil {
+ t.Fatalf("ReadBytes: %v", err)
+ }
+
+ err = b.UnreadByte()
+ if err != nil {
+ t.Fatalf("UnreadByte: %v", err)
+ }
+ c, err := b.ReadByte()
+ if err != nil {
+ t.Fatalf("ReadByte: %v", err)
+ }
+ if c != 'm' {
+ t.Errorf("ReadByte = %q; want %q", c, 'm')
+ }
+}
コアとなるコードの解説
readSlice
メソッドの変更
readSlice
メソッドは、ReadBytes
や ReadString
といった高レベルな読み取りメソッドから内部的に呼び出されるヘルパー関数です。この関数は、指定された区切り文字までバッファからバイトを読み取り、そのスライスを返します。
追加された b.lastRead = opRead
の行は、readSlice
が正常に読み取り操作を完了したことを Buffer
の内部状態に明示的に記録します。opRead
は、一般的な読み取り操作が行われたことを示す内部定数です。この設定により、UnreadByte
メソッドが呼び出された際に、直前の操作が有効な読み取りであったと認識され、正しく1バイトをバッファに戻すことができるようになります。
TestUnreadByte
テストケース
この新しいテストケースは、修正が正しく機能することを確認するために追加されました。
new(Buffer)
で新しいBuffer
を作成し、"abcdefghijklmnopqrstuvwxyz"
という文字列を書き込みます。b.ReadBytes('m')
を呼び出し、文字 'm' が出現するまでバッファから読み取ります。この操作により、バッファの読み取り位置は 'm' の直後になります。b.UnreadByte()
を呼び出し、直前に読み取られたバイト(この場合は 'm')をバッファに戻そうとします。修正前はここでエラーが発生していました。b.ReadByte()
を呼び出し、バッファから1バイト読み取ります。UnreadByte
が成功していれば、ここで読み取られるのは 'm' であるはずです。- 最後に、読み取られたバイトが期待通り 'm' であることをアサートします。これにより、
ReadBytes
の後にUnreadByte
が正しく機能し、バッファの読み取り位置が適切に巻き戻されたことが検証されます。
このテストケースは、バグの再現と修正の検証を簡潔かつ効果的に行っています。
関連リンク
- Go言語
bytes
パッケージのドキュメント: https://pkg.go.dev/bytes - Go言語
bytes.Buffer
のドキュメント: https://pkg.go.dev/bytes#Buffer
参考にした情報源リンク
- Go言語の公式リポジトリ (GitHub): https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/6976050 (コミットメッセージに記載されているCLリンク)