[インデックス 19121] ファイルの概要
このコミットは、Go言語のコンパイラ(cmd/gc
)におけるリフレクション関連の変更を取り消すものです。具体的には、以前のコミット(CL 66510044 / 6c0339d94123)によって導入された、埋め込み型かつ非公開フィールドのCanSet
挙動に関する修正が、他の問題を引き起こしたため元に戻されました。
コミット
commit c48db9a47388bfbcec97d33b03ee81ffe7eb50af
Author: Russ Cox <rsc@golang.org>
Date: Mon Apr 14 09:48:11 2014 -0400
undo CL 66510044 / 6c0339d94123
Broke other things - see issue 7522.
Fixes #7522.
Reopens issue 7363.
««« original CL description
cmd/gc: make embedded, unexported fields read-only.
Fixes #7363.
LGTM=gri
R=gri, rsc, bradfitz
CC=golang-codereviews
https://golang.org/cl/66510044
»»»
LGTM=r, mpvl
R=golang-codereviews, r
CC=golang-codereviews, iant, mpvl
https://golang.org/cl/85580046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c48db9a47388bfbcec97d33b03ee81ffe7eb50af
元コミット内容
このコミットが取り消している元のコミット(CL 66510044 / 6c0339d94123)の目的は、「cmd/gc
: 埋め込み型で非公開のフィールドを読み取り専用にする」ことでした。これは、issue 7363
で報告された問題を修正するためのものでした。
変更の背景
このコミットは、以前のコミット(CL 66510044 / 6c0339d94123)がGoのリフレクションシステムにおいて新たな問題(issue 7522
)を引き起こしたため、その変更を取り消す目的で作成されました。元のコミットはissue 7363
を修正しようとしましたが、その修正が他の予期せぬ副作用をもたらしたため、一度その修正をロールバックし、issue 7363
を再オープンする形となりました。これは、複雑なシステムにおける変更が、意図しない連鎖的な影響を及ぼす典型的な例と言えます。
前提知識の解説
Go言語のリフレクション
Go言語のリフレクションは、プログラムの実行中に型情報を検査したり、変数の値を動的に操作したりする機能です。reflect
パッケージを通じて提供され、主に以下の主要な型を使用します。
reflect.Type
: Goの型の情報を表します。例えば、構造体のフィールド名やメソッドなどを取得できます。reflect.Value
: Goの変数の値を表します。この値を通じて、変数の内容を読み取ったり、書き換えたりすることができます。
reflect.Value.CanSet()
reflect.Value
型にはCanSet()
というメソッドがあります。このメソッドは、そのreflect.Value
が表す変数の値を変更できるかどうかを返します。Goのリフレクションのルールとして、以下の条件を満たす場合にのみCanSet()
はtrue
を返します。
- アドレス可能であること (Addressable):
reflect.Value
が、メモリ上の特定のアドレスを持つ変数(例えば、ポインタの参照外しや構造体のフィールドなど)を表している必要があります。一時的な値やマップのキーなどはアドレス可能ではありません。 - 非公開フィールドでないこと: 構造体の非公開(unexported)フィールドは、そのフィールドが定義されたパッケージの外からは
CanSet()
がfalse
を返します。これはカプセル化の原則に基づいています。
埋め込み型 (Embedded Types)
Go言語の構造体は、他の構造体をフィールドとして埋め込むことができます。これにより、埋め込まれた型のメソッドやフィールドが、外側の構造体のメソッドやフィールドであるかのように直接アクセスできるようになります。これは、継承に似た機能を提供しますが、Goでは「コンポジション(合成)」として扱われます。
例:
type Inner struct {
unexportedField int // 非公開フィールド
ExportedField string // 公開フィールド
}
type Outer struct {
Inner // Inner型を埋め込み
}
この場合、Outer
型のインスタンスからunexportedField
やExportedField
に直接アクセスできますが、unexportedField
は非公開であるため、パッケージ外からは直接操作できません。
issue 7363
の問題
issue 7363
は、「reflect: CanSet
が非公開の埋め込み構造体フィールドに対してtrue
を返す」という問題でした。これは、reflect.Value.CanSet()
のドキュメントに記載されている「非公開の構造体フィールドの使用によって取得されたものでない限り、Value
は変更可能である」というルールに反する挙動でした。つまり、非公開の埋め込みフィールドであっても、リフレクションを通じてCanSet()
がtrue
を返してしまい、意図しない書き換えが可能になる可能性があったということです。
技術的詳細
このコミットは、src/cmd/gc/reflect.c
というファイルに対する変更を取り消しています。このファイルはGoコンパイラのバックエンドの一部であり、Goの型システムとリフレクションの内部的な挙動を定義しています。特に、Goの型情報がどのようにランタイムに表現され、リフレクションがその情報にどのようにアクセスするかを扱っています。
元のコミット(CL 66510044)では、reflect.c
内のdgopkgpath
およびdgostringptr
関連のロジックが変更され、埋め込み型で非公開のフィールドに対するCanSet
の挙動が修正されました。具体的には、フィールドが非公開であるかどうかのチェックが追加され、非公開フィールドの場合はCanSet
がfalse
を返すように調整されたと考えられます。
しかし、この修正がissue 7522
で報告された「他のもの」を壊したため、このコミットではその変更が元に戻されました。これは、コンパイラの内部的な型表現やリフレクションの挙動が非常にデリケートであり、一つの修正が広範囲に影響を及ぼす可能性があることを示しています。
また、test/fixedbugs/issue7363.go
というテストファイルが削除されています。これは、元のコミットでissue 7363
の修正を検証するために追加されたテストですが、今回のコミットでその修正が取り消されたため、関連するテストも不要となり削除されました。このテストファイルは、reflect.ValueOf(b).Elem().Field(0).CanSet()
がtrue
を返した場合にパニックを起こすことで、issue 7363
の修正が正しく適用されたことを確認するものでした。
コアとなるコードの変更箇所
src/cmd/gc/reflect.c
--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -1138,8 +1138,7 @@ ok:
ot = dgopkgpath(s, ot, t1->sym->pkg);
} else {
ot = dgostringptr(s, ot, nil);
- if(t1->type->sym != S &&
- (t1->type->sym->pkg == builtinpkg || !exportname(t1->type->sym->name)))
+ if(t1->type->sym != S && t1->type->sym->pkg == builtinpkg)
ot = dgopkgpath(s, ot, localpkg);
else
ot = dgostringptr(s, ot, nil);
test/fixedbugs/issue7363.go
このファイルは完全に削除されました。
コアとなるコードの解説
src/cmd/gc/reflect.c
の変更は、Goコンパイラがリフレクションの型情報を生成する部分にあります。特に、dgopkgpath
とdgostringptr
は、Goの型がランタイムでどのように表現されるかに関連する関数です。
元の変更では、if
文の条件が以下のように変更されていました。
if(t1->type->sym != S &&
(t1->type->sym->pkg == builtinpkg || !exportname(t1->type->sym->name)))
この条件は、t1->type->sym
が存在し、かつそのシンボルがbuiltinpkg
(組み込みパッケージ)に属するか、またはexportname
関数がfalse
を返す(つまり、非公開の名前である)場合に特定の処理を行うことを意味していました。これは、非公開フィールドに対するCanSet
の挙動を制御しようとした試みと考えられます。
しかし、このコミットではその変更が元に戻され、条件が以下のように簡素化されました。
if(t1->type->sym != S && t1->type->sym->pkg == builtinpkg)
これにより、非公開フィールドに関する特別なチェックが削除され、元の挙動に戻されました。このロールバックは、元の修正がGoのリフレクションシステム全体に予期せぬ影響を与え、他の重要な機能が損なわれたため、一時的に安定性を優先した結果であると推測されます。
test/fixedbugs/issue7363.go
の削除は、このコミットがissue 7363
の修正を無効にしたことを明確に示しています。このテストは、非公開の埋め込み構造体フィールドに対してreflect.Value.CanSet()
がtrue
を返さないことを期待していましたが、修正が取り消されたため、このテストはもはや適切ではなくなりました。
関連リンク
- 元の変更セット (CL 66510044): https://golang.org/cl/66510044
- このコミットの変更セット (CL 85580046): https://golang.org/cl/85580046
- Go Issue 7363:
reflect: CanSet returns true for unexported embedded struct fields
(このコミットにより再オープンされました)- https://github.com/golang/go/issues/7363 (GitHub上のGoリポジトリのIssueトラッカー)
参考にした情報源リンク
- Go言語の
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - Go言語の埋め込み型に関するドキュメントやチュートリアル (一般的なGoの学習リソース)
- Go言語のコンパイラ内部に関する情報 (Goのソースコードや関連する設計ドキュメント)
- Go言語のIssueトラッカー (GitHub Issues)
- Go Code Review Comments (Goのコードレビューガイドライン)
- Go言語のソースコード (特に
src/cmd/gc/reflect.c
およびsrc/reflect/value.go
など)