Repository: apache/dubbo-go-hessian2 (go)

Issue:
when a struct has more than one kind of empty slice whose type is pointer ,decode will panic
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!

-->


**What happened**:
1.在同一个结构体里面有两种不同的指针切片的时候，如果切片两个都赋值为空切片，反序列化的时候会造成Panic。
2.在同一个结构体里面有两种不同的结构切片的时候，如果切片两个都赋值为空切片，反序列化后两个切片都会赋值为nil。
下面这段代码能复现这个结果
**What you expected to happen**:
1.反序列化回来两个空切片
2.反序列化回来两个空切片
**How to reproduce it (as minimally and precisely as possible)**:

import (
	hessian "github.com/apache/dubbo-go-hessian2"
)

func init() {
	hessian.RegisterPOJO(new(SpPoint))
	hessian.RegisterPOJO(new(Point))
	hessian.RegisterPOJO(new(ReqInfo))
	hessian.RegisterPOJO(new(ReqInfo2))
}

type SpPoint struct {
	X  int
	Y  int
	Sp int
}

func (SpPoint) JavaClassName() string {
	return "com.test.SpPoint"
}

type ReqInfo struct {
	Name     string
	SpPoints []*SpPoint
	Points   []*Point
}

type ReqInfo2 struct {
	Name     string
	SpPoints []SpPoint
	Points   []Point
}

func (ReqInfo2) JavaClassName() string {
	return "com.test.ReqInfo2"
}

func (ReqInfo) JavaClassName() string {
	return "com.test.ReqInfo"
}

func main() {
	d := ReqInfo{
		Name:     "test",
		SpPoints: []*SpPoint{},
		Points:   []*Point{},
	}
	//d := ReqInfo2{
	//	Name:     "test",
	//	SpPoints: []SpPoint{},
	//	Points:   []Point{},
	//}

	encoder := hessian.NewEncoder()
	err := encoder.Encode(d)
	if err != nil {
		return
	}
	decoder := hessian.NewDecoder(encoder.Buffer())
	doInterface, err := decoder.Decode()

	_ = doInterface
}

**Anything else we need to know?**:


Pull request:
fix issue #340

**Which issue(s) this PR fixes**:

Fixes #340


Patch:
diff --git a/hessian.go b/hessian.go
index 0549984..231dbea 100644
--- a/hessian.go
+++ b/hessian.go
@@ -15,6 +15,10 @@
  * limitations under the License.
  */
 
+// This file contains the implementation of
+// [dubbo2 rpc protocol](https://dubbo.apache.org/zh/docs/concepts/rpc-protocol/#dubbo2), which is deprecated.
+// Use the [implementation](https://github.com/apache/dubbo-go/tree/master/protocol/dubbo/hessian2) in dubbo-go project instead.
+
 package hessian
 
 import (
diff --git a/ref.go b/ref.go
index e23d688..f892601 100644
--- a/ref.go
+++ b/ref.go
@@ -26,6 +26,9 @@ import (
 	perrors "github.com/pkg/errors"
 )
 
+// Empty slice is not nil, but the addresses of all empty slice are the same.
+var _emptySliceAddr = unsafe.Pointer(reflect.ValueOf([]interface{}{}).Pointer())
+
 // used to ref object,list,map
 type _refElem struct {
 	// record the kind of target, objects are the same only if the address and kind are the same
@@ -124,18 +127,20 @@ func (e *Encoder) checkRefMap(v reflect.Value) (int, bool) {
 		}
 	}
 
-	if elem, ok := e.refMap[addr]; ok {
-		if elem.kind == kind {
-			// If kind is not struct, just return the index. Otherwise,
-			// check whether the types are same, because the different
-			// empty struct may share the same address and kind.
-			if elem.kind != reflect.Struct {
-				return elem.index, ok
-			} else if elem.tp == tp {
-				return elem.index, ok
+	if addr != _emptySliceAddr {
+		if elem, ok := e.refMap[addr]; ok {
+			if elem.kind == kind {
+				// If kind is not struct, just return the index. Otherwise,
+				// check whether the types are same, because the different
+				// empty struct may share the same address and kind.
+				if elem.kind != reflect.Struct {
+					return elem.index, ok
+				} else if elem.tp == tp {
+					return elem.index, ok
+				}
 			}
+			return 0, false
 		}
-		return 0, false
 	}
 
 	n := len(e.refMap)
diff --git a/request.go b/request.go
index 7a50465..a6def73 100644
--- a/request.go
+++ b/request.go
@@ -15,6 +15,10 @@
  * limitations under the License.
  */
 
+// This file contains the implementation of
+// [dubbo2 rpc protocol](https://dubbo.apache.org/zh/docs/concepts/rpc-protocol/#dubbo2), which is deprecated.
+// Use the [implementation](https://github.com/apache/dubbo-go/tree/master/protocol/dubbo/hessian2) in dubbo-go project instead.
+
 package hessian
 
 import (
diff --git a/response.go b/response.go
index 66235d7..5f90592 100644
--- a/response.go
+++ b/response.go
@@ -15,6 +15,10 @@
  * limitations under the License.
  */
 
+// This file contains the implementation of
+// [dubbo2 rpc protocol](https://dubbo.apache.org/zh/docs/concepts/rpc-protocol/#dubbo2), which is deprecated.
+// Use the [implementation](https://github.com/apache/dubbo-go/tree/master/protocol/dubbo/hessian2) in dubbo-go project instead.
+
 package hessian
 
 import (


Test patch:
diff --git a/testcases/issue340/issue340_test.go b/testcases/issue340/issue340_test.go
new file mode 100644
index 0000000..67d4218
--- /dev/null
+++ b/testcases/issue340/issue340_test.go
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package issue340
+
+import (
+	"reflect"
+	"testing"
+)
+
+import (
+	hessian "github.com/apache/dubbo-go-hessian2"
+)
+
+import (
+	"github.com/stretchr/testify/assert"
+)
+
+type Point struct {
+	X int
+	Y int
+}
+
+func (Point) JavaClassName() string {
+	return "com.test.Point"
+}
+
+type SpPoint struct {
+	X  int
+	Y  int
+	Sp int
+}
+
+func (SpPoint) JavaClassName() string {
+	return "com.test.SpPoint"
+}
+
+type ReqInfo struct {
+	Name     string
+	Points   []*Point
+	SpPoints []*SpPoint
+}
+
+func (ReqInfo) JavaClassName() string {
+	return "com.test.ReqInfo"
+}
+
+func TestIssue340Case(t *testing.T) {
+	req := &ReqInfo{
+		Name:     "test",
+		Points:   []*Point{},
+		SpPoints: []*SpPoint{},
+	}
+
+	hessian.RegisterPOJO(&Point{})
+	hessian.RegisterPOJO(&SpPoint{})
+	hessian.RegisterPOJO(req)
+
+	encoder := hessian.NewEncoder()
+	err := encoder.Encode(req)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	enBuf := encoder.Buffer()
+
+	decoder := hessian.NewDecoder(enBuf)
+	deReq, err := decoder.Decode()
+	assert.Nil(t, err)
+
+	t.Log(deReq)
+
+	assert.True(t, reflect.DeepEqual(req, deReq))
+}