[インデックス 10306] ファイルの概要
このコミットは、Goコンパイラ(gc
)における型チェックの挙動を修正し、append
およびcopy
操作において非エクスポートフィールドへの暗黙的な代入を禁止するものです。具体的には、src/cmd/gc/typecheck.c
に型チェックロジックが追加され、この変更を検証するための新しいテストケースtest/fixedbugs/bug378.go
が追加されています。
コミット
- コミットハッシュ:
151b2f15094168946993448f7f4a5e2a8441bd76
- Author: Luuk van Dijk lvd@golang.org
- Date: Wed Nov 9 11:17:06 2011 +0100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/151b2f15094168946993448f7f4a5e2a8441bd76
元コミット内容
gc: Fail on implicit assigment to non-exported fields in copy and append.
Fixes #1387.
R=rsc
CC=golang-dev
https://golang.org/cl/5348046
変更の背景
Go言語では、パッケージ外部からアクセス可能な識別子(変数、関数、型、フィールドなど)は「エクスポートされた」ものと呼ばれ、大文字で始まります。一方、小文字で始まる識別子は「非エクスポート(または内部)フィールド」と呼ばれ、そのパッケージ内でのみアクセス可能です。このルールは、カプセル化を強制し、APIの安定性を保つために重要です。
このコミット以前のGoコンパイラには、append
やcopy
といった組み込み関数を使用する際に、このエクスポートルールが適切に適用されないというバグが存在していました。具体的には、非エクスポートフィールドを持つ構造体のスライスに対してappend
やcopy
を行うと、本来許されないはずの非エクスポートフィールドへの暗黙的な代入が発生してしまう可能性がありました。これは、Go言語の設計思想に反する挙動であり、予期せぬデータ変更やセキュリティ上の問題を引き起こす可能性がありました。
この問題は、GoのIssue #1387として報告されており、このコミットはその問題を修正することを目的としています。
前提知識の解説
Go言語のエクスポートルール
Go言語では、識別子(変数名、関数名、型名、構造体のフィールド名など)の最初の文字が大文字である場合、その識別子はパッケージ外に「エクスポート」されます。つまり、他のパッケージからその識別子にアクセスできます。一方、最初の文字が小文字である場合、その識別子はパッケージ内に限定され、パッケージ外からはアクセスできません。これは、オブジェクト指向プログラミングにおけるカプセル化の概念をGo言語で実現する方法の一つです。
例:
package mypackage
type MyStruct struct {
ExportedField int // エクスポートされる
unexportedField int // エクスポートされない
}
func ExportedFunction() { // エクスポートされる
// ...
}
func unexportedFunction() { // エクスポートされない
// ...
}
append
関数
append
はGoの組み込み関数で、スライスに要素を追加するために使用されます。
newSlice = append(slice, elements...)
の形式で使われ、slice
にelements
を追加した新しいスライスを返します。必要に応じて、基盤となる配列の容量を増やします。
copy
関数
copy
もGoの組み込み関数で、ソーススライスの要素をデスティネーションスライスにコピーするために使用されます。
copiedCount = copy(dst, src)
の形式で使われ、コピーされた要素の数を返します。コピーは、デスティネーションスライスの長さとソーススライスの長さの小さい方まで行われます。
Goコンパイラ (gc
) と型チェック
gc
はGo言語の公式コンパイラです。コンパイルプロセスの一部として、ソースコードがGo言語の仕様に準拠しているかを確認する「型チェック」フェーズがあります。このフェーズでは、変数の型が正しく使用されているか、関数の引数と戻り値の型が一致しているか、エクスポートルールが守られているかなどが検証されます。このコミットは、この型チェックフェーズに新しい制約を追加するものです。
技術的詳細
このコミットの核心は、src/cmd/gc/typecheck.c
ファイルにexportassignok
という概念を導入し、append
とcopy
の型チェックロジックに組み込んだ点です。
exportassignok
は、Goコンパイラの内部関数またはロジックの一部であり、特定の型が非エクスポートフィールドを含んでいる場合に、その型への暗黙的な代入が許可されるべきかどうかを判断します。このコミットでは、append
とcopy
の操作において、要素の型が非エクスポートフィールドを持つ構造体である場合、その操作をエラーとして扱うように変更されています。
具体的には、以下の変更が行われています。
-
append
関数への適用:append
の型チェックロジックにおいて、スライスの要素型(t->type
)がexportassignok
の条件を満たさない場合、つまり非エクスポートフィールドへの暗黙的な代入が発生する可能性がある場合に、コンパイルエラー(yyerror
)を発生させ、処理を中断(goto error
)します。 -
copy
関数への適用:copy
の型チェックロジックにおいて、デスティネーションスライスの要素型(n->left->type->type
)がexportassignok
の条件を満たさない場合、同様にコンパイルエラーを発生させ、処理を中断します。
この変更により、コンパイラはappend
やcopy
を通じて非エクスポートフィールドが不正に操作されることを事前に検出し、開発者に警告するようになります。これにより、Go言語のカプセル化の原則がより厳密に守られるようになります。
コアとなるコードの変更箇所
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -1032,6 +1032,9 @@ reswitch:
yyerror("first argument to append must be slice; have %lT", t);
goto error;
}
+ if(!exportassignok(t->type, "append"))
+ goto error;
+
if(n->isddd) {
if(args->next == nil) {
yyerror("cannot use ... on first argument to append");
@@ -1098,6 +1101,8 @@ reswitch:
yyerror("arguments to copy have different element types: %lT and %lT", n->left->type, n->right->type);
goto error;
}
+ if(!exportassignok(n->left->type->type, "copy"))
+ goto error;
goto ret;
case OCONV:
test/fixedbugs/bug378.go
--- /dev/null
+++ b/test/fixedbugs/bug378.go
@@ -0,0 +1,27 @@
+// errchk $G $D/$F.go
+
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Issue 1387
+package foo
+
+import "bytes"
+
+func i() {
+ a := make([]bytes.Buffer, 1)
+ b := a[0] // ERROR "unexported field"
+}
+
+func f() {
+ a := make([]bytes.Buffer, 1)
+ a = append(a, a...) // ERROR "unexported field"
+}
+
+
+func g() {
+ a := make([]bytes.Buffer, 1)
+ b := make([]bytes.Buffer, 1)
+ copy(b, a) // ERROR "unexported field"
+}
コアとなるコードの解説
src/cmd/gc/typecheck.c
の変更
-
append
の型チェック部分:if(!exportassignok(t->type, "append"))
の行が追加されました。t->type
はappend
の第一引数(スライス)の要素型を表します。この要素型がexportassignok
のチェックをパスしない場合(つまり、非エクスポートフィールドへの暗黙的な代入が問題となる場合)、goto error;
によってコンパイルエラーが発生します。 -
copy
の型チェック部分:if(!exportassignok(n->left->type->type, "copy"))
の行が追加されました。n->left->type->type
はcopy
の第一引数(デスティネーションスライス)の要素型を表します。この要素型がexportassignok
のチェックをパスしない場合、同様にgoto error;
によってコンパイルエラーが発生します。
これらの変更により、append
やcopy
が非エクスポートフィールドを持つ型を扱う際に、Go言語のカプセル化ルールに違反する操作を未然に防ぐことができるようになりました。
test/fixedbugs/bug378.go
の追加
このファイルは、上記の変更が正しく機能することを確認するためのテストケースです。
bytes.Buffer
型は、内部に非エクスポートフィールドを持っています。このテストでは、bytes.Buffer
のスライスに対してappend
やcopy
、および直接的な代入操作を試みています。
b := a[0]
:bytes.Buffer
型の要素を直接代入しようとしています。これは非エクスポートフィールドへのアクセスとなるため、ERROR "unexported field"
が期待されます。a = append(a, a...)
:bytes.Buffer
のスライスに対してappend
を実行しています。この操作も非エクスポートフィールドへの暗黙的な代入を引き起こすため、ERROR "unexported field"
が期待されます。copy(b, a)
:bytes.Buffer
のスライス間でcopy
を実行しています。これも同様に非エクスポートフィールドへの暗黙的な代入となるため、ERROR "unexported field"
が期待されます。
// errchk $G $D/$F.go
というコメントは、このテストファイルがコンパイルエラーを発生させることを期待していることを示しています。このテストが成功するということは、コンパイラが非エクスポートフィールドへの不正な操作を正しく検出していることを意味します。
関連リンク
- Go Change List: https://golang.org/cl/5348046
参考にした情報源リンク
- このコミットのGitHubページ: https://github.com/golang/go/commit/151b2f15094168946993448f7f4a5e2a8441bd76
- Go言語の公式ドキュメント(エクスポートルール、スライス、append、copy関数に関する一般的な情報)
- Go言語のIssueトラッカー(ただし、Issue #1387は現在のGitHubリポジトリでは直接見つかりませんでした。これは、Goの初期のIssueトラッキングシステムや内部的なIssue番号である可能性があります。)
bytes.Buffer
のGoドキュメント(内部構造を確認するため)- Goコンパイラのソースコード(
src/cmd/gc/
ディレクトリ内の他のファイルを参照し、exportassignok
の具体的な実装やyyerror
の挙動を理解するため) - Go言語の仕様書(型システム、エクスポートルールに関する公式な定義を確認するため)