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