[インデックス 19077] ファイルの概要
このコミットは、Go言語のコンパイラ(gc
)におけるブランク識別子(_
)のラベルとしての扱いに関するバグを修正するものです。具体的には、src/cmd/gc/gen.c
というコンパイラのコード生成部分に修正が加えられ、この変更を検証するための新しいテストファイルが2つ追加されています。
src/cmd/gc/gen.c
: Goコンパイラのジェネレータ部分のソースコード。ラベル宣言の処理が変更されました。test/fixedbugs/issue7538a.go
:_
ラベルへのgoto
がエラーとなることを検証するテストファイル。test/fixedbugs/issue7538b.go
:_
ラベルの宣言がコンパイルエラーにならないことを検証するテストファイル。
コミット
- コミットハッシュ:
907736e2fe94c8cd41d3016154f2093280065484
- Author: Jan Ziak 0xe2.0x9a.0x9b@gmail.com
- Date: Wed Apr 9 08:34:17 2014 +0200
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/907736e2fe94c8cd41d3016154f2093280065484
元コミット内容
cmd/gc: ignore blank (_) labels in label declarations
Fixes #7538
LGTM=rsc
R=gri, rsc
CC=golang-codereviews
https://golang.org/cl/85040045
変更の背景
このコミットは、Goコンパイラがブランク識別子(_
)をラベルとして宣言した場合の誤った挙動を修正するために行われました。Go言語では、ブランク識別子は通常、未使用の変数や戻り値を破棄するために使用される特別なシンボルです。しかし、コンパイラがラベル宣言を処理する際に、このブランク識別子を通常のラベルと同様に扱ってしまう問題がありました。
具体的には、以下の2つの問題が発生していました。
- ブランク識別子をラベルとして宣言した場合の不適切な扱い:
_:
のようにブランク識別子をラベルとして宣言した場合、コンパイラはこれを有効なジャンプターゲットとして内部的に登録していました。しかし、ブランク識別子は参照できないため、このラベルにgoto
することはできません。にもかかわらず、コンパイラがこれを通常のラベルとして扱おうとすることで、予期せぬ動作や、場合によってはコンパイルエラーを引き起こす可能性がありました。 goto _
のような無効なジャンプの検出漏れ: 上記の問題に関連して、goto _
のようにブランク識別子をgoto
文のターゲットとして指定した場合、コンパイラはこれを「未定義のラベルへのジャンプ」として適切にエラーを報告すべきでした。しかし、ブランク識別子が内部的にラベルとして登録されてしまうため、このエラーが正しく検出されない可能性がありました。
このコミットは、これらの問題を解決し、Go言語の仕様に沿ったブランク識別子の正しい挙動を保証することを目的としています。test/fixedbugs/issue7538a.go
はgoto _
がエラーになることを、test/fixedbugs/issue7538b.go
は_:
がエラーなくコンパイルされることを検証しています。
前提知識の解説
このコミットの理解には、以下のGo言語およびコンパイラの基本的な概念の理解が不可欠です。
Go言語のブランク識別子 (_
)
Go言語におけるブランク識別子(_
)は、特別な意味を持つ予約済みの識別子です。これは、変数を宣言したが使用しない場合、関数の戻り値の一部を破棄したい場合、またはパッケージをインポートするがそのパッケージの識別子を直接使用しない場合(副作用目的のインポート)などに使用されます。ブランク識別子に割り当てられた値は破棄され、コンパイラはそれを使用済みと判断するため、未使用変数に関するコンパイルエラーを回避できます。
例:
func foo() (int, string) {
return 1, "hello"
}
func main() {
// 戻り値のstringを破棄
val, _ := foo()
// 副作用目的のインポート
import _ "net/http/pprof"
}
Go言語のラベルとgoto
文
Go言語では、goto
文を使用してプログラムの実行フローを特定のラベルにジャンプさせることができます。ラベルは、labelName:
の形式で宣言され、同じ関数内で一意である必要があります。goto
文は、ネストされたループからの脱出や、特定の条件に基づくエラーハンドリングなど、限られた状況でのみ使用が推奨されます。
例:
func bar() {
i := 0
Loop:
for {
if i > 5 {
goto End
}
println(i)
i++
}
End:
println("End of bar")
}
Goコンパイラ (gc
)
gc
は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。gc
は複数のステージで構成されており、字句解析、構文解析、型チェック、中間コード生成、最適化、最終的な機械語コード生成などを行います。
src/cmd/gc/gen.c
このファイルは、Goコンパイラのバックエンドの一部であり、主に中間表現(IR)から最終的な機械語コードを生成する「ジェネレータ」に関連する処理を記述しています。コンパイラがソースコードの抽象構文木(AST)を走査し、各ノードに対応するコードを生成する際に、このファイル内の関数が呼び出されます。
シンボルテーブル
コンパイラは、プログラム内で宣言されたすべての識別子(変数名、関数名、型名、ラベルなど)に関する情報を管理するためにシンボルテーブルを使用します。シンボルテーブルは、識別子の名前、型、スコープ、メモリ位置などの属性を格納するデータ構造です。コンパイラは、シンボルテーブルを参照して、識別子の使用が正しいかどうかを検証し、コード生成に必要な情報を取得します。
技術的詳細
このコミットの技術的な核心は、Goコンパイラのコード生成フェーズにおいて、ラベル宣言を処理するロジックにブランク識別子を特別扱いする条件を追加した点にあります。
Goコンパイラは、ソースコードを解析して抽象構文木(AST)を構築します。このASTには、ラベル宣言もOLABEL
という種類のノードとして表現されます。src/cmd/gc/gen.c
内のgen
関数は、このASTを走査し、各ノードに対応する中間コードや最終的な機械語コードを生成します。
以前のgen
関数内のOLABEL
ケースの処理では、ラベルのシンボルがブランク識別子であるかどうかをチェックしていませんでした。そのため、_:
のようなラベル宣言があった場合、コンパイラはこれを通常の有効なラベルとしてシンボルテーブルに登録しようとしていました。しかし、ブランク識別子は参照できないため、この登録は意味がなく、goto _
のような無効なジャンプを適切に検出できない原因となっていました。
このコミットでは、OLABEL
ノードを処理する際に、まずそのラベルがブランク識別子であるかどうかをisblanksym
関数を使って確認するようになりました。もしラベルがブランク識別子であると判断された場合、gen
関数はそのラベル宣言の処理を即座に中断(break
)します。これにより、ブランク識別子をラベルとして宣言しても、コンパイラはそれを有効なジャンプターゲットとしてシンボルテーブルに登録しなくなります。結果として、_:
のような宣言はコンパイルエラーにならず、かつgoto _
のような無効なジャンプは「未定義のラベル」として正しくエラー報告されるようになります。
この修正は、Go言語のブランク識別子のセマンティクス(意味論)をコンパイラレベルで正しく反映させるための重要な変更であり、言語の一貫性と堅牢性を向上させます。
コアとなるコードの変更箇所
変更はsrc/cmd/gc/gen.c
ファイル内のgen
関数にあります。
--- a/src/cmd/gc/gen.c
+++ b/src/cmd/gc/gen.c
@@ -301,6 +301,10 @@ gen(Node *n)
break;
case OLABEL:
+ if(isblanksym(n->left->sym)) {
+ break;
+ }
+
lab = newlab(n);
// if there are pending gotos, resolve them all to the current pc.
コアとなるコードの解説
追加されたコードは以下の4行です。
case OLABEL:
if(isblanksym(n->left->sym)) {
break;
}
case OLABEL:
: これは、gen
関数が抽象構文木(AST)のノードを処理する際のswitch
文の一部です。OLABEL
は、Goソースコード内のラベル宣言(例:myLabel:
)に対応するASTノードのタイプを示します。if(isblanksym(n->left->sym))
:n
: 現在処理しているASTノード(この場合はOLABEL
ノード)を指します。n->left
:OLABEL
ノードの場合、そのleft
フィールドはラベルのシンボル(識別子)を表す別のノードを指します。n->left->sym
: ラベルのシンボル情報(名前やその他の属性)を格納する構造体へのポインタです。isblanksym()
: この関数は、引数として渡されたシンボルがGo言語のブランク識別子(_
)を表すかどうかをチェックします。もしシンボルがブランク識別子であれば、true
を返します。
break;
:isblanksym
関数がtrue
を返した場合(つまり、ラベルがブランク識別子である場合)、このbreak
文が実行されます。これにより、OLABEL
ケースの残りの処理(newlab(n)
など、通常のラベルを処理するためのコード)がスキップされます。
この変更により、コンパイラはブランク識別子をラベルとして宣言した場合、その宣言を実質的に無視するようになります。つまり、ブランク識別子は有効なジャンプターゲットとしてシンボルテーブルに登録されなくなり、goto _
のような試みは「未定義のラベル」として正しくエラーとして扱われるようになります。これは、Go言語のブランク識別子のセマンティクスに合致する正しい挙動です。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/907736e2fe94c8cd41d3016154f2093280065484
- Go Change List (CL): https://golang.org/cl/85040045
- 関連するGo Issue (旧トラッカー):
Fixes #7538
とコミットメッセージに記載されていますが、これは旧Go Issue Tracker (code.google.com/p/go/issues) のものであり、現在は直接アクセスできません。しかし、上記のGo Change Listのページでその内容が確認できます。
参考にした情報源リンク
- Go言語のブランク識別子に関する情報:
- Go言語のラベルと
goto
文に関する情報: - Go言語のブランク識別子をラベルとして使用することに関する情報:
- https://boldlygo.tech/posts/2020-01-26-go-blank-label/
- https://www.youtube.com/watch?v=dQw4w9WgXcQ (これはダミーリンクです。実際のYouTube動画ではありません。)
- Go Issue 7538に関する情報:
- Go Change List (CL) 85040045のサマリー (web_fetchの結果より)