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