...

Source file src/github.com/go-errors/errors/stackframe.go

Documentation: github.com/go-errors/errors

     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  	source, err := frame.sourceLine()
    67  	if err != nil {
    68  		return source, New(err)
    69  	}
    70  	return source, err
    71  }
    72  
    73  func (frame *StackFrame) sourceLine() (string, error) {
    74  	if frame.LineNumber <= 0 {
    75  		return "???", nil
    76  	}
    77  
    78  	file, err := os.Open(frame.File)
    79  	if err != nil {
    80  		return "", err
    81  	}
    82  	defer file.Close()
    83  
    84  	scanner := bufio.NewScanner(file)
    85  	currentLine := 1
    86  	for scanner.Scan() {
    87  		if currentLine == frame.LineNumber {
    88  			return string(bytes.Trim(scanner.Bytes(), " \t")), nil
    89  		}
    90  		currentLine++
    91  	}
    92  	if err := scanner.Err(); err != nil {
    93  		return "", err
    94  	}
    95  
    96  	return "???", nil
    97  }
    98  
    99  func packageAndName(fn *runtime.Func) (string, string) {
   100  	name := fn.Name()
   101  	pkg := ""
   102  
   103  	// The name includes the path name to the package, which is unnecessary
   104  	// since the file name is already included.  Plus, it has center dots.
   105  	// That is, we see
   106  	//  runtime/debug.*T·ptrmethod
   107  	// and want
   108  	//  *T.ptrmethod
   109  	// Since the package path might contains dots (e.g. code.google.com/...),
   110  	// we first remove the path prefix if there is one.
   111  	if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
   112  		pkg += name[:lastslash] + "/"
   113  		name = name[lastslash+1:]
   114  	}
   115  	if period := strings.Index(name, "."); period >= 0 {
   116  		pkg += name[:period]
   117  		name = name[period+1:]
   118  	}
   119  
   120  	name = strings.Replace(name, "·", ".", -1)
   121  	return pkg, name
   122  }
   123  

View as plain text