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

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

このコミットは、Go言語のコンパイラ(gc)における特定のクラッシュバグを修正するものです。具体的には、go tool gc -V コマンドを実行した際に発生するクラッシュを解消します。この問題は、バージョン情報を出力する際の print 関数のフォーマット文字列と引数の不一致によって引き起こされていました。

コミット

commit 8b92066e31e127f7193c29e716af39a06ec0b82a
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Wed Oct 26 16:16:46 2011 +0900

    gc: fix [568]g -V crash
    
    R=lvd
    CC=golang-dev
    https://golang.org/cl/5314060
---
 src/cmd/gc/lex.c | 2 +--
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c
index 1d7f0e82e3..6e66b502fb 100644
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -229,7 +229,7 @@ main(int argc, char *argv[])
  	\tp = expstring();
  	\tif(strcmp(p, "X:none") == 0)
  	\t\tp = "";
-\t\tprint("%cg version %s%s%s%s\\n", thechar, getgoversion(), *p ? " " : "", p);\n
+\t\tprint("%cg version %s%s%s\\n", thechar, getgoversion(), *p ? " " : "", p);\n
  	\texits(0);\
  	} ARGEND

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

https://github.com/golang/go/commit/8b92066e31e127f7193c29e716af39a06ec0b82a

元コミット内容

gc: fix [568]g -V crash

R=lvd
CC=golang-dev
https://golang.org/cl/5314060

変更の背景

このコミットは、Goコンパイラ(gc)が -V フラグ付きで実行された際に発生するクラッシュを修正するために行われました。具体的には、go tool gc -V のようにバージョン情報を表示しようとすると、コンパイラが異常終了するというバグが存在していました。

このクラッシュの原因は、バージョン情報を出力するために使用される print 関数(C言語の printf に相当)のフォーマット文字列に、引数よりも多くのフォーマット指定子(%s)が含まれていたことにあります。これにより、print 関数は存在しないメモリ領域から引数を読み込もうとし、結果としてセグメンテーション違反などのクラッシュを引き起こしていました。

コミットメッセージにある [568]g は、このバグがGoのバグトラッカーで568番目の問題として報告されていたことを示唆しています。この修正は、コンパイラの安定性と信頼性を向上させるために不可欠でした。

前提知識の解説

  • Go コンパイラ (gc): Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担います。通常、go buildgo run コマンドを通じて間接的に利用されますが、go tool gc のように直接呼び出すことも可能です。
  • go tool: Goツールチェインの一部であり、Go言語のビルド、テスト、デバッグなど、様々な開発タスクを支援するサブコマンド群を提供します。go tool gc はコンパイラを直接実行するためのコマンドです。
  • -V フラグ: 多くのコマンドラインツールで、バージョン情報を表示するために使用される共通のフラグです。go tool gc -V は、Goコンパイラのバージョン情報を出力することを意図しています。
  • src/cmd/gc/lex.c: Goコンパイラのソースコードの一部です。ファイル名から字句解析(lexical analysis)に関連するコードが含まれていることが示唆されますが、この特定のコンテキストでは、コンパイラのメインエントリポイント(main 関数)が含まれており、コマンドライン引数の処理やバージョン情報の出力ロジックが配置されています。
  • print 関数: Goコンパイラの内部で使用される、C言語の printf に似た関数です。フォーマット文字列とそれに続く可変個の引数を受け取り、指定されたフォーマットで文字列を出力します。フォーマット文字列内の %c%s などのフォーマット指定子は、対応する引数の型と位置を示します。
  • getgoversion(): Goコンパイラの現在のバージョン文字列を返す内部関数です。
  • expstring(): "experimental string" の略である可能性があり、Goコンパイラの特定のビルドや実験的な機能に関する追加情報(例: リビジョンハッシュ、ビルド日時など)を取得するために使用される内部関数です。
  • 三項演算子 (*p ? " " : ""): C言語の構文で、条件 ? 真の場合の値 : 偽の場合の値 という形式を取ります。この場合、p が指す文字列が空でなければスペース " " を、空であれば空文字列 "" を返します。これは、バージョン情報と expstring から得られる追加情報との間に適切な区切り文字を挿入するために使用されます。

技術的詳細

このバグは、src/cmd/gc/lex.c ファイル内の main 関数において、バージョン情報を出力する print 関数の呼び出しに問題があったために発生しました。

元のコードでは、print 関数のフォーマット文字列は "%cg version %s%s%s%s\\n" となっていました。このフォーマット文字列には、以下の5つのフォーマット指定子が含まれています。

  1. %c: thechar (おそらくコンパイラの種類を示す単一文字)
  2. %s: getgoversion() (Goのバージョン文字列)
  3. %s: *p ? " " : "" (バージョンと追加情報の間のスペース)
  4. %s: p (実験的な文字列、expstring() の結果)
  5. 余分な %s: この指定子に対応する引数が存在しませんでした。

しかし、print 関数に渡されている引数は thechar, getgoversion(), *p ? " " : "", p の4つしかありませんでした。

C言語の printf 系関数では、フォーマット文字列内の指定子の数と、実際に渡される引数の数が一致しない場合、未定義の動作(Undefined Behavior)を引き起こします。この場合、余分な %s が存在するため、print 関数はスタック上の次のメモリ位置を文字列へのポインタとして解釈しようとします。そのメモリ位置が有効な文字列ポインタでなかった場合、プログラムは不正なメモリアクセスを試み、結果としてクラッシュ(セグメンテーション違反など)が発生します。

このコミットでは、フォーマット文字列から余分な %s を削除することで、この問題を修正しています。修正後のフォーマット文字列は "%cg version %s%s%s\\n" となり、これによりフォーマット指定子の数(4つ)と引数の数(4つ)が一致し、print 関数が正しく動作するようになりました。

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

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -229,7 +229,7 @@ main(int argc, char *argv[])
  	\tp = expstring();
  	\tif(strcmp(p, "X:none") == 0)
  	\t\tp = "";
-\t\tprint("%cg version %s%s%s%s\\n", thechar, getgoversion(), *p ? " " : "", p);\n
+\t\tprint("%cg version %s%s%s\\n", thechar, getgoversion(), *p ? " " : "", p);\n
  	\texits(0);\
  	} ARGEND

コアとなるコードの解説

変更された行は、Goコンパイラのバージョン情報を標準出力に表示するための print 関数の呼び出しです。

  • 変更前:

    print("%cg version %s%s%s%s\\n", thechar, getgoversion(), *p ? " " : "", p);
    

    この行では、%s フォーマット指定子が4つありますが、対応する引数は getgoversion(), (*p ? " " : ""), p の3つしかありません。これにより、最後の %s に対応する引数が不足し、未定義の動作を引き起こしていました。

  • 変更後:

    print("%cg version %s%s%s\\n", thechar, getgoversion(), *p ? " " : "", p);
    

    変更後では、フォーマット文字列から余分な %s が1つ削除されています。これにより、フォーマット指定子の数(%c 1つ、%s 3つ、合計4つ)と、thechar, getgoversion(), (*p ? " " : ""), p という引数の数(4つ)が完全に一致するようになりました。この修正により、print 関数は期待通りに動作し、クラッシュが解消されます。

この修正は、Goコンパイラの内部コードにおける典型的なC言語の printf フォーマット文字列のバグであり、引数の不一致が原因で発生するクラッシュを効果的に防いでいます。

関連リンク

  • Go Change List (CL): https://golang.org/cl/5314060 このリンクは、このコミットに対応するGoのコードレビューシステム(Gerrit)上の変更リストを示しています。通常、ここにはより詳細な議論や関連するテスト、背景情報が含まれています。

参考にした情報源リンク

  • Go言語の公式ドキュメント (Goコンパイラ、go tool コマンドに関する一般的な情報)
  • C言語の printf 関数に関するドキュメント (フォーマット指定子と引数の対応に関する一般的な情報)
  • Go言語のソースコード (src/cmd/gc/lex.c および関連ファイル)
  • Go言語のバグトラッカー (バグ番号 568 に関する情報、ただし今回の検索では直接的な情報は見つからなかったため、一般的な情報源として記載)
  • Google検索: "golang bug 568 g -V crash" (直接的な情報は見つからなかったが、調査の過程で使用)