Slide 1

Slide 1 text

The Go gopher was designed by Renée French. The gopher stickers was made by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license. go vetのprintf.Analyzer の挙動を追え! 2022/01/15(土) tenntenn Conference 2022 資料:https://tenn.in/printf 動画:https://tenn.in/printf-video

Slide 2

Slide 2 text

上田拓也 Go ビギナーズ
 Go Conference
 @tenntenn tenntenn.dev Google Developer Expert (Go) 一般社団法人 Gophers Japan 代表理事 Experts Team

Slide 3

Slide 3 text

この活動を支える企業 ■ シェアフル株式会社 ● https://sharefull.com/ ● 採用情報:https://www.wantedly.com/projects/787895 ● 作成した静的解析ツール ○ https://github.com/sharefull/mockfunc ○ https://github.com/sharefull/refactortools

Slide 4

Slide 4 text

go vetとは? ■ 標準の静的解析ツール ● コンパイラでは発見できないバグを見つける ● go testを走らせれば自動で実行される(Go1.10から) ● The Go Playgroundでも実行される ○ go.dev/playでは実行されなくなった! 参考:14. 静的解析とコード生成 - プログラミング言語Go完全入門

Slide 5

Slide 5 text

printfパッケージ ■ printf系の関数の使い方をチェックする ● printf.Analyzerを提供 ● ラッパーでも指摘する! package main import "fmt" func main() { fmt.Printf("%s\n", 100) } Playgroundで動かす %sなのに数値が渡されている

Slide 6

Slide 6 text

コードリーディングタイム! https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/printf/

Slide 7

Slide 7 text

読むポイント ■ printfっぽい関数をどう見分けているのか? ● シグニチャ? ● ラッパーを更にラップしてた場合はどうする? ■ 依存しているパッケージで定義された関数は? ● どうやって依存関係を辿る? ● 依存してるパッケージが依存するパッケージは?

Slide 8

Slide 8 text

まとめ ■ printf.Analyzer恐るべし ● printf系関数を地の果てまで追う ○ 引数への代入文を挟むと逃げ切れる ● ラッパーでも容赦なし! ● 依存しているパッケージも調べる ● Factって便利だね

Slide 9

Slide 9 text

補足資料

Slide 10

Slide 10 text

analysis.Factインタフェース 10 ■ 述語を表現した型 ● types.Objectやtypes.Packageが対象 ● 「絶対returnしない」のような述語を表す ● 「関数F」や「パッケージP」などの主語は表さない type Fact interface { AFact() // 明示的に実装させるためのメソッド(空でよい) }

Slide 11

Slide 11 text

Factの設定 ■ analysis.Analyzer型のFactTypesフィールドに設定 ● 型が分かれば十分 ● FactはGobでシリアライズされる 11 type Analyzer struct { /* 略 */ // インポートまたはエクスポートするFact(型情報が必要) // 要素はポインタである必要がある FactTypes []Fact }

Slide 12

Slide 12 text

Factのエクスポート ■ analysis.Pass型のフィールドからエクスポート 12 type Pass struct { /* 略 */ // オブジェクトに関連付けられたFactをエクスポートする // 第2引数に渡す具象型の値はポインタである必要がある // スレッドセーフではない ExportObjectFact func(obj types.Object, fact Fact) // パッケージに関連付けられたFactをエクスポートする // 他の挙動はExportObjectFactと同じ ExportPackageFact func(fact Fact) }

Slide 13

Slide 13 text

Factのインポート ■ analysis.Pass型のフィールドからインポート 13 type Pass struct { /* 略 */ // オブジェクトに関連付けられたFactをインポートする // 第2引数に渡す具象型の値はポインタである必要がある // Factを満たす場合はインポートした値を // 第2引数で渡したポインタが指す先に代入 // スレッドセーフではない ImportObjectFact func(obj types.Object, fact Fact) bool // パッケージに関連付けられたFactをインポートする // 他の挙動はImportObjectFactと同じ ImportPackageFact func(pkg *types.Package, fact Fact) bool }

Slide 14

Slide 14 text

Factを使った解析 ■ 依存するパッケージもすべて解析対象になる ● analysis.Analyzer型のFactTypesフィールドがnilじゃない場合 ● importしているパッケージにもRunフィールドの関数を適用 ○ Factを使ってない場合はimportしているパッケージは対象外 ● FactはGobでシリアライズされる 14 ドライバー go vet, goplz 静的解析ツール unitchecker.Main パッケージ1 実行 解析 静的解析ツール unitchecker.Main 静的解析ツール unitchecker.Main パッケージ2 解析 パッケージ3 解析 Fact Fact Gob Export Export Import import "pkg1" import "pkg2"

Slide 15

Slide 15 text

Factの利用例 15 ■ x/tools/go/analysis/passes/printfパッケージ ● go vetの中で使われているAnalyzerを定義している ● printf系の関数をラップしているかどうかを表すFactを定義している ● importしているパッケージも対象として解析ができる type isWrapper struct{ Kind Kind } func (f *isWrapper) AFact() {} func (f *isWrapper) String() string { switch f.Kind { case KindPrintf: return "printfWrapper" case KindPrint: return "printWrapper" case KindErrorf: return "errorfWrapper" default: return "unknownWrapper" } }