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 !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}