fix(decode): overflow-check uint and float32 destinations
Assisted-by: GLM 5.3
This commit is contained in:
+8
-4
@@ -63,14 +63,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
and nested arrays of tables: `[[a]]`, `b.c = 1`, `[[a]]`, `[a.b]` parses, as
|
||||
the TOML examples in the spec shape it. The records of the previous element
|
||||
falsely rejected the same paths in the next one.
|
||||
- `UseLiteralMultiline` falls back to the escaped basic string when the value
|
||||
cannot be carried verbatim by the literal form: a run of three single quotes,
|
||||
a control character, or a lone carriage return. Such values previously
|
||||
produced output that did not re-parse.
|
||||
- `Marshal` returns an error for a table header key or an inline-table key that
|
||||
is not valid UTF-8, the way scalar keys already did, instead of silently
|
||||
emitting corrupt TOML (a header that lost its key, an inline table with a
|
||||
missing key).
|
||||
- `UseLiteralMultiline` falls back to the escaped basic string when the value
|
||||
cannot be carried verbatim by the literal form: a run of three single quotes,
|
||||
a control character, or a lone carriage return. Such values previously
|
||||
produced output that did not re-parse.
|
||||
- Decoding into a `uint` destination checks the type's platform width instead
|
||||
of only the fixed widths, so a 32-bit `uint` no longer truncates silently;
|
||||
decoding a finite float beyond the `float32` range is an overflow error
|
||||
instead of a silent infinity.
|
||||
- Struct fields that resolve to one key at equal depth decode through the
|
||||
field declared later, matching the documented rule; the first one won before.
|
||||
- A float with an exponent marker but no digits (`1e`, `0.0E`) is rejected;
|
||||
|
||||
@@ -5,7 +5,6 @@ package interpres
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -205,21 +204,21 @@ func setInt(dst reflect.Value, v int64) error {
|
||||
if v < 0 {
|
||||
return fmt.Errorf("interpres: cannot assign negative %d to %s", v, dst.Type())
|
||||
}
|
||||
var max uint64
|
||||
switch dst.Kind() {
|
||||
case reflect.Uint8:
|
||||
max = math.MaxUint8
|
||||
case reflect.Uint16:
|
||||
max = math.MaxUint16
|
||||
case reflect.Uint32:
|
||||
max = math.MaxUint32
|
||||
}
|
||||
if max != 0 && uint64(v) > max {
|
||||
// OverflowUint knows every width, uint included on platforms where it
|
||||
// is narrower than uint64; SetUint would silently truncate instead.
|
||||
if dst.OverflowUint(uint64(v)) {
|
||||
return fmt.Errorf("interpres: integer %d overflows %s", v, dst.Type())
|
||||
}
|
||||
dst.SetUint(uint64(v))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
dst.SetFloat(float64(v))
|
||||
// A finite value beyond the float32 range would silently become ±Inf;
|
||||
// infinities and NaN themselves pass through. An int64 never
|
||||
// overflows either float width.
|
||||
f := float64(v)
|
||||
if dst.OverflowFloat(f) {
|
||||
return fmt.Errorf("interpres: integer %d overflows %s", v, dst.Type())
|
||||
}
|
||||
dst.SetFloat(f)
|
||||
default:
|
||||
return fmt.Errorf("interpres: cannot assign integer to %s", dst.Type())
|
||||
}
|
||||
@@ -229,6 +228,9 @@ func setInt(dst reflect.Value, v int64) error {
|
||||
func setFloat(dst reflect.Value, v float64) error {
|
||||
switch dst.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if dst.OverflowFloat(v) {
|
||||
return fmt.Errorf("interpres: float %g overflows %s", v, dst.Type())
|
||||
}
|
||||
dst.SetFloat(v)
|
||||
return nil
|
||||
default:
|
||||
|
||||
@@ -223,6 +223,33 @@ func TestUnmarshalIntToUint64FitsMaxInt64(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalFloat32Overflow(t *testing.T) {
|
||||
// A finite float64 beyond the float32 range must not decode silently as
|
||||
// an infinity.
|
||||
type C struct {
|
||||
X float32 `toml:"x"`
|
||||
}
|
||||
var c C
|
||||
err := Unmarshal([]byte("x = 1e300\n"), &c)
|
||||
if err == nil {
|
||||
t.Fatal("expected overflow error for float32")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "overflow") {
|
||||
t.Errorf("err = %v, want substring 'overflow'", err.Error())
|
||||
}
|
||||
// Infinities themselves pass through, and in-range values are untouched.
|
||||
var ok C
|
||||
if err := Unmarshal([]byte("x = inf\n"), &ok); err != nil {
|
||||
t.Fatalf("inf should decode into float32, got %v", err)
|
||||
}
|
||||
if !math.IsInf(float64(ok.X), 1) {
|
||||
t.Errorf("X = %v, want +Inf", ok.X)
|
||||
}
|
||||
if err := Unmarshal([]byte("x = 1.5\n"), &ok); err != nil || ok.X != 1.5 {
|
||||
t.Fatalf("1.5 should decode into float32, got %v (X=%v)", err, ok.X)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalNegativeIntToUint(t *testing.T) {
|
||||
type C struct {
|
||||
X uint8 `toml:"x"`
|
||||
|
||||
+2
-2
@@ -126,8 +126,8 @@ The decoder converts to the destination type with explicit overflow checks:
|
||||
| Destination kind | Rule |
|
||||
|---|---|
|
||||
| `int`, `int8`, `int16`, `int32`, `int64` | the `int64` value must not overflow the destination |
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | the value must be non-negative; `uint8`, `uint16` and `uint32` enforce their own maxima; `uint64` accepts any non-negative `int64` |
|
||||
| `float32`, `float64` | copied verbatim; an integer also coerces, so TOML `5` decodes into `5.0` |
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | the value must be non-negative and must not overflow the destination's own width, `uint` on a 32-bit platform included; `uint64` accepts any non-negative `int64` |
|
||||
| `float32`, `float64` | copied verbatim, except that a finite value beyond the `float32` range is an overflow error rather than a silent infinity; an integer also coerces, so TOML `5` decodes into `5.0` |
|
||||
| `bool`, `string` | exact kind match only, no coercion across kinds |
|
||||
| `time.Time` | offset date-times only; no implicit conversion to or from the local variants |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user