]> Cypherpunks.ru repositories - gostls13.git/blob - src/runtime/runtime-seh_windows_test.go
cmd/compile/internal/inline: score call sites exposed by inlines
[gostls13.git] / src / runtime / runtime-seh_windows_test.go
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
5 package runtime_test
6
7 import (
8         "internal/abi"
9         "internal/syscall/windows"
10         "runtime"
11         "slices"
12         "testing"
13         "unsafe"
14 )
15
16 func sehf1() int {
17         return sehf1()
18 }
19
20 func sehf2() {}
21
22 func TestSehLookupFunctionEntry(t *testing.T) {
23         if runtime.GOARCH != "amd64" {
24                 t.Skip("skipping amd64-only test")
25         }
26         // This test checks that Win32 is able to retrieve
27         // function metadata stored in the .pdata section
28         // by the Go linker.
29         // Win32 unwinding will fail if this test fails,
30         // as RtlUnwindEx uses RtlLookupFunctionEntry internally.
31         // If that's the case, don't bother investigating further,
32         // first fix the .pdata generation.
33         sehf1pc := abi.FuncPCABIInternal(sehf1)
34         var fnwithframe func()
35         fnwithframe = func() {
36                 fnwithframe()
37         }
38         fnwithoutframe := func() {}
39         tests := []struct {
40                 name     string
41                 pc       uintptr
42                 hasframe bool
43         }{
44                 {"no frame func", abi.FuncPCABIInternal(sehf2), false},
45                 {"no func", sehf1pc - 1, false},
46                 {"func at entry", sehf1pc, true},
47                 {"func in prologue", sehf1pc + 1, true},
48                 {"anonymous func with frame", abi.FuncPCABIInternal(fnwithframe), true},
49                 {"anonymous func without frame", abi.FuncPCABIInternal(fnwithoutframe), false},
50                 {"pc at func body", runtime.NewContextStub().GetPC(), true},
51         }
52         for _, tt := range tests {
53                 var base uintptr
54                 fn := windows.RtlLookupFunctionEntry(tt.pc, &base, nil)
55                 if !tt.hasframe {
56                         if fn != 0 {
57                                 t.Errorf("%s: unexpected frame", tt.name)
58                         }
59                         continue
60                 }
61                 if fn == 0 {
62                         t.Errorf("%s: missing frame", tt.name)
63                 }
64         }
65 }
66
67 func sehCallers() []uintptr {
68         // We don't need a real context,
69         // RtlVirtualUnwind just needs a context with
70         // valid a pc, sp and fp (aka bp).
71         ctx := runtime.NewContextStub()
72
73         pcs := make([]uintptr, 15)
74         var base, frame uintptr
75         var n int
76         for i := 0; i < len(pcs); i++ {
77                 fn := windows.RtlLookupFunctionEntry(ctx.GetPC(), &base, nil)
78                 if fn == 0 {
79                         break
80                 }
81                 pcs[i] = ctx.GetPC()
82                 n++
83                 windows.RtlVirtualUnwind(0, base, ctx.GetPC(), fn, uintptr(unsafe.Pointer(ctx)), nil, &frame, nil)
84         }
85         return pcs[:n]
86 }
87
88 // SEH unwinding does not report inlined frames.
89 //
90 //go:noinline
91 func sehf3(pan bool) []uintptr {
92         return sehf4(pan)
93 }
94
95 //go:noinline
96 func sehf4(pan bool) []uintptr {
97         var pcs []uintptr
98         if pan {
99                 panic("sehf4")
100         }
101         pcs = sehCallers()
102         return pcs
103 }
104
105 func testSehCallersEqual(t *testing.T, pcs []uintptr, want []string) {
106         t.Helper()
107         got := make([]string, 0, len(want))
108         for _, pc := range pcs {
109                 fn := runtime.FuncForPC(pc)
110                 if fn == nil || len(got) >= len(want) {
111                         break
112                 }
113                 name := fn.Name()
114                 switch name {
115                 case "runtime.panicmem":
116                         // These functions are skipped as they appear inconsistently depending
117                         // whether inlining is on or off.
118                         continue
119                 }
120                 got = append(got, name)
121         }
122         if !slices.Equal(want, got) {
123                 t.Fatalf("wanted %v, got %v", want, got)
124         }
125 }
126
127 func TestSehUnwind(t *testing.T) {
128         if runtime.GOARCH != "amd64" {
129                 t.Skip("skipping amd64-only test")
130         }
131         pcs := sehf3(false)
132         testSehCallersEqual(t, pcs, []string{"runtime_test.sehCallers", "runtime_test.sehf4",
133                 "runtime_test.sehf3", "runtime_test.TestSehUnwind"})
134 }
135
136 func TestSehUnwindPanic(t *testing.T) {
137         if runtime.GOARCH != "amd64" {
138                 t.Skip("skipping amd64-only test")
139         }
140         want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindPanic.func1", "runtime.gopanic",
141                 "runtime_test.sehf4", "runtime_test.sehf3", "runtime_test.TestSehUnwindPanic"}
142         defer func() {
143                 if r := recover(); r == nil {
144                         t.Fatal("did not panic")
145                 }
146                 pcs := sehCallers()
147                 testSehCallersEqual(t, pcs, want)
148         }()
149         sehf3(true)
150 }
151
152 func TestSehUnwindDoublePanic(t *testing.T) {
153         if runtime.GOARCH != "amd64" {
154                 t.Skip("skipping amd64-only test")
155         }
156         want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindDoublePanic.func1.1", "runtime.gopanic",
157                 "runtime_test.TestSehUnwindDoublePanic.func1", "runtime.gopanic", "runtime_test.TestSehUnwindDoublePanic"}
158         defer func() {
159                 defer func() {
160                         if recover() == nil {
161                                 t.Fatal("did not panic")
162                         }
163                         pcs := sehCallers()
164                         testSehCallersEqual(t, pcs, want)
165                 }()
166                 if recover() == nil {
167                         t.Fatal("did not panic")
168                 }
169                 panic(2)
170         }()
171         panic(1)
172 }
173
174 func TestSehUnwindNilPointerPanic(t *testing.T) {
175         if runtime.GOARCH != "amd64" {
176                 t.Skip("skipping amd64-only test")
177         }
178         want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindNilPointerPanic.func1", "runtime.gopanic",
179                 "runtime.sigpanic", "runtime_test.TestSehUnwindNilPointerPanic"}
180         defer func() {
181                 if r := recover(); r == nil {
182                         t.Fatal("did not panic")
183                 }
184                 pcs := sehCallers()
185                 testSehCallersEqual(t, pcs, want)
186         }()
187         var p *int
188         if *p == 3 {
189                 t.Fatal("did not see nil pointer panic")
190         }
191 }