blob: 8a23dea35ceb93edc90ec5c5cb77c3b27b298017 [file] [log] [blame]
// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build debug
package errors // import "upspin.io/errors"
import (
"bytes"
"fmt"
"runtime"
"strings"
)
// stack is a type that is embedded in an Error struct, and contains
// information about the stack that created that Error.
type stack struct {
callers []uintptr
// TODO(adg): add time of creation
}
// populateStack uses the runtime to populate the Error's stack struct with
// information about the current stack. It should be called from the E
// function, when the Error is being created.
// If the Error has another Error value in its Err field, populateStack
// coalesces the stack from the inner error (if any) with the current stack,
// so that any given Error value only prints one stack.
func (e *Error) populateStack() {
e.callers = callers()
e2, ok := e.Err.(*Error)
if !ok {
return
}
// Move distinct callers from inner error to outer error
// (and throw the common callers away)
// so that we only print the stack trace once.
i := 0
ok = false
for ; i < len(e.callers) && i < len(e2.callers); i++ {
if e.callers[len(e.callers)-1-i] != e2.callers[len(e2.callers)-1-i] {
break
}
ok = true
}
if ok { // The stacks have some PCs in common.
head := e2.callers[:len(e2.callers)-i]
tail := e.callers
e.callers = make([]uintptr, len(head)+len(tail))
copy(e.callers, head)
copy(e.callers[len(head):], tail)
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 // Should never happen, and this is just debugging.
}
}
return &f
}
// printStack formats and prints the stack for this Error to the given buffer.
// It should be called from the Error's Error method.
func (e *Error) printStack(b *bytes.Buffer) {
printCallers := callers()
// Iterate backward through e.callers (the last in the stack is the
// earliest call, such as main) skipping over the PCs that are shared
// by the error stack and by this function call stack, printing the
// names of the functions and their file names and line numbers.
var prev string // the name of the last-seen function
var diff bool // do the print and error call stacks differ now?
for i := 0; i < len(e.callers); i++ {
thisFrame := frame(e.callers, i)
name := thisFrame.Func.Name()
if !diff && i < len(printCallers) {
if name == frame(printCallers, i).Func.Name() {
// both stacks share this PC, skip it.
continue
}
// No match, don't consider printCallers again.
diff = true
}
// Don't print the same function twice.
// (Can happen when multiple error stacks have been coalesced.)
if name == prev {
continue
}
// Find the uncommon prefix between this and the previous
// function name, separating by dots and slashes.
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.
pad(b, Separator)
fmt.Fprintf(b, "%v:%d: ", thisFrame.File, thisFrame.Line)
if trim > 0 {
b.WriteString("...")
}
b.WriteString(name[trim:])
prev = name
}
}
// callers is a wrapper for runtime.Callers that allocates a slice.
func callers() []uintptr {
var stk [64]uintptr
const skip = 4 // Skip 4 stack frames; ok for both E and Error funcs.
n := runtime.Callers(skip, stk[:])
return stk[:n]
}