結論
scopelintを廃止しました。
代わりにポリシーの違う2つのlintに分けました。
今後は
- とにかくループ変数に対するすべてのポインター参照を抹殺したいなら looppointer
- 多少見つけそこなっても良いからfalse-positiveはクソだ、と思うなら exportloopref
をご利用ください。
背景
scopelintについて
golangのループ変数の使い方をチェックするlinter作ってみた という記事にも書きましたが、
Goのループ変数にまつわる厄介なバグを検出する scopelint というlinterを作ってメンテしてきました。
が、どうしてもfalse-positivesがうまいこと取り除けず、検出ポリシーを定めて何らかを犠牲にする必要が生じました。
ループ変数にまつわる厄介なバグについて
こちらとても厄介で、(scopelintの記事にも書きましたが)、Let's Encryptが証明書発行周りでこのバグを踏んで、
世の中の証明書が大量に無効化され、新しく作り直す手間にかられるという騒動になったりしています。
golangでforループを回すと、中でうっかりループ変数に対するポインタを取ることで、スコープ外で参照したときに意図しない結果を得たりすることになります。
package main
func main() {
var intSlice []*int
println("loop expecting 10, 11, 12, 13")
for _, p := range []int{10, 11, 12, 13} {
intSlice = append(intSlice, &p) // want "taking a pointer for the loop variable p"
}
println(`slice expecting "10, 11, 12, 13" but "13, 13, 13, 13"`)
for _, p := range intSlice {
printp(p)
}
}
func printp(p *int) {
println(*p)
}
意外と気づきにくいバグの元になってプログラマは容易に死にます。
linterの作り方について
scopelintはかれこれ3年前に作ったlinterです。
当時はgoのlinter界隈はみんなで頑張ってASTを解析し、それぞれ自分なりの方法でdiagnosticsをレポートしていました。
scopelintも同様です。
結果メンテナンスコストがとにかく高く、腰の重い作業になっていました。
ですが、最近は golang.org/x/tools/go/analysis や golang.org/x/tools/go/ast/inspector が用意されています。
基本的なAST解析は彼らに任せることができるようになってきました。
また、linterの基本構造が統一されることで、linterづくりはとても平易になっています。
go vet
に同梱されたlinterを眺めて見るだけでも、自分で作りたいlinterのヒントをえることが可能です。
今回はlooppointer, exportlooprefともにこれらを参照して作りましたが、とにかく簡単にできるようになっています。
gophersはぜひ、自作のlinterづくりに勤しんでみてはいかがでしょう。