fix(encode): reject a nil MarshalTOML result
Assisted-by: GLM 5.3
This commit is contained in:
@@ -45,6 +45,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- A `MarshalTOML` result of `nil` with a nil error fails the marshal with
|
||||||
|
`MarshalTOML returned a nil value`. The field silently vanished before, and
|
||||||
|
inside a value array the nil result reached reflection as a zero value and
|
||||||
|
panicked.
|
||||||
- Decoding into a struct that embeds a pointer to itself terminates. The
|
- Decoding into a struct that embeds a pointer to itself terminates. The
|
||||||
schema walk recursed through the embedded type forever, so such a
|
schema walk recursed through the embedded type forever, so such a
|
||||||
`Unmarshal` call hung the process; the walk now tracks the struct types on
|
`Unmarshal` call hung the process; the walk now tracks the struct types on
|
||||||
|
|||||||
+4
-1
@@ -309,7 +309,10 @@ The returned value is encoded as if it had been passed in place of the
|
|||||||
receiver, so it may be a scalar, a slice, an array of tables, or another
|
receiver, so it may be a scalar, a slice, an array of tables, or another
|
||||||
struct or map, including the `Marshaler` result of another type; the encoder
|
struct or map, including the `Marshaler` result of another type; the encoder
|
||||||
recurses. An error returned from `MarshalTOML` fails the marshal wrapped with
|
recurses. An error returned from `MarshalTOML` fails the marshal wrapped with
|
||||||
the key path, for example `interpres: server.port: bad timestamp`.
|
the key path, for example `interpres: server.port: bad timestamp`. A result
|
||||||
|
of `nil` with a nil error fails the same way with
|
||||||
|
`MarshalTOML returned a nil value`: nil has no TOML representation, so
|
||||||
|
dropping the field silently is not an option.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Port int
|
type Port int
|
||||||
|
|||||||
@@ -300,6 +300,12 @@ func buildMapDoc(v reflect.Value, doc *tomlDoc, ctx string) error {
|
|||||||
|
|
||||||
// --- reflection walk: field dispatch -------------------------------------
|
// --- reflection walk: field dispatch -------------------------------------
|
||||||
|
|
||||||
|
// errNilMarshalTOML reports a Marshaler whose method returned a nil value
|
||||||
|
// with no error. nil has no TOML representation, so dropping the field
|
||||||
|
// silently or panicking on the invalid reflect.Value would both hide the
|
||||||
|
// contract violation.
|
||||||
|
var errNilMarshalTOML = errors.New("MarshalTOML returned a nil value")
|
||||||
|
|
||||||
func addField(doc *tomlDoc, name string, v reflect.Value, ctx string) error {
|
func addField(doc *tomlDoc, name string, v reflect.Value, ctx string) error {
|
||||||
if v.CanInterface() {
|
if v.CanInterface() {
|
||||||
if m, ok := v.Interface().(Marshaler); ok {
|
if m, ok := v.Interface().(Marshaler); ok {
|
||||||
@@ -307,6 +313,9 @@ func addField(doc *tomlDoc, name string, v reflect.Value, ctx string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return &EncodeError{Path: joinKey(ctx, name), Err: err}
|
return &EncodeError{Path: joinKey(ctx, name), Err: err}
|
||||||
}
|
}
|
||||||
|
if mv == nil {
|
||||||
|
return &EncodeError{Path: joinKey(ctx, name), Err: errNilMarshalTOML}
|
||||||
|
}
|
||||||
v = reflect.ValueOf(mv)
|
v = reflect.ValueOf(mv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,6 +457,9 @@ func addArrayValue(doc *tomlDoc, name string, v reflect.Value, ctx string) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return &EncodeError{Path: fmt.Sprintf("%s[%d]", joinKey(ctx, name), i), Err: err}
|
return &EncodeError{Path: fmt.Sprintf("%s[%d]", joinKey(ctx, name), i), Err: err}
|
||||||
}
|
}
|
||||||
|
if mv == nil {
|
||||||
|
return &EncodeError{Path: fmt.Sprintf("%s[%d]", joinKey(ctx, name), i), Err: errNilMarshalTOML}
|
||||||
|
}
|
||||||
ev = reflect.ValueOf(mv)
|
ev = reflect.ValueOf(mv)
|
||||||
ev = followPtr(ev)
|
ev = followPtr(ev)
|
||||||
}
|
}
|
||||||
@@ -474,7 +486,14 @@ func normaliseValue(v reflect.Value) (any, error) {
|
|||||||
}
|
}
|
||||||
if v.CanInterface() {
|
if v.CanInterface() {
|
||||||
if m, ok := v.Interface().(Marshaler); ok {
|
if m, ok := v.Interface().(Marshaler); ok {
|
||||||
return m.MarshalTOML()
|
mv, err := m.MarshalTOML()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if mv == nil {
|
||||||
|
return nil, errNilMarshalTOML
|
||||||
|
}
|
||||||
|
return mv, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The datetime structs are TOML scalars; the emitter renders each of them.
|
// The datetime structs are TOML scalars; the emitter renders each of them.
|
||||||
|
|||||||
@@ -383,6 +383,39 @@ func TestMarshalerErrorPropagates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nilMarshalerFunc is a Marshaler whose method returns nil with no error.
|
||||||
|
type nilMarshalerFunc struct{}
|
||||||
|
|
||||||
|
func (nilMarshalerFunc) MarshalTOML() (any, error) { return nil, nil }
|
||||||
|
|
||||||
|
func TestMarshalRejectsNilMarshalerResult(t *testing.T) {
|
||||||
|
// nil has no TOML representation, so a MarshalTOML result of nil is an
|
||||||
|
// error, not a silently dropped field.
|
||||||
|
_, err := Marshal(struct {
|
||||||
|
F nilMarshalerFunc `toml:"f"`
|
||||||
|
}{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error for a nil MarshalTOML result")
|
||||||
|
}
|
||||||
|
ee, ok := errors.AsType[*EncodeError](err)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected an *EncodeError, got %T: %v", err, err)
|
||||||
|
}
|
||||||
|
if ee.Path != "f" {
|
||||||
|
t.Fatalf("Path = %q, want %q", ee.Path, "f")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside a value array the nil result used to reach reflection as a zero
|
||||||
|
// Value and panic.
|
||||||
|
_, err = Marshal(map[string]any{"arr": []any{1, nilMarshalerFunc{}}})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error for a nil MarshalTOML result in an array")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "MarshalTOML returned a nil value") {
|
||||||
|
t.Errorf("err = %v, want the nil-result message", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Two fields that resolve to one TOML key must marshal as one key, resolved
|
// Two fields that resolve to one TOML key must marshal as one key, resolved
|
||||||
// the way the decoder resolves it, or the output would carry a duplicate key
|
// the way the decoder resolves it, or the output would carry a duplicate key
|
||||||
// and never re-parse.
|
// and never re-parse.
|
||||||
|
|||||||
Reference in New Issue
Block a user