[インデックス 11924] ファイルの概要
このコミットは、Go言語のディストリビューションツール (cmd/dist) におけるWindows固有のコード (src/cmd/dist/windows.c) のバグ修正です。具体的には、xprintf 関数が固定サイズのバッファを使用していたために、出力が途中で切り捨てられる可能性があった問題を解決しています。これにより、dist env コマンドなどの出力が完全に行われるようになります。
コミット
commit 6fae34db94fe02f60edb207c95037a9bf8611e71
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date: Wed Feb 15 14:25:27 2012 +1100
cmd/dist: xprintf() can't output whole of 'dist env'.
R=golang-dev, mpimenov, alex.brainman
CC=golang-dev
https://golang.org/cl/5667045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6fae34db94fe02f60edb207c95037a9bf8611e71
元コミット内容
cmd/dist: xprintf() can't output whole of 'dist env'.
このコミットメッセージは、cmd/dist ツール内の xprintf() 関数が、dist env コマンドの出力全体を正しく表示できないという問題を指摘しています。これは、出力が途中で切り捨てられることを示唆しています。
変更の背景
Go言語のビルドおよびディストリビューションプロセスを管理する cmd/dist ツールは、様々な環境情報を出力する機能を持っています。Windows環境において、このツールが内部で使用する xprintf 関数が、固定サイズのバッファ (buf[1024]) を用いてフォーマット済み文字列を生成していました。
この設計には、以下のような潜在的な問題がありました。
- バッファオーバーフロー/出力の切り捨て:
vsnprintf関数は、指定されたバッファサイズを超えて書き込もうとすると、バッファオーバーフローを引き起こすか、または文字列を切り捨てます。この場合、bufのサイズが1024バイトに固定されていたため、xprintfに渡されるフォーマット文字列と可変引数によって生成される最終的な文字列が1024バイトを超えると、出力が途中で切れてしまい、完全な情報が表示されないという問題が発生していました。特にdist envのように環境変数リストなど、可変長で長い文字列が出力される場合に顕在化しやすかったと考えられます。 - メモリの非効率性: 常に1024バイトのスタックメモリを確保するため、短い文字列の場合には無駄が生じます。
このコミットは、この出力の切り捨て問題を解決し、xprintf が常に完全な文字列を出力できるようにすることを目的としています。
前提知識の解説
vsnprintf 関数
vsnprintf はC言語の標準ライブラリ関数で、printf ファミリーの一つです。可変引数リスト (va_list) を受け取り、指定されたバッファにフォーマットされた文字列を書き込みます。
- 書式:
int vsnprintf(char *str, size_t size, const char *format, va_list ap); - 引数:
str: フォーマットされた文字列を書き込むバッファへのポインタ。size: バッファの最大サイズ(ヌル終端文字を含む)。format: フォーマット文字列。ap: 可変引数リスト。
- 戻り値:
- バッファに書き込まれた(または書き込まれるはずだった)文字数(ヌル終端文字を含まない)。
sizeが小さすぎて全ての文字を書き込めなかった場合でも、必要な文字数を返します。- 重要なイディオム:
strにNULLを、sizeに0を渡すと、vsnprintfは実際に書き込みを行わずに、フォーマットされた文字列に必要な文字数(ヌル終端文字を含まない)を返します。この機能は、動的にメモリを確保する際に非常に有用です。
WriteFile 関数 (Windows API)
WriteFile はWindows APIの一部で、指定されたファイルまたはI/Oデバイスにデータを書き込むために使用されます。
- 書式:
BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped); - 引数:
hFile: 書き込み先のファイルまたはI/Oデバイスのハンドル。このコミットではGetStdHandle(STD_OUTPUT_HANDLE)を使用しており、これは標準出力(コンソール)へのハンドルを取得します。lpBuffer: 書き込むデータを含むバッファへのポインタ。nNumberOfBytesToWrite: 書き込むバイト数。lpNumberOfBytesWritten: 実際に書き込まれたバイト数を受け取る変数へのポインタ。lpOverlapped: 非同期I/Oに使用される構造体へのポインタ(同期I/Oの場合はNULL)。
- 戻り値: 成功した場合は非ゼロ、失敗した場合はゼロ。
xmalloc および xfree
これらはGoプロジェクト内で定義されているカスタムのメモリ管理関数である可能性が高いです。通常、malloc や free のラッパーとして実装され、メモリ確保の失敗時のエラーハンドリングや、デバッグ情報の追加など、プロジェクト固有の要件を満たすために使用されます。この文脈では、xmalloc はメモリを動的に確保し、xfree はそのメモリを解放する役割を担います。
cmd/dist
cmd/dist はGo言語のソースコードリポジトリ内のディレクトリであり、Goのビルドシステムの一部を構成するツール群を含んでいます。Goのコンパイラやツールチェーンのビルド、テスト、インストールなど、Goディストリビューションの管理に関連するタスクを実行します。windows.c は、Windowsプラットフォームに特化した処理を実装しているファイルです。
技術的詳細
このコミットの核心は、xprintf 関数における文字列バッファの管理方法を、固定サイズから動的サイズへと変更した点にあります。
変更前:
void
xprintf(char *fmt, ...)
{
va_list arg;
static char buf[1024]; // 固定サイズのバッファ
DWORD n;
va_start(arg, fmt);
vsnprintf(buf, sizeof buf, fmt, arg); // バッファサイズ1024でフォーマット
va_end(arg);
n = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &n, 0); // strlen(buf)を使用
}
変更前のコードでは、buf という1024バイトの静的バッファが宣言されていました。vsnprintf はこのバッファにフォーマットされた文字列を書き込みますが、もし生成される文字列が1024バイトを超えた場合、文字列は切り捨てられます。WriteFile 関数に渡される長さは strlen(buf) で計算されますが、これは切り捨てられた文字列の長さしか返さないため、完全な出力は行われませんでした。
変更後:
void
xprintf(char *fmt, ...)
{
va_list arg;
char *p;
DWORD n, w;
va_start(arg, fmt);
n = vsnprintf(NULL, 0, fmt, arg); // 1. 必要なバッファサイズを計算
p = xmalloc(n+1); // 2. 必要なサイズでメモリを動的に確保
vsnprintf(p, n+1, fmt, arg); // 3. 動的に確保したバッファにフォーマット
va_end(arg);
w = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0); // 4. 正しい長さnで書き込み
xfree(p); // 5. メモリを解放
}
変更後のコードでは、以下のステップで動的なバッファ管理を行っています。
- 必要なバッファサイズの計算: 最初の
vsnprintf(NULL, 0, fmt, arg)の呼び出しは、実際に文字列を書き込まずに、フォーマットされた文字列に必要な文字数(ヌル終端文字を含まない)をnに返します。 - メモリの動的確保:
xmalloc(n+1)を使用して、計算されたnの長さにヌル終端文字のための1バイトを加えたサイズのメモリを動的に確保します。これにより、文字列がどんなに長くても、常に十分なサイズのバッファが確保されます。 - 文字列のフォーマット: 2回目の
vsnprintf(p, n+1, fmt, arg)の呼び出しで、動的に確保されたバッファpにフォーマットされた文字列を書き込みます。 - 出力:
WriteFile関数にpとnを渡して、標準出力に文字列を書き込みます。ここでnを使用することで、文字列が切り捨てられることなく、正確な長さで出力されることが保証されます。 - メモリの解放:
xfree(p)を呼び出して、動的に確保したメモリを解放します。これにより、メモリリークを防ぎます。
この変更により、xprintf は出力文字列の長さに依存せず、常に完全な内容を標準出力に書き出すことができるようになりました。
コアとなるコードの変更箇所
diff --git a/src/cmd/dist/windows.c b/src/cmd/dist/windows.c
index 1655f77704..aa961eb6cf 100644
--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -833,14 +833,17 @@ void
xprintf(char *fmt, ...)
{
va_list arg;
- static char buf[1024];
- DWORD n;
+ char *p;
+ DWORD n, w;
va_start(arg, fmt);
- vsnprintf(buf, sizeof buf, fmt, arg);
+ n = vsnprintf(NULL, 0, fmt, arg);
+ p = xmalloc(n+1);
+ vsnprintf(p, n+1, fmt, arg);
va_end(arg);
- n = 0;
- WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &n, 0);
+ w = 0;
+ WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0);
+ xfree(p);
}
int
コアとなるコードの解説
- static char buf[1024];- 固定サイズの静的バッファ
bufの宣言が削除されました。これは、出力文字列の長さに応じて動的にメモリを確保する新しいアプローチに置き換えられます。
- 固定サイズの静的バッファ
- DWORD n;- 以前のコードで使用されていた
n変数が削除されました。新しいコードでは、nは必要な文字列長を格納するために再利用され、wが実際に書き込まれたバイト数を格納するために導入されます。
- 以前のコードで使用されていた
+ char *p;- 動的に確保されたバッファへのポインタ
pが宣言されました。
- 動的に確保されたバッファへのポインタ
+ DWORD n, w;nはフォーマットされた文字列の長さを保持し、wはWriteFileによって実際に書き込まれたバイト数を保持するための変数として宣言されました。
- vsnprintf(buf, sizeof buf, fmt, arg);- 固定バッファへの
vsnprintf呼び出しが削除されました。
- 固定バッファへの
+ n = vsnprintf(NULL, 0, fmt, arg);- これが変更の核心です。
vsnprintfにNULLバッファと0サイズを渡すことで、フォーマットされた文字列に必要なバイト数(ヌル終端文字を含まない)をnに取得します。
- これが変更の核心です。
+ p = xmalloc(n+1);nで得られた必要なバイト数にヌル終端文字のための1バイトを加えたサイズで、xmallocを使ってヒープメモリを動的に確保し、そのポインタをpに格納します。
+ vsnprintf(p, n+1, fmt, arg);- 動的に確保されたバッファ
pに、フォーマットされた文字列を書き込みます。n+1はバッファのサイズ(ヌル終端文字を含む)です。
- 動的に確保されたバッファ
- n = 0;- 不要になった初期化が削除されました。
- WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &n, 0);- 固定バッファ
bufとstrlen(buf)を使用していたWriteFile呼び出しが削除されました。
- 固定バッファ
+ w = 0;WriteFileのlpNumberOfBytesWritten引数に渡すためのw変数の初期化です。
+ WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0);- 動的に確保されたバッファ
pと、正確な文字列長nを使用してWriteFileを呼び出します。これにより、出力が切り捨てられることなく、完全な文字列が書き込まれます。
- 動的に確保されたバッファ
+ xfree(p);- 動的に確保したメモリ
pをxfreeを使って解放します。これにより、メモリリークを防ぎます。
- 動的に確保したメモリ
これらの変更により、xprintf 関数は、出力する文字列の長さに応じて動的にメモリを確保し、常に完全な文字列を標準出力に書き出す、より堅牢で安全な実装になりました。
関連リンク
- Go CL (Code Review) ページ: https://golang.org/cl/5667045
参考にした情報源リンク
vsnprintfのドキュメント (例: cppreference.com): https://en.cppreference.com/w/c/io/vsnprintfWriteFileのドキュメント (Microsoft Learn): https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-writefile- Go言語のソースコードリポジトリ (GitHub): https://github.com/golang/go
- Go言語の
cmd/distについての一般的な情報 (Goのドキュメントや関連するブログ記事など)