Source file src/runtime/callers_test.go

     1  // Copyright 2016 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  	"slices"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  func f1(pan bool) []uintptr {
    15  	return f2(pan) // line 15
    16  }
    17  
    18  func f2(pan bool) []uintptr {
    19  	return f3(pan) // line 19
    20  }
    21  
    22  func f3(pan bool) []uintptr {
    23  	if pan {
    24  		panic("f3") // line 24
    25  	}
    26  	ret := make([]uintptr, 20)
    27  	return ret[:runtime.Callers(0, ret)] // line 27
    28  }
    29  
    30  func testCallers(t *testing.T, pcs []uintptr, pan bool) {
    31  	m := make(map[string]int, len(pcs))
    32  	frames := runtime.CallersFrames(pcs)
    33  	for {
    34  		frame, more := frames.Next()
    35  		if frame.Function != "" {
    36  			m[frame.Function] = frame.Line
    37  		}
    38  		if !more {
    39  			break
    40  		}
    41  	}
    42  
    43  	var seen []string
    44  	for k := range m {
    45  		seen = append(seen, k)
    46  	}
    47  	t.Logf("functions seen: %s", strings.Join(seen, " "))
    48  
    49  	var f3Line int
    50  	if pan {
    51  		f3Line = 24
    52  	} else {
    53  		f3Line = 27
    54  	}
    55  	want := []struct {
    56  		name string
    57  		line int
    58  	}{
    59  		{"f1", 15},
    60  		{"f2", 19},
    61  		{"f3", f3Line},
    62  	}
    63  	for _, w := range want {
    64  		if got := m["runtime_test."+w.name]; got != w.line {
    65  			t.Errorf("%s is line %d, want %d", w.name, got, w.line)
    66  		}
    67  	}
    68  }
    69  
    70  func testCallersEqual(t *testing.T, pcs []uintptr, want []string) {
    71  	t.Helper()
    72  
    73  	got := make([]string, 0, len(want))
    74  
    75  	frames := runtime.CallersFrames(pcs)
    76  	for {
    77  		frame, more := frames.Next()
    78  		if !more || len(got) >= len(want) {
    79  			break
    80  		}
    81  		got = append(got, frame.Function)
    82  	}
    83  	if !slices.Equal(want, got) {
    84  		t.Fatalf("wanted %v, got %v", want, got)
    85  	}
    86  }
    87  
    88  func TestCallers(t *testing.T) {
    89  	testCallers(t, f1(false), false)
    90  }
    91  
    92  func TestCallersPanic(t *testing.T) {
    93  	// Make sure we don't have any extra frames on the stack (due to
    94  	// open-coded defer processing)
    95  	want := []string{"runtime.Callers", "runtime_test.TestCallersPanic.func1",
    96  		"runtime.gopanic", "runtime_test.f3", "runtime_test.f2", "runtime_test.f1",
    97  		"runtime_test.TestCallersPanic"}
    98  
    99  	defer func() {
   100  		if r := recover(); r == nil {
   101  			t.Fatal("did not panic")
   102  		}
   103  		pcs := make([]uintptr, 20)
   104  		pcs = pcs[:runtime.Callers(0, pcs)]
   105  		testCallers(t, pcs, true)
   106  		testCallersEqual(t, pcs, want)
   107  	}()
   108  	f1(true)
   109  }
   110  
   111  func TestCallersDoublePanic(t *testing.T) {
   112  	// Make sure we don't have any extra frames on the stack (due to
   113  	// open-coded defer processing)
   114  	want := []string{"runtime.Callers", "runtime_test.TestCallersDoublePanic.func1.1",
   115  		"runtime.gopanic", "runtime_test.TestCallersDoublePanic.func1", "runtime.gopanic", "runtime_test.TestCallersDoublePanic"}
   116  
   117  	defer func() {
   118  		defer func() {
   119  			pcs := make([]uintptr, 20)
   120  			pcs = pcs[:runtime.Callers(0, pcs)]
   121  			if recover() == nil {
   122  				t.Fatal("did not panic")
   123  			}
   124  			testCallersEqual(t, pcs, want)
   125  		}()
   126  		if recover() == nil {
   127  			t.Fatal("did not panic")
   128  		}
   129  		panic(2)
   130  	}()
   131  	panic(1)
   132  }
   133  
   134  // Test that a defer after a successful recovery looks like it is called directly
   135  // from the function with the defers.
   136  func TestCallersAfterRecovery(t *testing.T) {
   137  	want := []string{"runtime.Callers", "runtime_test.TestCallersAfterRecovery.func1", "runtime_test.TestCallersAfterRecovery"}
   138  
   139  	defer func() {
   140  		pcs := make([]uintptr, 20)
   141  		pcs = pcs[:runtime.Callers(0, pcs)]
   142  		testCallersEqual(t, pcs, want)
   143  	}()
   144  	defer func() {
   145  		if recover() == nil {
   146  			t.Fatal("did not recover from panic")
   147  		}
   148  	}()
   149  	panic(1)
   150  }
   151  
   152  func TestCallersAbortedPanic(t *testing.T) {
   153  	want := []string{"runtime.Callers", "runtime_test.TestCallersAbortedPanic.func2", "runtime_test.TestCallersAbortedPanic"}
   154  
   155  	defer func() {
   156  		r := recover()
   157  		if r != nil {
   158  			t.Fatalf("should be no panic remaining to recover")
   159  		}
   160  	}()
   161  
   162  	defer func() {
   163  		// panic2 was aborted/replaced by panic1, so when panic2 was
   164  		// recovered, there is no remaining panic on the stack.
   165  		pcs := make([]uintptr, 20)
   166  		pcs = pcs[:runtime.Callers(0, pcs)]
   167  		testCallersEqual(t, pcs, want)
   168  	}()
   169  	defer func() {
   170  		r := recover()
   171  		if r != "panic2" {
   172  			t.Fatalf("got %v, wanted %v", r, "panic2")
   173  		}
   174  	}()
   175  	defer func() {
   176  		// panic2 aborts/replaces panic1, because it is a recursive panic
   177  		// that is not recovered within the defer function called by
   178  		// panic1 panicking sequence
   179  		panic("panic2")
   180  	}()
   181  	panic("panic1")
   182  }
   183  
   184  func TestCallersAbortedPanic2(t *testing.T) {
   185  	want := []string{"runtime.Callers", "runtime_test.TestCallersAbortedPanic2.func2", "runtime_test.TestCallersAbortedPanic2"}
   186  	defer func() {
   187  		r := recover()
   188  		if r != nil {
   189  			t.Fatalf("should be no panic remaining to recover")
   190  		}
   191  	}()
   192  	defer func() {
   193  		pcs := make([]uintptr, 20)
   194  		pcs = pcs[:runtime.Callers(0, pcs)]
   195  		testCallersEqual(t, pcs, want)
   196  	}()
   197  	func() {
   198  		defer func() {
   199  			r := recover()
   200  			if r != "panic2" {
   201  				t.Fatalf("got %v, wanted %v", r, "panic2")
   202  			}
   203  		}()
   204  		func() {
   205  			defer func() {
   206  				// Again, panic2 aborts/replaces panic1
   207  				panic("panic2")
   208  			}()
   209  			panic("panic1")
   210  		}()
   211  	}()
   212  }
   213  
   214  func TestCallersNilPointerPanic(t *testing.T) {
   215  	// Make sure we don't have any extra frames on the stack (due to
   216  	// open-coded defer processing)
   217  	want := []string{"runtime.Callers", "runtime_test.TestCallersNilPointerPanic.func1",
   218  		"runtime.gopanic", "runtime.panicmem", "runtime.sigpanic",
   219  		"runtime_test.TestCallersNilPointerPanic"}
   220  
   221  	defer func() {
   222  		if r := recover(); r == nil {
   223  			t.Fatal("did not panic")
   224  		}
   225  		pcs := make([]uintptr, 20)
   226  		pcs = pcs[:runtime.Callers(0, pcs)]
   227  		testCallersEqual(t, pcs, want)
   228  	}()
   229  	var p *int
   230  	if *p == 3 {
   231  		t.Fatal("did not see nil pointer panic")
   232  	}
   233  }
   234  
   235  func TestCallersDivZeroPanic(t *testing.T) {
   236  	// Make sure we don't have any extra frames on the stack (due to
   237  	// open-coded defer processing)
   238  	want := []string{"runtime.Callers", "runtime_test.TestCallersDivZeroPanic.func1",
   239  		"runtime.gopanic", "runtime.panicdivide",
   240  		"runtime_test.TestCallersDivZeroPanic"}
   241  
   242  	defer func() {
   243  		if r := recover(); r == nil {
   244  			t.Fatal("did not panic")
   245  		}
   246  		pcs := make([]uintptr, 20)
   247  		pcs = pcs[:runtime.Callers(0, pcs)]
   248  		testCallersEqual(t, pcs, want)
   249  	}()
   250  	var n int
   251  	if 5/n == 1 {
   252  		t.Fatal("did not see divide-by-sizer panic")
   253  	}
   254  }
   255  
   256  func TestCallersDeferNilFuncPanic(t *testing.T) {
   257  	// Make sure we don't have any extra frames on the stack. We cut off the check
   258  	// at runtime.sigpanic, because non-open-coded defers (which may be used in
   259  	// non-opt or race checker mode) include an extra 'deferreturn' frame (which is
   260  	// where the nil pointer deref happens).
   261  	state := 1
   262  	want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanic.func1",
   263  		"runtime.gopanic", "runtime.panicmem", "runtime.sigpanic"}
   264  
   265  	defer func() {
   266  		if r := recover(); r == nil {
   267  			t.Fatal("did not panic")
   268  		}
   269  		pcs := make([]uintptr, 20)
   270  		pcs = pcs[:runtime.Callers(0, pcs)]
   271  		testCallersEqual(t, pcs, want)
   272  		if state == 1 {
   273  			t.Fatal("nil defer func panicked at defer time rather than function exit time")
   274  		}
   275  
   276  	}()
   277  	var f func()
   278  	defer f()
   279  	// Use the value of 'state' to make sure nil defer func f causes panic at
   280  	// function exit, rather than at the defer statement.
   281  	state = 2
   282  }
   283  
   284  // Same test, but forcing non-open-coded defer by putting the defer in a loop.  See
   285  // issue #36050
   286  func TestCallersDeferNilFuncPanicWithLoop(t *testing.T) {
   287  	state := 1
   288  	want := []string{"runtime.Callers", "runtime_test.TestCallersDeferNilFuncPanicWithLoop.func1",
   289  		"runtime.gopanic", "runtime.panicmem", "runtime.sigpanic", "runtime.deferreturn", "runtime_test.TestCallersDeferNilFuncPanicWithLoop"}
   290  
   291  	defer func() {
   292  		if r := recover(); r == nil {
   293  			t.Fatal("did not panic")
   294  		}
   295  		pcs := make([]uintptr, 20)
   296  		pcs = pcs[:runtime.Callers(0, pcs)]
   297  		testCallersEqual(t, pcs, want)
   298  		if state == 1 {
   299  			t.Fatal("nil defer func panicked at defer time rather than function exit time")
   300  		}
   301  
   302  	}()
   303  
   304  	for i := 0; i < 1; i++ {
   305  		var f func()
   306  		defer f()
   307  	}
   308  	// Use the value of 'state' to make sure nil defer func f causes panic at
   309  	// function exit, rather than at the defer statement.
   310  	state = 2
   311  }
   312  
   313  // issue #51988
   314  // Func.Endlineno was lost when instantiating generic functions, leading to incorrect
   315  // stack trace positions.
   316  func TestCallersEndlineno(t *testing.T) {
   317  	testNormalEndlineno(t)
   318  	testGenericEndlineno[int](t)
   319  }
   320  
   321  func testNormalEndlineno(t *testing.T) {
   322  	defer testCallerLine(t, callerLine(t, 0)+1)
   323  }
   324  
   325  func testGenericEndlineno[_ any](t *testing.T) {
   326  	defer testCallerLine(t, callerLine(t, 0)+1)
   327  }
   328  
   329  func testCallerLine(t *testing.T, want int) {
   330  	if have := callerLine(t, 1); have != want {
   331  		t.Errorf("callerLine(1) returned %d, but want %d\n", have, want)
   332  	}
   333  }
   334  
   335  func callerLine(t *testing.T, skip int) int {
   336  	_, _, line, ok := runtime.Caller(skip + 1)
   337  	if !ok {
   338  		t.Fatalf("runtime.Caller(%d) failed", skip+1)
   339  	}
   340  	return line
   341  }
   342  
   343  func BenchmarkCallers(b *testing.B) {
   344  	b.Run("cached", func(b *testing.B) {
   345  		// Very pcvalueCache-friendly, no inlining.
   346  		callersCached(b, 100)
   347  	})
   348  	b.Run("inlined", func(b *testing.B) {
   349  		// Some inlining, still pretty cache-friendly.
   350  		callersInlined(b, 100)
   351  	})
   352  	b.Run("no-cache", func(b *testing.B) {
   353  		// Cache-hostile
   354  		callersNoCache(b, 100)
   355  	})
   356  }
   357  
   358  func callersCached(b *testing.B, n int) int {
   359  	if n <= 0 {
   360  		pcs := make([]uintptr, 32)
   361  		b.ResetTimer()
   362  		for i := 0; i < b.N; i++ {
   363  			runtime.Callers(0, pcs)
   364  		}
   365  		b.StopTimer()
   366  		return 0
   367  	}
   368  	return 1 + callersCached(b, n-1)
   369  }
   370  
   371  func callersInlined(b *testing.B, n int) int {
   372  	if n <= 0 {
   373  		pcs := make([]uintptr, 32)
   374  		b.ResetTimer()
   375  		for i := 0; i < b.N; i++ {
   376  			runtime.Callers(0, pcs)
   377  		}
   378  		b.StopTimer()
   379  		return 0
   380  	}
   381  	return 1 + callersInlined1(b, n-1)
   382  }
   383  func callersInlined1(b *testing.B, n int) int { return callersInlined2(b, n) }
   384  func callersInlined2(b *testing.B, n int) int { return callersInlined3(b, n) }
   385  func callersInlined3(b *testing.B, n int) int { return callersInlined4(b, n) }
   386  func callersInlined4(b *testing.B, n int) int { return callersInlined(b, n) }
   387  
   388  func callersNoCache(b *testing.B, n int) int {
   389  	if n <= 0 {
   390  		pcs := make([]uintptr, 32)
   391  		b.ResetTimer()
   392  		for i := 0; i < b.N; i++ {
   393  			runtime.Callers(0, pcs)
   394  		}
   395  		b.StopTimer()
   396  		return 0
   397  	}
   398  	switch n % 16 {
   399  	case 0:
   400  		return 1 + callersNoCache(b, n-1)
   401  	case 1:
   402  		return 1 + callersNoCache(b, n-1)
   403  	case 2:
   404  		return 1 + callersNoCache(b, n-1)
   405  	case 3:
   406  		return 1 + callersNoCache(b, n-1)
   407  	case 4:
   408  		return 1 + callersNoCache(b, n-1)
   409  	case 5:
   410  		return 1 + callersNoCache(b, n-1)
   411  	case 6:
   412  		return 1 + callersNoCache(b, n-1)
   413  	case 7:
   414  		return 1 + callersNoCache(b, n-1)
   415  	case 8:
   416  		return 1 + callersNoCache(b, n-1)
   417  	case 9:
   418  		return 1 + callersNoCache(b, n-1)
   419  	case 10:
   420  		return 1 + callersNoCache(b, n-1)
   421  	case 11:
   422  		return 1 + callersNoCache(b, n-1)
   423  	case 12:
   424  		return 1 + callersNoCache(b, n-1)
   425  	case 13:
   426  		return 1 + callersNoCache(b, n-1)
   427  	case 14:
   428  		return 1 + callersNoCache(b, n-1)
   429  	default:
   430  		return 1 + callersNoCache(b, n-1)
   431  	}
   432  }
   433  
   434  func BenchmarkFPCallers(b *testing.B) {
   435  	b.Run("cached", func(b *testing.B) {
   436  		// Very pcvalueCache-friendly, no inlining.
   437  		fpCallersCached(b, 100)
   438  	})
   439  }
   440  
   441  func fpCallersCached(b *testing.B, n int) int {
   442  	if n <= 0 {
   443  		pcs := make([]uintptr, 32)
   444  		b.ResetTimer()
   445  		for i := 0; i < b.N; i++ {
   446  			runtime.FPCallers(pcs)
   447  		}
   448  		b.StopTimer()
   449  		return 0
   450  	}
   451  	return 1 + fpCallersCached(b, n-1)
   452  }
   453  
   454  func TestFPUnwindAfterRecovery(t *testing.T) {
   455  	if !runtime.FramePointerEnabled {
   456  		t.Skip("frame pointers not supported for this architecture")
   457  	}
   458  	// Make sure that frame pointer unwinding succeeds from a deferred
   459  	// function run after recovering from a panic. It can fail if the
   460  	// recovery does not properly restore the caller's frame pointer before
   461  	// running the remaining deferred functions.
   462  	//
   463  	// This test does not verify the accuracy of the call stack (it
   464  	// currently includes a frame from runtime.deferreturn which would
   465  	// normally be omitted). It is only intended to check that producing the
   466  	// call stack won't crash.
   467  	defer func() {
   468  		pcs := make([]uintptr, 32)
   469  		for i := range pcs {
   470  			// If runtime.recovery doesn't properly restore the
   471  			// frame pointer before returning control to this
   472  			// function, it will point somewhere lower in the stack
   473  			// from one of the frames of runtime.gopanic() or one of
   474  			// it's callees prior to recovery.  So, we put some
   475  			// non-zero values on the stack to ensure that frame
   476  			// pointer unwinding will crash if it sees the old,
   477  			// invalid frame pointer.
   478  			pcs[i] = 10
   479  		}
   480  		runtime.FPCallers(pcs)
   481  		t.Logf("%v", pcs)
   482  	}()
   483  	defer func() {
   484  		if recover() == nil {
   485  			t.Fatal("did not recover from panic")
   486  		}
   487  	}()
   488  	panic(1)
   489  }
   490  

View as plain text