k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/internal/third_party/go-json-experiment/json/fields.go (about)

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package json
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"reflect"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"unicode"
    16  	"unicode/utf8"
    17  )
    18  
    19  var errIgnoredField = errors.New("ignored field")
    20  
    21  type isZeroer interface {
    22  	IsZero() bool
    23  }
    24  
    25  var isZeroerType = reflect.TypeOf((*isZeroer)(nil)).Elem()
    26  
    27  type structFields struct {
    28  	flattened       []structField // listed in depth-first ordering
    29  	byActualName    map[string]*structField
    30  	byFoldedName    map[string][]*structField
    31  	inlinedFallback *structField
    32  }
    33  
    34  type structField struct {
    35  	id      int   // unique numeric ID in breadth-first ordering
    36  	index   []int // index into a struct according to reflect.Type.FieldByIndex
    37  	typ     reflect.Type
    38  	fncs    *arshaler
    39  	isZero  func(addressableValue) bool
    40  	isEmpty func(addressableValue) bool
    41  	fieldOptions
    42  }
    43  
    44  func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
    45  	var fs structFields
    46  	fs.byActualName = make(map[string]*structField, root.NumField())
    47  	fs.byFoldedName = make(map[string][]*structField, root.NumField())
    48  
    49  	// ambiguous is a sentinel value to indicate that at least two fields
    50  	// at the same depth have the same name, and thus cancel each other out.
    51  	// This follows the same rules as selecting a field on embedded structs
    52  	// where the shallowest field takes precedence. If more than one field
    53  	// exists at the shallowest depth, then the selection is illegal.
    54  	// See https://go.dev/ref/spec#Selectors.
    55  	ambiguous := new(structField)
    56  
    57  	// Setup a queue for a breath-first search.
    58  	var queueIndex int
    59  	type queueEntry struct {
    60  		typ           reflect.Type
    61  		index         []int
    62  		visitChildren bool // whether to recursively visit inlined field in this struct
    63  	}
    64  	queue := []queueEntry{{root, nil, true}}
    65  	seen := map[reflect.Type]bool{root: true}
    66  
    67  	// Perform a breadth-first search over all reachable fields.
    68  	// This ensures that len(f.index) will be monotonically increasing.
    69  	for queueIndex < len(queue) {
    70  		qe := queue[queueIndex]
    71  		queueIndex++
    72  
    73  		t := qe.typ
    74  		inlinedFallbackIndex := -1         // index of last inlined fallback field in current struct
    75  		namesIndex := make(map[string]int) // index of each field with a given JSON object name in current struct
    76  		var hasAnyJSONTag bool             // whether any Go struct field has a `json` tag
    77  		var hasAnyJSONField bool           // whether any JSON serializable fields exist in current struct
    78  		for i := 0; i < t.NumField(); i++ {
    79  			sf := t.Field(i)
    80  			_, hasTag := sf.Tag.Lookup("json")
    81  			hasAnyJSONTag = hasAnyJSONTag || hasTag
    82  			options, err := parseFieldOptions(sf)
    83  			if err != nil {
    84  				if err == errIgnoredField {
    85  					continue
    86  				}
    87  				return structFields{}, &SemanticError{GoType: t, Err: err}
    88  			}
    89  			hasAnyJSONField = true
    90  			f := structField{
    91  				// Allocate a new slice (len=N+1) to hold both
    92  				// the parent index (len=N) and the current index (len=1).
    93  				// Do this to avoid clobbering the memory of the parent index.
    94  				index:        append(append(make([]int, 0, len(qe.index)+1), qe.index...), i),
    95  				typ:          sf.Type,
    96  				fieldOptions: options,
    97  			}
    98  			if sf.Anonymous && !f.hasName {
    99  				f.inline = true // implied by use of Go embedding without an explicit name
   100  			}
   101  			if f.inline || f.unknown {
   102  				// Handle an inlined field that serializes to/from
   103  				// zero or more JSON object members.
   104  
   105  				if f.inline && f.unknown {
   106  					err := fmt.Errorf("Go struct field %s cannot have both `inline` and `unknown` specified", sf.Name)
   107  					return structFields{}, &SemanticError{GoType: t, Err: err}
   108  				}
   109  				switch f.fieldOptions {
   110  				case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true}:
   111  				case fieldOptions{name: f.name, quotedName: f.quotedName, unknown: true}:
   112  				default:
   113  					err := fmt.Errorf("Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name)
   114  					return structFields{}, &SemanticError{GoType: t, Err: err}
   115  				}
   116  
   117  				// Unwrap one level of pointer indirection similar to how Go
   118  				// only allows embedding either T or *T, but not **T.
   119  				tf := f.typ
   120  				if tf.Kind() == reflect.Pointer && tf.Name() == "" {
   121  					tf = tf.Elem()
   122  				}
   123  				// Reject any types with custom serialization otherwise
   124  				// it becomes impossible to know what sub-fields to inline.
   125  				if which, _ := implementsWhich(tf,
   126  					jsonMarshalerV2Type, jsonMarshalerV1Type, textMarshalerType,
   127  					jsonUnmarshalerV2Type, jsonUnmarshalerV1Type, textUnmarshalerType,
   128  				); which != nil && tf != rawValueType {
   129  					err := fmt.Errorf("inlined Go struct field %s of type %s must not implement JSON marshal or unmarshal methods", sf.Name, tf)
   130  					return structFields{}, &SemanticError{GoType: t, Err: err}
   131  				}
   132  
   133  				// Handle an inlined field that serializes to/from
   134  				// a finite number of JSON object members backed by a Go struct.
   135  				if tf.Kind() == reflect.Struct {
   136  					if f.unknown {
   137  						err := fmt.Errorf("inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a json.RawValue", sf.Name, tf)
   138  						return structFields{}, &SemanticError{GoType: t, Err: err}
   139  					}
   140  					if qe.visitChildren {
   141  						queue = append(queue, queueEntry{tf, f.index, !seen[tf]})
   142  					}
   143  					seen[tf] = true
   144  					continue
   145  				}
   146  
   147  				// Handle an inlined field that serializes to/from any number of
   148  				// JSON object members back by a Go map or RawValue.
   149  				switch {
   150  				case tf == rawValueType:
   151  					f.fncs = nil // specially handled in arshal_inlined.go
   152  				case tf.Kind() == reflect.Map && tf.Key() == stringType:
   153  					f.fncs = lookupArshaler(tf.Elem())
   154  				default:
   155  					err := fmt.Errorf("inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or json.RawValue", sf.Name, tf)
   156  					return structFields{}, &SemanticError{GoType: t, Err: err}
   157  				}
   158  
   159  				// Reject multiple inlined fallback fields within the same struct.
   160  				if inlinedFallbackIndex >= 0 {
   161  					err := fmt.Errorf("inlined Go struct fields %s and %s cannot both be a Go map or json.RawValue", t.Field(inlinedFallbackIndex).Name, sf.Name)
   162  					return structFields{}, &SemanticError{GoType: t, Err: err}
   163  				}
   164  				inlinedFallbackIndex = i
   165  
   166  				// Multiple inlined fallback fields across different structs
   167  				// follow the same precedence rules as Go struct embedding.
   168  				if fs.inlinedFallback == nil {
   169  					fs.inlinedFallback = &f // store first occurrence at lowest depth
   170  				} else if len(fs.inlinedFallback.index) == len(f.index) {
   171  					fs.inlinedFallback = ambiguous // at least two occurrences at same depth
   172  				}
   173  			} else {
   174  				// Handle normal Go struct field that serializes to/from
   175  				// a single JSON object member.
   176  
   177  				// Provide a function that uses a type's IsZero method.
   178  				switch {
   179  				case sf.Type.Kind() == reflect.Interface && sf.Type.Implements(isZeroerType):
   180  					f.isZero = func(va addressableValue) bool {
   181  						// Avoid panics calling IsZero on a nil interface or
   182  						// non-nil interface with nil pointer.
   183  						return va.IsNil() || (va.Elem().Kind() == reflect.Pointer && va.Elem().IsNil()) || va.Interface().(isZeroer).IsZero()
   184  					}
   185  				case sf.Type.Kind() == reflect.Pointer && sf.Type.Implements(isZeroerType):
   186  					f.isZero = func(va addressableValue) bool {
   187  						// Avoid panics calling IsZero on nil pointer.
   188  						return va.IsNil() || va.Interface().(isZeroer).IsZero()
   189  					}
   190  				case sf.Type.Implements(isZeroerType):
   191  					f.isZero = func(va addressableValue) bool { return va.Interface().(isZeroer).IsZero() }
   192  				case reflect.PointerTo(sf.Type).Implements(isZeroerType):
   193  					f.isZero = func(va addressableValue) bool { return va.Addr().Interface().(isZeroer).IsZero() }
   194  				}
   195  
   196  				// Provide a function that can determine whether the value would
   197  				// serialize as an empty JSON value.
   198  				switch sf.Type.Kind() {
   199  				case reflect.String, reflect.Map, reflect.Array, reflect.Slice:
   200  					f.isEmpty = func(va addressableValue) bool { return va.Len() == 0 }
   201  				case reflect.Pointer, reflect.Interface:
   202  					f.isEmpty = func(va addressableValue) bool { return va.IsNil() }
   203  				}
   204  
   205  				f.id = len(fs.flattened)
   206  				f.fncs = lookupArshaler(sf.Type)
   207  				fs.flattened = append(fs.flattened, f)
   208  
   209  				// Reject user-specified names with invalid UTF-8.
   210  				if !utf8.ValidString(f.name) {
   211  					err := fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, f.name)
   212  					return structFields{}, &SemanticError{GoType: t, Err: err}
   213  				}
   214  				// Reject multiple fields with same name within the same struct.
   215  				if j, ok := namesIndex[f.name]; ok {
   216  					err := fmt.Errorf("Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name)
   217  					return structFields{}, &SemanticError{GoType: t, Err: err}
   218  				}
   219  				namesIndex[f.name] = i
   220  
   221  				// Multiple fields of the same name across different structs
   222  				// follow the same precedence rules as Go struct embedding.
   223  				if f2 := fs.byActualName[f.name]; f2 == nil {
   224  					fs.byActualName[f.name] = &fs.flattened[len(fs.flattened)-1] // store first occurrence at lowest depth
   225  				} else if len(f2.index) == len(f.index) {
   226  					fs.byActualName[f.name] = ambiguous // at least two occurrences at same depth
   227  				}
   228  			}
   229  		}
   230  
   231  		// NOTE: New users to the json package are occasionally surprised that
   232  		// unexported fields are ignored. This occurs by necessity due to our
   233  		// inability to directly introspect such fields with Go reflection
   234  		// without the use of unsafe.
   235  		//
   236  		// To reduce friction here, refuse to serialize any Go struct that
   237  		// has no JSON serializable fields, has at least one Go struct field,
   238  		// and does not have any `json` tags present. For example,
   239  		// errors returned by errors.New would fail to serialize.
   240  		isEmptyStruct := t.NumField() == 0
   241  		if !isEmptyStruct && !hasAnyJSONTag && !hasAnyJSONField {
   242  			err := errors.New("Go struct has no exported fields")
   243  			return structFields{}, &SemanticError{GoType: t, Err: err}
   244  		}
   245  	}
   246  
   247  	// Remove all fields that are duplicates.
   248  	// This may move elements forward to fill the holes from removed fields.
   249  	var n int
   250  	for _, f := range fs.flattened {
   251  		switch f2 := fs.byActualName[f.name]; {
   252  		case f2 == ambiguous:
   253  			delete(fs.byActualName, f.name)
   254  		case f2 == nil:
   255  			continue // may be nil due to previous delete
   256  		// TODO(https://go.dev/issue/45955): Use slices.Equal.
   257  		case reflect.DeepEqual(f.index, f2.index):
   258  			f.id = n
   259  			fs.flattened[n] = f
   260  			fs.byActualName[f.name] = &fs.flattened[n] // fix pointer to new location
   261  			n++
   262  		}
   263  	}
   264  	fs.flattened = fs.flattened[:n]
   265  	if fs.inlinedFallback == ambiguous {
   266  		fs.inlinedFallback = nil
   267  	}
   268  	if len(fs.flattened) != len(fs.byActualName) {
   269  		panic(fmt.Sprintf("BUG: flattened list of fields mismatches fields mapped by name: %d != %d", len(fs.flattened), len(fs.byActualName)))
   270  	}
   271  
   272  	// Sort the fields according to a depth-first ordering.
   273  	// This operation will cause pointers in byActualName to become incorrect,
   274  	// which we will correct in another loop shortly thereafter.
   275  	sort.Slice(fs.flattened, func(i, j int) bool {
   276  		si := fs.flattened[i].index
   277  		sj := fs.flattened[j].index
   278  		for len(si) > 0 && len(sj) > 0 {
   279  			switch {
   280  			case si[0] < sj[0]:
   281  				return true
   282  			case si[0] > sj[0]:
   283  				return false
   284  			default:
   285  				si = si[1:]
   286  				sj = sj[1:]
   287  			}
   288  		}
   289  		return len(si) < len(sj)
   290  	})
   291  
   292  	// Recompute the mapping of fields in the byActualName map.
   293  	// Pre-fold all names so that we can lookup folded names quickly.
   294  	for i, f := range fs.flattened {
   295  		foldedName := string(foldName([]byte(f.name)))
   296  		fs.byActualName[f.name] = &fs.flattened[i]
   297  		fs.byFoldedName[foldedName] = append(fs.byFoldedName[foldedName], &fs.flattened[i])
   298  	}
   299  	for foldedName, fields := range fs.byFoldedName {
   300  		if len(fields) > 1 {
   301  			// The precedence order for conflicting nocase names
   302  			// is by breadth-first order, rather than depth-first order.
   303  			sort.Slice(fields, func(i, j int) bool {
   304  				return fields[i].id < fields[j].id
   305  			})
   306  			fs.byFoldedName[foldedName] = fields
   307  		}
   308  	}
   309  
   310  	return fs, nil
   311  }
   312  
   313  type fieldOptions struct {
   314  	name       string
   315  	quotedName string // quoted name per RFC 8785, section 3.2.2.2.
   316  	hasName    bool
   317  	nocase     bool
   318  	inline     bool
   319  	unknown    bool
   320  	omitzero   bool
   321  	omitempty  bool
   322  	string     bool
   323  	format     string
   324  }
   325  
   326  // parseFieldOptions parses the `json` tag in a Go struct field as
   327  // a structured set of options configuring parameters such as
   328  // the JSON member name and other features.
   329  // As a special case, it returns errIgnoredField if the field is ignored.
   330  func parseFieldOptions(sf reflect.StructField) (out fieldOptions, err error) {
   331  	tag, hasTag := sf.Tag.Lookup("json")
   332  
   333  	// Check whether this field is explicitly ignored.
   334  	if tag == "-" {
   335  		return fieldOptions{}, errIgnoredField
   336  	}
   337  
   338  	// Check whether this field is unexported.
   339  	if !sf.IsExported() {
   340  		// In contrast to v1, v2 no longer forwards exported fields from
   341  		// embedded fields of unexported types since Go reflection does not
   342  		// allow the same set of operations that are available in normal cases
   343  		// of purely exported fields.
   344  		// See https://go.dev/issue/21357 and https://go.dev/issue/24153.
   345  		if sf.Anonymous {
   346  			return fieldOptions{}, fmt.Errorf("embedded Go struct field %s of an unexported type must be explicitly ignored with a `json:\"-\"` tag", sf.Type.Name())
   347  		}
   348  		// Tag options specified on an unexported field suggests user error.
   349  		if hasTag {
   350  			return fieldOptions{}, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag)
   351  		}
   352  		return fieldOptions{}, errIgnoredField
   353  	}
   354  
   355  	// Determine the JSON member name for this Go field. A user-specified name
   356  	// may be provided as either an identifier or a single-quoted string.
   357  	// The single-quoted string allows arbitrary characters in the name.
   358  	// See https://go.dev/issue/2718 and https://go.dev/issue/3546.
   359  	out.name = sf.Name // always starts with an uppercase character
   360  	if len(tag) > 0 && !strings.HasPrefix(tag, ",") {
   361  		// For better compatibility with v1, accept almost any unescaped name.
   362  		n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool {
   363  			return !strings.ContainsRune(",\\'\"`", r) // reserve comma, backslash, and quotes
   364  		}))
   365  		opt := tag[:n]
   366  		if n == 0 {
   367  			// Allow a single quoted string for arbitrary names.
   368  			opt, n, err = consumeTagOption(tag)
   369  			if err != nil {
   370  				return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err)
   371  			}
   372  		}
   373  		out.hasName = true
   374  		out.name = opt
   375  		tag = tag[n:]
   376  	}
   377  	b, _ := appendString(nil, out.name, false, nil)
   378  	out.quotedName = string(b)
   379  
   380  	// Handle any additional tag options (if any).
   381  	var wasFormat bool
   382  	seenOpts := make(map[string]bool)
   383  	for len(tag) > 0 {
   384  		// Consume comma delimiter.
   385  		if tag[0] != ',' {
   386  			return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid character %q before next option (expecting ',')", sf.Name, tag[0])
   387  		}
   388  		tag = tag[len(","):]
   389  		if len(tag) == 0 {
   390  			return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", sf.Name)
   391  		}
   392  
   393  		// Consume and process the tag option.
   394  		opt, n, err := consumeTagOption(tag)
   395  		if err != nil {
   396  			return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err)
   397  		}
   398  		rawOpt := tag[:n]
   399  		tag = tag[n:]
   400  		switch {
   401  		case wasFormat:
   402  			return fieldOptions{}, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", sf.Name)
   403  		case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "":
   404  			return fieldOptions{}, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", sf.Name, rawOpt, opt)
   405  		}
   406  		switch opt {
   407  		case "nocase":
   408  			out.nocase = true
   409  		case "inline":
   410  			out.inline = true
   411  		case "unknown":
   412  			out.unknown = true
   413  		case "omitzero":
   414  			out.omitzero = true
   415  		case "omitempty":
   416  			out.omitempty = true
   417  		case "string":
   418  			out.string = true
   419  		case "format":
   420  			if !strings.HasPrefix(tag, ":") {
   421  				return fieldOptions{}, fmt.Errorf("Go struct field %s is missing value for `format` tag option", sf.Name)
   422  			}
   423  			tag = tag[len(":"):]
   424  			opt, n, err := consumeTagOption(tag)
   425  			if err != nil {
   426  				return fieldOptions{}, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", sf.Name, err)
   427  			}
   428  			tag = tag[n:]
   429  			out.format = opt
   430  			wasFormat = true
   431  		default:
   432  			// Reject keys that resemble one of the supported options.
   433  			// This catches invalid mutants such as "omitEmpty" or "omit_empty".
   434  			normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
   435  			switch normOpt {
   436  			case "nocase", "inline", "unknown", "omitzero", "omitempty", "string", "format":
   437  				return fieldOptions{}, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt)
   438  			}
   439  
   440  			// NOTE: Everything else is ignored. This does not mean it is
   441  			// forward compatible to insert arbitrary tag options since
   442  			// a future version of this package may understand that tag.
   443  		}
   444  
   445  		// Reject duplicates.
   446  		if seenOpts[opt] {
   447  			return fieldOptions{}, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt)
   448  		}
   449  		seenOpts[opt] = true
   450  	}
   451  	return out, nil
   452  }
   453  
   454  func consumeTagOption(in string) (string, int, error) {
   455  	switch r, _ := utf8.DecodeRuneInString(in); {
   456  	// Option as a Go identifier.
   457  	case r == '_' || unicode.IsLetter(r):
   458  		n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit))
   459  		return in[:n], n, nil
   460  	// Option as a single-quoted string.
   461  	case r == '\'':
   462  		// The grammar is nearly identical to a double-quoted Go string literal,
   463  		// but uses single quotes as the terminators. The reason for a custom
   464  		// grammar is because both backtick and double quotes cannot be used
   465  		// verbatim in a struct tag.
   466  		//
   467  		// Convert a single-quoted string to a double-quote string and rely on
   468  		// strconv.Unquote to handle the rest.
   469  		var inEscape bool
   470  		b := []byte{'"'}
   471  		n := len(`'`)
   472  		for len(in) > n {
   473  			r, rn := utf8.DecodeRuneInString(in[n:])
   474  			switch {
   475  			case inEscape:
   476  				if r == '\'' {
   477  					b = b[:len(b)-1] // remove escape character: `\'` => `'`
   478  				}
   479  				inEscape = false
   480  			case r == '\\':
   481  				inEscape = true
   482  			case r == '"':
   483  				b = append(b, '\\') // insert escape character: `"` => `\"`
   484  			case r == '\'':
   485  				b = append(b, '"')
   486  				n += len(`'`)
   487  				out, err := strconv.Unquote(string(b))
   488  				if err != nil {
   489  					return "", 0, fmt.Errorf("invalid single-quoted string: %s", in[:n])
   490  				}
   491  				return out, n, nil
   492  			}
   493  			b = append(b, in[n:][:rn]...)
   494  			n += rn
   495  		}
   496  		if n > 10 {
   497  			n = 10 // limit the amount of context printed in the error
   498  		}
   499  		return "", 0, fmt.Errorf("single-quoted string not terminated: %s...", in[:n])
   500  	case len(in) == 0:
   501  		return "", 0, io.ErrUnexpectedEOF
   502  	default:
   503  		return "", 0, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r)
   504  	}
   505  }
   506  
   507  func isLetterOrDigit(r rune) bool {
   508  	return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
   509  }