hcldec: Handle or forbid cty.DynamicPseudoType attributes in nested blocks
Our BlockList, BlockSet, and BlockMap specs all produce cty collection values, which require all elements to have a homogeneous type. If the nested spec contained an attribute of type cty.DynamicPseudoType, that would create the risk of each element having a different type, which would previously have caused decoding to panic. Now we either handle this during decode (BlockList, BlockSet) or forbid it outright (BlockMap) to prevent that crash. BlockMap could _potentially_ also handle this during decode, but that would require a more significant reorganization of its implementation than I want to take on right now, and decoding dynamically-typed values inside collections is an edge case anyway.
This commit is contained in:
parent
d6049c2a04
commit
b82170e941
@ -394,6 +394,15 @@ func decodeBlockMapSpec(body hcl.Body, impliedName string) (hcldec.Spec, hcl.Dia
|
||||
return errSpec, diags
|
||||
}
|
||||
|
||||
if hcldec.ImpliedType(spec).HasDynamicTypes() {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid block_map spec",
|
||||
Detail: "A block_map spec may not contain attributes with type 'any'.",
|
||||
Subject: body.MissingItemRange().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
return spec, diags
|
||||
}
|
||||
|
||||
|
@ -426,6 +426,49 @@ b {}
|
||||
},
|
||||
{
|
||||
`
|
||||
b {
|
||||
a = true
|
||||
}
|
||||
b {
|
||||
a = 1
|
||||
}
|
||||
`,
|
||||
&BlockListSpec{
|
||||
TypeName: "b",
|
||||
Nested: &AttrSpec{
|
||||
Name: "a",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.DynamicVal,
|
||||
1, // Unconsistent argument types in b blocks
|
||||
},
|
||||
{
|
||||
`
|
||||
b {
|
||||
a = true
|
||||
}
|
||||
b {
|
||||
a = "not a bool"
|
||||
}
|
||||
`,
|
||||
&BlockListSpec{
|
||||
TypeName: "b",
|
||||
Nested: &AttrSpec{
|
||||
Name: "a",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.ListVal([]cty.Value{
|
||||
cty.StringVal("true"), // type unification generalizes all the values to strings
|
||||
cty.StringVal("not a bool"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`
|
||||
b {}
|
||||
b {}
|
||||
`,
|
||||
@ -465,6 +508,49 @@ b "bar" "baz" {}
|
||||
},
|
||||
{
|
||||
`
|
||||
b {
|
||||
a = true
|
||||
}
|
||||
b {
|
||||
a = 1
|
||||
}
|
||||
`,
|
||||
&BlockSetSpec{
|
||||
TypeName: "b",
|
||||
Nested: &AttrSpec{
|
||||
Name: "a",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.DynamicVal,
|
||||
1, // Unconsistent argument types in b blocks
|
||||
},
|
||||
{
|
||||
`
|
||||
b {
|
||||
a = true
|
||||
}
|
||||
b {
|
||||
a = "not a bool"
|
||||
}
|
||||
`,
|
||||
&BlockSetSpec{
|
||||
TypeName: "b",
|
||||
Nested: &AttrSpec{
|
||||
Name: "a",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("true"), // type unification generalizes all the values to strings
|
||||
cty.StringVal("not a bool"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`
|
||||
b "foo" {}
|
||||
b "bar" {}
|
||||
`,
|
||||
|
@ -478,6 +478,44 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe
|
||||
if len(elems) == 0 {
|
||||
ret = cty.ListValEmpty(s.Nested.impliedType())
|
||||
} else {
|
||||
// Since our target is a list, all of the decoded elements must have the
|
||||
// same type or cty.ListVal will panic below. Different types can arise
|
||||
// if there is an attribute spec of type cty.DynamicPseudoType in the
|
||||
// nested spec; all given values must be convertable to a single type
|
||||
// in order for the result to be considered valid.
|
||||
etys := make([]cty.Type, len(elems))
|
||||
for i, v := range elems {
|
||||
etys[i] = v.Type()
|
||||
}
|
||||
ety, convs := convert.UnifyUnsafe(etys)
|
||||
if ety == cty.NilType {
|
||||
// FIXME: This is a pretty terrible error message.
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
|
||||
Detail: "Corresponding attributes in all blocks of this type must be the same.",
|
||||
Subject: &sourceRanges[0],
|
||||
})
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
for i, v := range elems {
|
||||
if convs[i] != nil {
|
||||
newV, err := convs[i](v)
|
||||
if err != nil {
|
||||
// FIXME: This is a pretty terrible error message.
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
|
||||
Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err),
|
||||
Subject: &sourceRanges[i],
|
||||
})
|
||||
// Bail early here so we won't panic below in cty.ListVal
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
elems[i] = newV
|
||||
}
|
||||
}
|
||||
|
||||
ret = cty.ListVal(elems)
|
||||
}
|
||||
|
||||
@ -593,6 +631,44 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel
|
||||
if len(elems) == 0 {
|
||||
ret = cty.SetValEmpty(s.Nested.impliedType())
|
||||
} else {
|
||||
// Since our target is a set, all of the decoded elements must have the
|
||||
// same type or cty.SetVal will panic below. Different types can arise
|
||||
// if there is an attribute spec of type cty.DynamicPseudoType in the
|
||||
// nested spec; all given values must be convertable to a single type
|
||||
// in order for the result to be considered valid.
|
||||
etys := make([]cty.Type, len(elems))
|
||||
for i, v := range elems {
|
||||
etys[i] = v.Type()
|
||||
}
|
||||
ety, convs := convert.UnifyUnsafe(etys)
|
||||
if ety == cty.NilType {
|
||||
// FIXME: This is a pretty terrible error message.
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
|
||||
Detail: "Corresponding attributes in all blocks of this type must be the same.",
|
||||
Subject: &sourceRanges[0],
|
||||
})
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
for i, v := range elems {
|
||||
if convs[i] != nil {
|
||||
newV, err := convs[i](v)
|
||||
if err != nil {
|
||||
// FIXME: This is a pretty terrible error message.
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
|
||||
Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err),
|
||||
Subject: &sourceRanges[i],
|
||||
})
|
||||
// Bail early here so we won't panic below in cty.ListVal
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
elems[i] = newV
|
||||
}
|
||||
}
|
||||
|
||||
ret = cty.SetVal(elems)
|
||||
}
|
||||
|
||||
@ -675,6 +751,9 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel
|
||||
if s.Nested == nil {
|
||||
panic("BlockSetSpec with no Nested Spec")
|
||||
}
|
||||
if ImpliedType(s).HasDynamicTypes() {
|
||||
panic("cty.DynamicPseudoType attributes may not be used inside a BlockMapSpec")
|
||||
}
|
||||
|
||||
elems := map[string]interface{}{}
|
||||
for _, childBlock := range content.Blocks {
|
||||
|
Loading…
Reference in New Issue
Block a user