Skip to content

Commit

Permalink
Fix: struct2MapAll logics (#1356)
Browse files Browse the repository at this point in the history
* fix struct2MapAll logics

* fix size computation for allocation may overflow
  • Loading branch information
justxuewei committed Aug 3, 2021
1 parent 5e781ec commit 2dca192
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 32 deletions.
2 changes: 1 addition & 1 deletion cluster/router/tag/router_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func getRule(rawRule string) (*RouterRule, error) {

// parseTags use for flattening tags data to @addressToTagNames and @tagNameToAddresses
func (t *RouterRule) parseTags() {
t.AddressToTagNames = make(map[string][]string, 2*len(t.Tags))
t.AddressToTagNames = make(map[string][]string, len(t.Tags))
t.TagNameToAddresses = make(map[string][]string, len(t.Tags))
for _, tag := range t.Tags {
for _, address := range tag.Addresses {
Expand Down
97 changes: 70 additions & 27 deletions filter/filter_impl/generic_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
import (
"github.com/apache/dubbo-go/common/constant"
"github.com/apache/dubbo-go/common/extension"
"github.com/apache/dubbo-go/common/logger"
"github.com/apache/dubbo-go/filter"
"github.com/apache/dubbo-go/protocol"
invocation2 "github.com/apache/dubbo-go/protocol/invocation"
Expand Down Expand Up @@ -59,7 +60,7 @@ func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, i
if oldParams, ok := oldArguments[2].([]interface{}); ok {
newParams := make([]hessian.Object, 0, len(oldParams))
for i := range oldParams {
newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i])))
newParams = append(newParams, hessian.Object(objToMap(oldParams[i])))
}
newArguments := []interface{}{
oldArguments[0],
Expand All @@ -85,60 +86,89 @@ func GetGenericFilter() filter.Filter {
return &GenericFilter{}
}

func struct2MapAll(obj interface{}) interface{} {
func objToMap(obj interface{}) interface{} {
if obj == nil {
return obj
}

t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
if t.Kind() == reflect.Struct {

// if obj is a POJO, get the struct from the pointer (if it is a pointer)
pojo, isPojo := obj.(hessian.POJO)
if isPojo {
for t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
}

switch t.Kind() {
case reflect.Struct:
result := make(map[string]interface{}, t.NumField())
if isPojo {
result["class"] = pojo.JavaClassName()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
kind := value.Kind()
if kind == reflect.Struct || kind == reflect.Slice || kind == reflect.Map {
if value.CanInterface() {
tmp := value.Interface()
if _, ok := tmp.(time.Time); ok {
setInMap(result, field, tmp)
continue
}
setInMap(result, field, struct2MapAll(tmp))
if !value.CanInterface() {
logger.Debugf("objToMap for %v is skipped because it couldn't be converted to interface", field)
continue
}
valueIface := value.Interface()
switch kind {
case reflect.Ptr:
if value.IsNil() {
setInMap(result, field, nil)
continue
}
} else {
if value.CanInterface() {
setInMap(result, field, value.Interface())
setInMap(result, field, objToMap(valueIface))
case reflect.Struct, reflect.Slice, reflect.Map:
if isPrimitive(valueIface) {
logger.Warnf("\"%s\" is primitive. The application may crash if it's transferred between "+
"systems implemented by different languages, e.g. dubbo-go <-> dubbo-java. We recommend "+
"you represent the object by basic types, like string.", value.Type())
setInMap(result, field, valueIface)
continue
}

setInMap(result, field, objToMap(valueIface))
default:
setInMap(result, field, valueIface)
}
}
return result
} else if t.Kind() == reflect.Slice {
case reflect.Array, reflect.Slice:
value := reflect.ValueOf(obj)
var newTemps = make([]interface{}, 0, value.Len())
newTemps := make([]interface{}, 0, value.Len())
for i := 0; i < value.Len(); i++ {
newTemp := struct2MapAll(value.Index(i).Interface())
newTemp := objToMap(value.Index(i).Interface())
newTemps = append(newTemps, newTemp)
}
return newTemps
} else if t.Kind() == reflect.Map {
var newTempMap = make(map[interface{}]interface{}, v.Len())
case reflect.Map:
newTempMap := make(map[interface{}]interface{}, v.Len())
iter := v.MapRange()
for iter.Next() {
if !iter.Value().CanInterface() {
continue
}
key := iter.Key()
mapV := iter.Value().Interface()
newTempMap[convertMapKey(key)] = struct2MapAll(mapV)
newTempMap[mapKey(key)] = objToMap(mapV)
}
return newTempMap
} else {
case reflect.Ptr:
return objToMap(v.Elem().Interface())
default:
return obj
}
}

func convertMapKey(key reflect.Value) interface{} {
// mapKey converts the map key to interface type
func mapKey(key reflect.Value) interface{} {
switch key.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8,
reflect.Int16, reflect.Int32, reflect.Int64,
Expand All @@ -147,21 +177,34 @@ func convertMapKey(key reflect.Value) interface{} {
reflect.Float64, reflect.String:
return key.Interface()
default:
return key.String()
name := key.String()
if name == "class" {
panic(`"class" is a reserved keyword`)
}
return name
}
}

// setInMap sets the struct into the map using the tag or the name of the struct as the key
func setInMap(m map[string]interface{}, structField reflect.StructField, value interface{}) (result map[string]interface{}) {
result = m
if tagName := structField.Tag.Get("m"); tagName == "" {
result[headerAtoa(structField.Name)] = value
result[toUnexport(structField.Name)] = value
} else {
result[tagName] = value
}
return
}

func headerAtoa(a string) (b string) {
b = strings.ToLower(a[:1]) + a[1:]
return
// toUnexport is to lower the first letter
func toUnexport(a string) string {
return strings.ToLower(a[:1]) + a[1:]
}

// isPrimitive determines if the object is primitive
func isPrimitive(obj interface{}) bool {
if _, ok := obj.(time.Time); ok {
return true
}
return false
}
22 changes: 19 additions & 3 deletions filter/filter_impl/generic_filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestStruct2MapAll(t *testing.T) {
testData.CaCa.XxYy.Xx = "3"
testData.DaDa = time.Date(2020, 10, 29, 2, 34, 0, 0, time.Local)
testData.EeEe = 100
m := struct2MapAll(testData).(map[string]interface{})
m := objToMap(testData).(map[string]interface{})
assert.Equal(t, "1", m["aaAa"].(string))
assert.Equal(t, "1", m["baBa"].(string))
assert.Equal(t, "2", m["caCa"].(map[string]interface{})["aaAa"].(string))
Expand Down Expand Up @@ -85,7 +85,7 @@ func TestStruct2MapAllSlice(t *testing.T) {
tmp.XxYy.xxXx = "3"
tmp.XxYy.Xx = "3"
testData.CaCa = append(testData.CaCa, tmp)
m := struct2MapAll(testData).(map[string]interface{})
m := objToMap(testData).(map[string]interface{})

assert.Equal(t, "1", m["aaAa"].(string))
assert.Equal(t, "1", m["baBa"].(string))
Expand Down Expand Up @@ -120,7 +120,7 @@ func TestStruct2MapAllMap(t *testing.T) {
testData.CaCa["k1"] = "v1"
testData.CaCa["kv2"] = "v2"
testData.IntMap[1] = 1
m := struct2MapAll(testData)
m := objToMap(testData)

assert.Equal(t, reflect.Map, reflect.TypeOf(m).Kind())
mappedStruct := m.(map[string]interface{})
Expand All @@ -135,3 +135,19 @@ func TestStruct2MapAllMap(t *testing.T) {
assert.Equal(t, reflect.Map, reflect.TypeOf(intMap).Kind())
assert.Equal(t, 1, intMap.(map[interface{}]interface{})[1])
}

type mockParent struct {
Children []*mockChild
}

func (p *mockParent) JavaClassName() string {
return "org.apache.dubbo.mockParent"
}

type mockChild struct {
Name string
}

func (p *mockChild) JavaClassName() string {
return "org.apache.dubbo.mockChild"
}
2 changes: 1 addition & 1 deletion filter/filter_impl/generic_service_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol.
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
result.SetResult(struct2MapAll(v.Interface()))
result.SetResult(objToMap(v.Interface()))
}
return result
}
Expand Down

0 comments on commit 2dca192

Please sign in to comment.