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

[インデックス 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関数(および関連するScanfSscanなど)が、ブール値の文字列表現である"FALSE"を正しくパースできないという問題でした。

Go言語のfmtパッケージは、C言語のprintf/scanfに似た書式付きI/O機能を提供します。Scan系の関数は、入力ストリームから値を読み取り、指定された型の変数に格納します。ブール値をスキャンする際、"true"や"false"(大文字小文字を区別しない)といった文字列を認識する必要があります。

元の実装では、"FALSE"の'F'の後に続く文字をチェックするロジックに誤りがあり、"FALSE"を正しくfalseブール値として解釈できませんでした。このバグにより、ユーザーが"FALSE"という文字列を入力した場合に、予期せぬエラーや誤った値が変数に格納される可能性がありました。

前提知識の解説

  • fmtパッケージ: Go言語の標準ライブラリの一つで、書式付きI/O(入力と出力)を提供します。PrintfSprintfのような出力関数だけでなく、ScanfSscanfのような入力関数も含まれます。これらの入力関数は、文字列やバイト列から指定された書式に従って値を読み取ります。
  • 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"を認識します。

  1. case 'f', 'F': で最初の文字 'F' を認識。
  2. s.accept("aA") で次の文字 'A' を認識。
  3. s.accept("lL") で次の文字 'L' を認識。
  4. s.accept("sS") で次の文字 'S' を認識。
  5. s.accept("eE") で次の文字 'E' を認識。 これにより、"FALSE"という文字列全体が正しくfalseブール値として解釈されるようになります。

また、src/pkg/fmt/scan_test.goには、この修正が正しく機能することを確認するための新しいテストケースが追加されています。{"%v%v", "FALSE23", args(&truth, &i), args(false, 23), ""}というテストケースは、"FALSE23"という文字列を読み込み、最初の%vfalseとして、次の%v23として正しくパースされることを検証します。

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

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): 期待される結果です。truthfalseになり、i23になることを検証します。
    • "": 残りの入力文字列(この場合はなし)。

このテストケースの追加により、"FALSE"という文字列が正しくfalseブール値としてパースされ、その後の数値も正しく読み取られることが保証されます。これは、修正が意図通りに機能していることを確認するための重要な回帰テストとなります。

関連リンク

参考にした情報源リンク

  • 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言語のパーサー実装に関する一般的な知識)