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