1// Copyright 2022 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 5package runtime_test 6 7import ( 8 "fmt" 9 "internal/testenv" 10 "runtime" 11 "testing" 12) 13 14// The tests in this file test the function start line metadata included in 15// _func and inlinedCall. TestStartLine hard-codes the start lines of functions 16// in this file. If code moves, the test will need to be updated. 17// 18// The "start line" of a function should be the line containing the func 19// keyword. 20 21func normalFunc() int { 22 return callerStartLine(false) 23} 24 25func multilineDeclarationFunc() int { 26 return multilineDeclarationFunc1(0, 0, 0) 27} 28 29//go:noinline 30func multilineDeclarationFunc1( 31 a, b, c int) int { 32 return callerStartLine(false) 33} 34 35func blankLinesFunc() int { 36 37 // Some 38 // lines 39 // without 40 // code 41 42 return callerStartLine(false) 43} 44 45func inlineFunc() int { 46 return inlineFunc1() 47} 48 49func inlineFunc1() int { 50 return callerStartLine(true) 51} 52 53var closureFn func() int 54 55func normalClosure() int { 56 // Assign to global to ensure this isn't inlined. 57 closureFn = func() int { 58 return callerStartLine(false) 59 } 60 return closureFn() 61} 62 63func inlineClosure() int { 64 return func() int { 65 return callerStartLine(true) 66 }() 67} 68 69func TestStartLine(t *testing.T) { 70 // We test inlined vs non-inlined variants. We can't do that if 71 // optimizations are disabled. 72 testenv.SkipIfOptimizationOff(t) 73 74 testCases := []struct { 75 name string 76 fn func() int 77 want int 78 }{ 79 { 80 name: "normal", 81 fn: normalFunc, 82 want: 21, 83 }, 84 { 85 name: "multiline-declaration", 86 fn: multilineDeclarationFunc, 87 want: 30, 88 }, 89 { 90 name: "blank-lines", 91 fn: blankLinesFunc, 92 want: 35, 93 }, 94 { 95 name: "inline", 96 fn: inlineFunc, 97 want: 49, 98 }, 99 { 100 name: "normal-closure", 101 fn: normalClosure, 102 want: 57, 103 }, 104 { 105 name: "inline-closure", 106 fn: inlineClosure, 107 want: 64, 108 }, 109 } 110 111 for _, tc := range testCases { 112 t.Run(tc.name, func(t *testing.T) { 113 got := tc.fn() 114 if got != tc.want { 115 t.Errorf("start line got %d want %d", got, tc.want) 116 } 117 }) 118 } 119} 120 121//go:noinline 122func callerStartLine(wantInlined bool) int { 123 var pcs [1]uintptr 124 n := runtime.Callers(2, pcs[:]) 125 if n != 1 { 126 panic(fmt.Sprintf("no caller of callerStartLine? n = %d", n)) 127 } 128 129 frames := runtime.CallersFrames(pcs[:]) 130 frame, _ := frames.Next() 131 132 inlined := frame.Func == nil // Func always set to nil for inlined frames 133 if wantInlined != inlined { 134 panic(fmt.Sprintf("caller %s inlined got %v want %v", frame.Function, inlined, wantInlined)) 135 } 136 137 return runtime.FrameStartLine(&frame) 138} 139