1// Copyright 2019 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	"bufio"
9	"bytes"
10	"fmt"
11	"internal/testenv"
12	"os/exec"
13	"path/filepath"
14	"runtime"
15	"strings"
16	"syscall"
17	"testing"
18)
19
20func TestVectoredHandlerExceptionInNonGoThread(t *testing.T) {
21	if *flagQuick {
22		t.Skip("-quick")
23	}
24	if strings.HasPrefix(testenv.Builder(), "windows-amd64-2012") {
25		testenv.SkipFlaky(t, 49681)
26	}
27	testenv.MustHaveGoBuild(t)
28	testenv.MustHaveCGO(t)
29	testenv.MustHaveExecPath(t, "gcc")
30	testprog.Lock()
31	defer testprog.Unlock()
32	dir := t.TempDir()
33
34	// build c program
35	dll := filepath.Join(dir, "veh.dll")
36	cmd := exec.Command("gcc", "-shared", "-o", dll, "testdata/testwinlibthrow/veh.c")
37	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
38	if err != nil {
39		t.Fatalf("failed to build c exe: %s\n%s", err, out)
40	}
41
42	// build go exe
43	exe := filepath.Join(dir, "test.exe")
44	cmd = exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "testdata/testwinlibthrow/main.go")
45	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
46	if err != nil {
47		t.Fatalf("failed to build go library: %s\n%s", err, out)
48	}
49
50	// run test program in same thread
51	cmd = exec.Command(exe)
52	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
53	if err == nil {
54		t.Fatal("error expected")
55	}
56	if _, ok := err.(*exec.ExitError); ok && len(out) > 0 {
57		if !bytes.Contains(out, []byte("Exception 0x2a")) {
58			t.Fatalf("unexpected failure while running executable: %s\n%s", err, out)
59		}
60	} else {
61		t.Fatalf("unexpected error while running executable: %s\n%s", err, out)
62	}
63	// run test program in a new thread
64	cmd = exec.Command(exe, "thread")
65	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
66	if err == nil {
67		t.Fatal("error expected")
68	}
69	if err, ok := err.(*exec.ExitError); ok {
70		if err.ExitCode() != 42 {
71			t.Fatalf("unexpected failure while running executable: %s\n%s", err, out)
72		}
73	} else {
74		t.Fatalf("unexpected error while running executable: %s\n%s", err, out)
75	}
76}
77
78func TestVectoredHandlerDontCrashOnLibrary(t *testing.T) {
79	if *flagQuick {
80		t.Skip("-quick")
81	}
82	if runtime.GOARCH == "arm" {
83		//TODO: remove this skip and update testwinlib/main.c
84		// once windows/arm supports c-shared buildmode.
85		// See go.dev/issues/43800.
86		t.Skip("this test can't run on windows/arm")
87	}
88	testenv.MustHaveGoBuild(t)
89	testenv.MustHaveCGO(t)
90	testenv.MustHaveExecPath(t, "gcc")
91	testprog.Lock()
92	defer testprog.Unlock()
93	dir := t.TempDir()
94
95	// build go dll
96	dll := filepath.Join(dir, "testwinlib.dll")
97	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", dll, "-buildmode", "c-shared", "testdata/testwinlib/main.go")
98	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
99	if err != nil {
100		t.Fatalf("failed to build go library: %s\n%s", err, out)
101	}
102
103	// build c program
104	exe := filepath.Join(dir, "test.exe")
105	cmd = exec.Command("gcc", "-L"+dir, "-I"+dir, "-ltestwinlib", "-o", exe, "testdata/testwinlib/main.c")
106	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
107	if err != nil {
108		t.Fatalf("failed to build c exe: %s\n%s", err, out)
109	}
110
111	// run test program
112	cmd = exec.Command(exe)
113	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
114	if err != nil {
115		t.Fatalf("failure while running executable: %s\n%s", err, out)
116	}
117	var expectedOutput string
118	if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
119		// TODO: remove when windows/arm64 and windows/arm support SEH stack unwinding.
120		expectedOutput = "exceptionCount: 1\ncontinueCount: 1\nunhandledCount: 0\n"
121	} else {
122		expectedOutput = "exceptionCount: 1\ncontinueCount: 1\nunhandledCount: 1\n"
123	}
124	// cleaning output
125	cleanedOut := strings.ReplaceAll(string(out), "\r\n", "\n")
126	if cleanedOut != expectedOutput {
127		t.Errorf("expected output %q, got %q", expectedOutput, cleanedOut)
128	}
129}
130
131func sendCtrlBreak(pid int) error {
132	kernel32, err := syscall.LoadDLL("kernel32.dll")
133	if err != nil {
134		return fmt.Errorf("LoadDLL: %v\n", err)
135	}
136	generateEvent, err := kernel32.FindProc("GenerateConsoleCtrlEvent")
137	if err != nil {
138		return fmt.Errorf("FindProc: %v\n", err)
139	}
140	result, _, err := generateEvent.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
141	if result == 0 {
142		return fmt.Errorf("GenerateConsoleCtrlEvent: %v\n", err)
143	}
144	return nil
145}
146
147// TestCtrlHandler tests that Go can gracefully handle closing the console window.
148// See https://golang.org/issues/41884.
149func TestCtrlHandler(t *testing.T) {
150	testenv.MustHaveGoBuild(t)
151	t.Parallel()
152
153	// build go program
154	exe := filepath.Join(t.TempDir(), "test.exe")
155	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "testdata/testwinsignal/main.go")
156	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
157	if err != nil {
158		t.Fatalf("failed to build go exe: %v\n%s", err, out)
159	}
160
161	// run test program
162	cmd = exec.Command(exe)
163	var stdout strings.Builder
164	var stderr strings.Builder
165	cmd.Stdout = &stdout
166	cmd.Stderr = &stderr
167	inPipe, err := cmd.StdinPipe()
168	if err != nil {
169		t.Fatalf("Failed to create stdin pipe: %v", err)
170	}
171	// keep inPipe alive until the end of the test
172	defer inPipe.Close()
173
174	// in a new command window
175	const _CREATE_NEW_CONSOLE = 0x00000010
176	cmd.SysProcAttr = &syscall.SysProcAttr{
177		CreationFlags: _CREATE_NEW_CONSOLE,
178		HideWindow:    true,
179	}
180	if err := cmd.Start(); err != nil {
181		t.Fatalf("Start failed: %v", err)
182	}
183	defer func() {
184		cmd.Process.Kill()
185		cmd.Wait()
186	}()
187
188	// check child exited gracefully, did not timeout
189	if err := cmd.Wait(); err != nil {
190		t.Fatalf("Program exited with error: %v\n%s", err, &stderr)
191	}
192
193	// check child received, handled SIGTERM
194	if expected, got := syscall.SIGTERM.String(), strings.TrimSpace(stdout.String()); expected != got {
195		t.Fatalf("Expected '%s' got: %s", expected, got)
196	}
197}
198
199// TestLibraryCtrlHandler tests that Go DLL allows calling program to handle console control events.
200// See https://golang.org/issues/35965.
201func TestLibraryCtrlHandler(t *testing.T) {
202	if *flagQuick {
203		t.Skip("-quick")
204	}
205	if runtime.GOARCH != "amd64" {
206		t.Skip("this test can only run on windows/amd64")
207	}
208	testenv.MustHaveGoBuild(t)
209	testenv.MustHaveCGO(t)
210	testenv.MustHaveExecPath(t, "gcc")
211	testprog.Lock()
212	defer testprog.Unlock()
213	dir := t.TempDir()
214
215	// build go dll
216	dll := filepath.Join(dir, "dummy.dll")
217	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", dll, "-buildmode", "c-shared", "testdata/testwinlibsignal/dummy.go")
218	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
219	if err != nil {
220		t.Fatalf("failed to build go library: %s\n%s", err, out)
221	}
222
223	// build c program
224	exe := filepath.Join(dir, "test.exe")
225	cmd = exec.Command("gcc", "-o", exe, "testdata/testwinlibsignal/main.c")
226	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
227	if err != nil {
228		t.Fatalf("failed to build c exe: %s\n%s", err, out)
229	}
230
231	// run test program
232	cmd = exec.Command(exe)
233	var stderr bytes.Buffer
234	cmd.Stderr = &stderr
235	outPipe, err := cmd.StdoutPipe()
236	if err != nil {
237		t.Fatalf("Failed to create stdout pipe: %v", err)
238	}
239	outReader := bufio.NewReader(outPipe)
240
241	cmd.SysProcAttr = &syscall.SysProcAttr{
242		CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
243	}
244	if err := cmd.Start(); err != nil {
245		t.Fatalf("Start failed: %v", err)
246	}
247
248	errCh := make(chan error, 1)
249	go func() {
250		if line, err := outReader.ReadString('\n'); err != nil {
251			errCh <- fmt.Errorf("could not read stdout: %v", err)
252		} else if strings.TrimSpace(line) != "ready" {
253			errCh <- fmt.Errorf("unexpected message: %v", line)
254		} else {
255			errCh <- sendCtrlBreak(cmd.Process.Pid)
256		}
257	}()
258
259	if err := <-errCh; err != nil {
260		t.Fatal(err)
261	}
262	if err := cmd.Wait(); err != nil {
263		t.Fatalf("Program exited with error: %v\n%s", err, &stderr)
264	}
265}
266
267func TestIssue59213(t *testing.T) {
268	if runtime.GOOS != "windows" {
269		t.Skip("skipping windows only test")
270	}
271	if *flagQuick {
272		t.Skip("-quick")
273	}
274	testenv.MustHaveGoBuild(t)
275	testenv.MustHaveCGO(t)
276
277	goEnv := func(arg string) string {
278		cmd := testenv.Command(t, testenv.GoToolPath(t), "env", arg)
279		cmd.Stderr = new(bytes.Buffer)
280
281		line, err := cmd.Output()
282		if err != nil {
283			t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
284		}
285		out := string(bytes.TrimSpace(line))
286		t.Logf("%v: %q", cmd, out)
287		return out
288	}
289
290	cc := goEnv("CC")
291	cgoCflags := goEnv("CGO_CFLAGS")
292
293	t.Parallel()
294
295	tmpdir := t.TempDir()
296	dllfile := filepath.Join(tmpdir, "test.dll")
297	exefile := filepath.Join(tmpdir, "gotest.exe")
298
299	// build go dll
300	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", dllfile, "-buildmode", "c-shared", "testdata/testwintls/main.go")
301	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
302	if err != nil {
303		t.Fatalf("failed to build go library: %s\n%s", err, out)
304	}
305
306	// build c program
307	cmd = testenv.Command(t, cc, "-o", exefile, "testdata/testwintls/main.c")
308	testenv.CleanCmdEnv(cmd)
309	cmd.Env = append(cmd.Env, "CGO_CFLAGS="+cgoCflags)
310	out, err = cmd.CombinedOutput()
311	if err != nil {
312		t.Fatalf("failed to build c exe: %s\n%s", err, out)
313	}
314
315	// run test program
316	cmd = testenv.Command(t, exefile, dllfile, "GoFunc")
317	out, err = testenv.CleanCmdEnv(cmd).CombinedOutput()
318	if err != nil {
319		t.Fatalf("failed: %s\n%s", err, out)
320	}
321}
322