[インデックス 12889] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおけるバグ修正に関するものです。具体的には、fmt
パッケージが提供する書式設定機能において、非常に大きな負のint64
値をバイナリ形式(%b
)で出力しようとした際に発生するクラッシュを修正しています。
影響を受けるファイルは以下の通りです。
src/pkg/fmt/fmt_test.go
:fmt
パッケージのテストファイル。このコミットでは、バグを再現し、修正を検証するための新しいテストケースが追加されています。src/pkg/fmt/format.go
:fmt
パッケージの書式設定ロジックを実装している主要なファイル。このコミットでは、バッファサイズの定義が修正されています。
コミット
- コミットハッシュ:
a662d3d9a757c0556f27d650a9dfe3bf0f2db1bf
- Author: Rob Pike r@golang.org
- Date: Fri Apr 13 09:28:37 2012 +1000
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a662d3d9a757c0556f27d650a9dfe3bf0f2db1bf
元コミット内容
fmt: fix crash of %b on huge negative int64
The buffer had 64 bytes but needs one more for the sign.
Fixes #3510.
R=golang-dev, dave, dsymonds
CC=golang-dev
https://golang.org/cl/6011057
変更の背景
このコミットの背景には、Go言語のfmt
パッケージが、非常に大きな負のint64
整数をバイナリ形式(%b
)で文字列に変換する際に、プログラムがクラッシュするという深刻なバグが存在していました。
具体的には、int64
型は64ビットの符号付き整数であり、その最小値は-2^63
です。この値をバイナリで表現すると、符号ビットを含めて64桁の2進数となります。しかし、fmt
パッケージ内部で使用されていたバッファのサイズが、この64桁のバイナリ表現に加えて、負の符号(-
)を格納するためのスペースを考慮していなかったため、バッファオーバーフローが発生し、結果としてプログラムがクラッシュしていました。
この問題は、Go言語のIssueトラッカーで#3510
として報告されていました。コミットメッセージにあるFixes #3510
は、このコミットがその特定のバグ報告に対応するものであることを示しています。
前提知識の解説
fmt
パッケージ
Go言語のfmt
パッケージは、C言語のprintf
やscanf
に似た、書式設定されたI/O(入出力)機能を提供します。これにより、様々なデータ型を文字列に変換したり、文字列からデータを解析したりすることができます。
Sprintf
関数:fmt.Sprintf
は、指定された書式文字列と引数を使用して文字列を生成し、その結果の文字列を返します。ファイルや標準出力に直接書き込むのではなく、文字列として結果を取得したい場合に利用されます。- 書式動詞(Verb):
fmt
パッケージでは、%d
(10進数)、%x
(16進数)、%s
(文字列)など、様々な書式動詞が用意されています。このコミットで問題となっているのは、整数をバイナリ形式で出力するための%b
です。
整数表現とint64
int64
: Go言語におけるint64
型は、64ビット幅の符号付き整数型です。これは、約-9 * 10^18
から9 * 10^18
までの範囲の整数値を表現できます。- 負の数の表現(2の補数): コンピュータ内部では、負の数は通常「2の補数」形式で表現されます。64ビットの2の補数表現では、最上位ビット(MSB)が符号ビットとして機能し、
0
であれば正、1
であれば負を示します。 - バイナリ表現: 整数を2進数で表現したものです。例えば、10進数の
5
は2進数で101
、10進数の-5
は2の補数表現で...11111011
のように表現されます。int64
の最小値である-2^63
は、2進数で1000...000
(1と63個の0)と表現されます。
バッファ
プログラミングにおいて「バッファ」とは、データを一時的に格納するためのメモリ領域のことです。このコミットの文脈では、fmt
パッケージが数値を文字列に変換する際に、その文字列を一時的に保持するために使用するメモリ領域を指します。バッファのサイズが不足すると、書き込もうとするデータがバッファの境界を超えてしまい、メモリ破壊やプログラムのクラッシュを引き起こす可能性があります(バッファオーバーフロー)。
技術的詳細
このバグの根本原因は、fmt
パッケージがint64
型の数値をバイナリ形式(%b
)で書式設定する際に、内部で使用するバッファのサイズ見積もりが不十分だったことにあります。
int64
の最小値は-2^63
です。この値をバイナリで表現すると、1
の後に63個の0
が続く形になります(1000000000000000000000000000000000000000000000000000000000000000
)。これは64ビット(64桁)のバイナリ表現です。
しかし、負の数を文字列として出力する際には、この64桁のバイナリ表現の前に、負の符号を示すハイフン(-
)が追加されます。つまり、int64
の最小値をバイナリで出力する場合、合計で1 (符号) + 64 (バイナリ桁数) = 65
文字が必要になります。
元のコードでは、このバッファサイズを64
バイトとして定義していました(nByte = 64
)。このサイズでは、符号文字のための1バイトが不足していました。そのため、int64
の最小値のような「巨大な負の数」を%b
で書式設定しようとすると、fmt
パッケージは65バイト目のデータを64バイトのバッファに書き込もうとし、結果としてバッファオーバーフローが発生し、プログラムがクラッシュしていました。
このコミットでは、このバッファサイズを65
バイトに増やすことで、符号文字のためのスペースを確保し、バッファオーバーフローを防いでいます。
コアとなるコードの変更箇所
このコミットによるコードの変更は、主に以下の2つのファイルで行われています。
-
src/pkg/fmt/fmt_test.go
:--- a/src/pkg/fmt/fmt_test.go +++ b/src/pkg/fmt/fmt_test.go @@ -461,6 +461,9 @@ var fmttests = []struct { // zero reflect.Value, which formats as <nil>.\n\ // This test is just to check that it shows the two NaNs at all.\n\ {\"%v\", map[float64]int{math.NaN(): 1, math.NaN(): 2}, \"map[NaN:<nil> NaN:<nil>]\"},\n\ +\n\ +\t// Used to crash because nByte didn't allow for a sign.\n\ +\t{\"%b\", int64(-1 << 63), \"-1000000000000000000000000000000000000000000000000000000000000000\"},\n\ }\n\ \n\ func TestSprintf(t *testing.T) {
この変更では、
fmttests
というテストケースのスライスに新しいエントリが追加されています。{"%b", int64(-1 << 63), "-1000000000000000000000000000000000000000000000000000000000000000"}
このテストケースは、%b
書式動詞を使用してint64
の最小値(-1 << 63
は-2^63
と同じ)をフォーマットし、期待される出力文字列が正しいことを検証します。このテストケースは、修正前のバージョンではクラッシュを引き起こすはずでした。
-
src/pkg/fmt/format.go
:--- a/src/pkg/fmt/format.go +++ b/src/pkg/fmt/format.go @@ -10,7 +10,7 @@ import (\n\ )\n\ \n\ const (\n\ -\tnByte = 64\n\ +\tnByte = 65 // %b of an int64, plus a sign.\n\ \n\ \tldigits = \"0123456789abcdef\"\n\ \tudigits = \"0123456789ABCDEF\"\n
この変更は、
nByte
という定数の値を64
から65
に変更しています。コメントも追加され、int64
のバイナリ表現に加えて符号のためのスペースが必要であることが明記されています。
コアとなるコードの解説
src/pkg/fmt/fmt_test.go
の変更
追加されたテストケースは、このバグの再現と修正の検証に不可欠です。
int64(-1 << 63)
は、Go言語でint64
型の最小値を表現する慣用的な方法です。1 << 63
は2^63
を意味し、それに-1
を掛けることで-2^63
、つまりint64
の最小値が得られます。
このテストケースが追加されたことで、将来的に同様のバグが再発した場合でも、自動テストによって早期に検出できるようになります。
src/pkg/fmt/format.go
の変更
nByte
定数は、fmt
パッケージが数値を文字列に変換する際に使用する内部バッファの最大サイズを定義していました。
元の値64
は、64ビットの整数が最大で64桁のバイナリ表現になることを想定していましたが、負の数、特にint64
の最小値の場合、そのバイナリ表現の前に負の符号(-
)が付くため、合計で65文字が必要となります。
nByte = 65
への変更は、この不足していた1バイトのスペースを確保し、バッファオーバーフローを防ぐための直接的な修正です。この変更により、fmt
パッケージはint64
の最小値を%b
で正しく書式設定できるようになりました。
関連リンク
- Go CL 6011057: https://golang.org/cl/6011057
参考にした情報源リンク
- コミットメッセージ
- Go言語の
fmt
パッケージに関する一般的な知識 - Go言語における整数型とビット演算に関する知識
- 2の補数表現に関する一般的な知識
- Web検索: "golang issue 3510" (ただし、この検索結果は、このコミットが参照する2012年のIssue 3510とは異なる、より新しい
gopls
のクラッシュレポートに関するものであり、直接的な関連性は見られませんでした。)