Goで静的解析ツールを作ってみた話
はじめまして、しぶちゃりです。
今回は以前から欲しいなと思っていたGo言語の静的解析ツールを作成したのでご紹介します。
gocapture
Go言語で開発をしている方なら馴染みのある問題の1つにスライスの容量(cap)問題があります。
この問題を説明する前にGo言語の配列とスライスの違いについて説明します。
配列は固定長、スライスは可変長
Go言語には配列とスライスという2種類の似たようなデータ構造があります。
細かな仕様についてはそれだけで記事になると思いますので今回は扱いません。
今回この2つのデータ構造を使う上で重要なことは、スライスが配列の参照型で長さと容量を持っているという点です。
スライスで発生する容量問題
スライスの特徴について前述しましたが、具体的にスライスを使う上でどのような問題が生じるかについて説明します。以下サンプルコードです。
package main
import "fmt"
func main() {
const MAX = 10000
var checkCap int
sampleSlice := make([]int, 0)
fmt.Println(len(sampleSlice), cap(sampleSlice))
for i := 0; i < MAX; i++ {
sampleSlice = append(sampleSlice, i)
if checkCap < cap(sampleSlice) {
fmt.Printf("%d slice: len %d -> cap %d \n", i, len(sampleSlice), cap(sampleSlice))
}
checkCap = cap(sampleSlice)
}
}
0 0
0 slice: len 1 -> cap 1
1 slice: len 2 -> cap 2
2 slice: len 3 -> cap 4
4 slice: len 5 -> cap 8
8 slice: len 9 -> cap 16
16 slice: len 17 -> cap 32
32 slice: len 33 -> cap 64
64 slice: len 65 -> cap 128
128 slice: len 129 -> cap 256
256 slice: len 257 -> cap 512
512 slice: len 513 -> cap 1024
1024 slice: len 1025 -> cap 1280
1280 slice: len 1281 -> cap 1696
1696 slice: len 1697 -> cap 2304
2304 slice: len 2305 -> cap 3072
3072 slice: len 3073 -> cap 4096
4096 slice: len 4097 -> cap 5120
5120 slice: len 5121 -> cap 7168
7168 slice: len 7169 -> cap 9216
9216 slice: len 9217 -> cap 12288
このようにGo言語では事前に定義していたスライスの容量(cap)を超えた場合に1024までは値を2倍で確保し直します。
その他の特徴やより詳細な説明はtentenさんが書いている以下の記事がおすすめです。
実装して理解するスライス #golang
適切な容量を確保していないと新たなメモリを都度確保することになり、非常に効率が悪いです。
そして、解決方法としてはmakeを利用し容量を確保するということになります。
ただし場合によってはcapを指定できない場合もあるからだと思いますが、Go言語の標準チェックツールであるgo vetでは容量を確保していない部分に関して指摘をすることはありません。
意図的に容量を確保していない場合はいいですが、ヒューマンエラーはつきものです。仮にこの知識を持っていないエンジニアが実装した場合思わぬことに繋がる場合もあります。
そこで今回作成した静的解析ツールがgocaptureという容量を確保していないスライスや、ここでは提示していませんが、長さを確保していないmap型をチェックしてくれる静的解析のツールです。
使用例
package main
func main() {
// sufficient capacity
c := make([]int, 0, 5)
print(c)
// zero capacity
a := make([]int, 0)
print(a)
}
fish
go vet -vettool=(which gocapture) ./...
bash
go vet -vettool=`which gocapture` ./...
output
./main.go:9:19: captured the not capacity
このようにgo vetと合わせて利用することで上記の容量の問題に気づくことが可能です。
現状念のためということもあり、意図的に容量を確保していない部分もチェックするようにしていますが、アップデートにより明示的にチェックから外す場所を指定できるようにしようとも考えています。
最後に
長くなりましたが、ここまで読んでいただきありがとうございました。
もし要望や、実装の改善などがあればぜひissueやPRよろしくお願い致します!
とても励みになります!
Discussion