report_reflect.go 13 KB

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