From 3ba123efba846422b7585f66c3474a93814446ab Mon Sep 17 00:00:00 2001 From: Rafael Passos Date: Thu, 17 Sep 2026 07:42:12 -0300 Subject: [PATCH 1/2] test: regression test for mapping into unexported fields Validate the behaviour of returning an error instead of panicking when trying to set a value for an unexported variable. Decoding a map into a struct with an embedded pointer to an unexported type tagged with `mapstructure:",squash"` currently panics with: `reflect: reflect.Value.Set using value obtained using unexported field` Add TestDecode_EmbeddedUnexportedPointerSquash_FromMapToStruct to lock in the expected behaviour: Decode returns an error containing "unsupported type for squash" rather than panicking. This behaviour change was noticed by the `Test_EmbeddedPrivateStructPointer` in anchore/fangs: https://github.com/anchore/fangs/blob/d9f7358ffc5f9c1935fd3bbc2e5a16fb92ff5b6c/load_test.go#L731 Signed-off-by: Rafael Passos --- mapstructure_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/mapstructure_test.go b/mapstructure_test.go index baf40dfe..96ee5e97 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -917,6 +917,33 @@ func TestDecode_EmbeddedPointerSquash_WithoutPreInitializedStructs_FromMapToStru } } +type EmbeddedUnexportedPointerSquash struct { + *embeddedUnexported `mapstructure:",squash"` + Vunique string +} + +type embeddedUnexported struct { + Vstring string +} + +func TestDecode_EmbeddedUnexportedPointerSquash_FromMapToStruct(t *testing.T) { + t.Parallel() + + input := map[string]any{ + "Vstring": "foo", + "Vunique": "bar", + } + + result := EmbeddedUnexportedPointerSquash{} + err := Decode(input, &result) + if err == nil { + t.Fatal("expected an error decoding into an unexported embedded pointer squash, got nil") + } + if !strings.Contains(err.Error(), "unsupported type for squash") { + t.Fatalf("unexpected error message: %s", err) + } +} + func TestDecode_EmbeddedPointerSquashWithNestedMapstructure_FromStructToMap(t *testing.T) { t.Parallel() From d04dd307cfe3dc9d52b478930d4fe011fad00e36 Mon Sep 17 00:00:00 2001 From: Rafael Passos Date: Thu, 17 Sep 2026 09:05:15 -0300 Subject: [PATCH 2/2] fix: don't panic when squashing unexported embedded pointers Guard the allocation with CanSet() and return the previous "unsupported type for squash" error for fields that cannot be set, rather than panicking. Signed-off-by: Rafael Passos --- mapstructure.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mapstructure.go b/mapstructure.go index 9087fd96..43f325a0 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -1625,6 +1625,13 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e case reflect.Ptr: if fieldVal.Type().Elem().Kind() == reflect.Struct { if fieldVal.IsNil() { + if !fieldVal.CanSet() { + errs = append(errs, newDecodeError( + name+"."+fieldType.Name, + fmt.Errorf("unsupported type for squash: %s", fieldVal.Kind()), + )) + continue + } fieldVal.Set(reflect.New(fieldVal.Type().Elem())) } structs = append(structs, fieldVal.Elem())