[インデックス 15501] ファイルの概要
このコミットは、Go言語の静的解析ツールである cmd/vet
の printf
フォーマット文字列チェック機能に対する改善を含んでいます。具体的には、浮動小数点数に対して %b
フォーマット動詞が有効であることを認識するように修正し、gotype
からの「無効な型」に関するレポートを -v
(verbose) フラグが設定されている場合に表示するように拡張しています。これにより、vet
ツールがより正確な診断を提供し、開発者が printf
の誤用を特定しやすくなります。
コミット
commit 3dc7f17e892a5422ca9a21e1eb3187850c31cebb
Author: Rob Pike <r@golang.org>
Date: Thu Feb 28 11:32:53 2013 -0800
cmd/vet: %b is a valid floating-point format.
Also add a report about "invalid type" from gotype, if -v is set.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7420045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3dc7f17e892a5422ca9a21e1eb3187850c31cebb
元コミット内容
cmd/vet: %b is a valid floating-point format.
Also add a report about "invalid type" from gotype, if -v is set.
変更の背景
この変更の背景には、cmd/vet
ツールが printf
スタイルのフォーマット文字列を解析する際の精度向上と、より詳細な診断情報の提供という目的があります。
-
%b
フォーマット動詞の誤認識の修正: 以前のcmd/vet
は、%b
フォーマット動詞を整数型にのみ適用可能と誤って認識していました。しかし、Go言語のfmt
パッケージにおいて、%b
は整数だけでなく浮動小数点数(バイナリ指数表記)にも使用できる有効なフォーマット動詞です。この誤認識により、正当なコードがvet
によって誤って警告される可能性がありました。このコミットは、%b
が浮動小数点数にも適用可能であることをvet
に正しく認識させることで、誤検知を減らし、ツールの信頼性を向上させます。 -
gotype
からの「無効な型」レポートの追加:cmd/vet
は、コードの静的解析を行う際に、Goコンパイラの型チェッカーであるgotype
の情報も利用します。しかし、gotype
が型情報を解決できない、または無効な型を検出した場合、その情報がvet
の診断に十分に活用されていませんでした。この変更により、-v
(verbose) フラグが設定されている場合に、gotype
が報告する「無効な型」に関する警告をvet
が出力するようになります。これにより、開発者は型システムの問題に起因するprintf
の誤用をより早期に、かつ詳細に把握できるようになります。 -
エラーメッセージの改善:
printf
の引数に関するエラーメッセージが、問題のある引数自体を含むように改善されています。これにより、開発者はどの引数が問題を引き起こしているのかをより迅速に特定できるようになり、デバッグの効率が向上します。
これらの変更は、cmd/vet
が提供する静的解析の品質を高め、開発者がより堅牢でバグの少ないGoプログラムを作成するのを支援することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のツールと概念に関する知識が役立ちます。
-
cmd/vet
:vet
はGo言語の公式な静的解析ツールです。コンパイラでは検出できないが、コードの潜在的なバグや疑わしい構成を特定するために使用されます。例えば、printf
フォーマット文字列と引数の不一致、構造体タグの誤り、ロックの誤用などを検出します。vet
はGoのソースコードを解析し、抽象構文木 (AST) を構築して、そのAST上で様々なチェックを実行します。 -
fmt
パッケージとprintf
フォーマット動詞: Go言語のfmt
パッケージは、フォーマットされたI/Oを実装するための機能を提供します。fmt.Printf
やfmt.Sprintf
などの関数は、C言語のprintf
に似たフォーマット文字列を使用します。フォーマット文字列には、%d
(整数)、%s
(文字列)、%f
(浮動小数点数) などの「フォーマット動詞」が含まれ、対応する引数の値をどのように表示するかを制御します。%b
: 整数型の場合、基数2のバイナリ表現で表示します。浮動小数点型の場合、p
フォーマット動詞と同様に、バイナリ指数表記(例:-123.456e+78
のような形式)で表示します。%v
: 引数のデフォルトのフォーマットで表示します。これは、型に応じて適切なフォーマットを自動的に選択するため、非常に便利です。
-
gotype
:gotype
はGo言語の型チェッカーです。Goのソースコードを解析し、型情報(変数、関数、式の型など)を解決します。cmd/vet
は、printf
の引数の型がフォーマット動詞と一致しているかをチェックする際に、このgotype
から得られる型情報を利用します。gotype
が型を解決できない場合、それは通常、コードに構文エラーや型エラーがあることを示します。 -
抽象構文木 (AST): コンパイラや静的解析ツールは、ソースコードを直接扱うのではなく、まずそれを抽象構文木 (Abstract Syntax Tree, AST) と呼ばれるツリー構造に変換します。ASTは、プログラムの構造を抽象的に表現したもので、各ノードはコードの構成要素(変数宣言、関数呼び出し、式など)に対応します。
cmd/vet
は、このASTを走査して、特定のパターンや誤用を検出します。 -
types
パッケージ: Goの標準ライブラリには、Goプログラムの型情報を扱うためのgo/types
パッケージがあります。cmd/vet
のようなツールは、このパッケージを利用して、ソースコード内の識別子や式の型を正確に判断します。types.Invalid
は、go/types
パッケージが型を解決できなかったり、無効な型を検出したりした場合に返される特別な型です。
技術的詳細
このコミットは、src/cmd/vet/print.go
ファイル内の printf
フォーマットチェックロジックに焦点を当てています。
-
%b
フォーマット動詞の浮動小数点数対応:printVerbs
配列は、各printf
フォーマット動詞が受け入れる引数の型を定義しています。以前は、'b'
(バイナリ) 動詞はargInt
(整数型) のみを受け入れると定義されていました。// 変更前 {'b', numFlag, argInt},
このコミットにより、
argFloat
(浮動小数点型) も受け入れるように変更されました。// 変更後 {'b', numFlag, argInt | argFloat},
argInt | argFloat
はビットOR演算子で、'b'
が整数型または浮動小数点型のいずれかの引数を受け入れることを示します。これにより、vet
は浮動小数点数に対する%b
の使用を正当なものとして認識し、誤った警告を発しなくなります。 -
エラーメッセージの改善:
checkPrintfArg
関数内で、printf
のフォーマット文字列における*
(可変幅) の引数がint
型でない場合、およびフォーマット動詞と引数の型が一致しない場合の警告メッセージが改善されました。*
の引数に関する警告:
変更後では、// 変更前 f.Badf(call.Pos(), "arg for * in printf format not of type int") // 変更後 f.Badf(call.Pos(), "arg %s for * in printf format not of type int", call.Args[argNum+i])
call.Args[argNum+i]
を追加することで、どの引数が問題を引き起こしているかを具体的に表示するようになりました。- 型不一致に関する警告:
同様に、// 変更前 f.Badf(call.Pos(), "arg for printf verb %%%c of wrong type: %s", verb, typeString) // 変更後 f.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", arg, verb, typeString)
arg
(問題の引数) を追加することで、より具体的な情報を提供するようになりました。
-
gotype
からの「無効な型」レポートの追加:matchArgType
関数は、printf
の引数の型が期待される型と一致するかどうかをチェックします。このコミットでは、types.Invalid
型の引数に対する新しいケースが追加されました。case types.Invalid: if *verbose { f.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", arg) } return true // Probably a type check problem.
types.Invalid
は、gotype
が引数の型を解決できなかった場合に発生します。この場合、*verbose
(つまり-v
フラグが設定されている場合) が真であれば、vet
は「printf 引数 %v は無効または不明な型です」という警告を出力します。return true
とすることで、vet
はこの引数を「一致した」とみなし、これ以上エラーを報告しないようにします。これは、型チェックの問題が根本原因であるため、vet
がさらに誤ったエラーを報告するのを防ぐための措置です。
これらの変更は、cmd/vet
の printf
フォーマットチェックの堅牢性と診断能力を向上させ、開発者がより正確なフィードバックを得られるようにします。
コアとなるコードの変更箇所
変更は src/cmd/vet/print.go
ファイルに集中しています。
--- a/src/cmd/vet/print.go
+++ b/src/cmd/vet/print.go
@@ -243,7 +243,7 @@ var printVerbs = []printVerb{
// '+' is required sign for numbers, Go format for %v.
// '#' is alternate format for several verbs.
// ' ' is spacer for numbers
- {'b', numFlag, argInt},\n
+ {'b', numFlag, argInt | argFloat},\n
{'c', "-", argRune | argInt},\n
{'d', numFlag, argInt},\n
{'e', numFlag, argFloat},\n
@@ -280,7 +280,7 @@ func (f *File) checkPrintfArg(call *ast.CallExpr, verb rune, flags []byte, argNu\n \t\t\t// arg must be integer.\n \t\t\tfor i := 0; i < nargs-1; i++ {\n \t\t\t\tif !f.matchArgType(argInt, call.Args[argNum+i]) {\n-\t\t\t\t\tf.Badf(call.Pos(), "arg for * in printf format not of type int")\n+\t\t\t\t\tf.Badf(call.Pos(), "arg %s for * in printf format not of type int", call.Args[argNum+i])\n \t\t\t\t}\n \t\t\t}\n \t\t\tfor _, v := range printVerbs {\n@@ -291,7 +291,7 @@ func (f *File) checkPrintfArg(call *ast.CallExpr, verb rune, flags []byte, argNu\n \t\t\t\t\t\tif typ := f.pkg.types[arg]; typ != nil {\n \t\t\t\t\t\t\ttypeString = typ.String()\n \t\t\t\t\t\t}\n-\t\t\t\t\t\tf.Badf(call.Pos(), "arg for printf verb %%%c of wrong type: %s", verb, typeString)\n+\t\t\t\t\t\tf.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", arg, verb, typeString)\n \t\t\t\t\t}\n \t\t\t\t\tbreak\n \t\t\t\t}\n@@ -346,6 +346,11 @@ func (f *File) matchArgType(t printfArgType, arg ast.Expr) bool {\n \t\treturn t&argString != 0\n \tcase types.UntypedNil:\n \t\treturn t&argPointer != 0 // TODO?\n+\tcase types.Invalid:\n+\t\tif *verbose {\n+\t\t\tf.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", arg)\n+\t\t}\n+\t\treturn true // Probably a type check problem.\n \t}\n \treturn false\n }\n```
## コアとなるコードの解説
1. **`printVerbs` 配列の変更**:
`printVerbs` は、`printf` フォーマット動詞の定義を保持するグローバル変数です。この配列内の `'b'` エントリの `argType` フィールドが `argInt` から `argInt | argFloat` に変更されました。
* **変更前**: `{'b', numFlag, argInt}` は、`%b` が整数型 (`argInt`) の引数のみを受け入れることを示していました。
* **変更後**: `{'b', numFlag, argInt | argFloat}` は、`%b` が整数型または浮動小数点型 (`argFloat`) の引数を受け入れることを示します。これにより、`vet` は浮動小数点数に対する `%b` の使用を正しく認識し、誤った警告を抑制します。
2. **`checkPrintfArg` 関数内のエラーメッセージの変更**:
この関数は、`printf` の引数がフォーマット動詞と一致しているかをチェックします。
* `*` (可変幅) の引数が `int` 型でない場合のエラーメッセージ:
`f.Badf(call.Pos(), "arg for * in printf format not of type int")` から `f.Badf(call.Pos(), "arg %s for * in printf format not of type int", call.Args[argNum+i])` に変更されました。これにより、エラーメッセージに問題の引数自体 (`call.Args[argNum+i]`) が含まれるようになり、どの引数が原因であるかを一目で特定できるようになります。
* フォーマット動詞と引数の型が一致しない場合のエラーメッセージ:
`f.Badf(call.Pos(), "arg for printf verb %%%c of wrong type: %s", verb, typeString)` から `f.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", arg, verb, typeString)` に変更されました。同様に、エラーメッセージに問題の引数 (`arg`) が含まれることで、デバッグが容易になります。
3. **`matchArgType` 関数への `types.Invalid` ケースの追加**:
この関数は、与えられた引数の型が、期待される `printfArgType` と一致するかどうかを判断します。
* 新しい `case types.Invalid:` ブロックが追加されました。これは、`gotype` が引数の型を解決できなかった場合に発生します。
* `if *verbose` 条件は、`vet` コマンドが `-v` (verbose) フラグ付きで実行された場合にのみ、以下の警告を出力することを示します。
* `f.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", arg)` は、無効または不明な型を持つ `printf` 引数について警告を出力します。`arg.Pos()` はソースコード内の位置を示し、`arg` は問題の引数自体です。
* `return true` は、`types.Invalid` の場合でも、この引数を「一致した」とみなすことを意味します。これは、型解決の問題が根本原因であるため、`vet` がさらに誤った型不一致のエラーを報告するのを防ぐための措置です。これにより、`vet` は型チェックの問題に起因する混乱を避け、より適切な診断を提供します。
これらの変更は、`cmd/vet` の `printf` フォーマットチェックの精度とユーザビリティを大幅に向上させます。
## 関連リンク
* Go CL 7420045: [https://golang.org/cl/7420045](https://golang.org/cl/7420045)
## 参考にした情報源リンク
* Go言語の `fmt` パッケージドキュメント: [https://pkg.go.dev/fmt](https://pkg.go.dev/fmt)
* Go言語の `cmd/vet` ドキュメント (Goのソースコードリポジトリ内): [https://github.com/golang/go/tree/master/src/cmd/vet](https://github.com/golang/go/tree/master/src/cmd/vet)
* Go言語の `go/types` パッケージドキュメント: [https://pkg.go.dev/go/types](https://pkg.go.dev/go/types)
* Go言語の `go/ast` パッケージドキュメント: [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
* Go言語の `gotype` ツールに関する情報 (Goのツールチェーンの一部): [https://pkg.go.dev/golang.org/x/tools/cmd/gotype](https://pkg.go.dev/golang.org/x/tools/cmd/gotype)
* Go言語の `printf` フォーマットに関する一般的な情報 (例: Go by Example): [https://gobyexample.com/string-formatting](https://gobyexample.com/string-formatting)
* Go言語の `vet` ツールに関するブログ記事やチュートリアル (一般的な情報源):
* A Tour of Go: [https://go.dev/tour/](https://go.dev/tour/)
* Effective Go: [https://go.dev/doc/effective_go](https://go.dev/doc/effective_go)
* Go Blog: [https://go.dev/blog/](https://go.dev/blog/)
(これらのリンクは一般的な情報源であり、特定のコミットに直接関連するものではありませんが、背景知識の理解に役立ちます。)