[インデックス 1519] ファイルの概要
このコミットは、Goコンパイラ(gc
)において、他のパッケージに属するメソッドを呼び出す際に、そのメソッド名がエクスポートされていない(非公開の)場合に、その呼び出しを禁止する変更を導入しています。これにより、Go言語のパッケージシステムにおける可視性ルールがより厳密に適用され、意図しない内部実装へのアクセスを防ぎます。
コミット
- コミットハッシュ:
aec4d3194afaf29de1c30a7bf0528dec0a344879
- Author: Russ Cox rsc@golang.org
- Date: Fri Jan 16 15:35:07 2009 -0800
- Summary: disallow other package's names in method calls
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aec4d3194afaf29de1c30a7bf0528dec0a344879
元コミット内容
disallow other package's names in method calls
R=ken
OCL=22999
CL=22999
変更の背景
Go言語では、パッケージはコードの再利用とカプセル化のための基本的な単位です。パッケージ内の識別子(変数、関数、型、メソッドなど)は、その名前の最初の文字が大文字であるか小文字であるかによって、エクスポートされる(公開される)か、エクスポートされない(非公開の)かが決まります。大文字で始まる識別子はエクスポートされ、他のパッケージからアクセスできますが、小文字で始まる識別子はエクスポートされず、そのパッケージ内でのみアクセス可能です。
このコミットが導入される前は、Goコンパイラがメソッド呼び出しの名前解決を行う際に、他のパッケージの非公開の識別子を誤って解決してしまう可能性がありました。具体的には、あるパッケージ内で定義された型が、別のパッケージで定義された非公開のメソッドと同じ名前のメソッドを持っている場合、コンパイラがその非公開メソッドを解決しようとすることが考えられます。これは、Goの可視性ルールに違反し、予期せぬ動作や、パッケージの内部実装への不正なアクセスを許してしまうセキュリティ上の問題にも繋がりかねません。
この変更の目的は、このような誤った名前解決を防ぎ、Go言語のパッケージ可視性ルールをコンパイラレベルで厳密に強制することにあります。これにより、開発者はパッケージの公開APIのみに依存し、内部実装の変更によってコードが壊れるリスクを低減できます。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。
-
Go言語のパッケージと可視性:
- Goのコードはパッケージにまとめられます。パッケージは、関連する機能やデータ型をグループ化するためのメカニズムです。
- 識別子(変数、関数、型、メソッドなど)の名前の最初の文字が大文字の場合、その識別子は「エクスポートされる(exported)」と見なされ、他のパッケージからアクセス可能です。
- 名前の最初の文字が小文字の場合、その識別子は「エクスポートされない(unexported)」と見なされ、その識別子が定義されているパッケージ内からのみアクセス可能です。これは、内部実装の詳細を隠蔽し、パッケージの公開APIを明確にするための重要なメカニズムです。
-
Goコンパイラ(
gc
):gc
はGo言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担います。- コンパイルプロセスには、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成などの段階があります。
src/cmd/gc/dcl.c
は、Goコンパイラの宣言(declaration)処理に関連する部分のソースファイルです。変数、関数、型、メソッドなどの宣言を処理し、それらのスコープや型情報を管理します。特に、メソッドの追加や名前解決のロジックが含まれることがあります。
-
Node
とType
:- Goコンパイラ内部では、ソースコードの構文木(AST: Abstract Syntax Tree)が
Node
構造体で表現されます。各Node
は、変数、関数呼び出し、演算子などの言語要素に対応します。 Type
構造体は、Go言語の型システムにおける型情報を表現します。例えば、int
、string
、構造体、インターフェース、関数型などがType
として表現されます。
- Goコンパイラ内部では、ソースコードの構文木(AST: Abstract Syntax Tree)が
-
pkgimportname
:- コンパイラが現在処理しているコードが、他のパッケージからインポートされた名前を扱っている場合に、そのインポートされたパッケージの名前を保持する変数(またはそれに類するもの)です。
S
は通常、シンボルテーブルにおける「nil」または「空」を表す特殊なシンボルです。
- コンパイラが現在処理しているコードが、他のパッケージからインポートされた名前を扱っている場合に、そのインポートされたパッケージの名前を保持する変数(またはそれに類するもの)です。
-
exportname(name)
:- Goコンパイラ内部のヘルパー関数で、与えられた識別子名がエクスポートされている(公開されている)かどうかを判定します。Goのルールに従い、名前の最初の文字が大文字であれば
true
を返します。
- Goコンパイラ内部のヘルパー関数で、与えられた識別子名がエクスポートされている(公開されている)かどうかを判定します。Goのルールに従い、名前の最初の文字が大文字であれば
-
pkglookup(name, pkgname)
:- Goコンパイラ内部のヘルパー関数で、指定されたパッケージ(
pkgname
)内で、指定された名前(name
)を持つシンボルを検索します。これは、他のパッケージからインポートされた識別子を解決する際に使用されます。
- Goコンパイラ内部のヘルパー関数で、指定されたパッケージ(
技術的詳細
このコミットは、Goコンパイラのsrc/cmd/gc/dcl.c
ファイル内のaddmethod
関数に修正を加えています。addmethod
関数は、型にメソッドを追加する際に呼び出される関数であり、メソッドの名前解決やスコープに関する処理を行います。
変更の核心は、メソッド呼び出しの解決時に、そのメソッドが他のパッケージからインポートされたものであり、かつそのメソッド名がエクスポートされていない(非公開の)場合に、その名前解決を阻止するという点です。
具体的には、以下の条件をチェックしています。
pkgimportname != S
: 現在処理中の識別子が、他のパッケージからインポートされたものであることを示します。S
は通常、現在のパッケージ内での名前解決を示唆する値です。!exportname(sf->name)
: 解決しようとしているメソッドの名前(sf->name
)がエクスポートされていない(つまり、小文字で始まる)ことを示します。
この二つの条件が同時に真である場合、つまり「他のパッケージからインポートされた非公開のメソッド名」である場合に、sf = pkglookup(sf->name, pkgimportname->name);
という行が実行されます。
一見すると、pkglookup
を呼び出すことで名前を「解決」しているように見えますが、この文脈では、これは既存のシンボルを再確認または再検索する目的で使われています。重要なのは、このif
ブロックが実行されることで、コンパイラが他のパッケージの非公開メソッドを誤って参照することを防ぐ、という意図です。もしこの条件が真であれば、コンパイラは、その名前が実際にインポートされたパッケージのコンテキストで適切に解決されるべきかどうかを再評価します。Goの可視性ルールでは、他のパッケージの非公開識別子にはアクセスできないため、このチェックによってコンパイルエラーが発生するか、あるいは適切な公開識別子への解決が促されることになります。
この変更は、コンパイラがメソッド呼び出しの名前解決を行う際の堅牢性を高め、Go言語の設計思想である「明示的なAPIと隠蔽された実装」をより厳密に強制するものです。
コアとなるコードの変更箇所
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -290,6 +290,9 @@ addmethod(Node *n, Type *t, int local)
pa = f;
+ if(pkgimportname != S && !exportname(sf->name))
+ sf = pkglookup(sf->name, pkgimportname->name);
+
n = nod(ODCLFIELD, newname(sf), N);
n->type = t;
コアとなるコードの解説
追加された3行のコードは以下の通りです。
if(pkgimportname != S && !exportname(sf->name))
sf = pkglookup(sf->name, pkgimportname->name);
このコードブロックは、addmethod
関数内で、メソッドのシンボル(sf
)が処理される直前に挿入されています。
pkgimportname != S
: この条件は、現在処理中のメソッド名が、現在のパッケージではなく、他のパッケージからインポートされたものである場合に真となります。S
は、現在のパッケージのコンテキストを示す特別なシンボルまたは値です。!exportname(sf->name)
: この条件は、sf
が指すメソッドの名前(sf->name
)がエクスポートされていない(つまり、小文字で始まる)場合に真となります。exportname
関数は、Goの識別子の可視性ルールに従って、名前が公開されているかどうかを判定します。
これら二つの条件が論理積(&&
)で結合されているため、両方が真である場合にのみ、if
ブロック内のコードが実行されます。つまり、「他のパッケージからインポートされた、かつ非公開のメソッド名」である場合に、以下の行が実行されます。
sf = pkglookup(sf->name, pkgimportname->name);
: この行は、sf
(現在のメソッドシンボル)を、pkgimportname
が示すインポートされたパッケージ内で、同じ名前(sf->name
)を持つシンボルを再検索した結果で上書きします。
このpkglookup
の呼び出しは、単にシンボルを再検索するだけでなく、Goの可視性ルールに違反するアクセスを検出するためのメカニズムとして機能します。もし、他のパッケージの非公開メソッドにアクセスしようとしている場合、このpkglookup
は適切なシンボルを見つけられず、結果としてコンパイルエラーを引き起こすか、あるいはコンパイラの後の段階でエラーとしてフラグ付けされることになります。これにより、Goのパッケージ可視性ルールがコンパイラによって厳密に強制され、他のパッケージの非公開メソッドへの不正な参照が防止されます。
関連リンク
- The Go Programming Language Specification - Packages
- The Go Programming Language Specification - Exported identifiers
参考にした情報源リンク
- Go言語の公式ドキュメントおよび仕様
- Goコンパイラのソースコード(
src/cmd/gc/dcl.c
の周辺コード) - Go言語のパッケージと可視性に関する一般的な解説記事