delve/service/api/prettyprint.go

655 lines
17 KiB
Go

package api
import (
"bytes"
"fmt"
"io"
"math"
"reflect"
"strconv"
"strings"
"text/tabwriter"
)
const (
// strings longer than this will cause slices, arrays and structs to be printed on multiple lines when newlines is enabled
maxShortStringLen = 7
// string used for one indentation level (when printing on multiple lines)
indentString = "\t"
)
type prettyFlags uint8
const (
prettyTop prettyFlags = 1 << iota
prettyNewlines
prettyIncludeType
prettyShortenType
)
func (flags prettyFlags) top() bool { return flags&prettyTop != 0 }
func (flags prettyFlags) includeType() bool { return flags&prettyIncludeType != 0 }
func (flags prettyFlags) newlines() bool { return flags&prettyNewlines != 0 }
func (flags prettyFlags) shortenType() bool { return flags&prettyShortenType != 0 }
func (flags prettyFlags) set(flag prettyFlags, v bool) prettyFlags {
if v {
return flags | flag
} else {
return flags &^ flag
}
}
// SinglelineString returns a representation of v on a single line.
func (v *Variable) SinglelineString() string {
var buf bytes.Buffer
v.writeTo(&buf, prettyTop|prettyIncludeType, "", "")
return buf.String()
}
// SinglelineStringWithShortTypes returns a representation of v on a single line, with types shortened.
func (v *Variable) SinglelineStringWithShortTypes() string {
var buf bytes.Buffer
v.writeTo(&buf, prettyTop|prettyIncludeType|prettyShortenType, "", "")
return buf.String()
}
// SinglelineStringFormatted returns a representation of v on a single line, using the format specified by fmtstr.
func (v *Variable) SinglelineStringFormatted(fmtstr string) string {
var buf bytes.Buffer
v.writeTo(&buf, prettyTop|prettyIncludeType, "", fmtstr)
return buf.String()
}
// MultilineString returns a representation of v on multiple lines.
func (v *Variable) MultilineString(indent, fmtstr string) string {
var buf bytes.Buffer
v.writeTo(&buf, prettyTop|prettyNewlines|prettyIncludeType, indent, fmtstr)
return buf.String()
}
func (v *Variable) typeStr(flags prettyFlags) string {
if flags.shortenType() {
return ShortenType(v.Type)
}
return v.Type
}
func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if v.Unreadable != "" {
fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable)
return
}
if !flags.top() && v.Addr == 0 && v.Value == "" {
if flags.includeType() && v.Type != "void" {
fmt.Fprintf(buf, "%s nil", v.typeStr(flags))
} else {
fmt.Fprint(buf, "nil")
}
return
}
switch v.Kind {
case reflect.Slice:
v.writeSliceTo(buf, flags, indent, fmtstr)
case reflect.Array:
v.writeArrayTo(buf, flags, indent, fmtstr)
case reflect.Ptr:
if v.Type == "" || len(v.Children) == 0 {
fmt.Fprint(buf, "nil")
} else if v.Children[0].OnlyAddr && v.Children[0].Addr != 0 {
v.writePointerTo(buf, flags)
} else {
if flags.top() && flags.newlines() && v.Children[0].Addr != 0 {
v.writePointerTo(buf, flags)
fmt.Fprint(buf, "\n")
}
fmt.Fprint(buf, "*")
v.Children[0].writeTo(buf, flags.set(prettyTop, false), indent, fmtstr)
}
case reflect.UnsafePointer:
if len(v.Children) == 0 {
fmt.Fprintf(buf, "unsafe.Pointer(nil)")
} else {
fmt.Fprintf(buf, "unsafe.Pointer(%#x)", v.Children[0].Addr)
}
case reflect.Chan:
if flags.newlines() {
v.writeStructTo(buf, flags, indent, fmtstr)
} else {
if len(v.Children) == 0 {
fmt.Fprintf(buf, "%s nil", v.typeStr(flags))
} else {
fmt.Fprintf(buf, "%s %s/%s", v.typeStr(flags), v.Children[0].Value, v.Children[1].Value)
}
}
case reflect.Struct:
if v.Value != "" {
fmt.Fprintf(buf, "%s(%s)", v.typeStr(flags), v.Value)
flags = flags.set(prettyIncludeType, false)
}
v.writeStructTo(buf, flags, indent, fmtstr)
case reflect.Interface:
if v.Addr == 0 {
// an escaped interface variable that points to nil, this shouldn't
// happen in normal code but can happen if the variable is out of scope.
fmt.Fprintf(buf, "nil")
return
}
if flags.includeType() {
if v.Children[0].Kind == reflect.Invalid {
fmt.Fprintf(buf, "%s ", v.typeStr(flags))
if v.Children[0].Addr == 0 {
fmt.Fprint(buf, "nil")
return
}
} else {
fmt.Fprintf(buf, "%s(%s) ", v.typeStr(flags), v.Children[0].Type)
}
}
data := v.Children[0]
if data.Kind == reflect.Ptr {
if len(data.Children) == 0 {
fmt.Fprint(buf, "...")
} else if data.Children[0].Addr == 0 {
fmt.Fprint(buf, "nil")
} else if data.Children[0].OnlyAddr {
fmt.Fprintf(buf, "0x%x", v.Children[0].Addr)
} else {
v.Children[0].writeTo(buf, flags.set(prettyTop, false).set(prettyIncludeType, !flags.includeType()), indent, fmtstr)
}
} else if data.OnlyAddr {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "*(*%q)(%#x)", v.typeStr(flags), v.Addr)
} else {
fmt.Fprintf(buf, "*(*%s)(%#x)", v.typeStr(flags), v.Addr)
}
} else {
v.Children[0].writeTo(buf, flags.set(prettyTop, false).set(prettyIncludeType, !flags.includeType()), indent, fmtstr)
}
case reflect.Map:
v.writeMapTo(buf, flags, indent, fmtstr)
case reflect.Func:
if v.Value == "" {
fmt.Fprint(buf, "nil")
} else {
fmt.Fprintf(buf, "%s", v.Value)
if flags.newlines() && len(v.Children) > 0 {
fmt.Fprintf(buf, " {\n")
for i := range v.Children {
fmt.Fprintf(buf, "%s%s%s %s = ", indent, indentString, v.Children[i].Name, v.Children[i].typeStr(flags))
v.Children[i].writeTo(buf, flags.set(prettyTop, false).set(prettyIncludeType, false), indent+indentString, fmtstr)
fmt.Fprintf(buf, "\n")
}
fmt.Fprintf(buf, "%s}", indent)
}
}
default:
v.writeBasicType(buf, fmtstr)
}
}
func (v *Variable) writePointerTo(buf io.Writer, flags prettyFlags) {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "(%q)(%#x)", v.typeStr(flags), v.Children[0].Addr)
} else {
fmt.Fprintf(buf, "(%s)(%#x)", v.typeStr(flags), v.Children[0].Addr)
}
}
func (v *Variable) writeBasicType(buf io.Writer, fmtstr string) {
if v.Value == "" && v.Kind != reflect.String {
fmt.Fprintf(buf, "(unknown %s)", v.Kind)
return
}
switch v.Kind {
case reflect.Bool:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
var b bool = v.Value == "true"
fmt.Fprintf(buf, fmtstr, b)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
n, _ := strconv.ParseInt(ExtractIntValue(v.Value), 10, 64)
fmt.Fprintf(buf, fmtstr, n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
n, _ := strconv.ParseUint(ExtractIntValue(v.Value), 10, 64)
fmt.Fprintf(buf, fmtstr, n)
case reflect.Float32, reflect.Float64:
if fmtstr == "" {
buf.Write([]byte(v.Value))
return
}
x, _ := strconv.ParseFloat(v.Value, 64)
fmt.Fprintf(buf, fmtstr, x)
case reflect.Complex64, reflect.Complex128:
if fmtstr == "" {
fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value)
return
}
real, _ := strconv.ParseFloat(v.Children[0].Value, 64)
imag, _ := strconv.ParseFloat(v.Children[1].Value, 64)
var x complex128 = complex(real, imag)
fmt.Fprintf(buf, fmtstr, x)
case reflect.String:
if fmtstr == "" {
s := v.Value
if len(s) != int(v.Len) {
s = fmt.Sprintf("%s...+%d more", s, int(v.Len)-len(s))
}
fmt.Fprintf(buf, "%q", s)
return
}
fmt.Fprintf(buf, fmtstr, v.Value)
}
}
func ExtractIntValue(s string) string {
if s == "" || s[len(s)-1] != ')' {
return s
}
open := strings.LastIndex(s, "(")
if open < 0 {
return s
}
return s[open+1 : len(s)-1]
}
func (v *Variable) writeSliceTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if flags.includeType() {
fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.typeStr(flags), v.Len, v.Cap)
}
if v.Base == 0 && len(v.Children) == 0 {
fmt.Fprintf(buf, "nil")
return
}
v.writeSliceOrArrayTo(buf, flags, indent, fmtstr)
}
func (v *Variable) writeArrayTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if flags.includeType() {
fmt.Fprintf(buf, "%s ", v.typeStr(flags))
}
v.writeSliceOrArrayTo(buf, flags, indent, fmtstr)
}
func (v *Variable) writeStructTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if int(v.Len) != len(v.Children) && len(v.Children) == 0 {
if strings.Contains(v.Type, "/") {
fmt.Fprintf(buf, "(*%q)(%#x)", v.typeStr(flags), v.Addr)
} else {
fmt.Fprintf(buf, "(*%s)(%#x)", v.typeStr(flags), v.Addr)
}
return
}
if flags.includeType() {
fmt.Fprintf(buf, "%s ", v.typeStr(flags))
}
nl := v.shouldNewlineStruct(flags.newlines())
fmt.Fprint(buf, "{")
for i := range v.Children {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
fmt.Fprintf(buf, "%s: ", v.Children[i].Name)
v.Children[i].writeTo(buf, prettyIncludeType.set(prettyNewlines, nl), indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ",")
if !nl {
fmt.Fprint(buf, " ")
}
}
}
if len(v.Children) != int(v.Len) {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
}
fmt.Fprint(buf, "}")
}
func (v *Variable) writeMapTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
if flags.includeType() {
fmt.Fprintf(buf, "%s ", v.typeStr(flags))
}
if v.Base == 0 && len(v.Children) == 0 {
fmt.Fprintf(buf, "nil")
return
}
nl := flags.newlines() && (len(v.Children) > 0)
fmt.Fprint(buf, "[")
for i := 0; i < len(v.Children); i += 2 {
key := &v.Children[i]
value := &v.Children[i+1]
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
key.writeTo(buf, 0, indent+indentString, fmtstr)
fmt.Fprint(buf, ": ")
value.writeTo(buf, prettyFlags(0).set(prettyNewlines, nl), indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ", ")
}
}
if len(v.Children)/2 != int(v.Len) {
if len(v.Children) != 0 {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-(len(v.Children)/2))
} else {
fmt.Fprint(buf, "...")
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprint(buf, "]")
}
func (v *Variable) shouldNewlineArray(newlines bool) bool {
if !newlines || len(v.Children) == 0 {
return false
}
kind, hasptr := (&v.Children[0]).recursiveKind()
switch kind {
case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map, reflect.Interface:
return true
case reflect.String:
if hasptr {
return true
}
for i := range v.Children {
if len(v.Children[i].Value) > maxShortStringLen {
return true
}
}
return false
default:
return false
}
}
func (v *Variable) recursiveKind() (reflect.Kind, bool) {
hasptr := false
var kind reflect.Kind
for {
kind = v.Kind
if kind == reflect.Ptr {
hasptr = true
if len(v.Children) == 0 {
return kind, hasptr
}
v = &(v.Children[0])
} else {
break
}
}
return kind, hasptr
}
func (v *Variable) shouldNewlineStruct(newlines bool) bool {
if !newlines || len(v.Children) == 0 {
return false
}
for i := range v.Children {
kind, hasptr := (&v.Children[i]).recursiveKind()
switch kind {
case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map, reflect.Interface:
return true
case reflect.String:
if hasptr {
return true
}
if len(v.Children[i].Value) > maxShortStringLen {
return true
}
}
}
return false
}
func (v *Variable) writeSliceOrArrayTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
nl := v.shouldNewlineArray(flags.newlines())
fmt.Fprint(buf, "[")
for i := range v.Children {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
v.Children[i].writeTo(buf, prettyFlags(0).set(prettyNewlines, nl), indent+indentString, fmtstr)
if i != len(v.Children)-1 || nl {
fmt.Fprint(buf, ",")
}
}
if len(v.Children) != int(v.Len) {
if len(v.Children) != 0 {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
} else {
fmt.Fprint(buf, ",")
}
fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children))
} else {
fmt.Fprint(buf, "...")
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprint(buf, "]")
}
// PrettyExamineMemory examine the memory and format data
//
// `format` specifies the data format (or data type), `size` specifies size of each data,
// like 4byte integer, 1byte character, etc. `count` specifies the number of values.
func PrettyExamineMemory(address uintptr, memArea []byte, isLittleEndian bool, format byte, size int) string {
var (
cols int
colFormat string
colBytes = size
addrLen int
addrFmt string
)
// Different versions of golang output differently about '#'.
// See https://ci.appveyor.com/project/derekparker/delve-facy3/builds/30179356.
switch format {
case 'b':
cols = 4 // Avoid emitting rows that are too long when using binary format
colFormat = fmt.Sprintf("%%0%db", colBytes*8)
case 'o':
cols = 8
colFormat = fmt.Sprintf("0%%0%do", colBytes*3) // Always keep one leading zero for octal.
case 'd':
cols = 8
colFormat = fmt.Sprintf("%%0%dd", colBytes*3)
case 'x':
cols = 8
colFormat = fmt.Sprintf("0x%%0%dx", colBytes*2) // Always keep one leading '0x' for hex.
default:
return fmt.Sprintf("not supported format %q\n", string(format))
}
colFormat += "\t"
l := len(memArea)
rows := l / (cols * colBytes)
if l%(cols*colBytes) != 0 {
rows++
}
// Avoid the lens of two adjacent address are different, so always use the last addr's len to format.
if l != 0 {
addrLen = len(fmt.Sprintf("%x", uint64(address)+uint64(l)))
}
addrFmt = "0x%0" + strconv.Itoa(addrLen) + "x:\t"
var b strings.Builder
w := tabwriter.NewWriter(&b, 0, 0, 3, ' ', 0)
for i := 0; i < rows; i++ {
fmt.Fprintf(w, addrFmt, address)
for j := 0; j < cols; j++ {
offset := i*(cols*colBytes) + j*colBytes
if offset+colBytes <= len(memArea) {
n := byteArrayToUInt64(memArea[offset:offset+colBytes], isLittleEndian)
fmt.Fprintf(w, colFormat, n)
}
}
fmt.Fprintln(w, "")
address += uintptr(cols * colBytes)
}
w.Flush()
return b.String()
}
func byteArrayToUInt64(buf []byte, isLittleEndian bool) uint64 {
var n uint64
if isLittleEndian {
for i := len(buf) - 1; i >= 0; i-- {
n = n<<8 + uint64(buf[i])
}
} else {
for i := 0; i < len(buf); i++ {
n = n<<8 + uint64(buf[i])
}
}
return n
}
const stacktraceTruncatedMessage = "(truncated)"
func digits(n int) int {
if n <= 0 {
return 1
}
return int(math.Floor(math.Log10(float64(n)))) + 1
}
type StackTraceColors struct {
FunctionColor string
BasenameColor string
NormalColor string
}
func PrintStack(formatPath func(string) string, out io.Writer, stack []Stackframe, ind string, offsets bool, stc StackTraceColors, include func(Stackframe) bool) {
if len(stack) == 0 {
return
}
extranl := offsets
for i := range stack {
if extranl {
break
}
extranl = extranl || (len(stack[i].Defers) > 0) || (len(stack[i].Arguments) > 0) || (len(stack[i].Locals) > 0)
}
fileLine := func(file string, line int) string {
file = formatPath(file)
if stc.BasenameColor == "" {
return fmt.Sprintf("%s:%d", file, line)
}
slash := strings.LastIndex(file, "/")
if slash < 0 {
slash = 0
} else {
slash++
}
return fmt.Sprintf("%s%s%s:%d%s", file[:slash], stc.BasenameColor, file[slash:], line, stc.NormalColor)
}
d := digits(len(stack) - 1)
fmtstr := "%s%" + strconv.Itoa(d) + "d 0x%016x in " + stc.FunctionColor + "%s" + stc.NormalColor + "\n"
s := ind + strings.Repeat(" ", d+2+len(ind))
for i := range stack {
if !include(stack[i]) {
continue
}
if stack[i].Err != "" {
fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err)
continue
}
fmt.Fprintf(out, fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
fmt.Fprintf(out, "%sat %s\n", s, fileLine(stack[i].File, stack[i].Line))
if offsets {
fmt.Fprintf(out, "%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
}
for j, d := range stack[i].Defers {
deferHeader := fmt.Sprintf("%s defer %d: ", s, j+1)
s2 := strings.Repeat(" ", len(deferHeader))
if d.Unreadable != "" {
fmt.Fprintf(out, "%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
continue
}
fmt.Fprintf(out, "%s%#016x in %s%s%s\n", deferHeader, d.DeferredLoc.PC, stc.FunctionColor, d.DeferredLoc.Function.Name(), stc.NormalColor)
fmt.Fprintf(out, "%sat %s:%d\n", s2, formatPath(d.DeferredLoc.File), d.DeferredLoc.Line)
fmt.Fprintf(out, "%sdeferred by %s at %s\n", s2, d.DeferLoc.Function.Name(), fileLine(d.DeferLoc.File, d.DeferLoc.Line))
}
for j := range stack[i].Arguments {
fmt.Fprintf(out, "%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
}
for j := range stack[i].Locals {
fmt.Fprintf(out, "%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
}
if extranl {
fmt.Fprintln(out)
}
}
if len(stack) > 0 && !stack[len(stack)-1].Bottom {
fmt.Fprintf(out, "%s"+stacktraceTruncatedMessage+"\n", ind)
}
}