From 6a9a212c94d4e85c1c55c9a06011763b7711d8c0 Mon Sep 17 00:00:00 2001 From: Phil Pearl Date: Wed, 17 Jul 2019 18:16:32 +0100 Subject: [PATCH 1/3] Cope with nil pointer on marshal Should marshal to `null` not panic. Fixes #242 --- helpers.go | 19 +++++++++++++++++++ tests/basic_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/helpers.go b/helpers.go index b86b87d..fba794e 100644 --- a/helpers.go +++ b/helpers.go @@ -5,6 +5,7 @@ import ( "io" "io/ioutil" "net/http" + "reflect" "strconv" "github.com/mailru/easyjson/jlexer" @@ -26,10 +27,18 @@ type Optional interface { IsDefined() bool } +func isNilInterface(i interface{}) bool { + v := reflect.ValueOf(i) + return v.Kind() == reflect.Ptr && v.IsNil() +} + // Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied // from a chain of smaller chunks. func Marshal(v Marshaler) ([]byte, error) { w := jwriter.Writer{} + if isNilInterface(v) { + return nullBytes, nil + } v.MarshalEasyJSON(&w) return w.BuildBytes() } @@ -37,6 +46,9 @@ func Marshal(v Marshaler) ([]byte, error) { // MarshalToWriter marshals the data to an io.Writer. func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) { jw := jwriter.Writer{} + if isNilInterface(v) { + return w.Write(nullBytes) + } v.MarshalEasyJSON(&jw) return jw.DumpTo(w) } @@ -47,6 +59,13 @@ func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) { // invoked (in this case a 500 reply is possible). func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bool, written int, err error) { jw := jwriter.Writer{} + if isNilInterface(v) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Length", strconv.Itoa(len(nullBytes))) + written, err = w.Write(nullBytes) + return true, written, err + } + v.MarshalEasyJSON(&jw) if jw.Error != nil { return false, 0, jw.Error diff --git a/tests/basic_test.go b/tests/basic_test.go index 938add0..259220f 100644 --- a/tests/basic_test.go +++ b/tests/basic_test.go @@ -1,6 +1,8 @@ package tests import ( + "bytes" + "net/http/httptest" "reflect" "testing" @@ -243,3 +245,35 @@ func TestDisallowUnknown(t *testing.T) { t.Error("want error, got nil") } } + +func TestNil(t *testing.T) { + var p *PrimitiveTypes + + data, err := easyjson.Marshal(p) + if err != nil { + t.Errorf("easyjson.Marshal() error: %v", err) + } + if string(data) != "null" { + t.Errorf("Wanted null, got %q", string(data)) + } + + var b bytes.Buffer + if n, err := easyjson.MarshalToWriter(p, &b); err != nil || n != 4 { + t.Errorf("easyjson.MarshalToWriter() error: %v, written %d", err, n) + } + + if s := b.String(); s != "null" { + t.Errorf("Wanted null, got %q", s) + } + + w := httptest.NewRecorder() + started, written, err := easyjson.MarshalToHTTPResponseWriter(p, w) + if !started || written != 4 || err != nil { + t.Errorf("easyjson.MarshalToHTTPResponseWriter() error: %v, written %d, started %t", + err, written, started) + } + + if s := w.Body.String(); s != "null" { + t.Errorf("Wanted null, got %q", s) + } +} From 560c81d14fd9ad0d450857f743b14f4bd1ab6875 Mon Sep 17 00:00:00 2001 From: Phil Pearl Date: Thu, 18 Jul 2019 15:31:56 +0100 Subject: [PATCH 2/3] benchmark for isNilInterface --- helpers_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 helpers_test.go diff --git a/helpers_test.go b/helpers_test.go new file mode 100644 index 0000000..a10a46c --- /dev/null +++ b/helpers_test.go @@ -0,0 +1,12 @@ +package easyjson + +import "testing" + +func BenchmarkNilCheck(b *testing.B) { + var a *int + for i := 0; i < b.N; i++ { + if !isNilInterface(a) { + b.Fatal("expected it to be nil") + } + } +} From 5f3e4e91b9046ffc60f482430dc2a8330f0632b8 Mon Sep 17 00:00:00 2001 From: Alexandr Mayorskiy Date: Sun, 12 Apr 2020 18:19:06 +0300 Subject: [PATCH 3/3] improve marshaler nil check --- helpers.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/helpers.go b/helpers.go index 2b41085..447e492 100644 --- a/helpers.go +++ b/helpers.go @@ -5,8 +5,8 @@ import ( "io" "io/ioutil" "net/http" - "reflect" "strconv" + "unsafe" "github.com/mailru/easyjson/jlexer" "github.com/mailru/easyjson/jwriter" @@ -38,27 +38,28 @@ type UnknownsMarshaler interface { } func isNilInterface(i interface{}) bool { - v := reflect.ValueOf(i) - return v.Kind() == reflect.Ptr && v.IsNil() + return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0 } // Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied // from a chain of smaller chunks. func Marshal(v Marshaler) ([]byte, error) { - w := jwriter.Writer{} if isNilInterface(v) { return nullBytes, nil } + + w := jwriter.Writer{} v.MarshalEasyJSON(&w) return w.BuildBytes() } // MarshalToWriter marshals the data to an io.Writer. func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) { - jw := jwriter.Writer{} if isNilInterface(v) { return w.Write(nullBytes) } + + jw := jwriter.Writer{} v.MarshalEasyJSON(&jw) return jw.DumpTo(w) } @@ -68,7 +69,6 @@ func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) { // false if an error occurred before any http.ResponseWriter methods were actually // invoked (in this case a 500 reply is possible). func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bool, written int, err error) { - jw := jwriter.Writer{} if isNilInterface(v) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Length", strconv.Itoa(len(nullBytes))) @@ -76,6 +76,7 @@ func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bo return true, written, err } + jw := jwriter.Writer{} v.MarshalEasyJSON(&jw) if jw.Error != nil { return false, 0, jw.Error