Reject unsupported inline pointer schemas safely
This commit is contained in:
@@ -589,7 +589,9 @@ Unexported fields and `yaml:"-"` are ignored. `omitempty` is accepted but does n
|
||||
change binding or projection. An explicit `yaml:",inline"` flattens a struct
|
||||
or pointer-to-struct, or designates at most one string-keyed inline map for otherwise
|
||||
unmatched names. Ambiguous names, named inline fields, multiple inline maps, and
|
||||
unsupported or malformed tag options are schema errors. Embedded fields are ordinary
|
||||
unsupported or malformed tag options are schema errors. Inline pointers must
|
||||
terminate in a struct; pointer-to-map and recursive pointer-only inline declarations
|
||||
are rejected before the defaults factory runs. Embedded fields are ordinary
|
||||
named fields unless explicitly inlined. `configkit:"secret"` is the only current
|
||||
ConfigKit field tag and marks its bound field or container for secret classification
|
||||
and redacted inspection.
|
||||
|
||||
@@ -188,6 +188,12 @@ existing descendants remain explainable after parent replacement.
|
||||
|
||||
## 7. P2 — Reject unsupported inline pointer-to-map schemas safely
|
||||
|
||||
Status: complete. Schema construction rejects inline map pointers, including
|
||||
multiple pointer levels, and detects nonterminating inline pointer chains. Public
|
||||
regressions verify safe errors before defaults run; supported inline coverage stays
|
||||
green. Full tests pass with the issue-8 TMPDIR workaround; vet, build and whitespace
|
||||
checks pass.
|
||||
|
||||
Evidence: [schema.go](../../schema.go), `describeFields`, dereferences inline field
|
||||
types before choosing the map branch, then stores a map descriptor for a field
|
||||
whose actual reflected value is a pointer. [projection.go](../../projection.go)
|
||||
|
||||
@@ -212,9 +212,17 @@ func (b *schemaBuilder) describeFields(descriptor *typeDescriptor, current refle
|
||||
return &schemaIssue{code: CodeSchemaTag, path: path.clone(), message: "inline field cannot declare a YAML name"}
|
||||
}
|
||||
inlineType := field.Type
|
||||
seenPointers := make(map[reflect.Type]bool)
|
||||
for inlineType.Kind() == reflect.Pointer {
|
||||
if seenPointers[inlineType] {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline pointer chain must terminate in a struct"}
|
||||
}
|
||||
seenPointers[inlineType] = true
|
||||
inlineType = inlineType.Elem()
|
||||
}
|
||||
if inlineType.Kind() == reflect.Map && field.Type.Kind() == reflect.Pointer {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline maps cannot be pointers"}
|
||||
}
|
||||
switch inlineType.Kind() {
|
||||
case reflect.Struct:
|
||||
if b.building[inlineType] {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package configkit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -302,3 +303,40 @@ func mustPath(t *testing.T, pointer string) Path {
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func TestInlineMapPointersFailBeforeDefaults(t *testing.T) {
|
||||
type cfg struct {
|
||||
Extra *map[string]int `yaml:",inline"`
|
||||
}
|
||||
for _, nonNil := range []bool{false, true} {
|
||||
called := false
|
||||
_, err := Load(NoSource(), Options[cfg]{Definition: Definition[cfg]{Defaults: func() cfg {
|
||||
called = true
|
||||
if nonNil {
|
||||
m := map[string]int{"value": 1}
|
||||
return cfg{Extra: &m}
|
||||
}
|
||||
return cfg{}
|
||||
}}})
|
||||
var loadErr *LoadError
|
||||
if !errors.As(err, &loadErr) || called {
|
||||
t.Fatalf("invalid inline schema reached defaults: called=%v error=%v", called, err)
|
||||
}
|
||||
if len(loadErr.Diagnostics()) != 1 || loadErr.Diagnostics()[0].Code != CodeSchemaInline {
|
||||
t.Fatal("missing inline schema diagnostic")
|
||||
}
|
||||
}
|
||||
type pointer *pointer
|
||||
type recursive struct {
|
||||
Extra pointer `yaml:",inline"`
|
||||
}
|
||||
if _, err := Load[recursive](NoSource()); err == nil {
|
||||
t.Fatal("recursive inline pointer was accepted")
|
||||
}
|
||||
type double struct {
|
||||
Extra **map[string]int `yaml:",inline"`
|
||||
}
|
||||
if _, err := Load[double](NoSource()); err == nil {
|
||||
t.Fatal("double inline map pointer was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user