[インデックス 10742] ファイルの概要
このコミットは、Goコンパイラのgc
(Go Compiler)部分にあるfmt.c
ファイルに対する小規模な修正を含んでいます。主な目的は、シンボルが不正な状態("garbled state")にある場合にエラーメッセージを出力する際のクラッシュを防ぐこと、およびOCOMPLIT
(複合リテラル)をエクスポートモードで正しくレンダリングすることです。これにより、コンパイラの堅牢性とエクスポートされる情報の正確性が向上します。
コミット
commit 7cf4825425be6098b95a313ebe8008ea59c23611
Author: Luuk van Dijk <lvd@golang.org>
Date: Tue Dec 13 09:15:46 2011 +0100
gc: small fixes to fmt.c
don't crash when printing error messages about symbols in a garbled state.
render OCOMPLIT in export mode.
R=rsc
CC=golang-dev
https://golang.org/cl/5466045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7cf4825425be6098b95a313ebe8008ea59c23611
元コミット内容
gc: small fixes to fmt.c
don't crash when printing error messages about symbols in a garbled state.
render OCOMPLIT in export mode.
R=rsc
CC=golang-dev
https://golang.org/cl/5466045
変更の背景
このコミットは、Goコンパイラの安定性と機能性を向上させるために行われました。具体的には、以下の2つの主要な問題に対処しています。
- シンボルが不正な状態でのクラッシュ防止: コンパイラが内部的にシンボル情報を処理する際、何らかの理由でシンボルが「garbled state」(破損または不完全な状態)になることがありました。このような状況で、コンパイラがそのシンボルに関するエラーメッセージを出力しようとすると、不正なメモリ参照などが発生し、コンパイラ自体がクラッシュする可能性がありました。この修正は、このようなクラッシュを防ぎ、より堅牢なエラー報告メカニズムを提供することを目的としています。
OCOMPLIT
のエクスポートモードでの正しいレンダリング:OCOMPLIT
はGo言語の「複合リテラル」(例:[]int{1, 2, 3}
やstruct{X int}{X: 1}
)を表す抽象構文木(AST)ノードです。コンパイラが型情報などをエクスポートする際、これらの複合リテラルが正しく表現されない場合がありました。特に、エクスポートモード(FExp
)では、単に「composite literal」という汎用的な文字列として出力されることがあり、エクスポートされた情報が不完全になる問題がありました。この修正により、エクスポートされる情報がより詳細で正確になります。
これらの修正は、コンパイラのデバッグ能力と、他のツール(リンカなど)がコンパイラから受け取る情報の品質を向上させる上で重要です。
前提知識の解説
このコミットを理解するためには、Goコンパイラの内部構造と、特にgc
(Go Compiler)の役割に関する基本的な知識が必要です。
- Goコンパイラ (
gc
): Go言語の公式コンパイラは、通常gc
と呼ばれます。これは、Goソースコードを機械語に変換する主要なツールです。gc
は、字句解析、構文解析、型チェック、最適化、コード生成など、複数のフェーズで動作します。 fmt.c
:gc
のソースコードの一部であり、主にコンパイラの内部データ構造(ASTノード、シンボル、型など)を人間が読める形式や、他のコンパイラフェーズやツールが利用できる形式に「フォーマット」(整形)する役割を担っています。デバッグ出力、エラーメッセージ、エクスポートされる型情報などの生成に関わります。Sym
(Symbol): コンパイラが扱う識別子(変数名、関数名、型名など)を表す内部データ構造です。各シンボルは、その名前、所属するパッケージ、型などの情報を持っています。Node
(AST Node): 抽象構文木(AST)の各ノードを表す内部データ構造です。Goのソースコードは、構文解析フェーズでASTに変換され、コンパイラの各フェーズはこのASTを操作します。Node
は、式、文、宣言など、プログラムの様々な要素を表します。OCOMPLIT
:Node
の種類の一つで、Go言語の複合リテラル(Composite Literal)を表します。例えば、[]int{1, 2, 3}
やmap[string]int{"a": 1}
、struct{X int}{X: 1}
などがこれに該当します。OTARRAY
:Node
の種類の一つで、配列型を表します。例えば、[]int
(スライス)や[5]string
(固定長配列)などです。FExp
(Export Mode Formatting):fmt.c
内で使用されるフォーマットモードの一つで、コンパイラが型情報などを他のコンパイルユニットやリンカにエクスポートする際に適用されるモードです。このモードでは、人間が読むためだけでなく、機械が解析しやすい形式で情報が出力される必要があります。builtinpkg
: Go言語の組み込み型や関数が定義されている特別なパッケージです。例えば、int
,string
,len
,make
などがこれに属します。
技術的詳細
このコミットは、fmt.c
内のいくつかのフォーマット関数に焦点を当てています。
-
symfmt
関数の修正:symfmt
はシンボル(Sym
構造体)をフォーマットする関数です。case FExp:
ブロックは、エクスポートモードでのシンボルのフォーマットを扱います。- 変更前は、
@\"%Z\".%s
という形式でパッケージパスとシンボル名を出力していましたが、これはbuiltinpkg
(組み込みパッケージ)のシンボルに対しても適用されていました。組み込みパッケージのシンボルは、通常、明確なパッケージパスを持たないか、エクスポート時にそのパスが不要な場合があります。 - 追加された
if(s->pkg != builtinpkg)
チェックにより、組み込みパッケージのシンボルに対しては、このパッケージパスを含む形式での出力がスキップされるようになりました。これにより、不必要な情報が出力されたり、場合によってはbuiltinpkg
のシンボルが「garbled state」にある場合にクラッシュする可能性が低減されます。
-
exprfmt
関数のOTARRAY
ケースの修正:exprfmt
はASTノード(Node
構造体)をフォーマットする関数です。case OTARRAY:
ブロックは、配列型ノードのフォーマットを扱います。- 変更前は、
[]%N
という形式でn->left
(配列の要素型)のみを出力していました。しかし、型チェックが完了する前など、特定のコンパイラフェーズではn->left
がまだ設定されていない(NULL
である)場合があります。 - 追加された
if(n->left)
チェックと、n->right
へのフォールバック(return fmtprint(f, "[]%N", n->right); // happens before typecheck
)により、n->left
がNULL
の場合でもn->right
(これは通常、型チェック前のASTノードで一時的に使用される)を使用してフォーマットを試みるようになりました。これにより、型チェック前の段階で配列型をフォーマットしようとした際のクラッシュが防止されます。
-
exprfmt
関数のOCOMPLIT
ケースの修正:case OCOMPLIT:
ブロックは、複合リテラルノードのフォーマットを扱います。- 変更前は、
fmtstrcpy(f, "composite literal")
と、常に汎用的な文字列を出力していました。これは、特にエクスポートモード(FExp
)において、複合リテラルの詳細な型や要素情報が失われることを意味していました。 - 追加された
if(fmtmode == FErr)
チェックにより、エラーモード(FErr
)の場合のみ、以前の汎用的な文字列を出力するようになりました。 - それ以外のモード(特にエクスポートモード)では、
return fmtprint(f, "%N{ %,H }", n->right, n->list);
という形式で、複合リテラルの実際の型(n->right
)と要素リスト(n->list
)を詳細にフォーマットして出力するようになりました。これにより、エクスポートされる情報がより正確で有用になります。
-
Sconv
関数の修正:Sconv
は、シンボル名を文字列に変換するユーティリティ関数です。- 変更前は、
if(s->name[0] == '_' && s->name[1] == '\0')
という条件で、シンボル名が_
であるかをチェックしていました。しかし、s->name
がNULL
である可能性が考慮されていませんでした。シンボルが「garbled state」にある場合、s->name
がNULL
になることがあり、その状態でs->name[0]
にアクセスしようとすると、NULLポインタデリファレンスによるクラッシュが発生します。 - 追加された
s->name &&
チェックにより、s->name
がNULL
でないことを確認してから、その内容にアクセスするようになりました。これにより、不正なシンボル名によるクラッシュが防止されます。
これらの修正は、Goコンパイラの内部的な堅牢性を高め、特にデバッグやエクスポートのシナリオにおいて、より正確で有用な情報を提供することを目的としています。
コアとなるコードの変更箇所
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -511,7 +511,8 @@ symfmt(Fmt *fp, Sym *s)
return fmtprint(fp, "%s.%s", s->pkg->name, s->name); // dcommontype, typehash
return fmtprint(fp, "%s.%s", s->pkg->prefix, s->name); // (methodsym), typesym, weaksym
case FExp:
- return fmtprint(fp, "@\"%Z\".%s", s->pkg->path, s->name);
+ if(s->pkg != builtinpkg)
+ return fmtprint(fp, "@\"%Z\".%s", s->pkg->path, s->name);
}
}
@@ -1073,7 +1074,9 @@ exprfmt(Fmt *f, Node *n, int prec)
return fmtprint(f, "%T", n->type);
case OTARRAY:
- return fmtprint(f, "[]%N", n->left);
+ if(n->left)
+ return fmtprint(f, "[]%N", n->left);
+ return fmtprint(f, "[]%N", n->right); // happens before typecheck
case OTPAREN:
return fmtprint(f, "(%N)", n->left);
@@ -1109,7 +1112,9 @@ exprfmt(Fmt *f, Node *n, int prec)
return fmtprint(f, "%T { %H }", n->type, n->nbody);
case OCOMPLIT:
- return fmtstrcpy(f, "composite literal");
+ if(fmtmode == FErr)
+ return fmtstrcpy(f, "composite literal");
+ return fmtprint(f, "%N{ %,H }", n->right, n->list);
case OPTRLIT:
return fmtprint(f, "&%N", n->left);
@@ -1401,7 +1406,7 @@ Sconv(Fmt *fp)
if(s == S)
return fmtstrcpy(fp, "<S>");
- if(s->name[0] == '_' && s->name[1] == '\0')
+ if(s->name && s->name[0] == '_' && s->name[1] == '\0')
return fmtstrcpy(fp, "_");
sf = fp->flags;
コアとなるコードの解説
上記の差分は、src/cmd/gc/fmt.c
ファイル内の4つの異なる箇所に対する修正を示しています。
-
symfmt
関数内、FExp
ケースの変更:- 変更前:
return fmtprint(fp, "@\"%Z\".%s", s->pkg->path, s->name);
- 変更後:
if(s->pkg != builtinpkg) return fmtprint(fp, "@\"%Z\".%s", s->pkg->path, s->name);
- 解説: シンボルをエクスポートモード(
FExp
)でフォーマットする際に、そのシンボルがbuiltinpkg
(Goの組み込みパッケージ)に属していない場合にのみ、パッケージパスを含む形式で出力するように変更されました。これにより、組み込みシンボルに対して不適切なパッケージパスが出力されることを防ぎ、また、builtinpkg
のシンボルが不正な状態にある場合に発生しうるクラッシュを回避します。
- 変更前:
-
exprfmt
関数内、OTARRAY
ケースの変更:- 変更前:
return fmtprint(f, "[]%N", n->left);
- 変更後:
if(n->left) return fmtprint(f, "[]%N", n->left); return fmtprint(f, "[]%N", n->right); // happens before typecheck
- 解説: 配列型ノード(
OTARRAY
)をフォーマットする際に、まずn->left
(配列の要素型)が存在するかどうかを確認します。もしn->left
がNULL
でなければ、通常通り[]%N
の形式でn->left
をフォーマットします。しかし、n->left
がNULL
の場合(例えば、型チェック前の段階)、代わりにn->right
を使用してフォーマットを試みます。これは、コンパイラの初期段階でASTノードが完全に構築されていない場合でも、クラッシュせずにフォーマットを続行できるようにするための堅牢性向上策です。
- 変更前:
-
exprfmt
関数内、OCOMPLIT
ケースの変更:- 変更前:
return fmtstrcpy(f, "composite literal");
- 変更後:
if(fmtmode == FErr) return fmtstrcpy(f, "composite literal"); return fmtprint(f, "%N{ %,H }", n->right, n->list);
- 解説: 複合リテラルノード(
OCOMPLIT
)をフォーマットする際に、フォーマットモードがエラーモード(FErr
)である場合にのみ、汎用的な文字列「composite literal」を出力するように変更されました。それ以外のモード(特にエクスポートモード)では、%N{ %,H }
という形式で、複合リテラルの実際の型(n->right
)と要素のリスト(n->list
)を詳細にフォーマットして出力するようになりました。これにより、エクスポートされる情報がより具体的で有用になります。
- 変更前:
-
Sconv
関数内、シンボル名チェックの変更:- 変更前:
if(s->name[0] == '_' && s->name[1] == '\0')
- 変更後:
if(s->name && s->name[0] == '_' && s->name[1] == '\0')
- 解説: シンボル名が
_
であるかをチェックする条件に、s->name
がNULL
でないことの確認(s->name &&
)が追加されました。これにより、s->name
がNULL
である場合にs->name[0]
にアクセスしようとして発生するNULLポインタデリファレンスによるクラッシュを防ぎます。これは、シンボルが「garbled state」にある場合に特に重要です。
- 変更前:
これらの変更は、Goコンパイラの内部的な安定性を高め、特にエラー報告と型情報のエクスポートの正確性を向上させることに貢献しています。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/5466045
参考にした情報源リンク
- N/A (提供されたコミット情報とGoコンパイラの一般的な知識に基づいて解説を生成しました。)