Skip to content

Commit 864b88d

Browse files
jbardinapparentlymart
authored andcommitted
convert: prevent invalid types in dynamicReplace
When converting an unknown map into an object with optional attributes, that object may have attributes which don't match the map element type. This conversion will be valid, but when building Null or Unknown values of that type we need to ensure all attributes have a valid type. The fallthrough path for dynamicReplace when non-primitive types didn't match contained a cty.NilVal element type, which would result in an overall invalid type. We can early returns to ensure there are no uninitialized values, and let the fallback use the original out type. Since at this point the types appear to not be convertible, we can assume they are from attributes which were skipped during conversion due to being optional, otherwise the conversion would not have been valid.
1 parent d279406 commit 864b88d

File tree

2 files changed

+38
-15
lines changed

2 files changed

+38
-15
lines changed

cty/convert/conversion_dynamic.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ func dynamicPassthrough(in cty.Value, path cty.Path) (cty.Value, error) {
4040
// perspective, and will panic if it finds that they are not. For example if
4141
// in is an object and out is a map, this function will still attempt to iterate
4242
// through both as if they were the same.
43+
// While the outermost in and out types may be compatible from a Convert
44+
// perspective, inner types may not match when converting between maps and
45+
// objects with optional attributes when the optional attributes don't match
46+
// the map element type. Therefor in the case of a non-primitive type mismatch,
47+
// we have to assume conversion was possible and pass the out type through.
4348
func dynamicReplace(in, out cty.Type) cty.Type {
4449
if in == cty.DynamicPseudoType || in == cty.NilType {
4550
// Short circuit this case, there's no point worrying about this if in
@@ -56,11 +61,9 @@ func dynamicReplace(in, out cty.Type) cty.Type {
5661
// return it unchanged.
5762
return out
5863
case out.IsMapType():
59-
var elemType cty.Type
60-
6164
// Maps are compatible with other maps or objects.
6265
if in.IsMapType() {
63-
elemType = dynamicReplace(in.ElementType(), out.ElementType())
66+
return cty.Map(dynamicReplace(in.ElementType(), out.ElementType()))
6467
}
6568

6669
if in.IsObjectType() {
@@ -69,10 +72,10 @@ func dynamicReplace(in, out cty.Type) cty.Type {
6972
types = append(types, t)
7073
}
7174
unifiedType, _ := unify(types, true)
72-
elemType = dynamicReplace(unifiedType, out.ElementType())
75+
return cty.Map(dynamicReplace(unifiedType, out.ElementType()))
7376
}
7477

75-
return cty.Map(elemType)
78+
return out
7679
case out.IsObjectType():
7780
// Objects are compatible with other objects and maps.
7881
outTypes := map[string]cty.Type{}
@@ -97,33 +100,29 @@ func dynamicReplace(in, out cty.Type) cty.Type {
97100

98101
return cty.Object(outTypes)
99102
case out.IsSetType():
100-
var elemType cty.Type
101-
102103
// Sets are compatible with other sets, lists, tuples.
103104
if in.IsSetType() || in.IsListType() {
104-
elemType = dynamicReplace(in.ElementType(), out.ElementType())
105+
return cty.Set(dynamicReplace(in.ElementType(), out.ElementType()))
105106
}
106107

107108
if in.IsTupleType() {
108109
unifiedType, _ := unify(in.TupleElementTypes(), true)
109-
elemType = dynamicReplace(unifiedType, out.ElementType())
110+
return cty.Set(dynamicReplace(unifiedType, out.ElementType()))
110111
}
111112

112-
return cty.Set(elemType)
113+
return out
113114
case out.IsListType():
114-
var elemType cty.Type
115-
116115
// Lists are compatible with other lists, sets, and tuples.
117116
if in.IsSetType() || in.IsListType() {
118-
elemType = dynamicReplace(in.ElementType(), out.ElementType())
117+
return cty.List(dynamicReplace(in.ElementType(), out.ElementType()))
119118
}
120119

121120
if in.IsTupleType() {
122121
unifiedType, _ := unify(in.TupleElementTypes(), true)
123-
elemType = dynamicReplace(unifiedType, out.ElementType())
122+
return cty.List(dynamicReplace(unifiedType, out.ElementType()))
124123
}
125124

126-
return cty.List(elemType)
125+
return out
127126
case out.IsTupleType():
128127
// Tuples are only compatible with other tuples
129128
var types []cty.Type

cty/convert/public_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,6 +1770,30 @@ func TestConvert(t *testing.T) {
17701770
Type: cty.String,
17711771
Want: cty.UnknownVal(cty.String).RefineNotNull(),
17721772
},
1773+
1774+
// Make sure we get valid unknown attribute types when converting from
1775+
// a map to an object with optional attributes.
1776+
{
1777+
Value: cty.ObjectVal(map[string]cty.Value{
1778+
"TTTattr": cty.UnknownVal(cty.Map(cty.String)),
1779+
}),
1780+
Type: cty.Object(map[string]cty.Type{
1781+
"TTTattr": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
1782+
"string": cty.String,
1783+
"set": cty.Set(cty.String),
1784+
"list": cty.List(cty.String),
1785+
"map": cty.Map(cty.String),
1786+
}, []string{"set", "list", "map"}),
1787+
}),
1788+
Want: cty.ObjectVal(map[string]cty.Value{
1789+
"TTTattr": cty.UnknownVal(cty.Object(map[string]cty.Type{
1790+
"list": cty.List(cty.String),
1791+
"map": cty.Map(cty.String),
1792+
"set": cty.Set(cty.String),
1793+
"string": cty.String,
1794+
})),
1795+
}),
1796+
},
17731797
}
17741798

17751799
for _, test := range tests {

0 commit comments

Comments
 (0)