[インデックス 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言語の仕様書(型システム、エクスポートルールに関する公式な定義を確認するため)