report_reflect.go 13 KB

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