| // Copyright 2016 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package runtime_test | |
| import ( | |
| "runtime" | |
| "slices" | |
| "strings" | |
| "testing" | |
| ) | |
| func f1(pan bool) []uintptr { | |
| return f2(pan) // line 15 | |
| } | |
| func f2(pan bool) []uintptr { | |
| return f3(pan) // line 19 | |
| } | |
| func f3(pan bool) []uintptr { | |
| if pan { | |
| panic("f3") // line 24 | |
| } | |
| ret := make([]uintptr, 20) | |
| return ret[:runtime.Callers(0, ret)] // line 27 | |
| } | |
| func testCallers(t *testing.T, pcs []uintptr, pan bool) { | |
| m := make(map[string]int, len(pcs)) | |
| frames := runtime.CallersFrames(pcs) | |
| for { | |
| frame, more := frames.Next() | |
| if frame.Function != "" { | |
| m[frame.Function] = frame.Line | |
| } | |
| if !more { | |
| break | |
| } | |
| } | |
| var seen []string | |
| for k := range m { | |
| seen = append(seen, k) | |
| } | |
| t.Logf("functions seen: %s", strings.Join(seen, " ")) | |
| var f3Line int | |
| if pan { | |
| f3Line = 24 | |
| } else { | |
| f3Line = 27 | |
| } | |
| want := []struct { | |
| name string | |
| line int | |
| }{ | |
| {"f1", 15}, | |
| {"f2", 19}, | |
| {"f3", f3Line}, | |
| } | |
| for _, w := range want { | |
| if got := m["runtime_test."+w.name]; got != w.line { | |
| t.Errorf("%s is line %d, want %d", w.name, got, w.line) | |
| } | |
| } | |
| } | |
| func testCallersEqual(t *testing.T, pcs []uintptr, want []string) { | |
| t.Helper() | |
| got := make([]string, 0, len(want)) | |
| frames := runtime.CallersFrames(pcs) | |
| for { | |
| frame, more := frames.Next() | |
| if !more || len(got) >= len(want) { | |
| break | |
| } | |
| got = append(got, frame.Function) | |
| } | |
| if !slices.Equal(want, got) { | |
| t.Fatalf("wanted %v, got %v", want, got) | |
| } | |
| } | |
| func TestCallers(t *testing.T) { | |
| testCallers(t, f1(false), false) | |
| } | |
| func TestCallersPanic(t *testing.T) { | |
| // Make sure we don't have any extra frames on the stack (due to | |
| // open-coded defer processing) | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersPanic.func1", | |
| "runtime.gopanic", "runtime_test.f3", "runtime_test.f2", "runtime_test.f1", | |
| "runtime_test.TestCallersPanic"} | |
| defer func() { | |
| if r := recover(); r == nil { | |
| t.Fatal("did not panic") | |
| } | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| testCallers(t, pcs, true) | |
| testCallersEqual(t, pcs, want) | |
| }() | |
| f1(true) | |
| } | |
| func TestCallersDoublePanic(t *testing.T) { | |
| // Make sure we don't have any extra frames on the stack (due to | |
| // open-coded defer processing) | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersDoublePanic.func1.1", | |
| "runtime.gopanic", "runtime_test.TestCallersDoublePanic.func1", "runtime.gopanic", "runtime_test.TestCallersDoublePanic"} | |
| defer func() { | |
| defer func() { | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| if recover() == nil { | |
| t.Fatal("did not panic") | |
| } | |
| testCallersEqual(t, pcs, want) | |
| }() | |
| if recover() == nil { | |
| t.Fatal("did not panic") | |
| } | |
| panic(2) | |
| }() | |
| panic(1) | |
| } | |
| // Test that a defer after a successful recovery looks like it is called directly | |
| // from the function with the defers. | |
| func TestCallersAfterRecovery(t *testing.T) { | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersAfterRecovery.func1", "runtime_test.TestCallersAfterRecovery"} | |
| defer func() { | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| testCallersEqual(t, pcs, want) | |
| }() | |
| defer func() { | |
| if recover() == nil { | |
| t.Fatal("did not recover from panic") | |
| } | |
| }() | |
| panic(1) | |
| } | |
| func TestCallersAbortedPanic(t *testing.T) { | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersAbortedPanic.func2", "runtime_test.TestCallersAbortedPanic"} | |
| defer func() { | |
| r := recover() | |
| if r != nil { | |
| t.Fatalf("should be no panic remaining to recover") | |
| } | |
| }() | |
| defer func() { | |
| // panic2 was aborted/replaced by panic1, so when panic2 was | |
| // recovered, there is no remaining panic on the stack. | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| testCallersEqual(t, pcs, want) | |
| }() | |
| defer func() { | |
| r := recover() | |
| if r != "panic2" { | |
| t.Fatalf("got %v, wanted %v", r, "panic2") | |
| } | |
| }() | |
| defer func() { | |
| // panic2 aborts/replaces panic1, because it is a recursive panic | |
| // that is not recovered within the defer function called by | |
| // panic1 panicking sequence | |
| panic("panic2") | |
| }() | |
| panic("panic1") | |
| } | |
| func TestCallersAbortedPanic2(t *testing.T) { | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersAbortedPanic2.func2", "runtime_test.TestCallersAbortedPanic2"} | |
| defer func() { | |
| r := recover() | |
| if r != nil { | |
| t.Fatalf("should be no panic remaining to recover") | |
| } | |
| }() | |
| defer func() { | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| testCallersEqual(t, pcs, want) | |
| }() | |
| func() { | |
| defer func() { | |
| r := recover() | |
| if r != "panic2" { | |
| t.Fatalf("got %v, wanted %v", r, "panic2") | |
| } | |
| }() | |
| func() { | |
| defer func() { | |
| // Again, panic2 aborts/replaces panic1 | |
| panic("panic2") | |
| }() | |
| panic("panic1") | |
| }() | |
| }() | |
| } | |
| func TestCallersNilPointerPanic(t *testing.T) { | |
| // Make sure we don't have any extra frames on the stack (due to | |
| // open-coded defer processing) | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersNilPointerPanic.func1", | |
| "runtime.gopanic", "runtime.panicmem", "runtime.sigpanic", | |
| "runtime_test.TestCallersNilPointerPanic"} | |
| defer func() { | |
| if r := recover(); r == nil { | |
| t.Fatal("did not panic") | |
| } | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| testCallersEqual(t, pcs, want) | |
| }() | |
| var p *int | |
| if *p == 3 { | |
| t.Fatal("did not see nil pointer panic") | |
| } | |
| } | |
| func TestCallersDivZeroPanic(t *testing.T) { | |
| // Make sure we don't have any extra frames on the stack (due to | |
| // open-coded defer processing) | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersDivZeroPanic.func1", | |
| "runtime.gopanic", "runtime.panicdivide", | |
| "runtime_test.TestCallersDivZeroPanic"} | |
| defer func() { | |
| if r := recover(); r == nil { | |
| t.Fatal("did not panic") | |
| } | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| testCallersEqual(t, pcs, want) | |
| }() | |
| var n int | |
| if 5/n == 1 { | |
| t.Fatal("did not see divide-by-sizer panic") | |
| } | |
| } | |
| func TestCallersDeferNilFuncPanic(t *testing.T) { | |
| // Make sure we don't have any extra frames on the stack. We cut off the check | |
| // at runtime.sigpanic, because non-open-coded defers (which may be used in | |
| // non-opt or race checker mode) include an extra 'deferreturn' frame (which is | |
| // where the nil pointer deref happens). | |
| state := 1 | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanic.func1", | |
| "runtime.gopanic", "runtime.panicmem", "runtime.sigpanic"} | |
| defer func() { | |
| if r := recover(); r == nil { | |
| t.Fatal("did not panic") | |
| } | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| testCallersEqual(t, pcs, want) | |
| if state == 1 { | |
| t.Fatal("nil defer func panicked at defer time rather than function exit time") | |
| } | |
| }() | |
| var f func() | |
| defer f() | |
| // Use the value of 'state' to make sure nil defer func f causes panic at | |
| // function exit, rather than at the defer statement. | |
| state = 2 | |
| } | |
| // Same test, but forcing non-open-coded defer by putting the defer in a loop. See | |
| // issue #36050 | |
| func TestCallersDeferNilFuncPanicWithLoop(t *testing.T) { | |
| state := 1 | |
| want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanicWithLoop.func1", | |
| "runtime.gopanic", "runtime.panicmem", "runtime.sigpanic", "runtime.deferreturn", "runtime_test.TestCallersDeferNilFuncPanicWithLoop"} | |
| defer func() { | |
| if r := recover(); r == nil { | |
| t.Fatal("did not panic") | |
| } | |
| pcs := make([]uintptr, 20) | |
| pcs = pcs[:runtime.Callers(0, pcs)] | |
| testCallersEqual(t, pcs, want) | |
| if state == 1 { | |
| t.Fatal("nil defer func panicked at defer time rather than function exit time") | |
| } | |
| }() | |
| for i := 0; i < 1; i++ { | |
| var f func() | |
| defer f() | |
| } | |
| // Use the value of 'state' to make sure nil defer func f causes panic at | |
| // function exit, rather than at the defer statement. | |
| state = 2 | |
| } | |
| // issue #51988 | |
| // Func.Endlineno was lost when instantiating generic functions, leading to incorrect | |
| // stack trace positions. | |
| func TestCallersEndlineno(t *testing.T) { | |
| testNormalEndlineno(t) | |
| testGenericEndlineno[int](t) | |
| } | |
| func testNormalEndlineno(t *testing.T) { | |
| defer testCallerLine(t, callerLine(t, 0)+1) | |
| } | |
| func testGenericEndlineno[_ any](t *testing.T) { | |
| defer testCallerLine(t, callerLine(t, 0)+1) | |
| } | |
| func testCallerLine(t *testing.T, want int) { | |
| if have := callerLine(t, 1); have != want { | |
| t.Errorf("callerLine(1) returned %d, but want %d\n", have, want) | |
| } | |
| } | |
| func callerLine(t *testing.T, skip int) int { | |
| _, _, line, ok := runtime.Caller(skip + 1) | |
| if !ok { | |
| t.Fatalf("runtime.Caller(%d) failed", skip+1) | |
| } | |
| return line | |
| } | |
| func BenchmarkCallers(b *testing.B) { | |
| b.Run("cached", func(b *testing.B) { | |
| // Very pcvalueCache-friendly, no inlining. | |
| callersCached(b, 100) | |
| }) | |
| b.Run("inlined", func(b *testing.B) { | |
| // Some inlining, still pretty cache-friendly. | |
| callersInlined(b, 100) | |
| }) | |
| b.Run("no-cache", func(b *testing.B) { | |
| // Cache-hostile | |
| callersNoCache(b, 100) | |
| }) | |
| } | |
| func callersCached(b *testing.B, n int) int { | |
| if n <= 0 { | |
| pcs := make([]uintptr, 32) | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| runtime.Callers(0, pcs) | |
| } | |
| b.StopTimer() | |
| return 0 | |
| } | |
| return 1 + callersCached(b, n-1) | |
| } | |
| func callersInlined(b *testing.B, n int) int { | |
| if n <= 0 { | |
| pcs := make([]uintptr, 32) | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| runtime.Callers(0, pcs) | |
| } | |
| b.StopTimer() | |
| return 0 | |
| } | |
| return 1 + callersInlined1(b, n-1) | |
| } | |
| func callersInlined1(b *testing.B, n int) int { return callersInlined2(b, n) } | |
| func callersInlined2(b *testing.B, n int) int { return callersInlined3(b, n) } | |
| func callersInlined3(b *testing.B, n int) int { return callersInlined4(b, n) } | |
| func callersInlined4(b *testing.B, n int) int { return callersInlined(b, n) } | |
| func callersNoCache(b *testing.B, n int) int { | |
| if n <= 0 { | |
| pcs := make([]uintptr, 32) | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| runtime.Callers(0, pcs) | |
| } | |
| b.StopTimer() | |
| return 0 | |
| } | |
| switch n % 16 { | |
| case 0: | |
| return 1 + callersNoCache(b, n-1) | |
| case 1: | |
| return 1 + callersNoCache(b, n-1) | |
| case 2: | |
| return 1 + callersNoCache(b, n-1) | |
| case 3: | |
| return 1 + callersNoCache(b, n-1) | |
| case 4: | |
| return 1 + callersNoCache(b, n-1) | |
| case 5: | |
| return 1 + callersNoCache(b, n-1) | |
| case 6: | |
| return 1 + callersNoCache(b, n-1) | |
| case 7: | |
| return 1 + callersNoCache(b, n-1) | |
| case 8: | |
| return 1 + callersNoCache(b, n-1) | |
| case 9: | |
| return 1 + callersNoCache(b, n-1) | |
| case 10: | |
| return 1 + callersNoCache(b, n-1) | |
| case 11: | |
| return 1 + callersNoCache(b, n-1) | |
| case 12: | |
| return 1 + callersNoCache(b, n-1) | |
| case 13: | |
| return 1 + callersNoCache(b, n-1) | |
| case 14: | |
| return 1 + callersNoCache(b, n-1) | |
| default: | |
| return 1 + callersNoCache(b, n-1) | |
| } | |
| } | |
| func BenchmarkFPCallers(b *testing.B) { | |
| b.Run("cached", func(b *testing.B) { | |
| // Very pcvalueCache-friendly, no inlining. | |
| fpCallersCached(b, 100) | |
| }) | |
| } | |
| func fpCallersCached(b *testing.B, n int) int { | |
| if n <= 0 { | |
| pcs := make([]uintptr, 32) | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| runtime.FPCallers(pcs) | |
| } | |
| b.StopTimer() | |
| return 0 | |
| } | |
| return 1 + fpCallersCached(b, n-1) | |
| } | |
| func TestFPUnwindAfterRecovery(t *testing.T) { | |
| if !runtime.FramePointerEnabled { | |
| t.Skip("frame pointers not supported for this architecture") | |
| } | |
| // Make sure that frame pointer unwinding succeeds from a deferred | |
| // function run after recovering from a panic. It can fail if the | |
| // recovery does not properly restore the caller's frame pointer before | |
| // running the remaining deferred functions. | |
| // | |
| // This test does not verify the accuracy of the call stack (it | |
| // currently includes a frame from runtime.deferreturn which would | |
| // normally be omitted). It is only intended to check that producing the | |
| // call stack won't crash. | |
| defer func() { | |
| pcs := make([]uintptr, 32) | |
| for i := range pcs { | |
| // If runtime.recovery doesn't properly restore the | |
| // frame pointer before returning control to this | |
| // function, it will point somewhere lower in the stack | |
| // from one of the frames of runtime.gopanic() or one of | |
| // its callees prior to recovery. So, we put some | |
| // non-zero values on the stack to ensure that frame | |
| // pointer unwinding will crash if it sees the old, | |
| // invalid frame pointer. | |
| pcs[i] = 10 | |
| } | |
| runtime.FPCallers(pcs) | |
| t.Logf("%v", pcs) | |
| }() | |
| defer func() { | |
| if recover() == nil { | |
| t.Fatal("did not recover from panic") | |
| } | |
| }() | |
| panic(1) | |
| } | |
| //go:noinline | |
| func deref() int { | |
| var i *int | |
| runtime.KeepAlive(&i) | |
| return *i | |
| } | |
| func TestFPUnwindStackGrowthAfterRecovery(t *testing.T) { | |
| if !runtime.FramePointerEnabled { | |
| t.Skip("frame pointers not supported for this architecture") | |
| } | |
| state := runtime.StackPoisonCopy() | |
| defer state.Restore() | |
| defer func() { | |
| if recover() == nil { | |
| t.Fatal("did not recover from panic") | |
| } | |
| growStack(nil) | |
| var pcs [32]uintptr | |
| runtime.FPCallers(pcs[:]) | |
| }() | |
| deref() | |
| } |