fix: avoid float overflow in mapping.Unmarshal (#3590)
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -614,7 +615,18 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
|||||||
if err := setValueFromString(typeKind, target, v.String()); err != nil {
|
if err := setValueFromString(typeKind, target, v.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32:
|
||||||
|
fValue, err := v.Float64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fValue > math.MaxFloat32 {
|
||||||
|
return float32OverflowError(v.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
target.SetFloat(fValue)
|
||||||
|
case reflect.Float64:
|
||||||
fValue, err := v.Float64()
|
fValue, err := v.Float64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -731,6 +731,34 @@ func TestUnmarshalInt32WithOverflow(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalInt64WithOverflow(t *testing.T) {
|
||||||
|
t.Run("int64 from string", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value int64 `key:"int,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"int": "18446744073709551616", // overflow, 1 << 64
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("int64 from json.Number", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value int64 `key:"int,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"int": json.Number("18446744073709551616"), // overflow, 1 << 64
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalUint8WithOverflow(t *testing.T) {
|
func TestUnmarshalUint8WithOverflow(t *testing.T) {
|
||||||
t.Run("uint8 from string", func(t *testing.T) {
|
t.Run("uint8 from string", func(t *testing.T) {
|
||||||
type inner struct {
|
type inner struct {
|
||||||
@@ -758,7 +786,7 @@ func TestUnmarshalUint8WithOverflow(t *testing.T) {
|
|||||||
assert.Error(t, UnmarshalKey(m, &in))
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("uint8 from json.Number", func(t *testing.T) {
|
t.Run("uint8 from json.Number with negative", func(t *testing.T) {
|
||||||
type inner struct {
|
type inner struct {
|
||||||
Value uint8 `key:"int"`
|
Value uint8 `key:"int"`
|
||||||
}
|
}
|
||||||
@@ -812,7 +840,7 @@ func TestUnmarshalUint16WithOverflow(t *testing.T) {
|
|||||||
assert.Error(t, UnmarshalKey(m, &in))
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("uint16 from json.Number", func(t *testing.T) {
|
t.Run("uint16 from json.Number with negative", func(t *testing.T) {
|
||||||
type inner struct {
|
type inner struct {
|
||||||
Value uint16 `key:"int"`
|
Value uint16 `key:"int"`
|
||||||
}
|
}
|
||||||
@@ -866,7 +894,7 @@ func TestUnmarshalUint32WithOverflow(t *testing.T) {
|
|||||||
assert.Error(t, UnmarshalKey(m, &in))
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("uint32 from json.Number", func(t *testing.T) {
|
t.Run("uint32 from json.Number with negative", func(t *testing.T) {
|
||||||
type inner struct {
|
type inner struct {
|
||||||
Value uint32 `key:"int"`
|
Value uint32 `key:"int"`
|
||||||
}
|
}
|
||||||
@@ -893,6 +921,116 @@ func TestUnmarshalUint32WithOverflow(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalUint64WithOverflow(t *testing.T) {
|
||||||
|
t.Run("uint64 from string", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value uint64 `key:"int,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"int": "18446744073709551616", // overflow, 1 << 64
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint64 from json.Number", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value uint64 `key:"int,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"int": json.Number("18446744073709551616"), // overflow, 1 << 64
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalFloat32WithOverflow(t *testing.T) {
|
||||||
|
t.Run("float32 from string greater than float64", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value float32 `key:"float,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"float": "1.79769313486231570814527423731704356798070e+309", // overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float32 from string greater than float32", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value float32 `key:"float,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"float": "1.79769313486231570814527423731704356798070e+300", // overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float32 from json.Number greater than float64", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value float32 `key:"float"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"float": json.Number("1.79769313486231570814527423731704356798070e+309"), // overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float32 from json.Number greater than float32", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value float32 `key:"float"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"float": json.Number("1.79769313486231570814527423731704356798070e+300"), // overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalFloat64WithOverflow(t *testing.T) {
|
||||||
|
t.Run("float64 from string greater than float64", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value float64 `key:"float,string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"float": "1.79769313486231570814527423731704356798070e+309", // overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float32 from json.Number greater than float64", func(t *testing.T) {
|
||||||
|
type inner struct {
|
||||||
|
Value float64 `key:"float"`
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{
|
||||||
|
"float": json.Number("1.79769313486231570814527423731704356798070e+309"), // overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
var in inner
|
||||||
|
assert.Error(t, UnmarshalKey(m, &in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalBoolSliceRequired(t *testing.T) {
|
func TestUnmarshalBoolSliceRequired(t *testing.T) {
|
||||||
type inner struct {
|
type inner struct {
|
||||||
Bools []bool `key:"bools"`
|
Bools []bool `key:"bools"`
|
||||||
@@ -1119,16 +1257,20 @@ func TestUnmarshalFloat(t *testing.T) {
|
|||||||
type inner struct {
|
type inner struct {
|
||||||
Float32 float32 `key:"float32"`
|
Float32 float32 `key:"float32"`
|
||||||
Float32Str float32 `key:"float32str,string"`
|
Float32Str float32 `key:"float32str,string"`
|
||||||
|
Float32Num float32 `key:"float32num"`
|
||||||
Float64 float64 `key:"float64"`
|
Float64 float64 `key:"float64"`
|
||||||
Float64Str float64 `key:"float64str,string"`
|
Float64Str float64 `key:"float64str,string"`
|
||||||
|
Float64Num float64 `key:"float64num"`
|
||||||
DefaultFloat float32 `key:"defaultfloat,default=5.5"`
|
DefaultFloat float32 `key:"defaultfloat,default=5.5"`
|
||||||
Optional float32 `key:",optional"`
|
Optional float32 `key:",optional"`
|
||||||
}
|
}
|
||||||
m := map[string]any{
|
m := map[string]any{
|
||||||
"float32": float32(1.5),
|
"float32": float32(1.5),
|
||||||
"float32str": "2.5",
|
"float32str": "2.5",
|
||||||
"float64": float64(3.5),
|
"float32num": json.Number("2.6"),
|
||||||
|
"float64": 3.5,
|
||||||
"float64str": "4.5",
|
"float64str": "4.5",
|
||||||
|
"float64num": json.Number("4.6"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var in inner
|
var in inner
|
||||||
@@ -1136,8 +1278,10 @@ func TestUnmarshalFloat(t *testing.T) {
|
|||||||
if ast.NoError(UnmarshalKey(m, &in)) {
|
if ast.NoError(UnmarshalKey(m, &in)) {
|
||||||
ast.Equal(float32(1.5), in.Float32)
|
ast.Equal(float32(1.5), in.Float32)
|
||||||
ast.Equal(float32(2.5), in.Float32Str)
|
ast.Equal(float32(2.5), in.Float32Str)
|
||||||
|
ast.Equal(float32(2.6), in.Float32Num)
|
||||||
ast.Equal(3.5, in.Float64)
|
ast.Equal(3.5, in.Float64)
|
||||||
ast.Equal(4.5, in.Float64Str)
|
ast.Equal(4.5, in.Float64Str)
|
||||||
|
ast.Equal(4.6, in.Float64Num)
|
||||||
ast.Equal(float32(5.5), in.DefaultFloat)
|
ast.Equal(float32(5.5), in.DefaultFloat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
errUnsupportedType = errors.New("unsupported type on setting field value")
|
errUnsupportedType = errors.New("unsupported type on setting field value")
|
||||||
errNumberOverflow = errors.New("integer overflow")
|
|
||||||
errNumberRange = errors.New("wrong number range setting")
|
errNumberRange = errors.New("wrong number range setting")
|
||||||
optionsCache = make(map[string]optionsCacheValue)
|
optionsCache = make(map[string]optionsCacheValue)
|
||||||
cacheLock sync.RWMutex
|
cacheLock sync.RWMutex
|
||||||
@@ -43,6 +42,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
integer interface {
|
||||||
|
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
|
||||||
|
}
|
||||||
|
|
||||||
optionsCacheValue struct {
|
optionsCacheValue struct {
|
||||||
key string
|
key string
|
||||||
options *fieldOptions
|
options *fieldOptions
|
||||||
@@ -104,21 +107,32 @@ func convertTypeFromString(kind reflect.Kind, str string) (any, error) {
|
|||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
intValue, err := strconv.ParseInt(str, 10, 64)
|
intValue, err := strconv.ParseInt(str, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("the value %q cannot be parsed as int", str)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return intValue, nil
|
return intValue, nil
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
uintValue, err := strconv.ParseUint(str, 10, 64)
|
uintValue, err := strconv.ParseUint(str, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("the value %q cannot be parsed as uint", str)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return uintValue, nil
|
return uintValue, nil
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32:
|
||||||
floatValue, err := strconv.ParseFloat(str, 64)
|
floatValue, err := strconv.ParseFloat(str, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("the value %q cannot be parsed as float", str)
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if floatValue > math.MaxFloat32 {
|
||||||
|
return 0, float32OverflowError(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return floatValue, nil
|
||||||
|
case reflect.Float64:
|
||||||
|
floatValue, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return floatValue, nil
|
return floatValue, nil
|
||||||
@@ -216,6 +230,10 @@ func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func intOverflowError[T integer](v T, kind reflect.Kind) error {
|
||||||
|
return fmt.Errorf("parsing \"%d\" as %s: value out of range", v, kind.String())
|
||||||
|
}
|
||||||
|
|
||||||
func isLeftInclude(b byte) (bool, error) {
|
func isLeftInclude(b byte) (bool, error) {
|
||||||
switch b {
|
switch b {
|
||||||
case '[':
|
case '[':
|
||||||
@@ -238,6 +256,10 @@ func isRightInclude(b byte) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func float32OverflowError(str string) error {
|
||||||
|
return fmt.Errorf("parsing %q as float32: value out of range", str)
|
||||||
|
}
|
||||||
|
|
||||||
func maybeNewValue(fieldType reflect.Type, value reflect.Value) {
|
func maybeNewValue(fieldType reflect.Type, value reflect.Value) {
|
||||||
if fieldType.Kind() == reflect.Ptr && value.IsNil() {
|
if fieldType.Kind() == reflect.Ptr && value.IsNil() {
|
||||||
value.Set(reflect.New(value.Type().Elem()))
|
value.Set(reflect.New(value.Type().Elem()))
|
||||||
@@ -486,7 +508,7 @@ func parseSegments(val string) []string {
|
|||||||
func setIntValue(value reflect.Value, v any, min, max int64) error {
|
func setIntValue(value reflect.Value, v any, min, max int64) error {
|
||||||
iv := v.(int64)
|
iv := v.(int64)
|
||||||
if iv < min || iv > max {
|
if iv < min || iv > max {
|
||||||
return errNumberOverflow
|
return intOverflowError(iv, value.Kind())
|
||||||
}
|
}
|
||||||
|
|
||||||
value.SetInt(iv)
|
value.SetInt(iv)
|
||||||
@@ -534,7 +556,7 @@ func setMatchedPrimitiveValue(kind reflect.Kind, value reflect.Value, v any) err
|
|||||||
func setUintValue(value reflect.Value, v any, boundary uint64) error {
|
func setUintValue(value reflect.Value, v any, boundary uint64) error {
|
||||||
iv := v.(uint64)
|
iv := v.(uint64)
|
||||||
if iv > boundary {
|
if iv > boundary {
|
||||||
return errNumberOverflow
|
return intOverflowError(iv, value.Kind())
|
||||||
}
|
}
|
||||||
|
|
||||||
value.SetUint(iv)
|
value.SetUint(iv)
|
||||||
@@ -615,7 +637,8 @@ func usingDifferentKeys(key string, field reflect.StructField) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string, opts *fieldOptionsWithContext) error {
|
func validateAndSetValue(kind reflect.Kind, value reflect.Value, str string,
|
||||||
|
opts *fieldOptionsWithContext) error {
|
||||||
if !value.CanSet() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user