http://makiuchi-d.github.io/2019/12/17/qiita-e9032a0c50b0f31e490a.ja.html

Goアドベントカレンダーその2の穴埋めです。

TL; DR

作りました: https://github.com/makiuchi-d/ptrtoerr

なぜ必要なのか

まずは次のコードを実行してみてください。

https://play.golang.org/p/j4ffNK4Xx84

package main

import "fmt"

type MyErr struct{}

func (*MyErr) Error() string {
	return "MyErr"
}

func F1() *MyErr {
	return nil
}

func F2() error {
	return F1()
}

func main() {
	err := F2()
	if err != nil {
		fmt.Println("Error!")
	}
}

F1()がnilを返しているのでF2()もnilを返すのですが、返ってきたerrはnilにならずに"Error!"が表示されます。 不思議ですね!

そうです。 これはGoに詳しいみなさんならよくご存知の、nilポインタを入れたinterfaceはnilではないというお話です。

Goでは、型変換は基本的に明示的にしなければならないのですが、interface型への変換だけは例外的に暗黙に行われます。 このコードでは、F1()の戻り値は*MyDrr型(ポインタ型)ですが、F2()ではerror型(interface)になっています。 つまり、F2()のreturn文で暗黙的な型変換が行われています。

Goのnilリテラルには、ポインタとしてのnilと、interfaceとしてのnilの2つの意味があります。 さらにinterfaceであるerror型はinterfaceとしてのnilと比較することでエラー判定をするため、 非エラーのつもりでポインタとしてのnilを入れてしまうとエラーとみなされてしまいます。

このようなミスは、上のexample.goでも示したとおりコンパイルも通りますし、見た目にもわかりにくいです。 そこで静的解析です。

nilポインタをerror型に入れている場所を探せばよいのですが、ポインタがnilかどうかは実行時でないとわからないため、 ポインタ型をerror型に入れている場所を探すことにしました。 また、error以外のinterface型へポインタを入れることは普通によくあることなので、error型限定です。

静的解析で検出する

作ったものはこちらです: https://github.com/makiuchi-d/ptrtoerr

まず手始めに、GoStaticAnalysis skeletonでコード生成しました。 これにより、最初からロジックに集中できてとても便利ですね。

検出すべきものを洗い出す

ポインタ型をerror型に入れている場所を検出したいのですが、このような型変換が起こるのは次のようなケースです。