[インデックス 15448] ファイルの概要
このコミットは、Go言語の静的解析ツールである cmd/vet の printf フォーマット文字列チェッカーの改善と、関連するテストファイルの修正、および 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 vet の printf フォーマット文字列チェッカーの精度向上と、より堅牢なコードベースの維持という目的があります。
-
unsafe.Pointerのprintfフォーマットチェックの不備: 以前のgo vetのprintfチェッカーは、unsafe.Pointer型に対するフォーマット動詞のチェックが不十分でした。特に、%p(ポインタのアドレス表示) 以外のフォーマット動詞がunsafe.Pointerに適用された場合に、誤検知または見逃しが発生する可能性がありました。このコミットでは、unsafe.Pointerが%pだけでなく、整数型 (%x,%Xなど) としても扱えるように、チェックロジックを修正しています。これは、unsafe.Pointerがメモリ上のアドレスを表現する際に、ポインタとしてだけでなく、整数値としても解釈されることがあるためです。 -
テストの不正確さ:
src/cmd/vet/test_print.goにあった*unsafe.Pointer(つまりunsafe.Pointerへのポインタ) を%pでフォーマットするテストは、unsafe.Pointer自体のテストとしては適切ではありませんでした。unsafe.Pointerはそれ自体がポインタ型であり、そのポインタへのポインタをテストしても、unsafe.Pointerの本質的なprintfフォーマットの挙動を検証することにはなりません。このコミットでは、テスト対象をunsafe.Pointerそのものに変更し、より実用的なテストケースを追加しています。 -
エラーメッセージの改善:
printfフォーマットの型ミスマッチが発生した場合、以前は単に「wrong type」と表示されるだけでした。このコミットでは、go/types.TypeのString()メソッドを利用して、期待される型ではなく、実際に渡された引数の型情報をエラーメッセージに含めるように改善されています。これにより、開発者はどの型が問題を引き起こしているのかをより具体的に把握できるようになり、デバッグが容易になります。 -
新たな
printfチェッカーによる発見: 新しいprintfチェッカーがより多くのファイルで実行可能になった結果、src/pkg/sync/atomic/atomic_test.go内に存在するいくつかの不正確なフォーマット動詞が発見されました。具体的には、float32やfloat64の値を%d(整数) でフォーマットしようとしていた箇所が%g(汎用浮動小数点) に修正されています。これは、go vetの改善が実際のコードベースの品質向上に貢献した例と言えます。
これらの変更は、Go言語のツールチェーンの品質と信頼性を向上させ、開発者がより安全で正確なコードを書くのを支援することを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とツールに関する知識が必要です。
-
go vet:go vetは、Go言語のソースコードを静的に解析し、潜在的なバグや疑わしい構成を報告するツールです。コンパイルは通るが、実行時に問題を引き起こす可能性のあるコードパターン(例:printfフォーマット文字列と引数の不一致、到達不能なコード、ロックの誤用など)を検出します。このコミットでは、go vetのprintfチェッカーの機能が改善されています。 -
printfフォーマット動詞: Go言語のfmtパッケージ(fmt.Printf,fmt.Sprintfなど)は、C言語のprintf関数と同様に、フォーマット文字列と引数を使って出力を整形します。フォーマット文字列には、引数の型や表示形式を指定するための「フォーマット動詞」が含まれます。%p: ポインタのアドレスを16進数で表示します。%d: 整数を10進数で表示します。%x,%X: 整数を16進数で表示します(小文字/大文字)。%g: 浮動小数点数を、精度に応じて%eまたは%fの形式で表示します。
-
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) とも互換性があるべきという観点から修正が行われています。
- 任意の型のポインタから
-
go/typesパッケージ:go/typesパッケージは、Goプログラムの型情報を表現するためのAPIを提供します。コンパイラや静的解析ツールがGoの型システムを理解し、操作するために使用されます。go/types.Type: Goの型を表すインターフェースです。Stringerインターフェース:String() stringメソッドを持つインターフェースです。このインターフェースを実装する型は、fmtパッケージのPrint系関数で文字列として出力される際に、String()メソッドの戻り値が使用されます。go/types.TypeはStringerを実装しており、その型の文字列表現を返すことができます。
技術的詳細
このコミットは、主に src/cmd/vet/print.go の checkPrintfArg 関数と matchArgType 関数、および関連するテストファイルと既存のコードベースの修正に焦点を当てています。
src/cmd/vet/print.go の変更点
-
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ポインタ参照を防ぎます。これは、パッケージ情報が利用できない状況(例えば、単一のファイルがパッケージコンテキストなしで解析される場合など)での堅牢性を高めます。 -
エラーメッセージにおける型情報の追加:
--- 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\tbreakmatchArgTypeがfalseを返した場合(つまり型ミスマッチがあった場合)、エラーメッセージに実際の引数の型情報を追加するように変更されました。f.pkg.types[arg]を使用して引数の型 (go/types.Type) を取得し、そのString()メソッドを呼び出すことで、型名を文字列として取得しています。これにより、vetの出力がより詳細になり、デバッグが容易になります。 -
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のチェックが行われているため、冗長なチェックを削除し、コードの重複を避けるための変更です。 -
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.Pointerがuintptrに変換可能であり、%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)
}
}
}
TestStoreLoadRelAcq32 と TestStoreLoadRelAcq64 関数内の t.Fatalf のフォーマット文字列が修正されました。
d2がfloat32または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
-
checkPrintfArgのf.pkg == nilチェック: この追加は、go vetがコードを解析する際に、現在のパッケージ情報 (f.pkg) が利用できない状況(例えば、部分的なコードスニペットや、パッケージのコンテキストが完全にロードされていない場合)で、後続の型チェックロジックが安全に動作するようにするためのガード句です。これにより、nilポインタ参照によるパニックを防ぎ、ツールの堅牢性を高めます。 -
エラーメッセージへの型情報追加:
f.Badfはgo 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.Pointerはuintptrに変換可能であり、%xや%Xのような整数フォーマット動詞でも有効な引数と見なされるべきです。この変更は、unsafe.Pointerがポインタとしても整数としても扱えるというGoのセマンティクスを正確に反映させるために、argPointerとargIntの両方とマッチするようにロジックを拡張しています。ビット演算子|は論理ORを表し、tがargPointerまたはargIntのいずれかのフラグを含んでいればtrueを返します。
src/cmd/vet/test_print.go
UnsafePointerPrintfTestの修正: このテストは、unsafe.Pointer型がprintfでどのように扱われるかを検証します。var up *unsafe.Pointerをvar up unsafe.Pointerに変更したことで、テスト対象がunsafe.Pointerそのものになりました。*unsafe.Pointerはunsafe.Pointerへのポインタであり、unsafe.Pointer自体の挙動をテストするには不適切でした。fmt.Printf("%p, %x %X", up, up, up)に変更したことで、unsafe.Pointerが%p(ポインタ) だけでなく、%xおよび%X(16進数整数) としても正しくフォーマットされることを確認できるようになりました。これは、unsafe.Pointerがuintptrに変換可能であるというGoの仕様に基づいています。
src/pkg/sync/atomic/atomic_test.go
t.Fatalfのフォーマット文字列修正:TestStoreLoadRelAcq32とTestStoreLoadRelAcq64は、sync/atomicパッケージの機能テストです。これらのテスト内で、float32やfloat64型の変数d2をデバッグ出力する際に、誤って%d(整数) フォーマット動詞が使用されていました。これは、浮動小数点数を整数として表示しようとするため、誤った出力や潜在的な実行時エラーを引き起こす可能性があります。新しいprintfチェッカーがこの問題を検出したため、%g(汎用浮動小数点) に修正されました。これにより、浮動小数点数が正しく表示されるようになり、テストの出力が正確になります。
これらの変更は、go vet ツールの精度と有用性を高め、Go言語のコードベース全体の品質と信頼性を向上させるための重要なステップです。
関連リンク
- Go Change-Id:
https://golang.org/cl/7385051 - Go言語の
unsafeパッケージに関する公式ドキュメント: https://pkg.go.dev/unsafe - Go言語の
fmtパッケージに関する公式ドキュメント: https://pkg.go.dev/fmt - Go言語の
go/typesパッケージに関する公式ドキュメント: https://pkg.go.dev/go/types
参考にした情報源リンク
- Go言語の公式ドキュメント (上記「関連リンク」に記載の各パッケージドキュメント)
- Go言語の
go vetツールに関する一般的な情報 (Goの公式ブログやドキュメント) unsafe.Pointerとuintptrの関係性に関するGo言語の仕様や解説記事printfフォーマット動詞に関する一般的なプログラミング知識