Fix panic when unmarshaling datetime values to incompatible types (#1028) (#1029)

Return a type mismatch error instead of panicking when datetime values
(DateTime, LocalDate, LocalTime, LocalDateTime) are unmarshaled into
incompatible Go types. This makes the decoder safer for processing
untrusted TOML input.

https://claude.ai/code/session_011jwvtDS5M2KncLrqJpgMr5

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Pelletier
2026-01-23 22:04:40 -05:00
committed by GitHub
parent 4a1b05ca08
commit 2edc61f171
2 changed files with 55 additions and 9 deletions
+14 -9
View File
@@ -852,6 +852,9 @@ func (d *decoder) unmarshalDateTime(value *unstable.Node, v reflect.Value) error
return err return err
} }
if v.Kind() != reflect.Interface && v.Type() != timeType {
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("datetime", v.Type()))
}
v.Set(reflect.ValueOf(dt)) v.Set(reflect.ValueOf(dt))
return nil return nil
} }
@@ -862,14 +865,14 @@ func (d *decoder) unmarshalLocalDate(value *unstable.Node, v reflect.Value) erro
return err return err
} }
if v.Kind() != reflect.Interface && v.Type() != timeType {
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local date", v.Type()))
}
if v.Type() == timeType { if v.Type() == timeType {
cast := ld.AsTime(time.Local) v.Set(reflect.ValueOf(ld.AsTime(time.Local)))
v.Set(reflect.ValueOf(cast))
return nil return nil
} }
v.Set(reflect.ValueOf(ld)) v.Set(reflect.ValueOf(ld))
return nil return nil
} }
@@ -883,6 +886,9 @@ func (d *decoder) unmarshalLocalTime(value *unstable.Node, v reflect.Value) erro
return unstable.NewParserError(rest, "extra characters at the end of a local time") return unstable.NewParserError(rest, "extra characters at the end of a local time")
} }
if v.Kind() != reflect.Interface {
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local time", v.Type()))
}
v.Set(reflect.ValueOf(lt)) v.Set(reflect.ValueOf(lt))
return nil return nil
} }
@@ -897,15 +903,14 @@ func (d *decoder) unmarshalLocalDateTime(value *unstable.Node, v reflect.Value)
return unstable.NewParserError(rest, "extra characters at the end of a local date time") return unstable.NewParserError(rest, "extra characters at the end of a local date time")
} }
if v.Kind() != reflect.Interface && v.Type() != timeType {
return unstable.NewParserError(d.p.Raw(value.Raw), "%s", d.typeMismatchString("local datetime", v.Type()))
}
if v.Type() == timeType { if v.Type() == timeType {
cast := ldt.AsTime(time.Local) v.Set(reflect.ValueOf(ldt.AsTime(time.Local)))
v.Set(reflect.ValueOf(cast))
return nil return nil
} }
v.Set(reflect.ValueOf(ldt)) v.Set(reflect.ValueOf(ldt))
return nil return nil
} }
+41
View File
@@ -4344,3 +4344,44 @@ value = "b"
}, },
}, cfg) }, cfg)
} }
func TestIssue1028(t *testing.T) {
// Datetime values assigned to incompatible types should return an error,
// not panic.
type Item struct {
Name string `toml:"name"`
}
type Config struct {
Items map[string]Item `toml:"items"`
}
// Error: "cannot decode TOML datetime into struct field Config.Items of type map[string]Item"
t.Run("OffsetDateTime", func(t *testing.T) {
var c Config
err := toml.Unmarshal([]byte(`items = 2023-01-01T10:20:30Z`), &c)
assert.Error(t, err)
})
// Error: "cannot decode TOML local datetime into struct field Config.Items of type map[string]Item"
t.Run("LocalDateTime", func(t *testing.T) {
var c Config
err := toml.Unmarshal([]byte(`items = 2023-01-01T10:20:30`), &c)
assert.Error(t, err)
})
// Error: "cannot decode TOML local date into struct field Config.Items of type map[string]Item"
t.Run("LocalDate", func(t *testing.T) {
var c Config
err := toml.Unmarshal([]byte(`items = 2023-01-01`), &c)
assert.Error(t, err)
})
// Error: "cannot decode TOML local time into struct field Config.Items of type map[string]Item"
t.Run("LocalTime", func(t *testing.T) {
var c Config
err := toml.Unmarshal([]byte(`items = 10:20:30`), &c)
assert.Error(t, err)
})
}