[インデックス 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\tbreak
matchArgType
が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
フォーマット動詞に関する一般的なプログラミング知識