Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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つの問題が発生していました。

  1. ブランク識別子をラベルとして宣言した場合の不適切な扱い: _: のようにブランク識別子をラベルとして宣言した場合、コンパイラはこれを有効なジャンプターゲットとして内部的に登録していました。しかし、ブランク識別子は参照できないため、このラベルにgotoすることはできません。にもかかわらず、コンパイラがこれを通常のラベルとして扱おうとすることで、予期せぬ動作や、場合によってはコンパイルエラーを引き起こす可能性がありました。
  2. goto _のような無効なジャンプの検出漏れ: 上記の問題に関連して、goto _のようにブランク識別子をgoto文のターゲットとして指定した場合、コンパイラはこれを「未定義のラベルへのジャンプ」として適切にエラーを報告すべきでした。しかし、ブランク識別子が内部的にラベルとして登録されてしまうため、このエラーが正しく検出されない可能性がありました。

このコミットは、これらの問題を解決し、Go言語の仕様に沿ったブランク識別子の正しい挙動を保証することを目的としています。test/fixedbugs/issue7538a.gogoto _がエラーになることを、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言語のブランク識別子のセマンティクスに合致する正しい挙動です。

関連リンク

参考にした情報源リンク