[インデックス 1282] ファイルの概要
このコミットは、Go言語のコンパイラにおける「空のswitch文」に関するバグ(bug128)を修正するものです。具体的には、switch
文の本体が空であるか、case
ラベルを持たない場合に発生するコンパイルエラーを解消し、より堅牢なコンパイラの挙動を実現しています。
コミット
commit a0a14b98faf07d87a52c13b5ff08547703598972
Author: Ken Thompson <ken@golang.org>
Date: Thu Dec 4 16:05:40 2008 -0800
empty switches -- bug128
R=r
OCL=20520
CL=20522
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a0a14b98faf07d87a52c13b5ff08547703598972
元コミット内容
empty switches -- bug128
R=r
OCL=20520
CL=20522
変更の背景
このコミットは、Go言語の初期開発段階におけるコンパイラのバグ修正の一環として行われました。当時のGoコンパイラ(gc
および6g
)は、switch
文がcase
ラベルを全く持たない、いわゆる「空のswitch文」を適切に処理できないという問題(bug128)を抱えていました。
具体的には、src/cmd/gc/walk.c
内のcasebody
関数が、switch
文の本体にcase
ラベルが存在しない場合にエラー(yyerror("switch statement must have case labels");
)を発生させていました。これは、Go言語の仕様上、switch
文がcase
ラベルを持たないことを許容する場合がある(例えば、switch {}
のような形式)にも関わらず、コンパイラがこれを誤ってエラーとして扱っていたためです。
この修正の背景には、Go言語の文法とコンパイラの挙動をより厳密に一致させ、開発者が意図しないコンパイルエラーに遭遇しないようにするという目的がありました。特に、switch
文は制御フローの重要な要素であり、その挙動が仕様と異なることは、プログラミングの妨げとなるため、早期の修正が求められました。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの基本的な概念とC言語の構文に関する知識が必要です。
Goコンパイラの構造(初期段階)
Go言語の初期のコンパイラは、主にC言語で記述されており、複数のコンポーネントに分かれていました。
gc
(Go Compiler): Go言語のソースコードを解析し、中間表現に変換する主要なコンパイラフロントエンド。構文解析、意味解析、型チェックなどを行います。6g
(Go Compiler for amd64):gc
によって生成された中間表現を受け取り、特定のアーキテクチャ(この場合はamd64)向けの機械語コードを生成するバックエンドコンパイラ。
switch
文の内部表現
Goコンパイラ内部では、ソースコードの各要素が抽象構文木(AST: Abstract Syntax Tree)として表現されます。switch
文もASTのノードとして扱われ、その本体(nbody
)は、case
節やdefault
節のリストとして表現されます。
OEMPTY
ノード
OEMPTY
は、Goコンパイラ内部で使用される特殊なノードタイプの一つで、空のステートメントやブロックを表すために用いられます。例えば、switch {}
のように、switch
文の本体が完全に空である場合に、このOEMPTY
ノードが生成されることがあります。
yyerror
関数
yyerror
は、コンパイラが構文エラーや意味エラーを検出した際に、エラーメッセージを出力するために使用される関数です。通常、この関数が呼び出されると、コンパイルプロセスは中断されます。
setlineno
関数
setlineno
は、コンパイラが現在処理しているソースコードの行番号を設定する関数です。これにより、エラーメッセージやデバッグ情報が正確なソースコードの位置を指し示すことができます。
Node
構造体
Node
は、Goコンパイラ内部でASTの各ノードを表すために使用されるC言語の構造体です。n->op
はノードの操作タイプ(例: OCASE
、OEMPTY
など)を示し、n->nbody
はノードの子ノードのリストを指します。
技術的詳細
このコミットの技術的な核心は、Goコンパイラがswitch
文の本体を処理する方法の変更にあります。
変更前
変更前のsrc/cmd/gc/walk.c
では、switch
文のセマンティックウォーク(意味解析と最適化の前処理)を行うwalkswitch
関数内で、casebody(n->nbody)
という関数呼び出しが行われていました。このcasebody
関数は、switch
文の本体(n->nbody
)がcase
ラベルを一つも持たない場合に、yyerror("switch statement must have case labels");
というエラーを発生させていました。
これは、switch {}
のような空のswitch
文や、case
ラベルを持たないswitch
文がGo言語の文法上許容されるにも関わらず、コンパイラがこれを不正な構文として扱っていたことを意味します。
また、src/cmd/6g/gen.c
のswgen
関数(switch
文のコード生成を担当)では、switch
文の本体をイテレートする際に、OEMPTY
ノードを特別に処理するロジックがありませんでした。これにより、空のswitch
文がコード生成段階で予期せぬ挙動を引き起こす可能性がありました。
変更後
このコミットでは、以下の2つの主要な変更が行われました。
-
src/cmd/gc/walk.c
の変更:walkswitch
関数内のif(!casebody(n->nbody))
という条件分岐が削除され、単にcasebody(n->nbody);
と呼び出す形に変更されました。これにより、casebody
関数がエラーを発生させることなく、switch
文の本体を処理できるようになりました。casebody
関数自体は、switch
文の本体が適切に構成されているか(例えば、case
ラベルが重複していないかなど)をチェックする役割は引き続き担いますが、空のswitch
文をエラーとして扱うことはなくなりました。 -
src/cmd/6g/gen.c
の変更:swgen
関数に、if(c1->op == OEMPTY) break;
という行が追加されました。これは、switch
文の本体を構成するノードを走査するループにおいて、現在のノードがOEMPTY
(空のステートメント)である場合、それ以上処理を続ける必要がないため、ループを即座に終了させることを意味します。これにより、空のswitch
文がコード生成段階で正しく扱われ、不必要な処理やエラーが回避されるようになりました。
これらの変更により、Goコンパイラは「空のswitch文」をGo言語の仕様に沿って正しく処理できるようになり、コンパイルエラーが解消されました。
コアとなるコードの変更箇所
--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -536,6 +536,8 @@ swgen(Node *n)
c1 = listfirst(&save1, &n->nbody);
while(c1 != N) {
setlineno(c1);
+ if(c1->op == OEMPTY)
+ break;
if(c1->op != OCASE) {
if(s0 == C && dflt == P)
yyerror("unreachable statements in a switch");
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -277,9 +277,7 @@ loop:
if(top != Etop)
goto nottop;
- if(!casebody(n->nbody))
- yyerror("switch statement must have case labels");
-
+ casebody(n->nbody);
if(n->ntest == N)
n->ntest = booltrue;
walkstate(n->ninit);
コアとなるコードの解説
src/cmd/6g/gen.c
の変更
setlineno(c1);
+ if(c1->op == OEMPTY)
+ break;
if(c1->op != OCASE) {
この変更は、swgen
関数(switch
文のコード生成ロジック)内で行われています。swgen
関数は、switch
文の本体(n->nbody
)を構成する各ノードをc1
として順に処理します。
追加されたif(c1->op == OEMPTY) break;
は、現在のノードc1
がOEMPTY
(空のステートメントを表す内部ノード)である場合に、ループを即座に終了させることを意味します。これにより、switch {}
のような空のswitch
文が来た際に、それ以上無駄な処理を行わずに、適切にコード生成を完了できるようになります。これは、コンパイラの効率性と正確性を向上させます。
src/cmd/gc/walk.c
の変更
- if(!casebody(n->nbody))
- yyerror("switch statement must have case labels");
-
+ casebody(n->nbody);
この変更は、walkswitch
関数(switch
文の意味解析とウォーク処理)内で行われています。変更前は、casebody(n->nbody)
の戻り値がfalse
(つまり、case
ラベルが見つからなかった場合)であれば、yyerror
を呼び出してコンパイルエラーを発生させていました。
変更後は、if(!casebody(n->nbody))
という条件分岐が削除され、casebody(n->nbody);
が常に呼び出されるようになりました。これにより、casebody
関数がcase
ラベルの有無によってエラーを発生させることはなくなります。casebody
関数は引き続き、switch
文の本体の構造が正しいか(例えば、case
ラベルが重複していないかなど)をチェックする役割を担いますが、空のswitch
文をエラーとして扱うことはなくなりました。これは、Go言語の仕様で空のswitch
文が許容されるようになったことに対応する修正です。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の
switch
文に関する仕様: https://go.dev/ref/spec#Switch_statements
参考にした情報源リンク
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- Go言語の初期のバグトラッカー(bug128に関する情報がある可能性): (当時のGoのバグトラッカーはGoogle Code上にあった可能性があり、現在はアーカイブされているため直接リンクを見つけるのは困難です。しかし、コミットメッセージに
bug128
と明記されていることから、当時の開発プロセスにおいてこの番号で管理されていたことが伺えます。) - Goコンパイラの内部構造に関する一般的な情報源(Goのコンパイラ設計に関する論文やブログ記事など)
- "The Go Programming Language Specification"
- "Go Compiler Internals" (Goのコンパイラに関する書籍やオンラインリソース)
- Goのソースコード自体 (
src/cmd/gc/
,src/cmd/6g/
ディレクトリ内のファイル)
[インデックス 1282] ファイルの概要
このコミットは、Go言語のコンパイラにおける「空のswitch文」に関するバグ(bug128)を修正するものです。具体的には、switch
文の本体が空であるか、case
ラベルを持たない場合に発生するコンパイルエラーを解消し、より堅牢なコンパイラの挙動を実現しています。
コミット
commit a0a14b98faf07d87a52c13b5ff08547703598972
Author: Ken Thompson <ken@golang.org>
Date: Thu Dec 4 16:05:40 2008 -0800
empty switches -- bug128
R=r
OCL=20520
CL=20522
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a0a14b98faf07d87a52c13b5ff08547703598972
元コミット内容
empty switches -- bug128
R=r
OCL=20520
CL=20522
変更の背景
このコミットは、Go言語の初期開発段階におけるコンパイラのバグ修正の一環として行われました。当時のGoコンパイラ(gc
および6g
)は、switch
文がcase
ラベルを全く持たない、いわゆる「空のswitch文」を適切に処理できないという問題(bug128)を抱えていました。
具体的には、src/cmd/gc/walk.c
内のcasebody
関数が、switch
文の本体にcase
ラベルが存在しない場合にエラー(yyerror("switch statement must have case labels");
)を発生させていました。これは、Go言語の仕様上、switch
文がcase
ラベルを持たないことを許容する場合がある(例えば、switch {}
のような形式)にも関わらず、コンパイラがこれを誤ってエラーとして扱っていたためです。
この修正の背景には、Go言語の文法とコンパイラの挙動をより厳密に一致させ、開発者が意図しないコンパイルエラーに遭遇しないようにするという目的がありました。特に、switch
文は制御フローの重要な要素であり、その挙動が仕様と異なることは、プログラミングの妨げとなるため、早期の修正が求められました。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの基本的な概念とC言語の構文に関する知識が必要です。
Goコンパイラの構造(初期段階)
Go言語の初期のコンパイラは、主にC言語で記述されており、複数のコンポーネントに分かれていました。
gc
(Go Compiler): Go言語のソースコードを解析し、中間表現に変換する主要なコンパイラフロントエンド。構文解析、意味解析、型チェックなどを行います。6g
(Go Compiler for amd64):gc
によって生成された中間表現を受け取り、特定のアーキテクチャ(この場合はamd64)向けの機械語コードを生成するバックエンドコンパイラ。
switch
文の内部表現
Goコンパイラ内部では、ソースコードの各要素が抽象構文木(AST: Abstract Syntax Tree)として表現されます。switch
文もASTのノードとして扱われ、その本体(nbody
)は、case
節やdefault
節のリストとして表現されます。
OEMPTY
ノード
OEMPTY
は、Goコンパイラ内部で使用される特殊なノードタイプの一つで、空のステートメントやブロックを表すために用いられます。例えば、switch {}
のように、switch
文の本体が完全に空である場合に、このOEMPTY
ノードが生成されることがあります。
yyerror
関数
yyerror
は、コンパイラが構文エラーや意味エラーを検出した際に、エラーメッセージを出力するために使用される関数です。通常、この関数が呼び出されると、コンパイルプロセスは中断されます。
setlineno
関数
setlineno
は、コンパイラが現在処理しているソースコードの行番号を設定する関数です。これにより、エラーメッセージやデバッグ情報が正確なソースコードの位置を指し示すことができます。
Node
構造体
Node
は、Goコンパイラ内部でASTの各ノードを表すために使用されるC言語の構造体です。n->op
はノードの操作タイプ(例: OCASE
、OEMPTY
など)を示し、n->nbody
はノードの子ノードのリストを指します。
技術的詳細
このコミットの技術的な核心は、Goコンパイラがswitch
文の本体を処理する方法の変更にあります。
変更前
変更前のsrc/cmd/gc/walk.c
では、switch
文のセマンティックウォーク(意味解析と最適化の前処理)を行うwalkswitch
関数内で、casebody(n->nbody)
という関数呼び出しが行われていました。このcasebody
関数は、switch
文の本体(n->nbody
)がcase
ラベルを一つも持たない場合に、yyerror("switch statement must have case labels");
というエラーを発生させていました。
これは、switch {}
のような空のswitch
文や、case
ラベルを持たないswitch
文がGo言語の文法上許容されるにも関わらず、コンパイラがこれを不正な構文として扱っていたことを意味します。
また、src/cmd/6g/gen.c
のswgen
関数(switch
文のコード生成を担当)では、switch
文の本体をイテレートする際に、OEMPTY
ノードを特別に処理するロジックがありませんでした。これにより、空のswitch
文がコード生成段階で予期せぬ挙動を引き起こす可能性がありました。
変更後
このコミットでは、以下の2つの主要な変更が行われました。
-
src/cmd/gc/walk.c
の変更:walkswitch
関数内のif(!casebody(n->nbody))
という条件分岐が削除され、単にcasebody(n->nbody);
と呼び出す形に変更されました。これにより、casebody
関数がエラーを発生させることなく、switch
文の本体を処理できるようになりました。casebody
関数自体は、switch
文の本体が適切に構成されているか(例えば、case
ラベルが重複していないかなど)をチェックする役割は引き続き担いますが、空のswitch
文をエラーとして扱うことはなくなりました。 -
src/cmd/6g/gen.c
の変更:swgen
関数に、if(c1->op == OEMPTY) break;
という行が追加されました。これは、switch
文の本体を構成するノードを走査するループにおいて、現在のノードがOEMPTY
(空のステートメント)である場合、それ以上処理を続ける必要がないため、ループを即座に終了させることを意味します。これにより、空のswitch
文がコード生成段階で正しく扱われ、不必要な処理やエラーが回避されるようになりました。
これらの変更により、Goコンパイラは「空のswitch文」をGo言語の仕様に沿って正しく処理できるようになり、コンパイルエラーが解消されました。
コアとなるコードの変更箇所
--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -536,6 +536,8 @@ swgen(Node *n)
c1 = listfirst(&save1, &n->nbody);
while(c1 != N) {
setlineno(c1);
+ if(c1->op == OEMPTY)
+ break;
if(c1->op != OCASE) {
if(s0 == C && dflt == P)
yyerror("unreachable statements in a switch");
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -277,9 +277,7 @@ loop:
if(top != Etop)
goto nottop;
- if(!casebody(n->nbody))
- yyerror("switch statement must have case labels");
-
+ casebody(n->nbody);
if(n->ntest == N)
n->ntest = booltrue;
walkstate(n->ninit);
コアとなるコードの解説
src/cmd/6g/gen.c
の変更
setlineno(c1);
+ if(c1->op == OEMPTY)
+ break;
if(c1->op != OCASE) {
この変更は、swgen
関数(switch
文のコード生成ロジック)内で行われています。swgen
関数は、switch
文の本体(n->nbody
)を構成する各ノードをc1
として順に処理します。
追加されたif(c1->op == OEMPTY) break;
は、現在のノードc1
がOEMPTY
(空のステートメントを表す内部ノード)である場合に、ループを即座に終了させることを意味します。これにより、switch {}
のような空のswitch
文が来た際に、それ以上無駄な処理を行わずに、適切にコード生成を完了できるようになります。これは、コンパイラの効率性と正確性を向上させます。
src/cmd/gc/walk.c
の変更
- if(!casebody(n->nbody))
- yyerror("switch statement must have case labels");
-
+ casebody(n->nbody);
この変更は、walkswitch
関数(switch
文の意味解析とウォーク処理)内で行われています。変更前は、casebody(n->nbody)
の戻り値がfalse
(つまり、case
ラベルが見つからなかった場合)であれば、yyerror
を呼び出してコンパイルエラーを発生させていました。
変更後は、if(!casebody(n->nbody))
という条件分岐が削除され、casebody(n->nbody);
が常に呼び出されるようになりました。これにより、casebody
関数がcase
ラベルの有無によってエラーを発生させることはなくなります。casebody
関数は引き続き、switch
文の本体の構造が正しいか(例えば、case
ラベルが重複していないかなど)をチェックする役割を担いますが、空のswitch
文をエラーとして扱うことはなくなりました。これは、Go言語の仕様で空のswitch
文が許容されるようになったことに対応する修正です。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の
switch
文に関する仕様: https://go.dev/ref/spec#Switch_statements
参考にした情報源リンク
- Go言語のGitHubリポジトリ: https://github.com/golang/go
- Go言語の初期のバグトラッカー(bug128に関する情報がある可能性): (当時のGoのバグトラッカーはGoogle Code上にあった可能性があり、現在はアーカイブされているため直接リンクを見つけるのは困難です。しかし、コミットメッセージに
bug128
と明記されていることから、当時の開発プロセスにおいてこの番号で管理されていたことが伺えます。) - Goコンパイラの内部構造に関する一般的な情報源(Goのコンパイラ設計に関する論文やブログ記事など)
- "The Go Programming Language Specification"
- "Go Compiler Internals" (Goのコンパイラに関する書籍やオンラインリソース)
- Goのソースコード自体 (
src/cmd/gc/
,src/cmd/6g/
ディレクトリ内のファイル)