LoginSignup
2
3

More than 5 years have passed since last update.

Go の JSON のパースと nil の扱い segmentation violation 対策

Posted at

Go の本当に簡単なプログラムの実行でズブズブにハマった。何にハマったのかと言うとやりたいことは、単純に、JSON のパースをしたいだけなのに、簡単に panic が起こって、問題が特定できないことだ。他の人がこの問題をどうやって解決しているのかはぜひ知りたいところ。

$ go run main.go 
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10b7834]

goroutine 1 [running]:
main.main()
    /Users/ushio/Codes/terraform/src/github.com/TsuyoshiUshio/SpikeAutorest/cmd/spike2/main.go:51 +0x320

こんなエラーが簡単に起こってしまう。普段 C# とかで甘やかされていると、びっくりしてしまうだろう。出ている場所はわかるのだが、それが何を表しているのか一瞬わからない。C# とか、Java とかに慣れてると、それらの言語では起こらない箇所で簡単に、このエラーがでる。

例えば実用的だけど、簡単なパースのプログラムの例。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
)

type DataFactoryCreateOrUpdateParameter struct {
    ID                                   *string             `json:"id,omitempty"`
    Name                                 *string             `json:"name,omitempty"`
    Type                                 *string             `json:"type,omitempty"`
    Location                             *string             `json:"location,omitempty"`
    Tags                                 *map[string]*string `json:"tags,omitempty"`
    *DataFactoryCreateOrUpdateProperties `json:"properties,omitempty"`
}

type DataFactoryCreateOrUpdateProperties struct {
    DataFactoryID     *string `json:"datafactoryId,omitempty"`
    ProvisioningState *string `json:"provisioningState,omitempty"`
    Error             *string `json:"error,omitempty"`
    ErrorMessage      *string `json:"errorMessage,omitempty"`
}

func main() {
    logger := log.New(os.Stdout, "JSON PARSE", log.Lshortfile)

    jsonFile := `
    {
        "name": "gosdktestadfname01",
        "id": "/subscriptions/somesubscription/resourcegroups/spikeadf01/providers/Microsoft.DataFactory/datafactories/gosdktestadfname01",
        "type": "Microsoft.DataFactory/datafactories",
        "location": "West US",
        "tags": {},
        "properties": {
          "dataFactoryId": "1d55e29b-14e8-4e47-a4b6-a61c77c2854a",
          "provisioningState": "PendingCreation",
          "error": null,
          "errorMessage": null
        }
      }
    `

    byteString := []byte(jsonFile)
    v := &DataFactoryCreateOrUpdateParameter{}
    json.Unmarshal(byteString, v)

    logger.Printf("Id: %s Name: %s ProvisioningState: %s", *v.ID, *v.Name, *v.DataFactoryCreateOrUpdateProperties.ProvisioningState)
    fmt.Printf("Id: %s Name: %s ProvisioningState: %s Error: %s", *v.ID, *v.Name, *v.ProvisioningState, ToString(v.DataFactoryCreateOrUpdateProperties.Error))

}

これのコードは、先ほどのエラーが出る。どこかと言うと、

fmt.Printf("Id: %s Name: %s ProvisioningState: %s Error: %s", *v.ID, *v.Name, *v.ProvisioningState, ToString(v.DataFactoryCreateOrUpdateProperties.Error))

ここの箇所。パースすると、v.DataFactoryCreateOrUpdateProperties.Error は、nil になる。C# とかの感覚だと、string の変数が、null になるのは不思議な感覚じゃ無いと思うけど、ここでのポイントは、stringnil などと言うものは存在しない。しかし、パース元のJSONでは、null になっている。そう言う場合どうなるかと言うと、ポインタが nil になるっぽい。
 だから、このケースでは、v.DataFactoryCreateOrUpdateProperties.Error へのポインタ自体がnil なので、参照した瞬間に先のエラーになる。

他にこのエラーを起こそうと思ったら簡単。

v := &DataFactoryCreateOrUpdateParameter{}

v := DataFactoryCreateOrUpdateParameter{}

してあげても簡単に起きてしまう。

対策

じゃあどうしたらいいだろう。上記のようなケースは特殊なケースではなく、十分起こりうるユースケースだこれがベストプラクティスかは知らないけど、例えばこんなメソッドを書くのはどうだろう。

func ToString(stringPointer *string) string {
    if stringPointer == nil {
        return ""
    }
    return *stringPointer
}
    fmt.Printf("Id: %s Name: %s ProvisioningState: %s Error: %s", *v.ID, *v.Name, *v.ProvisioningState, ToString(v.DataFactoryCreateOrUpdateProperties.Error))

こんな感じでラップしてしまえば、ありうるケースに置いて、二度とこのエラーは起きない。

エラーの発見

じゃあ、どうやって、このエラーを発見するか?このポインタとnil の関係がわかってしまえばわかりやすい。行番号を見つけたら、その直前で、デバッガに頼ってブレークポイントを設定して nilを見つけるのが簡単そうだ。

もし、もっといいコードやデバッグ方法があればぜひお教えください!

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3