From 4a34c33ad713db9437a9173c5d5529ca0ed606e3 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 20 Mar 2024 16:06:28 -0700 Subject: [PATCH] json: Refuse to encode cty.DynamicVal Two different bugs interacted here to cause a cty.DynamicVal to end up encoding as nothing at all, with no error: - The main marshal function was checking for dynamic type before checking for unknown value, so cty.DynamicVal was being passed to marshalDynamic. - marshalDynamic was not checking the error result from encoding the value, and so was successfully returning nothing at all. This situation will now return the same "value is not known" error that the function previously returned for all other situations where the given value is unknown. The JSON representation of cty values can only represent fully-known values. --- CHANGELOG.md | 1 + cty/json/marshal.go | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c4426b..2575b80e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * `msgpack`: Now uses string encoding instead of float encoding for a whole number that is too large to fit in any of MessagePack's integer types. * `function/stdlib`: Type conversion functions (constructed with `MakeToFunc`) can now convert null values of unknown type into null values of the target type, rather than returning an unknown value in that case. +* `json`: Will now correctly reject attempts to encode `cty.DynamicVal`, whereas before it would just produce an invalid JSON document without any error. (This is invalid because JSON encoding cannot support unknown values at all; `cty.DynamicVal` is a special case of unknown value where even the _type_ isn't known.) # 1.14.3 (February 29, 2024) diff --git a/cty/json/marshal.go b/cty/json/marshal.go index 7a14ce81..07d9f331 100644 --- a/cty/json/marshal.go +++ b/cty/json/marshal.go @@ -12,6 +12,9 @@ func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error { if val.IsMarked() { return path.NewErrorf("value has marks, so it cannot be serialized as JSON") } + if !val.IsKnown() { + return path.NewErrorf("value is not known") + } // If we're going to decode as DynamicPseudoType then we need to save // dynamic type information to recover the real type. @@ -24,10 +27,6 @@ func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error { return nil } - if !val.IsKnown() { - return path.NewErrorf("value is not known") - } - // The caller should've guaranteed that the given val is conformant with // the given type t, so we'll proceed under that assumption here. @@ -185,7 +184,10 @@ func marshalDynamic(val cty.Value, path cty.Path, b *bytes.Buffer) error { return path.NewErrorf("failed to serialize type: %s", err) } b.WriteString(`{"value":`) - marshal(val, val.Type(), path, b) + err = marshal(val, val.Type(), path, b) + if err != nil { + return path.NewErrorf("failed to serialize value: %s", err) + } b.WriteString(`,"type":`) b.Write(typeJSON) b.WriteRune('}')