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

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

このコミットは、Go言語の静的解析ツールである cmd/vetprintf フォーマット文字列チェック機能に対する改善を含んでいます。具体的には、浮動小数点数に対して %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 スタイルのフォーマット文字列を解析する際の精度向上と、より詳細な診断情報の提供という目的があります。

  1. %b フォーマット動詞の誤認識の修正: 以前の cmd/vet は、%b フォーマット動詞を整数型にのみ適用可能と誤って認識していました。しかし、Go言語の fmt パッケージにおいて、%b は整数だけでなく浮動小数点数(バイナリ指数表記)にも使用できる有効なフォーマット動詞です。この誤認識により、正当なコードが vet によって誤って警告される可能性がありました。このコミットは、%b が浮動小数点数にも適用可能であることを vet に正しく認識させることで、誤検知を減らし、ツールの信頼性を向上させます。

  2. gotype からの「無効な型」レポートの追加: cmd/vet は、コードの静的解析を行う際に、Goコンパイラの型チェッカーである gotype の情報も利用します。しかし、gotype が型情報を解決できない、または無効な型を検出した場合、その情報が vet の診断に十分に活用されていませんでした。この変更により、-v (verbose) フラグが設定されている場合に、gotype が報告する「無効な型」に関する警告を vet が出力するようになります。これにより、開発者は型システムの問題に起因する printf の誤用をより早期に、かつ詳細に把握できるようになります。

  3. エラーメッセージの改善: printf の引数に関するエラーメッセージが、問題のある引数自体を含むように改善されています。これにより、開発者はどの引数が問題を引き起こしているのかをより迅速に特定できるようになり、デバッグの効率が向上します。

これらの変更は、cmd/vet が提供する静的解析の品質を高め、開発者がより堅牢でバグの少ないGoプログラムを作成するのを支援することを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のツールと概念に関する知識が役立ちます。

  • cmd/vet: vet はGo言語の公式な静的解析ツールです。コンパイラでは検出できないが、コードの潜在的なバグや疑わしい構成を特定するために使用されます。例えば、printf フォーマット文字列と引数の不一致、構造体タグの誤り、ロックの誤用などを検出します。vet はGoのソースコードを解析し、抽象構文木 (AST) を構築して、そのAST上で様々なチェックを実行します。

  • fmt パッケージと printf フォーマット動詞: Go言語の fmt パッケージは、フォーマットされたI/Oを実装するための機能を提供します。fmt.Printffmt.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 フォーマットチェックロジックに焦点を当てています。

  1. %b フォーマット動詞の浮動小数点数対応: printVerbs 配列は、各 printf フォーマット動詞が受け入れる引数の型を定義しています。以前は、'b' (バイナリ) 動詞は argInt (整数型) のみを受け入れると定義されていました。

    // 変更前
    {'b', numFlag, argInt},
    

    このコミットにより、argFloat (浮動小数点型) も受け入れるように変更されました。

    // 変更後
    {'b', numFlag, argInt | argFloat},
    

    argInt | argFloat はビットOR演算子で、'b' が整数型または浮動小数点型のいずれかの引数を受け入れることを示します。これにより、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. 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/vetprintf フォーマットチェックの堅牢性と診断能力を向上させ、開発者がより正確なフィードバックを得られるようにします。

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

変更は 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/)
    (これらのリンクは一般的な情報源であり、特定のコミットに直接関連するものではありませんが、背景知識の理解に役立ちます。)