はじめまして、しぶちゃりです。
今回はGoの静的解析ツール nilassign
の紹介をさせていただきます。
nilに代入することで発生するpanic
Cをはじめとしたポインタの概念が存在する言語ではnullにデータを渡すことで言語ごとにエラーが発生します。これはデータを代入するアドレスが存在しない(null)にも関わらず利用するためです。
その例に漏れず、Goのnull型に相当するnilも、データを渡したり、参照することでエラーが発生します。以下がその例です。
package main
import (
"fmt"
)
func main() {
var i *int
*i = 1
fmt.Println(i)
}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x49777f]
goroutine 1 [running]:
main.main()
/tmp/sandbox000987391/prog.go:9 +0x1f
またポインタ型のフィールドを持つ構造体でもruntime panicが起きます。
package main
import (
"fmt"
)
func main() {
n := Node{}
n.Value = 1 // OK
n.ChildNode.Value = 1 // runtime panic
fmt.Println(n)
}
type Node struct {
Value int
ChildNode *Node
}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x497783]
goroutine 1 [running]:
main.main()
/tmp/sandbox811268421/prog.go:10 +0x23
このようにruntime panicでプログラムが異常終了するにも関わらず、代入自体は言語仕様的に代入可能性を満たすため、できてしまいます。
また、go vetコマンドを用いてもこのエラーは検出されないためgo buildコマンドでコンパイル自体は成功します。そのため、複雑なデータ構造の操作などが発生した際に思わぬバグを埋め込んでしまう可能性があります。
nilassignでnil参照を検出する
上記のようなエラーを事前に検出し防ぐために、nilassignというlinterを作成しました。
このlinterを用いることで、コンパイルよりも前にruntime panicを防ぐことが可能です。
以下がその例です。
package main
func main() {
{
var i *int
*i = 2 // ng
}
{
n := &Node{}
n.Val = 1 // ok
*n.Pval = 1 // NG
n.Node.Val = 1 // NG
n.Node.Node.Val = 1 // NG
n.ChildNode = &Node{Val: 1} // OK
n.PVal = &num // OK
}
}
type Node struct {
Val int
Pval *int
Node *Node
}
fish
go vet -vettool=(which nilassign) ./...
./main.go:6:3: this assignment occurs invalid memory address or nil pointer dereference
./main.go:13:3: this assignment occurs invalid memory address or nil pointer dereference
./main.go:14:3: this assignment occurs invalid memory address or nil pointer dereference
./main.go:15:3: this assignment occurs invalid memory address or nil pointer dereference
bash
$ go vet -vettool=`which nilassign` ./...
./main.go:6:3: this assignment occurs invalid memory address or nil pointer dereference
./main.go:13:3: this assignment occurs invalid memory address or nil pointer dereference
./main.go:14:3: this assignment occurs invalid memory address or nil pointer dereference
./main.go:15:3: this assignment occurs invalid memory address or nil pointer dereference
このツールはCIにも組み込むことが可能です。
CircleCI
- run:
name: Install nilassign
command: go get github.com/sivchari/nilassign
- run:
name: Run nilassign
command: go vet -vettool=`which nilassign` ./...
GitHub Actions
- name: Install nilassign
run: go get github.com/sivchari/nilassign
- name: Run nilassign
run: go vet -vettool=`which nilassign` ./...
golangci-lint経由でも利用可能
上記のlinterはgolangci-lintにも組み込まれているため、golangci-lint経由で利用することも可能です。
golangci-lintはよく利用しており、お世話になっているため、自分が作成したlinterがマージされたのはとても嬉しかったです。
まとめ
今回ご紹介したlinterはGitHubで公開しています。もしいいなと思ったらスターをいただけるとモチベーションにつながるのでとても嬉しいです。
最後までお読みいただきありがとうございました。