1package reflectsource 2 3import ( 4 "bytes" 5 "fmt" 6 "go/ast" 7 "io/ioutil" 8 "runtime" 9 "strings" 10 11 "github.com/shurcooL/go/parserutil" 12 "github.com/shurcooL/go/printerutil" 13 "github.com/shurcooL/go/reflectfind" 14) 15 16// GetParentFuncAsString gets the parent func as a string. 17func GetParentFuncAsString() string { 18 // TODO: Replace use of debug.Stack() with direct use of runtime package... 19 // TODO: Use runtime.FuncForPC(runtime.Caller()).Name() to get func name if source code not found. 20 stack := string(stack()) 21 22 funcName := getLine(stack, 3) 23 funcName = funcName[1:strings.Index(funcName, ": ")] 24 if dotPos := strings.LastIndex(funcName, "."); dotPos != -1 { // Trim package prefix. 25 funcName = funcName[dotPos+1:] 26 } 27 28 funcArgs := getLine(stack, 5) 29 funcArgs = funcArgs[strings.Index(funcArgs, ": ")+len(": "):] 30 funcArgs = funcArgs[strings.Index(funcArgs, "(") : strings.LastIndex(funcArgs, ")")+len(")")] // TODO: This may fail if there are 2+ func calls on one line. 31 32 return funcName + funcArgs 33} 34 35// GetParentFuncArgsAsString gets the parent func with its args as a string. 36func GetParentFuncArgsAsString(args ...interface{}) string { 37 // TODO: Replace use of debug.Stack() with direct use of runtime package... 38 // TODO: Use runtime.FuncForPC(runtime.Caller()).Name() to get func name if source code not found. 39 stack := string(stack()) 40 41 funcName := getLine(stack, 3) 42 funcName = funcName[1:strings.Index(funcName, ": ")] 43 if dotPos := strings.LastIndex(funcName, "."); dotPos != -1 { // Trim package prefix. 44 funcName = funcName[dotPos+1:] 45 } 46 47 funcArgs := "(" 48 for i, arg := range args { 49 // TODO: Add arg names. Maybe not? 50 if i != 0 { 51 funcArgs += ", " 52 } 53 funcArgs += fmt.Sprintf("%#v", arg) // TODO: Maybe use goon instead. Need to move elsewhere to avoid import cycle. 54 } 55 funcArgs += ")" 56 57 return funcName + funcArgs 58} 59 60// GetExprAsString gets the expression as a string. 61func GetExprAsString(_ interface{}) string { 62 return GetParentArgExprAsString(0) 63} 64 65func getParent2ArgExprAllAsAst() []ast.Expr { 66 // TODO: Replace use of debug.Stack() with direct use of runtime package... 67 stack := string(stack()) 68 69 // TODO: Bounds error checking, get rid of GetLine gists, etc. 70 parentName := getLine(stack, 5) 71 if !strings.Contains(parentName, ": ") { 72 // TODO: This happens when source file isn't present in same location as when built. See if can do anything better 73 // via direct use of runtime package (instead of debug.Stack(), which will exclude any func names)... 74 return nil 75 } 76 parentName = parentName[1:strings.Index(parentName, ": ")] 77 if dotPos := strings.LastIndex(parentName, "."); dotPos != -1 { // Trim package prefix. 78 parentName = parentName[dotPos+1:] 79 } 80 81 str := getLine(stack, 7) 82 str = str[strings.Index(str, ": ")+len(": "):] 83 p, err := parserutil.ParseStmt(str) 84 if err != nil { 85 return nil 86 } 87 88 innerQuery := func(i interface{}) bool { 89 if ident, ok := i.(*ast.Ident); ok && ident.Name == parentName { 90 return true 91 } 92 return false 93 } 94 95 query := func(i interface{}) bool { 96 if c, ok := i.(*ast.CallExpr); ok && nil != reflectfind.First(c.Fun, innerQuery) { 97 return true 98 } 99 return false 100 } 101 callExpr, _ := reflectfind.First(p, query).(*ast.CallExpr) 102 103 if callExpr == nil { 104 return nil 105 } 106 return callExpr.Args 107} 108 109// GetParentArgExprAsString gets the argIndex argument expression of parent func call as a string. 110func GetParentArgExprAsString(argIndex uint32) string { 111 args := getParent2ArgExprAllAsAst() 112 if args == nil { 113 return "<expr not found>" 114 } 115 if argIndex >= uint32(len(args)) { 116 return "<out of range>" 117 } 118 119 return printerutil.SprintAstBare(args[argIndex]) 120} 121 122// GetParentArgExprAllAsString gets all argument expressions of parent func call as a string. 123func GetParentArgExprAllAsString() []string { 124 args := getParent2ArgExprAllAsAst() 125 if args == nil { 126 return nil 127 } 128 129 out := make([]string, len(args)) 130 for i := range args { 131 out[i] = printerutil.SprintAstBare(args[i]) 132 } 133 return out 134} 135 136func getMySecondArgExprAsString(int, int) string { 137 return GetParentArgExprAsString(1) 138} 139 140func getLine(s string, lineIndex int) string { 141 return strings.Split(s, "\n")[lineIndex] 142} 143 144var ( 145 dunno = []byte("???") 146 centerDot = []byte("·") 147 dot = []byte(".") 148 slash = []byte("/") 149) 150 151// stack returns a formatted stack trace of the goroutine that calls it. 152// For each routine, it includes the source line information and PC value, 153// then attempts to discover, for Go functions, the calling function or 154// method and the text of the line containing the invocation. 155// 156// It was deprecated in Go 1.5, suggested to use package runtime's Stack instead, 157// and replaced by another implementation in Go 1.6. 158// 159// stack implements the Go 1.5 version of debug.Stack(), skipping 1 frame, 160// instead of 2, since it's being called directly (rather than via debug.Stack()). 161func stack() []byte { 162 buf := new(bytes.Buffer) // the returned data 163 // As we loop, we open files and read them. These variables record the currently 164 // loaded file. 165 var lines [][]byte 166 var lastFile string 167 for i := 1; ; i++ { // Caller we care about is the user, 1 frame up 168 pc, file, line, ok := runtime.Caller(i) 169 if !ok { 170 break 171 } 172 // Print this much at least. If we can't find the source, it won't show. 173 fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) 174 if file != lastFile { 175 data, err := ioutil.ReadFile(file) 176 if err != nil { 177 continue 178 } 179 lines = bytes.Split(data, []byte{'\n'}) 180 lastFile = file 181 } 182 line-- // in stack trace, lines are 1-indexed but our array is 0-indexed 183 fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) 184 } 185 return buf.Bytes() 186} 187 188// source returns a space-trimmed slice of the n'th line. 189func source(lines [][]byte, n int) []byte { 190 if n < 0 || n >= len(lines) { 191 return dunno 192 } 193 return bytes.Trim(lines[n], " \t") 194} 195 196// function returns, if possible, the name of the function containing the PC. 197func function(pc uintptr) []byte { 198 fn := runtime.FuncForPC(pc) 199 if fn == nil { 200 return dunno 201 } 202 name := []byte(fn.Name()) 203 // The name includes the path name to the package, which is unnecessary 204 // since the file name is already included. Plus, it has center dots. 205 // That is, we see 206 // runtime/debug.*T·ptrmethod 207 // and want 208 // *T.ptrmethod 209 // Since the package path might contains dots (e.g. code.google.com/...), 210 // we first remove the path prefix if there is one. 211 if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 { 212 name = name[lastslash+1:] 213 } 214 if period := bytes.Index(name, dot); period >= 0 { 215 name = name[period+1:] 216 } 217 name = bytes.Replace(name, centerDot, dot, -1) 218 return name 219} 220