Description

bindJSON will panic since version 1.7.0 if JSON body is set to "null" for a POST request.

How to reproduce

package main

import (
    "fmt"

    "github.com/gin-gonic/gin"
)

type Example struct {
    ID string `json:"id"`
}

func main() {
    fmt.Println(gin.Version)
    r := gin.New()

    r.POST("/v1/jobs", func(ctx *gin.Context) {
        var ex *Example

        err := ctx.BindJSON(&ex)
        fmt.Println(err)
    })

    r.Run()
}

Expectations

$ curl --location --request POST 'localhost:8080/v1/jobs' \
--header 'Content-Type: application/json' \
--data-raw 'null'
400 Bad Request

Actual result

$ curl --location --request POST 'localhost:8080/v1/jobs' \
--header 'Content-Type: application/json' \
--data-raw 'null'

ct.Value.Interface on zero Value
goroutine 36 [running]:
net/http.(*conn).serve.func1()
        /usr/local/opt/go/libexec/src/net/http/server.go:1802 +0xb9
panic({0x15cd100, 0xc000308600})
        /usr/local/opt/go/libexec/src/runtime/panic.go:1047 +0x266
reflect.valueInterface({0x0, 0x0, 0x105b9a5}, 0x8)
        /usr/local/opt/go/libexec/src/reflect/value.go:1356 +0x10e
reflect.Value.Interface(...)
        /usr/local/opt/go/libexec/src/reflect/value.go:1351
github.com/gin-gonic/gin/binding.(*defaultValidator).ValidateStruct(0x15ea7a0, {0x15ea7a0, 0x0})
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/binding/default_validator.go:45 +0xf2
github.com/gin-gonic/gin/binding.(*defaultValidator).ValidateStruct(0xc00013f2c0, {0x15927c0, 0xc0000100b0})
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/binding/default_validator.go:45 +0x105
github.com/gin-gonic/gin/binding.validate(...)
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/binding/binding.go:117
github.com/gin-gonic/gin/binding.decodeJSON({0x1f7d398, 0xc000452fc0}, {0x15927c0, 0xc0000100b0})
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/binding/json.go:55 +0x9b
github.com/gin-gonic/gin/binding.jsonBinding.Bind({}, 0x1e005b8, {0x15927c0, 0xc0000100b0})
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/binding/json.go:37 +0x5f
github.com/gin-gonic/gin.(*Context).ShouldBindWith(...)
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/context.go:703
github.com/gin-gonic/gin.(*Context).MustBindWith(0xc000438100, {0x15927c0, 0xc0000100b0}, {0x17963a0, 0x1bc6208})
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/context.go:646 +0x47
github.com/gin-gonic/gin.(*Context).BindJSON(...)
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/context.go:609
main.main.func1(0x40)
        /Users/yanis/Qonto/test/main.go:18 +0x4c
github.com/gin-gonic/gin.(*Context).Next(...)
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/context.go:165
github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc000464680, 0xc000438100)
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/gin.go:489 +0x63e
github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc000464680, {0x17970d0, 0xc0000cb880}, 0xc000438000)
        /Users/yanis/go/pkg/mod/github.com/gin-gonic/gin@v1.7.0/gin.go:445 +0x1c5
net/http.serverHandler.ServeHTTP({0x1795b58}, {0x17970d0, 0xc0000cb880}, 0xc000438000)
        /usr/local/opt/go/libexec/src/net/http/server.go:2879 +0x43b
net/http.(*conn).serve(0xc0001faa00, {0x179bb40, 0xc0003c1a40})
        /usr/local/opt/go/libexec/src/net/http/server.go:1930 +0xb08
created by net/http.(*Server).Serve
        /usr/local/opt/go/libexec/src/net/http/server.go:3034 +0x4e8

Environment

  • go version: 1.17.5
  • gin version (or commit ref): Working version 1.6.3, not working since 1.7.0
  • operating system: MacOS Big Sur

More:

Looks like there's an issue with https://github.com/gin-gonic/gin/commit/4bfae4c8c8f7764cc587022ba6d9d2fd18e6c47d, and especially the ValidateStruct function. From my understanding, the nil check evaluates to false when obj is a nil interface.

I quickly tried to check for nil interfaces which seem to solve the issue. I'm not sure about side effects though:

if (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil()) {
return nil
}

Comment From: Bisstocuz

Use BindJSON will panic, you should use ShouldBindJSON instead.

Comment From: yns01

Thanks @Bisstocuz, but the result is the same, it panics.

Comment From: Bisstocuz

var ex *Example

This is a pointer, it has no memory allocation. Use var ex Example instead.

Comment From: yns01

I know, however this used to work prior to 1.7.0. I don't think the code should panic.

Comment From: yns01

Also, this does not seem to cause any issues to the json decoder. The panics occurs after the decoding, in the struct validation fn.

        var ex *Example

        dec := json.NewDecoder(ctx.Request.Body)
        err := dec.Decode(&ex)
        fmt.Println(err) <nil>
        fmt.Println(ex) &{hello} OR nil if I pass "null" as a body

Comment From: wickcy

Also, this does not seem to cause any issues to the json decoder. The panics occurs after the decoding, in the struct validation fn.

``` var ex *Example

  dec := json.NewDecoder(ctx.Request.Body)
  err := dec.Decode(&ex)
  fmt.Println(err) <nil>
  fmt.Println(ex) &{hello} OR nil if I pass "null" as a body

```

var ex Example

err := ctx.BindJSON(&ex)

I also encountered this problem. In these two days, it can be modified to solve it.

Comment From: sdlyingyong

I tried this. It worked.

ex := new(Example)
err := ctx.BindJSON(ex)