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

[インデックス 17195] ファイルの概要

このコミットは、Go言語の標準ライブラリ sync/atomic パッケージにおいて、Swap 系関数が nil ポインタを渡された場合にパニック(nil dereference)を引き起こさないことを保証するためのテストケースを追加するものです。具体的には、TestNilDeref 関数に SwapInt32, SwapUint32, SwapInt64, SwapUint64, SwapUintptr, SwapPointer の各関数が nil を引数として呼び出された際のテストを追加しています。

コミット

commit d3f36dbfc7dbed2fe93746a563dd253a98547a6b
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Tue Aug 13 21:18:33 2013 +0400

    sync/atomic: add Swap to nil deref test
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12870043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d3f36dbfc7dbed2fe93746a563dd253a98547a6b

元コミット内容

sync/atomic パッケージに、Swap 系関数が nil ポインタを逆参照しないことを確認するテストを追加します。

変更の背景

Go言語の sync/atomic パッケージは、低レベルのアトミック操作を提供し、複数のゴルーチンが共有データに安全にアクセスできるようにします。これらの関数は通常、ポインタを介してメモリ上の値を操作します。しかし、誤って nil ポインタをこれらの関数に渡した場合、プログラムがクラッシュ(パニック)する可能性があります。

Goのランタイムや標準ライブラリは、堅牢性と安定性を非常に重視しています。そのため、予期せぬ nil ポインタの逆参照によるパニックは、可能な限り防ぐべき挙動とされています。このコミット以前にも、CompareAndSwapAdd といった他のアトミック操作関数に対して nil ポインタが渡された場合のテスト (TestNilDeref) が存在していました。

このコミットの背景には、Swap 系関数についても同様に nil ポインタが渡された場合に安全であることを保証する必要があるという認識があったと考えられます。つまり、これらの関数が nil ポインタを引数として受け取った際に、内部で nil ポインタの逆参照が発生し、プログラムが異常終了するようなバグがないことを確認するためのテストカバレッジの拡充が目的です。これにより、開発者が誤って nil ポインタを渡してしまった場合でも、少なくともアトミック操作関数自体が原因でパニックが発生しないように、より堅牢なライブラリを提供することを目指しています。

前提知識の解説

1. Go言語の sync/atomic パッケージ

sync/atomic パッケージは、Go言語で並行処理を行う際に、共有変数へのアクセスを同期するための低レベルなアトミック操作を提供します。アトミック操作とは、その操作が不可分(分割できない)であることを意味します。つまり、複数のゴルーチンが同時に同じ変数にアクセスしようとしても、アトミック操作は一度に一つのゴルーチンによってのみ完全に実行され、途中で他のゴルーチンに割り込まれることがありません。これにより、データ競合(data race)を防ぎ、プログラムの正確性を保証します。

主なアトミック操作には以下のようなものがあります。

  • Load: メモリからアトミックに値を読み込みます。
  • Store: メモリにアトミックに値を書き込みます。
  • Add: メモリ上の値にアトミックに加算します。
  • Swap: メモリ上の値を新しい値とアトミックに交換し、元の値を返します。
  • CompareAndSwap (CAS): メモリ上の値が期待する値と一致する場合にのみ、新しい値にアトミックに交換します。

これらの関数は、int32, int64, uint32, uint64, uintptr, unsafe.Pointer などの型に対して提供されています。

2. nil ポインタと nil デリファレンス (Nil Dereference)

Go言語において、ポインタはメモリ上のアドレスを指し示します。nil ポインタは、どのメモリアドレスも指していない状態を表します。これは、C/C++における NULL ポインタに相当します。

nil デリファレンス(nil dereference)とは、nil ポインタが指すアドレスにアクセスしようとすることです。Goでは、nil ポインタを逆参照しようとすると、ランタイムパニック(runtime panic)が発生し、プログラムが異常終了します。これは、プログラムのバグであり、通常は避けるべき挙動です。

例えば、以下のようなコードは nil デリファレンスを引き起こします。

var p *int // p は nil
fmt.Println(*p) // ここでパニックが発生

sync/atomic パッケージの関数はポインタを引数として受け取るため、誤って nil ポインタを渡してしまうと、内部で nil デリファレンスが発生する可能性があります。このコミットは、そのような状況でもパニックが発生しないように、ライブラリの堅牢性を高めるためのテストを追加しています。

3. TestNilDeref 関数

atomic_test.go 内の TestNilDeref 関数は、sync/atomic パッケージの各関数が nil ポインタを引数として受け取った場合に、パニックが発生しないことを検証するためのテスト関数です。このテストは、recover 機構を利用して、パニックが発生するかどうかを捕捉し、期待される挙動(パニックしない、または特定のパニックメッセージを出す)と照合します。

このテストの目的は、開発者が sync/atomic 関数を誤って使用した場合でも、ライブラリ自体がクラッシュの原因とならないようにすることです。

技術的詳細

このコミットは、src/pkg/sync/atomic/atomic_test.go ファイル内の TestNilDeref 関数に、Swap 系関数に対する nil ポインタのテストケースを追加しています。

TestNilDeref 関数は、以下のような構造を持っています。

func TestNilDeref(t *testing.T) {
	// ... 既存のテストケース ...

	// 各関数を nil を引数として呼び出す無名関数をスライスに格納
	funcs := []func(){
		func() { CompareAndSwapUint64(nil, 0, 0) },
		func() { CompareAndSwapUintptr(nil, 0, 0) },
		func() { CompareAndSwapPointer(nil, nil, nil) },
		// ここに新しい Swap 系関数のテストが追加される
		func() { SwapInt32(nil, 0) },
		func() { SwapUint32(nil, 0) },
		func() { SwapInt64(nil, 0) },
		func() { SwapUint64(nil, 0) },
		func() { SwapUintptr(nil, 0) },
		func() { SwapPointer(nil, nil) },
		// ... 他の Add 系関数のテスト ...
	}

	// 各無名関数をゴルーチンで実行し、パニックを捕捉
	for i, f := range funcs {
		func() {
			defer func() {
				if r := recover(); r != nil {
					// パニックが発生した場合の処理
					// ここでは、パニックが発生しないことを期待しているため、エラーとして報告
					t.Errorf("#%d: unexpected panic: %v", i, r)
				}
			}()
			f() // nil を引数として関数を呼び出す
		}()
	}
}

sync/atomic パッケージの関数は、内部で unsafe.Pointer を使用して型安全性をバイパスし、任意の型のポインタを操作できるようにしています。しかし、nil ポインタが渡された場合、これらの関数は通常、ポインタの逆参照を行う前に nil チェックを行うか、あるいは nil ポインタの操作が安全に行われるように設計されている必要があります。

このコミットで追加されたテストは、Swap 系関数が nil ポインタを引数として受け取った際に、内部で nil デリファレンスによるパニックが発生しないことを確認します。もしパニックが発生した場合、テストは失敗し、それは sync/atomic パッケージの Swap 関数にバグがあることを示唆します。

具体的には、SwapInt32(nil, 0) のような呼び出しは、int32 型の値を指すポインタが nil であるにもかかわらず、そのポインタが指すメモリ位置の値を交換しようとします。Goのランタイムは、このような不正なメモリアクセスを検出し、パニックを発生させます。しかし、sync/atomic パッケージの関数は、このような状況を適切に処理し、パニックを回避するように実装されているべきです。このテストは、その実装が正しく行われていることを検証します。

コアとなるコードの変更箇所

変更は src/pkg/sync/atomic/atomic_test.go ファイルの TestNilDeref 関数内で行われています。

--- a/src/pkg/sync/atomic/atomic_test.go
+++ b/src/pkg/sync/atomic/atomic_test.go
@@ -1466,6 +1466,12 @@ func TestNilDeref(t *testing.T) {
 		func() { CompareAndSwapUint64(nil, 0, 0) },
 		func() { CompareAndSwapUintptr(nil, 0, 0) },
 		func() { CompareAndSwapPointer(nil, nil, nil) },
+\t\tfunc() { SwapInt32(nil, 0) },
+\t\tfunc() { SwapUint32(nil, 0) },
+\t\tfunc() { SwapInt64(nil, 0) },
+\t\tfunc() { SwapUint64(nil, 0) },
+\t\tfunc() { SwapUintptr(nil, 0) },
+\t\tfunc() { SwapPointer(nil, nil) },
 		func() { AddInt32(nil, 0) },
 		func() { AddUint32(nil, 0) },
 		func() { AddInt64(nil, 0) },

追加された行は以下の6行です。

  • func() { SwapInt32(nil, 0) },
  • func() { SwapUint32(nil, 0) },
  • func() { SwapInt64(nil, 0) },
  • func() { SwapUint64(nil, 0) },
  • func() { SwapUintptr(nil, 0) },
  • func() { SwapPointer(nil, nil) },

これらの行は、TestNilDeref 関数内の funcs スライスに追加されています。

コアとなるコードの解説

追加された各行は、sync/atomic パッケージの Swap 系関数を nil ポインタを最初の引数として呼び出す無名関数です。

  • func() { SwapInt32(nil, 0) }: int32 型の値をアトミックに交換する SwapInt32 関数に nil ポインタと値 0 を渡して呼び出します。
  • func() { SwapUint32(nil, 0) }: uint32 型の値をアトミックに交換する SwapUint32 関数に nil ポインタと値 0 を渡して呼び出します。
  • func() { SwapInt64(nil, 0) }: int64 型の値をアトミックに交換する SwapInt64 関数に nil ポインタと値 0 を渡して呼び出します。
  • func() { SwapUint64(nil, 0) }: uint64 型の値をアトミックに交換する SwapUint64 関数に nil ポインタと値 0 を渡して呼び出します。
  • func() { SwapUintptr(nil, 0) }: uintptr 型の値をアトミックに交換する SwapUintptr 関数に nil ポインタと値 0 を渡して呼び出します。
  • func() { SwapPointer(nil, nil) }: unsafe.Pointer 型の値をアトミックに交換する SwapPointer 関数に nil ポインタと nil ポインタを渡して呼び出します。

これらの無名関数は、TestNilDeref 関数内のループで順次実行されます。各無名関数は deferrecover を使用したブロック内で実行されるため、もし Swap 関数が nil ポインタの逆参照によってパニックを引き起こした場合でも、そのパニックは捕捉されます。テストのロジックは、パニックが捕捉された場合にエラーを報告するように設計されているため、これらの Swap 関数が nil ポインタで呼び出された際にパニックが発生しないことが検証されます。

この追加により、sync/atomic パッケージの Swap 系関数が、誤って nil ポインタが渡された場合でも、ランタイムパニックを引き起こさずに安全に動作することが保証されるようになります。これは、ライブラリの堅牢性と信頼性を向上させる上で重要なテストカバレッジの拡充です。

関連リンク

参考にした情報源リンク