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:
Martin Atkins 2018-12-19 14:39:29 -08:00
parent 253da47fd6
commit 62acf2ce82
2 changed files with 84 additions and 20 deletions

View File

@ -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
}

View File

@ -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},
},
},
},