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

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

このコミットは、Go言語のツールチェインの一部である cmd/dist における bsubst 関数内のバグを修正するものです。具体的には、置換後の文字列が元の文字列よりも長い場合に、バッファの再割り当て(bgrow)後にポインタ p が無効になる問題を解決しています。これにより、bsubst 関数が正しく動作し、cmd/dist ツールが安定して機能するようになります。

コミット

  • コミットハッシュ: c06bd52a2e5a38125635b770b5ddad2cb3ce6909
  • 作者: Alex Brainman alex.brainman@gmail.com
  • 日付: Sun Feb 5 15:16:39 2012 +1100

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

https://github.com/golang/go/commit/c06bd52a2e5a38125635b770b5ddad2cb3ce6909

元コミット内容

cmd/dist: fix bug in bsubst

R=golang-dev, r, dsymonds, akumar
CC=golang-dev
https://golang.org/cl/5624054

変更の背景

この変更は、cmd/dist ツール内の bsubst 関数に存在するバグを修正するために行われました。bsubst 関数は、バッファ内の文字列を別の文字列で置換する役割を担っています。元の実装では、置換後の文字列が元の文字列よりも長い場合(nx < ny のケース)、バッファのサイズを増やすために bgrow 関数が呼び出されていました。しかし、bgrow はバッファのメモリを再割り当てする可能性があるため、bgrow の呼び出し後に、それまで有効だったポインタ p が無効なメモリを指してしまう可能性がありました。これにより、その後の xmemmove などの操作が不正なメモリに対して行われ、クラッシュや予期せぬ動作を引き起こす可能性がありました。

このバグは、cmd/dist の安定性と信頼性に影響を与えるため、修正が必要とされました。cmd/dist はGo言語のビルドプロセスにおいて重要な役割を果たすツールであり、その正確な動作はGo言語全体のビルドの成功に直結します。

前提知識の解説

  • cmd/dist: Go言語のソースコードをビルドするために使用される内部ツールです。Goのコンパイラやリンカなどのツールチェインを構築する際に利用されます。Goのビルドシステムの中核をなす部分の一つです。
  • bsubst 関数: cmd/dist 内で定義されている関数で、バッファ (Buf 構造体) 内の特定の文字列 (x) を別の文字列 (y) で置換する処理を行います。これは、ビルド時にパスやバージョン情報などを動的に埋め込む際などに使用される可能性があります。
  • Buf 構造体: cmd/dist/buf.c で定義されている、動的にサイズが変更可能なバッファを表現するための構造体です。通常、p はバッファの先頭へのポインタ、len は現在のバッファ内のデータの長さ、size はバッファの総容量などを保持します。
  • xmemmove: C言語の標準ライブラリ関数 memmove に似た機能を持つ関数で、メモリブロックを移動させます。memmove と同様に、コピー元とコピー先のメモリ領域が重なっていても正しく動作することが保証されています。
  • bgrow 関数: Buf 構造体のバッファサイズを拡張するために使用される関数です。現在のバッファ容量が不足した場合に、より大きなメモリ領域を確保し、既存のデータを新しい領域にコピーします。この操作により、バッファの先頭アドレス (b->p) が変更される可能性があります。
  • ポインタの無効化: C言語において、realloc のようなメモリ再割り当て関数が呼び出されると、元のメモリブロックが解放され、新しいメモリブロックが異なるアドレスに割り当てられることがあります。この場合、元のメモリブロックを指していたポインタは無効になり、新しいメモリブロックのアドレスを指すように更新する必要があります。更新を怠ると、無効なポインタ(ダングリングポインタ)を参照することになり、未定義動作やクラッシュの原因となります。

技術的詳細

このバグは、bsubst 関数が文字列置換を行う際に、置換後の文字列 (y) の長さ (ny) が置換前の文字列 (x) の長さ (nx) よりも長い場合 (nx < ny) に発生していました。

元のコードでは、nx < ny の場合に bgrow(b, ny-nx) を呼び出してバッファサイズを拡張していました。しかし、bgrow は内部で realloc のような処理を行う可能性があり、その結果、Buf 構造体 b の内部ポインタ b->p が新しいメモリ領域を指すように変更されることがあります。

問題は、bgrow が呼び出される前に計算されたポインタ p (これは b->p からのオフセットで計算されていた) が、bgrow 呼び出し後に b->p が変更された場合でも更新されずにそのまま使用されていた点です。これにより、bgrow 呼び出し後の xmemmove(p+ny, p+nx, ...) の行で、p が古い(無効な)メモリ領域を指したままになり、不正なメモリ操作が発生していました。

修正は、このポインタの無効化問題を解決するために行われました。具体的には、bgrow を呼び出す前に、現在のポインタ p がバッファの先頭 b->p からどれだけ離れているか (pos = p - b->p;) を計算して保存します。そして、bgrow 呼び出し後に b->p が更新された場合でも、保存しておいたオフセット pos を使って新しい b->p から正しい位置を再計算し、p = b->p + pos; として p を更新しています。これにより、bgrow によるバッファの再割り当て後も p が常に有効なメモリ領域を指すことが保証され、後続の xmemmove 操作が正しく行われるようになります。

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

src/cmd/dist/buf.c ファイルの bsubst 関数内の変更です。

--- a/src/cmd/dist/buf.c
+++ b/src/cmd/dist/buf.c
@@ -115,8 +115,11 @@ bsubst(Buf *b, char *x, char *y)\n 		if(p == nil)\n 		break;\n 		if(nx != ny) {\n-		if(nx < ny)\n+		if(nx < ny) {\n+			pos = p - b->p;\n 			bgrow(b, ny-nx);\n+			p = b->p + pos;\n+		}\n 		xmemmove(p+ny, p+nx, (b->p+b->len)-(p+nx));\n 		}\n 		xmemmove(p, y, ny);\n```

## コアとなるコードの解説

変更は `if(nx < ny)` ブロック内で行われています。

*   **変更前**:
    ```c
    if(nx < ny)
        bgrow(b, ny-nx);
    ```
    このコードでは、`bgrow` が呼び出された後も `p` の値は変更されません。もし `bgrow` がバッファを再割り当てし、`b->p` が新しいアドレスを指すようになった場合、`p` は古い(無効な)アドレスを指したままになります。

*   **変更後**:
    ```c
    if(nx < ny) {
        pos = p - b->p; // (1) 現在のポインタpの、バッファ先頭b->pからのオフセットを保存
        bgrow(b, ny-nx); // (2) バッファを拡張。b->pが変更される可能性がある
        p = b->p + pos; // (3) 新しいb->pと保存したオフセットを使ってpを再計算
    }
    ```
    1.  `pos = p - b->p;`: `bgrow` が呼び出される前に、現在のポインタ `p` がバッファの先頭 `b->p` からどれだけ離れているか(オフセット)を `pos` 変数に保存します。これは、`p` がバッファ内のどの相対位置を指しているかを記憶するためです。
    2.  `bgrow(b, ny-nx);`: バッファ `b` のサイズを `ny-nx` バイト分拡張します。この関数呼び出しにより、`b->p` が新しいメモリ領域の先頭アドレスを指すように変更される可能性があります。
    3.  `p = b->p + pos;`: `bgrow` の呼び出し後、`b->p` が新しいアドレスを指している可能性があるため、保存しておいたオフセット `pos` を新しい `b->p` に加算することで、`p` を正しいメモリ位置に更新します。これにより、`p` は常に有効なメモリ領域を指すことが保証され、その後の `xmemmove` 操作が安全に行われます。

この修正により、`bsubst` 関数は、置換後の文字列が元の文字列よりも長い場合でも、メモリの再割り当てを安全に処理し、バグなく動作するようになります。

## 関連リンク

*   Go言語の公式リポジトリ: [https://github.com/golang/go](https://github.com/golang/go)
*   このコミットが関連するGoの変更リスト (CL): [https://golang.org/cl/5624054](https://golang.org/cl/5624054)

## 参考にした情報源リンク

*   Go言語のソースコード (特に `src/cmd/dist/buf.c`): [https://github.com/golang/go/blob/master/src/cmd/dist/buf.c](https://github.com/golang/go/blob/master/src/cmd/dist/buf.c)
*   C言語の `memmove` 関数に関する情報 (一般的なメモリ操作の理解のため): [https://en.cppreference.com/w/c/string/byte/memmove](https://en.cppreference.com/w/c/string/byte/memmove)
*   C言語の `realloc` 関数に関する情報 (メモリ再割り当てとポインタ無効化の理解のため): [https://en.cppreference.com/w/c/memory/realloc](https://en.cppreference.com/w/c/memory/realloc)
*   Go言語の `cmd/dist` ツールの役割に関する一般的な情報 (Goのビルドプロセスについて): [https://go.dev/doc/](https://go.dev/doc/) (Goの公式ドキュメント)
*   Go言語のコードレビュープロセスに関する情報 (CLの理解のため): [https://go.dev/doc/contribute](https://go.dev/doc/contribute)