fix(decode): terminate the schema walk on a self-embedded pointer struct
Assisted-by: GLM 5.3
This commit is contained in:
@@ -41,6 +41,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Decoding into a struct that embeds a pointer to itself terminates. The
|
||||||
|
schema walk recursed through the embedded type forever, so such a
|
||||||
|
`Unmarshal` call hung the process; the walk now tracks the struct types on
|
||||||
|
the current path and stops when one repeats.
|
||||||
- An array-of-tables header whose path runs through an inline table
|
- An array-of-tables header whose path runs through an inline table
|
||||||
(`a = {b = {}}` followed by `[[a.b.c]]`) is rejected. The frozen-inline-table
|
(`a = {b = {}}` followed by `[[a.b.c]]`) is rejected. The frozen-inline-table
|
||||||
check covered `[table]` headers and dotted keys but not the intermediate
|
check covered `[table]` headers and dotted keys but not the intermediate
|
||||||
|
|||||||
@@ -255,8 +255,15 @@ type structSchema struct {
|
|||||||
|
|
||||||
func newStructSchema(t reflect.Type) structSchema {
|
func newStructSchema(t reflect.Type) structSchema {
|
||||||
s := structSchema{byName: make(map[string]structFieldLoc, t.NumField())}
|
s := structSchema{byName: make(map[string]structFieldLoc, t.NumField())}
|
||||||
|
// A struct may embed a pointer to itself, which is legal Go, so the walk
|
||||||
|
// tracks the struct types on the current path and stops when one repeats;
|
||||||
|
// without the guard the recursion never terminates. A self-promoted key
|
||||||
|
// always loses to the shallower original, so skipping it changes nothing.
|
||||||
|
visiting := map[reflect.Type]bool{}
|
||||||
var walk func(t reflect.Type, prefix []int, depth int)
|
var walk func(t reflect.Type, prefix []int, depth int)
|
||||||
walk = func(t reflect.Type, prefix []int, depth int) {
|
walk = func(t reflect.Type, prefix []int, depth int) {
|
||||||
|
visiting[t] = true
|
||||||
|
defer delete(visiting, t)
|
||||||
for i := range t.NumField() {
|
for i := range t.NumField() {
|
||||||
f := t.Field(i)
|
f := t.Field(i)
|
||||||
if f.PkgPath != "" { // unexported
|
if f.PkgPath != "" { // unexported
|
||||||
@@ -277,7 +284,9 @@ func newStructSchema(t reflect.Type) structSchema {
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case ft.Kind() == reflect.Struct && !isScalarStruct(ft):
|
case ft.Kind() == reflect.Struct && !isScalarStruct(ft):
|
||||||
walk(ft, path, depth+1)
|
if !visiting[ft] {
|
||||||
|
walk(ft, path, depth+1)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
case ft.Kind() == reflect.Map && ft.Key().Kind() == reflect.String:
|
case ft.Kind() == reflect.Map && ft.Key().Kind() == reflect.String:
|
||||||
s.embedMaps = append(s.embedMaps, path)
|
s.embedMaps = append(s.embedMaps, path)
|
||||||
|
|||||||
@@ -538,6 +538,34 @@ func TestUnmarshalEmbeddedPointerStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A struct embedding a pointer to itself is legal Go; decoding into it must
|
||||||
|
// terminate. The schema walk used to recurse through the embedded type
|
||||||
|
// forever.
|
||||||
|
func TestUnmarshalSelfEmbeddedPointerStructTerminates(t *testing.T) {
|
||||||
|
type SelfLink struct {
|
||||||
|
*SelfLink
|
||||||
|
X int `toml:"x"`
|
||||||
|
Y string `toml:"y"`
|
||||||
|
}
|
||||||
|
var n SelfLink
|
||||||
|
if err := Unmarshal([]byte("x = 1\ny = \"s\"\n"), &n); err != nil {
|
||||||
|
t.Fatalf("unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if n.X != 1 || n.Y != "s" {
|
||||||
|
t.Fatalf("decoded: %+v", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A nil self pointer on the encode side stays skippable, as any nil
|
||||||
|
// embedded pointer is.
|
||||||
|
out, err := Marshal(SelfLink{X: 2})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal: %v", err)
|
||||||
|
}
|
||||||
|
if want := "x = 2\ny = \"\"\n"; string(out) != want {
|
||||||
|
t.Errorf("output mismatch:\ngot: %q\nwant: %q", out, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type RoundTripExtra map[string]int
|
type RoundTripExtra map[string]int
|
||||||
|
|
||||||
type RoundTripMapCfg struct {
|
type RoundTripMapCfg struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user