github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/terraform/parser/funcs/conversion.go (about) 1 // Copied from github.com/hashicorp/terraform/internal/lang/funcs 2 package funcs 3 4 import ( 5 "fmt" 6 "sort" 7 "strconv" 8 "strings" 9 10 "github.com/zclconf/go-cty/cty" 11 "github.com/zclconf/go-cty/cty/convert" 12 "github.com/zclconf/go-cty/cty/function" 13 ) 14 15 // MakeToFunc constructs a "to..." function, like "tostring", which converts 16 // its argument to a specific type or type kind. 17 // 18 // The given type wantTy can be any type constraint that cty's "convert" package 19 // would accept. In particular, this means that you can pass 20 // cty.List(cty.DynamicPseudoType) to mean "list of any single type", which 21 // will then cause cty to attempt to unify all of the element types when given 22 // a tuple. 23 func MakeToFunc(wantTy cty.Type) function.Function { 24 return function.New(&function.Spec{ 25 Params: []function.Parameter{ 26 { 27 Name: "v", 28 // We use DynamicPseudoType rather than wantTy here so that 29 // all values will pass through the function API verbatim and 30 // we can handle the conversion logic within the Type and 31 // Impl functions. This allows us to customize the error 32 // messages to be more appropriate for an explicit type 33 // conversion, whereas the cty function system produces 34 // messages aimed at _implicit_ type conversions. 35 Type: cty.DynamicPseudoType, 36 AllowNull: true, 37 AllowMarked: true, 38 }, 39 }, 40 Type: func(args []cty.Value) (cty.Type, error) { 41 gotTy := args[0].Type() 42 if gotTy.Equals(wantTy) { 43 return wantTy, nil 44 } 45 conv := convert.GetConversionUnsafe(args[0].Type(), wantTy) 46 if conv == nil { 47 // We'll use some specialized errors for some trickier cases, 48 // but most we can handle in a simple way. 49 switch { 50 case gotTy.IsTupleType() && wantTy.IsTupleType(): 51 return cty.NilType, function.NewArgErrorf(0, "incompatible tuple type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) 52 case gotTy.IsObjectType() && wantTy.IsObjectType(): 53 return cty.NilType, function.NewArgErrorf(0, "incompatible object type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) 54 default: 55 return cty.NilType, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) 56 } 57 } 58 // If a conversion is available then everything is fine. 59 return wantTy, nil 60 }, 61 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 62 // We didn't set "AllowUnknown" on our argument, so it is guaranteed 63 // to be known here but may still be null. 64 ret, err := convert.Convert(args[0], retType) 65 if err != nil { 66 val, _ := args[0].UnmarkDeep() 67 // Because we used GetConversionUnsafe above, conversion can 68 // still potentially fail in here. For example, if the user 69 // asks to convert the string "a" to bool then we'll 70 // optimistically permit it during type checking but fail here 71 // once we note that the value isn't either "true" or "false". 72 gotTy := val.Type() 73 switch { 74 case Contains(args[0], MarkedSensitive): 75 // Generic message so we won't inadvertently disclose 76 // information about sensitive values. 77 return cty.NilVal, function.NewArgErrorf(0, "cannot convert this sensitive %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) 78 79 case gotTy == cty.String && wantTy == cty.Bool: 80 what := "string" 81 if !val.IsNull() { 82 what = strconv.Quote(val.AsString()) 83 } 84 return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to bool; only the strings "true" or "false" are allowed`, what) 85 case gotTy == cty.String && wantTy == cty.Number: 86 what := "string" 87 if !val.IsNull() { 88 what = strconv.Quote(val.AsString()) 89 } 90 return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to number; given string must be a decimal representation of a number`, what) 91 default: 92 return cty.NilVal, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) 93 } 94 } 95 return ret, nil 96 }, 97 }) 98 } 99 100 var TypeFunc = function.New(&function.Spec{ 101 Params: []function.Parameter{ 102 { 103 Name: "value", 104 Type: cty.DynamicPseudoType, 105 AllowDynamicType: true, 106 AllowUnknown: true, 107 AllowNull: true, 108 }, 109 }, 110 Type: function.StaticReturnType(cty.String), 111 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 112 return cty.StringVal(TypeString(args[0].Type())).Mark(MarkedRaw), nil 113 }, 114 }) 115 116 // Modified copy of TypeString from go-cty: 117 // https://github.com/zclconf/go-cty-debug/blob/master/ctydebug/type_string.go 118 // 119 // TypeString returns a string representation of a given type that is 120 // reminiscent of Go syntax calling into the cty package but is mainly 121 // intended for easy human inspection of values in tests, debug output, etc. 122 // 123 // The resulting string will include newlines and indentation in order to 124 // increase the readability of complex structures. It always ends with a 125 // newline, so you can print this result directly to your output. 126 func TypeString(ty cty.Type) string { 127 var b strings.Builder 128 writeType(ty, &b, 0) 129 return b.String() 130 } 131 132 func writeType(ty cty.Type, b *strings.Builder, indent int) { 133 switch { 134 case ty == cty.NilType: 135 b.WriteString("nil") 136 return 137 case ty.IsObjectType(): 138 atys := ty.AttributeTypes() 139 if len(atys) == 0 { 140 b.WriteString("object({})") 141 return 142 } 143 attrNames := make([]string, 0, len(atys)) 144 for name := range atys { 145 attrNames = append(attrNames, name) 146 } 147 sort.Strings(attrNames) 148 b.WriteString("object({\n") 149 indent++ 150 for _, name := range attrNames { 151 aty := atys[name] 152 b.WriteString(indentSpaces(indent)) 153 fmt.Fprintf(b, "%s: ", name) 154 writeType(aty, b, indent) 155 b.WriteString(",\n") 156 } 157 indent-- 158 b.WriteString(indentSpaces(indent)) 159 b.WriteString("})") 160 case ty.IsTupleType(): 161 etys := ty.TupleElementTypes() 162 if len(etys) == 0 { 163 b.WriteString("tuple([])") 164 return 165 } 166 b.WriteString("tuple([\n") 167 indent++ 168 for _, ety := range etys { 169 b.WriteString(indentSpaces(indent)) 170 writeType(ety, b, indent) 171 b.WriteString(",\n") 172 } 173 indent-- 174 b.WriteString(indentSpaces(indent)) 175 b.WriteString("])") 176 case ty.IsCollectionType(): 177 ety := ty.ElementType() 178 switch { 179 case ty.IsListType(): 180 b.WriteString("list(") 181 case ty.IsMapType(): 182 b.WriteString("map(") 183 case ty.IsSetType(): 184 b.WriteString("set(") 185 default: 186 // At the time of writing there are no other collection types, 187 // but we'll be robust here and just pass through the GoString 188 // of anything we don't recognize. 189 b.WriteString(ty.FriendlyName()) 190 return 191 } 192 // Because object and tuple types render split over multiple 193 // lines, a collection type container around them can end up 194 // being hard to see when scanning, so we'll generate some extra 195 // indentation to make a collection of structural type more visually 196 // distinct from the structural type alone. 197 complexElem := ety.IsObjectType() || ety.IsTupleType() 198 if complexElem { 199 indent++ 200 b.WriteString("\n") 201 b.WriteString(indentSpaces(indent)) 202 } 203 writeType(ty.ElementType(), b, indent) 204 if complexElem { 205 indent-- 206 b.WriteString(",\n") 207 b.WriteString(indentSpaces(indent)) 208 } 209 b.WriteString(")") 210 default: 211 // For any other type we'll just use its GoString and assume it'll 212 // follow the usual GoString conventions. 213 b.WriteString(ty.FriendlyName()) 214 } 215 } 216 217 func indentSpaces(level int) string { 218 return strings.Repeat(" ", level) 219 } 220 221 func Type(input []cty.Value) (cty.Value, error) { 222 return TypeFunc.Call(input) 223 }