[インデックス 18896] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbytes
パッケージ内のMap
関数におけるパニック(panic)を修正するものです。具体的には、utf8.RuneLen
関数が不正なルーン(rune)に対して-1
を返した場合に、内部バッファの拡張が適切に行われず、結果としてパニックが発生する問題を解決しています。この修正により、不正なルーンがbytes.Map
関数によって処理される際に、Unicodeの置換文字である\uFFFD
(U+FFFD)に適切に変換され、バッファオーバーフローによるパニックが回避されるようになります。
コミット
commit 1a21dbc5720326b0e325a54c3e01c0e50b32eb03
Author: Rui Ueyama <ruiu@google.com>
Date: Tue Mar 18 20:52:58 2014 -0700
bytes: fix panic in Map
utf8.RuneLen returns -1 for an invalid rune. In that case we
need to extend the internal buffer at least by 3 for \uFFFD.
Fixes #7577.
LGTM=iant
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/77420044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1a21dbc5720326b0e325a54c3e01c0e50b32eb03
元コミット内容
bytes: fix panic in Map
utf8.RuneLen
は不正なルーンに対して-1
を返します。その場合、\uFFFD
のために内部バッファを少なくとも3バイト拡張する必要があります。
Fixes #7577.
変更の背景
このコミットは、Go言語のbytes
パッケージにあるMap
関数が、不正なUTF-8シーケンスを含むバイトスライスを処理する際にパニックを引き起こすバグを修正するために行われました。
Go言語では、文字列はUTF-8でエンコードされたバイトスライスとして扱われます。bytes.Map
関数は、入力バイトスライス内の各ルーン(Unicodeコードポイント)に対してユーザー定義のマッピング関数を適用し、その結果を新しいバイトスライスとして返します。
問題は、マッピング関数が不正なルーン(例えば、utf8.MaxRune + 1
のような範囲外のルーン)を返した場合に発生しました。Goのunicode/utf8
パッケージのRuneLen
関数は、有効なルーンのUTF-8エンコードに必要なバイト数を返しますが、不正なルーンに対しては-1
を返します。
bytes.Map
関数は、内部で新しいバイトスライスを構築する際に、各ルーンのエンコードに必要なバイト数に基づいてバッファのサイズを計算していました。しかし、RuneLen
が-1
を返した場合、この計算が負の値となり、結果としてmake
関数に不正なサイズが渡され、パニックが発生していました。
このバグは、GoのIssueトラッカーでIssue #7577として報告されていました。このコミットは、その問題を解決することを目的としています。
前提知識の解説
- Go言語の
rune
とbyte
:- Go言語において、
byte
は8ビットのバイトを表し、uint8
のエイリアスです。 rune
はUnicodeコードポイントを表し、int32
のエイリアスです。Goの文字列はUTF-8でエンコードされたバイトのシーケンスであり、range
ループで文字列をイテレートすると、各要素はrune
として取得されます。
- Go言語において、
- UTF-8エンコーディング:
- UTF-8はUnicode文字を可変長のバイトシーケンスで表現するエンコーディング方式です。ASCII文字は1バイトで表現され、他の文字は2バイトから4バイトで表現されます。
- 不正なUTF-8シーケンスは、有効なUnicode文字にデコードできないバイトシーケンスを指します。
unicode/utf8
パッケージ:- Goの標準ライブラリである
unicode/utf8
パッケージは、UTF-8エンコードされたテキストを操作するための関数を提供します。 utf8.RuneLen(r rune) int
: この関数は、与えられたルーンr
をUTF-8でエンコードした場合のバイト数を返します。もしルーンr
が有効なUnicodeコードポイントの範囲外(0 <= r <= unicode.MaxRune
)である場合、この関数は-1
を返します。utf8.RuneError
(\uFFFD
): これはUnicodeの置換文字(Replacement Character)U+FFFDを表すルーンです。不正な文字やデコードできない文字の代わりに表示されます。UTF-8エンコードでは通常3バイト(0xEF 0xBF 0xBD
)を占めます。
- Goの標準ライブラリである
bytes.Map
関数:func Map(mapping func(r rune) rune, s []byte) []byte
: この関数は、バイトスライスs
をルーンのシーケンスとして解釈し、各ルーンにmapping
関数を適用します。mapping
関数が返したルーンをUTF-8エンコードし、その結果を新しいバイトスライスとして返します。
技術的詳細
bytes.Map
関数は、入力バイトスライスs
をイテレートし、各UTF-8エンコードされたルーンをデコードします。デコードされたルーンはmapping
関数に渡され、その結果として新しいルーンr
が返されます。このr
を再度UTF-8エンコードして、結果のバイトスライスに追加します。
問題の核心は、mapping
関数がutf8.MaxRune + 1
のような不正なルーンを返した場合にありました。
元のコードでは、新しいルーンr
のUTF-8エンコード長をutf8.RuneLen(r)
で取得し、その長さを既存のバッファサイズnbytes
に加算して、バッファが最大サイズmaxbytes
を超えるかどうかをチェックしていました。
// 変更前
if nbytes+utf8.RuneLen(r) > maxbytes {
// Grow the buffer.
// ...
}
ここで、utf8.RuneLen(r)
が不正なルーンに対して-1
を返すと、nbytes + (-1)
という計算が行われ、バッファサイズが実際よりも小さく見積もられる可能性がありました。さらに深刻なのは、この負の値がバッファの再割り当てロジックに影響を与え、最終的にmake
関数に不正なサイズが渡されてパニックを引き起こす可能性があったことです。
修正では、utf8.RuneLen(r)
の戻り値をrl
という変数に格納し、rl
が-1
であるかどうかを明示的にチェックします。もしrl
が-1
であれば、それは不正なルーンであることを意味するため、そのルーンはutf8.RuneError
(\uFFFD
)に置き換えられると仮定し、そのエンコード長である3バイト(len(string(utf8.RuneError))
)をrl
に設定します。
// 変更後
rl := utf8.RuneLen(r)
if rl < 0 {
rl = len(string(utf8.RuneError)) // \uFFFD is 3 bytes
}
if nbytes+rl > maxbytes {
// Grow the buffer.
// ...
}
これにより、不正なルーンが返された場合でも、バッファの拡張に必要なバイト数が正しく計算され、パニックが回避されます。\uFFFD
はUTF-8で3バイトを占めるため、少なくとも3バイトのバッファ拡張が必要であるというコミットメッセージの記述と一致します。
また、テストケースTestMap
に、マッピング関数がutf8.MaxRune + 1
を返す場合のテストが追加されました。このテストは、bytes.Map
がパニックを起こさずに\uFFFD
を返すことを検証します。
コアとなるコードの変更箇所
src/pkg/bytes/bytes.go
--- a/src/pkg/bytes/bytes.go
+++ b/src/pkg/bytes/bytes.go
@@ -356,7 +356,11 @@ func Map(mapping func(r rune) rune, s []byte) []byte {
}
r = mapping(r)
if r >= 0 {
- if nbytes+utf8.RuneLen(r) > maxbytes {
+ rl := utf8.RuneLen(r)
+ if rl < 0 {
+ rl = len(string(utf8.RuneError))
+ }
+ if nbytes+rl > maxbytes {
// Grow the buffer.
maxbytes = maxbytes*2 + utf8.UTFMax
nb := make([]byte, maxbytes)
src/pkg/bytes/bytes_test.go
--- a/src/pkg/bytes/bytes_test.go
+++ b/src/pkg/bytes/bytes_test.go
@@ -785,6 +785,16 @@ func TestMap(t *testing.T) {\n if string(m) != expect {\n t.Errorf(\"drop: expected %q got %q\", expect, m)\n }\n+\n+\t// 6. Invalid rune
+\tinvalidRune := func(r rune) rune {\n+\t\treturn utf8.MaxRune + 1\n+\t}\n+\tm = Map(invalidRune, []byte(\"x\"))\n+\texpect = \"\\uFFFD\"\n+\tif string(m) != expect {\n+\t\tt.Errorf(\"invalidRune: expected %q got %q\", expect, m)\n+\t}\n }\n \n func TestToUpper(t *testing.T) { runStringTests(t, ToUpper, \"ToUpper\", upperTests) }\
コアとなるコードの解説
src/pkg/bytes/bytes.go
の変更
- 変更前:
if nbytes+utf8.RuneLen(r) > maxbytes {
- ここでは、マッピング関数から返されたルーン
r
のUTF-8エンコード長を直接utf8.RuneLen(r)
で取得し、それをバッファの現在の長さnbytes
に加算していました。 - もし
r
が不正なルーン(例:utf8.MaxRune + 1
)であった場合、utf8.RuneLen(r)
は-1
を返します。この-1
がnbytes
に加算されると、バッファの必要なサイズが過小評価され、結果としてバッファの再割り当てが適切に行われず、後続の書き込みでパニックを引き起こす可能性がありました。
- ここでは、マッピング関数から返されたルーン
- 変更後:
rl := utf8.RuneLen(r) if rl < 0 { rl = len(string(utf8.RuneError)) } if nbytes+rl > maxbytes {
rl := utf8.RuneLen(r)
: まず、ルーンr
のエンコード長をrl
変数に格納します。if rl < 0 { rl = len(string(utf8.RuneError)) }
:rl
が負の値(つまり-1
)である場合、それはr
が不正なルーンであることを意味します。この場合、GoのUTF-8デコーダは通常、不正なシーケンスを\uFFFD
(Unicode置換文字)に置き換えます。\uFFFD
はUTF-8で3バイトでエンコードされるため、rl
をlen(string(utf8.RuneError))
(これは3を返す)に設定し直します。これにより、不正なルーンが返された場合でも、バッファの拡張に必要な最小限のサイズ(3バイト)が確保されるようになります。if nbytes+rl > maxbytes {
: 修正されたrl
の値を使用して、バッファのサイズチェックと拡張ロジックが実行されます。これにより、不正なルーンが返された場合でも、バッファが適切に拡張され、パニックが回避されます。
src/pkg/bytes/bytes_test.go
の変更
- 新しいテストケースの追加:
invalidRune
という匿名関数が定義されています。この関数は、入力されたルーンr
に関わらず、常にutf8.MaxRune + 1
という不正なルーンを返します。m = Map(invalidRune, []byte("x"))
:bytes.Map
関数にこのinvalidRune
マッピング関数と、任意の入力バイトスライス(ここでは"x"
)を渡して呼び出します。expect = "\\uFFFD"
:期待される結果は、入力ルーンが不正なルーンにマッピングされた結果として、\uFFFD
(Unicode置換文字)が返されることです。if string(m) != expect { t.Errorf(...) }
: 実際の出力m
が期待される\uFFFD
と一致するかどうかを検証します。このテストケースは、bytes.Map
が不正なルーンを適切に処理し、パニックを起こさずに\uFFFD
を返すことを保証します。
これらの変更により、bytes.Map
関数はより堅牢になり、不正なルーンの処理においても安定して動作するようになりました。
関連リンク
- Go Issue #7577: bytes: Map panics on invalid rune
- Go Code Review 77420044: https://golang.org/cl/77420044
参考にした情報源リンク
- GoDoc: unicode/utf8 package
- GoDoc: bytes package
- Unicode Replacement Character (U+FFFD)
- UTF-8 - Wikipedia
- Go言語のruneと文字列について (一般的なGoのrune解説)
- Go言語のbytes.Map関数の使い方 (Go公式ブログの文字列に関する記事、Map関数に直接言及はないが関連知識として)