1// Copyright 2012 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//go:build cgo
6
7package runtime_test
8
9import (
10	"bytes"
11	"fmt"
12	"internal/testenv"
13	"os"
14	"os/exec"
15	"runtime"
16	"strconv"
17	"strings"
18	"testing"
19	"time"
20)
21
22func TestCgoCrashHandler(t *testing.T) {
23	t.Parallel()
24	testCrashHandler(t, true)
25}
26
27func TestCgoSignalDeadlock(t *testing.T) {
28	// Don't call t.Parallel, since too much work going on at the
29	// same time can cause the testprogcgo code to overrun its
30	// timeouts (issue #18598).
31
32	if testing.Short() && runtime.GOOS == "windows" {
33		t.Skip("Skipping in short mode") // takes up to 64 seconds
34	}
35	got := runTestProg(t, "testprogcgo", "CgoSignalDeadlock")
36	want := "OK\n"
37	if got != want {
38		t.Fatalf("expected %q, but got:\n%s", want, got)
39	}
40}
41
42func TestCgoTraceback(t *testing.T) {
43	t.Parallel()
44	got := runTestProg(t, "testprogcgo", "CgoTraceback")
45	want := "OK\n"
46	if got != want {
47		t.Fatalf("expected %q, but got:\n%s", want, got)
48	}
49}
50
51func TestCgoCallbackGC(t *testing.T) {
52	t.Parallel()
53	switch runtime.GOOS {
54	case "plan9", "windows":
55		t.Skipf("no pthreads on %s", runtime.GOOS)
56	}
57	if testing.Short() {
58		switch {
59		case runtime.GOOS == "dragonfly":
60			t.Skip("see golang.org/issue/11990")
61		case runtime.GOOS == "linux" && runtime.GOARCH == "arm":
62			t.Skip("too slow for arm builders")
63		case runtime.GOOS == "linux" && (runtime.GOARCH == "mips64" || runtime.GOARCH == "mips64le"):
64			t.Skip("too slow for mips64x builders")
65		}
66	}
67	if testenv.Builder() == "darwin-amd64-10_14" {
68		// TODO(#23011): When the 10.14 builders are gone, remove this skip.
69		t.Skip("skipping due to platform bug on macOS 10.14; see https://golang.org/issue/43926")
70	}
71	got := runTestProg(t, "testprogcgo", "CgoCallbackGC")
72	want := "OK\n"
73	if got != want {
74		t.Fatalf("expected %q, but got:\n%s", want, got)
75	}
76}
77
78func TestCgoExternalThreadPanic(t *testing.T) {
79	t.Parallel()
80	if runtime.GOOS == "plan9" {
81		t.Skipf("no pthreads on %s", runtime.GOOS)
82	}
83	got := runTestProg(t, "testprogcgo", "CgoExternalThreadPanic")
84	want := "panic: BOOM"
85	if !strings.Contains(got, want) {
86		t.Fatalf("want failure containing %q. output:\n%s\n", want, got)
87	}
88}
89
90func TestCgoExternalThreadSIGPROF(t *testing.T) {
91	t.Parallel()
92	// issue 9456.
93	switch runtime.GOOS {
94	case "plan9", "windows":
95		t.Skipf("no pthreads on %s", runtime.GOOS)
96	}
97
98	exe, err := buildTestProg(t, "testprogcgo", "-tags=threadprof")
99	if err != nil {
100		t.Fatal(err)
101	}
102
103	got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoExternalThreadSIGPROF")).CombinedOutput()
104	if err != nil {
105		t.Fatalf("exit status: %v\n%s", err, got)
106	}
107
108	if want := "OK\n"; string(got) != want {
109		t.Fatalf("expected %q, but got:\n%s", want, got)
110	}
111}
112
113func TestCgoExternalThreadSignal(t *testing.T) {
114	t.Parallel()
115	// issue 10139
116	switch runtime.GOOS {
117	case "plan9", "windows":
118		t.Skipf("no pthreads on %s", runtime.GOOS)
119	}
120
121	exe, err := buildTestProg(t, "testprogcgo", "-tags=threadprof")
122	if err != nil {
123		t.Fatal(err)
124	}
125
126	got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoExternalThreadSignal")).CombinedOutput()
127	if err != nil {
128		t.Fatalf("exit status: %v\n%s", err, got)
129	}
130
131	want := []byte("OK\n")
132	if !bytes.Equal(got, want) {
133		t.Fatalf("expected %q, but got:\n%s", want, got)
134	}
135}
136
137func TestCgoDLLImports(t *testing.T) {
138	// test issue 9356
139	if runtime.GOOS != "windows" {
140		t.Skip("skipping windows specific test")
141	}
142	got := runTestProg(t, "testprogcgo", "CgoDLLImportsMain")
143	want := "OK\n"
144	if got != want {
145		t.Fatalf("expected %q, but got %v", want, got)
146	}
147}
148
149func TestCgoExecSignalMask(t *testing.T) {
150	t.Parallel()
151	// Test issue 13164.
152	switch runtime.GOOS {
153	case "windows", "plan9":
154		t.Skipf("skipping signal mask test on %s", runtime.GOOS)
155	}
156	got := runTestProg(t, "testprogcgo", "CgoExecSignalMask", "GOTRACEBACK=system")
157	want := "OK\n"
158	if got != want {
159		t.Errorf("expected %q, got %v", want, got)
160	}
161}
162
163func TestEnsureDropM(t *testing.T) {
164	t.Parallel()
165	// Test for issue 13881.
166	switch runtime.GOOS {
167	case "windows", "plan9":
168		t.Skipf("skipping dropm test on %s", runtime.GOOS)
169	}
170	got := runTestProg(t, "testprogcgo", "EnsureDropM")
171	want := "OK\n"
172	if got != want {
173		t.Errorf("expected %q, got %v", want, got)
174	}
175}
176
177// Test for issue 14387.
178// Test that the program that doesn't need any cgo pointer checking
179// takes about the same amount of time with it as without it.
180func TestCgoCheckBytes(t *testing.T) {
181	t.Parallel()
182	// Make sure we don't count the build time as part of the run time.
183	testenv.MustHaveGoBuild(t)
184	exe, err := buildTestProg(t, "testprogcgo")
185	if err != nil {
186		t.Fatal(err)
187	}
188
189	// Try it 10 times to avoid flakiness.
190	const tries = 10
191	var tot1, tot2 time.Duration
192	for i := 0; i < tries; i++ {
193		cmd := testenv.CleanCmdEnv(exec.Command(exe, "CgoCheckBytes"))
194		cmd.Env = append(cmd.Env, "GODEBUG=cgocheck=0", fmt.Sprintf("GO_CGOCHECKBYTES_TRY=%d", i))
195
196		start := time.Now()
197		cmd.Run()
198		d1 := time.Since(start)
199
200		cmd = testenv.CleanCmdEnv(exec.Command(exe, "CgoCheckBytes"))
201		cmd.Env = append(cmd.Env, fmt.Sprintf("GO_CGOCHECKBYTES_TRY=%d", i))
202
203		start = time.Now()
204		cmd.Run()
205		d2 := time.Since(start)
206
207		if d1*20 > d2 {
208			// The slow version (d2) was less than 20 times
209			// slower than the fast version (d1), so OK.
210			return
211		}
212
213		tot1 += d1
214		tot2 += d2
215	}
216
217	t.Errorf("cgo check too slow: got %v, expected at most %v", tot2/tries, (tot1/tries)*20)
218}
219
220func TestCgoPanicDeadlock(t *testing.T) {
221	t.Parallel()
222	// test issue 14432
223	got := runTestProg(t, "testprogcgo", "CgoPanicDeadlock")
224	want := "panic: cgo error\n\n"
225	if !strings.HasPrefix(got, want) {
226		t.Fatalf("output does not start with %q:\n%s", want, got)
227	}
228}
229
230func TestCgoCCodeSIGPROF(t *testing.T) {
231	t.Parallel()
232	got := runTestProg(t, "testprogcgo", "CgoCCodeSIGPROF")
233	want := "OK\n"
234	if got != want {
235		t.Errorf("expected %q got %v", want, got)
236	}
237}
238
239func TestCgoCrashTraceback(t *testing.T) {
240	t.Parallel()
241	switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform {
242	case "darwin/amd64":
243	case "linux/amd64":
244	case "linux/ppc64le":
245	default:
246		t.Skipf("not yet supported on %s", platform)
247	}
248	got := runTestProg(t, "testprogcgo", "CrashTraceback")
249	for i := 1; i <= 3; i++ {
250		if !strings.Contains(got, fmt.Sprintf("cgo symbolizer:%d", i)) {
251			t.Errorf("missing cgo symbolizer:%d", i)
252		}
253	}
254}
255
256func TestCgoCrashTracebackGo(t *testing.T) {
257	t.Parallel()
258	switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform {
259	case "darwin/amd64":
260	case "linux/amd64":
261	case "linux/ppc64le":
262	default:
263		t.Skipf("not yet supported on %s", platform)
264	}
265	got := runTestProg(t, "testprogcgo", "CrashTracebackGo")
266	for i := 1; i <= 3; i++ {
267		want := fmt.Sprintf("main.h%d", i)
268		if !strings.Contains(got, want) {
269			t.Errorf("missing %s", want)
270		}
271	}
272}
273
274func TestCgoTracebackContext(t *testing.T) {
275	t.Parallel()
276	got := runTestProg(t, "testprogcgo", "TracebackContext")
277	want := "OK\n"
278	if got != want {
279		t.Errorf("expected %q got %v", want, got)
280	}
281}
282
283func TestCgoTracebackContextPreemption(t *testing.T) {
284	t.Parallel()
285	got := runTestProg(t, "testprogcgo", "TracebackContextPreemption")
286	want := "OK\n"
287	if got != want {
288		t.Errorf("expected %q got %v", want, got)
289	}
290}
291
292func testCgoPprof(t *testing.T, buildArg, runArg, top, bottom string) {
293	t.Parallel()
294	if runtime.GOOS != "linux" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "ppc64le") {
295		t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
296	}
297	testenv.MustHaveGoRun(t)
298
299	exe, err := buildTestProg(t, "testprogcgo", buildArg)
300	if err != nil {
301		t.Fatal(err)
302	}
303
304	cmd := testenv.CleanCmdEnv(exec.Command(exe, runArg))
305	got, err := cmd.CombinedOutput()
306	if err != nil {
307		if testenv.Builder() == "linux-amd64-alpine" {
308			// See Issue 18243 and Issue 19938.
309			t.Skipf("Skipping failing test on Alpine (golang.org/issue/18243). Ignoring error: %v", err)
310		}
311		t.Fatalf("%s\n\n%v", got, err)
312	}
313	fn := strings.TrimSpace(string(got))
314	defer os.Remove(fn)
315
316	for try := 0; try < 2; try++ {
317		cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-tagignore=ignore", "-traces"))
318		// Check that pprof works both with and without explicit executable on command line.
319		if try == 0 {
320			cmd.Args = append(cmd.Args, exe, fn)
321		} else {
322			cmd.Args = append(cmd.Args, fn)
323		}
324
325		found := false
326		for i, e := range cmd.Env {
327			if strings.HasPrefix(e, "PPROF_TMPDIR=") {
328				cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir()
329				found = true
330				break
331			}
332		}
333		if !found {
334			cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir())
335		}
336
337		out, err := cmd.CombinedOutput()
338		t.Logf("%s:\n%s", cmd.Args, out)
339		if err != nil {
340			t.Error(err)
341			continue
342		}
343
344		trace := findTrace(string(out), top)
345		if len(trace) == 0 {
346			t.Errorf("%s traceback missing.", top)
347			continue
348		}
349		if trace[len(trace)-1] != bottom {
350			t.Errorf("invalid traceback origin: got=%v; want=[%s ... %s]", trace, top, bottom)
351		}
352	}
353}
354
355func TestCgoPprof(t *testing.T) {
356	testCgoPprof(t, "", "CgoPprof", "cpuHog", "runtime.main")
357}
358
359func TestCgoPprofPIE(t *testing.T) {
360	testCgoPprof(t, "-buildmode=pie", "CgoPprof", "cpuHog", "runtime.main")
361}
362
363func TestCgoPprofThread(t *testing.T) {
364	testCgoPprof(t, "", "CgoPprofThread", "cpuHogThread", "cpuHogThread2")
365}
366
367func TestCgoPprofThreadNoTraceback(t *testing.T) {
368	testCgoPprof(t, "", "CgoPprofThreadNoTraceback", "cpuHogThread", "runtime._ExternalCode")
369}
370
371func TestRaceProf(t *testing.T) {
372	if (runtime.GOOS != "linux" && runtime.GOOS != "freebsd") || runtime.GOARCH != "amd64" {
373		t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
374	}
375
376	testenv.MustHaveGoRun(t)
377
378	// This test requires building various packages with -race, so
379	// it's somewhat slow.
380	if testing.Short() {
381		t.Skip("skipping test in -short mode")
382	}
383
384	exe, err := buildTestProg(t, "testprogcgo", "-race")
385	if err != nil {
386		t.Fatal(err)
387	}
388
389	got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoRaceprof")).CombinedOutput()
390	if err != nil {
391		t.Fatal(err)
392	}
393	want := "OK\n"
394	if string(got) != want {
395		t.Errorf("expected %q got %s", want, got)
396	}
397}
398
399func TestRaceSignal(t *testing.T) {
400	t.Parallel()
401	if (runtime.GOOS != "linux" && runtime.GOOS != "freebsd") || runtime.GOARCH != "amd64" {
402		t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
403	}
404
405	testenv.MustHaveGoRun(t)
406
407	// This test requires building various packages with -race, so
408	// it's somewhat slow.
409	if testing.Short() {
410		t.Skip("skipping test in -short mode")
411	}
412
413	exe, err := buildTestProg(t, "testprogcgo", "-race")
414	if err != nil {
415		t.Fatal(err)
416	}
417
418	got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoRaceSignal")).CombinedOutput()
419	if err != nil {
420		t.Logf("%s\n", got)
421		t.Fatal(err)
422	}
423	want := "OK\n"
424	if string(got) != want {
425		t.Errorf("expected %q got %s", want, got)
426	}
427}
428
429func TestCgoNumGoroutine(t *testing.T) {
430	switch runtime.GOOS {
431	case "windows", "plan9":
432		t.Skipf("skipping numgoroutine test on %s", runtime.GOOS)
433	}
434	t.Parallel()
435	got := runTestProg(t, "testprogcgo", "NumGoroutine")
436	want := "OK\n"
437	if got != want {
438		t.Errorf("expected %q got %v", want, got)
439	}
440}
441
442func TestCatchPanic(t *testing.T) {
443	t.Parallel()
444	switch runtime.GOOS {
445	case "plan9", "windows":
446		t.Skipf("no signals on %s", runtime.GOOS)
447	case "darwin":
448		if runtime.GOARCH == "amd64" {
449			t.Skipf("crash() on darwin/amd64 doesn't raise SIGABRT")
450		}
451	}
452
453	testenv.MustHaveGoRun(t)
454
455	exe, err := buildTestProg(t, "testprogcgo")
456	if err != nil {
457		t.Fatal(err)
458	}
459
460	for _, early := range []bool{true, false} {
461		cmd := testenv.CleanCmdEnv(exec.Command(exe, "CgoCatchPanic"))
462		// Make sure a panic results in a crash.
463		cmd.Env = append(cmd.Env, "GOTRACEBACK=crash")
464		if early {
465			// Tell testprogcgo to install an early signal handler for SIGABRT
466			cmd.Env = append(cmd.Env, "CGOCATCHPANIC_EARLY_HANDLER=1")
467		}
468		if out, err := cmd.CombinedOutput(); err != nil {
469			t.Errorf("testprogcgo CgoCatchPanic failed: %v\n%s", err, out)
470		}
471	}
472}
473
474func TestCgoLockOSThreadExit(t *testing.T) {
475	switch runtime.GOOS {
476	case "plan9", "windows":
477		t.Skipf("no pthreads on %s", runtime.GOOS)
478	}
479	t.Parallel()
480	testLockOSThreadExit(t, "testprogcgo")
481}
482
483func TestWindowsStackMemoryCgo(t *testing.T) {
484	if runtime.GOOS != "windows" {
485		t.Skip("skipping windows specific test")
486	}
487	testenv.SkipFlaky(t, 22575)
488	o := runTestProg(t, "testprogcgo", "StackMemory")
489	stackUsage, err := strconv.Atoi(o)
490	if err != nil {
491		t.Fatalf("Failed to read stack usage: %v", err)
492	}
493	if expected, got := 100<<10, stackUsage; got > expected {
494		t.Fatalf("expected < %d bytes of memory per thread, got %d", expected, got)
495	}
496}
497
498func TestSigStackSwapping(t *testing.T) {
499	switch runtime.GOOS {
500	case "plan9", "windows":
501		t.Skipf("no sigaltstack on %s", runtime.GOOS)
502	}
503	t.Parallel()
504	got := runTestProg(t, "testprogcgo", "SigStack")
505	want := "OK\n"
506	if got != want {
507		t.Errorf("expected %q got %v", want, got)
508	}
509}
510
511func TestCgoTracebackSigpanic(t *testing.T) {
512	// Test unwinding over a sigpanic in C code without a C
513	// symbolizer. See issue #23576.
514	if runtime.GOOS == "windows" {
515		// On Windows if we get an exception in C code, we let
516		// the Windows exception handler unwind it, rather
517		// than injecting a sigpanic.
518		t.Skip("no sigpanic in C on windows")
519	}
520	t.Parallel()
521	got := runTestProg(t, "testprogcgo", "TracebackSigpanic")
522	t.Log(got)
523	want := "runtime.sigpanic"
524	if !strings.Contains(got, want) {
525		t.Errorf("did not see %q in output", want)
526	}
527	// No runtime errors like "runtime: unexpected return pc".
528	nowant := "runtime: "
529	if strings.Contains(got, nowant) {
530		t.Errorf("unexpectedly saw %q in output", nowant)
531	}
532}
533
534func TestCgoPanicCallback(t *testing.T) {
535	t.Parallel()
536	got := runTestProg(t, "testprogcgo", "PanicCallback")
537	t.Log(got)
538	want := "panic: runtime error: invalid memory address or nil pointer dereference"
539	if !strings.Contains(got, want) {
540		t.Errorf("did not see %q in output", want)
541	}
542	want = "panic_callback"
543	if !strings.Contains(got, want) {
544		t.Errorf("did not see %q in output", want)
545	}
546	want = "PanicCallback"
547	if !strings.Contains(got, want) {
548		t.Errorf("did not see %q in output", want)
549	}
550	// No runtime errors like "runtime: unexpected return pc".
551	nowant := "runtime: "
552	if strings.Contains(got, nowant) {
553		t.Errorf("did not see %q in output", want)
554	}
555}
556
557// Test that C code called via cgo can use large Windows thread stacks
558// and call back in to Go without crashing. See issue #20975.
559//
560// See also TestBigStackCallbackSyscall.
561func TestBigStackCallbackCgo(t *testing.T) {
562	if runtime.GOOS != "windows" {
563		t.Skip("skipping windows specific test")
564	}
565	t.Parallel()
566	got := runTestProg(t, "testprogcgo", "BigStack")
567	want := "OK\n"
568	if got != want {
569		t.Errorf("expected %q got %v", want, got)
570	}
571}
572
573func nextTrace(lines []string) ([]string, []string) {
574	var trace []string
575	for n, line := range lines {
576		if strings.HasPrefix(line, "---") {
577			return trace, lines[n+1:]
578		}
579		fields := strings.Fields(strings.TrimSpace(line))
580		if len(fields) == 0 {
581			continue
582		}
583		// Last field contains the function name.
584		trace = append(trace, fields[len(fields)-1])
585	}
586	return nil, nil
587}
588
589func findTrace(text, top string) []string {
590	lines := strings.Split(text, "\n")
591	_, lines = nextTrace(lines) // Skip the header.
592	for len(lines) > 0 {
593		var t []string
594		t, lines = nextTrace(lines)
595		if len(t) == 0 {
596			continue
597		}
598		if t[0] == top {
599			return t
600		}
601	}
602	return nil
603}
604
605func TestSegv(t *testing.T) {
606	switch runtime.GOOS {
607	case "plan9", "windows":
608		t.Skipf("no signals on %s", runtime.GOOS)
609	}
610
611	for _, test := range []string{"Segv", "SegvInCgo"} {
612		test := test
613		t.Run(test, func(t *testing.T) {
614			t.Parallel()
615			got := runTestProg(t, "testprogcgo", test)
616			t.Log(got)
617			want := "SIGSEGV"
618			if !strings.Contains(got, want) {
619				t.Errorf("did not see %q in output", want)
620			}
621
622			// No runtime errors like "runtime: unknown pc".
623			switch runtime.GOOS {
624			case "darwin", "illumos", "solaris":
625				// TODO(golang.org/issue/49182): Skip, runtime
626				// throws while attempting to generate
627				// traceback.
628			default:
629				nowant := "runtime: "
630				if strings.Contains(got, nowant) {
631					t.Errorf("unexpectedly saw %q in output", nowant)
632				}
633			}
634		})
635	}
636}
637
638func TestAbortInCgo(t *testing.T) {
639	switch runtime.GOOS {
640	case "plan9", "windows":
641		// N.B. On Windows, C abort() causes the program to exit
642		// without going through the runtime at all.
643		t.Skipf("no signals on %s", runtime.GOOS)
644	}
645
646	t.Parallel()
647	got := runTestProg(t, "testprogcgo", "Abort")
648	t.Log(got)
649	want := "SIGABRT"
650	if !strings.Contains(got, want) {
651		t.Errorf("did not see %q in output", want)
652	}
653	// No runtime errors like "runtime: unknown pc".
654	nowant := "runtime: "
655	if strings.Contains(got, nowant) {
656		t.Errorf("did not see %q in output", want)
657	}
658}
659
660// TestEINTR tests that we handle EINTR correctly.
661// See issue #20400 and friends.
662func TestEINTR(t *testing.T) {
663	switch runtime.GOOS {
664	case "plan9", "windows":
665		t.Skipf("no EINTR on %s", runtime.GOOS)
666	case "linux":
667		if runtime.GOARCH == "386" {
668			// On linux-386 the Go signal handler sets
669			// a restorer function that is not preserved
670			// by the C sigaction call in the test,
671			// causing the signal handler to crash when
672			// returning the normal code. The test is not
673			// architecture-specific, so just skip on 386
674			// rather than doing a complicated workaround.
675			t.Skip("skipping on linux-386; C sigaction does not preserve Go restorer")
676		}
677	}
678
679	t.Parallel()
680	output := runTestProg(t, "testprogcgo", "EINTR")
681	want := "OK\n"
682	if output != want {
683		t.Fatalf("want %s, got %s\n", want, output)
684	}
685}
686
687// Issue #42207.
688func TestNeedmDeadlock(t *testing.T) {
689	switch runtime.GOOS {
690	case "plan9", "windows":
691		t.Skipf("no signals on %s", runtime.GOOS)
692	}
693	output := runTestProg(t, "testprogcgo", "NeedmDeadlock")
694	want := "OK\n"
695	if output != want {
696		t.Fatalf("want %s, got %s\n", want, output)
697	}
698}
699
700func TestCgoTracebackGoroutineProfile(t *testing.T) {
701	output := runTestProg(t, "testprogcgo", "GoroutineProfile")
702	want := "OK\n"
703	if output != want {
704		t.Fatalf("want %s, got %s\n", want, output)
705	}
706}
707