1// Copyright 2014 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// Issue 7978.  Stack tracing didn't work during cgo code after calling a Go
6// callback.  Make sure GC works and the stack trace is correct.
7
8package cgotest
9
10/*
11#include <stdint.h>
12
13void issue7978cb(void);
14
15#if defined(__APPLE__) && defined(__arm__)
16// on Darwin/ARM, libSystem doesn't provide implementation of the __sync_fetch_and_add
17// primitive, and although gcc supports it, it doesn't inline its definition.
18// Clang could inline its definition, so we require clang on Darwin/ARM.
19#if defined(__clang__)
20#define HAS_SYNC_FETCH_AND_ADD 1
21#else
22#define HAS_SYNC_FETCH_AND_ADD 0
23#endif
24#else
25#define HAS_SYNC_FETCH_AND_ADD 1
26#endif
27
28// use ugly atomic variable sync since that doesn't require calling back into
29// Go code or OS dependencies
30static void issue7978c(uint32_t *sync) {
31#if HAS_SYNC_FETCH_AND_ADD
32	while(__sync_fetch_and_add(sync, 0) != 0)
33		;
34	__sync_fetch_and_add(sync, 1);
35	while(__sync_fetch_and_add(sync, 0) != 2)
36		;
37	issue7978cb();
38	__sync_fetch_and_add(sync, 1);
39	while(__sync_fetch_and_add(sync, 0) != 6)
40		;
41#endif
42}
43*/
44import "C"
45
46import (
47	"runtime"
48	"runtime/debug"
49	"strings"
50	"sync/atomic"
51	"testing"
52)
53
54var issue7978sync uint32
55
56func issue7978check(t *testing.T, wantFunc string, badFunc string, depth int) {
57	runtime.GC()
58	buf := make([]byte, 65536)
59	trace := string(buf[:runtime.Stack(buf, true)])
60	for _, goroutine := range strings.Split(trace, "\n\n") {
61		if strings.Contains(goroutine, "test.issue7978go") {
62			trace := strings.Split(goroutine, "\n")
63			// look for the expected function in the stack
64			for i := 0; i < depth; i++ {
65				if badFunc != "" && strings.Contains(trace[1+2*i], badFunc) {
66					t.Errorf("bad stack: found %s in the stack:\n%s", badFunc, goroutine)
67					return
68				}
69				if strings.Contains(trace[1+2*i], wantFunc) {
70					return
71				}
72			}
73			t.Errorf("bad stack: didn't find %s in the stack:\n%s", wantFunc, goroutine)
74			return
75		}
76	}
77	t.Errorf("bad stack: goroutine not found. Full stack dump:\n%s", trace)
78}
79
80func issue7978wait(store uint32, wait uint32) {
81	if store != 0 {
82		atomic.StoreUint32(&issue7978sync, store)
83	}
84	for atomic.LoadUint32(&issue7978sync) != wait {
85		runtime.Gosched()
86	}
87}
88
89//export issue7978cb
90func issue7978cb() {
91	// Force a stack growth from the callback to put extra
92	// pressure on the runtime. See issue #17785.
93	growStack(64)
94	issue7978wait(3, 4)
95}
96
97func growStack(n int) int {
98	var buf [128]int
99	if n == 0 {
100		return 0
101	}
102	return buf[growStack(n-1)]
103}
104
105func issue7978go() {
106	C.issue7978c((*C.uint32_t)(&issue7978sync))
107	issue7978wait(7, 8)
108}
109
110func test7978(t *testing.T) {
111	if runtime.Compiler == "gccgo" {
112		t.Skip("gccgo can not do stack traces of C code")
113	}
114	if C.HAS_SYNC_FETCH_AND_ADD == 0 {
115		t.Skip("clang required for __sync_fetch_and_add support on darwin/arm")
116	}
117	debug.SetTraceback("2")
118	issue7978sync = 0
119	go issue7978go()
120	// test in c code, before callback
121	issue7978wait(0, 1)
122	issue7978check(t, "_Cfunc_issue7978c(", "", 1)
123	// test in go code, during callback
124	issue7978wait(2, 3)
125	issue7978check(t, "test.issue7978cb(", "test.issue7978go", 3)
126	// test in c code, after callback
127	issue7978wait(4, 5)
128	issue7978check(t, "_Cfunc_issue7978c(", "_cgoexpwrap", 1)
129	// test in go code, after return from cgo
130	issue7978wait(6, 7)
131	issue7978check(t, "test.issue7978go(", "", 3)
132	atomic.StoreUint32(&issue7978sync, 8)
133}
134