[インデックス 1952] ファイルの概要
このコミットは、Goコンパイラ(gc)における型宣言の処理に関するバグ修正です。具体的には、type n t; のような型宣言において、t の型情報が n に不適切にコピーされたり、t 自体が意図せず変更されたりする問題を解決しています。これにより、型のシグネチャ印刷キューの状態や、基底型にメソッドが誤って関連付けられるといった不整合が修正されました。
コミット
commit 07687705a4c2db718a3601d3558807833938dfbf
Author: Russ Cox <rsc@golang.org>
Date: Thu Apr 2 21:38:11 2009 -0700
type n t;
was copying a bit too much about t into n,
like whether the signature was queued to be printed.
(bug reported by anton)
was also editing t, meaning you could do
type T int;
func (p int) Meth() { }
both fixed.
R=ken
OCL=27052
CL=27052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/07687705a4c2db718a3601d3558807833938dfbf
元コミット内容
src/cmd/gc/dcl.c | 14 ++++++++++++--
src/cmd/gc/obj.c | 1 +\
src/cmd/gc/subr.c | 2 +-\
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index 51c76be752..2d83d9f9fd 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -91,6 +91,7 @@ void
updatetype(Type *n, Type *t)
{
Sym *s;
+ int local;
s = n->sym;
if(s == S || s->otype != n)
@@ -118,10 +119,19 @@ updatetype(Type *n, Type *t)
fatal("updatetype %T / %T", n, t);
}
- if(n->local)
- t->local = 1;
+ // decl was
+ // type n t;
+ // copy t, but then zero out state associated with t
+ // that is no longer associated with n.
+ local = n->local;
*n = *t;
n->sym = s;
+ n->local = local;
+ n->siggen = 0;
+ n->methptr = 0;
+ n->printed = 0;
+ n->method = nil;
+ n->vargen = 0;
// catch declaration of incomplete type
switch(n->etype) {
diff --git a/src/cmd/gc/obj.c b/src/cmd/gc/obj.c
index 7c80ee22a8..9c0b6edb66 100644
--- a/src/cmd/gc/obj.c
+++ b/src/cmd/gc/obj.c
@@ -425,6 +425,7 @@ dumpsignatures(void)
t = d->dtype;
et = t->etype;
s = signame(t);
+//print("signame %S for %T\n", s, t);
if(s == S)
continue;
diff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c
index fe5d33084e..fb8a1744a4 100644
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -1637,8 +1637,8 @@ signame(Type *t)
ss->oname->class = PEXTERN;
}
+//print("siggen %T %d\n", t, t->siggen);
if(!t->siggen) {
-//print("siggen %T\n", t);\n \t// special case: don\'t generate the empty interface
\t\tif(strcmp(buf, \"empty\") == 0)\
\t\t\tgoto out;\
変更の背景
このコミットは、Goコンパイラ(gc)における型システム処理の初期段階で発見された2つの関連するバグを修正するために行われました。
- 不適切な型情報コピー:
type n t;のような型宣言(nが新しい型、tが既存の型)において、tの内部状態(例えば、そのシグネチャが印刷キューに入っているかどうかを示すフラグsiggen)がnに誤ってコピーされていました。これは、nがtとは独立した新しい型であるにもかかわらず、tの一時的なコンパイラ内部状態まで引き継いでしまうという問題を引き起こしました。 - 基底型の意図しない変更: さらに深刻な問題として、
updatetype関数がtの内容をnにコピーする際に、t自体のlocalフラグをn->localの値で上書きしていました。これにより、type T int; func (p int) Meth() { }のようなコードが書かれた場合、int型(tに相当)にMethメソッドが誤って関連付けられてしまう可能性がありました。これは、Goの型システムにおいて、基底型に直接メソッドを追加することはできないという重要な原則に反する動作です。
これらのバグは、コンパイラの健全性を損ない、予期せぬ動作やコンパイルエラーを引き起こす可能性がありました。特に、anton によって報告されたバグは、シグネチャの印刷に関する問題を示唆しています。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの概念とGo言語の基本的な型システムに関する知識が必要です。
- Goコンパイラ (
gc): Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担います。src/cmd/gcディレクトリにそのソースコードがあります。 - 型システム: Go言語は静的型付け言語であり、すべての変数と式には型があります。型は、値がどのような種類のデータであるか、どのような操作が可能であるかを定義します。
- 型宣言 (
type): Goではtype NewType OldTypeのように既存の型から新しい型を宣言できます。この新しい型は、基底型とは異なる独自のメソッドセットを持つことができます。 - メソッド: Goでは、型にメソッドを関連付けることができます。
func (receiver Type) MethodName(args) { ... }の形式で定義されます。 Type構造体: Goコンパイラの内部では、Go言語の各型はTypeというC言語の構造体で表現されます。この構造体には、型の種類(整数、文字列、構造体など)、サイズ、アラインメント、そしてメソッドやシグネチャに関する情報など、型に関するあらゆるメタデータが含まれています。Sym構造体: シンボルテーブルのエントリを表す構造体で、変数名、関数名、型名などの識別子とそれに関連する情報(型、スコープなど)を管理します。updatetype関数:src/cmd/gc/dcl.cにあるこの関数は、型宣言type n t;の際に、新しい型nのType構造体を既存の型tの情報で更新する役割を担います。siggen(Signature Generation Flag):Type構造体内のフラグの一つで、その型のシグネチャ(例えば、関数の引数と戻り値の型リスト)が既に生成され、コンパイラの内部で処理されたかどうかを示すために使用されます。これは、重複する処理を避けるための最適化や、特定の型のシグネチャを一度だけ印刷するために利用されます。methptr(Method Pointer):Type構造体内のフィールドで、その型に関連付けられたメソッドのリストへのポインタを保持します。printed:Type構造体内のフラグで、その型が既にコンパイラの出力(例えば、デバッグ情報や型定義の出力)で印刷されたかどうかを示します。method:Type構造体内のフィールドで、その型に直接関連付けられたメソッドのリストを保持します。vargen:Type構造体内のフラグで、変数の生成に関連する状態を示します。
技術的詳細
このコミットの核心は、src/cmd/gc/dcl.c 内の updatetype 関数の修正にあります。この関数は、type n t; という形式の型宣言を処理する際に呼び出されます。
元の実装では、*n = *t; という行で t の Type 構造体の内容が n にビット単位でコピーされていました。これは、t のすべてのフィールドが n にそのままコピーされることを意味します。このコピーには、siggen、methptr、printed、method、vargen といった、コンパイラの内部状態や一時的なフラグも含まれていました。
問題は、n が t とは独立した新しい型であるにもかかわらず、t の一時的な状態(例えば、t のシグネチャが既に印刷キューに入っているという情報)まで n が引き継いでしまうことでした。これは、n のシグネチャが適切に処理されない、あるいは重複して処理されるといった不整合を引き起こす可能性がありました。
さらに、if(n->local) t->local = 1; という行は、n がローカル型である場合に t の local フラグを 1 に設定していました。これは、t がグローバルな型であるにもかかわらず、n のローカル性によって t がローカル型として扱われるという、基底型への意図しない副作用を引き起こしました。この副作用が、type T int; func (p int) Meth() { } のようなコードで int 型にメソッドが誤って関連付けられるというバグの原因でした。Goでは、基底型に直接メソッドを追加することはできません。メソッドは、その型を基底型とする新しい名前付き型にのみ関連付けられます。
修正では、以下の変更が加えられました。
localフラグの適切な処理:*n = *t;の前にn->localの値をlocal変数に一時的に保存し、*n = *t;でコピーした後、n->local = local;で元のnのlocal状態を復元するようにしました。これにより、tのlocalフラグが意図せず変更されることを防ぎます。- コンパイラ内部状態のクリア:
*n = *t;でtの内容をnにコピーした後、n->siggen = 0; n->methptr = 0; n->printed = 0; n->method = nil; n->vargen = 0;といった行が追加されました。これにより、nがtからコピーされた一時的なコンパイラ内部状態(シグネチャ生成フラグ、メソッドポインタ、印刷済みフラグなど)をリセットし、nが新しい独立した型として適切に初期化されるようにします。特にn->method = nil;は、基底型tに関連付けられたメソッドがnに誤って引き継がれることを防ぎます。
src/cmd/gc/obj.c と src/cmd/gc/subr.c の変更は、デバッグ用の print ステートメントのコメントアウトであり、直接的なバグ修正というよりは、デバッグ中に使用されたコードのクリーンアップと考えられます。特に signame 関数は型のシグネチャ名を生成する役割を担っており、siggen フラグと密接に関連しています。
コアとなるコードの変更箇所
src/cmd/gc/dcl.c
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -91,6 +91,7 @@ void
updatetype(Type *n, Type *t)
{
Sym *s;
+ int local; // 追加
s = n->sym;
if(s == S || s->otype != n)
@@ -118,10 +119,19 @@ updatetype(Type *n, Type *t)
fatal("updatetype %T / %T", n, t);
}
- if(n->local)
- t->local = 1;
+ // decl was
+ // type n t;
+ // copy t, but then zero out state associated with t
+ // that is no longer associated with n.
+ local = n->local; // nのlocalフラグを一時保存
*n = *t;
n->sym = s;
+ n->local = local; // nのlocalフラグを復元
+ n->siggen = 0; // siggenフラグをリセット
+ n->methptr = 0; // methptrをリセット
+ n->printed = 0; // printedフラグをリセット
+ n->method = nil; // methodリストをnilに設定
+ n->vargen = 0; // vargenフラグをリセット
// catch declaration of incomplete type
switch(n->etype) {
src/cmd/gc/obj.c
--- a/src/cmd/gc/obj.c
+++ b/src/cmd/gc/obj.c
@@ -425,6 +425,7 @@ dumpsignatures(void)
t = d->dtype;
et = t->etype;
s = signame(t);
+//print("signame %S for %T\n", s, t); // コメントアウト
if(s == S)
continue;
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -1637,8 +1637,8 @@ signame(Type *t)
ss->oname->class = PEXTERN;
}
+//print("siggen %T %d\n", t, t->siggen); // コメントアウト
if(!t->siggen) {
-//print("siggen %T\n", t); // コメントアウト
// special case: don't generate the empty interface
if(strcmp(buf, "empty") == 0)
goto out;
コアとなるコードの解説
src/cmd/gc/dcl.c の updatetype 関数がこのコミットの主要な変更点です。
-
local変数の導入と復元:- 変更前:
if(n->local) t->local = 1;は、新しい型nがローカルスコープで宣言された場合、基底型tのlocalフラグまで変更していました。これは、intのような組み込み型が意図せずローカル型として扱われる可能性を生み出し、その結果、intにメソッドが関連付けられるという誤った動作につながりました。 - 変更後:
int local;を導入し、local = n->local;でnの元のlocal状態を保存します。*n = *t;でtの内容がnにコピーされた後、n->local = local;でnのlocalフラグを元の状態に戻します。これにより、基底型tのlocalフラグが変更されることを防ぎ、nが自身のローカル性を持つことを保証します。
- 変更前:
-
コンパイラ内部状態のリセット:
- 変更前:
*n = *t;はtのすべてのフィールドをnにコピーしていました。これには、siggen、methptr、printed、method、vargenといった、コンパイラが型の処理中に一時的に使用する内部状態やフラグも含まれていました。これらのフラグは、型のシグネチャが既に生成されたか、メソッドが既に処理されたか、型が既に印刷されたかなどを示します。新しい型nはtとは独立してこれらの処理を受けるべきであるため、これらの状態がコピーされるのは不適切でした。 - 変更後:
n->siggen = 0; n->methptr = 0; n->printed = 0; n->method = nil; n->vargen = 0;の行が追加されました。これは、*n = *t;によるコピーの後、nのこれらの内部状態フラグを明示的にゼロ(またはnil)にリセットします。これにより、nは「未処理」の新しい型として扱われ、コンパイラはnのシグネチャ生成やメソッド処理を最初から適切に行うことができます。特にn->method = nil;は、基底型tに関連付けられたメソッドが新しい型nに誤って引き継がれることを確実に防ぎます。
- 変更前:
src/cmd/gc/obj.c と src/cmd/gc/subr.c の変更は、デバッグ中に一時的に追加された print ステートメントをコメントアウトしたものです。これらはバグ修正の直接的な原因ではありませんが、修正作業中にデバッグのために使用され、その後クリーンアップされたものと考えられます。
このコミットは、Goコンパイラの型システムが、型宣言のセマンティクス(特に新しい名前付き型が基底型から独立した存在であること)を正確に反映するようにするための重要なステップでした。
関連リンク
- Go言語の型システムに関する公式ドキュメント(当時のものを見つけるのは難しいですが、現在のドキュメントも参考になります)
- Goコンパイラのソースコード(
src/cmd/gc)
参考にした情報源リンク
- Go言語の公式ドキュメント (現在のもの): https://go.dev/doc/
- Goコンパイラのソースコード (GitHub): https://github.com/golang/go/tree/master/src/cmd/compile (当時の
gcは現在のcompileに相当します) - Go言語の型システムに関する一般的な情報 (例: A Tour of Go - Types): https://go.dev/tour/basics/11
- Go言語のメソッドに関する一般的な情報 (例: A Tour of Go - Methods): https://go.dev/tour/methods/1
- Go言語の歴史に関する情報 (Goの初期の設計思想を理解するのに役立つ場合があります)