[インデックス 14942] ファイルの概要
このコミットは、Go言語の標準ライブラリである crypto/cipher パッケージ内の StreamWriter 型におけるエラー処理の不具合を修正するものです。具体的には、StreamWriter の Write メソッドが非ポインタレシーバで定義されていたため、エラーを StreamWriter の Err フィールドに永続化しようとする試みが無意味であった点を修正しています。
コミット
commit 0fb6f5f21b20759a0cdbc25df0c152600fd93b4d
Author: Adam Langley <agl@golang.org>
Date: Mon Jan 21 11:22:08 2013 -0500
crypto/cipher: don't persist errors in StreamWriter.
I messed this up from the beginning. The receiver isn't a pointer so
setting Err is useless. In order to maintain the API, just remove the
superfluous code.
Fixes #4657.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7161043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0fb6f5f21b20759a0cdbc25df0c152600fd93b4d
元コミット内容
このコミットの元々の内容は、crypto/cipher パッケージの StreamWriter において、エラーを Err フィールドに永続化しないようにする修正です。コミットメッセージには「レシーバがポインタではないため、Err を設定しても無意味だった。APIを維持するために、余分なコードを削除する」と明記されています。これは、以前の実装が設計上の誤りを含んでいたことを示しています。
変更の背景
この変更の背景には、Go言語におけるメソッドのレシーバの挙動と、構造体の状態管理に関する理解があります。StreamWriter の Write メソッドは、当初非ポインタレシーバ (func (w StreamWriter) Write(...)) として定義されていました。Goにおいて、非ポインタレシーバを持つメソッド内でレシーバのフィールドを変更しても、その変更はメソッドの呼び出し元には反映されません。これは、メソッドがレシーバの値のコピーを受け取るためです。
StreamWriter は、暗号化ストリーム (Stream) と出力ライター (io.Writer) をラップし、暗号化されたデータを書き込むための構造体です。この構造体には Err というフィールドがあり、書き込み中に発生したエラーを保持することを意図していました。しかし、Write メソッドが非ポインタレシーバであったため、w.Err = err のように Err フィールドにエラーを代入しても、それはメソッド内のローカルな w のコピーに対して行われるだけであり、呼び出し元の StreamWriter インスタンスの Err フィールドは更新されませんでした。
この問題は、Go issue #4657 として報告されており、コミットメッセージの Fixes #4657. がそれを示しています。この不具合により、StreamWriter の Err フィールドは常にゼロ値(nil)のままであり、エラー状態を正しく伝達できないという問題がありました。
前提知識の解説
Go言語のメソッドとレシーバ
Go言語では、関数を型に関連付ける「メソッド」を定義できます。メソッドは、そのメソッドが操作する値(レシーバ)を指定します。レシーバには2つの種類があります。
-
値レシーバ (Value Receiver):
func (t MyType) MethodName(...)のように定義されます。メソッドが呼び出されると、レシーバの値のコピーがメソッドに渡されます。したがって、メソッド内でレシーバのフィールドを変更しても、元の値には影響しません。これは、プリミティブ型や不変なデータ構造を扱う場合に適しています。 -
ポインタレシーバ (Pointer Receiver):
func (t *MyType) MethodName(...)のように定義されます。メソッドが呼び出されると、レシーバの**アドレス(ポインタ)**がメソッドに渡されます。これにより、メソッド内でレシーバのフィールドを変更すると、元の値が直接変更されます。これは、構造体の状態を変更する場合や、大きな構造体のコピーを避ける場合に適しています。
今回のケースでは、StreamWriter の Write メソッドが値レシーバ (func (w StreamWriter) Write(...)) であったため、w.Err = err の代入は w のコピーに対して行われ、元の StreamWriter インスタンスの Err フィールドは更新されませんでした。
crypto/cipher パッケージ
crypto/cipher パッケージは、Go言語の標準ライブラリの一部であり、ストリーム暗号やブロック暗号のモード(CBC, CTR, GCMなど)を実装するためのインターフェースとユーティリティを提供します。このパッケージは、具体的な暗号アルゴリズム(AES, DESなど)の実装ではなく、それらのアルゴリズムをどのようにデータストリームに適用するかという「モード」の概念を扱います。
cipher.Streamインターフェース: データをXOR演算によって暗号化/復号化するストリーム暗号のインターフェースを定義します。XORKeyStream(dst, src []byte)メソッドを持ち、srcのバイト列を鍵ストリームとXORし、結果をdstに書き込みます。StreamWriter構造体:Streamインターフェースとio.Writerインターフェースを組み合わせて、暗号化されたデータを透過的に書き込むためのヘルパー構造体です。ユーザーは平文をStreamWriterに書き込み、StreamWriterは内部でStreamを使って暗号化し、その結果をラップされたio.Writerに書き込みます。
io.Writer インターフェースと io.ErrShortWrite
io.Writerインターフェース: Go言語でデータを書き込むための基本的なインターフェースです。Write(p []byte) (n int, err error)メソッドを定義しており、pのバイト列を書き込み、書き込まれたバイト数nとエラーerrを返します。io.ErrShortWrite:ioパッケージで定義されているエラー定数です。io.WriterのWriteメソッドが、渡されたバイト列のすべてを書き込めなかった場合に返されることがあります。これは、部分的な書き込みが発生したことを示します。
技術的詳細
StreamWriter の Write メソッドは、以下の処理を行っていました。
- 入力
srcをw.S.XORKeyStreamで暗号化し、一時的なバッファcに格納します。 - 暗号化されたデータ
cを内部のw.W(ラップされたio.Writer) に書き込みます。 - 書き込み結果の
n(書き込まれたバイト数) とerr(エラー) を受け取ります。 - もし
nがlen(src)(元の平文の長さ) と異なり、かつエラーがnilであった場合、io.ErrShortWriteをエラーとして設定します。これは、暗号化されたデータがすべて書き込まれなかった場合に、その事実をエラーとして明示するためのロジックです。 - 問題の箇所: 最後に、
w.Err = errという行で、発生したエラーをStreamWriter自身のErrフィールドに保存しようとしていました。しかし、前述の通りWriteメソッドは値レシーバであったため、この代入はwのローカルコピーに対して行われ、呼び出し元のStreamWriterインスタンスのErrフィールドには影響しませんでした。
このコミットは、この「無意味なエラーの永続化」のロジックを削除することで、コードの誤解を招く部分を取り除いています。StreamWriter の Err フィールド自体は残されていますが、コミットメッセージで // unused とコメントが追加されており、将来的には削除されるか、別の目的で使用される可能性が示唆されています。この修正により、StreamWriter の Write メソッドは、発生したエラーを直接呼び出し元に return するという、Goの標準的なエラーハンドリングパターンに沿うことになります。
コアとなるコードの変更箇所
変更は src/pkg/crypto/cipher/io.go ファイルに集中しています。
--- a/src/pkg/crypto/cipher/io.go
+++ b/src/pkg/crypto/cipher/io.go
@@ -28,13 +28,10 @@ func (r StreamReader) Read(dst []byte) (n int, err error) {
type StreamWriter struct {
S Stream
W io.Writer
- Err error
+ Err error // unused
}
func (w StreamWriter) Write(src []byte) (n int, err error) {
-\tif w.Err != nil {\n-\t\treturn 0, w.Err\n-\t}\n \tc := make([]byte, len(src))\n \tw.S.XORKeyStream(c, src)\n \tn, err = w.W.Write(c)\n@@ -42,7 +39,6 @@ func (w StreamWriter) Write(src []byte) (n int, err error) {
\t\tif err == nil { // should never happen
\t\t\terr = io.ErrShortWrite
\t\t}\n-\t\tw.Err = err\n \t}\n \treturn\n }\n```
## コアとなるコードの解説
このコミットでは、`StreamWriter` 構造体と、その `Write` メソッドから以下のコードが削除されました。
1. **`StreamWriter` 構造体定義の変更**:
```diff
- Err error
+ Err error // unused
```
`Err` フィールド自体は残されていますが、`// unused` というコメントが追加され、このフィールドが現在の実装では使用されていないことが明示されました。これは、将来的なリファクタリングや削除の準備とも考えられます。
2. **`Write` メソッド内のエラーチェックと永続化の削除**:
```diff
- func (w StreamWriter) Write(src []byte) (n int, err error) {
- if w.Err != nil {
- return 0, w.Err
- }
// ... (既存の暗号化と書き込みロジック)
- if err == nil { // should never happen
- err = io.ErrShortWrite
- }
- w.Err = err
// ... (return)
```
- `if w.Err != nil { return 0, w.Err }` の削除: これは、以前に `w.Err` に保存されたエラーがあれば、それを使ってすぐにリターンするというロジックでした。しかし、`w.Err` が値レシーバのために常に `nil` であったため、このチェックは常に `false` となり、実行されることはありませんでした。したがって、このコードは冗長であり、削除されました。
- `w.Err = err` の削除: これが最も重要な変更点です。`Write` メソッド内で発生したエラーを `w.Err` フィールドに代入しようとする行が削除されました。これにより、値レシーバの制約によって無効であったエラーの永続化の試みが完全に排除されました。
この修正により、`StreamWriter.Write` メソッドは、内部の `io.Writer` から返されたエラー、または `io.ErrShortWrite` を、直接呼び出し元に `return` するようになりました。これはGoのエラーハンドリングの慣習に沿ったものであり、呼び出し元は `Write` メソッドの戻り値としてエラーを直接受け取り、適切に処理することができます。
## 関連リンク
- Go issue #4657: [https://github.com/golang/go/issues/4657](https://github.com/golang/go/issues/4657)
- Go CL 7161043: [https://golang.org/cl/7161043](https://golang.org/cl/7161043) (Gerrit Code Review)
## 参考にした情報源リンク
- Go言語公式ドキュメント: [https://go.dev/doc/](https://go.dev/doc/)
- A Tour of Go - Methods: [https://go.dev/tour/methods/1](https://go.dev/tour/methods/1)
- A Tour of Go - Methods with pointer receivers: [https://go.dev/tour/methods/8](https://go.dev/tour/methods/8)
- `io` package documentation: [https://pkg.go.dev/io](https://pkg.go.dev/io)
- `crypto/cipher` package documentation: [https://pkg.go.dev/crypto/cipher](https://pkg.go.dev/crypto/cipher)
- Go issue tracker: [https://github.com/golang/go/issues](https://github.com/golang/go/issues)