mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-21 16:58:08 +08:00
In the go, the stack burst will occupy 1G memory, the error message is as follows ```runtime: goroutine stack exceeds 1000000000-byte limit``` If it is a user of gin, the structure of the circular reference is defined on the server side, and the server memory may be used up as long as 100 go processes, and even the server may be down. So I suggest that the mapping function should add a limit on the number of calls, and the number of times can be configured. Of course, I tried several other modifications when I solved the stackoverflow problem described in #1978, but it was not compatible with the existing API, so I did not adopt the solution I tried.
356 lines
8.0 KiB
Go
356 lines
8.0 KiB
Go
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
|
// Use of this source code is governed by a MIT style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package binding
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin/internal/json"
|
|
)
|
|
|
|
var LimitMappingCallNumber = 10000
|
|
|
|
var errUnknownType = errors.New("Unknown type")
|
|
|
|
func mapUri(ptr interface{}, m map[string][]string) error {
|
|
return mapFormByTag(ptr, m, "uri")
|
|
}
|
|
|
|
func mapForm(ptr interface{}, form map[string][]string) error {
|
|
return mapFormByTag(ptr, form, "form")
|
|
}
|
|
|
|
var emptyField = reflect.StructField{}
|
|
|
|
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
|
return mappingByPtr(ptr, formSource(form), tag)
|
|
}
|
|
|
|
// setter tries to set value on a walking by fields of a struct
|
|
type setter interface {
|
|
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
|
|
}
|
|
|
|
type formSource map[string][]string
|
|
|
|
var _ setter = formSource(nil)
|
|
|
|
// TrySet tries to set a value by request's form source (like map[string][]string)
|
|
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
|
|
return setByForm(value, field, form, tagValue, opt)
|
|
}
|
|
|
|
func mappingByPtr(ptr interface{}, setter setter, tag string) error {
|
|
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag, 0)
|
|
return err
|
|
}
|
|
|
|
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string, callNumber int) (bool, error) {
|
|
var vKind = value.Kind()
|
|
|
|
if vKind == reflect.Ptr {
|
|
var isNew bool
|
|
vPtr := value
|
|
if value.IsNil() {
|
|
isNew = true
|
|
vPtr = reflect.New(value.Type().Elem())
|
|
}
|
|
isSetted, err := mapping(vPtr.Elem(), field, setter, tag, callNumber+1)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if isNew && isSetted {
|
|
value.Set(vPtr)
|
|
}
|
|
return isSetted, nil
|
|
}
|
|
|
|
if callNumber > LimitMappingCallNumber {
|
|
return false, errors.New("Maybe entering a circular reference")
|
|
}
|
|
|
|
if vKind != reflect.Struct || !field.Anonymous {
|
|
ok, err := tryToSetValue(value, field, setter, tag)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if ok {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
if vKind == reflect.Struct {
|
|
tValue := value.Type()
|
|
|
|
var isSetted bool
|
|
for i := 0; i < value.NumField(); i++ {
|
|
sf := tValue.Field(i)
|
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
|
continue
|
|
}
|
|
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag, callNumber+1)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
isSetted = isSetted || ok
|
|
}
|
|
return isSetted, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
type setOptions struct {
|
|
isDefaultExists bool
|
|
defaultValue string
|
|
}
|
|
|
|
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
|
var tagValue string
|
|
var setOpt setOptions
|
|
|
|
tagValue = field.Tag.Get(tag)
|
|
tagValue, opts := head(tagValue, ",")
|
|
|
|
if tagValue == "-" { // just ignoring this field
|
|
return false, nil
|
|
}
|
|
if tagValue == "" { // default value is FieldName
|
|
tagValue = field.Name
|
|
}
|
|
if tagValue == "" { // when field is "emptyField" variable
|
|
return false, nil
|
|
}
|
|
|
|
var opt string
|
|
for len(opts) > 0 {
|
|
opt, opts = head(opts, ",")
|
|
|
|
if k, v := head(opt, "="); k == "default" {
|
|
setOpt.isDefaultExists = true
|
|
setOpt.defaultValue = v
|
|
}
|
|
}
|
|
|
|
return setter.TrySet(value, field, tagValue, setOpt)
|
|
}
|
|
|
|
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
|
|
vs, ok := form[tagValue]
|
|
if !ok && !opt.isDefaultExists {
|
|
return false, nil
|
|
}
|
|
|
|
switch value.Kind() {
|
|
case reflect.Slice:
|
|
if !ok {
|
|
vs = []string{opt.defaultValue}
|
|
}
|
|
return true, setSlice(vs, value, field)
|
|
case reflect.Array:
|
|
if !ok {
|
|
vs = []string{opt.defaultValue}
|
|
}
|
|
if len(vs) != value.Len() {
|
|
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
|
}
|
|
return true, setArray(vs, value, field)
|
|
default:
|
|
var val string
|
|
if !ok {
|
|
val = opt.defaultValue
|
|
}
|
|
|
|
if len(vs) > 0 {
|
|
val = vs[0]
|
|
}
|
|
return true, setWithProperType(val, value, field)
|
|
}
|
|
}
|
|
|
|
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
|
|
switch value.Kind() {
|
|
case reflect.Int:
|
|
return setIntField(val, 0, value)
|
|
case reflect.Int8:
|
|
return setIntField(val, 8, value)
|
|
case reflect.Int16:
|
|
return setIntField(val, 16, value)
|
|
case reflect.Int32:
|
|
return setIntField(val, 32, value)
|
|
case reflect.Int64:
|
|
switch value.Interface().(type) {
|
|
case time.Duration:
|
|
return setTimeDuration(val, value, field)
|
|
}
|
|
return setIntField(val, 64, value)
|
|
case reflect.Uint:
|
|
return setUintField(val, 0, value)
|
|
case reflect.Uint8:
|
|
return setUintField(val, 8, value)
|
|
case reflect.Uint16:
|
|
return setUintField(val, 16, value)
|
|
case reflect.Uint32:
|
|
return setUintField(val, 32, value)
|
|
case reflect.Uint64:
|
|
return setUintField(val, 64, value)
|
|
case reflect.Bool:
|
|
return setBoolField(val, value)
|
|
case reflect.Float32:
|
|
return setFloatField(val, 32, value)
|
|
case reflect.Float64:
|
|
return setFloatField(val, 64, value)
|
|
case reflect.String:
|
|
value.SetString(val)
|
|
case reflect.Struct:
|
|
switch value.Interface().(type) {
|
|
case time.Time:
|
|
return setTimeField(val, field, value)
|
|
}
|
|
return json.Unmarshal([]byte(val), value.Addr().Interface())
|
|
case reflect.Map:
|
|
return json.Unmarshal([]byte(val), value.Addr().Interface())
|
|
default:
|
|
return errUnknownType
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setIntField(val string, bitSize int, field reflect.Value) error {
|
|
if val == "" {
|
|
val = "0"
|
|
}
|
|
intVal, err := strconv.ParseInt(val, 10, bitSize)
|
|
if err == nil {
|
|
field.SetInt(intVal)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func setUintField(val string, bitSize int, field reflect.Value) error {
|
|
if val == "" {
|
|
val = "0"
|
|
}
|
|
uintVal, err := strconv.ParseUint(val, 10, bitSize)
|
|
if err == nil {
|
|
field.SetUint(uintVal)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func setBoolField(val string, field reflect.Value) error {
|
|
if val == "" {
|
|
val = "false"
|
|
}
|
|
boolVal, err := strconv.ParseBool(val)
|
|
if err == nil {
|
|
field.SetBool(boolVal)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func setFloatField(val string, bitSize int, field reflect.Value) error {
|
|
if val == "" {
|
|
val = "0.0"
|
|
}
|
|
floatVal, err := strconv.ParseFloat(val, bitSize)
|
|
if err == nil {
|
|
field.SetFloat(floatVal)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
|
|
timeFormat := structField.Tag.Get("time_format")
|
|
if timeFormat == "" {
|
|
timeFormat = time.RFC3339
|
|
}
|
|
|
|
switch tf := strings.ToLower(timeFormat); tf {
|
|
case "unix", "unixnano":
|
|
tv, err := strconv.ParseInt(val, 10, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d := time.Duration(1)
|
|
if tf == "unixnano" {
|
|
d = time.Second
|
|
}
|
|
|
|
t := time.Unix(tv/int64(d), tv%int64(d))
|
|
value.Set(reflect.ValueOf(t))
|
|
return nil
|
|
|
|
}
|
|
|
|
if val == "" {
|
|
value.Set(reflect.ValueOf(time.Time{}))
|
|
return nil
|
|
}
|
|
|
|
l := time.Local
|
|
if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
|
|
l = time.UTC
|
|
}
|
|
|
|
if locTag := structField.Tag.Get("time_location"); locTag != "" {
|
|
loc, err := time.LoadLocation(locTag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
l = loc
|
|
}
|
|
|
|
t, err := time.ParseInLocation(timeFormat, val, l)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value.Set(reflect.ValueOf(t))
|
|
return nil
|
|
}
|
|
|
|
func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
|
|
for i, s := range vals {
|
|
err := setWithProperType(s, value.Index(i), field)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
|
|
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
|
|
err := setArray(vals, slice, field)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value.Set(slice)
|
|
return nil
|
|
}
|
|
|
|
func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error {
|
|
d, err := time.ParseDuration(val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value.Set(reflect.ValueOf(d))
|
|
return nil
|
|
}
|
|
|
|
func head(str, sep string) (head string, tail string) {
|
|
idx := strings.Index(str, sep)
|
|
if idx < 0 {
|
|
return str, ""
|
|
}
|
|
return str[:idx], str[idx+len(sep):]
|
|
}
|