1// Copyright 2023 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
6
7import (
8	"internal/abi"
9	"internal/stringslite"
10	"runtime/internal/sys"
11)
12
13func XTestInlineUnwinder(t TestingT) {
14	if TestenvOptimizationOff() {
15		t.Skip("skipping test with inlining optimizations disabled")
16	}
17
18	pc1 := abi.FuncPCABIInternal(tiuTest)
19	f := findfunc(pc1)
20	if !f.valid() {
21		t.Fatalf("failed to resolve tiuTest at PC %#x", pc1)
22	}
23
24	want := map[string]int{
25		"tiuInlined1:3 tiuTest:10":               0,
26		"tiuInlined1:3 tiuInlined2:6 tiuTest:11": 0,
27		"tiuInlined2:7 tiuTest:11":               0,
28		"tiuTest:12":                             0,
29	}
30	wantStart := map[string]int{
31		"tiuInlined1": 2,
32		"tiuInlined2": 5,
33		"tiuTest":     9,
34	}
35
36	// Iterate over the PCs in tiuTest and walk the inline stack for each.
37	prevStack := "x"
38	for pc := pc1; pc < pc1+1024 && findfunc(pc) == f; pc += sys.PCQuantum {
39		stack := ""
40		u, uf := newInlineUnwinder(f, pc)
41		if file, _ := u.fileLine(uf); file == "?" {
42			// We're probably in the trailing function padding, where findfunc
43			// still returns f but there's no symbolic information. Just keep
44			// going until we definitely hit the end. If we see a "?" in the
45			// middle of unwinding, that's a real problem.
46			//
47			// TODO: If we ever have function end information, use that to make
48			// this robust.
49			continue
50		}
51		for ; uf.valid(); uf = u.next(uf) {
52			file, line := u.fileLine(uf)
53			const wantFile = "symtabinl_test.go"
54			if !stringslite.HasSuffix(file, wantFile) {
55				t.Errorf("tiuTest+%#x: want file ...%s, got %s", pc-pc1, wantFile, file)
56			}
57
58			sf := u.srcFunc(uf)
59
60			name := sf.name()
61			const namePrefix = "runtime."
62			if stringslite.HasPrefix(name, namePrefix) {
63				name = name[len(namePrefix):]
64			}
65			if !stringslite.HasPrefix(name, "tiu") {
66				t.Errorf("tiuTest+%#x: unexpected function %s", pc-pc1, name)
67			}
68
69			start := int(sf.startLine) - tiuStart
70			if start != wantStart[name] {
71				t.Errorf("tiuTest+%#x: want startLine %d, got %d", pc-pc1, wantStart[name], start)
72			}
73			if sf.funcID != abi.FuncIDNormal {
74				t.Errorf("tiuTest+%#x: bad funcID %v", pc-pc1, sf.funcID)
75			}
76
77			if len(stack) > 0 {
78				stack += " "
79			}
80			stack += FmtSprintf("%s:%d", name, line-tiuStart)
81		}
82
83		if stack != prevStack {
84			prevStack = stack
85
86			t.Logf("tiuTest+%#x: %s", pc-pc1, stack)
87
88			if _, ok := want[stack]; ok {
89				want[stack]++
90			}
91		}
92	}
93
94	// Check that we got all the stacks we wanted.
95	for stack, count := range want {
96		if count == 0 {
97			t.Errorf("missing stack %s", stack)
98		}
99	}
100}
101
102func lineNumber() int {
103	_, _, line, _ := Caller(1)
104	return line // return 0 for error
105}
106
107// Below here is the test data for XTestInlineUnwinder
108
109var tiuStart = lineNumber() // +0
110var tiu1, tiu2, tiu3 int    // +1
111func tiuInlined1() { // +2
112	tiu1++ // +3
113} // +4
114func tiuInlined2() { // +5
115	tiuInlined1() // +6
116	tiu2++        // +7
117} // +8
118func tiuTest() { // +9
119	tiuInlined1() // +10
120	tiuInlined2() // +11
121	tiu3++        // +12
122} // +13
123