[インデックス 14585] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるシンボルエクスポートの挙動を修正し、不要なプライベートシンボルがエクスポートされるのを防ぐものです。特に、Go言語の組み込み(builtin)シンボルがユーザーコードによって再宣言された場合に、その再宣言されたシンボルが正しく扱われ、エクスポートデータが破損しないようにするための変更が含まれています。これにより、コンパイラの効率性が向上し、エクスポートされるメタデータの正確性が保証されます。
コミット
commit 561edbd63ceb0b1c0177051a0c151c68278219d6
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Sat Dec 8 14:43:00 2012 +0100
cmd/gc: do not export useless private symbols.
Fixes #4252.
R=rsc, golang-dev, mirtchovski, daniel.morsing, dave, lvd
CC=golang-dev
https://golang.org/cl/6856126
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/561edbd63ceb0b1c0177051a0c151c68278219d6
元コミット内容
cmd/gc: do not export useless private symbols.
Fixes #4252.
このコミットの目的は、Goコンパイラ(cmd/gc
)が「役に立たないプライベートシンボル」をエクスポートしないようにすることです。これは、Go言語のIssue #4252を修正するものです。
変更の背景
Go言語では、パッケージ間でシンボル(変数、関数、型など)を共有するためにエクスポートメカニズムを使用します。エクスポートされるシンボルは、その名前が大文字で始まるものに限られます。しかし、コンパイラが内部的に処理するシンボルの中には、外部に公開する必要のないプライベートなものや、Goの組み込み関数や型(len
, true
, false
, nil
など)がユーザーコードによって再宣言された場合に、その再宣言されたシンボルがエクスポートデータに誤って含まれてしまう問題がありました。
Issue #4252は、まさにこの問題、特に組み込みシンボルがユーザーコードで再宣言された際に、コンパイラがそれらを適切に処理せず、エクスポートデータが破損したり、予期せぬ挙動を引き起こしたりする可能性を指摘していました。例えば、あるパッケージがtrue
という名前の変数を定義した場合、それがGoの組み込みのtrue
と混同され、エクスポートデータに不正確な情報が含まれることが考えられます。
このコミットは、このような不要な、あるいは誤ったシンボルのエクスポートを防ぎ、コンパイラが生成するエクスポートデータ(Goのパッケージバイナリ形式である.a
ファイルなどに含まれる)の正確性と効率性を向上させることを目的としています。これにより、コンパイルされたパッケージのサイズが削減され、リンケージ時の問題が回避されます。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。
- Go言語のパッケージとエクスポート: Go言語では、コードはパッケージに分割されます。パッケージ内のシンボルは、その名前が大文字で始まる場合にのみ、他のパッケージから参照可能(エクスポートされる)になります。小文字で始まるシンボルはパッケージプライベートです。
- Goコンパイラ (
cmd/gc
): Go言語の公式コンパイラです。ソースコードを解析し、中間表現を生成し、最終的に実行可能なバイナリやパッケージアーカイブ(.a
ファイル)を生成します。 - シンボルテーブル: コンパイラがプログラム内のすべての識別子(変数名、関数名、型名など)とその属性(型、スコープ、メモリ位置など)を管理するために使用するデータ構造です。
- エクスポートデータ: コンパイラがパッケージをコンパイルする際に生成するメタデータの一部で、そのパッケージが外部に公開するシンボルに関する情報が含まれます。他のパッケージがそのパッケージをインポートする際に、このエクスポートデータを読み込んで、利用可能なシンボルを認識します。
- 組み込み(Builtin)シンボル: Go言語に最初から定義されている関数、型、定数などです(例:
len
,cap
,make
,new
,true
,false
,nil
,int
,string
など)。これらは特別な扱いを受け、通常はどのパッケージからも明示的にインポートすることなく利用できます。 - シンボルの再宣言: Go言語では、組み込みシンボルと同じ名前の変数や関数をユーザーが定義することが可能です。この場合、そのスコープ内ではユーザー定義のシンボルが優先されます。コンパイラは、このような再宣言を正しく処理し、組み込みシンボルとユーザー定義シンボルを区別する必要があります。
Sym
構造体: Goコンパイラの内部でシンボルを表すデータ構造です。これにはシンボルの名前、パッケージ、フラグ(エクスポートされているか、パッケージプライベートかなど)が含まれます。Node
構造体: Goコンパイラの内部で抽象構文木(AST)のノードを表すデータ構造です。シンボルへの参照を含むことがあります。
技術的詳細
このコミットの主要な変更点は、src/cmd/gc/export.c
とsrc/cmd/gc/lex.c
の2つのファイルに集中しています。
src/cmd/gc/export.c
の変更
このファイルは、Goコンパイラがシンボルをエクスポートするロジックを扱います。
packagesym
関数の削除: 以前は、エクスポートされないパッケージローカルなシンボルをマークするためのpackagesym
関数が存在しました。このコミットでは、この関数が削除され、autoexport
関数内の関連する呼び出しも削除されています。これは、シンボルがエクスポートされるかどうかを判断するロジックがより洗練されたため、明示的にパッケージローカルとしてマークする必要がなくなったことを示唆しています。exportedsym
関数の導入: 新たにexportedsym(Sym *sym)
関数が導入されました。この関数は、与えられたシンボルが現在のパッケージをインポートする他のファイルから可視である(エクスポートされる)かどうかを判断します。- 組み込みパッケージ(
builtinpkg
)のシンボルは常にエクスポートされると見なされます。 - 現在のローカルパッケージ(
localpkg
)に属し、かつその名前がエクスポート可能(大文字で始まる)である場合にエクスポートされると判断します。 - この関数は、シンボルが実際にエクスポートされるべきかどうかを判断する中心的なロジックを提供します。
- 組み込みパッケージ(
reexportdep
関数の修正:reexportdep
関数は、エクスポートされるシンボルが依存する他のシンボル(例えば、エクスポートされる型のフィールドの型など)もエクスポートリストに追加する役割を担っています。この関数内の複数の箇所で、以前はn->sym->pkg != localpkg && n->sym->pkg != builtinpkg
のような条件で外部パッケージのシンボルをチェックしていた部分が、新しく導入された!exportedsym(n->sym)
に置き換えられました。- これは、シンボルが外部パッケージのものであるかどうかだけでなく、「そのシンボルがエクスポートされるべきではないプライベートシンボルである」というより一般的な条件で依存関係をチェックするように変更されたことを意味します。
- 特に、ユーザーが組み込みシンボルを再宣言した場合、その再宣言されたシンボルは
localpkg
に属しますが、exportedsym
はそれがエクスポートされるべきではないと判断するため、エクスポートリストに誤って追加されることを防ぎます。
src/cmd/gc/lex.c
の変更
このファイルは、Goコンパイラの字句解析(lexical analysis)とシンボル初期化の段階を扱います。
- 組み込みシンボルの
origpkg
設定:lexfini
関数内で、Goの組み込みシンボル(true
,false
,nil
,byte
,error
,rune
,iota
など)が初期化される際に、それらのSym
構造体のorigpkg
フィールドがbuiltinpkg
に設定されるようになりました。origpkg
は、シンボルが元々どのパッケージで定義されたかを示すフィールドです。- この変更により、ユーザーがこれらの組み込みシンボルと同じ名前のシンボルを再宣言した場合でも、コンパイラは元の組み込みシンボルが
builtinpkg
に属することを正確に認識できるようになります。 - これにより、
exportedsym
関数が組み込みシンボルとユーザー定義の同名シンボルを区別し、エクスポートの判断を正しく行えるようになります。
テストファイルの追加
test/fixedbugs/issue4252.dir/
ディレクトリに、a.go
とmain.go
という2つの新しいテストファイルが追加されました。
a.go
は、true
,false
,nil
,append
,error
,int
,len
といったGoの組み込みシンボルをパッケージa
内で再宣言しています。そして、これらの再宣言されたシンボルが正しく動作することを確認するテストロジックを含んでいます。main.go
は、パッケージa
をインポートし、a.InlinedFakeTrue()
,a.InlinedFakeFalse()
,a.InlinedFakeNil()
といった関数を呼び出しています。これらの関数は、パッケージa
内で再宣言された組み込みシンボルを使用しており、main.go
はそれらが組み込みシンボルではなく、パッケージa
内で定義されたシンボルとして扱われていることを検証します。test/fixedbugs/issue4252.go
は、これらのテストを実行するためのラッパーファイルです。
これらのテストは、このコミットが解決しようとしている問題(組み込みシンボルの再宣言とエクスポートデータの破損)が実際に修正されたことを検証するために非常に重要です。
コアとなるコードの変更箇所
src/cmd/gc/export.c
--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -22,22 +22,8 @@ exportsym(Node *n)
}
n->sym->flags |= SymExport;
-\texportlist = list(exportlist, n);\n-}\n-\n-// Mark n's symbol as package-local\n-static void\n-packagesym(Node *n)\n-{\n-\tif(n == N || n->sym == S)\n-\t\treturn;\n-\tif(n->sym->flags & (SymExport|SymPackage)) {\n-\t\tif(n->sym->flags & SymExport)\n-\t\t\tyyerror("export/package mismatch: %S", n->sym);\n-\t\treturn;\n-\t}\n-\tn->sym->flags |= SymPackage;\n-\n+\tif(debug['E'])\n+\t\tprint("export symbol %S\\n", n->sym);\n \texportlist = list(exportlist, n);\n }\n \n@@ -58,6 +44,18 @@ initname(char *s)\n return strcmp(s, "init") == 0;\n }\n \n+// exportedsym returns whether a symbol will be visible\n+// to files that import our package.\n+static int\n+exportedsym(Sym *sym)\n+{\n+\t// Builtins are visible everywhere.\n+\tif(sym->pkg == builtinpkg || sym->origpkg == builtinpkg)\n+\t\treturn 1;\n+\n+\treturn sym->pkg == localpkg && exportname(sym->name);\n+}\n+\n void\n autoexport(Node *n, int ctxt)\n {\n@@ -69,8 +67,6 @@ autoexport(Node *n, int ctxt)\n return;\n if(exportname(n->sym->name) || initname(n->sym->name))\n exportsym(n);\n-\telse\n-\t\tpackagesym(n);\n }\n \n static void
@@ -104,17 +100,17 @@ reexportdep(Node *n)\n if(!n)\n return;\n \n-// print("reexportdep %+hN\\n", n);\n+\t//print("reexportdep %+hN\\n", n);\n switch(n->op) {\n case ONAME:\n switch(n->class&~PHEAP) {\n case PFUNC:\n // methods will be printed along with their type\n-\t\t\tif(!n->type || n->type->thistuple > 0)\n+\t\t\tif(n->left && n->left->op == OTYPE)\n break;\n // fallthrough\n case PEXTERN:\n-\t\t\tif (n->sym && n->sym->pkg != localpkg && n->sym->pkg != builtinpkg)\n+\t\t\tif(n->sym && !exportedsym(n->sym))\n exportlist = list(exportlist, n);\n }\n break;\n@@ -125,7 +121,7 @@ reexportdep(Node *n)\n if(t != types[t->etype] && t != idealbool && t != idealstring) {\n if(isptr[t->etype])\n t = t->type;\n-\t\t\tif (t && t->sym && t->sym->def && t->sym->pkg != localpkg && t->sym->pkg != builtinpkg) {\n+\t\t\tif(t && t->sym && t->sym->def && !exportedsym(t->sym)) {\n exportlist = list(exportlist, t->sym->def);\n }\n }\n@@ -136,15 +132,19 @@ reexportdep(Node *n)\n if(t != types[n->type->etype] && t != idealbool && t != idealstring) {\n if(isptr[t->etype])\n t = t->type;\n-\t\t\tif (t && t->sym && t->sym->def && t->sym->pkg != localpkg && t->sym->pkg != builtinpkg) {\n-//\t\t\t\tprint("reexport literal type %+hN\\n", t->sym);\n+\t\t\tif(t && t->sym && t->sym->def && !exportedsym(t->sym)) {\n+\t\t\t\tif(debug['E'])\n+\t\t\t\t\tprint("reexport literal type %S\\n", t->sym);\n exportlist = list(exportlist, t->sym->def);\n }\n }\n // fallthrough\n case OTYPE:\n-\t\tif (n->sym && n->sym->pkg != localpkg && n->sym->pkg != builtinpkg)\n+\t\tif(n->sym && !exportedsym(n->sym)) {\n+\t\t\tif(debug['E'])\n+\t\t\t\tprint("reexport literal/type %S\\n", n->sym);\n exportlist = list(exportlist, n);\n+\t\t}\n break;\n
// for operations that need a type when rendered, put the type on the export list.
@@ -158,8 +158,9 @@ reexportdep(Node *n)\n t = n->type;\n if(!t->sym && t->type)\n t = t->type;\n-\t\tif (t && t->sym && t->sym->def && t->sym->pkg != localpkg && t->sym->pkg != builtinpkg) {\n-//\t\t\tprint("reexport convnop %+hN\\n", t->sym->def);\n+\t\tif(t && t->sym && t->sym->def && !exportedsym(t->sym)) {\n+\t\t\tif(debug['E'])\n+\t\t\t\tprint("reexport type for convnop %S\\n", t->sym);\n exportlist = list(exportlist, t->sym->def);\n }\n break;
src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -2016,8 +2016,10 @@ lexfini(void)\n s->lexical = lex;\n
etype = syms[i].etype;\n-\t\tif(etype != Txxx && (etype != TANY || debug['A']) && s->def == N)\n+\t\tif(etype != Txxx && (etype != TANY || debug['A']) && s->def == N) {\n \t\t\ts->def = typenod(types[etype]);\n+\t\t\ts->origpkg = builtinpkg;\n+\t\t}\n
etype = syms[i].op;\n-\t\tif(etype != OXXX && s->def == N) {\n+\t\tif(etype != OXXX && s->def == N) {\n \t\t\ts->def = nod(OXXX, N, N);\n \t\t\ts->def->sym = s;\n \t\t\ts->def->etype = etype;\n+\t\t\ts->origpkg = builtinpkg;\n \t\t}\n \t}\n \n+\t// backend-specific builtin types (e.g. int).\n \tfor(i=0; typedefs[i].name; i++) {\n \t\ts = lookup(typedefs[i].name);\n-\t\tif(s->def == N)\n+\t\tif(s->def == N) {\n \t\t\ts->def = typenod(types[typedefs[i].etype]);\n+\t\t\ts->origpkg = builtinpkg;\n+\t\t}\n \t}\n \n // there's only so much table-driven we can handle.\n // these are special cases.\n \ts = lookup("byte");\n-\tif(s->def == N)\n+\tif(s->def == N) {\n \t\ts->def = typenod(bytetype);\n-\t\n+\t\ts->origpkg = builtinpkg;\n+\t}\n+\n \ts = lookup("error");\n-\tif(s->def == N)\n+\tif(s->def == N) {\n \t\ts->def = typenod(errortype);\n+\t\ts->origpkg = builtinpkg;\n+\t}\n \n \ts = lookup("rune");\n-\tif(s->def == N)\n+\tif(s->def == N) {\n \t\ts->def = typenod(runetype);\n+\t\ts->origpkg = builtinpkg;\n+\t}\n \n \ts = lookup("nil");\n \tif(s->def == N) {\n \t\tv.ctype = CTNIL;\n \t\ts->def = nodlit(v);\n \t\ts->def->sym = s;\n+\t\ts->origpkg = builtinpkg;\n \t}\n-\t\n+\n \ts = lookup("iota");\n \tif(s->def == N) {\n \t\ts->def = nod(OIOTA, N, N);\n \t\ts->def->sym = s;\n+\t\ts->origpkg = builtinpkg;\n \t}\n \n \ts = lookup("true");\n \tif(s->def == N) {\n \t\ts->def = nodbool(1);\n \t\ts->def->sym = s;\n+\t\ts->origpkg = builtinpkg;\n \t}\n \n \ts = lookup("false");\n \tif(s->def == N) {\n \t\ts->def = nodbool(0);\n \t\ts->def->sym = s;\n+\t\ts->origpkg = builtinpkg;\n \t}\n-\t\n+\n \tnodfp = nod(ONAME, N, N);\n \tnodfp->type = types[TINT32];\n \tnodfp->xoffset = 0;
コアとなるコードの解説
src/cmd/gc/export.c
packagesym
の削除: 以前は、パッケージ内部のシンボルを明示的に「パッケージローカル」としてマークするpackagesym
関数がありました。しかし、新しいexportedsym
関数が導入されたことで、シンボルがエクスポートされるべきかどうかをより包括的に判断できるようになり、この明示的なマーキングが不要になりました。これにより、コードの複雑さが軽減されています。exportedsym
の導入: この関数は、シンボルが外部に公開されるべきかどうかを判断する新しい中心的なロジックです。sym->pkg == builtinpkg || sym->origpkg == builtinpkg
: この条件は非常に重要です。builtinpkg
はGoの組み込みシンボルが属する仮想的なパッケージです。sym->pkg
がbuiltinpkg
であるか、またはsym->origpkg
(シンボルが元々定義されたパッケージ)がbuiltinpkg
である場合、そのシンボルは常にエクスポートされると見なされます。これは、組み込みシンボルがどのパッケージからでも利用可能であるというGoの言語仕様を反映しています。sym->pkg == localpkg && exportname(sym->name)
: この条件は、シンボルが現在のローカルパッケージに属し、かつその名前がGoのエクスポート規則(大文字で始まる)に従っている場合にエクスポートされることを意味します。- この新しい関数により、
reexportdep
などの他の関数は、シンボルがエクスポートされるべきかどうかを判断する際に、より正確で一貫したロジックを使用できるようになりました。特に、ユーザーが組み込みシンボルを再宣言した場合、その再宣言されたシンボルはlocalpkg
に属し、かつ名前が小文字で始まるため(例:var true = ...
)、exportname(sym->name)
がfalse
を返し、結果としてexportedsym
もfalse
を返します。これにより、再宣言されたプライベートな組み込みシンボルが誤ってエクスポートデータに含まれることを防ぎます。
reexportdep
の修正:reexportdep
関数は、エクスポートされるシンボルが依存する他のシンボルもエクスポートリストに追加する役割を担っています。この関数内の複数の箇所で、以前のn->sym->pkg != localpkg && n->sym->pkg != builtinpkg
という条件が!exportedsym(n->sym)
に置き換えられました。これは、依存するシンボルが「外部パッケージのシンボルである」という単純な条件から、「エクスポートされるべきではないプライベートシンボルである」というより正確な条件に変わったことを意味します。これにより、エクスポートデータに不要な依存関係が含まれることを防ぎます。
src/cmd/gc/lex.c
- 組み込みシンボルの
origpkg
設定:lexfini
関数は、コンパイラの初期化段階で組み込みシンボルをセットアップします。このコミットでは、true
,false
,nil
,byte
,error
,rune
,iota
などの組み込みシンボルが初期化される際に、そのSym
構造体のorigpkg
フィールドがbuiltinpkg
に明示的に設定されるようになりました。- この変更は、
export.c
のexportedsym
関数と連携して機能します。exportedsym
はsym->origpkg == builtinpkg
をチェックすることで、シンボルが組み込みのものであるかどうかを正確に判断できます。 - これにより、ユーザーが
true
やfalse
といった組み込みシンボルと同じ名前のローカル変数や定数を定義した場合でも、コンパイラはそれらを組み込みシンボルとは異なるものとして扱い、エクスポートデータに誤って含めることを防ぐことができます。これは、Go言語が組み込みシンボルの再宣言を許可しているため、コンパイラがこのセマンティクスを正しく処理するために不可欠な変更です。
- この変更は、
これらの変更により、Goコンパイラはエクスポートされるシンボルをより厳密に管理し、不要なプライベートシンボルや、ユーザーによって再宣言された組み込みシンボルがエクスポートデータに誤って含まれることを防ぎます。これにより、コンパイルされたパッケージの正確性、効率性、および互換性が向上します。
関連リンク
- Go Issue #4252: https://github.com/golang/go/issues/4252
- Go CL 6856126: https://golang.org/cl/6856126
参考にした情報源リンク
- Go言語の公式ドキュメント(パッケージ、エクスポート、組み込み関数などに関するセクション)
- Goコンパイラのソースコード(
src/cmd/gc
ディレクトリ内のファイル) - Go言語のIssueトラッカー(特にIssue #4252の議論)