wI2L / jettison

Highly configurable, fast JSON encoder for Go
https://pkg.go.dev/github.com/wI2L/jettison
MIT License
174 stars 13 forks source link

Panic when marshalling a struct with a map and a custom MarshalJSON() #12

Open ziemekobel-ef opened 1 year ago

ziemekobel-ef commented 1 year ago

Marshalling panics when a struct containing a map field and a custom MarshalJSON() is provided. Tested on Apple M1 Pro, running macOS 14.0.

The below test code:

type Fields struct {
    AdditionalProperties map[string]string `json:"-"`
}

func (a Fields) MarshalJSON() ([]byte, error) {
    var err error
    object := make(map[string]json.RawMessage)

    for fieldName, field := range a.AdditionalProperties {
        object[fieldName], err = json.Marshal(field)
        if err != nil {
            return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
        }
    }
    return json.Marshal(object)
}

func TestJettison(t *testing.T) {
    v := Fields{
        AdditionalProperties: map[string]string{
            "foo": "bar",
        },
    }
    _, err := jettison.Marshal(v)
    if err != nil {
        t.Fatal(err)
    }
}

results in:

unexpected fault address 0x6bff37c970
fatal error: fault
[signal SIGBUS: bus error code=0x1 addr=0x6bff37c970 pc=0x100fa32d8]

goroutine 6 [running]:
runtime.throw({0x101105eaf?, 0x100fa07b4?})
    /usr/local/go/src/runtime/panic.go:1077 +0x40 fp=0x1400005d910 sp=0x1400005d8e0 pc=0x100fcabd0
runtime.sigpanic()
    /usr/local/go/src/runtime/signal_unix.go:858 +0x178 fp=0x1400005d970 sp=0x1400005d910 pc=0x100fe26c8
runtime.evacuated(...)
    /usr/local/go/src/runtime/map.go:205
runtime.mapiternext(0x1400005dab8)
    /usr/local/go/src/runtime/map.go:897 +0xe8 fp=0x1400005d9f0 sp=0x1400005d980 pc=0x100fa32d8
runtime.mapiterinit(0xb?, 0x0?, 0x10117aa40?)
    /usr/local/go/src/runtime/map.go:864 +0x2a0 fp=0x1400005da20 sp=0x1400005d9f0 pc=0x100fa31b0
ef-studio/catalyst/tests/lib.Fields.MarshalJSON({0x1400005db40?})
    /Users/ziemekobel/ws/ef-studio/backend/go/catalyst/tests/lib/jettison_test.go:21 +0x58 fp=0x1400005db20 sp=0x1400005da20 pc=0x101105078
github.com/wI2L/jettison.encodeJSONMarshaler({0x10118e100?, 0x1400005dd98}, {0x1400016a000, 0x0, 0x1000}, {{0x1011b98c0, 0x10131db00}, {0x10110eada, 0x23}, 0x5, ...}, ...)
    /Users/ziemekobel/go/pkg/mod/github.com/w!i2!l/jettison@v0.7.4/encode.go:692 +0x7c fp=0x1400005dbc0 sp=0x1400005db20 pc=0x1010fb9ec
github.com/wI2L/jettison.encodeMarshaler(0x1400005dd98, {0x1400016a000, 0x0, 0x1000}, {{0x1011b98c0, 0x10131db00}, {0x10110eada, 0x23}, 0x5, 0x0, ...}, ...)
    /Users/ziemekobel/go/pkg/mod/github.com/w!i2!l/jettison@v0.7.4/encode.go:668 +0x2b4 fp=0x1400005dc80 sp=0x1400005dbc0 pc=0x1010fb524
github.com/wI2L/jettison.newMarshalerTypeInstr.newJSONMarshalerInstr.func5(0x1400000e1f8?, {0x1400016a000?, 0x1400005dd58?, 0x1010f7134?}, {{0x1011b98c0, 0x10131db00}, {0x10110eada, 0x23}, 0x5, 0x0, ...})
    /Users/ziemekobel/go/pkg/mod/github.com/w!i2!l/jettison@v0.7.4/instruction.go:241 +0x68 fp=0x1400005dd10 sp=0x1400005dc80 pc=0x1010fdec8
github.com/wI2L/jettison.cachedInstr.wrapInlineInstr.func1(0x14000108ea0, {0x1400016a003, 0x0, 0x1000}, {{0x1011b98c0, 0x10131db00}, {0x10110eada, 0x23}, 0x5, 0x0, ...})
    /Users/ziemekobel/go/pkg/mod/github.com/w!i2!l/jettison@v0.7.4/instruction.go:406 +0x94 fp=0x1400005dd90 sp=0x1400005dd10 pc=0x1010fd094
github.com/wI2L/jettison.marshalJSON({0x10118e100, 0x14000108ea0?}, {{0x1011b98c0, 0x10131db00}, {0x10110eada, 0x23}, 0x5, 0x0, 0x0, 0x0})
    /Users/ziemekobel/go/pkg/mod/github.com/w!i2!l/jettison@v0.7.4/json.go:167 +0xe0 fp=0x1400005de70 sp=0x1400005dd90 pc=0x1011007b0
github.com/wI2L/jettison.Marshal({0x10118e100?, 0x14000108ea0?})
    /Users/ziemekobel/go/pkg/mod/github.com/w!i2!l/jettison@v0.7.4/json.go:115 +0x84 fp=0x1400005df10 sp=0x1400005de70 pc=0x101100674
ef-studio/catalyst/tests/lib.TestJettison(0x140001524e0)
    /Users/ziemekobel/ws/ef-studio/backend/go/catalyst/tests/lib/jettison_test.go:36 +0x7c fp=0x1400005df60 sp=0x1400005df10 pc=0x10110525c
wI2L commented 1 year ago

It's probably related to the map hiter header copied from the runtime, used to reduce allocations for map ranges. I'll have a look.

Can you tell me which version of Go you are using please, and share the unit test you seems to be using ?

ziemekobel-ef commented 1 year ago

@wI2L It's 1.21

And here's the entire test code:

package lib_test

import (
    "encoding/json"
    "fmt"
    "testing"

    "github.com/wI2L/jettison"
)

type Fields struct {
    AdditionalProperties map[string]string `json:"-"`
}

func (a Fields) MarshalJSON() ([]byte, error) {
    var err error
    object := make(map[string]json.RawMessage)

    for fieldName, field := range a.AdditionalProperties {
        object[fieldName], err = json.Marshal(field)
        if err != nil {
            return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
        }
    }
    return json.Marshal(object)
}

func TestJettison(t *testing.T) {
    v := Fields{
        AdditionalProperties: map[string]string{
            "foo": "bar",
        },
    }
    _, err := jettison.Marshal(v)
    if err != nil {
        t.Fatal(err)
    }
}
wI2L commented 1 year ago

@ziemekobel-ef Found the issue, I'll send a fix for it.

morya commented 8 months ago

yes, same error here.

import "gorm.io/datatypes"
struct A {
  B datatypes.JSONMap // type JSONMap map[string]interface{}
}

datatypes.JSONMap is a map[string] type, which cause out of memory error.