xref: /aosp_15_r20/external/go-cmp/cmp/report_reflect.go (revision 88d15eac089d7f20c739ff1001d56b91872b21a1)
1// Copyright 2019, 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
5package cmp
6
7import (
8	"bytes"
9	"fmt"
10	"reflect"
11	"strconv"
12	"strings"
13	"unicode"
14	"unicode/utf8"
15
16	"github.com/google/go-cmp/cmp/internal/value"
17)
18
19var (
20	anyType    = reflect.TypeOf((*interface{})(nil)).Elem()
21	stringType = reflect.TypeOf((*string)(nil)).Elem()
22	bytesType  = reflect.TypeOf((*[]byte)(nil)).Elem()
23	byteType   = reflect.TypeOf((*byte)(nil)).Elem()
24)
25
26type formatValueOptions struct {
27	// AvoidStringer controls whether to avoid calling custom stringer
28	// methods like error.Error or fmt.Stringer.String.
29	AvoidStringer bool
30
31	// PrintAddresses controls whether to print the address of all pointers,
32	// slice elements, and maps.
33	PrintAddresses bool
34
35	// QualifiedNames controls whether FormatType uses the fully qualified name
36	// (including the full package path as opposed to just the package name).
37	QualifiedNames bool
38
39	// VerbosityLevel controls the amount of output to produce.
40	// A higher value produces more output. A value of zero or lower produces
41	// no output (represented using an ellipsis).
42	// If LimitVerbosity is false, then the level is treated as infinite.
43	VerbosityLevel int
44
45	// LimitVerbosity specifies that formatting should respect VerbosityLevel.
46	LimitVerbosity bool
47}
48
49// FormatType prints the type as if it were wrapping s.
50// This may return s as-is depending on the current type and TypeMode mode.
51func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
52	// Check whether to emit the type or not.
53	switch opts.TypeMode {
54	case autoType:
55		switch t.Kind() {
56		case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
57			if s.Equal(textNil) {
58				return s
59			}
60		default:
61			return s
62		}
63		if opts.DiffMode == diffIdentical {
64			return s // elide type for identical nodes
65		}
66	case elideType:
67		return s
68	}
69
70	// Determine the type label, applying special handling for unnamed types.
71	typeName := value.TypeString(t, opts.QualifiedNames)
72	if t.Name() == "" {
73		// According to Go grammar, certain type literals contain symbols that
74		// do not strongly bind to the next lexicographical token (e.g., *T).
75		switch t.Kind() {
76		case reflect.Chan, reflect.Func, reflect.Ptr:
77			typeName = "(" + typeName + ")"
78		}
79	}
80	return &textWrap{Prefix: typeName, Value: wrapParens(s)}
81}
82
83// wrapParens wraps s with a set of parenthesis, but avoids it if the
84// wrapped node itself is already surrounded by a pair of parenthesis or braces.
85// It handles unwrapping one level of pointer-reference nodes.
86func wrapParens(s textNode) textNode {
87	var refNode *textWrap
88	if s2, ok := s.(*textWrap); ok {
89		// Unwrap a single pointer reference node.
90		switch s2.Metadata.(type) {
91		case leafReference, trunkReference, trunkReferences:
92			refNode = s2
93			if s3, ok := refNode.Value.(*textWrap); ok {
94				s2 = s3
95			}
96		}
97
98		// Already has delimiters that make parenthesis unnecessary.
99		hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
100		hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
101		if hasParens || hasBraces {
102			return s
103		}
104	}
105	if refNode != nil {
106		refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
107		return s
108	}
109	return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
110}
111
112// FormatValue prints the reflect.Value, taking extra care to avoid descending
113// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
114func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
115	if !v.IsValid() {
116		return nil
117	}
118	t := v.Type()
119
120	// Check slice element for cycles.
121	if parentKind == reflect.Slice {
122		ptrRef, visited := ptrs.Push(v.Addr())
123		if visited {
124			return makeLeafReference(ptrRef, false)
125		}
126		defer ptrs.Pop()
127		defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
128	}
129
130	// Check whether there is an Error or String method to call.
131	if !opts.AvoidStringer && v.CanInterface() {
132		// Avoid calling Error or String methods on nil receivers since many
133		// implementations crash when doing so.
134		if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
135			var prefix, strVal string
136			func() {
137				// Swallow and ignore any panics from String or Error.
138				defer func() { recover() }()
139				switch v := v.Interface().(type) {
140				case error:
141					strVal = v.Error()
142					prefix = "e"
143				case fmt.Stringer:
144					strVal = v.String()
145					prefix = "s"
146				}
147			}()
148			if prefix != "" {
149				return opts.formatString(prefix, strVal)
150			}
151		}
152	}
153
154	// Check whether to explicitly wrap the result with the type.
155	var skipType bool
156	defer func() {
157		if !skipType {
158			out = opts.FormatType(t, out)
159		}
160	}()
161
162	switch t.Kind() {
163	case reflect.Bool:
164		return textLine(fmt.Sprint(v.Bool()))
165	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
166		return textLine(fmt.Sprint(v.Int()))
167	case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
168		return textLine(fmt.Sprint(v.Uint()))
169	case reflect.Uint8:
170		if parentKind == reflect.Slice || parentKind == reflect.Array {
171			return textLine(formatHex(v.Uint()))
172		}
173		return textLine(fmt.Sprint(v.Uint()))
174	case reflect.Uintptr:
175		return textLine(formatHex(v.Uint()))
176	case reflect.Float32, reflect.Float64:
177		return textLine(fmt.Sprint(v.Float()))
178	case reflect.Complex64, reflect.Complex128:
179		return textLine(fmt.Sprint(v.Complex()))
180	case reflect.String:
181		return opts.formatString("", v.String())
182	case reflect.UnsafePointer, reflect.Chan, reflect.Func:
183		return textLine(formatPointer(value.PointerOf(v), true))
184	case reflect.Struct:
185		var list textList
186		v := makeAddressable(v) // needed for retrieveUnexportedField
187		maxLen := v.NumField()
188		if opts.LimitVerbosity {
189			maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
190			opts.VerbosityLevel--
191		}
192		for i := 0; i < v.NumField(); i++ {
193			vv := v.Field(i)
194			if vv.IsZero() {
195				continue // Elide fields with zero values
196			}
197			if len(list) == maxLen {
198				list.AppendEllipsis(diffStats{})
199				break
200			}
201			sf := t.Field(i)
202			if supportExporters && !isExported(sf.Name) {
203				vv = retrieveUnexportedField(v, sf, true)
204			}
205			s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
206			list = append(list, textRecord{Key: sf.Name, Value: s})
207		}
208		return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
209	case reflect.Slice:
210		if v.IsNil() {
211			return textNil
212		}
213
214		// Check whether this is a []byte of text data.
215		if t.Elem() == byteType {
216			b := v.Bytes()
217			isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
218			if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
219				out = opts.formatString("", string(b))
220				skipType = true
221				return opts.FormatType(t, out)
222			}
223		}
224
225		fallthrough
226	case reflect.Array:
227		maxLen := v.Len()
228		if opts.LimitVerbosity {
229			maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
230			opts.VerbosityLevel--
231		}
232		var list textList
233		for i := 0; i < v.Len(); i++ {
234			if len(list) == maxLen {
235				list.AppendEllipsis(diffStats{})
236				break
237			}
238			s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
239			list = append(list, textRecord{Value: s})
240		}
241
242		out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
243		if t.Kind() == reflect.Slice && opts.PrintAddresses {
244			header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
245			out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
246		}
247		return out
248	case reflect.Map:
249		if v.IsNil() {
250			return textNil
251		}
252
253		// Check pointer for cycles.
254		ptrRef, visited := ptrs.Push(v)
255		if visited {
256			return makeLeafReference(ptrRef, opts.PrintAddresses)
257		}
258		defer ptrs.Pop()
259
260		maxLen := v.Len()
261		if opts.LimitVerbosity {
262			maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
263			opts.VerbosityLevel--
264		}
265		var list textList
266		for _, k := range value.SortKeys(v.MapKeys()) {
267			if len(list) == maxLen {
268				list.AppendEllipsis(diffStats{})
269				break
270			}
271			sk := formatMapKey(k, false, ptrs)
272			sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
273			list = append(list, textRecord{Key: sk, Value: sv})
274		}
275
276		out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
277		out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
278		return out
279	case reflect.Ptr:
280		if v.IsNil() {
281			return textNil
282		}
283
284		// Check pointer for cycles.
285		ptrRef, visited := ptrs.Push(v)
286		if visited {
287			out = makeLeafReference(ptrRef, opts.PrintAddresses)
288			return &textWrap{Prefix: "&", Value: out}
289		}
290		defer ptrs.Pop()
291
292		// Skip the name only if this is an unnamed pointer type.
293		// Otherwise taking the address of a value does not reproduce
294		// the named pointer type.
295		if v.Type().Name() == "" {
296			skipType = true // Let the underlying value print the type instead
297		}
298		out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
299		out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
300		out = &textWrap{Prefix: "&", Value: out}
301		return out
302	case reflect.Interface:
303		if v.IsNil() {
304			return textNil
305		}
306		// Interfaces accept different concrete types,
307		// so configure the underlying value to explicitly print the type.
308		return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
309	default:
310		panic(fmt.Sprintf("%v kind not handled", v.Kind()))
311	}
312}
313
314func (opts formatOptions) formatString(prefix, s string) textNode {
315	maxLen := len(s)
316	maxLines := strings.Count(s, "\n") + 1
317	if opts.LimitVerbosity {
318		maxLen = (1 << opts.verbosity()) << 5   // 32, 64, 128, 256, etc...
319		maxLines = (1 << opts.verbosity()) << 2 //  4, 8, 16, 32, 64, etc...
320	}
321
322	// For multiline strings, use the triple-quote syntax,
323	// but only use it when printing removed or inserted nodes since
324	// we only want the extra verbosity for those cases.
325	lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
326	isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
327	for i := 0; i < len(lines) && isTripleQuoted; i++ {
328		lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
329		isPrintable := func(r rune) bool {
330			return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
331		}
332		line := lines[i]
333		isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
334	}
335	if isTripleQuoted {
336		var list textList
337		list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
338		for i, line := range lines {
339			if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
340				comment := commentString(fmt.Sprintf("%d elided lines", numElided))
341				list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
342				break
343			}
344			list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
345		}
346		list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
347		return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
348	}
349
350	// Format the string as a single-line quoted string.
351	if len(s) > maxLen+len(textEllipsis) {
352		return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
353	}
354	return textLine(prefix + formatString(s))
355}
356
357// formatMapKey formats v as if it were a map key.
358// The result is guaranteed to be a single line.
359func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
360	var opts formatOptions
361	opts.DiffMode = diffIdentical
362	opts.TypeMode = elideType
363	opts.PrintAddresses = disambiguate
364	opts.AvoidStringer = disambiguate
365	opts.QualifiedNames = disambiguate
366	opts.VerbosityLevel = maxVerbosityPreset
367	opts.LimitVerbosity = true
368	s := opts.FormatValue(v, reflect.Map, ptrs).String()
369	return strings.TrimSpace(s)
370}
371
372// formatString prints s as a double-quoted or backtick-quoted string.
373func formatString(s string) string {
374	// Use quoted string if it the same length as a raw string literal.
375	// Otherwise, attempt to use the raw string form.
376	qs := strconv.Quote(s)
377	if len(qs) == 1+len(s)+1 {
378		return qs
379	}
380
381	// Disallow newlines to ensure output is a single line.
382	// Only allow printable runes for readability purposes.
383	rawInvalid := func(r rune) bool {
384		return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
385	}
386	if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {
387		return "`" + s + "`"
388	}
389	return qs
390}
391
392// formatHex prints u as a hexadecimal integer in Go notation.
393func formatHex(u uint64) string {
394	var f string
395	switch {
396	case u <= 0xff:
397		f = "0x%02x"
398	case u <= 0xffff:
399		f = "0x%04x"
400	case u <= 0xffffff:
401		f = "0x%06x"
402	case u <= 0xffffffff:
403		f = "0x%08x"
404	case u <= 0xffffffffff:
405		f = "0x%010x"
406	case u <= 0xffffffffffff:
407		f = "0x%012x"
408	case u <= 0xffffffffffffff:
409		f = "0x%014x"
410	case u <= 0xffffffffffffffff:
411		f = "0x%016x"
412	}
413	return fmt.Sprintf(f, u)
414}
415