@@ -19,6 +19,7 @@ import (
1919 "github.com/knadh/koanf/v2"
2020
2121 encoder "go.opentelemetry.io/collector/confmap/internal/mapstructure"
22+ "go.opentelemetry.io/collector/confmap/internal/third_party/composehook"
2223)
2324
2425const (
@@ -234,7 +235,7 @@ func decodeConfig(m *Conf, result any, errorUnused bool, skipTopLevelUnmarshaler
234235 TagName : MapstructureTag ,
235236 WeaklyTypedInput : false ,
236237 MatchName : caseSensitiveMatchName ,
237- DecodeHook : mapstructure .ComposeDecodeHookFunc (
238+ DecodeHook : composehook .ComposeDecodeHookFunc (
238239 useExpandValue (),
239240 expandNilStructPointersHookFunc (),
240241 mapstructure .StringToSliceHookFunc ("," ),
@@ -306,6 +307,23 @@ func isStringyStructure(t reflect.Type) bool {
306307 return false
307308}
308309
310+ // safeWrapDecodeHookFunc wraps a DecodeHookFuncValue to ensure fromVal is a valid `reflect.Value`
311+ // object and therefore it is safe to call `reflect.Value` methods on fromVal.
312+ //
313+ // Use this only if the hook does not need to be called on untyped nil values.
314+ // Typed nil values are safe to call and will be passed to the hook.
315+ // See https://github.com/golang/go/issues/51649
316+ func safeWrapDecodeHookFunc (
317+ f mapstructure.DecodeHookFuncValue ,
318+ ) mapstructure.DecodeHookFuncValue {
319+ return func (fromVal reflect.Value , toVal reflect.Value ) (any , error ) {
320+ if ! fromVal .IsValid () {
321+ return nil , nil
322+ }
323+ return f (fromVal , toVal )
324+ }
325+ }
326+
309327// When a value has been loaded from an external source via a provider, we keep both the
310328// parsed value and the original string value. This allows us to expand the value to its
311329// original string representation when decoding into a string field, and use the original otherwise.
@@ -355,7 +373,7 @@ func useExpandValue() mapstructure.DecodeHookFuncType {
355373// we want an unmarshaled Config to be equivalent to
356374// Config{Thing: &SomeStruct{}} instead of Config{Thing: nil}
357375func expandNilStructPointersHookFunc () mapstructure.DecodeHookFuncValue {
358- return func (from reflect.Value , to reflect.Value ) (any , error ) {
376+ return safeWrapDecodeHookFunc ( func (from reflect.Value , to reflect.Value ) (any , error ) {
359377 // ensure we are dealing with map to map comparison
360378 if from .Kind () == reflect .Map && to .Kind () == reflect .Map {
361379 toElem := to .Type ().Elem ()
@@ -375,7 +393,7 @@ func expandNilStructPointersHookFunc() mapstructure.DecodeHookFuncValue {
375393 }
376394 }
377395 return from .Interface (), nil
378- }
396+ })
379397}
380398
381399// mapKeyStringToMapKeyTextUnmarshalerHookFunc returns a DecodeHookFuncType that checks that a conversion from
@@ -422,7 +440,7 @@ func mapKeyStringToMapKeyTextUnmarshalerHookFunc() mapstructure.DecodeHookFuncTy
422440// unmarshalerEmbeddedStructsHookFunc provides a mechanism for embedded structs to define their own unmarshal logic,
423441// by implementing the Unmarshaler interface.
424442func unmarshalerEmbeddedStructsHookFunc () mapstructure.DecodeHookFuncValue {
425- return func (from reflect.Value , to reflect.Value ) (any , error ) {
443+ return safeWrapDecodeHookFunc ( func (from reflect.Value , to reflect.Value ) (any , error ) {
426444 if to .Type ().Kind () != reflect .Struct {
427445 return from .Interface (), nil
428446 }
@@ -455,14 +473,14 @@ func unmarshalerEmbeddedStructsHookFunc() mapstructure.DecodeHookFuncValue {
455473 }
456474 }
457475 return fromAsMap , nil
458- }
476+ })
459477}
460478
461479// Provides a mechanism for individual structs to define their own unmarshal logic,
462480// by implementing the Unmarshaler interface, unless skipTopLevelUnmarshaler is
463481// true and the struct matches the top level object being unmarshaled.
464482func unmarshalerHookFunc (result any , skipTopLevelUnmarshaler bool ) mapstructure.DecodeHookFuncValue {
465- return func (from reflect.Value , to reflect.Value ) (any , error ) {
483+ return safeWrapDecodeHookFunc ( func (from reflect.Value , to reflect.Value ) (any , error ) {
466484 if ! to .CanAddr () {
467485 return from .Interface (), nil
468486 }
@@ -495,14 +513,14 @@ func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure.
495513 }
496514
497515 return unmarshaler , nil
498- }
516+ })
499517}
500518
501519// marshalerHookFunc returns a DecodeHookFuncValue that checks structs that aren't
502520// the original to see if they implement the Marshaler interface.
503521func marshalerHookFunc (orig any ) mapstructure.DecodeHookFuncValue {
504522 origType := reflect .TypeOf (orig )
505- return func (from reflect.Value , _ reflect.Value ) (any , error ) {
523+ return safeWrapDecodeHookFunc ( func (from reflect.Value , _ reflect.Value ) (any , error ) {
506524 if from .Kind () != reflect .Struct {
507525 return from .Interface (), nil
508526 }
@@ -520,7 +538,7 @@ func marshalerHookFunc(orig any) mapstructure.DecodeHookFuncValue {
520538 return nil , err
521539 }
522540 return conf .ToStringMap (), nil
523- }
541+ })
524542}
525543
526544// Unmarshaler interface may be implemented by types to customize their behavior when being unmarshaled from a Conf.
@@ -562,7 +580,7 @@ type Marshaler interface {
562580// 4. configuration have no `keys` field specified, the output should be default config
563581// - for example, input is {}, then output is Config{ Keys: ["a", "b"]}
564582func zeroSliceHookFunc () mapstructure.DecodeHookFuncValue {
565- return func (from reflect.Value , to reflect.Value ) (any , error ) {
583+ return safeWrapDecodeHookFunc ( func (from reflect.Value , to reflect.Value ) (any , error ) {
566584 if to .CanSet () && to .Kind () == reflect .Slice && from .Kind () == reflect .Slice {
567585 if from .IsNil () {
568586 // input slice is nil, set output slice to nil.
@@ -574,7 +592,7 @@ func zeroSliceHookFunc() mapstructure.DecodeHookFuncValue {
574592 }
575593
576594 return from .Interface (), nil
577- }
595+ })
578596}
579597
580598type moduleFactory [T any , S any ] interface {
0 commit comments