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

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

このコミットは、Go言語のビルドシステム (cmd/dist) におけるWindows環境特有の問題を解決するためのものです。具体的には、Mercurial(hgコマンド)がWindows上でバッチファイルとしてインストールされている場合に発生する問題と、FormatMessageW APIの稀なクラッシュを修正しています。

コミット

commit 0669261af107eddb13d71293d654b595417f8053
Author: Alexey Borzenkov <snaury@gmail.com>
Date:   Mon Apr 9 15:39:59 2012 -0400

    cmd/dist: don't fail when Mercurial is a batch file on Windows
    
    On windows Mercurial installed with easy_install typically creates
    an hg.bat batch file in Python Scripts directory, which cannot be used
    with CreateProcess unless full path is specified. Work around by
    launching hg via cmd.exe /c.
    
    Additionally, fix a rare FormatMessageW crash.
    
    Fixes #3093.
    
    R=golang-dev, rsc, alex.brainman, aram, jdpoirier, mattn.jp
    CC=golang-dev
    https://golang.org/cl/5937043

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

https://github.com/golang/go/commit/0669261af107eddb13d71293d654b595417f8053

元コミット内容

cmd/dist: don't fail when Mercurial is a batch file on Windows On windows Mercurial installed with easy_install typically creates an hg.bat batch file in Python Scripts directory, which cannot be used with CreateProcess unless full path is specified. Work around by launching hg via cmd.exe /c. Additionally, fix a rare FormatMessageW crash. Fixes #3093.

変更の背景

このコミットは、主に2つの問題に対処しています。

  1. Windows環境でのMercurial (hg) コマンドの実行問題: Windows環境において、easy_installなどのツールでMercurialをインストールすると、hg.exeのような実行ファイルではなく、hg.batというバッチファイルが生成されることがありました。WindowsのCreateProcess APIは、実行ファイル(.exe)を直接指定する場合にはパス解決が容易ですが、バッチファイル(.bat.cmd)を直接指定して実行しようとすると、完全なパスが指定されていない限り、正しく起動できないという挙動を示すことがあります。Goのビルドシステム (cmd/dist) がMercurialを呼び出す際に、この挙動によってhg.batの起動に失敗し、ビルドプロセスが中断する問題が発生していました。

  2. FormatMessageW APIの稀なクラッシュ: Windows APIのFormatMessageW関数は、システムエラーメッセージなどをフォーマットするために使用されます。この関数は、メッセージ文字列内の挿入シーケンス(例: %1, %2)を引数で置き換える機能を持っています。しかし、メッセージ文字列に挿入シーケンスが含まれているにもかかわらず、対応する引数が提供されない場合や、引数の型が不一致の場合に、稀にクラッシュ(アクセス違反など)を引き起こす可能性がありました。このコミットは、この潜在的なクラッシュを回避するための修正も含まれています。

これらの問題は、Goのビルドプロセスの安定性と信頼性に影響を与えるため、修正が必要とされました。特に、Fixes #3093は、Mercurialのバッチファイルに関する具体的なバグ報告に対応しています。

前提知識の解説

  • Go言語のビルドシステム (cmd/dist): Go言語のソースコードからコンパイラ、ツール、標準ライブラリなどをビルドするための内部ツールです。Goのソースコード管理にはGitとMercurialが使用されており、cmd/distはこれらのバージョン管理システムと連携して動作します。
  • Mercurial (hg): 分散型バージョン管理システムの一つで、Gitと同様にソースコードの変更履歴を管理します。Goプロジェクトの初期にはMercurialが主要なバージョン管理システムとして利用されていました。
  • Windowsバッチファイル (.bat, .cmd): Windowsのコマンドプロンプトで実行されるスクリプトファイルです。複数のコマンドを記述し、自動実行するために使用されます。
  • CreateProcess API: Windows APIの一つで、新しいプロセスを作成し、実行可能ファイルを起動するために使用されます。このAPIは、実行するプログラムのパスと引数を指定します。
  • cmd.exe /c: Windowsのコマンドプロンプト (cmd.exe) を起動し、指定されたコマンドを実行した後に終了させるためのオプションです。バッチファイルや内部コマンドを確実に実行するためにしばしば使用されます。例えば、cmd.exe /c my_script.batとすることで、my_script.batcmd.exeによって解釈・実行されます。
  • FormatMessageW API: Windows APIの一つで、システムエラーコードやメッセージリソースからメッセージ文字列を取得し、フォーマットするために使用されます。Wサフィックスは、ワイド文字(Unicode)バージョンであることを示します。
  • FORMAT_MESSAGE_ALLOCATE_BUFFER: FormatMessageWフラグの一つで、関数がメッセージバッファを自動的に割り当て、そのポインタを返すように指示します。
  • FORMAT_MESSAGE_FROM_SYSTEM: FormatMessageWフラグの一つで、システムが定義するエラーメッセージテーブルからメッセージを検索するように指示します。
  • FORMAT_MESSAGE_IGNORE_INSERTS: FormatMessageWフラグの一つで、メッセージ文字列内の挿入シーケンス(例: %1)を無視するように指示します。このフラグが指定されると、FormatMessageWは挿入シーケンスの引数を期待せず、メッセージをそのまま返します。これにより、引数不足や型不一致によるクラッシュを防ぐことができます。

技術的詳細

このコミットは、src/cmd/dist/windows.cファイルに対して2つの主要な変更を加えています。

  1. Mercurialコマンドの実行方法の変更: GoのビルドシステムがMercurial (hg) コマンドを実行する際、Windows環境ではCreateProcess APIを直接使用していました。しかし、hghg.batのようなバッチファイルである場合、CreateProcessは完全なパスが指定されていないと正しく起動できない問題がありました。 この修正では、hgコマンドが呼び出される際に、そのコマンドの前に明示的にcmd.exe /cを付加するように変更しています。これにより、hgコマンドはcmd.exeのコンテキスト内で実行されるため、バッチファイルであっても正しく解釈・実行されるようになります。これは、argv(引数リスト)の最初の要素が"hg"である場合にのみ適用されます。

  2. FormatMessageWのクラッシュ修正: errstr関数内でFormatMessageWが呼び出される際に、新しいフラグFORMAT_MESSAGE_IGNORE_INSERTSが追加されました。 以前のコードでは、FormatMessageWFORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEMフラグを使用していました。システムエラーメッセージの中には、挿入シーケンスを含むものがあります。FormatMessageWは、これらの挿入シーケンスに対応する引数が提供されない場合に、稀にクラッシュする可能性がありました。 FORMAT_MESSAGE_IGNORE_INSERTSフラグを追加することで、FormatMessageWはメッセージ内の挿入シーケンスを無視し、引数を期待しなくなります。これにより、システムエラーメッセージの取得時に、メッセージのフォーマットに関する潜在的な問題を回避し、クラッシュを防ぐことができます。この関数はエラー文字列を取得する目的で使用されており、挿入シーケンスを埋める必要がないため、このフラグの追加は適切な修正です。

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

--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -115,7 +115,7 @@ errstr(void)
  	binit(&b);
  	code = GetLastError();
  	r = nil;
-	FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,\
+	FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,\
  	\tnil, code, 0, (Rune*)&r, 0, nil);\
  	toutf(&b, r);\
  	return bstr(&b);  // leak but we're dying anyway
@@ -285,9 +285,11 @@ genrun(Buf *b, char *dir, int mode, Vec *argv, int wait)\
  	binit(&cmd);\
  \n
  	for(i=0; i<argv->len; i++) {
+\t\tq = argv->p[i];
+\t\tif(i == 0 && streq(q, "hg"))
+\t\t\tbwritestr(&cmd, "cmd.exe /c ");
  	\tif(i > 0)\
  	\t\tbwritestr(&cmd, " ");
-\t\tq = argv->p[i];
  	\tif(contains(q, " ") || contains(q, "\t") || contains(q, "\"") || contains(q, "\\\\") || hassuffix(q, "\\")) {
  	\t\tbwritestr(&cmd, "\"");
  	\t\tnslash = 0;

コアとなるコードの解説

errstr関数の変更

-	FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,\
+	FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,\
 		nil, code, 0, (Rune*)&r, 0, nil);

errstr関数は、Windowsのシステムエラーコードからエラーメッセージ文字列を取得するために使用されます。この変更では、FormatMessageW関数の呼び出しにFORMAT_MESSAGE_IGNORE_INSERTSフラグが追加されています。これにより、取得されるメッセージ文字列に挿入シーケンス(例: %1)が含まれていても、FormatMessageWはそれらを無視し、引数を期待せずにメッセージをそのまま返します。これにより、引数不足による稀なクラッシュを防ぎます。

genrun関数の変更

 	for(i=0; i<argv->len; i++) {
+		q = argv->p[i];
+		if(i == 0 && streq(q, "hg"))
+			bwritestr(&cmd, "cmd.exe /c ");
 		if(i > 0)
 			bwritestr(&cmd, " ");
-		q = argv->p[i];
 		if(contains(q, " ") || contains(q, "\t") || contains(q, "\"") || contains(q, "\\\\") || hassuffix(q, "\\")) {

genrun関数は、外部コマンドを実行するためのコマンドライン文字列を構築します。この変更は、コマンドライン引数を処理するループの内部で行われています。

  • q = argv->p[i]; の行がループの先頭に移動しました。これは、q(現在の引数)がif文の条件判定で使用される前に確実に初期化されるようにするためです。
  • 新しいifif(i == 0 && streq(q, "hg")) が追加されました。これは、現在の引数がコマンドラインの最初の要素(i == 0)であり、かつそのコマンドが文字列"hg"(Mercurialコマンド)である場合に真となります。
  • この条件が真の場合、bwritestr(&cmd, "cmd.exe /c "); が実行され、構築中のコマンドライン文字列の先頭にcmd.exe /c が追加されます。これにより、hgコマンドがバッチファイルであっても、cmd.exeによって正しく実行されるようになります。

関連リンク

参考にした情報源リンク