gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

stackframe.go (2891B)


      1 package errors
      2 
      3 import (
      4 	"bufio"
      5 	"bytes"
      6 	"fmt"
      7 	"os"
      8 	"runtime"
      9 	"strings"
     10 )
     11 
     12 // A StackFrame contains all necessary information about to generate a line
     13 // in a callstack.
     14 type StackFrame struct {
     15 	// The path to the file containing this ProgramCounter
     16 	File string
     17 	// The LineNumber in that file
     18 	LineNumber int
     19 	// The Name of the function that contains this ProgramCounter
     20 	Name string
     21 	// The Package that contains this function
     22 	Package string
     23 	// The underlying ProgramCounter
     24 	ProgramCounter uintptr
     25 }
     26 
     27 // NewStackFrame popoulates a stack frame object from the program counter.
     28 func NewStackFrame(pc uintptr) (frame StackFrame) {
     29 
     30 	frame = StackFrame{ProgramCounter: pc}
     31 	if frame.Func() == nil {
     32 		return
     33 	}
     34 	frame.Package, frame.Name = packageAndName(frame.Func())
     35 
     36 	// pc -1 because the program counters we use are usually return addresses,
     37 	// and we want to show the line that corresponds to the function call
     38 	frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
     39 	return
     40 
     41 }
     42 
     43 // Func returns the function that contained this frame.
     44 func (frame *StackFrame) Func() *runtime.Func {
     45 	if frame.ProgramCounter == 0 {
     46 		return nil
     47 	}
     48 	return runtime.FuncForPC(frame.ProgramCounter)
     49 }
     50 
     51 // String returns the stackframe formatted in the same way as go does
     52 // in runtime/debug.Stack()
     53 func (frame *StackFrame) String() string {
     54 	str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
     55 
     56 	source, err := frame.SourceLine()
     57 	if err != nil {
     58 		return str
     59 	}
     60 
     61 	return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
     62 }
     63 
     64 // SourceLine gets the line of code (from File and Line) of the original source if possible.
     65 func (frame *StackFrame) SourceLine() (string, error) {
     66 	if frame.LineNumber <= 0 {
     67 		return "???", nil
     68 	}
     69 
     70 	file, err := os.Open(frame.File)
     71 	if err != nil {
     72 		return "", New(err)
     73 	}
     74 	defer file.Close()
     75 
     76 	scanner := bufio.NewScanner(file)
     77 	currentLine := 1
     78 	for scanner.Scan() {
     79 		if currentLine == frame.LineNumber {
     80 			return string(bytes.Trim(scanner.Bytes(), " \t")), nil
     81 		}
     82 		currentLine++
     83 	}
     84 	if err := scanner.Err(); err != nil {
     85 		return "", New(err)
     86 	}
     87 
     88 	return "???", nil
     89 }
     90 
     91 func packageAndName(fn *runtime.Func) (string, string) {
     92 	name := fn.Name()
     93 	pkg := ""
     94 
     95 	// The name includes the path name to the package, which is unnecessary
     96 	// since the file name is already included.  Plus, it has center dots.
     97 	// That is, we see
     98 	//  runtime/debug.*T·ptrmethod
     99 	// and want
    100 	//  *T.ptrmethod
    101 	// Since the package path might contains dots (e.g. code.google.com/...),
    102 	// we first remove the path prefix if there is one.
    103 	if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
    104 		pkg += name[:lastslash] + "/"
    105 		name = name[lastslash+1:]
    106 	}
    107 	if period := strings.Index(name, "."); period >= 0 {
    108 		pkg += name[:period]
    109 		name = name[period+1:]
    110 	}
    111 
    112 	name = strings.Replace(name, "·", ".", -1)
    113 	return pkg, name
    114 }