cli: Prevent overuse of console-only type function
The console-only `type` function allows interrogation of any value's type. An implementation quirk is that we use a cty.Mark to allow the console to display this type information without the usual HCL quoting. For example: > type("boop") string instead of: > type("boop") "string" Because these marks can propagate when used in complex expressions, using the type function as part of a complex expression could result in this "print as raw" mark being attached to a collection. When this happened, it would result in a crash when we tried to iterate over a marked value. The `type` function was never intended to be used in this way, which is why its use is limited to the console command. Its purpose was as a pseudo-builtin, used only at the top level to display the type of a given value. This commit goes some way to preventing the use of the `type` function in complex expressions, by refusing to display any non-string value which was marked by `type`, or contains a sub-value which was so marked.
This commit is contained in:
parent
c1dc94a3d2
commit
691b98b612
|
@ -17,10 +17,6 @@ func FormatValue(v cty.Value, indent int) string {
|
|||
if !v.IsKnown() {
|
||||
return "(known after apply)"
|
||||
}
|
||||
if v.Type().Equals(cty.String) && v.HasMark(marks.Raw) {
|
||||
raw, _ := v.Unmark()
|
||||
return raw.AsString()
|
||||
}
|
||||
if v.HasMark(marks.Sensitive) {
|
||||
return "(sensitive)"
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/hashicorp/terraform/internal/lang"
|
||||
"github.com/hashicorp/terraform/internal/lang/marks"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
|
@ -54,6 +55,29 @@ func (s *Session) handleEval(line string) (string, tfdiags.Diagnostics) {
|
|||
return "", diags
|
||||
}
|
||||
|
||||
// The raw mark is used only by the console-only `type` function, in order
|
||||
// to allow display of a string value representation of the type without the
|
||||
// usual HCL formatting. If we receive a string value with this mark, we do
|
||||
// not want to format it any further.
|
||||
//
|
||||
// Due to mark propagation in cty, calling `type` as part of a larger
|
||||
// expression can lead to other values being marked, which can in turn lead
|
||||
// to unpredictable results. If any non-string value has the raw mark, we
|
||||
// return a diagnostic explaining that this use of `type` is not permitted.
|
||||
if marks.Contains(val, marks.Raw) {
|
||||
if val.Type().Equals(cty.String) {
|
||||
raw, _ := val.Unmark()
|
||||
return raw.AsString(), diags
|
||||
} else {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid use of type function",
|
||||
"The console-only \"type\" function cannot be used as part of an expression.",
|
||||
))
|
||||
return "", diags
|
||||
}
|
||||
}
|
||||
|
||||
return FormatValue(val, 0), diags
|
||||
}
|
||||
|
||||
|
|
|
@ -120,6 +120,20 @@ func TestSession_basicState(t *testing.T) {
|
|||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("type function", func(t *testing.T) {
|
||||
testSession(t, testSessionTest{
|
||||
State: state,
|
||||
Inputs: []testSessionInput{
|
||||
{
|
||||
Input: "type(test_instance.foo)",
|
||||
Output: `object({
|
||||
id: string,
|
||||
})`,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSession_stateless(t *testing.T) {
|
||||
|
@ -178,6 +192,18 @@ func TestSession_stateless(t *testing.T) {
|
|||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("type function cannot be used in expressions", func(t *testing.T) {
|
||||
testSession(t, testSessionTest{
|
||||
Inputs: []testSessionInput{
|
||||
{
|
||||
Input: `[for i in [1, "two", true]: type(i)]`,
|
||||
Error: true,
|
||||
ErrorContains: "Invalid use of type function",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testSession(t *testing.T, test testSessionTest) {
|
||||
|
@ -221,6 +247,9 @@ func testSession(t *testing.T, test testSessionTest) {
|
|||
t.Fatalf("failed to create scope: %s", diags.Err())
|
||||
}
|
||||
|
||||
// Ensure that any console-only functions are available
|
||||
scope.ConsoleMode = true
|
||||
|
||||
// Build the session
|
||||
s := &Session{
|
||||
Scope: scope,
|
||||
|
|
Loading…
Reference in New Issue