github.com/bookerzzz/grok@v0.0.0/stack.go (about)

     1  package grok
     2  
     3  import (
     4  	"runtime/debug"
     5  	"bytes"
     6  	"bufio"
     7  	"os"
     8  	"strings"
     9  	"io/ioutil"
    10  	"io"
    11  	"sync"
    12  	"fmt"
    13  	"regexp"
    14  	"strconv"
    15  	"os/exec"
    16  	"unsafe"
    17  	"reflect"
    18  )
    19  
    20  const (
    21  	newline = '\n'
    22  	tab = '\t'
    23  	help = `Interactive stack:
    24  
    25  > help (h)          prints this help menu
    26  > exit (x)          exits the interactive stack
    27  > show (?)          repeats the current position
    28  > quick (q)         summary of the entire stack
    29  > next (n)          next item in the stack
    30  > prev (p)          previous item in the stack
    31  > top (t)           go to the top of the stack
    32  > jump (j) {index}  jumps to the entry at the given index
    33  > dump (d) {index}  prints a dump of the value corresponding to {index} of the current stack entry
    34  
    35  `
    36  )
    37  var (
    38  	stackMutex = sync.Mutex{}
    39  	rxPointA   = regexp.MustCompile(`^(?P<Package>.+)\.(\(?P<Receiver>([^\)]+)\)\.)(?P<Func>[^\(]+)\((?P<Args>[^\)]*)\)\s*\t(?P<File>[^:]+):(?P<Line>[0-9]+)\s(?P<Identifier>\+0x[0-9a-f]+)$`)
    40  	rxPointB   = regexp.MustCompile(`^(?P<Package>.+)\.(?P<Func>[^\(]+)\((?P<Args>[^\)]*)\)\s*\t(?P<File>[^:]+):(?P<Line>[0-9]+) (?P<Identifier>\+0x[0-9a-f]+)$`)
    41  	rxPointC   = regexp.MustCompile(`^(?P<Package>.+)\.(\(?P<Receiver>([^\)]+)\)\.)(?P<Func>[^\(]+)\s*\t(?P<File>[^:]+):(?P<Line>[0-9]+)\s(?P<Identifier>\+0x[0-9a-f]+)$`)
    42  	rxPointD   = regexp.MustCompile(`^(?P<Package>.+)\.(?P<Func>[^\(]+)\s*\t(?P<File>[^:]+):(?P<Line>[0-9]+)\s(?P<Identifier>\+0x[0-9a-f]+)$`)
    43  )
    44  
    45  type point struct {
    46  	Position   string
    47  	Package    string
    48  	Receiver   string
    49  	Func       string
    50  	File       string
    51  	Line       string
    52  	Args       []arg
    53  	Identifier string
    54  }
    55  
    56  type arg struct {
    57  	Hex string
    58  	Pointer uintptr
    59  	Value interface{}
    60  	Err error
    61  }
    62  
    63  func Stack(options ...Option) {
    64  	c := defaults
    65  	for _, o := range options {
    66  		o(&c)
    67  	}
    68  
    69  	// only one stack at a time
    70  	stackMutex.Lock()
    71  	defer stackMutex.Unlock()
    72  
    73  	colour := colourer(c)
    74  	out := outer(c)
    75  	points := stack(c)
    76  
    77  	for _, p := range points {
    78  		args := ""
    79  		for i, a := range p.Args {
    80  			if i > 0 {
    81  				args = args + ", "
    82  			}
    83  			args = args + colour(a.Hex, colourRed)
    84  		}
    85  		out("%s(%s) %s:%s\n", colour(p.Func, colourGreen), args, colour(p.File, colourBlue), colour(p.Line, colourRed))
    86  	}
    87  }
    88  
    89  func stack(c Conf) []point {
    90  	points := []point{}
    91  	stack := debug.Stack()
    92  	r := bytes.NewReader(stack)
    93  
    94  	lines := [][]byte{}
    95  	line := []byte{}
    96  	for {
    97  		b, err := r.ReadByte()
    98  		if err != nil {
    99  			lines = append(lines, line)
   100  			break;
   101  		}
   102  		if b == newline {
   103  			lines = append(lines, line)
   104  			line = []byte{}
   105  			continue;
   106  		}
   107  		line = append(line, b)
   108  	}
   109  
   110  	for i := 1; i < len(lines); i = i+2 {
   111  		// Skip stack creation lines
   112  		if i > 0 && i <= 6 {
   113  			continue
   114  		}
   115  		// skip empty lines
   116  		if len(lines[i]) == 0 {
   117  			continue
   118  		}
   119  		p := point{
   120  			Position: fmt.Sprintf("%d", len(points)),
   121  		}
   122  		l := append(lines[i], lines[i+1]...)
   123  		for _, rx := range []*regexp.Regexp{rxPointA, rxPointB, rxPointC, rxPointD} {
   124  			if rx.Match(l) {
   125  				matches := rx.FindSubmatch(l)
   126  				names := rx.SubexpNames()
   127  				for k, m := range matches {
   128  					n := names[k]
   129  					switch n {
   130  					case "Package":
   131  						p.Package = string(m)
   132  					case "Receiver":
   133  						p.Receiver = string(m)
   134  					case "Func":
   135  						p.Func = string(m)
   136  					case "Args":
   137  						p.Args = []arg{}
   138  						args := bytes.Split(m, []byte(", "))
   139  						for _, h := range args {
   140  							if len(h) == 0 {
   141  								continue
   142  							}
   143  							a := arg{
   144  								Hex: string(h),
   145  							}
   146  							i, err := strconv.ParseUint(string(h[2:]), 16, 64)
   147  							if err != nil {
   148  								a.Err = err
   149  								p.Args = append(p.Args, a)
   150  								continue
   151  							}
   152  							a.Pointer = uintptr(i)
   153  							//v := reflect.Indirect(reflect.ValueOf((*interface{})(unsafe.Pointer(a.Pointer))))
   154  							v := unsafe.Pointer(a.Pointer)
   155  							if v != nil {
   156  								a.Value = v
   157  							}
   158  							p.Args = append(p.Args, a)
   159  						}
   160  					case "File":
   161  						p.File = string(m)
   162  					case "Line":
   163  						p.Line = string(m)
   164  					case "Identifier":
   165  						p.Identifier = string(m)
   166  					}
   167  				}
   168  			}
   169  		}
   170  		points = append(points, p)
   171  	}
   172  	return points
   173  }
   174  
   175  func hijackStdOutput() (original io.Writer, done func()) {
   176  	stdout := *os.Stdout
   177  	stderr := *os.Stderr
   178  	r, w, _ := os.Pipe()
   179  	*os.Stdout = *w
   180  	*os.Stderr = *w
   181  
   182  	return &stdout, func() {
   183  		w.Close()
   184  		captured, _ := ioutil.ReadAll(r)
   185  		*os.Stdout = stdout
   186  		*os.Stderr = stderr
   187  		os.Stdout.Write(captured)
   188  	}
   189  }
   190  
   191  func InteractiveStack(options ...Option) {
   192  	stackMutex.Lock()
   193  	defer stackMutex.Unlock()
   194  
   195  	stdout, done := hijackStdOutput()
   196  	defer done()
   197  
   198  	c := defaults
   199  	WithWriter(stdout)(&c)
   200  	for _, o := range options {
   201  		o(&c)
   202  	}
   203  	colour := colourer(c)
   204  	out := outer(c)
   205  	index := 0
   206  
   207  	points := stack(c)
   208  	reader := bufio.NewReader(os.Stdin)
   209  	out(colour(help, colourGreen))
   210  	points[index].out(c)
   211  	for {
   212  		out(colour("> ", colourRed))
   213  		input, err := reader.ReadString(newline)
   214  		if err != nil {
   215  			out(colour("Sorry, couldn't pick up what you just put down.\n", colourRed))
   216  			continue
   217  		}
   218  
   219  		command := ""
   220  		parts := []string{}
   221  		for _, p := range strings.Split(input, " ") {
   222  			p = strings.TrimSpace(p)
   223  			if len(p) == 0 {
   224  				continue
   225  			}
   226  			parts = append(parts, p)
   227  		}
   228  		if len(parts) > 0 {
   229  			command = parts[0]
   230  		}
   231  
   232  		if command == "exit" || command == "x" {
   233  			out(colour("Thank you. Goodbye.\n", colourGreen))
   234  			break;
   235  		}
   236  
   237  		switch command {
   238  		case "show", "?":
   239  			points[index].out(c)
   240  		case "top", "t":
   241  			index = 0
   242  			points[index].out(c)
   243  		case "next", "n":
   244  			if index == len(points) - 1 {
   245  				out(colour("You've reached the end!\n", colourRed))
   246  			} else {
   247  				index = index + 1
   248  			}
   249  			points[index].out(c)
   250  		case "prev", "p":
   251  			if index == 0 {
   252  				out(colour("You've reached the top!\n", colourRed))
   253  			} else {
   254  				index = index - 1
   255  			}
   256  			points[index].out(c)
   257  		case "dump", "d":
   258  			if len(parts) <= 0 {
   259  				out(colour("Not quite sure which arg to dump?!\n", colourRed))
   260  				out(colour(help, colourGreen))
   261  				continue
   262  			}
   263  			i, err := strconv.ParseUint(parts[1], 10, 64)
   264  			if err != nil {
   265  				out(colour("I don't understand %q. It's not a number.\n", colourRed), i)
   266  				continue
   267  			}
   268  			if int(i) < 0 || int(i) > len(points[index].Args) - 1 {
   269  				out(colour("I can't dump it. It's out of range.\n", colourRed))
   270  				continue
   271  			}
   272  			a := points[index].Args[int(i)]
   273  			dump(a.Hex, reflect.ValueOf(a.Value), c)
   274  		case "quick", "q":
   275  			for pi, p := range points {
   276  				args := ""
   277  				for i, a := range p.Args {
   278  					if i > 0 {
   279  						args = args + ", "
   280  					}
   281  					args = args + colour(a.Hex, colourGreen)
   282  				}
   283  				out("%s %s(%s) %s%s\n",colour(fmt.Sprintf("[%d]", pi), colourRed), colour(p.Func, colourBlue), args, colour(p.File, colourBlue), colour(":" + p.Line, colourRed))
   284  			}
   285  		case "jump", "j":
   286  			if len(parts) <= 0 {
   287  				out(colour("Not quite sure what I need to jump to?!\n", colourRed))
   288  				out(colour(help, colourGreen))
   289  				continue
   290  			}
   291  			i, err := strconv.ParseUint(parts[1], 10, 64)
   292  			if err != nil {
   293  				out(colour("I don't understand %q. It's not a number.\n", colourRed), i)
   294  				continue
   295  			}
   296  			if int(i) < 0 || int(i) > len(points) - 1 {
   297  				out(colour("I can't jump that far. It's out of range.\n", colourRed))
   298  				continue
   299  			}
   300  			index = int(i)
   301  			points[index].out(c)
   302  		case "open", "o":
   303  			editor := os.Getenv("EDITOR")
   304  			cmd := exec.Command(editor, points[index].File, "+" + points[index].Line)
   305  			cmd.Stdout = stdout
   306  			cmd.Stderr = stdout
   307  			cmd.Stdin = os.Stdin
   308  			err := cmd.Run()
   309  			if err != nil {
   310  				out(colour(err.Error()+"\n", colourRed))
   311  			}
   312  		default:
   313  			out(colour(help, colourGreen))
   314  		}
   315  	}
   316  }
   317  
   318  func (p *point) out(c Conf) {
   319  	colour := colourer(c)
   320  	out := outer(c)
   321  
   322  	out("position: %s\n", colour(p.Position, colourRed))
   323  	out("identifier: %s\n", colour(p.Identifier, colourRed))
   324  	out("package: %s\n", colour(p.Package, colourGreen))
   325  	out("receiver: %s\n", colour(p.Receiver, colourGreen))
   326  	out("function: %s\n", colour(p.Func, colourGreen))
   327  	out("args: \n")
   328  	for i, a := range p.Args {
   329  		out(colour("    [%d] %s %+v\n", colourRed), i, colour(a.Hex, colourGreen), a.Value)
   330  	}
   331  	out("file: %s%s\n", colour(p.File, colourBlue), colour(":" + p.Line, colourRed))
   332  }