メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#62595likely_accept

go/analysis/passes/printf: disallow %d formatting of pointers

新規提案

要約

AIによる要約であり、誤りを含む場合があります。

概要

fmt.Sprintf("%d", ptr) のようにポインタを %d 検証で数値として出力するコードは、多くの場合「デリファレンス忘れ」というバグである可能性が高いため、go vet の printf チェックでこれをエラーとして検出できるようにする提案です。

ステータス変更

(新規)likely_accept
2026年7月22日のプロポーザルレビューで @aclements により議論が行われ、翌23日に「likely accept」と判定されました。議論では、スライスや配列などフォーマット指定子が要素へ再帰的に適用される(broadcastされる)ケースにも本来は同様の問題があると指摘されましたが、現状のアナライザ実装はトップレベルの型しか見ておらず、そのようなケースは非常に稀であるとして、まずはポインタ単体に対する最も単純なチェックに限定して進めることになりました。

技術的背景

現状の問題点

Go の fmt パッケージは %d にポインタを渡すと、そのポインタが指す値ではなく、ポインタのアドレス値を10進数の数値として出力してしまいます。これは典型的には「ポインタを取り忘れた」「デリファレンスを忘れた」というバグに起因することがほとんどで、意図的にポインタのアドレスを10進表記したいケースはまず存在しません。

p := new(int)
*p = 42
fmt.Printf("%d\n", p) // 42 ではなく、アドレス値(例: 824634204264)が出力される

fmt パッケージ自体の挙動は後方互換性の観点から変更できませんが、go vet の printf チェックは fmt よりも意図的に厳格なルールを適用できるため、この提案では vet 側でエラーとして検出することが提案されています。

提案された解決策

go/analysis/passes/printf の解析ロジックを拡張し、%d(および同様の数値系検証)にポインタ型(あるいは chan 型)の値が渡された場合にエラーを報告するようにします。実装は既に CL 797320「go/analysis/passes/printf: reject %d of pointer or chan」として提出されています。なお、マップ・スライス・配列・構造体・配列へのポインタなど、要素に再帰的にフォーマットが適用される型については、今回のスコープには含めない方針となりました(アナライザがトップレベルの型のみを見る実装であるため)。

これによって何ができるようになるか

%d へのポインタの誤用を go vet(および go build 時の暗黙的な vet 実行)でコンパイル時・CI時に検出できるようになり、実行時までバグに気づかない、あるいはログ出力に無意味なアドレス値が混入するといった問題を未然に防げます。特に構造体のデバッグ出力やログ実装で %d%v/%p を取り違えるミスの早期発見に役立ちます。

コード例

// Before: ポインタを %d でフォーマットしてしまうバグ(vetでは検出されない)
type Counter struct{ n *int }
func (c Counter) String() string {
    return fmt.Sprintf("count=%d", c.n) // *int のデリファレンス忘れ、アドレス値が出力される
}
// After: go vet がエラーとして報告するため、修正が必要になる
func (c Counter) String() string {
    return fmt.Sprintf("count=%d", *c.n) // デリファレンスして正しい値を出力
}
// もしくは、本当にアドレス値の10進表記が必要な場合は明示的に変換する
func (c Counter) String() string {
    return fmt.Sprintf("addr=%d", uintptr(unsafe.Pointer(c.n)))
}

議論のハイライト

  • 提案者(Russ Coxとみられる書き方)は、%d でのポインタ整形はほぼ常にデリファレンス忘れによるバグであり、fmt 自体の挙動は変えられなくても vet はより厳格にできると主張しました。
  • 実装は既に CL 797320 として提出されており、%d に加えて chan 型のフォーマットも合わせて拒否する内容になっています。
  • @nightlyone から「マップやスライスも同様にインデックス忘れの可能性があるのでは」という指摘があり、これに対し @adonovan は、マップ・スライス・配列・構造体・配列へのポインタは %s%d を要素に再帰的に適用する仕様であり、この挙動自体は変更できないと回答しました。
  • @apparentlymart は「ポインタのスライス」に %d を使うケースも同様に問題になり得ると具体例を挙げましたが、稀なケースであるとして提案のスコープ外とすることが許容されました。
  • 最終的に @aclements は、スライス等への拡張は理論上望ましいものの、現在のアナライザ実装がトップレベル型のみを検査する構造であることを踏まえ、まずは「ポインタ単体への %d」という最も単純なケースに限定して進めるべきと結論づけました。

関連リンク