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

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

このコミットは、Go言語のcmd/vetツールにおけるprintfフォーマットの解析ロジックを改善するものです。具体的には、%s (文字列) および %q (クォートされた文字列/文字) のフォーマット動詞が認識するフラグを拡張し、より多様なprintf書式指定に対応できるようにしています。変更はsrc/cmd/vet/print.goファイルに対して行われています。

コミット

commit bb576704709e6edb8e45f0f2b1a8ddfefc99e010
Author: David Symonds <dsymonds@golang.org>
Date:   Fri Dec 21 13:48:36 2012 +1100

    cmd/vet: expand printf flags understood by %s and %q.
    
    Fixes #4580.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/7002043

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

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

元コミット内容

cmd/vet: expand printf flags understood by %s and %q.

Fixes #4580.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/7002043

変更の背景

Go言語のcmd/vetツールは、Goのソースコードを静的に解析し、潜在的なバグや疑わしい構造を報告するツールです。その機能の一つに、fmtパッケージのPrintf系の関数呼び出しにおけるフォーマット文字列の誤りを検出する機能があります。例えば、引数の型とフォーマット動詞が一致しない場合や、引数の数が合わない場合などです。

このコミットが行われる前は、cmd/vet%s%qといった特定のフォーマット動詞に対して、printf書式で利用可能なすべてのフラグ(例えば、幅指定や精度指定、パディングなど)を正しく認識していませんでした。そのため、fmt.Printf("%08s", "woo")fmt.Printf("% 8s", "woo")のように、%s%qに有効なフラグを付けても、cmd/vetがそれを誤って「不正なフォーマット」と判断し、誤った警告を発する可能性がありました。

この問題は、GoのIssue #4580として報告されていました。このコミットは、cmd/vet%sおよび%qに適用されるprintfフラグをより正確に理解できるようにすることで、この誤検知の問題を修正することを目的としています。これにより、開発者はprintfの機能をより柔軟に利用できるようになり、cmd/vetの警告の精度が向上します。

前提知識の解説

cmd/vetツール

cmd/vetは、Go言語の標準ツールチェーンに含まれる静的解析ツールです。ソースコードを検査し、以下のような潜在的な問題点を報告します。

  • printfフォーマットの誤り: fmt.Printfなどの関数で、フォーマット文字列と引数の型や数が一致しない場合。
  • 構造体タグの誤り: json:"field_name"のような構造体タグの構文エラー。
  • ロックの誤用: sync.Mutexなどのミューテックスのロック/アンロックの不整合。
  • 未使用の変数: 宣言されたが使用されていない変数。
  • シャドーイング: 外部スコープの変数を内部スコープで再宣言してしまうこと。

cmd/vetは、コンパイルエラーにはならないが実行時に問題を引き起こす可能性のあるコードパターンを早期に発見するのに役立ちます。

Go言語のfmtパッケージとprintfフォーマット

Go言語のfmtパッケージは、C言語のprintf関数に似た書式付きI/O機能を提供します。主な関数にはfmt.Printffmt.Sprintffmt.Fprintfなどがあります。これらの関数は、フォーマット文字列とそれに続く引数を受け取り、指定された書式で文字列を生成または出力します。

フォーマット文字列は、通常の文字列と「フォーマット動詞 (verb)」と呼ばれる特殊なシーケンスで構成されます。フォーマット動詞は%で始まり、その後に文字が続きます(例: %s, %d, %v)。

printfフォーマット動詞とフラグ

printfフォーマット動詞には、その動作を修飾するための「フラグ (flags)」、「幅 (width)」、「精度 (precision)」などを指定できます。一般的な構文は以下の通りです。

%[flags][width][.precision]verb

  • flags:
    • -: 左寄せ(デフォルトは右寄せ)。
    • +: 数値の符号を常に表示(正の場合も+を表示)。
    • (スペース): 正の数値の場合、符号の代わりにスペースを表示。
    • 0: 幅指定時に、空白の代わりにゼロでパディング。
    • #: 代替フォーマット(例: %#x0xプレフィックス、%#o0プレフィックス)。
  • width: 最小の出力幅を指定します。値が幅より短い場合、パディングされます。
  • .precision:
    • 浮動小数点数: 小数点以下の桁数。
    • 文字列: 出力する最大文字数。
    • 整数: 最小桁数(0でパディング)。
  • verb: フォーマットの種類を指定する文字(例: s for string, d for decimal, q for quoted string)。

このコミットで関連するのは、特に%s%q動詞です。

  • %s: 文字列をそのまま出力します。
  • %q: Goの構文で安全にエスケープされた、ダブルクォートされた文字列リテラルを出力します。

例えば、fmt.Printf("%08s", "woo")は、文字列"woo"を8文字幅で右寄せし、左側をゼロでパディングして"00000woo"と出力します。fmt.Printf("% 8s", "woo")は、スペースでパディングして" woo"と出力します。

技術的詳細

cmd/vetツールは、fmtパッケージのprintfフォーマット文字列を解析し、その有効性を検証します。この解析ロジックは、主にsrc/cmd/vet/print.goファイルに実装されています。

print.goファイルには、printVerbsというグローバル変数があります。これはprintVerb構造体のスライスであり、各要素が特定のフォーマット動詞(例: s, q, d)と、その動詞が受け入れるprintfフラグのセットを定義しています。

printVerb構造体は以下のようなフィールドを持つと考えられます(実際の定義はソースコードで確認する必要がありますが、コミット内容から推測できます):

type printVerb struct {
    verb rune   // フォーマット動詞の文字 (例: 's', 'q')
    flags string // その動詞が受け入れるフラグの文字列
}

flagsフィールドの文字列は、その動詞が有効と見なすprintfフラグの集合を表します。例えば、"-+#."という文字列は、その動詞が- (左寄せ)、+ (符号表示)、# (代替フォーマット)、. (精度指定) の各フラグを受け入れることを示します。cmd/vetは、フォーマット文字列を解析する際に、指定された動詞にこれらのフラグが許可されているかをこのflags文字列と照合してチェックします。

このコミット以前のprintVerbsの定義では、%s%qに対しては、認識されるフラグが限定的でした。

  • {'q', "-+#."}: %q-, +, #, . フラグを認識。
  • {'s', "-."}: %s-, . フラグを認識。

しかし、Goのfmtパッケージの実際のprintf実装では、%s%qに対しても、幅指定(0やスペースによるパディングを含む)が可能です。cmd/vetがこれらのフラグを認識しないため、有効なprintf書式であっても誤って警告を発していました。

このコミットの目的は、%s%qが実際にサポートするすべてのprintfフラグをcmd/vetが認識できるように、printVerbsの定義を更新することです。

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

--- a/src/cmd/vet/print.go
+++ b/src/cmd/vet/print.go
@@ -208,8 +208,8 @@ var printVerbs = []printVerb{
 	{'G', numFlag},
 	{'o', sharpNumFlag},
 	{'p', "-#"},
-	{'q', "-+#."},
-	{'s', "-."},
+	{'q', " -+.0"},
+	{'s', " -+.0"},
 	{'t', "-"},
 	{'T', "-"},
 	{'U', "-#"},
@@ -282,6 +282,8 @@ func BadFunctionUsedInTests() {
 	fmt.Println("%s", "hi")            // ERROR "possible formatting directive in Println call"
 	fmt.Printf("%s", "hi", 3)          // ERROR "wrong number of args in Printf call"
 	fmt.Printf("%s%%%d", "hi", 3)      // correct
+	fmt.Printf("%08s", "woo")          // correct
+	fmt.Printf("% 8s", "woo")          // correct
 	fmt.Printf("%.*d", 3, 3)           // correct
 	fmt.Printf("%.*d", 3, 3, 3)        // ERROR "wrong number of args in Printf call"
 	fmt.Printf("%q %q", multi()...)    // ok

コアとなるコードの解説

このコミットの主要な変更は、src/cmd/vet/print.goファイル内のprintVerbsスライスにおける'q''s'printVerbエントリのflagsフィールドの更新です。

変更前:

  • {'q', "-+#."}
  • {'s', "-."}

変更後:

  • {'q', " -+.0"}
  • {'s', " -+.0"}

新しいflags文字列" -+.0"は、以下のフラグが有効であることを示します。

  • (スペース): 正の数値の場合に符号の代わりにスペースを挿入するフラグ。文字列に対しても幅指定と組み合わせてパディングとして機能します。
  • -: 左寄せフラグ。
  • +: 符号表示フラグ。%q%sでは通常意味を持ちませんが、cmd/vetの内部ロジックで一貫性を持たせるために含まれている可能性があります。
  • .: 精度指定フラグ。文字列の場合は最大文字数を指定します。
  • 0: ゼロパディングフラグ。幅指定と組み合わせて、左側をゼロで埋めます。

この変更により、cmd/vet%sおよび%qフォーマット動詞に対して、スペースパディング (% 8s) やゼロパディング (%08s) を含む、より広範なprintfフラグの組み合わせを正しく認識できるようになります。

また、コミットの差分には、BadFunctionUsedInTests()関数内のテストケースの追加も含まれています。

	fmt.Printf("%08s", "woo")          // correct
	fmt.Printf("% 8s", "woo")          // correct

これらの行は、変更が正しく機能することを確認するためのテストケースです。以前はcmd/vetがこれらの行に対して誤った警告を発していた可能性がありますが、この変更後は「correct」とコメントされているように、正しく認識されるようになります。これにより、cmd/vetの誤検知が減少し、開発体験が向上します。

関連リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/bb576704709e6edb8e45f0f2b1a8ddfefc99e010
  • Go Code Review (CL) リンク: https://golang.org/cl/7002043
  • 関連するGo Issue (推定): Fixes #4580と記載されていますが、GitHubのgolang/goリポジトリのIssue #4580は直接見つかりませんでした。これは、Goプロジェクトが内部のIssueトラッカーやコードレビューシステムを使用しているため、GitHubのIssue番号と直接対応しない場合があることを示唆しています。しかし、このコミットがGoの公式リポジトリの一部であることから、Goプロジェクト内で追跡されていた問題であることは確かです。

参考にした情報源リンク