@@ -10,11 +10,143 @@ import (
10
10
"internal/abi"
11
11
"internal/testenv"
12
12
"runtime"
13
+ "runtime/debug"
13
14
"strings"
14
15
"sync"
15
16
"testing"
17
+ _ "unsafe"
16
18
)
17
19
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
+
18
150
var testTracebackArgsBuf [1000 ]byte
19
151
20
152
func TestTracebackArgs (t * testing.T ) {
@@ -443,3 +575,72 @@ func TestTracebackParentChildGoroutines(t *testing.T) {
443
575
}()
444
576
wg .Wait ()
445
577
}
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