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

[インデックス 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はノードの操作タイプ(例: OCASEOEMPTYなど)を示し、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.cswgen関数(switch文のコード生成を担当)では、switch文の本体をイテレートする際に、OEMPTYノードを特別に処理するロジックがありませんでした。これにより、空のswitch文がコード生成段階で予期せぬ挙動を引き起こす可能性がありました。

変更後

このコミットでは、以下の2つの主要な変更が行われました。

  1. src/cmd/gc/walk.cの変更: walkswitch関数内のif(!casebody(n->nbody))という条件分岐が削除され、単にcasebody(n->nbody);と呼び出す形に変更されました。これにより、casebody関数がエラーを発生させることなく、switch文の本体を処理できるようになりました。casebody関数自体は、switch文の本体が適切に構成されているか(例えば、caseラベルが重複していないかなど)をチェックする役割は引き続き担いますが、空のswitch文をエラーとして扱うことはなくなりました。

  2. 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;は、現在のノードc1OEMPTY(空のステートメントを表す内部ノード)である場合に、ループを即座に終了させることを意味します。これにより、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言語の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はノードの操作タイプ(例: OCASEOEMPTYなど)を示し、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.cswgen関数(switch文のコード生成を担当)では、switch文の本体をイテレートする際に、OEMPTYノードを特別に処理するロジックがありませんでした。これにより、空のswitch文がコード生成段階で予期せぬ挙動を引き起こす可能性がありました。

変更後

このコミットでは、以下の2つの主要な変更が行われました。

  1. src/cmd/gc/walk.cの変更: walkswitch関数内のif(!casebody(n->nbody))という条件分岐が削除され、単にcasebody(n->nbody);と呼び出す形に変更されました。これにより、casebody関数がエラーを発生させることなく、switch文の本体を処理できるようになりました。casebody関数自体は、switch文の本体が適切に構成されているか(例えば、caseラベルが重複していないかなど)をチェックする役割は引き続き担いますが、空のswitch文をエラーとして扱うことはなくなりました。

  2. 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;は、現在のノードc1OEMPTY(空のステートメントを表す内部ノード)である場合に、ループを即座に終了させることを意味します。これにより、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言語の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/ ディレクトリ内のファイル)