[インデックス 14715] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるswitch
文の式評価時に発生するエラーの行番号が誤って報告されるバグを修正します。具体的には、switch
文内でパニックが発生した際に、本来エラーが発生した行ではなく、switch
文の別の行が報告されてしまう問題を解決します。この修正により、開発者はより正確なエラー情報を得られるようになります。
コミット
commit 407d0c5ab732c8a5ac003c41c294590d824b1eff
Author: Russ Cox <rsc@golang.org>
Date: Sat Dec 22 10:01:15 2012 -0500
cmd/gc: fix error line in switch expr eval
Fixes #4562.
R=ken2
CC=golang-dev
https://golang.org/cl/7008044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/407d0c5ab732c8a5ac003c41c294590d824b1eff
元コミット内容
cmd/gc: fix error line in switch expr eval
このコミットは、Goコンパイラのcmd/gc
部分における、switch
文の式評価時のエラー行番号の誤りを修正します。
Fixes #4562.
GoのIssue #4562を修正します。
変更の背景
この変更の背景には、Goコンパイラがswitch
文の式評価中に発生するランタイムエラー(例えば、nilポインタのデリファレンスなど)に対して、誤ったソースコードの行番号を報告するというバグが存在していました。
具体的には、test/fixedbugs/issue4562.go
で示されているように、switch pT.val {
のようなコードでpT
がnil
の場合、pT.val
の評価時にパニックが発生します。しかし、コンパイラが生成するデバッグ情報やエラー報告において、このパニックが実際に発生した行(switch pT.val
の行)ではなく、switch
文内の別のcase
節の行が報告されてしまう問題がありました。
これは、コンパイラがコードを処理する際に、現在の行番号を示す内部状態が適切に更新されていない、または誤ったタイミングで上書きされていることが原因と考えられます。開発者にとって、エラーが実際に発生した正確な行番号が報告されないことは、デバッグ作業を著しく困難にするため、この問題は修正される必要がありました。
前提知識の解説
Goコンパイラ (cmd/gc
)
Go言語の公式コンパイラは、主にGo言語で書かれていますが、その初期のフロントエンド部分はC言語で書かれていました。このコミットが対象としているsrc/cmd/gc/swt.c
は、GoコンパイラのC言語部分であり、switch
文の構文解析と中間コード生成を担当していました。
switch
文
Go言語のswitch
文は、他の言語と同様に、与えられた式の値に基づいて複数のコードブロックの中から一つを実行するための制御構造です。Goのswitch
文は非常に柔軟で、式を省略してif-else if
のように使用したり、型アサーションと組み合わせて型スイッチとして使用したりすることもできます。
setlineno
関数
Goコンパイラの内部では、ソースコードの現在の行番号を追跡するためのメカニズムが存在します。setlineno
関数は、コンパイラがソースコードの特定の行を処理していることを示すために、内部的な行番号カウンタを更新する役割を担っていました。この行番号情報は、エラーメッセージやデバッグ情報に利用されます。コンパイラがコードを走査する際に、この関数を適切なタイミングで呼び出すことで、正確なエラー位置を報告することが可能になります。
エラー報告とデバッグ情報
コンパイラやランタイムシステムは、プログラムの実行中にエラーが発生した場合、そのエラーの種類と発生場所(ファイル名と行番号)を報告します。この情報は、開発者がバグを特定し修正するために不可欠です。行番号が誤っていると、エラーメッセージが誤解を招き、デバッグの効率が大幅に低下します。
技術的詳細
この修正は、Goコンパイラのsrc/cmd/gc/swt.c
ファイル内のcasebody
関数とwalkswitch
関数における行番号の設定ロジックに焦点を当てています。
casebody
関数は、switch
文の各case
節の本体を処理する役割を担っています。元のコードでは、casebody
関数の冒頭でlno = setlineno(sw);
が呼び出されていましたが、その直後にif(sw->list == nil) return;
というチェックがありました。この順序だと、switch
文にcase
節がない場合にsetlineno
が不必要に呼び出され、その後の処理で正しい行番号が設定されない可能性がありました。
また、casebody
内のループで各case
節を処理する際、lno = setlineno(n);
という行がありました。ここでsetlineno(n)
の戻り値がlno
というローカル変数に代入されていますが、このlno
変数はその後使用されていませんでした。これは、setlineno
がグローバルな行番号状態を更新する目的で呼び出されているにもかかわらず、その戻り値をローカル変数に格納していることが、意図しない行番号の不整合を引き起こす可能性を示唆しています。
walkswitch
関数は、switch
文全体の走査と変換を担当します。この関数には直接的な変更はありませんが、casebody
関数との連携において行番号のコンテキストが重要になります。
このバグの根本原因は、コンパイラがswitch
文の構造を処理する際に、現在のソースコードの行番号を示す内部状態(おそらくグローバルな変数)が、特定のコードパスで適切に更新されない、または誤ったタイミングで上書きされてしまうことにありました。特に、switch
式の評価中にパニックが発生した場合、その時点での正確な行番号がランタイムに伝達されず、代わりにswitch
文の別の部分(例えば、最初のcase
節の行など)の行番号が報告されてしまうという問題でした。
コアとなるコードの変更箇所
変更はsrc/cmd/gc/swt.c
ファイルに集中しています。
--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -259,10 +259,11 @@ casebody(Node *sw, Node *typeswvar)
Node *go, *br;
int32 lno, needvar;
- lno = setlineno(sw);
if(sw->list == nil)
return;
+ lno = setlineno(sw);
+
cas = nil; // cases
stat = nil; // statements
def = N; // defaults
@@ -270,7 +271,7 @@ casebody(Node *sw, Node *typeswvar)
for(l=sw->list; l; l=l->next) {
n = l->n;
- lno = setlineno(n);
+ setlineno(n);
if(n->op != OXCASE)
fatal("casebody %O", n->op);
n->op = OCASE;
@@ -793,7 +794,6 @@ typeswitch(Node *sw)
void
walkswitch(Node *sw)
{
-
/*
* reorder the body into (OLIST, cases, statements)
* cases have OGOTO into statements.
また、この修正を検証するための新しいテストケースが追加されています。
--- /dev/null
+++ b/test/fixedbugs/issue4562.go
@@ -0,0 +1,49 @@
+// run
+
+// Copyright 2012 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.
+
+package main
+
+import (
+ "fmt"
+ "runtime"
+ "strings"
+)
+
+type T struct {
+ val int
+}
+
+func main() {
+ defer expectError(22)
+ var pT *T
+ switch pT.val { // error should be here - line 22
+ case 0:
+ fmt.Println("0")
+ case 1: // used to show up here instead
+ fmt.Println("1")
+ case 2:
+ fmt.Println("2")
+ }
+ fmt.Println("finished")
+}
+
+func expectError(expectLine int) {
+ if recover() == nil {
+ panic("did not crash")
+ }
+ for i := 1;; i++ {
+ _, file, line, ok := runtime.Caller(i)
+ if !ok {
+ panic("cannot find issue4562.go on stack")
+ }
+ if strings.HasSuffix(file, "issue4562.go") {
+ if line != expectLine {
+ panic(fmt.Sprintf("crashed at line %d, wanted line %d", line, expectLine))
+ }
+ break
+ }
+ }
+}
コアとなるコードの解説
src/cmd/gc/swt.c
における変更点は以下の通りです。
-
casebody
関数内のsetlineno(sw)
の移動:- lno = setlineno(sw); if(sw->list == nil) return; + lno = setlineno(sw);
この変更では、
lno = setlineno(sw);
の行が、if(sw->list == nil) return;
のチェックの後に移動されました。- 修正前:
switch
文にcase
節がない場合(sw->list == nil
)、setlineno(sw)
が呼び出された直後にreturn
していました。これにより、switch
文全体の行番号が適切に設定されない、または一時的に設定された行番号がすぐに破棄される可能性がありました。 - 修正後:
switch
文にcase
節が存在する場合にのみsetlineno(sw)
が呼び出されるようになりました。これにより、switch
文の処理が実際に開始される前に、そのswitch
文自体の正確な行番号がコンパイラの内部状態に設定されることが保証されます。これは、switch
式の評価中に発生するエラーの行番号を正確にする上で重要です。
- 修正前:
-
casebody
関数内のsetlineno(n)
の変更:- lno = setlineno(n); + setlineno(n);
この変更では、
lno =
という代入が削除され、単にsetlineno(n);
が呼び出されるようになりました。- 修正前: 各
case
節(n
)の行番号をsetlineno
で設定し、その戻り値をローカル変数lno
に代入していました。しかし、このlno
変数はその後使用されていませんでした。これは、setlineno
がグローバルな行番号状態を更新する副作用を持つ関数であるにもかかわらず、その戻り値をローカル変数に格納することが、コンパイラの内部的な行番号状態の混乱を招いていた可能性を示唆しています。 - 修正後:
setlineno(n)
を呼び出すことで、各case
節の処理時にコンパイラのグローバルな行番号状態が適切に更新されることが保証されます。ローカル変数への代入を削除することで、この関数呼び出しの目的がグローバル状態の更新のみであることが明確になり、意図しない副作用や混乱が排除されます。これにより、switch
文内の任意の場所で発生したエラーに対して、より正確な行番号が報告されるようになります。
- 修正前: 各
これらの変更は、コンパイラがソースコードを走査する際の行番号の追跡ロジックを改善し、特にswitch
文のような複雑な構造内で発生するエラーに対して、より正確なソースコードの行番号を報告することを可能にしました。
test/fixedbugs/issue4562.go
は、この修正が正しく機能することを確認するためのテストケースです。このテストは、nil
ポインタのval
フィールドにアクセスしようとすることでパニックを意図的に発生させ、そのパニックがswitch pT.val
の行(22行目)で発生したことをruntime.Caller
とexpectError
関数を使って検証しています。修正前は、このテストは失敗し、パニックの行番号が22行目とは異なる場所(例えば、case 1:
の行など)を報告していました。修正後は、テストが成功し、正確な行番号が報告されることを確認できます。
関連リンク
- Go Issue #4562: https://go.dev/issue/4562
- Gerrit Change 7008044: https://go.googlesource.com/go/+/7008044 (これはコミットメッセージに記載されているリンクですが、GitHubのコミットページがよりアクセスしやすいです)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goコンパイラのソースコード (
src/cmd/gc/
) - Go Issue Tracker (
go.dev/issue
)