lang/funcs: transpose fix panics with null elements

Previously the "transpose" function would panic if any of the given lists
were null or if any of the strings inside the given lists were null.

Null values _are_ invalid in those positions because we can't project null
values into the keys of the resulting map, but we should return explicit
error messages in those cases rather than causing a cty panic.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
Martin Atkins
2025-03-01 07:52:56 -08:00
parent dd50b38c36
commit 97026e4048
3 changed files with 30 additions and 1 deletions

View File

@@ -16,6 +16,8 @@ import (
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/function/stdlib"
"github.com/zclconf/go-cty/cty/gocty"
"github.com/opentofu/opentofu/internal/tfdiags"
)
var LengthFunc = function.New(&function.Spec{
@@ -578,13 +580,22 @@ var TransposeFunc = function.New(&function.Spec{
outputMap := make(map[string]cty.Value)
tmpMap := make(map[string][]string)
path := make(cty.Path, 0, 2) // we'll append a maximum of two path steps in the loop below
for it := inputMap.ElementIterator(); it.Next(); {
inKey, inVal := it.Element()
keyPath := path.Index(inKey)
if inVal.IsNull() {
return cty.DynamicVal, function.NewArgErrorf(0, "cannot use null list for %s", tfdiags.FormatCtyPath(keyPath))
}
for iter := inVal.ElementIterator(); iter.Next(); {
_, val := iter.Element()
idx, val := iter.Element()
idxPath := keyPath.Index(idx)
if !val.Type().Equals(cty.String) {
return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must be a map of lists of strings")
}
if val.IsNull() {
return cty.DynamicVal, function.NewArgErrorf(0, "cannot use null string for %s", tfdiags.FormatCtyPath(idxPath))
}
outKey := val.AsString()
if _, ok := tmpMap[outKey]; !ok {

View File

@@ -1822,6 +1822,23 @@ func TestTranspose(t *testing.T) {
}).WithMarks(cty.NewValueMarks("beep", "boop", "bloop")),
false,
},
{ // Null value must be rejected because map keys cannot be null
cty.MapVal(map[string]cty.Value{
"key1": cty.ListVal([]cty.Value{
cty.StringVal("a"),
cty.NullVal(cty.String),
}),
}),
cty.NilVal,
true,
},
{ // Null list must be rejected, too
cty.MapVal(map[string]cty.Value{
"key1": cty.NullVal(cty.List(cty.String)),
}),
cty.NilVal,
true,
},
}
for _, test := range tests {