Skip to content

Commit 56a9aee

Browse files
authored
Merge pull request #710 from hashicorp/jbardin/marked-conditions
Better control of marks through conditional and for expressions
2 parents d20d07f + b48ba6e commit 56a9aee

File tree

3 files changed

+93
-30
lines changed

3 files changed

+93
-30
lines changed

hclsyntax/expression.go

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -788,21 +788,24 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
788788
})
789789
return cty.UnknownVal(resultType), diags
790790
}
791-
if !condResult.IsKnown() {
792-
// we use the unmarked values throughout the unknown branch
793-
_, condResultMarks := condResult.Unmark()
794-
trueResult, trueResultMarks := trueResult.Unmark()
795-
falseResult, falseResultMarks := falseResult.Unmark()
796791

797-
// use a value to merge marks
798-
_, resMarks := cty.DynamicVal.WithMarks(condResultMarks, trueResultMarks, falseResultMarks).Unmark()
792+
// Now that we have all three values, collect all the marks for the result.
793+
// Since it's possible that a condition value could be unknown, and the
794+
// consumer needs to deal with any marks from either branch anyway, we must
795+
// always combine them for consistent results.
796+
condResult, condResultMarks := condResult.Unmark()
797+
trueResult, trueResultMarks := trueResult.Unmark()
798+
falseResult, falseResultMarks := falseResult.Unmark()
799+
var resMarks []cty.ValueMarks
800+
resMarks = append(resMarks, condResultMarks, trueResultMarks, falseResultMarks)
799801

802+
if !condResult.IsKnown() {
800803
trueRange := trueResult.Range()
801804
falseRange := falseResult.Range()
802805

803806
// if both branches are known to be null, then the result must still be null
804807
if trueResult.IsNull() && falseResult.IsNull() {
805-
return cty.NullVal(resultType).WithMarks(resMarks), diags
808+
return cty.NullVal(resultType).WithMarks(resMarks...), diags
806809
}
807810

808811
// We might be able to offer a refined range for the result based on
@@ -841,7 +844,7 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
841844
ref = ref.NumberRangeUpperBound(hi, hiInc)
842845
}
843846

844-
return ref.NewValue().WithMarks(resMarks), diags
847+
return ref.NewValue().WithMarks(resMarks...), diags
845848
}
846849

847850
if trueResult.Type().IsCollectionType() && falseResult.Type().IsCollectionType() {
@@ -867,15 +870,15 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
867870
}
868871

869872
ref = ref.CollectionLengthLowerBound(lo).CollectionLengthUpperBound(hi)
870-
return ref.NewValue().WithMarks(resMarks), diags
873+
return ref.NewValue().WithMarks(resMarks...), diags
871874
}
872875
}
873876

874877
ret := cty.UnknownVal(resultType)
875878
if trueRange.DefinitelyNotNull() && falseRange.DefinitelyNotNull() {
876879
ret = ret.RefineNotNull()
877880
}
878-
return ret.WithMarks(resMarks), diags
881+
return ret.WithMarks(resMarks...), diags
879882
}
880883

881884
condResult, err := convert.Convert(condResult, cty.Bool)
@@ -892,8 +895,6 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
892895
return cty.UnknownVal(resultType), diags
893896
}
894897

895-
// Unmark result before testing for truthiness
896-
condResult, _ = condResult.UnmarkDeep()
897898
if condResult.True() {
898899
diags = append(diags, trueDiags...)
899900
if convs[0] != nil {
@@ -916,7 +917,7 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
916917
trueResult = cty.UnknownVal(resultType)
917918
}
918919
}
919-
return trueResult, diags
920+
return trueResult.WithMarks(resMarks...), diags
920921
} else {
921922
diags = append(diags, falseDiags...)
922923
if convs[1] != nil {
@@ -939,7 +940,7 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
939940
falseResult = cty.UnknownVal(resultType)
940941
}
941942
}
942-
return falseResult, diags
943+
return falseResult.WithMarks(resMarks...), diags
943944
}
944945
}
945946

@@ -1429,9 +1430,9 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
14291430
})
14301431
return cty.DynamicVal, diags
14311432
}
1432-
if !collVal.IsKnown() {
1433-
return cty.DynamicVal, diags
1434-
}
1433+
1434+
// Grab the CondExpr marks when we're returning early with an unknown
1435+
var condMarks cty.ValueMarks
14351436

14361437
// Before we start we'll do an early check to see if any CondExpr we've
14371438
// been given is of the wrong type. This isn't 100% reliable (it may
@@ -1459,6 +1460,9 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
14591460
})
14601461
return cty.DynamicVal, diags
14611462
}
1463+
1464+
_, condMarks = result.Unmark()
1465+
14621466
_, err := convert.Convert(result, cty.Bool)
14631467
if err != nil {
14641468
diags = append(diags, &hcl.Diagnostic{
@@ -1477,6 +1481,10 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
14771481
}
14781482
}
14791483

1484+
if !collVal.IsKnown() {
1485+
return cty.DynamicVal.WithMarks(append(marks, condMarks)...), diags
1486+
}
1487+
14801488
if e.KeyExpr != nil {
14811489
// Producing an object
14821490
var vals map[string]cty.Value
@@ -1517,6 +1525,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
15171525
known = false
15181526
continue
15191527
}
1528+
1529+
// Extract and merge marks from the include expression into the
1530+
// main set of marks
1531+
_, includeMarks := includeRaw.Unmark()
1532+
marks = append(marks, includeMarks)
1533+
15201534
include, err := convert.Convert(includeRaw, cty.Bool)
15211535
if err != nil {
15221536
if known {
@@ -1540,7 +1554,7 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
15401554

15411555
// Extract and merge marks from the include expression into the
15421556
// main set of marks
1543-
includeUnmarked, includeMarks := include.Unmark()
1557+
includeUnmarked, _ := include.Unmark()
15441558
marks = append(marks, includeMarks)
15451559
if includeUnmarked.False() {
15461560
// Skip this element
@@ -1565,6 +1579,10 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
15651579
known = false
15661580
continue
15671581
}
1582+
1583+
_, keyMarks := keyRaw.Unmark()
1584+
marks = append(marks, keyMarks)
1585+
15681586
if !keyRaw.IsKnown() {
15691587
known = false
15701588
continue
@@ -1587,8 +1605,7 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
15871605
continue
15881606
}
15891607

1590-
key, keyMarks := key.Unmark()
1591-
marks = append(marks, keyMarks)
1608+
key, _ = key.Unmark()
15921609

15931610
val, valDiags := e.ValExpr.Value(childCtx)
15941611
diags = append(diags, valDiags...)
@@ -1618,7 +1635,7 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
16181635
}
16191636

16201637
if !known {
1621-
return cty.DynamicVal, diags
1638+
return cty.DynamicVal.WithMarks(marks...), diags
16221639
}
16231640

16241641
if e.Group {
@@ -1664,6 +1681,12 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
16641681
known = false
16651682
continue
16661683
}
1684+
1685+
// Extract and merge marks from the include expression into the
1686+
// main set of marks
1687+
_, includeMarks := includeRaw.Unmark()
1688+
marks = append(marks, includeMarks)
1689+
16671690
if !includeRaw.IsKnown() {
16681691
// We will eventually return DynamicVal, but we'll continue
16691692
// iterating in case there are other diagnostics to gather
@@ -1689,10 +1712,7 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
16891712
continue
16901713
}
16911714

1692-
// Extract and merge marks from the include expression into the
1693-
// main set of marks
1694-
includeUnmarked, includeMarks := include.Unmark()
1695-
marks = append(marks, includeMarks)
1715+
includeUnmarked, _ := include.Unmark()
16961716
if includeUnmarked.False() {
16971717
// Skip this element
16981718
continue
@@ -1705,7 +1725,7 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
17051725
}
17061726

17071727
if !known {
1708-
return cty.DynamicVal, diags
1728+
return cty.DynamicVal.WithMarks(marks...), diags
17091729
}
17101730

17111731
return cty.TupleVal(vals).WithMarks(marks...), diags

hclsyntax/expression_template_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,14 +318,14 @@ trim`,
318318
cty.UnknownVal(cty.String).Refine().NotNull().StringPrefixFull(strings.Repeat("_", 128)).NewValue(),
319319
0,
320320
},
321-
{ // marks from uninterpolated values are ignored
321+
{ // all marks are passed through to ensure result is always consistent
322322
`hello%{ if false } ${target}%{ endif }`,
323323
&hcl.EvalContext{
324324
Variables: map[string]cty.Value{
325325
"target": cty.StringVal("world").Mark("sensitive"),
326326
},
327327
},
328-
cty.StringVal("hello"),
328+
cty.StringVal("hello").Mark("sensitive"),
329329
0,
330330
},
331331
{ // marks from interpolated values are passed through

hclsyntax/expression_test.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,49 @@ upper(
11431143
}).Mark("sensitive"),
11441144
0,
11451145
},
1146+
{
1147+
`{ for k, v in things: k => v if k != unknown_secret }`,
1148+
&hcl.EvalContext{
1149+
Variables: map[string]cty.Value{
1150+
"things": cty.MapVal(map[string]cty.Value{
1151+
"a": cty.True,
1152+
"b": cty.False,
1153+
"c": cty.False,
1154+
}),
1155+
"unknown_secret": cty.UnknownVal(cty.String).Mark("sensitive"),
1156+
},
1157+
},
1158+
cty.DynamicVal.Mark("sensitive"),
1159+
0,
1160+
},
1161+
{
1162+
`[ for v in things: v if v != unknown_secret ]`,
1163+
&hcl.EvalContext{
1164+
Variables: map[string]cty.Value{
1165+
"things": cty.TupleVal([]cty.Value{
1166+
cty.StringVal("a"),
1167+
cty.StringVal("b"),
1168+
}),
1169+
"unknown_secret": cty.UnknownVal(cty.String).Mark("sensitive"),
1170+
},
1171+
},
1172+
cty.DynamicVal.Mark("sensitive"),
1173+
0,
1174+
},
1175+
{
1176+
`[ for v in things: v if v != secret ]`,
1177+
&hcl.EvalContext{
1178+
Variables: map[string]cty.Value{
1179+
"things": cty.TupleVal([]cty.Value{
1180+
cty.UnknownVal(cty.String).Mark("mark"),
1181+
cty.StringVal("b"),
1182+
}),
1183+
"secret": cty.StringVal("b").Mark("sensitive"),
1184+
},
1185+
},
1186+
cty.DynamicVal.WithMarks(cty.NewValueMarks("mark", "sensitive")),
1187+
0,
1188+
},
11461189
{
11471190
`[{name: "Steve"}, {name: "Ermintrude"}].*.name`,
11481191
nil,
@@ -2175,7 +2218,7 @@ EOT
21752218
}).Mark("sensitive"),
21762219
},
21772220
},
2178-
cty.NumberIntVal(1),
2221+
cty.NumberIntVal(1).Mark("sensitive"),
21792222
0,
21802223
},
21812224
{ // auto-converts collection types

0 commit comments

Comments
 (0)