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

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

このコミットは、Goコンパイラ(cmd/gc)における組み込み型(builtin types)の埋め込みフィールドの扱いに関するバグ修正です。具体的には、埋め込まれた組み込み型が誤ってエクスポート(公開)されてしまう問題を解決し、Go言語の可視性ルール(エクスポートされないフィールドへのアクセス制限)が正しく適用されるようにします。また、reflectパッケージがこれらのフィールドを誤ってエクスポート済みと判断しないように修正します。

コミット

cmd/gc: 埋め込まれた組み込み型をエクスポートしないように修正。

このコミットは、Goコンパイラが構造体に埋め込まれた組み込み型(例: int, stringなど)のフィールドを誤ってエクスポート済みとして扱ってしまうバグを修正します。これにより、外部パッケージから本来アクセスできないはずのフィールドにアクセスできてしまう問題や、reflectパッケージがフィールドの可視性を誤って報告する問題が解決されます。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a45777fe9975583cac3ef7ee5d61937f5a003c5a

元コミット内容

commit a45777fe9975583cac3ef7ee5d61937f5a003c5a
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Sun Oct 7 06:53:57 2012 +0200

    cmd/gc: Don't export embedded builtins
    
    Fixes #4124.
    
    R=golang-dev, dave, minux.ma, remyoudompheng, rsc
    CC=golang-dev
    https://golang.org/cl/6543057

変更の背景

Go言語には、構造体への型の埋め込み(embedding)という機能があります。これにより、ある構造体が別の構造体やインターフェースのメソッドやフィールドを「昇格」させ、あたかも自身のメンバーであるかのように振る舞わせることができます。しかし、この機能が組み込み型(int, stringなど)に対して使用された場合、Goコンパイラ(cmd/gc)がその埋め込まれた組み込み型のフィールドを誤って「エクスポート済み」(つまり、パッケージ外部からアクセス可能)として扱ってしまうバグが存在しました。

このバグは、以下の2つの主要な問題を引き起こしていました。

  1. コンパイル時の可視性違反: 外部パッケージから、本来アクセスできないはずの埋め込まれた組み込み型のフィールドにアクセスしようとすると、コンパイラがエラーを報告せずにコンパイルを許可してしまう可能性がありました。Goの可視性ルールでは、小文字で始まるフィールドは非公開(unexported)であり、パッケージ内からのみアクセス可能です。
  2. リフレクションの誤った情報: reflectパッケージは、Goプログラムの実行時に型情報を検査・操作するための機能を提供します。このバグにより、reflectパッケージが埋め込まれた組み込み型のフィールドを誤って「エクスポート済み」と報告し、CanSet()などの操作を許可してしまう可能性がありました。これにより、本来変更できないはずの非公開フィールドがリフレクションを通じて変更されてしまうという、予期せぬ動作につながる恐れがありました。

このコミットは、これらの問題を修正し、Go言語の可視性ルールとリフレクションの動作が、埋め込まれた組み込み型に対しても一貫して適用されるようにするために行われました。

前提知識の解説

1. Go言語の型の埋め込み (Embedding)

Go言語では、構造体の中にフィールド名なしで別の型を宣言することで、その型のフィールドやメソッドを現在の構造体に「埋め込む」ことができます。これにより、埋め込まれた型のフィールドやメソッドが、あたかも現在の構造体の直接のメンバーであるかのようにアクセスできるようになります。これは継承とは異なり、コンポジション(合成)の一種と見なされます。

例:

type Inner struct {
    ID int
    name string // 非公開フィールド
}

type Outer struct {
    Inner // Inner型を埋め込み
    Description string
}

func main() {
    o := Outer{Inner: Inner{ID: 1, name: "test"}, Description: "example"}
    fmt.Println(o.ID) // Inner.ID にアクセス
    // fmt.Println(o.name) // エラー: o.name は非公開フィールドのためアクセス不可
}

2. Go言語の可視性ルール (Exported/Unexported Identifiers)

Go言語では、識別子(変数名、関数名、型名、フィールド名など)の最初の文字が大文字か小文字かによって、その識別子の可視性(スコープ)が決定されます。

  • 大文字で始まる識別子: エクスポート済み(Exported)。パッケージ外部からアクセス可能です。
  • 小文字で始まる識別子: 非エクスポート済み(Unexported)。宣言されたパッケージ内からのみアクセス可能です。

このルールは、カプセル化を強制し、APIの安定性を保つために重要です。

3. reflectパッケージ

reflectパッケージは、Goプログラムの実行時に、変数や型の情報を動的に検査・操作するための機能を提供します。これにより、プログラムは自身の構造を調べたり、実行時に未知の型の値を操作したりすることができます。

reflect.Value型は、Goの値をリフレクションで表現したものです。reflect.Valueのメソッドには、フィールドがエクスポートされているかどうかを判断するCanSet()などがあります。CanSet()は、そのreflect.Valueが変更可能(settable)であるかどうかを返します。変更可能であるためには、その値がアドレス可能(addressable)であり、かつエクスポートされたフィールドである必要があります。非エクスポートのフィールドは、たとえアドレス可能であってもCanSet()falseを返します。

4. Goコンパイラ (cmd/gc)

cmd/gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。このコンパイラは、Go言語の仕様に厳密に従ってコードを解析し、型チェック、最適化、コード生成などを行います。本コミットは、このコンパイラの内部的な型処理ロジック、特にシンボル(識別子)の管理と型情報の生成部分に修正を加えています。

技術的詳細

このコミットの技術的な核心は、Goコンパイラが埋め込まれた組み込み型(例: int)のフィールドを処理する際に、そのフィールドの「エクスポート状態」を正しく判断するように変更することです。

Goコンパイラ内部では、型やシンボル(識別子)は特定のデータ構造で表現されます。このコミット以前は、埋め込まれた組み込み型の場合、そのシンボルがbuiltinpkg(組み込みパッケージ)に属しているかどうかをチェックするロジックが不適切でした。これにより、本来非公開であるべきフィールドが、コンパイラ内部でエクスポート済みとして扱われてしまい、結果として外部からのアクセスやリフレクションによる操作が許可されてしまう問題が発生していました。

修正のポイントは以下の通りです。

  1. dcl.cにおけるembedded関数の修正: embedded関数は、埋め込まれたフィールドのシンボルを処理する際に、そのフィールドがエクスポートされるべきかどうかを判断します。以前はexportname(name) || s->pkg == builtinpkgという条件でエクスポートを判断していました。これは、「名前がエクスポート可能である」または「シンボルが組み込みパッケージに属している」場合にエクスポート済みと見なす、というロジックでした。 しかし、組み込み型(intなど)は、たとえ埋め込まれたとしても、そのフィールド名自体は小文字で始まるため、Goの可視性ルール上は非公開であるべきです。s->pkg == builtinpkgという条件が、この非公開性を無視してエクスポート済みと判断させてしまう原因でした。 修正後は、この条件から|| s->pkg == builtinpkgが削除され、exportname(name)のみでエクスポートを判断するようになりました。これにより、埋め込まれた組み込み型のフィールドは、その名前が小文字で始まる限り、正しく非公開として扱われるようになります。

  2. reflect.cにおける型情報の生成修正: reflect.cは、Goの型情報をリフレクションのために生成する部分です。特に、構造体のフィールドに関する情報を生成する際に、そのフィールドが組み込み型である場合に、そのパッケージパスをbuiltinpkgではなくlocalpkg(現在のパッケージ)として扱うように変更されました。 以前は、埋め込まれた組み込み型の場合でも、その型がbuiltinpkgに属していると判断し、その結果、リフレクション情報が誤ってエクスポート済みとして生成される可能性がありました。 修正では、t1->type->sym->pkg == builtinpkgという条件が追加され、もし型が組み込みパッケージに属している場合でも、そのフィールドが現在のパッケージに属しているかのようにdgopkgpath関数にlocalpkgを渡すように変更されました。これにより、リフレクションがフィールドの可視性を正しく認識し、CanSet()などが期待通りにfalseを返すようになります。

  3. align.clex.cにおけるシンボル割り当ての修正: これらのファイルでは、型の初期化や字句解析の過程で、型にシンボルを割り当てる処理が行われます。以前は、t->sym = s;のように直接シンボルsを割り当てていましたが、修正後はt->sym = s1;のように、pkglookupで取得したシンボルs1を割り当てるように変更されています。 これは、組み込み型の場合に、その型が属するパッケージ(builtinpkg)のシンボルを正しく参照するようにするための変更です。これにより、コンパイラ内部での型とシンボルの関連付けがより正確になり、前述の可視性判断のロジックと整合性が取れるようになります。

  4. fmt.cにおけるデバッグ出力の修正: fmt.cは、コンパイラのデバッグ出力やエラーメッセージのフォーマットを担当するファイルです。この変更は、デバッグ出力のフォーマットを%T:%Nから%hhS:%Nに変更し、型のシンボルをより正確に表示するように修正しています。これは直接的なバグ修正というよりは、デバッグ情報の改善に関連する変更です。

これらの変更により、Goコンパイラは埋め込まれた組み込み型のフィールドを、Go言語の可視性ルールに従って正しく非公開として扱い、リフレクションもその事実を正確に反映するようになりました。

コアとなるコードの変更箇所

src/cmd/gc/align.c

--- a/src/cmd/gc/align.c
+++ b/src/cmd/gc/align.c
@@ -607,7 +607,7 @@ typeinit(void)
 		fatal("typeinit: %s already defined", s->name);
 
 	tt = typ(etype);
-	tt->sym = s;
+	tt->sym = s1;
 
 	dowidth(t);
 	types[etype] = t;

src/cmd/gc/dcl.c

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -982,7 +982,7 @@ embedded(Sym *s)
 		*utfrune(name, CenterDot) = 0;
 	}
 
-	if(exportname(name) || s->pkg == builtinpkg)  // old behaviour, tests pass, but is it correct?
+	if(exportname(name))
 		n = newname(lookup(name));
 	else
 		n = newname(pkglookup(name, s->pkg));

src/cmd/gc/fmt.c

--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1181,7 +1181,7 @@ exprfmt(Fmt *f, Node *n, int prec)
 				tt = l->n->left->type->type;
 				if(t->sym == S)
 					tt = t->type;
-				fmtprint(f, " %T:%N", t, l->n->right);
+				fmtprint(f, " %hhS:%N", t->sym, l->n->right);
 			} else
 				fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1831,16 +1831,16 @@ lexinit(void)
 	if(etype != Txxx) {
 		if(etype < 0 || etype >= nelem(types))
 			fatal("lexinit: %s bad etype", s->name);
+		s1 = pkglookup(syms[i].name, builtinpkg);
 		tt = types[etype];
 		if(t == T) {
 			tt = typ(etype);
-			tt->sym = s;
+			tt->sym = s1;
 
 			if(etype != TANY && etype != TSTRING)
 				dowidth(t);
 			types[etype] = t;
 		}
-		s1 = pkglookup(syms[i].name, builtinpkg);
 		s1->lexical = LNAME;
 		s1->def = typenod(t);
 		continue;

src/cmd/gc/reflect.c

--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -866,7 +866,10 @@ ok:
 				ot = dgopkgpath(s, ot, t1->sym->pkg);
 			} else {
 				ot = dgostringptr(s, ot, nil);
-				ot = dgostringptr(s, ot, nil);
+				if(t1->type->sym != S && t1->type->sym->pkg == builtinpkg)
+					ot = dgopkgpath(s, ot, localpkg);
+				else
+					ot = dgostringptr(s, ot, nil);
 			}
 			ot = dsymptr(s, ot, dtypesym(t1->type), 0);
 			ot = dgostrlitptr(s, ot, t1->note);

テストファイル (test/fixedbugs/)

  • test/fixedbugs/bug460.dir/a.go (新規追加)
  • test/fixedbugs/bug460.dir/b.go (新規追加)
  • test/fixedbugs/bug460.go (新規追加)
  • test/fixedbugs/bug461.go (新規追加)

コアとなるコードの解説

src/cmd/gc/dcl.c の変更 (embedded 関数)

-	if(exportname(name) || s->pkg == builtinpkg)
+	if(exportname(name))

この変更が、このコミットの最も重要な修正点の一つです。embedded関数は、構造体に埋め込まれたフィールドのシンボルを処理する際に、そのフィールドがエクスポートされるべきかどうかを判断します。 以前のコードでは、フィールドの名前がエクスポート可能である(exportname(name))か、またはそのシンボルがbuiltinpkg(組み込みパッケージ、例: int, stringなどが属する)に属している場合に、そのフィールドをエクスポート済みと見なしていました。 しかし、Goの可視性ルールでは、フィールド名が小文字で始まる場合(例: int型を埋め込んだ際のフィールド名int)は、たとえそれが組み込み型であっても非公開であるべきです。s->pkg == builtinpkgという条件が、この非公開性を無視してエクスポート済みと判断させてしまう原因でした。 この条件を削除することで、埋め込まれた組み込み型のフィールドは、その名前が小文字で始まる限り、Goの可視性ルールに従って正しく非公開として扱われるようになります。これにより、外部パッケージからの不正なアクセスがコンパイル時に検出されるようになります。

src/cmd/gc/reflect.c の変更

-				ot = dgostringptr(s, ot, nil);
+				if(t1->type->sym != S && t1->type->sym->pkg == builtinpkg)
+					ot = dgopkgpath(s, ot, localpkg);
+				else
+					ot = dgostringptr(s, ot, nil);

この変更は、reflectパッケージが型情報を生成する際のロジックを修正します。特に、構造体のフィールドに関する情報を生成する際に、そのフィールドが組み込み型である場合のパッケージパスの扱いを修正しています。 以前は、埋め込まれた組み込み型の場合でも、その型がbuiltinpkgに属していると判断し、その結果、リフレクション情報が誤ってエクスポート済みとして生成される可能性がありました。 修正では、t1->type->sym != S && t1->type->sym->pkg == builtinpkgという条件が追加されました。これは、「型が有効なシンボルを持ち、かつそのシンボルが組み込みパッケージに属している」場合に、そのフィールドのパッケージパスをlocalpkg(現在のパッケージ)として扱うように指示します。 これにより、reflectパッケージは埋め込まれた組み込み型のフィールドを、現在のパッケージの非公開フィールドとして正しく認識し、CanSet()などのメソッドが期待通りにfalseを返すようになります。これは、test/fixedbugs/bug461.goでテストされている動作です。

src/cmd/gc/align.csrc/cmd/gc/lex.c の変更

-	tt->sym = s;
+	tt->sym = s1;

これらの変更は、型の初期化(align.ctypeinit関数)と字句解析(lex.clexinit関数)の過程で、型にシンボルを割り当てる処理を修正しています。 以前は、直接sというシンボルを割り当てていましたが、修正後はs1というシンボルを割り当てるように変更されています。s1は、pkglookup(syms[i].name, builtinpkg)によって取得されたシンボルです。 この変更は、組み込み型の場合に、その型が属するパッケージ(builtinpkg)のシンボルを正しく参照するようにするためのものです。これにより、コンパイラ内部での型とシンボルの関連付けがより正確になり、前述のdcl.creflect.cでの可視性判断ロジックと整合性が取れるようになります。これは、コンパイラが型の情報を内部的にどのように管理しているかに関わる、より低レベルな修正です。

src/cmd/gc/fmt.c の変更

-				fmtprint(f, " %T:%N", t, l->n->right);
+				fmtprint(f, " %hhS:%N", t->sym, l->n->right);

この変更は、コンパイラのデバッグ出力のフォーマットを修正しています。%Tは型全体を表示しますが、%hhSは型のシンボルのみを表示します。 これは直接的なバグ修正というよりは、コンパイラの内部的なデバッグ情報の表示を改善するための変更であり、問題の診断や理解を助ける目的があります。

テストファイルの追加

  • test/fixedbugs/bug460.dir/a.go

  • test/fixedbugs/bug460.dir/b.go

  • test/fixedbugs/bug460.go これらのファイルは、埋め込まれた組み込み型(int)の非公開フィールドに外部パッケージからアクセスしようとした場合に、コンパイラが正しくエラーを報告することを確認するためのテストです。bug460.goのコメントにある// errorcheckdirは、このディレクトリ内のファイルがコンパイルエラーを生成することを期待するテストであることを示しています。

  • test/fixedbugs/bug461.go このファイルは、reflectパッケージが埋め込まれた組み込み型(int)のフィールドを正しく非公開として認識し、CanSet()falseを返すことを確認するためのテストです。// runは、このテストが実行され、パニックが発生しないことを期待するテストであることを示しています。

これらのテストの追加は、修正が正しく機能していることを検証し、将来的な回帰を防ぐために不可欠です。

関連リンク

  • Go言語の埋め込みに関する公式ドキュメント(Go言語のバージョンによって内容が異なる可能性がありますが、概念は共通です)
  • Go言語の可視性ルールに関する公式ドキュメント

参考にした情報源リンク

  • コミットハッシュ: a45777fe9975583cac3ef7ee5d61937f5a003c5a
  • GitHub上のコミットページ: https://github.com/golang/go/commit/a45777fe9975583cac3ef7ee5d61937f5a003c5a
  • Go言語のソースコード(特にsrc/cmd/gcsrc/reflectディレクトリ)
  • Go言語のテストスイート(特にtest/fixedbugsディレクトリ)
  • Go言語の仕様書(識別子の可視性、型の埋め込みに関するセクション)
  • Go言語のIssueトラッカー(Issue #4124に関連する情報があれば)
  • Go言語のコンパイラに関する一般的な知識と資料# [インデックス 14047] ファイルの概要

このコミットは、Goコンパイラ(cmd/gc)における組み込み型(builtin types)の埋め込みフィールドの扱いに関するバグ修正です。具体的には、埋め込まれた組み込み型が誤ってエクスポート(公開)されてしまう問題を解決し、Go言語の可視性ルール(エクスポートされないフィールドへのアクセス制限)が正しく適用されるようにします。また、reflectパッケージがこれらのフィールドを誤ってエクスポート済みと判断しないように修正します。

コミット

cmd/gc: 埋め込まれた組み込み型をエクスポートしないように修正。

このコミットは、Goコンパイラが構造体に埋め込まれた組み込み型(例: int, stringなど)のフィールドを誤ってエクスポート済みとして扱ってしまうバグを修正します。これにより、外部パッケージから本来アクセスできないはずのフィールドにアクセスできてしまう問題や、reflectパッケージがフィールドの可視性を誤って報告する問題が解決されます。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a45777fe9975583cac3ef7ee5d61937f5a003c5a

元コミット内容

commit a45777fe9975583cac3ef7ee5d61937f5a003c5a
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Sun Oct 7 06:53:57 2012 +0200

    cmd/gc: Don't export embedded builtins
    
    Fixes #4124.
    
    R=golang-dev, dave, minux.ma, remyoudompheng, rsc
    CC=golang-dev
    https://golang.org/cl/6543057

変更の背景

Go言語には、構造体への型の埋め込み(embedding)という機能があります。これにより、ある構造体が別の構造体やインターフェースのメソッドやフィールドを「昇格」させ、あたかも自身のメンバーであるかのように振る舞わせることができます。しかし、この機能が組み込み型(int, stringなど)に対して使用された場合、Goコンパイラ(cmd/gc)がその埋め込まれた組み込み型のフィールドを誤って「エクスポート済み」(つまり、パッケージ外部からアクセス可能)として扱ってしまうバグが存在しました。

このバグは、以下の2つの主要な問題を引き起こしていました。

  1. コンパイル時の可視性違反: 外部パッケージから、本来アクセスできないはずの埋め込まれた組み込み型のフィールドにアクセスしようとすると、コンパイラがエラーを報告せずにコンパイルを許可してしまう可能性がありました。Goの可視性ルールでは、小文字で始まるフィールドは非公開(unexported)であり、パッケージ内からのみアクセス可能です。
  2. リフレクションの誤った情報: reflectパッケージは、Goプログラムの実行時に型情報を検査・操作するための機能を提供します。このバグにより、reflectパッケージが埋め込まれた組み込み型のフィールドを誤って「エクスポート済み」と報告し、CanSet()などの操作を許可してしまう可能性がありました。これにより、本来変更できないはずの非公開フィールドがリフレクションを通じて変更されてしまうという、予期せぬ動作につながる恐れがありました。

このコミットは、これらの問題を修正し、Go言語の可視性ルールとリフレクションの動作が、埋め込まれた組み込み型に対しても一貫して適用されるようにするために行われました。

前提知識の解説

1. Go言語の型の埋め込み (Embedding)

Go言語では、構造体の中にフィールド名なしで別の型を宣言することで、その型のフィールドやメソッドを現在の構造体に「埋め込む」ことができます。これにより、埋め込まれた型のフィールドやメソッドが、あたかも現在の構造体の直接のメンバーであるかのようにアクセスできるようになります。これは継承とは異なり、コンポジション(合成)の一種と見なされます。

例:

type Inner struct {
    ID int
    name string // 非公開フィールド
}

type Outer struct {
    Inner // Inner型を埋め込み
    Description string
}

func main() {
    o := Outer{Inner: Inner{ID: 1, name: "test"}, Description: "example"}
    fmt.Println(o.ID) // Inner.ID にアクセス
    // fmt.Println(o.name) // エラー: o.name は非公開フィールドのためアクセス不可
}

2. Go言語の可視性ルール (Exported/Unexported Identifiers)

Go言語では、識別子(変数名、関数名、型名、フィールド名など)の最初の文字が大文字か小文字かによって、その識別子の可視性(スコープ)が決定されます。

  • 大文字で始まる識別子: エクスポート済み(Exported)。パッケージ外部からアクセス可能です。
  • 小文字で始まる識別子: 非エクスポート済み(Unexported)。宣言されたパッケージ内からのみアクセス可能です。

このルールは、カプセル化を強制し、APIの安定性を保つために重要です。

3. reflectパッケージ

reflectパッケージは、Goプログラムの実行時に、変数や型の情報を動的に検査・操作するための機能を提供します。これにより、プログラムは自身の構造を調べたり、実行時に未知の型の値を操作したりすることができます。

reflect.Value型は、Goの値をリフレクションで表現したものです。reflect.Valueのメソッドには、フィールドがエクスポートされているかどうかを判断するCanSet()などがあります。CanSet()は、そのreflect.Valueが変更可能(settable)であるかどうかを返します。変更可能であるためには、その値がアドレス可能(addressable)であり、かつエクスポートされたフィールドである必要があります。非エクスポートのフィールドは、たとえアドレス可能であってもCanSet()falseを返します。

4. Goコンパイラ (cmd/gc)

cmd/gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。このコンパイラは、Go言語の仕様に厳密に従ってコードを解析し、型チェック、最適化、コード生成などを行います。本コミットは、このコンパイラの内部的な型処理ロジック、特にシンボル(識別子)の管理と型情報の生成部分に修正を加えています。

技術的詳細

このコミットの技術的な核心は、Goコンパイラが埋め込まれた組み込み型(例: int)のフィールドを処理する際に、そのフィールドの「エクスポート状態」を正しく判断するように変更することです。

Goコンパイラ内部では、型やシンボル(識別子)は特定のデータ構造で表現されます。このコミット以前は、埋め込まれた組み込み型の場合、そのシンボルがbuiltinpkg(組み込みパッケージ)に属しているかどうかをチェックするロジックが不適切でした。これにより、本来非公開であるべきフィールドが、コンパイラ内部でエクスポート済みとして扱われてしまい、結果として外部からのアクセスやリフレクションによる操作が許可されてしまう問題が発生していました。

修正のポイントは以下の通りです。

  1. dcl.cにおけるembedded関数の修正: embedded関数は、埋め込まれたフィールドのシンボルを処理する際に、そのフィールドがエクスポートされるべきかどうかを判断します。以前はexportname(name) || s->pkg == builtinpkgという条件でエクスポートを判断していました。これは、「名前がエクスポート可能である」または「シンボルが組み込みパッケージに属している」場合にエクスポート済みと見なす、というロジックでした。 しかし、組み込み型(intなど)は、たとえ埋め込まれたとしても、そのフィールド名自体は小文字で始まるため、Goの可視性ルール上は非公開であるべきです。s->pkg == builtinpkgという条件が、この非公開性を無視してエクスポート済みと判断させてしまう原因でした。 修正後は、この条件から|| s->pkg == builtinpkgが削除され、exportname(name)のみでエクスポートを判断するようになりました。これにより、埋め込まれた組み込み型のフィールドは、その名前が小文字で始まる限り、正しく非公開として扱われるようになります。

  2. reflect.cにおける型情報の生成修正: reflect.cは、Goの型情報をリフレクションのために生成する部分です。特に、構造体のフィールドに関する情報を生成する際に、そのフィールドが組み込み型である場合に、そのパッケージパスをbuiltinpkgではなくlocalpkg(現在のパッケージ)として扱うように変更されました。 以前は、埋め込まれた組み込み型の場合でも、その型がbuiltinpkgに属していると判断し、その結果、リフレクション情報が誤ってエクスポート済みとして生成される可能性がありました。 修正では、t1->type->sym->pkg == builtinpkgという条件が追加され、もし型が組み込みパッケージに属している場合でも、そのフィールドが現在のパッケージに属しているかのようにdgopkgpath関数にlocalpkgを渡すように変更されました。これにより、リフレクションがフィールドの可視性を正しく認識し、CanSet()などが期待通りにfalseを返すようになります。

  3. align.clex.cにおけるシンボル割り当ての修正: これらのファイルでは、型の初期化や字句解析の過程で、型にシンボルを割り当てる処理が行われます。以前は、t->sym = s;のように直接シンボルsを割り当てていましたが、修正後はt->sym = s1;のように、pkglookupで取得したシンボルs1を割り当てるように変更されています。 これは、組み込み型の場合に、その型が属するパッケージ(builtinpkg)のシンボルを正しく参照するようにするための変更です。これにより、コンパイラ内部での型とシンボルの関連付けがより正確になり、前述の可視性判断のロジックと整合性が取れるようになります。

  4. fmt.cにおけるデバッグ出力の修正: fmt.cは、コンパイラのデバッグ出力やエラーメッセージのフォーマットを担当するファイルです。この変更は、デバッグ出力のフォーマットを%T:%Nから%hhS:%Nに変更し、型のシンボルをより正確に表示するように修正しています。これは直接的なバグ修正というよりは、デバッグ情報の改善に関連する変更です。

これらの変更により、Goコンパイラは埋め込まれた組み込み型のフィールドを、Go言語の可視性ルールに従って正しく非公開として扱い、リフレクションもその事実を正確に反映するようになりました。

コアとなるコードの変更箇所

src/cmd/gc/align.c

--- a/src/cmd/gc/align.c
+++ b/src/cmd/gc/align.c
@@ -607,7 +607,7 @@ typeinit(void)
 		fatal("typeinit: %s already defined", s->name);
 
 	tt = typ(etype);
-	tt->sym = s;
+	tt->sym = s1;
 
 	dowidth(t);
 	types[etype] = t;

src/cmd/gc/dcl.c

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -982,7 +982,7 @@ embedded(Sym *s)
 		*utfrune(name, CenterDot) = 0;
 	}
 
-	if(exportname(name) || s->pkg == builtinpkg)  // old behaviour, tests pass, but is it correct?
+	if(exportname(name))
 		n = newname(lookup(name));
 	else
 		n = newname(pkglookup(name, s->pkg));

src/cmd/gc/fmt.c

--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1181,7 +1181,7 @@ exprfmt(Fmt *f, Node *n, int prec)
 				tt = l->n->left->type->type;
 				if(t->sym == S)
 					tt = t->type;
-				fmtprint(f, " %T:%N", t, l->n->right);
+				fmtprint(f, " %hhS:%N", t->sym, l->n->right);
 			} else
 				fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1831,16 +1831,16 @@ lexinit(void)
 	if(etype != Txxx) {
 		if(etype < 0 || etype >= nelem(types))
 			fatal("lexinit: %s bad etype", s->name);
+		s1 = pkglookup(syms[i].name, builtinpkg);
 		tt = types[etype];
 		if(t == T) {
 			tt = typ(etype);
-			tt->sym = s;
+			tt->sym = s1;
 
 			if(etype != TANY && etype != TSTRING)
 				dowidth(t);
 			types[etype] = t;
 		}
-		s1 = pkglookup(syms[i].name, builtinpkg);
 		s1->lexical = LNAME;
 		s1->def = typenod(t);
 		continue;

src/cmd/gc/reflect.c

--- a/src/cmd/gc/reflect.c
+++ b/src/cmd/gc/reflect.c
@@ -866,7 +866,10 @@ ok:
 				ot = dgopkgpath(s, ot, t1->sym->pkg);
 			} else {
 				ot = dgostringptr(s, ot, nil);
-				ot = dgostringptr(s, ot, nil);
+				if(t1->type->sym != S && t1->type->sym->pkg == builtinpkg)
+					ot = dgopkgpath(s, ot, localpkg);
+				else
+					ot = dgostringptr(s, ot, nil);
 			}
 			ot = dsymptr(s, ot, dtypesym(t1->type), 0);
 			ot = dgostrlitptr(s, ot, t1->note);

テストファイル (test/fixedbugs/)

  • test/fixedbugs/bug460.dir/a.go (新規追加)
  • test/fixedbugs/bug460.dir/b.go (新規追加)
  • test/fixedbugs/bug460.go (新規追加)
  • test/fixedbugs/bug461.go (新規追加)

コアとなるコードの解説

src/cmd/gc/dcl.c の変更 (embedded 関数)

-	if(exportname(name) || s->pkg == builtinpkg)
+	if(exportname(name))

この変更が、このコミットの最も重要な修正点の一つです。embedded関数は、構造体に埋め込まれたフィールドのシンボルを処理する際に、そのフィールドがエクスポートされるべきかどうかを判断します。 以前のコードでは、フィールドの名前がエクスポート可能である(exportname(name))か、またはそのシンボルがbuiltinpkg(組み込みパッケージ、例: int, stringなどが属する)に属している場合に、そのフィールドをエクスポート済みと見なしていました。 しかし、Goの可視性ルールでは、フィールド名が小文字で始まる場合(例: int型を埋め込んだ際のフィールド名int)は、たとえそれが組み込み型であっても非公開であるべきです。s->pkg == builtinpkgという条件が、この非公開性を無視してエクスポート済みと判断させてしまう原因でした。 この条件を削除することで、埋め込まれた組み込み型のフィールドは、その名前が小文字で始まる限り、Goの可視性ルールに従って正しく非公開として扱われるようになります。これにより、外部パッケージからの不正なアクセスがコンパイル時に検出されるようになります。

src/cmd/gc/reflect.c の変更

-				ot = dgostringptr(s, ot, nil);
+				if(t1->type->sym != S && t1->type->sym->pkg == builtinpkg)
+					ot = dgopkgpath(s, ot, localpkg);
+				else
+					ot = dgostringptr(s, ot, nil);

この変更は、reflectパッケージが型情報を生成する際のロジックを修正します。特に、構造体のフィールドに関する情報を生成する際に、そのフィールドが組み込み型である場合のパッケージパスの扱いを修正しています。 以前は、埋め込まれた組み込み型の場合でも、その型がbuiltinpkgに属していると判断し、その結果、リフレクション情報が誤ってエクスポート済みとして生成される可能性がありました。 修正では、t1->type->sym != S && t1->type->sym->pkg == builtinpkgという条件が追加されました。これは、「型が有効なシンボルを持ち、かつそのシンボルが組み込みパッケージに属している」場合に、そのフィールドのパッケージパスをlocalpkg(現在のパッケージ)として扱うように指示します。 これにより、reflectパッケージは埋め込まれた組み込み型のフィールドを、現在のパッケージの非公開フィールドとして正しく認識し、CanSet()などのメソッドが期待通りにfalseを返すようになります。これは、test/fixedbugs/bug461.goでテストされている動作です。

src/cmd/gc/align.csrc/cmd/gc/lex.c の変更

-	tt->sym = s;
+	tt->sym = s1;

これらの変更は、型の初期化(align.ctypeinit関数)と字句解析(lex.clexinit関数)の過程で、型にシンボルを割り当てる処理を修正しています。 以前は、直接sというシンボルを割り当てていましたが、修正後はs1というシンボルを割り当てるように変更されています。s1は、pkglookup(syms[i].name, builtinpkg)によって取得されたシンボルです。 この変更は、組み込み型の場合に、その型が属するパッケージ(builtinpkg)のシンボルを正しく参照するようにするためのものです。これにより、コンパイラ内部での型とシンボルの関連付けがより正確になり、前述のdcl.creflect.cでの可視性判断ロジックと整合性が取れるようになります。これは、コンパイラが型の情報を内部的にどのように管理しているかに関わる、より低レベルな修正です。

src/cmd/gc/fmt.c の変更

-				fmtprint(f, " %T:%N", t, l->n->right);
+				fmtprint(f, " %hhS:%N", t->sym, l->n->right);

この変更は、コンパイラのデバッグ出力のフォーマットを修正しています。%Tは型全体を表示しますが、%hhSは型のシンボルのみを表示します。 これは直接的なバグ修正というよりは、コンパイラの内部的なデバッグ情報の表示を改善するための変更であり、問題の診断や理解を助ける目的があります。

テストファイルの追加

  • test/fixedbugs/bug460.dir/a.go

  • test/fixedbugs/bug460.dir/b.go

  • test/fixedbugs/bug460.go これらのファイルは、埋め込まれた組み込み型(int)の非公開フィールドに外部パッケージからアクセスしようとした場合に、コンパイラが正しくエラーを報告することを確認するためのテストです。bug460.goのコメントにある// errorcheckdirは、このディレクトリ内のファイルがコンパイルエラーを生成することを期待するテストであることを示しています。

  • test/fixedbugs/bug461.go このファイルは、reflectパッケージが埋め込まれた組み込み型(int)のフィールドを正しく非公開として認識し、CanSet()falseを返すことを確認するためのテストです。// runは、このテストが実行され、パニックが発生しないことを期待するテストであることを示しています。

これらのテストの追加は、修正が正しく機能していることを検証し、将来的な回帰を防ぐために不可欠です。

関連リンク

  • Go言語の埋め込みに関する公式ドキュメント(Go言語のバージョンによって内容が異なる可能性がありますが、概念は共通です)
  • Go言語の可視性ルールに関する公式ドキュメント

参考にした情報源リンク

  • コミットハッシュ: a45777fe9975583cac3ef7ee5d61937f5a003c5a
  • GitHub上のコミットページ: https://github.com/golang/go/commit/a45777fe9975583cac3ef7ee5d61937f5a003c5a
  • Go言語のソースコード(特にsrc/cmd/gcsrc/reflectディレクトリ)
  • Go言語のテストスイート(特にtest/fixedbugsディレクトリ)
  • Go言語の仕様書(識別子の可視性、型の埋め込みに関するセクション)
  • Go言語のIssueトラッカー(Issue #4124に関連する情報があれば)
  • Go言語のコンパイラに関する一般的な知識と資料