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  }