[インデックス 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_start
とruntime·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
が追加されています。このテストでは、グローバル変数として定義されたObject1
とObject2
のインスタンスに対して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{}
)
Foo1
とFoo2
は、それぞれ異なる型の構造体Object1
とObject2
の複合リテラルへのポインタとしてグローバル変数で宣言されています。これらの変数はコンパイラによって静的メモリに配置される可能性があります。- テストでは、これらのグローバルオブジェクトに対して
runtime.SetFinalizer
を呼び出しています。修正前であれば、これらの呼び出しはパニックを引き起こす可能性がありました。 - ファイナライザを
nil
に設定することで、ファイナライザを解除する操作もテストしています。 - このテストがパニックせずに正常に完了することで、
SetFinalizer
がグローバルオブジェクトへのポインタを正しく無視し、エラーを発生させないことが確認されます。
関連リンク
- Go Issue 7656: runtime: runtime.SetFinalizer: pointer not at beginning of allocated block
- Go Documentation:
runtime.SetFinalizer
(Goのバージョンによって内容が異なる場合があります。最新のドキュメントを参照してください。)
参考にした情報源リンク
- 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_start
とruntime·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
が追加されています。このテストでは、グローバル変数として定義されたObject1
とObject2
のインスタンスに対して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{}
)
Foo1
とFoo2
は、それぞれ異なる型の構造体Object1
とObject2
の複合リテラルへのポインタとしてグローバル変数で宣言されています。これらの変数はコンパイラによって静的メモリに配置される可能性があります。- テストでは、これらのグローバルオブジェクトに対して
runtime.SetFinalizer
を呼び出しています。修正前であれば、これらの呼び出しはパニックを引き起こす可能性がありました。 - ファイナライザを
nil
に設定することで、ファイナライザを解除する操作もテストしています。 - このテストがパニックせずに正常に完了することで、
SetFinalizer
がグローバルオブジェクトへのポインタを正しく無視し、エラーを発生させないことが確認されます。
関連リンク
- Go Issue 7656: runtime: runtime.SetFinalizer: pointer not at beginning of allocated block
- Go Documentation:
runtime.SetFinalizer
(Goのバージョンによって内容が異なる場合があります。最新のドキュメントを参照してください。)
参考にした情報源リンク
- Go Issue 7656のGitHubページ
- Go言語のメモリ管理に関する一般的な情報源 (ヒープ、スタック、静的メモリ)
runtime.SetFinalizer
の動作と制約に関する情報源 (特にruntime.AddCleanup
との比較)- Go言語の複合リテラルとコンパイラの最適化に関する情報