http://makiuchi-d.github.io/2019/12/17/qiita-e9032a0c50b0f31e490a.ja.html
Goアドベントカレンダーその2の穴埋めです。
作りました: 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型に入れている場所を検出したいのですが、このような型変換が起こるのは次のようなケースです。