Repository: bytedance/sonic
Pull request #224 (2022-04-29 10:25:15)

fix: add nil pointer check


Patch:
diff --git a/encoder/assembler_amd64_go116.go b/encoder/assembler_amd64_go116.go
index 9455ed8..a03e09c 100644
--- a/encoder/assembler_amd64_go116.go
+++ b/encoder/assembler_amd64_go116.go
@@ -106,6 +106,7 @@ const (
     _LB_error_too_deep        = "_error_too_deep"
     _LB_error_invalid_number  = "_error_invalid_number"
     _LB_error_nan_or_infinite = "_error_nan_or_infinite"
+    _LB_panic = "_panic"
 )
 
 var (
@@ -273,6 +274,7 @@ func (self *_Assembler) builtins() {
     self.error_too_deep()
     self.error_invalid_number()
     self.error_nan_or_infinite()
+    self.go_panic()
 }
 
 func (self *_Assembler) epilogue() {
@@ -625,12 +627,24 @@ func (self *_Assembler) error_nan_or_infinite()  {
 
 var (
     _F_quote = jit.Imm(int64(native.S_quote))
+    _F_panic = jit.Func(goPanic)
 )
 
+func (self *_Assembler) go_panic() {
+    self.Link(_LB_panic)
+    self.Emit("MOVQ", _SP_p, jit.Ptr(_SP, 8))
+    self.call_go(_F_panic)
+}
+
 func (self *_Assembler) encode_string(doubleQuote bool) {
     self.Emit("MOVQ" , jit.Ptr(_SP_p, 8), _AX)  // MOVQ  8(SP.p), AX
     self.Emit("TESTQ", _AX, _AX)                // TESTQ AX, AX
     self.Sjmp("JZ"   , "_str_empty_{n}")        // JZ    _str_empty_{n}
+    self.Emit("CMPQ", jit.Ptr(_SP_p, 0), jit.Imm(0))
+    self.Sjmp("JNE"   , "_str_next_{n}") 
+    self.Emit("MOVQ", jit.Imm(int64(panicNilPointerOfNonEmptyString)), jit.Ptr(_SP, 0))
+    self.Sjmp("JMP", _LB_panic)
+    self.Link("_str_next_{n}")
 
     /* openning quote, check for double quote */
     if !doubleQuote {
@@ -865,10 +879,15 @@ func (self *_Assembler) _asm_OP_quote(_ *_Instr) {
 }
 
 func (self *_Assembler) _asm_OP_number(_ *_Instr) {
-    self.Emit("MOVQ" , jit.Ptr(_SP_p, 0), _AX)          // MOVQ    (SP.p), AX
     self.Emit("MOVQ" , jit.Ptr(_SP_p, 8), _CX)          // MOVQ    (SP.p), CX
     self.Emit("TESTQ", _CX, _CX)                        // TESTQ   CX, CX
     self.Sjmp("JZ"   , "_empty_{n}")                    // JZ      _empty_{n}
+    self.Emit("MOVQ" , jit.Ptr(_SP_p, 0), _AX)          // MOVQ    (SP.p), AX
+    self.Emit("TESTQ", _AX, _AX)                        // TESTQ   AX, AX
+    self.Sjmp("JNZ"   , "_number_next_{n}") 
+    self.Emit("MOVQ", jit.Imm(int64(panicNilPointerOfNonEmptyString)), jit.Ptr(_SP, 0))
+    self.Sjmp("JMP", _LB_panic)
+    self.Link("_number_next_{n}")
     self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 0))            // MOVQ    AX, (SP)
     self.Emit("MOVQ" , _CX, jit.Ptr(_SP, 8))            // MOVQ    CX, 8(SP)
     self.call_go(_F_isValidNumber)                      // CALL_GO isValidNumber
diff --git a/encoder/assembler_amd64_go117.go b/encoder/assembler_amd64_go117.go
index 7d59be5..331b742 100644
--- a/encoder/assembler_amd64_go117.go
+++ b/encoder/assembler_amd64_go117.go
@@ -107,6 +107,7 @@ const (
     _LB_error_too_deep        = "_error_too_deep"
     _LB_error_invalid_number  = "_error_invalid_number"
     _LB_error_nan_or_infinite = "_error_nan_or_infinite"
+    _LB_panic = "_panic"
 )
 
 var (
@@ -277,6 +278,7 @@ func (self *_Assembler) builtins() {
     self.error_too_deep()
     self.error_invalid_number()
     self.error_nan_or_infinite()
+    self.go_panic()
 }
 
 func (self *_Assembler) epilogue() {
@@ -636,12 +638,24 @@ func (self *_Assembler) error_nan_or_infinite()  {
 
 var (
     _F_quote = jit.Imm(int64(native.S_quote))
+    _F_panic = jit.Func(goPanic)
 )
 
-func (self *_Assembler) encode_string(doubleQuote bool) {
+func (self *_Assembler) go_panic() {
+    self.Link(_LB_panic)
+    self.Emit("MOVQ", _SP_p, _BX)
+    self.call_go(_F_panic)
+}
+
+func (self *_Assembler) encode_string(doubleQuote bool) {       
     self.Emit("MOVQ" , jit.Ptr(_SP_p, 8), _AX)  // MOVQ  8(SP.p), AX
     self.Emit("TESTQ", _AX, _AX)                // TESTQ AX, AX
     self.Sjmp("JZ"   , "_str_empty_{n}")        // JZ    _str_empty_{n}
+    self.Emit("CMPQ", jit.Ptr(_SP_p, 0), jit.Imm(0))
+    self.Sjmp("JNE"   , "_str_next_{n}") 
+    self.Emit("MOVQ", jit.Imm(int64(panicNilPointerOfNonEmptyString)), _AX)
+    self.Sjmp("JMP", _LB_panic)
+    self.Link("_str_next_{n}")
 
     /* openning quote, check for double quote */
     if !doubleQuote {
@@ -877,10 +891,15 @@ func (self *_Assembler) _asm_OP_quote(_ *_Instr) {
 }
 
 func (self *_Assembler) _asm_OP_number(_ *_Instr) {
-    self.Emit("MOVQ" , jit.Ptr(_SP_p, 0), _AX)          // MOVQ    (SP.p), AX
     self.Emit("MOVQ" , jit.Ptr(_SP_p, 8), _BX)          // MOVQ    (SP.p), BX
     self.Emit("TESTQ", _BX, _BX)                        // TESTQ   BX, BX
     self.Sjmp("JZ"   , "_empty_{n}")
+    self.Emit("MOVQ" , jit.Ptr(_SP_p, 0), _AX)          // MOVQ    (SP.p), AX
+    self.Emit("TESTQ", _AX, _AX)                        // TESTQ   AX, AX
+    self.Sjmp("JNZ"   , "_number_next_{n}") 
+    self.Emit("MOVQ", jit.Imm(int64(panicNilPointerOfNonEmptyString)), _AX)
+    self.Sjmp("JMP", _LB_panic)
+    self.Link("_number_next_{n}")
     self.call_go(_F_isValidNumber)                      // CALL_GO isValidNumber
     self.Emit("CMPB" , _AX, jit.Imm(0))                 // CMPB    AX, $0
     self.Sjmp("JE"   , _LB_error_invalid_number)        // JE      _error_invalid_number
diff --git a/encoder/errors.go b/encoder/errors.go
index 9c27888..ac6848a 100644
--- a/encoder/errors.go
+++ b/encoder/errors.go
@@ -21,6 +21,9 @@ import (
     `fmt`
     `reflect`
     `strconv`
+    `unsafe`
+
+    `github.com/bytedance/sonic/internal/rt`
 )
 
 var _ERR_too_deep = &json.UnsupportedValueError {
@@ -47,3 +50,16 @@ func error_number(number json.Number) error {
 func error_marshaler(ret []byte, pos int) error {
     return fmt.Errorf("invalid Marshaler output json syntax at %d: %q", pos, ret)
 }
+
+const (
+    panicNilPointerOfNonEmptyString int = 1 + iota
+)
+
+func goPanic(code int, val unsafe.Pointer) {
+    switch(code){
+    case panicNilPointerOfNonEmptyString:
+        panic(fmt.Sprintf("val: %#v has nil pointer while its length is not zero!", (*rt.GoString)(val)))
+    default:
+        panic("encoder error!")
+    }
+}
\ No newline at end of file


Test patch:
diff --git a/encoder/assembler_test.go b/encoder/assembler_test.go
index 79a592b..eb9952b 100644
--- a/encoder/assembler_test.go
+++ b/encoder/assembler_test.go
@@ -17,17 +17,18 @@
 package encoder
 
 import (
-    `encoding/hex`
-    `encoding/json`
-    `math`
-    `reflect`
-    `runtime`
-    `testing`
-    `unsafe`
+	"encoding/hex"
+	"encoding/json"
+	"math"
+	"reflect"
+	"runtime"
+	"strings"
+	"testing"
+	"unsafe"
 
-    `github.com/bytedance/sonic/internal/rt`
-    `github.com/davecgh/go-spew/spew`
-    `github.com/stretchr/testify/assert`
+	"github.com/bytedance/sonic/internal/rt"
+	"github.com/davecgh/go-spew/spew"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestAssembler_CompileAndLoad(t *testing.T) {
@@ -375,3 +376,37 @@ func TestAssembler_TwitterJSON_Structure(t *testing.T) {
     println(string(m))
     runtime.KeepAlive(s)
 }
+
+func TestScratchedString(t *testing.T) {
+    fatal := *(*string)(unsafe.Pointer(&rt.GoString{nil, 1}))
+    defer func(){
+        if v := recover(); v == nil {
+            t.Fatal()
+        } else if s, ok := v.(string); !ok {
+            t.Fatal(v)
+        }else{
+            if !strings.Contains(s, "has nil pointer while its length is not zero") {
+                t.Fatal(s)
+            }
+        }
+    }()
+    _, _ = Encode(fatal, 0)
+    t.Fatal()
+}
+
+func TestScratchedNumber(t *testing.T) {
+    fatal := *(*json.Number)(unsafe.Pointer(&rt.GoString{nil, 1}))
+    defer func(){
+        if v := recover(); v == nil {
+            t.Fatal()
+        } else if s, ok := v.(string); !ok {
+            t.Fatal(v)
+        }else{
+            if !strings.Contains(s, "has nil pointer while its length is not zero") {
+                t.Fatal(s)
+            }
+        }
+    }()
+    _, _ = Encode(fatal, 0)
+    t.Fatal()
+}
\ No newline at end of file