summaryrefslogtreecommitdiff
path: root/errors/debug_stack.go
blob: fa77ddc8a9e92ef7708d9b271f77a708b84dbd58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// +build debug

package errors

import (
	"bytes"
	"fmt"
	"runtime"
	"strings"
)

type stack struct {
	callers []uintptr
	// TODO(adg): add time of creation
}

func (e *Error) populateStack() {
	e.callers = callers()

	e2, ok := e.Err.(*Error)
	if !ok {
		return
	}

	i := 0

	ok = false
	for ; i < len(e.callers) && i < len(e2.callers); i++ {
		// check for similar
		if e.callers[len(e.callers)-1-i] != e2.callers[len(e2.callers)-1-i] {
			break
		}
		ok = true
	}

	if ok { //we have common PCs
		e2Head := e2.callers[:len(e2.callers)-i]
		eTail := e.callers

		e.callers = make([]uintptr, len(e2Head)+len(eTail))

		copy(e.callers, e2Head)
		copy(e.callers[len(e2Head):], eTail)

		e2.callers = nil
	}
}

// frame returns the nth frame, with the frame at top of stack being 0.
func frame(callers []uintptr, n int) runtime.Frame {
	frames := runtime.CallersFrames(callers)
	var f runtime.Frame
	for i := len(callers) - 1; i >= n; i-- {
		var ok bool
		f, ok = frames.Next()
		if !ok {
			break
		}
	}
	return f
}

func (e *Error) printStack(b *bytes.Buffer) {
	c := callers()

	var prev string
	var diff bool
	for i := 0; i < len(e.callers); i++ {
		pc := e.callers[len(e.callers)-i-1] // get current PC
		fn := runtime.FuncForPC(pc)         // get function by pc
		name := fn.Name()

		if !diff && i < len(c) {
			ppc := c[len(c)-i-1]
			pname := runtime.FuncForPC(ppc).Name()
			if name == pname {
				continue
			}
			diff = true
		}

		if name == prev {
			continue
		}

		trim := 0
		for {
			j := strings.IndexAny(name[trim:], "./")
			if j < 0 {
				break
			}
			if !strings.HasPrefix(prev, name[:j+trim]) {
				break
			}
			trim += j + 1 // skip over the separator
		}

		// Do the printing.
		appendStrToBuf(b, Separator)
		file, line := fn.FileLine(pc)
		fmt.Fprintf(b, "%v:%d: ", file, line)
		if trim > 0 {
			b.WriteString("...")
		}
		b.WriteString(name[trim:])

		prev = name
	}
}

func callers() []uintptr {
	var stk [64]uintptr
	const skip = 4
	n := runtime.Callers(skip, stk[:])
	return stk[:n]
}