[インデックス 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
ポインタに関する一般的な情報源