Skip to content

Commit 59d0de1

Browse files
committed
runtime: add tests of printing inlined frames in tracebacks
We're about to rewrite this code and it has almost no test coverage right now. This test is also more complete than the existing TestTracebackInlineExcluded, so we delete that test. For #54466. Change-Id: I144154282dac5eb3798f7d332b806f44c4a0bdf6 Reviewed-on: https://go-review.googlesource.com/c/go/+/466098 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Austin Clements <austin@google.com> Reviewed-by: Michael Pratt <mpratt@google.com>
1 parent d829b62 commit 59d0de1

File tree

2 files changed

+201
-40
lines changed

2 files changed

+201
-40
lines changed

src/runtime/stack_test.go

-40
Original file line numberDiff line numberDiff line change
@@ -897,43 +897,3 @@ func deferHeapAndStack(n int) (r int) {
897897

898898
// Pass a value to escapeMe to force it to escape.
899899
var escapeMe = func(x any) {}
900-
901-
// Test that when F -> G is inlined and F is excluded from stack
902-
// traces, G still appears.
903-
func TestTracebackInlineExcluded(t *testing.T) {
904-
defer func() {
905-
recover()
906-
buf := make([]byte, 4<<10)
907-
stk := string(buf[:Stack(buf, false)])
908-
909-
t.Log(stk)
910-
911-
if not := "tracebackExcluded"; strings.Contains(stk, not) {
912-
t.Errorf("found but did not expect %q", not)
913-
}
914-
if want := "tracebackNotExcluded"; !strings.Contains(stk, want) {
915-
t.Errorf("expected %q in stack", want)
916-
}
917-
}()
918-
tracebackExcluded()
919-
}
920-
921-
// tracebackExcluded should be excluded from tracebacks. There are
922-
// various ways this could come up. Linking it to a "runtime." name is
923-
// rather synthetic, but it's easy and reliable. See issue #42754 for
924-
// one way this happened in real code.
925-
//
926-
//go:linkname tracebackExcluded runtime.tracebackExcluded
927-
//go:noinline
928-
func tracebackExcluded() {
929-
// Call an inlined function that should not itself be excluded
930-
// from tracebacks.
931-
tracebackNotExcluded()
932-
}
933-
934-
// tracebackNotExcluded should be inlined into tracebackExcluded, but
935-
// should not itself be excluded from the traceback.
936-
func tracebackNotExcluded() {
937-
var x *int
938-
*x = 0
939-
}

src/runtime/traceback_test.go

+201
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,143 @@ import (
1010
"internal/abi"
1111
"internal/testenv"
1212
"runtime"
13+
"runtime/debug"
1314
"strings"
1415
"sync"
1516
"testing"
17+
_ "unsafe"
1618
)
1719

20+
// Test traceback printing of inlined frames.
21+
func TestTracebackInlined(t *testing.T) {
22+
check := func(t *testing.T, r *ttiResult, funcs ...string) {
23+
t.Helper()
24+
25+
// Check the printed traceback.
26+
frames := parseTraceback1(t, r.printed).frames
27+
t.Log(r.printed)
28+
// Find ttiLeaf
29+
for len(frames) > 0 && frames[0].funcName != "runtime_test.ttiLeaf" {
30+
frames = frames[1:]
31+
}
32+
if len(frames) == 0 {
33+
t.Errorf("missing runtime_test.ttiLeaf")
34+
return
35+
}
36+
frames = frames[1:]
37+
// Check the function sequence.
38+
for i, want := range funcs {
39+
got := "<end>"
40+
if i < len(frames) {
41+
got = frames[i].funcName
42+
if strings.HasSuffix(want, ")") {
43+
got += "(" + frames[i].args + ")"
44+
}
45+
}
46+
if got != want {
47+
t.Errorf("got %s, want %s", got, want)
48+
return
49+
}
50+
}
51+
}
52+
53+
t.Run("simple", func(t *testing.T) {
54+
// Check a simple case of inlining
55+
r := ttiSimple1()
56+
check(t, r, "runtime_test.ttiSimple3(...)", "runtime_test.ttiSimple2(...)", "runtime_test.ttiSimple1()")
57+
})
58+
59+
t.Run("sigpanic", func(t *testing.T) {
60+
// Check that sigpanic from an inlined function prints correctly
61+
r := ttiSigpanic1()
62+
check(t, r, "runtime_test.ttiSigpanic1.func1()", "panic", "runtime_test.ttiSigpanic3(...)", "runtime_test.ttiSigpanic2(...)", "runtime_test.ttiSigpanic1()")
63+
})
64+
65+
t.Run("wrapper", func(t *testing.T) {
66+
// Check that a method inlined into a wrapper prints correctly
67+
r := ttiWrapper1()
68+
check(t, r, "runtime_test.ttiWrapper.m1(...)", "runtime_test.ttiWrapper1()")
69+
})
70+
71+
t.Run("excluded", func(t *testing.T) {
72+
// Check that when F -> G is inlined and F is excluded from stack
73+
// traces, G still appears.
74+
r := ttiExcluded1()
75+
check(t, r, "runtime_test.ttiExcluded3(...)", "runtime_test.ttiExcluded1()")
76+
})
77+
}
78+
79+
type ttiResult struct {
80+
printed string
81+
}
82+
83+
//go:noinline
84+
func ttiLeaf() *ttiResult {
85+
// Get a printed stack trace.
86+
printed := string(debug.Stack())
87+
return &ttiResult{printed}
88+
}
89+
90+
//go:noinline
91+
func ttiSimple1() *ttiResult {
92+
return ttiSimple2()
93+
}
94+
func ttiSimple2() *ttiResult {
95+
return ttiSimple3()
96+
}
97+
func ttiSimple3() *ttiResult {
98+
return ttiLeaf()
99+
}
100+
101+
//go:noinline
102+
func ttiSigpanic1() (res *ttiResult) {
103+
defer func() {
104+
res = ttiLeaf()
105+
recover()
106+
}()
107+
ttiSigpanic2()
108+
panic("did not panic")
109+
}
110+
func ttiSigpanic2() {
111+
ttiSigpanic3()
112+
}
113+
func ttiSigpanic3() {
114+
var p *int
115+
*p = 3
116+
}
117+
118+
//go:noinline
119+
func ttiWrapper1() *ttiResult {
120+
var w ttiWrapper
121+
m := (*ttiWrapper).m1
122+
return m(&w)
123+
}
124+
125+
type ttiWrapper struct{}
126+
127+
func (w ttiWrapper) m1() *ttiResult {
128+
return ttiLeaf()
129+
}
130+
131+
//go:noinline
132+
func ttiExcluded1() *ttiResult {
133+
return ttiExcluded2()
134+
}
135+
136+
// ttiExcluded2 should be excluded from tracebacks. There are
137+
// various ways this could come up. Linking it to a "runtime." name is
138+
// rather synthetic, but it's easy and reliable. See issue #42754 for
139+
// one way this happened in real code.
140+
//
141+
//go:linkname ttiExcluded2 runtime.ttiExcluded2
142+
//go:noinline
143+
func ttiExcluded2() *ttiResult {
144+
return ttiExcluded3()
145+
}
146+
func ttiExcluded3() *ttiResult {
147+
return ttiLeaf()
148+
}
149+
18150
var testTracebackArgsBuf [1000]byte
19151

20152
func TestTracebackArgs(t *testing.T) {
@@ -443,3 +575,72 @@ func TestTracebackParentChildGoroutines(t *testing.T) {
443575
}()
444576
wg.Wait()
445577
}
578+
579+
type traceback struct {
580+
frames []*tbFrame
581+
createdBy *tbFrame // no args
582+
}
583+
584+
type tbFrame struct {
585+
funcName string
586+
args string
587+
inlined bool
588+
}
589+
590+
// parseTraceback parses a printed traceback to make it easier for tests to
591+
// check the result.
592+
func parseTraceback(t *testing.T, tb string) []*traceback {
593+
lines := strings.Split(tb, "\n")
594+
nLines := len(lines)
595+
fatal := func(f string, args ...any) {
596+
lineNo := nLines - len(lines) + 1
597+
msg := fmt.Sprintf(f, args...)
598+
t.Fatalf("%s (line %d):\n%s", msg, lineNo, tb)
599+
}
600+
parseFrame := func(funcName, args string) *tbFrame {
601+
// Consume file/line/etc
602+
if len(lines) == 0 || !strings.HasPrefix(lines[0], "\t") {
603+
fatal("missing source line")
604+
}
605+
lines = lines[1:]
606+
inlined := args == "..."
607+
return &tbFrame{funcName, args, inlined}
608+
}
609+
var tbs []*traceback
610+
var cur *traceback
611+
for len(lines) > 0 {
612+
line := lines[0]
613+
lines = lines[1:]
614+
switch {
615+
case strings.HasPrefix(line, "goroutine "):
616+
cur = &traceback{}
617+
tbs = append(tbs, cur)
618+
case line == "":
619+
cur = nil
620+
case line[0] == '\t':
621+
fatal("unexpected indent")
622+
case strings.HasPrefix(line, "created by "):
623+
funcName := line[len("created by "):]
624+
cur.createdBy = parseFrame(funcName, "")
625+
case strings.HasSuffix(line, ")"):
626+
line = line[:len(line)-1] // Trim trailing ")"
627+
funcName, args, found := strings.Cut(line, "(")
628+
if !found {
629+
fatal("missing (")
630+
}
631+
frame := parseFrame(funcName, args)
632+
cur.frames = append(cur.frames, frame)
633+
}
634+
}
635+
return tbs
636+
}
637+
638+
// parseTraceback1 is like parseTraceback, but expects tb to contain exactly one
639+
// goroutine.
640+
func parseTraceback1(t *testing.T, tb string) *traceback {
641+
tbs := parseTraceback(t, tb)
642+
if len(tbs) != 1 {
643+
t.Fatalf("want 1 goroutine, got %d:\n%s", len(tbs), tb)
644+
}
645+
return tbs[0]
646+
}

0 commit comments

Comments
 (0)