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

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

このコミットは、Go言語の静的解析ツールである cmd/vetprintf フォーマット文字列チェッカーの改善と、関連するテストファイルの修正、および sync/atomic パッケージのテストファイルにおけるフォーマット文字列の修正を含んでいます。主な目的は、unsafe.Pointer 型の printf フォーマットチェックの精度向上と、不正な型が指定された場合のより詳細なエラーメッセージの提供です。

コミット

cmd/vet: fix printf test for unsafe Pointer
And fix test. Pointer to unsafe.Pointer tests nothing important...
Also identify the incorrect type: go/types.Type is a Stringer.

Also fix a couple of incorrect format verbs found by new printf checker,
now that we can run it on more files.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7385051

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

https://github.com/golang/go/commit/6c2cbdb1428340fcbf7305d82147b87070788b16

元コミット内容

cmd/vet: fix printf test for unsafe Pointer
And fix test. Pointer to unsafe.Pointer tests nothing important...
Also identify the incorrect type: go/types.Type is a Stringer.

Also fix a couple of incorrect format verbs found by new printf checker,
now that we can run it on more files.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7385051

変更の背景

このコミットの背景には、Go言語の静的解析ツール go vetprintf フォーマット文字列チェッカーの精度向上と、より堅牢なコードベースの維持という目的があります。

  1. unsafe.Pointerprintf フォーマットチェックの不備: 以前の go vetprintf チェッカーは、unsafe.Pointer 型に対するフォーマット動詞のチェックが不十分でした。特に、%p (ポインタのアドレス表示) 以外のフォーマット動詞が unsafe.Pointer に適用された場合に、誤検知または見逃しが発生する可能性がありました。このコミットでは、unsafe.Pointer%p だけでなく、整数型 (%x, %X など) としても扱えるように、チェックロジックを修正しています。これは、unsafe.Pointer がメモリ上のアドレスを表現する際に、ポインタとしてだけでなく、整数値としても解釈されることがあるためです。

  2. テストの不正確さ: src/cmd/vet/test_print.go にあった *unsafe.Pointer (つまり unsafe.Pointer へのポインタ) を %p でフォーマットするテストは、unsafe.Pointer 自体のテストとしては適切ではありませんでした。unsafe.Pointer はそれ自体がポインタ型であり、そのポインタへのポインタをテストしても、unsafe.Pointer の本質的な printf フォーマットの挙動を検証することにはなりません。このコミットでは、テスト対象を unsafe.Pointer そのものに変更し、より実用的なテストケースを追加しています。

  3. エラーメッセージの改善: printf フォーマットの型ミスマッチが発生した場合、以前は単に「wrong type」と表示されるだけでした。このコミットでは、go/types.TypeString() メソッドを利用して、期待される型ではなく、実際に渡された引数の型情報をエラーメッセージに含めるように改善されています。これにより、開発者はどの型が問題を引き起こしているのかをより具体的に把握できるようになり、デバッグが容易になります。

  4. 新たな printf チェッカーによる発見: 新しい printf チェッカーがより多くのファイルで実行可能になった結果、src/pkg/sync/atomic/atomic_test.go 内に存在するいくつかの不正確なフォーマット動詞が発見されました。具体的には、float32float64 の値を %d (整数) でフォーマットしようとしていた箇所が %g (汎用浮動小数点) に修正されています。これは、go vet の改善が実際のコードベースの品質向上に貢献した例と言えます。

これらの変更は、Go言語のツールチェーンの品質と信頼性を向上させ、開発者がより安全で正確なコードを書くのを支援することを目的としています。

前提知識の解説

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

  1. go vet: go vet は、Go言語のソースコードを静的に解析し、潜在的なバグや疑わしい構成を報告するツールです。コンパイルは通るが、実行時に問題を引き起こす可能性のあるコードパターン(例: printf フォーマット文字列と引数の不一致、到達不能なコード、ロックの誤用など)を検出します。このコミットでは、go vetprintf チェッカーの機能が改善されています。

  2. printf フォーマット動詞: Go言語の fmt パッケージ(fmt.Printf, fmt.Sprintf など)は、C言語の printf 関数と同様に、フォーマット文字列と引数を使って出力を整形します。フォーマット文字列には、引数の型や表示形式を指定するための「フォーマット動詞」が含まれます。

    • %p: ポインタのアドレスを16進数で表示します。
    • %d: 整数を10進数で表示します。
    • %x, %X: 整数を16進数で表示します(小文字/大文字)。
    • %g: 浮動小数点数を、精度に応じて %e または %f の形式で表示します。
  3. unsafe.Pointer: unsafe.Pointer は、Go言語の型システムをバイパスして、任意の型のポインタを保持できる特殊なポインタ型です。これは、C言語との相互運用や、低レベルのメモリ操作を行う際に使用されます。unsafe.Pointer は、Goの型安全性を損なう可能性があるため、慎重に使用する必要があります。 unsafe.Pointer は、以下の変換が可能です。

    • 任意の型のポインタから unsafe.Pointer へ。
    • unsafe.Pointer から任意の型のポインタへ。
    • uintptr (符号なし整数型でポインタ値を保持できるサイズ) から unsafe.Pointer へ。
    • unsafe.Pointer から uintptr へ。 このコミットでは、unsafe.Pointer%p だけでなく、uintptr に変換可能であることから、整数フォーマット動詞 (%x, %X) とも互換性があるべきという観点から修正が行われています。
  4. go/types パッケージ: go/types パッケージは、Goプログラムの型情報を表現するためのAPIを提供します。コンパイラや静的解析ツールがGoの型システムを理解し、操作するために使用されます。

    • go/types.Type: Goの型を表すインターフェースです。
    • Stringer インターフェース: String() string メソッドを持つインターフェースです。このインターフェースを実装する型は、fmt パッケージの Print 系関数で文字列として出力される際に、String() メソッドの戻り値が使用されます。go/types.TypeStringer を実装しており、その型の文字列表現を返すことができます。

技術的詳細

このコミットは、主に src/cmd/vet/print.gocheckPrintfArg 関数と matchArgType 関数、および関連するテストファイルと既存のコードベースの修正に焦点を当てています。

src/cmd/vet/print.go の変更点

  1. checkPrintfArg 関数における f.pkg == nil のチェック追加:

    --- a/src/cmd/vet/print.go
    +++ b/src/cmd/vet/print.go
    @@ -276,6 +276,9 @@ func (f *File) checkPrintfArg(call *ast.CallExpr, verb rune, flags []byte, argNu
     					return
     				}
     			}
    +			if f.pkg == nil { // Nothing more to do.
    +				return
    +			}
     			// Verb is good. If nargs>1, we have something like %.*s and all but the final
     			// arg must be integer.
     			for i := 0; i < nargs-1; i++ {
    

    checkPrintfArg 関数は、printf フォーマット文字列の引数をチェックする役割を担っています。この変更は、f.pkg (現在のパッケージ情報) が nil の場合、それ以上チェックを行う必要がないことを明示し、早期リターンすることで、不必要な処理や潜在的なnilポインタ参照を防ぎます。これは、パッケージ情報が利用できない状況(例えば、単一のファイルがパッケージコンテキストなしで解析される場合など)での堅牢性を高めます。

  2. エラーメッセージにおける型情報の追加:

    --- a/src/cmd/vet/print.go
    +++ b/src/cmd/vet/print.go
    @@ -285,8 +288,13 @@ func (f *File) checkPrintfArg(call *ast.CallExpr, verb rune, flags []byte, argNu
     			for _, v := range printVerbs {\n \t\t\t\tif v.verb == verb {\n-\t\t\t\t\tif !f.matchArgType(v.typ, call.Args[argNum+nargs-1]) {\n-\t\t\t\t\t\tf.Badf(call.Pos(), \"arg for printf verb %%%c of wrong type\", verb)\n+\t\t\t\t\targ := call.Args[argNum+nargs-1]\n+\t\t\t\t\tif !f.matchArgType(v.typ, arg) {\n+\t\t\t\t\t\ttypeString := \"\"\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}\n \t\t\t\t\tbreak
    

    matchArgTypefalse を返した場合(つまり型ミスマッチがあった場合)、エラーメッセージに実際の引数の型情報を追加するように変更されました。f.pkg.types[arg] を使用して引数の型 (go/types.Type) を取得し、その String() メソッドを呼び出すことで、型名を文字列として取得しています。これにより、vet の出力がより詳細になり、デバッグが容易になります。

  3. matchArgType 関数における f.pkg == nil のチェック削除:

    --- a/src/cmd/vet/print.go
    +++ b/src/cmd/vet/print.go
    @@ -298,9 +306,6 @@ func (f *File) checkPrintfArg(call *ast.CallExpr, verb rune, flags []byte, argNu
     }\n \n func (f *File) matchArgType(t printfArgType, arg ast.Expr) bool {\n-\tif f.pkg == nil {\n-\t\treturn true // Don\'t know; assume OK.\n-\t}\n \t// TODO: for now, we can only test builtin types and untyped constants.\n \ttyp := f.pkg.types[arg]\n \tif typ == nil {\
    

    matchArgType 関数から、f.pkg == nil の場合に true を返す(チェックをスキップする)ロジックが削除されました。これは、前述の checkPrintfArg 関数で既に f.pkg == nil のチェックが行われているため、冗長なチェックを削除し、コードの重複を避けるための変更です。

  4. unsafe.Pointer の型チェックロジックの修正:

    --- a/src/cmd/vet/print.go
    +++ b/src/cmd/vet/print.go
    @@ -322,7 +327,7 @@ func (f *File) matchArgType(t printfArgType, arg ast.Expr) bool {\n \tcase types.String:\n \t\treturn t&argString != 0\n \tcase types.UnsafePointer:\n-\t\treturn t&argPointer != 0\n+\t\treturn t&(argPointer|argInt) != 0\n \tcase types.UntypedBool:\n \t\treturn t&argBool != 0\n \tcase types.UntypedComplex:\
    

    matchArgType 関数内で types.UnsafePointer を処理する部分が修正されました。以前は argPointer (ポインタ型) とのみマッチングしていましたが、この変更により argPointer または argInt (整数型) のいずれかともマッチするように拡張されました。これは、unsafe.Pointeruintptr に変換可能であり、%x%X のような整数フォーマット動詞でも適切に扱われるべきであるというGoのセマンティクスを反映しています。

src/cmd/vet/test_print.go の変更点

--- a/src/cmd/vet/test_print.go
+++ b/src/cmd/vet/test_print.go
@@ -14,8 +14,8 @@ import (
 )
 
 func UnsafePointerPrintfTest() {
-	var up *unsafe.Pointer
-	fmt.Printf("%p", up)
+	var up unsafe.Pointer
+	fmt.Printf("%p, %x %X", up, up, up)
 }
 
 // Error methods that do not satisfy the Error interface and should be checked.

UnsafePointerPrintfTest 関数が修正されました。

  • var up *unsafe.Pointer から var up unsafe.Pointer へ変更されました。これにより、unsafe.Pointer そのものの printf フォーマットの挙動がテストされるようになります。
  • fmt.Printf("%p", up) から fmt.Printf("%p, %x %X", up, up, up) へ変更されました。これは、unsafe.Pointer%p だけでなく、%x%X といった整数フォーマット動詞でも正しく扱われることを検証するためのテストケースの追加です。

src/pkg/sync/atomic/atomic_test.go の変更点

--- a/src/pkg/sync/atomic/atomic_test.go
+++ b/src/pkg/sync/atomic/atomic_test.go
@@ -1119,7 +1119,7 @@ func TestStoreLoadRelAcq32(t *testing.T) {
 					d1 := X.data1
 					d2 := X.data2
 					if d1 != i || d2 != float32(i) {
-						t.Fatalf("incorrect data: %d/%d (%d)", d1, d2, i)
+						t.Fatalf("incorrect data: %d/%g (%d)", d1, d2, i)
 					}
 				}
 			}
@@ -1167,7 +1167,7 @@ func TestStoreLoadRelAcq64(t *testing.T) {
 					d1 := X.data1
 					d2 := X.data2
 					if d1 != i || d2 != float64(i) {
-						t.Fatalf("incorrect data: %d/%d (%d)", d1, d2, i)
+						t.Fatalf("incorrect data: %d/%g (%d)", d1, d2, i)
 					}
 				}
 			}

TestStoreLoadRelAcq32TestStoreLoadRelAcq64 関数内の t.Fatalf のフォーマット文字列が修正されました。

  • d2float32 または float64 型であるにもかかわらず、以前は %d (整数) でフォーマットしようとしていました。
  • これを %g (汎用浮動小数点) に変更することで、浮動小数点数が正しく表示されるようになり、go vet の新しい printf チェッカーによって検出された問題を修正しています。

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

src/cmd/vet/print.go

// 変更1: checkPrintfArg 関数における f.pkg == nil のチェック追加
if f.pkg == nil { // Nothing more to do.
    return
}

// 変更2: エラーメッセージにおける型情報の追加
arg := call.Args[argNum+nargs-1]
if !f.matchArgType(v.typ, arg) {
    typeString := ""
    if typ := f.pkg.types[arg]; typ != nil {
        typeString = typ.String()
    }
    f.Badf(call.Pos(), "arg for printf verb %%%c of wrong type: %s", verb, typeString)
}

// 変更3: matchArgType 関数における f.pkg == nil のチェック削除
// 削除された行:
// if f.pkg == nil {
//     return true // Don't know; assume OK.
// }

// 変更4: unsafe.Pointer の型チェックロジックの修正
case types.UnsafePointer:
    return t&(argPointer|argInt) != 0 // 以前は t&argPointer != 0

src/cmd/vet/test_print.go

func UnsafePointerPrintfTest() {
    var up unsafe.Pointer // 以前は *unsafe.Pointer
    fmt.Printf("%p, %x %X", up, up, up) // 以前は "%p", up
}

src/pkg/sync/atomic/atomic_test.go

// TestStoreLoadRelAcq32 関数内
t.Fatalf("incorrect data: %d/%g (%d)", d1, d2, i) // 以前は %d/%d

// TestStoreLoadRelAcq64 関数内
t.Fatalf("incorrect data: %d/%g (%d)", d1, d2, i) // 以前は %d/%d

コアとなるコードの解説

src/cmd/vet/print.go

  • checkPrintfArgf.pkg == nil チェック: この追加は、go vet がコードを解析する際に、現在のパッケージ情報 (f.pkg) が利用できない状況(例えば、部分的なコードスニペットや、パッケージのコンテキストが完全にロードされていない場合)で、後続の型チェックロジックが安全に動作するようにするためのガード句です。これにより、nil ポインタ参照によるパニックを防ぎ、ツールの堅牢性を高めます。

  • エラーメッセージへの型情報追加: f.Badfgo vet がエラーを報告するための関数です。この変更により、printf のフォーマット動詞と引数の型が一致しない場合に、単に「wrong type」と表示するだけでなく、実際に渡された引数の型 (typeString) をエラーメッセージに含めるようになりました。f.pkg.types[arg] は、抽象構文木 (ast.Expr) からその式の型情報を取得します。go/types.Type インターフェースは String() メソッドを持つため、その型の人間が読める形式の文字列表現を取得できます。これにより、開発者はエラーの原因をより迅速に特定できます。

  • matchArgType からの f.pkg == nil チェック削除: これはリファクタリングの一環です。checkPrintfArg で既に f.pkg == nil のチェックが行われているため、matchArgType で同じチェックを繰り返す必要がなくなりました。これにより、コードの重複が解消され、保守性が向上します。

  • unsafe.Pointer の型チェックロジック修正: matchArgType 関数は、printf のフォーマット動詞が期待する型 (t) と、実際に渡された引数の型 (arg) が一致するかどうかを判断します。types.UnsafePointer のケースでは、以前はポインタ型 (argPointer) とのみマッチングしていました。しかし、unsafe.Pointeruintptr に変換可能であり、%x%X のような整数フォーマット動詞でも有効な引数と見なされるべきです。この変更は、unsafe.Pointer がポインタとしても整数としても扱えるというGoのセマンティクスを正確に反映させるために、argPointerargInt の両方とマッチするようにロジックを拡張しています。ビット演算子 | は論理ORを表し、targPointer または argInt のいずれかのフラグを含んでいれば true を返します。

src/cmd/vet/test_print.go

  • UnsafePointerPrintfTest の修正: このテストは、unsafe.Pointer 型が printf でどのように扱われるかを検証します。
    • var up *unsafe.Pointervar up unsafe.Pointer に変更したことで、テスト対象が unsafe.Pointer そのものになりました。*unsafe.Pointerunsafe.Pointer へのポインタであり、unsafe.Pointer 自体の挙動をテストするには不適切でした。
    • fmt.Printf("%p, %x %X", up, up, up) に変更したことで、unsafe.Pointer%p (ポインタ) だけでなく、%x および %X (16進数整数) としても正しくフォーマットされることを確認できるようになりました。これは、unsafe.Pointeruintptr に変換可能であるというGoの仕様に基づいています。

src/pkg/sync/atomic/atomic_test.go

  • t.Fatalf のフォーマット文字列修正: TestStoreLoadRelAcq32TestStoreLoadRelAcq64 は、sync/atomic パッケージの機能テストです。これらのテスト内で、float32float64 型の変数 d2 をデバッグ出力する際に、誤って %d (整数) フォーマット動詞が使用されていました。これは、浮動小数点数を整数として表示しようとするため、誤った出力や潜在的な実行時エラーを引き起こす可能性があります。新しい printf チェッカーがこの問題を検出したため、%g (汎用浮動小数点) に修正されました。これにより、浮動小数点数が正しく表示されるようになり、テストの出力が正確になります。

これらの変更は、go vet ツールの精度と有用性を高め、Go言語のコードベース全体の品質と信頼性を向上させるための重要なステップです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記「関連リンク」に記載の各パッケージドキュメント)
  • Go言語の go vet ツールに関する一般的な情報 (Goの公式ブログやドキュメント)
  • unsafe.Pointeruintptr の関係性に関するGo言語の仕様や解説記事
  • printf フォーマット動詞に関する一般的なプログラミング知識