goccy / go-json

Fast JSON encoder/decoder compatible with encoding/json for Go
MIT License
3.06k stars 149 forks source link

Panic when marshaling a map with custom marshaler #401

Open abemedia opened 2 years ago

abemedia commented 2 years ago

I just switched some working code from encoding/json to github.com/goccy/go-json and this caused a panic. Sometimes it panics with fatal error: invalid pointer found on stack and sometimes with runtime error: invalid memory address or nil pointer dereference.

The error occurs when using a map, where the values are a custom map type which implements the json.Marshaler interface.

Here's some code to reproduce the error. It works fine if you change github.com/goccy/go-json back to encoding/json:

package main

import (
    "log"

    "github.com/goccy/go-json"
)

type Set map[string]struct{}

func NewSet(items ...string) Set {
    s := make(Set, len(items))
    for _, v := range items {
        s[v] = struct{}{}
    }
    return s
}

func (s Set) MarshalJSON() ([]byte, error) {
    if s == nil {
        return []byte("null"), nil
    }

    if len(s) == 0 {
        return []byte("[]"), nil
    }

    size := 1
    for symbol := range s {
        size += len(symbol) + 3
    }

    b := make([]byte, 0, size)
    b = append(b, '[')
    for symbol := range s {
        b = append(b, '"')
        b = append(b, symbol...)
        b = append(b, '"', ',')
    }
    b[len(b)-1] = ']'

    return b, nil
}

func main() {
    m := map[string]Set{"foo": NewSet("foo", "bar")}
    b, err := json.Marshal(m)
    if err != nil {
        log.Fatal(err)
    }
    log.Println(string(b))
}
lovung commented 2 years ago

I got the same issue. But seems only happen after upgrading to go1.18.

inaneverb commented 2 years ago

I got the same issue. But seems only happen after upgrading to go1.18.

Nope. Can confirm I faced this issue on go1.17.

aryehlev commented 1 year ago

fixed here: https://github.com/goccy/go-json/pull/411

pcm720 commented 2 months ago

This bug can still be triggered with the following code:

package main

import (
    "fmt"

    "github.com/goccy/go-json"
)

type panicType map[uint64]struct{}

func (w panicType) MarshalJSON() ([]byte, error) {
    _ = w[1]
    return []byte{'1'}, nil
}

func main() {
    v := map[int]panicType{
        1: {
            15124: struct{}{},
        },
    }
    b, err := json.Marshal(v)
    fmt.Println(string(b), err)
}

Panic:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x1 pc=0x1049dda80]

goroutine 1 [running]:
main.panicType.MarshalJSON(0x104c33100?)
    test/main.go:12 +0x2c
github.com/goccy/go-json/internal/encoder.AppendMarshalJSON(0x140000a6680, 0x140000aa870, {0x140000e6000, 0x5, 0x400}, {0x104b45140?, 0x1?})
    go/pkg/mod/github.com/goccy/go-json@v0.10.3/internal/encoder/encoder.go:430 +0x1fc
github.com/goccy/go-json/internal/encoder/vm.appendMarshalJSON(0x1?, 0x140000e6000?, {0x140000e6000?, 0x0?, 0x140000e4008?}, {0x104b45140?, 0x1?})
    go/pkg/mod/github.com/goccy/go-json@v0.10.3/internal/encoder/vm/util.go:152 +0x28
github.com/goccy/go-json/internal/encoder/vm.Run(0x140000a6680, {0x140000e6000?, 0x0?, 0x400?}, 0x140000de070?)
    go/pkg/mod/github.com/goccy/go-json@v0.10.3/internal/encoder/vm/vm.go:271 +0x205c
github.com/goccy/go-json.encodeRunCode(0x140000a6680?, {0x140000e6000?, 0x104c5fcc8?, 0x14000098da0?}, 0x140000e8000?)
    go/pkg/mod/github.com/goccy/go-json@v0.10.3/encode.go:310 +0x64
github.com/goccy/go-json.encode(0x140000a6680, {0x104b41d60, 0x140000a0120})
    go/pkg/mod/github.com/goccy/go-json@v0.10.3/encode.go:235 +0x204
github.com/goccy/go-json.marshal({0x104b41d60, 0x140000a0120}, {0x0, 0x0, 0x14000098ed8?})
    go/pkg/mod/github.com/goccy/go-json@v0.10.3/encode.go:150 +0xbc
github.com/goccy/go-json.MarshalWithOption(...)
    go/pkg/mod/github.com/goccy/go-json@v0.10.3/json.go:185
github.com/goccy/go-json.Marshal({0x104b41d60?, 0x140000a0120?})
    go/pkg/mod/github.com/goccy/go-json@v0.10.3/json.go:170 +0x30
main.main()
    test/main.go:22 +0x94
exit status 2

encoding/json handles this code without any issues.


Looks like it's caused by https://github.com/goccy/go-json/blob/5e2ae3f23c1db71990844a230a4d11559efe128e/internal/encoder/vm/vm.go#L268-L270

ptrToPtr(p) loses the pointer and makes p equal to 0x1 instead of the actual pointer.
Just commenting out line 269 fixes the issue. Obviously not the right way to do it though :)