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

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

このコミットは、Go言語の標準ライブラリである crypto/cipher パッケージ内の StreamWriter 型におけるエラー処理の不具合を修正するものです。具体的には、StreamWriterWrite メソッドが非ポインタレシーバで定義されていたため、エラーを StreamWriterErr フィールドに永続化しようとする試みが無意味であった点を修正しています。

コミット

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言語におけるメソッドのレシーバの挙動と、構造体の状態管理に関する理解があります。StreamWriterWrite メソッドは、当初非ポインタレシーバ (func (w StreamWriter) Write(...)) として定義されていました。Goにおいて、非ポインタレシーバを持つメソッド内でレシーバのフィールドを変更しても、その変更はメソッドの呼び出し元には反映されません。これは、メソッドがレシーバの値のコピーを受け取るためです。

StreamWriter は、暗号化ストリーム (Stream) と出力ライター (io.Writer) をラップし、暗号化されたデータを書き込むための構造体です。この構造体には Err というフィールドがあり、書き込み中に発生したエラーを保持することを意図していました。しかし、Write メソッドが非ポインタレシーバであったため、w.Err = err のように Err フィールドにエラーを代入しても、それはメソッド内のローカルな w のコピーに対して行われるだけであり、呼び出し元の StreamWriter インスタンスの Err フィールドは更新されませんでした。

この問題は、Go issue #4657 として報告されており、コミットメッセージの Fixes #4657. がそれを示しています。この不具合により、StreamWriterErr フィールドは常にゼロ値(nil)のままであり、エラー状態を正しく伝達できないという問題がありました。

前提知識の解説

Go言語のメソッドとレシーバ

Go言語では、関数を型に関連付ける「メソッド」を定義できます。メソッドは、そのメソッドが操作する値(レシーバ)を指定します。レシーバには2つの種類があります。

  1. 値レシーバ (Value Receiver): func (t MyType) MethodName(...) のように定義されます。メソッドが呼び出されると、レシーバの値のコピーがメソッドに渡されます。したがって、メソッド内でレシーバのフィールドを変更しても、元の値には影響しません。これは、プリミティブ型や不変なデータ構造を扱う場合に適しています。

  2. ポインタレシーバ (Pointer Receiver): func (t *MyType) MethodName(...) のように定義されます。メソッドが呼び出されると、レシーバの**アドレス(ポインタ)**がメソッドに渡されます。これにより、メソッド内でレシーバのフィールドを変更すると、元の値が直接変更されます。これは、構造体の状態を変更する場合や、大きな構造体のコピーを避ける場合に適しています。

今回のケースでは、StreamWriterWrite メソッドが値レシーバ (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.WriterWrite メソッドが、渡されたバイト列のすべてを書き込めなかった場合に返されることがあります。これは、部分的な書き込みが発生したことを示します。

技術的詳細

StreamWriterWrite メソッドは、以下の処理を行っていました。

  1. 入力 srcw.S.XORKeyStream で暗号化し、一時的なバッファ c に格納します。
  2. 暗号化されたデータ c を内部の w.W (ラップされた io.Writer) に書き込みます。
  3. 書き込み結果の n (書き込まれたバイト数) と err (エラー) を受け取ります。
  4. もし nlen(src) (元の平文の長さ) と異なり、かつエラーが nil であった場合、io.ErrShortWrite をエラーとして設定します。これは、暗号化されたデータがすべて書き込まれなかった場合に、その事実をエラーとして明示するためのロジックです。
  5. 問題の箇所: 最後に、w.Err = err という行で、発生したエラーを StreamWriter 自身の Err フィールドに保存しようとしていました。しかし、前述の通り Write メソッドは値レシーバであったため、この代入は w のローカルコピーに対して行われ、呼び出し元の StreamWriter インスタンスの Err フィールドには影響しませんでした。

このコミットは、この「無意味なエラーの永続化」のロジックを削除することで、コードの誤解を招く部分を取り除いています。StreamWriterErr フィールド自体は残されていますが、コミットメッセージで // unused とコメントが追加されており、将来的には削除されるか、別の目的で使用される可能性が示唆されています。この修正により、StreamWriterWrite メソッドは、発生したエラーを直接呼び出し元に 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)