[インデックス 18564] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるfallthrough
キーワードの誤った使用を拒否するように修正を加えるものです。具体的には、fallthrough
が異なるcase
ブロック間で意図せず適用されてしまうバグ(Issue 6500)を修正し、コンパイラがより厳密なセマンティックチェックを行うように改善しています。
コミット
commit 96678f9dc026648889830cd058cd34d9e7759426
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Feb 19 07:55:03 2014 +0100
cmd/gc: reject incorrect use of fallthrough.
Fixes #6500.
LGTM=rsc
R=golang-codereviews, rsc
CC=golang-codereviews
https://golang.org/cl/14920053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/96678f9dc026648889830cd058cd34d9e7759426
元コミット内容
cmd/gc: reject incorrect use of fallthrough.
このコミットは、fallthrough
の誤った使用を拒否するようにGoコンパイラを修正します。
Issue 6500を修正します。
変更の背景
Go言語のswitch
ステートメントにおいて、fallthrough
キーワードは、現在のcase
ブロックの実行が完了した後、次のcase
ブロックのコードも実行することを明示的に指示するために使用されます。しかし、Goのswitch
ステートメントは、C言語などとは異なり、デフォルトではcase
ブロックの終わりに暗黙的なbreak
が存在します。そのため、次のcase
ブロックに処理を継続させたい場合は、必ずfallthrough
を記述する必要があります。
このコミットが修正する問題は、fallthrough
が意図しないcase
ブロックに適用されてしまう可能性があったことです。特に、型スイッチ(type switch)のような特定のコンテキストにおいて、fallthrough
が宣言を含むcase
ブロックの境界を越えて適用されると、Goの型システムやスコープ規則に違反する可能性がありました。Issue 6500は、この誤った挙動を報告したもので、コンパイラがこのような不正なfallthrough
の使用を検出し、エラーとして拒否する必要がありました。
前提知識の解説
-
Go言語の
switch
ステートメントとfallthrough
: Goのswitch
ステートメントは、他の多くの言語とは異なり、各case
ブロックの最後に暗黙的なbreak
が挿入されます。つまり、一致したcase
ブロックのコードが実行されると、自動的にswitch
ステートメント全体から抜けます。この挙動を上書きし、次のcase
ブロックのコードも実行したい場合にのみ、fallthrough
キーワードを明示的に使用します。fallthrough
は、その直後のcase
ブロックにのみ処理を移し、複数のcase
ブロックを連続して実行するような「フォールスルー」はサポートしていません。 -
Goコンパイラ (
cmd/gc
):cmd/gc
は、Go言語の公式コンパイラです。Goのソースコードを解析し、抽象構文木(AST)を構築し、型チェック、最適化、最終的な機械語コードの生成を行います。コンパイラのフロントエンドは、字句解析、構文解析(Yacc/Bisonによって生成されたパーサーを使用)、AST構築を担当します。 -
Yacc/Bisonと
go.y
、y.tab.c
: Yacc (Yet Another Compiler Compiler) やそのGNU版であるBisonは、文法定義ファイル(通常は.y
拡張子)からC言語のパーサーコード(通常はy.tab.c
)を生成するツールです。go.y
はGo言語の文法を定義するファイルであり、y.tab.c
はその文法定義から生成されたGoコンパイラのパーサーのC言語ソースコードです。パーサーは、ソースコードのトークン列を解析し、Go言語の文法規則に従ってASTノードを構築します。 -
AST (Abstract Syntax Tree): 抽象構文木は、ソースコードの構造を木構造で表現したものです。コンパイラはASTを操作して、型チェック、最適化、コード生成などの処理を行います。各ノードは、変数宣言、関数呼び出し、演算子、ステートメントなどの言語要素を表します。
-
Node
構造体とxoffset
: Goコンパイラの内部では、ASTの各要素はNode
構造体で表現されます。このNode
構造体には、そのノードの種類(op
)、子ノードへのポインタ、型情報など、様々なメタデータが含まれます。xoffset
は、このコミットで導入された、または既存のフィールドが再利用された可能性のあるフィールドで、特定のコードブロックの識別子として使用されます。これにより、fallthrough
ステートメントがどのcase
ブロックに属しているかを追跡し、その有効性を判断できるようになります。
技術的詳細
このコミットの核心は、fallthrough
ステートメントが、それが属するcase
ブロックのスコープ内で正しく使用されているかを検証するメカニズムを導入することです。
-
xoffset
の導入/利用:go.y
の変更により、caseblock
とnon_dcl_stmt
(fallthrough
ステートメントを含む)のASTノードに、現在のblock
(コードブロックの識別子)をxoffset
フィールドとして関連付けるようになりました。caseblock
が解析される際に、そのブロックの識別子(block
変数)が$1->xoffset
に設定されます。fallthrough
ステートメント(OXFALL
ノード)が生成される際に、そのノードのxoffset
にも現在のblock
が設定されます。
-
swt.c
でのチェックの強化:swt.c
ファイルは、switch
ステートメントのセマンティックチェックと変換を担当しています。以前は、casebody
関数内でlast->op == OXFALL
という条件だけでfallthrough
を検出していました。 このコミットでは、この条件に加えてlast->xoffset == n->xoffset
というチェックが追加されました。last
は現在のcase
ブロックの最後のステートメントを表すノードです。n
は現在のcase
ブロックのノードです。last->xoffset == n->xoffset
という条件は、fallthrough
ステートメントが、現在処理しているcase
ブロックと同じブロックに属していることを保証します。もしfallthrough
が異なるブロック(例えば、型スイッチの異なる型ケース)に属している場合、この条件は偽となり、コンパイラはエラーを報告します。
-
y.tab.c
の更新:go.y
の変更に伴い、Yaccによって生成されるy.tab.c
も更新されています。これは、go.y
で追加されたxoffset
への代入処理が、生成されたパーサーコードに反映されるためです。行番号の変更も、go.y
の変更がy.tab.c
に伝播した結果です。
この修正により、コンパイラは、fallthrough
が宣言を含むcase
ブロックの境界を越えて使用されるような、Go言語のセマンティクスに反するコードを正確に識別し、コンパイル時にエラーとして報告できるようになりました。これにより、開発者はより安全で意図通りのコードを書くことが強制されます。
コアとなるコードの変更箇所
src/cmd/gc/go.y
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -557,6 +557,7 @@ caseblock:
// This is so that the stmt_list action doesn't look at
// the case tokens if the stmt_list is empty.
yylast = yychar;
+ $1->xoffset = block;
}
stmt_list
{
@@ -1730,6 +1731,7 @@ non_dcl_stmt:
// will be converted to OFALL
$$ = nod(OXFALL, N, N);
+ $$->xoffset = block;
}
| LBREAK onew_name
{
src/cmd/gc/swt.c
--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -317,7 +317,7 @@ casebody(Node *sw, Node *typeswvar)
// botch - shouldn't fall thru declaration
last = stat->end->n;
- if(last->op == OXFALL) {
+ if(last->xoffset == n->xoffset && last->op == OXFALL) {
if(typeswvar) {
setlineno(last);
yyerror("cannot fallthrough in type switch");
src/cmd/gc/y.tab.c
go.y
の変更に伴い、生成されたパーサーコードであるy.tab.c
の関連する部分が更新されています。特に、yyreduce
関数内のcase 62
とcase 263
(go.y
のcaseblock
とnon_dcl_stmt
に対応)において、xoffset
への代入が追加されています。また、全体的な行番号の調整も行われています。
test/fixedbugs/issue6500.go
// errcheck
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test that fallthrough cannot be used to skip variable declarations.
package main
func main() {
switch 0 {
case 0:
fallthrough
case 1: // ERROR "fallthrough statement cannot be used to skip variable declaration"
var x int
_ = x
}
switch interface{}(nil) {
case nil:
fallthrough
case interface{}(nil): // ERROR "fallthrough statement cannot be used to skip variable declaration"
var x int
_ = x
}
switch 0 {
case 0:
fallthrough
case 1: // ERROR "fallthrough statement cannot be used to skip variable declaration"
const x = 0
_ = x
}
switch 0 {
case 0:
fallthrough
case 1: // ERROR "fallthrough statement cannot be used to skip variable declaration"
type X int
var x X
_ = x
}
}
コアとなるコードの解説
-
go.y
の変更:caseblock
の定義において、$1->xoffset = block;
が追加されました。これは、case
ブロックのASTノード($1
)に、そのブロックが属する現在のスコープ(block
変数で識別される)を記録することを意味します。non_dcl_stmt
(非宣言ステートメント)の一部として定義されているfallthrough
ステートメントの定義において、$$->xoffset = block;
が追加されました。これは、fallthrough
ステートメントのASTノード($$
)にも、それが記述された時点のスコープを記録することを意味します。 これらの変更により、コンパイラはfallthrough
ステートメントと、それが属するcase
ブロックが、同じ論理的なスコープ内にあるかどうかを後で確認できるようになります。
-
swt.c
の変更:casebody
関数内で、fallthrough
ステートメントのチェック条件がif(last->op == OXFALL)
からif(last->xoffset == n->xoffset && last->op == OXFALL)
に変更されました。last->op == OXFALL
は、case
ブロックの最後のステートメントがfallthrough
であるかを確認します。last->xoffset == n->xoffset
が追加された新しい条件です。last
はfallthrough
ノード、n
は現在のcase
ブロックのノードです。この条件は、fallthrough
ステートメントが、現在処理しているcase
ブロックと同じスコープ(xoffset
で識別される)に属していることを厳密にチェックします。- もし
fallthrough
が異なるスコープに属している場合(例えば、型スイッチで異なる型ケースにフォールスルーしようとした場合など)、この条件は偽となり、yyerror("cannot fallthrough in type switch");
のようなエラーメッセージが生成されます。これにより、宣言をスキップするような不正なfallthrough
の使用がコンパイル時に検出され、拒否されるようになります。
-
issue6500.go
テストケース: このテストケースは、fallthrough
が変数宣言、定数宣言、型宣言をスキップしようとする様々なシナリオを網羅しています。これらのシナリオは、Goの言語仕様では許可されていません。このコミットの修正が正しく機能していれば、これらのテストケースはすべてERROR
コメントで示されたコンパイルエラーを発生させるはずです。これにより、修正が意図通りに機能していることが検証されます。
これらの変更は、Goコンパイラがfallthrough
のセマンティクスをより厳密に強制し、開発者がGoの言語規則に準拠したコードを書くことを保証するために不可欠です。
関連リンク
- Go Issue 6500:
cmd/gc: fallthrough should not skip variable declarations
- https://github.com/golang/go/issues/6500 - Go Code Review 14920053:
cmd/gc: reject incorrect use of fallthrough.
- https://golang.org/cl/14920053
参考にした情報源リンク
- Go言語仕様: Switch statements - https://go.dev/ref/spec#Switch_statements
- Yacc/Bisonのドキュメント (一般的な情報源)
- Goコンパイラのソースコード (特に
src/cmd/gc
ディレクトリ) - Go言語のASTに関する一般的な情報