aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/pkg/errors/stack.go
blob: b485761a7cbe26a523ed0fc2cffc2f2e1d6ab91f (plain) (tree)


























































































































































































                                                                                           
package errors

import (
    "fmt"
    "io"
    "path"
    "runtime"
    "strings"
)

// Frame represents a program counter inside a stack frame.
type Frame uintptr

// pc returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f Frame) pc() uintptr { return uintptr(f) - 1 }

// file returns the full path to the file that contains the
// function for this Frame's pc.
func (f Frame) file() string {
    fn := runtime.FuncForPC(f.pc())
    if fn == nil {
        return "unknown"
    }
    file, _ := fn.FileLine(f.pc())
    return file
}

// line returns the line number of source code of the
// function for this Frame's pc.
func (f Frame) line() int {
    fn := runtime.FuncForPC(f.pc())
    if fn == nil {
        return 0
    }
    _, line := fn.FileLine(f.pc())
    return line
}

// Format formats the frame according to the fmt.Formatter interface.
//
//    %s    source file
//    %d    source line
//    %n    function name
//    %v    equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
//    %+s   function name and path of source file relative to the compile time
//          GOPATH separated by \n\t (<funcname>\n\t<path>)
//    %+v   equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
    switch verb {
    case 's':
        switch {
        case s.Flag('+'):
            pc := f.pc()
            fn := runtime.FuncForPC(pc)
            if fn == nil {
                io.WriteString(s, "unknown")
            } else {
                file, _ := fn.FileLine(pc)
                fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
            }
        default:
            io.WriteString(s, path.Base(f.file()))
        }
    case 'd':
        fmt.Fprintf(s, "%d", f.line())
    case 'n':
        name := runtime.FuncForPC(f.pc()).Name()
        io.WriteString(s, funcname(name))
    case 'v':
        f.Format(s, 's')
        io.WriteString(s, ":")
        f.Format(s, 'd')
    }
}

// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame

// Format formats the stack of Frames according to the fmt.Formatter interface.
//
//    %s    lists source files for each Frame in the stack
//    %v    lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
//    %+v   Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
    switch verb {
    case 'v':
        switch {
        case s.Flag('+'):
            for _, f := range st {
                fmt.Fprintf(s, "\n%+v", f)
            }
        case s.Flag('#'):
            fmt.Fprintf(s, "%#v", []Frame(st))
        default:
            fmt.Fprintf(s, "%v", []Frame(st))
        }
    case 's':
        fmt.Fprintf(s, "%s", []Frame(st))
    }
}

// stack represents a stack of program counters.
type stack []uintptr

func (s *stack) Format(st fmt.State, verb rune) {
    switch verb {
    case 'v':
        switch {
        case st.Flag('+'):
            for _, pc := range *s {
                f := Frame(pc)
                fmt.Fprintf(st, "\n%+v", f)
            }
        }
    }
}

func (s *stack) StackTrace() StackTrace {
    f := make([]Frame, len(*s))
    for i := 0; i < len(f); i++ {
        f[i] = Frame((*s)[i])
    }
    return f
}

func callers() *stack {
    const depth = 32
    var pcs [depth]uintptr
    n := runtime.Callers(3, pcs[:])
    var st stack = pcs[0:n]
    return &st
}

// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
    i := strings.LastIndex(name, "/")
    name = name[i+1:]
    i = strings.Index(name, ".")
    return name[i+1:]
}

func trimGOPATH(name, file string) string {
    // Here we want to get the source file path relative to the compile time
    // GOPATH. As of Go 1.6.x there is no direct way to know the compiled
    // GOPATH at runtime, but we can infer the number of path segments in the
    // GOPATH. We note that fn.Name() returns the function name qualified by
    // the import path, which does not include the GOPATH. Thus we can trim
    // segments from the beginning of the file path until the number of path
    // separators remaining is one more than the number of path separators in
    // the function name. For example, given:
    //
    //    GOPATH     /home/user
    //    file       /home/user/src/pkg/sub/file.go
    //    fn.Name()  pkg/sub.Type.Method
    //
    // We want to produce:
    //
    //    pkg/sub/file.go
    //
    // From this we can easily see that fn.Name() has one less path separator
    // than our desired output. We count separators from the end of the file
    // path until it finds two more than in the function name and then move
    // one character forward to preserve the initial path segment without a
    // leading separator.
    const sep = "/"
    goal := strings.Count(name, sep) + 2
    i := len(file)
    for n := 0; n < goal; n++ {
        i = strings.LastIndex(file[:i], sep)
        if i == -1 {
            // not enough separators found, set i so that the slice expression
            // below leaves file unmodified
            i = -len(sep)
            break
        }
    }
    // get back to 0 or trim the leading separator
    file = file[i+len(sep):]
    return file
}