[インデックス 17270] ファイルの概要
このコミットは、Go言語のランタイムテストコードにおける2つのnilポインタの誤用を修正するものです。具体的には、ガベージコレクションのテスト(gc_test.go)と、recoverメカニズムのテスト(recover3.go)において、nilポインタのデリファレンスによって意図しないパニックが発生する可能性があった箇所を修正しています。
コミット
commit 08fdf00906a5008428273742ef7df78552d3308d
Author: Russ Cox <rsc@golang.org>
Date: Thu Aug 15 11:51:04 2013 -0400
tests: remove two misuses of nil pointers
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12858044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/08fdf00906a5008428273742ef7df78552d3308d
元コミット内容
diff --git a/src/pkg/runtime/gc_test.go b/src/pkg/runtime/gc_test.go
index a3c731ccb0..dbd68c1c75 100644
--- a/src/pkg/runtime/gc_test.go
+++ b/src/pkg/runtime/gc_test.go
@@ -136,7 +136,9 @@ func TestGcRescan(t *testing.T) {
for i := 0; i < 10; i++ {
p := &Y{}
p.c = make(chan error)
- p.nextx = &head.X
+ if head != nil {
+ p.nextx = &head.X
+ }
p.nexty = head
p.p = new(int)
*p.p = 42
diff --git a/test/recover3.go b/test/recover3.go
index ebfa0a3075..e17bfb3f6a 100644
--- a/test/recover3.go
+++ b/test/recover3.go
@@ -64,7 +64,8 @@ func main() {
i = 99999
var sl []int
- check("array-bounds", func() { println(p[i]) }, "index out of range")
+ p1 := new([10]int)
+ check("array-bounds", func() { println(p1[i]) }, "index out of range")
check("slice-bounds", func() { println(sl[i]) }, "index out of range")
var inter interface{}
変更の背景
このコミットは、Go言語のテストスイート内で発生していた、nilポインタのデリファレンスによる予期せぬパニックを修正するために導入されました。テストの目的は特定の挙動(例えば、配列の範囲外アクセスによるパニックやガベージコレクションの動作)を検証することですが、nilポインタの誤用によって、本来テストしたい挙動とは異なる原因でパニックが発生してしまうことがありました。これにより、テストが不安定になったり、本来検出したいバグを見逃す可能性がありました。
特に、gc_test.goではheadポインタがnilである可能性を考慮せずにそのフィールドにアクセスしようとしていました。また、recover3.goでは、配列の範囲外アクセスをテストする際に、対象となる配列ポインタがnilであるために、配列アクセス以前にnilポインタデリファレンスによるパニックが発生していました。これらの問題を解決し、テストの堅牢性と正確性を向上させることが変更の背景にあります。
前提知識の解説
Goにおけるポインタとnil
Go言語におけるポインタは、変数のメモリアドレスを指し示す変数です。C/C++のようなポインタ演算は限定的ですが、オブジェクト(構造体、配列など)への参照として広く利用されます。
nilは、ポインタ、スライス、マップ、チャネル、関数、インターフェースなどのゼロ値として使用されます。ポインタがnilであるということは、それが何も指し示していない状態を意味します。
nilポインタデリファレンス
nilポインタデリファレンスとは、nilであるポインタが指し示すメモリ領域にアクセスしようとすることです。Go言語では、nilポインタをデリファレンスしようとすると、ランタイムパニック(panic: runtime error: invalid memory address or nil pointer dereference)が発生し、プログラムの実行が異常終了します。これは、プログラムのバグとして扱われ、通常は避けるべき挙動です。
Goのテストフレームワーク
Go言語には、標準ライブラリとしてtestingパッケージが提供されており、これを用いてユニットテストやベンチマークテストを記述します。テスト関数はTestXxxという命名規則に従い、*testing.T型の引数を取ります。テストの失敗はt.Errorやt.Fatalなどで報告されます。
panicとrecover
Go言語には、例外処理に似たpanicとrecoverというメカニズムがあります。
panic: プログラムの異常終了を引き起こします。通常、回復不能なエラーやプログラマの想定外の状況で使われます。recover:defer関数内で呼び出されることで、panicからの回復を試みることができます。これにより、プログラムの異常終了を防ぎ、エラーハンドリングを行うことが可能になります。
recover3.goのようなテストファイルは、panicが発生した際にrecoverが正しく機能するかどうかを検証するために書かれています。このテストでは、意図的にパニックを発生させ、それが期待通りのメッセージでrecoverされるかをcheck関数で検証しています。
技術的詳細
src/pkg/runtime/gc_test.goの修正
このファイルはGoランタイムのガベージコレクション(GC)のテストコードです。TestGcRescan関数内で、Yという構造体のインスタンスpを作成し、そのフィールドp.nextxにhead.Xのアドレスを代入しようとしていました。
元のコード:
p.nextx = &head.X
ここで問題となるのは、head変数がnilである可能性があるにもかかわらず、そのフィールドXに直接アクセスしようとしていた点です。もしheadがnilであれば、head.Xの評価時にnilポインタデリファレンスが発生し、テストが意図しないパニックで終了してしまいます。
修正後のコード:
if head != nil {
p.nextx = &head.X
}
この修正では、headがnilでないことを明示的にチェックするif文が追加されました。これにより、headがnilの場合にはp.nextx = &head.Xの行が実行されなくなり、nilポインタデリファレンスによるパニックが回避されます。この変更は、テストの安定性を向上させ、GCのロジック自体ではなく、テストコードの不備による失敗を防ぐことを目的としています。
test/recover3.goの修正
このファイルは、Goのpanicとrecoverメカニズム、特に配列やスライスの範囲外アクセスによるパニックが正しくrecoverされるかをテストするためのものです。
元のコード:
var sl []int
check("array-bounds", func() { println(p[i]) }, "index out of range")
ここでpは、main関数の冒頭でvar p *[10]intとして宣言されていますが、初期化されていませんでした。Goでは、ポインタ型のゼロ値はnilです。したがって、pはnilポインタのままでした。
check関数は、第2引数に渡された無名関数を実行し、その中で発生したパニックを捕捉して検証します。本来、このテストはp[i](iは99999という大きな値)が配列の範囲外アクセスを引き起こし、それによってパニックが発生することを期待していました。しかし、pがnilであるため、p[i]にアクセスしようとした瞬間にnilポインタデリファレンスによるパニックが発生してしまい、本来テストしたい「配列の範囲外アクセス」によるパニックとは異なる原因でテストが失敗していました。
修正後のコード:
p1 := new([10]int)
check("array-bounds", func() { println(p1[i]) }, "index out of range")
この修正では、pの代わりにp1という新しい変数new([10]int)が導入されました。new([10]int)は、要素数10のint型配列を指すポインタを新しく割り当て、そのアドレスをp1に代入します。これにより、p1は有効な配列を指すポインタとなり、p1[i]のアクセスはnilポインタデリファレンスではなく、期待通り「配列の範囲外アクセス」によるパニックを引き起こすようになります。この修正により、recover3.goのテストが意図した通りの挙動を検証できるようになりました。
コアとなるコードの変更箇所
src/pkg/runtime/gc_test.go
p.nextx = &head.Xの行が、if head != nil { p.nextx = &head.X }に変更されました。
test/recover3.go
check("array-bounds", func() { println(p[i]) }, "index out of range")の行が、p1 := new([10]int)の追加と、それに続くcheck("array-bounds", func() { println(p1[i]) }, "index out of range")に変更されました。
コアとなるコードの解説
src/pkg/runtime/gc_test.goの変更
この変更は、headというポインタがnilである可能性を考慮していなかったバグを修正しています。headがnilの場合にhead.Xとデリファレンスしようとすると、Goランタイムはnilポインタデリファレンスとしてパニックを発生させます。これは、ガベージコレクションのテストの文脈では、テスト対象のGCロジックとは無関係なテストコード自体のバグであり、テストの信頼性を損なうものでした。if head != nilというガード句を追加することで、headが有効なポインタである場合にのみhead.Xへのアクセスを試みるようになり、テストの安定性が確保されました。
test/recover3.goの変更
この変更は、recoverメカニズムのテストにおいて、意図しないパニックの原因を排除することを目的としています。元のコードでは、pという配列ポインタがnilのままで使用されており、p[i]へのアクセスは配列の範囲外アクセスによるパニックではなく、nilポインタデリファレンスによるパニックを引き起こしていました。これは、テストが本来検証したい「配列の範囲外アクセス時のパニックとrecover」というシナリオを正しく実行できていないことを意味します。
p1 := new([10]int)という行を追加することで、p1は実際にメモリ上に確保された10個のint要素を持つ配列を指す有効なポインタとなります。これにより、p1[i](iは99999)へのアクセスは、期待通り「配列の範囲外アクセス」というGoランタイムが生成するパニックを引き起こすようになり、check関数がそのパニックを捕捉して検証できるようになります。この修正により、recover3.goのテストは、その本来の目的を正確に果たすことができるようになりました。
関連リンク
- Go言語のポインタ: https://go.dev/tour/moretypes/1
- Go言語の
panicとrecover: https://go.dev/blog/defer-panic-and-recover - Go言語のテスト: https://go.dev/doc/tutorial/add-a-test
参考にした情報源リンク
- Go言語公式ドキュメント
- Go言語のソースコード (特に
src/pkg/runtime/とtest/ディレクトリ) - Go言語の
nilポインタに関する一般的な情報源