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

[インデックス 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.yy.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ブロックのスコープ内で正しく使用されているかを検証するメカニズムを導入することです。

  1. xoffsetの導入/利用: go.yの変更により、caseblocknon_dcl_stmtfallthroughステートメントを含む)のASTノードに、現在のblock(コードブロックの識別子)をxoffsetフィールドとして関連付けるようになりました。

    • caseblockが解析される際に、そのブロックの識別子(block変数)が$1->xoffsetに設定されます。
    • fallthroughステートメント(OXFALLノード)が生成される際に、そのノードのxoffsetにも現在のblockが設定されます。
  2. 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が異なるブロック(例えば、型スイッチの異なる型ケース)に属している場合、この条件は偽となり、コンパイラはエラーを報告します。
  3. 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 62case 263go.ycaseblocknon_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
	}
}

コアとなるコードの解説

  1. go.yの変更:

    • caseblockの定義において、$1->xoffset = block;が追加されました。これは、caseブロックのASTノード($1)に、そのブロックが属する現在のスコープ(block変数で識別される)を記録することを意味します。
    • non_dcl_stmt(非宣言ステートメント)の一部として定義されているfallthroughステートメントの定義において、$$->xoffset = block;が追加されました。これは、fallthroughステートメントのASTノード($$)にも、それが記述された時点のスコープを記録することを意味します。 これらの変更により、コンパイラはfallthroughステートメントと、それが属するcaseブロックが、同じ論理的なスコープ内にあるかどうかを後で確認できるようになります。
  2. 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が追加された新しい条件です。lastfallthroughノード、nは現在のcaseブロックのノードです。この条件は、fallthroughステートメントが、現在処理しているcaseブロックと同じスコープ(xoffsetで識別される)に属していることを厳密にチェックします。
      • もしfallthroughが異なるスコープに属している場合(例えば、型スイッチで異なる型ケースにフォールスルーしようとした場合など)、この条件は偽となり、yyerror("cannot fallthrough in type switch");のようなエラーメッセージが生成されます。これにより、宣言をスキップするような不正なfallthroughの使用がコンパイル時に検出され、拒否されるようになります。
  3. issue6500.goテストケース: このテストケースは、fallthroughが変数宣言、定数宣言、型宣言をスキップしようとする様々なシナリオを網羅しています。これらのシナリオは、Goの言語仕様では許可されていません。このコミットの修正が正しく機能していれば、これらのテストケースはすべてERRORコメントで示されたコンパイルエラーを発生させるはずです。これにより、修正が意図通りに機能していることが検証されます。

これらの変更は、Goコンパイラがfallthroughのセマンティクスをより厳密に強制し、開発者がGoの言語規則に準拠したコードを書くことを保証するために不可欠です。

関連リンク

参考にした情報源リンク

  • Go言語仕様: Switch statements - https://go.dev/ref/spec#Switch_statements
  • Yacc/Bisonのドキュメント (一般的な情報源)
  • Goコンパイラのソースコード (特にsrc/cmd/gcディレクトリ)
  • Go言語のASTに関する一般的な情報