hcl/hclsyntax: Handle template sequences in block labels
Template sequences are forbidden in block labels, but previously we were handling them in a very severe way, by bailing out of block parsing early and leaving the body in the AST as nil. The rest of HCL doesn't expect to find a nil body, and in any case we can safely keep parsing the rest of the block after recovering because the closing quote gives us an unambiguous resume point. Therefore we'll now process the rest of the block as normal, producing an AST that is complete aside from having an invalid label string inside of it. Skipping the template sequence in the returned label entirely creates a risk that analysis code (which may try to inspect a partial AST on error) will misinterpret the string as valid, so we generate a placeholder "${ ... }" or "%{ ... }" sequence in the returned string to make it clearer in any follow-up output that there was something there in the original source. Normal callers won't be affected by this because they don't process the AST when errors are present anyway.
This commit is contained in:
parent
253da47fd6
commit
62acf2ce82
@ -286,19 +286,9 @@ Token:
|
||||
diags = append(diags, labelDiags...)
|
||||
labels = append(labels, label)
|
||||
labelRanges = append(labelRanges, labelRange)
|
||||
if labelDiags.HasErrors() {
|
||||
p.recoverAfterBodyItem()
|
||||
return &Block{
|
||||
Type: blockType,
|
||||
Labels: labels,
|
||||
Body: nil,
|
||||
|
||||
TypeRange: ident.Range,
|
||||
LabelRanges: labelRanges,
|
||||
OpenBraceRange: ident.Range, // placeholder
|
||||
CloseBraceRange: ident.Range, // placeholder
|
||||
}, diags
|
||||
}
|
||||
// parseQuoteStringLiteral recovers up to the closing quote
|
||||
// if it encounters problems, so we can continue looking for
|
||||
// more labels and eventually the block body even.
|
||||
|
||||
case TokenIdent:
|
||||
tok = p.Read() // eat token
|
||||
@ -1648,7 +1638,16 @@ Token:
|
||||
Subject: &tok.Range,
|
||||
Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
|
||||
})
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
|
||||
// Now that we're returning an error callers won't attempt to use
|
||||
// the result for any real operations, but they might try to use
|
||||
// the partial AST for other analyses, so we'll leave a marker
|
||||
// to indicate that there was something invalid in the string to
|
||||
// help avoid misinterpretation of the partial result
|
||||
ret.WriteString(which)
|
||||
ret.WriteString("{ ... }")
|
||||
|
||||
p.recover(TokenTemplateSeqEnd) // we'll try to keep parsing after the sequence ends
|
||||
|
||||
case TokenEOF:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
@ -1669,7 +1668,7 @@ Token:
|
||||
Subject: &tok.Range,
|
||||
Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
|
||||
})
|
||||
p.recover(TokenOQuote)
|
||||
p.recover(TokenCQuote)
|
||||
break Token
|
||||
|
||||
}
|
||||
|
@ -285,6 +285,59 @@ func TestParseConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"block \"invalid ${not_allowed_here} foo\" {}\n",
|
||||
1, // Invalid string literal; Template sequences are not allowed in this string.
|
||||
&Body{
|
||||
Attributes: Attributes{},
|
||||
Blocks: Blocks{
|
||||
&Block{
|
||||
Type: "block",
|
||||
Labels: []string{"invalid ${ ... } foo"}, // invalid interpolation gets replaced with a placeholder here
|
||||
Body: &Body{
|
||||
Attributes: Attributes{},
|
||||
Blocks: Blocks{},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 41, Byte: 40},
|
||||
End: hcl.Pos{Line: 1, Column: 43, Byte: 42},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 43, Byte: 42},
|
||||
End: hcl.Pos{Line: 1, Column: 43, Byte: 42},
|
||||
},
|
||||
},
|
||||
|
||||
TypeRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
},
|
||||
LabelRanges: []hcl.Range{
|
||||
{
|
||||
Start: hcl.Pos{Line: 1, Column: 7, Byte: 6},
|
||||
End: hcl.Pos{Line: 1, Column: 40, Byte: 39},
|
||||
},
|
||||
},
|
||||
OpenBraceRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 41, Byte: 40},
|
||||
End: hcl.Pos{Line: 1, Column: 42, Byte: 41},
|
||||
},
|
||||
CloseBraceRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 42, Byte: 41},
|
||||
End: hcl.Pos{Line: 1, Column: 43, Byte: 42},
|
||||
},
|
||||
},
|
||||
},
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 43},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 43},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 43},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
`
|
||||
block "invalid" 1.2 {}
|
||||
@ -391,7 +444,19 @@ block "valid" {}
|
||||
&Block{
|
||||
Type: "block",
|
||||
Labels: []string{"fo"},
|
||||
Body: nil,
|
||||
Body: &Body{
|
||||
Attributes: map[string]*Attribute{},
|
||||
Blocks: []*Block{},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 13, Byte: 12},
|
||||
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||
},
|
||||
},
|
||||
|
||||
TypeRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
@ -404,12 +469,12 @@ block "valid" {}
|
||||
},
|
||||
},
|
||||
OpenBraceRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
Start: hcl.Pos{Line: 1, Column: 13, Byte: 12},
|
||||
End: hcl.Pos{Line: 1, Column: 14, Byte: 13},
|
||||
},
|
||||
CloseBraceRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
Start: hcl.Pos{Line: 1, Column: 14, Byte: 13},
|
||||
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user