[インデックス 184] ファイルの概要
このコミットは、Go言語の初期開発段階における2つのバグテストケースの追加と、それに伴うテスト出力の更新に関するものです。具体的には、定数式における関数呼び出しの誤った扱いと、整数除算におけるセグメンテーション違反という、Goコンパイラの初期の不具合を浮き彫りにするテストが追加されています。
コミット
commit e5373f27d0f4995eb34d85dc11be8600a80b7e76
Author: Robert Griesemer <gri@golang.org>
Date: Tue Jun 17 16:04:33 2008 -0700
- added 2 bug tests
SVN=123220
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e5373f27d0f4995eb34d85dc11be8600a80b7e76
元コミット内容
- added 2 bug tests
SVN=123220
変更の背景
このコミットは、Go言語の非常に初期の段階(2008年6月)に行われたものであり、Goコンパイラがまだ成熟しておらず、多くのバグを抱えていた時期に当たります。Go言語の設計思想の一つに「コンパイル時のエラー検出を重視する」というものがありますが、初期のコンパイラはその目標を完全に達成できていませんでした。
bug051.go
の追加は、Go言語における定数(const
)の扱いに関するコンパイラの不具合を修正するためのものです。Goの定数はコンパイル時に値が決定されるエンティティであり、その値はコンパイル時にコードに直接埋め込まれる「マクロ置換」のようなものとして扱われます。しかし、関数呼び出しの結果を定数として宣言しようとした場合、コンパイラがこれを適切にエラーとして扱えず、複数のエラーメッセージを生成したり、場合によっては無限ループに陥る可能性があったことを示唆しています。Go言語の仕様では、len
やcap
のような一部の組み込み関数を除き、通常の関数呼び出しの結果はコンパイル時定数にはなり得ません。このテストは、このような不正な定数宣言に対するコンパイラの挙動を検証し、修正を促すために追加されました。
bug052.go
の追加は、整数除算におけるセグメンテーション違反(Segmentation Fault)という、より深刻なランタイムエラーを修正するためのものです。これは、コンパイラが生成するコードに問題があり、特定の条件下での整数除算が不正なメモリアクセスを引き起こしていたことを示しています。特に、c/d
のような直接的な除算式が問題を引き起こし、一時変数に代入してから使用する場合には問題が発生しないという記述から、コンパイラの最適化や中間コード生成の段階でバグが存在した可能性が高いです。セグメンテーション違反はプログラムのクラッシュに直結するため、早期の修正が求められる重大なバグでした。
これらのテストの追加は、Goコンパイラの堅牢性を高め、より正確なエラー報告と安定した実行時挙動を実現するための重要なステップでした。
前提知識の解説
Go言語における定数(Constants)
Go言語の定数は、コンパイル時にその値が決定される不変のエンティティです。数値定数は任意精度を持ち、オーバーフローすることはありません。定数は、リテラル値、または定数式によって定義されます。定数式は、定数オペランドと定数関数(len
, cap
など)のみを含む式です。通常の関数呼び出しの結果は、コンパイル時定数にはなり得ません。
const PI = 3.14159 // リテラル定数
const TwoPI = 2 * PI // 定数式
// const InvalidConst = someFunction() // これはコンパイルエラーになる
コンパイル時定数と実行時定数
- コンパイル時定数: プログラムのコンパイル時に値が確定し、実行時には変更されない値。Goの
const
キーワードで宣言される定数はこれに該当します。 - 実行時定数: プログラムの実行中に値が確定するが、その後は変更されない値。例えば、
var
で宣言された変数をdefer
やinit
関数内で初期化し、その後変更しない場合などがこれに該当しますが、Go言語には明示的な「実行時定数」の概念はありません。
bug051.go
は、コンパイル時定数として扱われるべきではない関数呼び出しの結果をconst
に代入しようとした際に、コンパイラが適切にエラーを報告できないという問題を示しています。
セグメンテーション違反(Segmentation Fault)
セグメンテーション違反は、プログラムが許可されていないメモリ領域にアクセスしようとしたときに発生するエラーです。これは通常、以下のような原因で引き起こされます。
- ヌルポインタの逆参照:
nil
ポインタが指すアドレスにアクセスしようとした場合。 - 無効なポインタの逆参照: 初期化されていないポインタや、解放済みのメモリを指すポインタにアクセスしようとした場合。
- 配列の範囲外アクセス: 配列の境界を超えて要素にアクセスしようとした場合。
- スタックオーバーフロー: 関数呼び出しが深くなりすぎたり、大きなローカル変数を確保しすぎたりして、スタック領域を使い果たした場合。
- コンパイラのバグ: コンパイラが不正な機械語コードを生成し、それが不正なメモリアクセスを引き起こす場合。
bug052.go
は、Goコンパイラが生成するコードに問題があり、特定の整数除算がセグメンテーション違反を引き起こしていたことを示しています。これは、コンパイラのコード生成フェーズにおけるバグの典型的な例です。
Goコンパイラの初期の状況
Go言語は2009年に一般公開されましたが、このコミットはそれ以前の2008年に行われています。この時期のGoコンパイラはまだ開発の初期段階にあり、多くの機能が実装され、同時に多くのバグが発見・修正されていました。コンパイラの安定性、最適化、エラー報告の正確性などは、その後の数年間で大きく改善されていくことになります。
技術的詳細
このコミットは、Goコンパイラのテストスイートに2つの新しいバグテストケースを追加することで、コンパイラの堅牢性を向上させています。
test/bugs/bug051.go
の詳細
このテストケースは、定数宣言におけるコンパイラの不適切なエラー報告を検証します。
// errchk $G $D/$F.go
// Copyright 2009 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
func f() int {
return 0;
}
func main() {
const n = f(); // should report only one error
}
// errchk $G $D/$F.go
: この行は、Goコンパイラ($G
)がこのファイルをコンパイルした際に、エラーを報告することを期待していることを示しています。$D/$F.go
は現在のディレクトリにあるファイル名を指します。func f() int { return 0; }
: 単純な関数f
を定義しています。const n = f();
: ここが問題の箇所です。Go言語の仕様では、関数呼び出しの結果はコンパイル時定数にはなり得ません(len
やcap
などの組み込み関数を除く)。したがって、この行はコンパイルエラーになるべきです。// should report only one error
: コメントは、コンパイラがこの不正な定数宣言に対して、単一の明確なエラーメッセージを報告することを期待していることを示しています。元のコンパイラは、このケースで複数のエラーを報告したり、エラー報告の無限ループに陥ったりする可能性があったと考えられます。
このテストは、コンパイラが不正な定数式を正しく識別し、冗長なエラーメッセージを生成せずに、一度だけ適切なエラーを報告できることを保証するために追加されました。
test/bugs/bug052.go
の詳細
このテストケースは、整数除算におけるセグメンテーション違反を検証します。
// $G $D/$F.go && $L $F.$A && ./$A.out
// Copyright 2009 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
func main() {
c := 10;
d := 7;
var x [10]int;
i := 0;
/* this works:
q := c/d;
x[i] = q;
*/
// this doesn't:
x[i] = c/d; // BUG segmentation fault
}
// $G $D/$F.go && $L $F.$A && ./$A.out
: この行は、Goコンパイラ($G
)でファイルをコンパイルし、リンカ($L
)で実行可能ファイルを作成し、その実行可能ファイル(./$A.out
)を実行することを期待していることを示しています。これは、ランタイムエラーを検出するためのテストであることを意味します。c := 10; d := 7;
: 整数変数c
とd
を初期化しています。var x [10]int; i := 0;
: 整数配列x
とインデックス変数i
を初期化しています。/* this works: q := c/d; x[i] = q; */
: コメントアウトされたコードは、除算の結果を一時変数q
に格納してから配列に代入するパターンです。このパターンではセグメンテーション違反が発生しなかったことを示唆しています。x[i] = c/d; // BUG segmentation fault
: 問題の箇所です。除算の結果を直接配列要素に代入するこのパターンで、セグメンテーション違反が発生したことを示しています。
このテストは、コンパイラが生成する整数除算の機械語コードにバグがあり、特定のコンテキスト(この場合は直接代入)で不正なメモリアクセスを引き起こしていたことを明らかにしています。これは、コンパイラのコード生成バックエンドまたは中間表現の最適化フェーズにおけるバグであった可能性が高いです。
test/golden.out
の更新
test/golden.out
は、Goコンパイラのテストスイートにおける期待される出力(エラーメッセージや警告など)を記録するファイルです。このコミットでは、bug051.go
とbug052.go
の追加に伴い、これらの新しいテストケースに対する期待される出力が追記されています。
bug051.go
に対しては、expression must be a constant
というエラーが複数回報告され、最終的にfatal error: too many errors
とBUG: infinite loop in error reporting
が記録されています。これは、このコミット時点ではまだコンパイラがbug051.go
のケースを完全に正しく処理できておらず、エラー報告の無限ループに陥るという別のバグが存在していたことを示しています。このテストは、その無限ループのバグも同時に浮き彫りにしています。bug052.go
に対しては、BUG: incorrect code for division
という記述が追加されています。これは、このテストがコンパイラの除算コード生成の誤りを検出したことを示しています。
これらの更新は、テストが検出したバグの現状と、それに対するコンパイラの挙動を文書化する役割を果たしています。
コアとなるコードの変更箇所
このコミットで追加されたファイルは以下の通りです。
test/bugs/bug051.go
test/bugs/bug052.go
test/golden.out
(既存ファイルの変更)
test/bugs/bug051.go
--- /dev/null
+++ b/test/bugs/bug051.go
@@ -0,0 +1,15 @@
+// errchk $G $D/$F.go
+// Copyright 2009 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
+
+func f() int {
+ return 0;
+}
+
+func main() {
+ const n = f(); // should report only one error
+}
test/bugs/bug052.go
--- /dev/null
+++ b/test/bugs/bug052.go
@@ -0,0 +1,20 @@
+// $G $D/$F.go && $L $F.$A && ./$A.out
+// Copyright 2009 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
+
+func main() {
+ c := 10;
+ d := 7;
+ var x [10]int;
+ i := 0;
+ /* this works:
+ q := c/d;
+ x[i] = q;
+ */
+ // this doesn't:
+ x[i] = c/d; // BUG segmentation fault
+}
test/golden.out
--- a/test/golden.out
+++ b/test/golden.out
@@ -47,10 +47,10 @@ test0.go:49: illegal types for operand: AS
(<float32>FLOAT32)
(<int32>INT32)
test0.go:50: error in shape across assignment
-test0.go:55: illegal types for operand: CALLMETH
+test0.go:47: illegal types for operand: CALLMETH
(*<Point>{})\
(<Point>{<x><int32>INT32;<y><int32>INT32;<Point_Initialize>120({},{}){};<Point_Distance>101({},{}){};})\
-test0.go:54: illegal types for operand: AS
+test0.go:47: illegal types for operand: AS
(<Point>{<x><int32>INT32;<y><int32>INT32;<Point_Initialize>120({},{}){};<Point_Distance>101({},{}){};})\
({})
BUG: known to fail incorrectly
@@ -139,7 +139,6 @@ BUG: known to succeed incorrectly
=========== bugs/bug022.go
bugs/bug022.go:8: illegal types for operand: INDEXPTR
(*<string>*STRING)
-- (<int32>INT32)
BUG: known to fail incorrectly
=========== bugs/bug023.go
@@ -210,7 +209,7 @@ BUG: compilation should succeed
=========== bugs/bug043.go
bugs/bug043.go:14: error in shape across assignment
-bugs/bug043.go:17: error in shape across assignment
+bugs/bug043.go:14: error in shape across assignment
BUG: compilation should succeed
=========== bugs/bug044.go
@@ -251,6 +250,23 @@ bugs/bug050.go:3: package statement must be first
sys.6:1 bugs/bug050.go:2: syntax error
BUG: segfault
+=========== bugs/bug051.go
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: expression must be a constant
+bugs/bug051.go:10: fatal error: too many errors
+BUG: infinite loop in error reporting
+
+=========== bugs/bug052.go
+BUG: incorrect code for division
+
=========== fixedbugs/bug000.go
=========== fixedbugs/bug001.go
コアとなるコードの解説
このコミットのコアとなる変更は、Goコンパイラのテストスイートに2つの新しいバグテストケースを追加したことです。これらのテストは、Goコンパイラの初期の不具合を特定し、修正を促すために設計されています。
-
test/bugs/bug051.go
:- このファイルは、関数呼び出しの結果を
const
として宣言しようとした場合に、コンパイラがどのように振る舞うかをテストします。 - Go言語の仕様では、通常の関数呼び出しはコンパイル時定数にはなり得ません。したがって、
const n = f();
という行はコンパイルエラーになるべきです。 - このテストの目的は、コンパイラがこの不正な定数宣言に対して、単一の明確なエラーメッセージを報告し、エラー報告の無限ループに陥らないことを保証することです。
golden.out
の更新を見ると、このコミット時点ではまだコンパイラがこのケースを完全に正しく処理できておらず、複数のエラーメッセージと「fatal error: too many errors」「BUG: infinite loop in error reporting」という記述が残っています。これは、このテストが別のバグ(エラー報告の無限ループ)も同時に浮き彫りにしたことを示しています。
- このファイルは、関数呼び出しの結果を
-
test/bugs/bug052.go
:- このファイルは、特定の整数除算のシナリオで発生するセグメンテーション違反をテストします。
x[i] = c/d;
という直接的な除算と代入の組み合わせがセグメンテーション違反を引き起こす一方で、q := c/d; x[i] = q;
のように一時変数q
を介すると問題が発生しないという点が重要です。- これは、コンパイラのコード生成バックエンドまたは中間表現の最適化フェーズにバグがあり、特定の式ツリーの構造が不正な機械語コードを生成していた可能性を示唆しています。
golden.out
には「BUG: incorrect code for division」と記録されており、コンパイラが生成する除算コードに問題があることを明確に示しています。
これらのテストは、Goコンパイラの初期の不安定性や不正確性を浮き彫りにし、その後のコンパイラの改善と安定化に貢献しました。特に、セグメンテーション違反のようなランタイムエラーは、プログラムの信頼性に直接影響するため、これらのバグテストの追加は非常に重要でした。
関連リンク
- Go言語の定数に関する公式ドキュメント: https://go.dev/ref/spec#Constants
- Go言語のコンパイラに関する議論(初期の最適化など)は、GoのIssueトラッカーやメーリングリストで確認できますが、特定のバグに関する直接的なリンクを見つけるのは困難な場合があります。
参考にした情報源リンク
- Go言語の定数に関する情報: https://boldlygo.tech/posts/2023/07/24/go-constants/
- Go言語のコンパイラに関する一般的な情報: https://medium.com/@shubham_goyal/go-compiler-internals-a-deep-dive-into-the-go-compiler-architecture-and-optimization-techniques-b72212121212
- Goコンパイラの最適化に関するGitHub Issueの例: https://github.com/golang/go/issues/29095 (「constness」の喪失に関する議論)
- Goコンパイラのバグ修正に関する一般的な情報: https://stackoverflow.com/questions/tagged/go-compiler
- Go言語のリリースノートや変更履歴(初期のバグ修正に関する詳細が記載されている可能性あり): https://go.dev/doc/devel/release (ただし、このコミットは非常に古いため、具体的なリリースノートには記載されていない可能性があります)
- セグメンテーション違反に関する一般的な情報: https://ja.wikipedia.org/wiki/%E3%82%BB%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%95%E3%82%A9%E3%83%BC%E3%83%AB%E3%83%88