[インデックス 11733] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおけるブール値のパース処理に関するバグ修正です。具体的には、fmt
パッケージのScan
関数群が文字列"FALSE"を正しく認識できない問題を解決し、関連するテストケースを追加しています。
コミット
commit c0e74b63cf3ef73191f8908609c2c22b75a50be6
Author: Rob Pike <r@golang.org>
Date: Thu Feb 9 14:12:55 2012 +1100
fmt: scan FALSE correctly
Fixes bug 2922.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5642072
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c0e74b63cf3ef73191f8908609c2c22b75a50be6
元コミット内容
fmt: scan FALSE correctly
このコミットは、fmt
パッケージがブール値の文字列表現である"FALSE"を正しくスキャンできるように修正します。これはバグ2922の修正に対応しています。
変更の背景
このコミットは、Go言語のIssueトラッカーで報告されたバグ2922を修正するために行われました。バグ2922は、fmt
パッケージのScan
関数(および関連するScanf
、Sscan
など)が、ブール値の文字列表現である"FALSE"を正しくパースできないという問題でした。
Go言語のfmt
パッケージは、C言語のprintf
/scanf
に似た書式付きI/O機能を提供します。Scan
系の関数は、入力ストリームから値を読み取り、指定された型の変数に格納します。ブール値をスキャンする際、"true"や"false"(大文字小文字を区別しない)といった文字列を認識する必要があります。
元の実装では、"FALSE"の'F'の後に続く文字をチェックするロジックに誤りがあり、"FALSE"を正しくfalse
ブール値として解釈できませんでした。このバグにより、ユーザーが"FALSE"という文字列を入力した場合に、予期せぬエラーや誤った値が変数に格納される可能性がありました。
前提知識の解説
fmt
パッケージ: Go言語の標準ライブラリの一つで、書式付きI/O(入力と出力)を提供します。Printf
やSprintf
のような出力関数だけでなく、Scanf
やSscanf
のような入力関数も含まれます。これらの入力関数は、文字列やバイト列から指定された書式に従って値を読み取ります。Scan
関数群:fmt
パッケージには、Scan
,Scanf
,Sscan
,Sscanf
,Fscan
,Fscanf
など、様々な入力関数があります。これらは、それぞれ標準入力、文字列、ファイルなどからデータを読み取ります。- ブール値のパース:
fmt
パッケージのScan
関数群がブール値を読み取る際、入力文字列が"true"または"false"(大文字小文字を区別しない)のいずれかであるかを判断します。内部的には、これらの文字列の各文字を順にチェックして、正しいブール値に変換します。 s.accept
関数:fmt
パッケージの内部で使われるヘルパー関数で、入力ストリームから特定の文字セットのいずれか一つを消費しようとします。例えば、s.accept("aA")
は、次の文字が'a'または'A'であればそれを消費し、true
を返します。そうでなければ消費せず、false
を返します。これは、大文字小文字を区別しないマッチングを行う際によく使用されます。boolError
:fmt
パッケージ内部で定義されているエラーで、ブール値のパースに失敗した場合に返される可能性があります。
技術的詳細
このコミットの技術的な核心は、src/pkg/fmt/scan.go
内のscanBool
メソッドにおけるs.accept
関数の引数の修正です。
scanBool
メソッドは、入力ストリームからブール値を読み取る責任を負っています。false
という文字列をパースする際、このメソッドはまず最初の文字が'f'または'F'であることを確認します。その後、残りの文字が"alse"(大文字小文字を区別しない)と続くことを期待します。
元のコードでは、'f'または'F'の後に続く文字としてs.accept("aL")
と記述されていました。これは、'a'または'L'のいずれかの文字を期待するという意味です。しかし、"FALSE"という文字列の2番目の文字は'A'であり、'L'ではありません。したがって、s.accept("aL")
は'A'を正しく受け入れることができませんでした。
修正後のコードでは、s.accept("aL")
がs.accept("aA")
に変更されています。これにより、'f'または'F'の後に続く文字として'a'または'A'のいずれかを正しく受け入れることができるようになり、"FALSE"という文字列が正しくパースされるようになりました。
この変更により、scanBool
メソッドは以下のシーケンスで"FALSE"を認識します。
case 'f', 'F':
で最初の文字 'F' を認識。s.accept("aA")
で次の文字 'A' を認識。s.accept("lL")
で次の文字 'L' を認識。s.accept("sS")
で次の文字 'S' を認識。s.accept("eE")
で次の文字 'E' を認識。 これにより、"FALSE"という文字列全体が正しくfalse
ブール値として解釈されるようになります。
また、src/pkg/fmt/scan_test.go
には、この修正が正しく機能することを確認するための新しいテストケースが追加されています。{"%v%v", "FALSE23", args(&truth, &i), args(false, 23), ""}
というテストケースは、"FALSE23"という文字列を読み込み、最初の%v
がfalse
として、次の%v
が23
として正しくパースされることを検証します。
コアとなるコードの変更箇所
diff --git a/src/pkg/fmt/scan.go b/src/pkg/fmt/scan.go
index 36c6aebad0..fa9a5584a8 100644
--- a/src/pkg/fmt/scan.go
+++ b/src/pkg/fmt/scan.go
@@ -512,7 +512,7 @@ func (s *ss) scanBool(verb rune) bool {
}
return true
case 'f', 'F':
- if s.accept("aL") && (!s.accept("lL") || !s.accept("sS") || !s.accept("eE")) {
+ if s.accept("aA") && (!s.accept("lL") || !s.accept("sS") || !s.accept("eE")) {
s.error(boolError)
}
return false
diff --git a/src/pkg/fmt/scan_test.go b/src/pkg/fmt/scan_test.go
index b26c828cbf..61b48f9cc6 100644
--- a/src/pkg/fmt/scan_test.go
+++ b/src/pkg/fmt/scan_test.go
@@ -317,6 +317,7 @@ var overflowTests = []ScanTest{
{"(1-1e500i)", &complex128Val, 0},
}
+var truth bool
var i, j, k int
var f float64
var s, t string
@@ -350,6 +351,9 @@ var multiTests = []ScanfMultiTest{
// Bad UTF-8: should see every byte.
{"%c%c%c", "\xc2X\xc2", args(&r1, &r2, &r3), args(utf8.RuneError, 'X', utf8.RuneError), ""},
+
+ // Fixed bugs
+ {"%v%v", "FALSE23", args(&truth, &i), args(false, 23), ""},
}
func testScan(name string, t *testing.T, scan func(r io.Reader, a ...interface{}) (int, error)) {
コアとなるコードの解説
src/pkg/fmt/scan.go
の変更
-
変更前:
if s.accept("aL") && (!s.accept("lL") || !s.accept("sS") || !s.accept("eE")) {
s.accept("aL")
は、次の文字が'a'または'L'であることを期待していました。しかし、"FALSE"の2番目の文字は'A'であり、この条件では正しくマッチしませんでした。このため、"FALSE"という文字列が正しくパースされず、boolError
が発生する可能性がありました。 -
変更後:
if s.accept("aA") && (!s.accept("lL") || !s.accept("sS") || !s.accept("eE")) {
s.accept("aA")
に変更されたことで、次の文字が'a'または'A'のいずれかであれば受け入れるようになりました。これにより、"FALSE"の'A'が正しく認識され、その後の"LSE"も順に認識されることで、"FALSE"全体がfalse
として正しくパースされるようになりました。
src/pkg/fmt/scan_test.go
の変更
-
追加された変数:
var truth bool
新しいテストケースで使用するために、
bool
型の変数truth
が追加されました。 -
追加されたテストケース:
// Fixed bugs {"%v%v", "FALSE23", args(&truth, &i), args(false, 23), ""},
multiTests
スライスに新しいテストケースが追加されました。"%v%v"
: 2つの値を汎用的な書式で読み込むことを指定します。"FALSE23"
: 入力文字列です。args(&truth, &i)
: 入力された値を格納する変数のポインタです。truth
には"FALSE"が、i
には"23"が格納されることを期待します。args(false, 23)
: 期待される結果です。truth
がfalse
になり、i
が23
になることを検証します。""
: 残りの入力文字列(この場合はなし)。
このテストケースの追加により、"FALSE"という文字列が正しくfalse
ブール値としてパースされ、その後の数値も正しく読み取られることが保証されます。これは、修正が意図通りに機能していることを確認するための重要な回帰テストとなります。
関連リンク
- Go CL 5642072: https://golang.org/cl/5642072
参考にした情報源リンク
- Go Issue 2922: https://github.com/golang/go/issues/2922
- Go
fmt
パッケージのドキュメント: https://pkg.go.dev/fmt - Go言語のソースコード(
fmt
パッケージ関連)src/pkg/fmt/scan.go
src/pkg/fmt/scan_test.go
- Go言語の
s.accept
関数の一般的な動作に関する情報(Go言語のパーサー実装に関する一般的な知識)