https://zenn.dev/tenntenn/articles/658e16ae6966b3
2022年のセキュリティ・キャンプ全国大会に講師として参加しました。その中で、Build Constraintを用いたソースコードを解析する際に注意することを解説しました。本記事ではその際に解説した点について記述します。
セキュリティ・キャンプで用いた資料はこちらから閲覧できます。
Build Constraintとは、いわゆるビルドタグと呼ばれる機能で環境(OSやアーキテクチャ、Goのバージョンなど)によってコンパイルするファイルを書き分ける機能です。
次のように、//go:build
コメントディレクティブをつけることで特定の条件を満たす場合にコンパイルするように書けます。この場合はGoのバージョンが1.19以上の場合にのみコンパイルされるようにしています。
//go:build go1.19
package main
func init() {
panic("Go1.19 🎉")
}
Go1.18以下で動かすとパニックは起きませんが、Go1.19以上で動かすとパニックが発生します。Go Playgroundであれば複数のバージョンで実行できるので、試しに動かしてみると良いでしょう。なお、Go Playgroundは現在のバージョン、前のバージョン、開発バージョンで動かせます。記事執筆時点ではGo1.19が最新版なので、Go1.18で動かすと良いでしょう。Go1.19よりバージョンが上がった場合には、go1.19を最新版に読み替えてください。
ファイル名でもOSやアーキテクチャを指定することで出し分けることができます。prog__windows_amd64.go
と名前をつけると、GOOS
がwindows
でGOARCH
がamd64
の場合にだけ、コンパイル対象になります。
go vet
やgo/analysisを使った静的解析ツールは、動作する環境のGOOS
やGOARCH
、そしてGoのバージョンによって解析対象のコードが変わります。つまり、その環境のビルド対象になるようなコードが静的解析の解析対象にもなるということです。
たとえば、次のようなgopher
という名前の静的解析ツールがあった場合を考えます。識別子を見つけると、gopher!!
というメッセージを該当のソースコードの位置に対して出力します。
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.Ident)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.Ident:
if n.Name == "gopher" {
pass.Reportf(n.Pos(), "gopher!!")
}
}
})
return nil, nil
}
これを次の2つのファイルを持つexample
パッケージに対して実行します。
//go:build go1.19
package example
func gopher() {} // want "gopher!!"
次のように、この静的解析ツールをGo1.19のコンパイラでビルドし、上記のexample
パッケージを対象に実行してみます。ここでgo1.19
コマンドはGo1.19のgo
コマンドとしています[1]。go1.19 vet
コマンドを使ってビルドした静的解析ツールを実行するとa.go
の該当箇所でgopher!!
というメッセージが表示されています。
$ go1.19 build ./cmd/mylinter
$ cd testdata/src/example
$ go1.19 vet -vettool=`pwd`/../../../mylinter example
# a
./a.go:5:6: gopher!!
一方、Go1.18でビルドして実行してみます。この場合はa.go
が解析対象から外れるため、メッセージは表示されません。