Files
toml/integration_hardening_test.go
T
2026-09-28 14:05:12 -04:00

142 lines
4.2 KiB
Go

package toml
import (
"math"
"reflect"
"strconv"
"strings"
"testing"
)
func TestDecodeFixedArrayRoundTrip(t *testing.T) {
type Item struct {
Value uint16 `toml:"value"`
}
type Config struct {
Numbers [3]int64 `toml:"numbers"`
Items [2]*Item `toml:"items"`
Matrix [2][2]int8 `toml:"matrix"`
Empty [0]string `toml:"empty"`
}
want := Config{
Numbers: [3]int64{math.MinInt64, 0, math.MaxInt64},
Items: [2]*Item{{1}, {math.MaxUint16}},
Matrix: [2][2]int8{{-128, 127}, {0, 1}},
}
data, err := Marshal(want)
if err != nil {
t.Fatal(err)
}
var got Config
if err := Unmarshal(data, &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got %#v, want %#v", got, want)
}
for _, input := range []any{[]any{1}, []any{1, 2, 3}, []any{1, 256}} {
dst := [2]uint8{7, 8}
if err := Decode(input, &dst); err == nil || dst != [2]uint8{7, 8} {
t.Fatalf("invalid array changed target: %v (%v)", dst, err)
}
}
}
func TestDecodePointerDefaultsAndRollback(t *testing.T) {
type Settings struct {
Port uint16 `toml:"port"`
Name string `toml:"name"`
}
original := &Settings{Port: 8080, Name: "default"}
dst := original
if err := Decode(map[string]any{"name": "updated"}, &dst); err != nil {
t.Fatal(err)
}
if dst.Port != 8080 || dst.Name != "updated" || dst == original || original.Name != "default" {
t.Fatalf("defaults or original pointer changed: dst=%+v original=%+v", dst, original)
}
type Config struct {
Settings *Settings
Map map[string]int
Slice []int
Limit uint8
}
cfg := Config{original, map[string]int{"old": 1}, []int{2}, 3}
err := Decode(map[string]any{
"Settings": map[string]any{"port": 9090},
"Map": map[string]any{"new": 4},
"Slice": []any{5},
"Limit": 256,
}, &cfg)
if err == nil || cfg.Settings != original || *original != (Settings{8080, "default"}) ||
!reflect.DeepEqual(cfg.Map, map[string]int{"old": 1}) || !reflect.DeepEqual(cfg.Slice, []int{2}) || cfg.Limit != 3 {
t.Fatalf("partial update: %+v (%v)", cfg, err)
}
}
func TestDecodeCyclicInputErrors(t *testing.T) {
cycle := map[string]any{}
cycle["self"] = cycle
var number uint64
if err := Decode(cycle, &number); err == nil {
t.Fatal("accepted cyclic map as unsigned number")
}
type Node struct {
Next *Node `toml:"self"`
}
var node Node
if err := Decode(cycle, &node); err == nil || node.Next != nil {
t.Fatalf("recursive decode must fail without publication: %v", err)
}
}
func TestNumericKeyRangeIndependence(t *testing.T) {
for _, key := range []string{"0", "-1", "+1", "9223372036854775808", "-9223372036854775809", strings.Repeat("9", 100)} {
if _, err := Marshal(map[string]any{key: 1}); err == nil {
t.Errorf("encoded numeric key %q", key)
}
if _, err := NewParser([]byte(strconv.Quote(key) + " = 1")).Parse(); err == nil {
t.Errorf("parsed numeric key %q", key)
}
}
}
func TestDecodeIntegerWidths(t *testing.T) {
for _, typ := range []reflect.Type{
reflect.TypeFor[int](), reflect.TypeFor[int8](), reflect.TypeFor[int16](), reflect.TypeFor[int32](), reflect.TypeFor[int64](),
reflect.TypeFor[uint](), reflect.TypeFor[uint8](), reflect.TypeFor[uint16](), reflect.TypeFor[uint32](), reflect.TypeFor[uint64](),
} {
t.Run(typ.String(), func(t *testing.T) {
bits := typ.Bits()
signed := typ.Kind() >= reflect.Int && typ.Kind() <= reflect.Int64
max := uint64(math.MaxUint64) >> (64 - bits)
if signed {
max >>= 1
}
dst := reflect.New(typ)
if err := Decode(max, dst.Interface()); err != nil {
t.Fatal(err)
}
if max < math.MaxUint64 {
before := dst.Elem().Interface()
if err := Decode(max+1, dst.Interface()); err == nil || dst.Elem().Interface() != before {
t.Fatalf("overflow was accepted or mutated destination: %v", err)
}
}
if signed {
min := -int64(max) - 1
if err := Decode(min, dst.Interface()); err != nil || dst.Elem().Int() != min {
t.Fatalf("minimum changed: %v", err)
}
if min > math.MinInt64 {
if err := Decode(min-1, dst.Interface()); err == nil || dst.Elem().Int() != min {
t.Fatalf("underflow accepted: %v", err)
}
}
} else if err := Decode(int64(-1), dst.Interface()); err == nil {
t.Fatal("negative signed integer accepted")
}
})
}
}