[インデックス 12135] ファイルの概要
このコミットは、Goコンパイラ(gc
)のソースコードの一部である src/cmd/gc/fmt.c
ファイルに対する変更です。このファイルは、コンパイラ内部のデータ構造、特に抽象構文木(AST)のノード(Node
)をデバッグ目的などでフォーマットし、出力するためのルーチンを含んでいます。
コミット
- コミットハッシュ:
6c7daca23618b97e9f07b05ac8bf072a636fb616
- 作者: Russ Cox rsc@golang.org
- 日付: Wed Feb 22 00:29:23 2012 -0500
- コミットメッセージ:
gc: never crash during a debugging print TBR=lvd CC=golang-dev https://golang.org/cl/5686063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6c7daca23618b97e9f07b05ac8bf072a636fb616
元コミット内容
commit 6c7daca23618b97e9f07b05ac8bf072a636fb616
Author: Russ Cox <rsc@golang.org>
Date: Wed Feb 22 00:29:23 2012 -0500
gc: never crash during a debugging print
TBR=lvd
CC=golang-dev
https://golang.org/cl/5686063
---
src/cmd/gc/fmt.c | 6 +-----\n 1 file changed, 1 insertion(+), 5 deletions(-)\n
diff --git a/src/cmd/gc/fmt.c b/src/cmd/gc/fmt.c
index 093b276f07..5672c00103 100644
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1346,15 +1346,11 @@ nodefmt(Fmt *f, Node *n)\n Type *t;\n \n t = n->type;\n- if(n->orig == N) {\n-\t n->orig = n;\n-\t fatal("node with no orig %N", n);\n- }\n \n // we almost always want the original, except in export mode for literals\n // this saves the importer some work, and avoids us having to redo some\n // special casing for package unsafe\n- if(fmtmode != FExp || n->op != OLITERAL)\n+ if((fmtmode != FExp || n->op != OLITERAL) && n->orig != N)\n n = n->orig;\n \n if(f->flags&FmtLong && t != T) {\n```
## 変更の背景
このコミットの背景には、Goコンパイラ(`gc`)がデバッグ情報を出力する際にクラッシュする可能性があったという問題があります。具体的には、コンパイラ内部で抽象構文木(AST)のノード(`Node`)を処理する際、特定の状況下で `Node` の `orig` フィールドが設定されていない場合に、`fatal` 関数が呼び出され、コンパイラが異常終了していました。
`n->orig` は、ASTノードが変換(例えば、最適化や型チェック)される前の元のノードを指すポインタです。通常、ノードには元のノードが存在しますが、リテラル(`OLITERAL`)のような特定の種類のノードがエクスポートモード(`FExp`)で処理される場合など、一部のケースでは `n->orig` が意図的に `N`(null/nilに相当)であることがあります。
元のコードでは、`nodefmt` 関数内で `n->orig == N` の場合に無条件で `fatal` エラーを発生させていました。これは、`n->orig` が `N` であることが予期せぬ状態であると見なされていたためです。しかし、デバッグプリントのコンテキストでは、`n->orig` が `N` であることが正当な場合があり、その際にコンパイラがクラッシュするのは望ましくありませんでした。このコミットは、この不必要なクラッシュを防ぐことを目的としています。
## 前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造と概念に関する知識が必要です。
* **Goコンパイラ (`gc`)**: Go言語の公式コンパイラです。`src/cmd/gc` はそのソースコードの一部であり、コンパイラのフロントエンド(構文解析、型チェックなど)とバックエンド(コード生成)の大部分を担っています。
* **抽象構文木 (AST)**: ソースコードを解析して得られる、プログラムの構造を木構造で表現したものです。コンパイラはASTを操作して、様々な変換や最適化を行います。
* **`Node`**: Goコンパイラ内部でASTの各要素(変数、関数呼び出し、リテラルなど)を表すデータ構造です。`Node` には、その種類(`op` フィールド)、型(`type` フィールド)、そして元のノードへの参照(`orig` フィールド)など、様々な情報が含まれます。
* **`n->orig`**: `Node` 構造体内のフィールドで、現在のノードが変換や最適化によって生成されたものである場合に、その変換前の元のノードを指します。コンパイラの処理過程でASTノードが変化しても、元のソースコードのどの部分に対応するかを追跡するために使用されます。`N` は、Goコンパイラ内部で `Node` ポインタのnull値を示す定数です。
* **`fmt.c`**: Goコンパイラのソースコード内で、内部データ構造(特にASTノード)を人間が読める形式にフォーマットするための関数群が定義されているファイルです。デバッグ出力や、コンパイラの異なるステージ間で情報をやり取りする際などに利用されます。
* **`nodefmt` 関数**: `fmt.c` 内に定義されている関数で、特定の `Node` オブジェクトをフォーマットして出力する役割を担います。
* **`Fmt` 構造体と `fmtmode`**: `Fmt` はフォーマットのコンテキストを保持する構造体で、`fmtmode` は現在のフォーマットモードを示すフラグです。
* **`FExp` (Export Mode)**: コンパイラが内部データを外部にエクスポートする際(例えば、他のパッケージに公開される型や関数を表現する際)に使用されるモードです。このモードでは、内部的な詳細が省略されたり、特定の形式で表現されたりすることがあります。
* **`OLITERAL`**: `Node` の `op` フィールドが取りうる値の一つで、そのノードが数値、文字列、真偽値などのリテラルを表すことを示します。
* **`fatal` 関数**: Goコンパイラ内部で、回復不能なエラーが発生した場合にプログラムを即座に終了させるために使用される関数です。通常、予期せぬ内部状態やバグが検出された際に呼び出されます。
## 技術的詳細
このコミットの技術的な変更は、`src/cmd/gc/fmt.c` 内の `nodefmt` 関数に集中しています。
元のコードでは、`nodefmt` 関数の冒頭で、引数として渡された `Node *n` の `orig` フィールドが `N`(null)であるかどうかをチェックしていました。
```c
if(n->orig == N) {
n->orig = n;
fatal("node with no orig %N", n);
}
このコードは、「Node
に orig
が設定されていないのは異常な状態である」という前提に基づいていました。もし n->orig
が N
であれば、それはバグであると見なし、fatal
関数を呼び出してコンパイラをクラッシュさせていました。
しかし、コメントにあるように、「リテラルをエクスポートモードで扱う場合を除き、ほとんどの場合、元のノードが必要」というロジックが存在します。
// we almost always want the original, except in export mode for literals
// this saves the importer some work, and avoids us having to redo some
// special casing for package unsafe
if(fmtmode != FExp || n->op != OLITERAL)
n = n->orig;
この if
文は、「エクスポートモード(FExp
)でなく、かつリテラル(OLITERAL
)でない場合」に、現在のノード n
をその元のノード n->orig
に置き換えるという処理を行っていました。
問題は、エクスポートモードのリテラルなど、一部の正当なケースで n->orig
が N
であるにもかかわらず、最初の if(n->orig == N)
のチェックで fatal
が呼び出されてしまっていたことです。これは、デバッグプリントのコンテキストでは、n->orig
が N
であることが必ずしもエラーではない状況が存在したためです。
このコミットでは、この問題を解決するために、最初の if(n->orig == N)
ブロック(fatal
を含む)を完全に削除しました。そして、n
を n->orig
に置き換える条件をより厳密にしました。
変更後のコードは以下のようになります。
if((fmtmode != FExp || n->op != OLITERAL) && n->orig != N)
n = n->orig;
この変更により、n
を n->orig
に置き換えるのは、以下の両方の条件が満たされる場合に限られます。
fmtmode
がFExp
でない、またはn->op
がOLITERAL
でない(つまり、エクスポートモードのリテラルではない)。- かつ
n->orig
がN
ではない。
この修正により、n->orig
が N
である場合でも、それがエクスポートモードのリテラルであるなど、正当な理由がある場合には fatal
が呼び出されることなく、n
が n->orig
に置き換えられない(つまり、現在の n
がそのまま使用される)ようになります。これにより、デバッグプリント時の不必要なクラッシュが回避されます。
コアとなるコードの変更箇所
--- a/src/cmd/gc/fmt.c
+++ b/src/cmd/gc/fmt.c
@@ -1346,15 +1346,11 @@ nodefmt(Fmt *f, Node *n)\n Type *t;\n \n t = n->type;\n- if(n->orig == N) {\n-\t n->orig = n;\n-\t fatal("node with no orig %N", n);\n- }\n \n // we almost always want the original, except in export mode for literals\n // this saves the importer some work, and avoids us having to redo some\n // special casing for package unsafe\n- if(fmtmode != FExp || n->op != OLITERAL)\n+ if((fmtmode != FExp || n->op != OLITERAL) && n->orig != N)\n n = n->orig;\n \n if(f->flags&FmtLong && t != T) {\n```
## コアとなるコードの解説
このコミットのコアとなる変更は、`nodefmt` 関数内の2つの部分です。
1. **`fatal` を含むブロックの削除**:
```c
- if(n->orig == N) {
- n->orig = n;
- fatal("node with no orig %N", n);
- }
```
このブロックは、`Node` の `orig` フィールドが `N`(null)である場合に、コンパイラを強制終了させる `fatal` 関数を呼び出していました。このコミットでは、このチェックとそれに続くクラッシュロジックが完全に削除されました。これは、`n->orig` が `N` であることが必ずしもエラーではない状況が存在するという認識に基づいています。
2. **`if` 条件の変更**:
```c
- if(fmtmode != FExp || n->op != OLITERAL)
+ if((fmtmode != FExp || n->op != OLITERAL) && n->orig != N)
```
この行は、`n` をその元のノード `n->orig` に置き換えるかどうかを決定する条件を修正しています。
* **変更前**: `fmtmode` が `FExp` でない、または `n->op` が `OLITERAL` でない場合に `n = n->orig` を実行していました。この条件は、エクスポートモードのリテラル以外のすべてのケースで `orig` ノードを使用しようとします。
* **変更後**: 上記の条件に加えて、`n->orig != N` という条件が追加されました。これにより、`n` を `n->orig` に置き換えるのは、**エクスポートモードのリテラルではない** **かつ** **`n->orig` が実際に存在する場合**に限定されます。
この変更の組み合わせにより、`n->orig` が `N` であることが正当なケース(特にエクスポートモードのリテラル)において、コンパイラが `fatal` でクラッシュすることなく、現在の `n` をそのまま使用してデバッグプリントを続行できるようになりました。これは、コンパイラの堅牢性を向上させ、デバッグ時の利便性を高めるための重要な修正です。
## 関連リンク
* Go Gerrit Code Review: [https://golang.org/cl/5686063](https://golang.org/cl/5686063)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (Go Compiler Internalsに関する公開ドキュメントがあれば)
* Go言語のソースコード (特に `src/cmd/gc` ディレクトリ内の他のファイル)
* Go言語のIssue Tracker (関連するバグ報告や議論があれば)
* Go言語のメーリングリスト (golang-devなど、関連する議論があれば)