[インデックス 11053] ファイルの概要
このコミットは、Goコンパイラ(gc
)において、エクスポートされる再帰的なインターフェースのサイズに制限を設ける変更を導入しています。これにより、エッジケースの再帰型が過剰なメモリを消費するのを防ぎ、コンパイラの安定性と効率性を向上させることを目的としています。
コミット
commit aa63a928ea6b2fb6b2edb10fd8d98c98f20d5274
Author: Lorenzo Stoakes <lstoakes@gmail.com>
Date: Mon Jan 9 11:48:53 2012 -0500
gc: put limit on size of exported recursive interface
Prevents edge-case recursive types from consuming excessive memory.
Fixes #1909.
R=golang-dev, lvd, rsc
CC=golang-dev
https://golang.org/cl/5504108
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aa63a928ea6b2fb6b2edb10fd8d98c98f20d5274
元コミット内容
gc: put limit on size of exported recursive interface
Prevents edge-case recursive types from consuming excessive memory.
Fixes #1909.
R=golang-dev, lvd, rsc
CC=golang-dev
https://golang.org/cl/5504108
変更の背景
Go言語のコンパイラ(gc
)は、プログラムの型情報を処理し、必要に応じてエクスポートします。このプロセスにおいて、再帰的に定義された型、特にインターフェース型が深くネストされている場合、コンパイラがその型情報を処理する際に過剰なメモリを消費する可能性がありました。これは、コンパイラが型の文字列表現を生成する際や、型構造を走査する際に無限ループに近い状態に陥ることで発生し得ます。
この問題は、特定の「エッジケース」の再帰型で顕著になり、コンパイラのクラッシュやパフォーマンスの著しい低下を引き起こす可能性がありました。コミットメッセージにある Fixes #1909
は、この問題がGoのIssue Trackerで報告されていたことを示しており、このコミットはその報告された問題を解決するために行われました。
前提知識の解説
- Goコンパイラ (
gc
): Go言語の公式コンパイラです。ソースコードを機械語に変換する役割を担います。型チェック、最適化、コード生成など、コンパイルプロセスの様々な段階で型情報を扱います。 - 再帰型 (Recursive Types): 自身の定義の中に自身を含む型のことです。例えば、リンクリストのノードが次のノードへのポインタを持つ場合などが典型的な例です。インターフェース型も、そのメソッドシグネチャが再帰的に別のインターフェース型を参照するような場合に再帰的になり得ます。
type Node struct { Value int Next *Node } type RecursiveInterface interface { Method() RecursiveInterface }
src/cmd/gc/fmt.c
: Goコンパイラのソースコードの一部で、主に型のフォーマット(文字列表現への変換)を担当するC言語のファイルです。コンパイラ内部で型情報をデバッグ出力したり、エラーメッセージに含めたりする際に使用されます。Tconv
関数:src/cmd/gc/fmt.c
内に存在する関数で、Goの型 (Type
構造体) を文字列表現に変換する役割を担っています。この関数は、型の構造を再帰的に辿って文字列を構築します。t->trecur
:Type
構造体のメンバーで、Tconv
関数が特定の型を再帰的に処理している深さを追跡するために使用されます。これにより、無限再帰を防ぐための基本的なチェックが行われます。fp->nfmt
:Fmt
構造体のメンバーで、フォーマット処理中に生成された文字の総数を追跡するために使用されます。これは、出力される文字列の全体的なサイズや複雑さを示す指標となります。
技術的詳細
このコミットが対処している問題は、Tconv
関数が再帰的な型を文字列表現に変換する際に発生する可能性のあるメモリ消費の増大です。既存の t->trecur > 4
というチェックは、型の再帰深度が4を超えた場合に <...>
という省略記号を出力することで、無限再帰を防ぐためのものでした。しかし、このチェックだけでは、再帰深度が浅くても、非常に複雑な型構造を持つ場合に、生成される文字列が非常に長くなり、結果としてコンパイラのメモリ使用量が急増する可能性がありました。
具体的には、インターフェース型が多数のメソッドを持ち、それらのメソッドがさらに複雑な再帰型を参照しているようなシナリオが考えられます。このような場合、Tconv
関数は型の文字列表現を生成するために、大量のメモリを一時的に割り当てる必要がありました。
このコミットでは、この問題に対処するために、生成される文字列の全体的なサイズにも制限を設けるというアプローチが取られました。fp->nfmt
は、フォーマット処理中に書き込まれた文字の総数をカウントします。この値が一定の閾値(この場合は1000)を超えた場合も、型表現を <...>
に切り詰めることで、メモリ消費を抑制します。
これにより、再帰深度が深くなくても、生成される文字列が過度に長くなることを防ぎ、コンパイラのメモリ使用量を予測可能な範囲に保つことができます。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/fmt.c b/src/cmd/gc/fmt.c
index f3be53c8fb..3013d0d329 100644
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1450,7 +1450,7 @@ Tconv(Fmt *fp)
if(t == T)
return fmtstrcpy(fp, "<T>");
- if(t->trecur > 4)
+ if(t->trecur > 4 || fp->nfmt > 1000)
return fmtstrcpy(fp, "<...>");
t->trecur++;
コアとなるコードの解説
変更は src/cmd/gc/fmt.c
ファイル内の Tconv
関数の一箇所のみです。
元のコード:
if(t->trecur > 4)
変更後のコード:
if(t->trecur > 4 || fp->nfmt > 1000)
この変更は、型の文字列表現を省略する条件に fp->nfmt > 1000
という新しい条件を追加しています。
t->trecur > 4
: これは既存のチェックで、型の再帰深度が4を超えた場合に、無限再帰を防ぐために型表現を<...>
に切り詰めます。fp->nfmt > 1000
: これが新たに追加された条件です。fp->nfmt
は、現在のフォーマット処理で既に書き込まれた文字の総数を表します。この値が1000を超えた場合、つまり生成される文字列が既に1000文字を超えている場合、それ以上文字列を生成するのをやめて<...>
に切り詰めます。
この論理和 (||
) によって、どちらかの条件が真であれば、型表現は <...>
となり、それ以上のメモリ消費や処理時間の増大を防ぎます。これにより、再帰深度が浅くても、非常に長い型表現が生成されることによるメモリ問題が緩和されます。
関連リンク
- Go Issue #1909: https://golang.org/issue/1909
- Go CL 5504108: https://golang.org/cl/5504108
参考にした情報源リンク
- Go Issue Tracker (golang.org)
- Go Code Review (golang.org/cl)
- Go言語のソースコード (
src/cmd/gc/fmt.c
)