summaryrefslogtreecommitdiffstats
path: root/src/runtime/start_line_test.go
blob: 0762351b0c6f2269c293cf14245c8d9eb4c1ed11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Copyright 2022 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 (
	"fmt"
	"internal/testenv"
	"runtime"
	"testing"
)

// The tests in this file test the function start line metadata included in
// _func and inlinedCall. TestStartLine hard-codes the start lines of functions
// in this file. If code moves, the test will need to be updated.
//
// The "start line" of a function should be the line containing the func
// keyword.

func normalFunc() int {
	return callerStartLine(false)
}

func multilineDeclarationFunc() int {
	return multilineDeclarationFunc1(0, 0, 0)
}

//go:noinline
func multilineDeclarationFunc1(
	a, b, c int) int {
	return callerStartLine(false)
}

func blankLinesFunc() int {

	// Some
	// lines
	// without
	// code

	return callerStartLine(false)
}

func inlineFunc() int {
	return inlineFunc1()
}

func inlineFunc1() int {
	return callerStartLine(true)
}

var closureFn func() int

func normalClosure() int {
	// Assign to global to ensure this isn't inlined.
	closureFn = func() int {
		return callerStartLine(false)
	}
	return closureFn()
}

func inlineClosure() int {
	return func() int {
		return callerStartLine(true)
	}()
}

func TestStartLine(t *testing.T) {
	// We test inlined vs non-inlined variants. We can't do that if
	// optimizations are disabled.
	testenv.SkipIfOptimizationOff(t)

	testCases := []struct {
		name string
		fn   func() int
		want int
	}{
		{
			name: "normal",
			fn:   normalFunc,
			want: 21,
		},
		{
			name: "multiline-declaration",
			fn:   multilineDeclarationFunc,
			want: 30,
		},
		{
			name: "blank-lines",
			fn:   blankLinesFunc,
			want: 35,
		},
		{
			name: "inline",
			fn:   inlineFunc,
			want: 49,
		},
		{
			name: "normal-closure",
			fn:   normalClosure,
			want: 57,
		},
		{
			name: "inline-closure",
			fn:   inlineClosure,
			want: 64,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got := tc.fn()
			if got != tc.want {
				t.Errorf("start line got %d want %d", got, tc.want)
			}
		})
	}
}

//go:noinline
func callerStartLine(wantInlined bool) int {
	var pcs [1]uintptr
	n := runtime.Callers(2, pcs[:])
	if n != 1 {
		panic(fmt.Sprintf("no caller of callerStartLine? n = %d", n))
	}

	frames := runtime.CallersFrames(pcs[:])
	frame, _ := frames.Next()

	inlined := frame.Func == nil // Func always set to nil for inlined frames
	if wantInlined != inlined {
		panic(fmt.Sprintf("caller %s inlined got %v want %v", frame.Function, inlined, wantInlined))
	}

	return runtime.FrameStartLine(&frame)
}