From 397244ca55dcc6649accbc28a9ed49f0caab28d8 Mon Sep 17 00:00:00 2001 From: walle250ai Date: Wed, 29 Apr 2026 21:41:07 +0800 Subject: [PATCH] Fix overflow handling in ToUint8E and ToUint16E - Fix parseUint/parseInt to pass correct bitSize to strconv, so string inputs like '256' correctly return an error for uint8 - Add uint64Overflow and float64Overflow helper functions with per-type range checks (uint8: 0-255, uint16: 0-65535, etc.) - Add overflow checks in toUnsignedNumber for all numeric input types - Fix test framework bug: overflow string tests now run for all uint types, not just uint64 --- number.go | 118 ++++++++++++++++++++++++++++++++++++++++++++++++- number_test.go | 2 +- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/number.go b/number.go index a58dc4d..b3c94f2 100644 --- a/number.go +++ b/number.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "regexp" "strconv" "strings" @@ -202,52 +203,93 @@ func toUnsignedNumber[T Number](i any) (T, bool, bool) { if s < 0 { return 0, false, false } + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } return T(s), true, true case int8: if s < 0 { return 0, false, false } + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } return T(s), true, true case int16: if s < 0 { return 0, false, false } + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } return T(s), true, true case int32: if s < 0 { return 0, false, false } + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } return T(s), true, true case int64: if s < 0 { return 0, false, false } + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } return T(s), true, true case uint: + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } + return T(s), true, true case uint8: + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } + return T(s), true, true case uint16: + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } + return T(s), true, true case uint32: + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } + return T(s), true, true case uint64: + if uint64Overflow[T](s) { + return 0, true, false + } + return T(s), true, true case float32: if s < 0 { return 0, false, false } + if float64Overflow[T](float64(s)) { + return 0, true, false + } return T(s), true, true case float64: if s < 0 { return 0, false, false } + if float64Overflow[T](s) { + return 0, true, false + } return T(s), true, true case bool: @@ -262,12 +304,18 @@ func toUnsignedNumber[T Number](i any) (T, bool, bool) { if s < 0 { return 0, false, false } + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } return T(s), true, true case time.Month: if s < 0 { return 0, false, false } + if uint64Overflow[T](uint64(s)) { + return 0, true, false + } return T(s), true, true } @@ -275,6 +323,40 @@ func toUnsignedNumber[T Number](i any) (T, bool, bool) { return 0, true, false } +func uint64Overflow[T Number](v uint64) bool { + var t T + switch any(t).(type) { + case uint8: + return v > math.MaxUint8 + case uint16: + return v > math.MaxUint16 + case uint32: + return v > math.MaxUint32 + case uint64: + return false + case uint: + return v > uint64(^uint(0)) + } + return false +} + +func float64Overflow[T Number](v float64) bool { + var t T + switch any(t).(type) { + case uint8: + return v > float64(math.MaxUint8) || v < 0 + case uint16: + return v > float64(math.MaxUint16) || v < 0 + case uint32: + return v > float64(math.MaxUint32) || v < 0 + case uint64: + return v > float64(math.MaxUint64) || v < 0 + case uint: + return v > float64(^uint(0)) || v < 0 + } + return false +} + func toUnsignedNumberE[T Number](i any, parseFn func(string) (T, error)) (T, error) { n, valid, ok := toUnsignedNumber[T](i) if ok { @@ -405,7 +487,23 @@ func parseNumber[T Number](s string) (T, error) { } func parseInt[T integer](s string) (T, error) { - v, err := strconv.ParseInt(trimDecimal(s), 0, 0) + var t T + var bitSize int + + switch any(t).(type) { + case int: + bitSize = 0 + case int8: + bitSize = 8 + case int16: + bitSize = 16 + case int32: + bitSize = 32 + case int64: + bitSize = 64 + } + + v, err := strconv.ParseInt(trimDecimal(s), 0, bitSize) if err != nil { return 0, err } @@ -414,7 +512,23 @@ func parseInt[T integer](s string) (T, error) { } func parseUint[T unsigned](s string) (T, error) { - v, err := strconv.ParseUint(strings.TrimLeft(trimDecimal(s), "+"), 0, 0) + var t T + var bitSize int + + switch any(t).(type) { + case uint: + bitSize = 0 + case uint8: + bitSize = 8 + case uint16: + bitSize = 16 + case uint32: + bitSize = 32 + case uint64: + bitSize = 64 + } + + v, err := strconv.ParseUint(strings.TrimLeft(trimDecimal(s), "+"), 0, bitSize) if err != nil { return 0, err } diff --git a/number_test.go b/number_test.go index 9d84d18..207846b 100644 --- a/number_test.go +++ b/number_test.go @@ -262,7 +262,7 @@ func generateNumberTestCases(samples []any) []testCase { testCases = append(testCases, testCase{underflowString, zero, true}) } - if kind == reflect.Uint64 && isUint && overflowString != nil { + if isUint && overflowString != nil { testCases = append(testCases, testCase{overflowString, zero, true}) }