github.com/m10x/go/src@v0.0.0-20220112094212-ba61592315da/runtime/symtab_test.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package runtime_test 6 7 import ( 8 "runtime" 9 "strings" 10 "testing" 11 "unsafe" 12 ) 13 14 func TestCaller(t *testing.T) { 15 procs := runtime.GOMAXPROCS(-1) 16 c := make(chan bool, procs) 17 for p := 0; p < procs; p++ { 18 go func() { 19 for i := 0; i < 1000; i++ { 20 testCallerFoo(t) 21 } 22 c <- true 23 }() 24 defer func() { 25 <-c 26 }() 27 } 28 } 29 30 // These are marked noinline so that we can use FuncForPC 31 // in testCallerBar. 32 //go:noinline 33 func testCallerFoo(t *testing.T) { 34 testCallerBar(t) 35 } 36 37 //go:noinline 38 func testCallerBar(t *testing.T) { 39 for i := 0; i < 2; i++ { 40 pc, file, line, ok := runtime.Caller(i) 41 f := runtime.FuncForPC(pc) 42 if !ok || 43 !strings.HasSuffix(file, "symtab_test.go") || 44 (i == 0 && !strings.HasSuffix(f.Name(), "testCallerBar")) || 45 (i == 1 && !strings.HasSuffix(f.Name(), "testCallerFoo")) || 46 line < 5 || line > 1000 || 47 f.Entry() >= pc { 48 t.Errorf("incorrect symbol info %d: %t %d %d %s %s %d", 49 i, ok, f.Entry(), pc, f.Name(), file, line) 50 } 51 } 52 } 53 54 func lineNumber() int { 55 _, _, line, _ := runtime.Caller(1) 56 return line // return 0 for error 57 } 58 59 // Do not add/remove lines in this block without updating the line numbers. 60 var firstLine = lineNumber() // 0 61 var ( // 1 62 lineVar1 = lineNumber() // 2 63 lineVar2a, lineVar2b = lineNumber(), lineNumber() // 3 64 ) // 4 65 var compLit = []struct { // 5 66 lineA, lineB int // 6 67 }{ // 7 68 { // 8 69 lineNumber(), lineNumber(), // 9 70 }, // 10 71 { // 11 72 lineNumber(), // 12 73 lineNumber(), // 13 74 }, // 14 75 { // 15 76 lineB: lineNumber(), // 16 77 lineA: lineNumber(), // 17 78 }, // 18 79 } // 19 80 var arrayLit = [...]int{lineNumber(), // 20 81 lineNumber(), lineNumber(), // 21 82 lineNumber(), // 22 83 } // 23 84 var sliceLit = []int{lineNumber(), // 24 85 lineNumber(), lineNumber(), // 25 86 lineNumber(), // 26 87 } // 27 88 var mapLit = map[int]int{ // 28 89 29: lineNumber(), // 29 90 30: lineNumber(), // 30 91 lineNumber(): 31, // 31 92 lineNumber(): 32, // 32 93 } // 33 94 var intLit = lineNumber() + // 34 95 lineNumber() + // 35 96 lineNumber() // 36 97 func trythis() { // 37 98 recordLines(lineNumber(), // 38 99 lineNumber(), // 39 100 lineNumber()) // 40 101 } 102 103 // Modifications below this line are okay. 104 105 var l38, l39, l40 int 106 107 func recordLines(a, b, c int) { 108 l38 = a 109 l39 = b 110 l40 = c 111 } 112 113 func TestLineNumber(t *testing.T) { 114 trythis() 115 for _, test := range []struct { 116 name string 117 val int 118 want int 119 }{ 120 {"firstLine", firstLine, 0}, 121 {"lineVar1", lineVar1, 2}, 122 {"lineVar2a", lineVar2a, 3}, 123 {"lineVar2b", lineVar2b, 3}, 124 {"compLit[0].lineA", compLit[0].lineA, 9}, 125 {"compLit[0].lineB", compLit[0].lineB, 9}, 126 {"compLit[1].lineA", compLit[1].lineA, 12}, 127 {"compLit[1].lineB", compLit[1].lineB, 13}, 128 {"compLit[2].lineA", compLit[2].lineA, 17}, 129 {"compLit[2].lineB", compLit[2].lineB, 16}, 130 131 {"arrayLit[0]", arrayLit[0], 20}, 132 {"arrayLit[1]", arrayLit[1], 21}, 133 {"arrayLit[2]", arrayLit[2], 21}, 134 {"arrayLit[3]", arrayLit[3], 22}, 135 136 {"sliceLit[0]", sliceLit[0], 24}, 137 {"sliceLit[1]", sliceLit[1], 25}, 138 {"sliceLit[2]", sliceLit[2], 25}, 139 {"sliceLit[3]", sliceLit[3], 26}, 140 141 {"mapLit[29]", mapLit[29], 29}, 142 {"mapLit[30]", mapLit[30], 30}, 143 {"mapLit[31]", mapLit[31+firstLine] + firstLine, 31}, // nb it's the key not the value 144 {"mapLit[32]", mapLit[32+firstLine] + firstLine, 32}, // nb it's the key not the value 145 146 {"intLit", intLit - 2*firstLine, 34 + 35 + 36}, 147 148 {"l38", l38, 38}, 149 {"l39", l39, 39}, 150 {"l40", l40, 40}, 151 } { 152 if got := test.val - firstLine; got != test.want { 153 t.Errorf("%s on firstLine+%d want firstLine+%d (firstLine=%d, val=%d)", 154 test.name, got, test.want, firstLine, test.val) 155 } 156 } 157 } 158 159 func TestNilName(t *testing.T) { 160 defer func() { 161 if ex := recover(); ex != nil { 162 t.Fatalf("expected no nil panic, got=%v", ex) 163 } 164 }() 165 if got := (*runtime.Func)(nil).Name(); got != "" { 166 t.Errorf("Name() = %q, want %q", got, "") 167 } 168 } 169 170 var dummy int 171 172 func inlined() { 173 // Side effect to prevent elimination of this entire function. 174 dummy = 42 175 } 176 177 // A function with an InlTree. Returns a PC within the function body. 178 // 179 // No inline to ensure this complete function appears in output. 180 // 181 //go:noinline 182 func tracebackFunc(t *testing.T) uintptr { 183 // This body must be more complex than a single call to inlined to get 184 // an inline tree. 185 inlined() 186 inlined() 187 188 // Acquire a PC in this function. 189 pc, _, _, ok := runtime.Caller(0) 190 if !ok { 191 t.Fatalf("Caller(0) got ok false, want true") 192 } 193 194 return pc 195 } 196 197 // Test that CallersFrames handles PCs in the alignment region between 198 // functions (int 3 on amd64) without crashing. 199 // 200 // Go will never generate a stack trace containing such an address, as it is 201 // not a valid call site. However, the cgo traceback function passed to 202 // runtime.SetCgoTraceback may not be completely accurate and may incorrect 203 // provide PCs in Go code or the alignement region between functions. 204 // 205 // Go obviously doesn't easily expose the problematic PCs to running programs, 206 // so this test is a bit fragile. Some details: 207 // 208 // * tracebackFunc is our target function. We want to get a PC in the 209 // alignment region following this function. This function also has other 210 // functions inlined into it to ensure it has an InlTree (this was the source 211 // of the bug in issue 44971). 212 // 213 // * We acquire a PC in tracebackFunc, walking forwards until FuncForPC says 214 // we're in a new function. The last PC of the function according to FuncForPC 215 // should be in the alignment region (assuming the function isn't already 216 // perfectly aligned). 217 // 218 // This is a regression test for issue 44971. 219 func TestFunctionAlignmentTraceback(t *testing.T) { 220 pc := tracebackFunc(t) 221 222 // Double-check we got the right PC. 223 f := runtime.FuncForPC(pc) 224 if !strings.HasSuffix(f.Name(), "tracebackFunc") { 225 t.Fatalf("Caller(0) = %+v, want tracebackFunc", f) 226 } 227 228 // Iterate forward until we find a different function. Back up one 229 // instruction is (hopefully) an alignment instruction. 230 for runtime.FuncForPC(pc) == f { 231 pc++ 232 } 233 pc-- 234 235 // Is this an alignment region filler instruction? We only check this 236 // on amd64 for simplicity. If this function has no filler, then we may 237 // get a false negative, but will never get a false positive. 238 if runtime.GOARCH == "amd64" { 239 code := *(*uint8)(unsafe.Pointer(pc)) 240 if code != 0xcc { // INT $3 241 t.Errorf("PC %v code got %#x want 0xcc", pc, code) 242 } 243 } 244 245 // Finally ensure that Frames.Next doesn't crash when processing this 246 // PC. 247 frames := runtime.CallersFrames([]uintptr{pc}) 248 frame, _ := frames.Next() 249 if frame.Func != f { 250 t.Errorf("frames.Next() got %+v want %+v", frame.Func, f) 251 } 252 } 253 254 func BenchmarkFunc(b *testing.B) { 255 pc, _, _, ok := runtime.Caller(0) 256 if !ok { 257 b.Fatal("failed to look up PC") 258 } 259 f := runtime.FuncForPC(pc) 260 b.Run("Name", func(b *testing.B) { 261 for i := 0; i < b.N; i++ { 262 name := f.Name() 263 if name != "runtime_test.BenchmarkFunc" { 264 b.Fatalf("unexpected name %q", name) 265 } 266 } 267 }) 268 b.Run("Entry", func(b *testing.B) { 269 for i := 0; i < b.N; i++ { 270 pc := f.Entry() 271 if pc == 0 { 272 b.Fatal("zero PC") 273 } 274 } 275 }) 276 b.Run("FileLine", func(b *testing.B) { 277 for i := 0; i < b.N; i++ { 278 file, line := f.FileLine(pc) 279 if !strings.HasSuffix(file, "symtab_test.go") || line == 0 { 280 b.Fatalf("unexpected file/line %q:%d", file, line) 281 } 282 } 283 }) 284 }