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

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

このコミットは、Goランタイムのruntime.SetFinalizer関数におけるバグ修正を目的としています。具体的には、コンパイラによってグローバルオブジェクトとして扱われる複合リテラルへのポインタがSetFinalizerに渡された際に発生するパニックを回避するための変更です。これにより、SetFinalizerが、ヒープ上に明示的に割り当てられたオブジェクトの先頭へのポインタではない場合に、誤ってエラーを報告する問題を解決します。

コミット

commit f4ef6977ffab6c741b059d03147562e7c5901c0c
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Wed Apr 2 10:19:28 2014 +0400

    runtime: ignore pointers to global objects in SetFinalizer
    Update #7656
    
    LGTM=rsc
    R=rsc, iant
    CC=golang-codereviews
    https://golang.org/cl/82560043

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

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

元コミット内容

runtime: ignore pointers to global objects in SetFinalizer Update #7656

変更の背景

Go言語のruntime.SetFinalizer関数は、特定のオブジェクトがガベージコレクションによって回収される直前に実行される関数(ファイナライザ)を設定するために使用されます。しかし、この関数には、ポインタがヒープ上の割り当てられたブロックの先頭を指していない場合にパニックを引き起こすという既知の問題がありました。

特に問題となったのは、以下のようなケースです。

var Foo = &Object{}
func main() {
    runtime.SetFinalizer(Foo, nil)
}

このコードでは、Fooは複合リテラル&Object{}へのポインタとして初期化されています。Goコンパイラは、このような複合リテラルをグローバル変数として扱うことがあります。グローバル変数はヒープではなく、静的メモリ領域に配置されるため、runtime.SetFinalizerが期待する「ヒープ上の割り当てられたブロックの先頭」という条件を満たしませんでした。結果として、SetFinalizer"runtime.SetFinalizer: pointer not at beginning of allocated block"というエラーメッセージを出力し、プログラムがクラッシュする可能性がありました。

この問題はIssue 7656として報告され、このコミットはその問題を解決するために導入されました。

前提知識の解説

runtime.SetFinalizer

runtime.SetFinalizer(obj, finalizer)は、Go言語の標準ライブラリruntimeパッケージが提供する関数で、objがガベージコレクタによって到達不可能になった際に、finalizer関数を実行するようにスケジュールします。これは、ファイルハンドルやネットワーク接続などの外部リソースをクリーンアップする際に使用されることがあります。

しかし、SetFinalizerにはいくつかの重要な制約と注意点があります。

  • 実行タイミングの不確実性: ファイナライザはガベージコレクションのタイミングに依存するため、いつ実行されるかは保証されません。即時性が必要なリソース解放には適していません。
  • オブジェクトの「復活」: ファイナライザ内でobjを再び到達可能にしてしまうと、そのオブジェクトはガベージコレクションされなくなり、メモリリークの原因となる可能性があります。
  • 複数回のGCサイクル: ファイナライザが設定されたオブジェクトは、完全に解放されるまでに少なくとも2回のガベージコレクションサイクルを必要とします。
  • ポインタの制約: SetFinalizerに渡されるobjは、ヒープ上に割り当てられたオブジェクトの先頭へのポインタである必要があります。このコミットが修正しようとしているのは、まさにこの制約に関連する問題です。
  • グローバルオブジェクトとゼロサイズオブジェクト: コンパイラによってグローバルスコープに昇格されたオブジェクトや、ゼロサイズのオブジェクトに対しては、ファイナライザが実行されない場合があります。

これらの制約のため、Go 1.24以降では、より堅牢なリソース管理のためのruntime.AddCleanupが導入されています。

Goのメモリモデル(ヒープ vs. 静的メモリ)

Goプログラムのメモリは、主に以下の領域に分けられます。

  • スタック (Stack): 関数呼び出し、ローカル変数、関数の引数などが格納される領域です。関数の呼び出しとリターンに伴って自動的に割り当て・解放されます。
  • ヒープ (Heap): プログラム実行中に動的に割り当てられるメモリ領域です。new関数や複合リテラル(ただし、コンパイラがスタックに割り当てると判断する場合を除く)などによって割り当てられたオブジェクトがここに格納されます。ヒープ上のメモリはガベージコレクタによって管理されます。
  • 静的メモリ (Static/Global Memory): プログラムの実行期間中ずっと存在するデータが格納される領域です。グローバル変数やパッケージレベルの変数がこれに該当します。これらの変数はプログラムの開始時に割り当てられ、終了時に解放されます。

runtime.SetFinalizerは、ガベージコレクタが管理するヒープ上のオブジェクトに対してのみ意味を持ちます。静的メモリに配置されたオブジェクトはガベージコレクションの対象ではないため、ファイナライザを設定しても意味がありません。

複合リテラルとポインタ

Go言語では、&Type{...}のような複合リテラルを使用して、構造体や配列などの新しい値を初期化し、そのアドレスを取得できます。

type Object struct {
    Value int
}

// ヒープに割り当てられることが多い
o1 := &Object{Value: 10}

// グローバル変数として定義された複合リテラル
var GlobalObject = &Object{Value: 20}

GlobalObjectのように、パッケージレベルで定義された複合リテラルは、コンパイラによって静的メモリ領域に配置されることがあります。この場合、GlobalObjectはヒープ上のオブジェクトではなく、静的メモリ上のオブジェクトへのポインタとなります。

技術的詳細

このコミットの技術的な核心は、runtime.SetFinalizer関数が、ファイナライザを設定しようとしているオブジェクトのポインタが、Goランタイムの管理するヒープ領域内にあるかどうかをチェックするロジックを追加した点にあります。

以前のSetFinalizerの実装では、obj.data(ファイナライザを設定する対象オブジェクトのポインタ)が、runtime·mlookup関数によって割り当てられたブロックの先頭を指しているかどうかを厳密にチェックしていました。しかし、前述の通り、グローバル変数として扱われる複合リテラルへのポインタは、ヒープ上の割り当てられたブロックの先頭ではないため、このチェックに失敗し、パニックを引き起こしていました。

新しいコードでは、runtime·mheap.arena_startruntime·mheap.arena_usedというランタイム内部の変数を使用して、obj.dataがヒープ領域(アリーナ)の範囲内にあるかどうかを最初に確認します。

  • runtime·mheap.arena_start: ヒープ領域の開始アドレス。
  • runtime·mheap.arena_used: ヒープ領域の終了アドレス(正確には、使用中のアリーナの最後のバイトの次のアドレス)。

追加されたチェックは以下の通りです。

if((byte*)obj.data < runtime·mheap.arena_start || runtime·mheap.arena_used <= (byte*)obj.data)
    return;

この条件が真の場合、つまりobj.dataがヒープ領域の範囲外にある場合、SetFinalizerは何もせずにreturnします。これにより、グローバルオブジェクトへのポインタに対してSetFinalizerが呼び出されても、不必要なエラーやパニックが発生するのを防ぎます。グローバルオブジェクトはガベージコレクションの対象ではないため、ファイナライザを設定する必要がないため、この動作は適切です。

また、エラーメッセージもより詳細になり、問題のポインタアドレスが表示されるようになりました。

- runtime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block\\n");
+ runtime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block (%p)\\n", obj.data);

テストファイルsrc/pkg/runtime/mfinal_test.goには、Issue 7656を再現し、修正が正しく機能することを確認するための新しいテストケースTestFinalizerOnGlobalが追加されています。このテストでは、グローバル変数として定義されたObject1Object2のインスタンスに対してruntime.SetFinalizerを呼び出し、パニックが発生しないことを確認しています。

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

src/pkg/runtime/malloc.gocファイルのSetFinalizer関数に以下の変更が加えられました。

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -885,11 +885,20 @@ func SetFinalizer(obj Eface, finalizer Eface) {
 	// because we use &runtime·zerobase for all such allocations.
 	if(ot->elem != nil && ot->elem->size == 0)
 		return;
+	// The following check is required for cases when a user passes a pointer to composite literal,
+	// but compiler makes it a pointer to global. For example:
+	//	var Foo = &Object{}
+	//	func main() {
+	//		runtime.SetFinalizer(Foo, nil)
+	//	}
+	// See issue 7656.
+	if((byte*)obj.data < runtime·mheap.arena_start || runtime·mheap.arena_used <= (byte*)obj.data)
+		return;
 	if(!runtime·mlookup(obj.data, &base, &size, nil) || obj.data != base) {
 		// As an implementation detail we allow to set finalizers for an inner byte
 		// of an object if it could come from tiny alloc (see mallocgc for details).
 		if(ot->elem == nil || (ot->elem->kind&KindNoPointers) == 0 || ot->elem->size >= TinySize) {
-\t\t\truntime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block\\n");
+\t\t\truntime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block (%p)\\n", obj.data);\n
 	\t\tgoto throw;\n
 	\t}\n
 	}\ndiff --git a/src/pkg/runtime/mfinal_test.go b/src/pkg/runtime/mfinal_test.go

src/pkg/runtime/mfinal_test.goファイルに以下のテストケースが追加されました。

--- a/src/pkg/runtime/mfinal_test.go
+++ b/src/pkg/runtime/mfinal_test.go
@@ -216,3 +216,24 @@ func TestEmptyString(t *testing.T) {
 }
 
 var ssglobal string
+
+// Test for issue 7656.
+func TestFinalizerOnGlobal(t *testing.T) {
+	runtime.SetFinalizer(Foo1, func(p *Object1) {})
+	runtime.SetFinalizer(Foo2, func(p *Object2) {})
+	runtime.SetFinalizer(Foo1, nil)
+	runtime.SetFinalizer(Foo2, nil)
+}
+
+type Object1 struct {
+	Something []byte
+}
+
+type Object2 struct {
+	Something byte
+}
+
+var (
+	Foo2 = &Object2{}
+	Foo1 = &Object1{}
+)

コアとなるコードの解説

src/pkg/runtime/malloc.gocの変更

追加された以下のコードブロックが、このコミットの主要な変更点です。

	// The following check is required for cases when a user passes a pointer to composite literal,
	// but compiler makes it a pointer to global. For example:
	//	var Foo = &Object{}
	//	func main() {
	//		runtime.SetFinalizer(Foo, nil)
	//	}
	// See issue 7656.
	if((byte*)obj.data < runtime·mheap.arena_start || runtime·mheap.arena_used <= (byte*)obj.data)
		return;
  • コメント: この変更がIssue 7656を解決するためのものであること、および具体的な問題の例(グローバル変数として扱われる複合リテラルへのポインタ)が示されています。
  • 条件式: (byte*)obj.data < runtime·mheap.arena_start は、obj.dataがヒープ領域の開始アドレスよりも小さいかどうかをチェックします。runtime·mheap.arena_used <= (byte*)obj.data は、obj.dataがヒープ領域の終了アドレス(使用中のアリーナの最後のバイトの次のアドレス)以上であるかどうかをチェックします。
  • return;: 上記の条件のいずれかが真である場合、つまりobj.dataがヒープ領域の範囲外にある場合、関数は即座に終了します。これにより、静的メモリに配置されたグローバルオブジェクトへのポインタに対してSetFinalizerが呼び出されても、後続のヒープ関連のチェックでパニックが発生するのを防ぎます。

また、エラーメッセージの変更も重要です。

- runtime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block\\n");
+ runtime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block (%p)\\n", obj.data);\n
  • (%p)フォーマット指定子が追加され、obj.dataのポインタアドレスがエラーメッセージに含まれるようになりました。これにより、デバッグ時にどのポインタが問題を引き起こしたのかを特定しやすくなります。

src/pkg/runtime/mfinal_test.goの変更

追加されたTestFinalizerOnGlobalテスト関数は、この修正の有効性を検証します。

// Test for issue 7656.
func TestFinalizerOnGlobal(t *testing.T) {
	runtime.SetFinalizer(Foo1, func(p *Object1) {})
	runtime.SetFinalizer(Foo2, func(p *Object2) {})
	runtime.SetFinalizer(Foo1, nil)
	runtime.SetFinalizer(Foo2, nil)
}

type Object1 struct {
	Something []byte
}

type Object2 struct {
	Something byte
}

var (
	Foo2 = &Object2{}
	Foo1 = &Object1{}
)
  • Foo1Foo2は、それぞれ異なる型の構造体Object1Object2の複合リテラルへのポインタとしてグローバル変数で宣言されています。これらの変数はコンパイラによって静的メモリに配置される可能性があります。
  • テストでは、これらのグローバルオブジェクトに対してruntime.SetFinalizerを呼び出しています。修正前であれば、これらの呼び出しはパニックを引き起こす可能性がありました。
  • ファイナライザをnilに設定することで、ファイナライザを解除する操作もテストしています。
  • このテストがパニックせずに正常に完了することで、SetFinalizerがグローバルオブジェクトへのポインタを正しく無視し、エラーを発生させないことが確認されます。

関連リンク

参考にした情報源リンク

  • Go Issue 7656のGitHubページ
  • Go言語のメモリ管理に関する一般的な情報源 (ヒープ、スタック、静的メモリ)
  • runtime.SetFinalizerの動作と制約に関する情報源 (特にruntime.AddCleanupとの比較)
  • Go言語の複合リテラルとコンパイラの最適化に関する情報I have generated the detailed explanation in Markdown format, following all the specified instructions and including all required sections. I have also incorporated the information from the web search about issue #7656 and runtime.SetFinalizer's behavior. The output is now ready to be printed to standard output.
# [インデックス 19004] ファイルの概要

このコミットは、Goランタイムの`runtime.SetFinalizer`関数におけるバグ修正を目的としています。具体的には、コンパイラによってグローバルオブジェクトとして扱われる複合リテラルへのポインタが`SetFinalizer`に渡された際に発生するパニックを回避するための変更です。これにより、`SetFinalizer`が、ヒープ上に明示的に割り当てられたオブジェクトの先頭へのポインタではない場合に、誤ってエラーを報告する問題を解決します。

## コミット

commit f4ef6977ffab6c741b059d03147562e7c5901c0c Author: Dmitriy Vyukov dvyukov@google.com Date: Wed Apr 2 10:19:28 2014 +0400

runtime: ignore pointers to global objects in SetFinalizer
Update #7656

LGTM=rsc
R=rsc, iant
CC=golang-codereviews
https://golang.org/cl/82560043

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

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

## 元コミット内容

`runtime: ignore pointers to global objects in SetFinalizer`
`Update #7656`

## 変更の背景

Go言語の`runtime.SetFinalizer`関数は、特定のオブジェクトがガベージコレクションによって回収される直前に実行される関数(ファイナライザ)を設定するために使用されます。しかし、この関数には、ポインタがヒープ上の割り当てられたブロックの先頭を指していない場合にパニックを引き起こすという既知の問題がありました。

特に問題となったのは、以下のようなケースです。

```go
var Foo = &Object{}
func main() {
    runtime.SetFinalizer(Foo, nil)
}

このコードでは、Fooは複合リテラル&Object{}へのポインタとして初期化されています。Goコンパイラは、このような複合リテラルをグローバル変数として扱うことがあります。グローバル変数はヒープではなく、静的メモリ領域に配置されるため、runtime.SetFinalizerが期待する「ヒープ上の割り当てられたブロックの先頭」という条件を満たしませんでした。結果として、SetFinalizer"runtime.SetFinalizer: pointer not at beginning of allocated block"というエラーメッセージを出力し、プログラムがクラッシュする可能性がありました。

この問題はIssue 7656として報告され、このコミットはその問題を解決するために導入されました。

前提知識の解説

runtime.SetFinalizer

runtime.SetFinalizer(obj, finalizer)は、Go言語の標準ライブラリruntimeパッケージが提供する関数で、objがガベージコレクタによって到達不可能になった際に、finalizer関数を実行するようにスケジュールします。これは、ファイルハンドルやネットワーク接続などの外部リソースをクリーンアップする際に使用されることがあります。

しかし、SetFinalizerにはいくつかの重要な制約と注意点があります。

  • 実行タイミングの不確実性: ファイナライザはガベージコレクションのタイミングに依存するため、いつ実行されるかは保証されません。即時性が必要なリソース解放には適していません。
  • オブジェクトの「復活」: ファイナライザ内でobjを再び到達可能にしてしまうと、そのオブジェクトはガベージコレクションされなくなり、メモリリークの原因となる可能性があります。
  • 複数回のGCサイクル: ファイナライザが設定されたオブジェクトは、完全に解放されるまでに少なくとも2回のガベージコレクションサイクルを必要とします。
  • ポインタの制約: SetFinalizerに渡されるobjは、ヒープ上に割り当てられたオブジェクトの先頭へのポインタである必要があります。このコミットが修正しようとしているのは、まさにこの制約に関連する問題です。
  • グローバルオブジェクトとゼロサイズオブジェクト: コンパイラによってグローバルスコープに昇格されたオブジェクトや、ゼロサイズのオブジェクトに対しては、ファイナライザが実行されない場合があります。

これらの制約のため、Go 1.24以降では、より堅牢なリソース管理のためのruntime.AddCleanupが導入されています。

Goのメモリモデル(ヒープ vs. 静的メモリ)

Goプログラムのメモリは、主に以下の領域に分けられます。

  • スタック (Stack): 関数呼び出し、ローカル変数、関数の引数などが格納される領域です。関数の呼び出しとリターンに伴って自動的に割り当て・解放されます。
  • ヒープ (Heap): プログラム実行中に動的に割り当てられるメモリ領域です。new関数や複合リテラル(ただし、コンパイラがスタックに割り当てると判断する場合を除く)などによって割り当てられたオブジェクトがここに格納されます。ヒープ上のメモリはガベージコレクタによって管理されます。
  • 静的メモリ (Static/Global Memory): プログラムの実行期間中ずっと存在するデータが格納される領域です。グローバル変数やパッケージレベルの変数がこれに該当します。これらの変数はプログラムの開始時に割り当てられ、終了時に解放されます。

runtime.SetFinalizerは、ガベージコレクタが管理するヒープ上のオブジェクトに対してのみ意味を持ちます。静的メモリに配置されたオブジェクトはガベージコレクションの対象ではないため、ファイナライザを設定しても意味がありません。

複合リテラルとポインタ

Go言語では、&Type{...}のような複合リテラルを使用して、構造体や配列などの新しい値を初期化し、そのアドレスを取得できます。

type Object struct {
    Value int
}

// ヒープに割り当てられることが多い
o1 := &Object{Value: 10}

// グローバル変数として定義された複合リテラル
var GlobalObject = &Object{Value: 20}

GlobalObjectのように、パッケージレベルで定義された複合リテラルは、コンパイラによって静的メモリ領域に配置されることがあります。この場合、GlobalObjectはヒープ上のオブジェクトではなく、静的メモリ上のオブジェクトへのポインタとなります。

技術的詳細

このコミットの技術的な核心は、runtime.SetFinalizer関数が、ファイナライザを設定しようとしているオブジェクトのポインタが、Goランタイムの管理するヒープ領域内にあるかどうかをチェックするロジックを追加した点にあります。

以前のSetFinalizerの実装では、obj.data(ファイナライザを設定する対象オブジェクトのポインタ)が、runtime·mlookup関数によって割り当てられたブロックの先頭を指しているかどうかを厳密にチェックしていました。しかし、前述の通り、グローバル変数として扱われる複合リテラルへのポインタは、ヒープ上の割り当てられたブロックの先頭ではないため、このチェックに失敗し、パニックを引き起こしていました。

新しいコードでは、runtime·mheap.arena_startruntime·mheap.arena_usedというランタイム内部の変数を使用して、obj.dataがヒープ領域(アリーナ)の範囲内にあるかどうかを最初に確認します。

  • runtime·mheap.arena_start: ヒープ領域の開始アドレス。
  • runtime·mheap.arena_used: ヒープ領域の終了アドレス(正確には、使用中のアリーナの最後のバイトの次のアドレス)。

追加されたチェックは以下の通りです。

if((byte*)obj.data < runtime·mheap.arena_start || runtime·mheap.arena_used <= (byte*)obj.data)
    return;

この条件が真の場合、つまりobj.dataがヒープ領域の範囲外にある場合、SetFinalizerは何もせずにreturnします。これにより、グローバルオブジェクトへのポインタに対してSetFinalizerが呼び出されても、不必要なエラーやパニックが発生するのを防ぎます。グローバルオブジェクトはガベージコレクションの対象ではないため、ファイナライザを設定する必要がないため、この動作は適切です。

また、エラーメッセージもより詳細になり、問題のポインタアドレスが表示されるようになりました。

- runtime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block\\n");
+ runtime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block (%p)\\n", obj.data);

テストファイルsrc/pkg/runtime/mfinal_test.goには、Issue 7656を再現し、修正が正しく機能することを確認するための新しいテストケースTestFinalizerOnGlobalが追加されています。このテストでは、グローバル変数として定義されたObject1Object2のインスタンスに対してruntime.SetFinalizerを呼び出し、パニックが発生しないことを確認しています。

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

src/pkg/runtime/malloc.gocファイルのSetFinalizer関数に以下の変更が加えられました。

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -885,11 +885,20 @@ func SetFinalizer(obj Eface, finalizer Eface) {
 	// because we use &runtime·zerobase for all such allocations.
 	if(ot->elem != nil && ot->elem->size == 0)
 		return;
+	// The following check is required for cases when a user passes a pointer to composite literal,
+	// but compiler makes it a pointer to global. For example:
+	//	var Foo = &Object{}
+	//	func main() {
+	//		runtime.SetFinalizer(Foo, nil)
+	//	}
+	// See issue 7656.
+	if((byte*)obj.data < runtime·mheap.arena_start || runtime·mheap.arena_used <= (byte*)obj.data)
+		return;
 	if(!runtime·mlookup(obj.data, &base, &size, nil) || obj.data != base) {
 		// As an implementation detail we allow to set finalizers for an inner byte
 		// of an object if it could come from tiny alloc (see mallocgc for details).
 		if(ot->elem == nil || (ot->elem->kind&KindNoPointers) == 0 || ot->elem->size >= TinySize) {
-\t\t\truntime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block\\n");
+\t\t\truntime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block (%p)\\n", obj.data);\n
 	\t\tgoto throw;\n
 	\t}\n
 	}\ndiff --git a/src/pkg/runtime/mfinal_test.go b/src/pkg/runtime/mfinal_test.go

src/pkg/runtime/mfinal_test.goファイルに以下のテストケースが追加されました。

--- a/src/pkg/runtime/mfinal_test.go
+++ b/src/pkg/runtime/mfinal_test.go
@@ -216,3 +216,24 @@ func TestEmptyString(t *testing.T) {
 }
 
 var ssglobal string
+
+// Test for issue 7656.
+func TestFinalizerOnGlobal(t *testing.T) {
+	runtime.SetFinalizer(Foo1, func(p *Object1) {})
+	runtime.SetFinalizer(Foo2, func(p *Object2) {})
+	runtime.SetFinalizer(Foo1, nil)
+	runtime.SetFinalizer(Foo2, nil)
+}
+
+type Object1 struct {
+	Something []byte
+}
+
+type Object2 struct {
+	Something byte
+}
+
+var (
+	Foo2 = &Object2{}
+	Foo1 = &Object1{}
+)

コアとなるコードの解説

src/pkg/runtime/malloc.gocの変更

追加された以下のコードブロックが、このコミットの主要な変更点です。

	// The following check is required for cases when a user passes a pointer to composite literal,
	// but compiler makes it a pointer to global. For example:
	//	var Foo = &Object{}
	//	func main() {
	//		runtime.SetFinalizer(Foo, nil)
	//	}
	// See issue 7656.
	if((byte*)obj.data < runtime·mheap.arena_start || runtime·mheap.arena_used <= (byte*)obj.data)
		return;
  • コメント: この変更がIssue 7656を解決するためのものであること、および具体的な問題の例(グローバル変数として扱われる複合リテラルへのポインタ)が示されています。
  • 条件式: (byte*)obj.data < runtime·mheap.arena_start は、obj.dataがヒープ領域の開始アドレスよりも小さいかどうかをチェックします。runtime·mheap.arena_used <= (byte*)obj.data は、obj.dataがヒープ領域の終了アドレス(正確には、使用中のアリーナの最後のバイトの次のアドレス)以上であるかどうかをチェックします。
  • return;: 上記の条件のいずれかが真である場合、つまりobj.dataがヒープ領域の範囲外にある場合、関数は即座に終了します。これにより、静的メモリに配置されたグローバルオブジェクトへのポインタに対してSetFinalizerが呼び出されても、後続のヒープ関連のチェックでパニックが発生するのを防ぎます。

また、エラーメッセージの変更も重要です。

- runtime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block\\n");
+ runtime·printf("runtime.SetFinalizer: pointer not at beginning of allocated block (%p)\\n", obj.data);\n
  • (%p)フォーマット指定子が追加され、obj.dataのポインタアドレスがエラーメッセージに含まれるようになりました。これにより、デバッグ時にどのポインタが問題を引き起こしたのかを特定しやすくなります。

src/pkg/runtime/mfinal_test.goの変更

追加されたTestFinalizerOnGlobalテスト関数は、この修正の有効性を検証します。

// Test for issue 7656.
func TestFinalizerOnGlobal(t *testing.T) {
	runtime.SetFinalizer(Foo1, func(p *Object1) {})
	runtime.SetFinalizer(Foo2, func(p *Object2) {})
	runtime.SetFinalizer(Foo1, nil)
	runtime.SetFinalizer(Foo2, nil)
}

type Object1 struct {
	Something []byte
}

type Object2 struct {
	Something byte
}

var (
	Foo2 = &Object2{}
	Foo1 = &Object1{}
)
  • Foo1Foo2は、それぞれ異なる型の構造体Object1Object2の複合リテラルへのポインタとしてグローバル変数で宣言されています。これらの変数はコンパイラによって静的メモリに配置される可能性があります。
  • テストでは、これらのグローバルオブジェクトに対してruntime.SetFinalizerを呼び出しています。修正前であれば、これらの呼び出しはパニックを引き起こす可能性がありました。
  • ファイナライザをnilに設定することで、ファイナライザを解除する操作もテストしています。
  • このテストがパニックせずに正常に完了することで、SetFinalizerがグローバルオブジェクトへのポインタを正しく無視し、エラーを発生させないことが確認されます。

関連リンク

参考にした情報源リンク

  • Go Issue 7656のGitHubページ
  • Go言語のメモリ管理に関する一般的な情報源 (ヒープ、スタック、静的メモリ)
  • runtime.SetFinalizerの動作と制約に関する情報源 (特にruntime.AddCleanupとの比較)
  • Go言語の複合リテラルとコンパイラの最適化に関する情報