mirror of
https://github.com/golang/go
synced 2024-11-27 01:51:23 -07:00
972e883925
A non-trivial Cgo program may need to use callbacks and interact with go objects per goroutine. Because of the rules for passing pointers between Go and C, such a program needs to store handles to associated Go values. This often causes much extra effort to figure out a way to correctly deal with: 1) map collision; 2) identifying leaks and 3) concurrency. This CL implements a Handle representation in runtime/cgo package, and related methods such as Value, Delete, etc. which allows Go users can use a standard way to handle the above difficulties. In addition, the CL allows a Go value to have multiple handles, and the NewHandle always returns a different handle compare to the previously returned handles. In comparison, CL 294670 implements a different behavior of NewHandle that returns a unique handle when the Go value is referring to the same object. Benchmark: name time/op Handle/non-concurrent-16 487ns ± 1% Handle/concurrent-16 674ns ± 1% Fixes #37033 Change-Id: I0eadb9d44332fffef8fb567c745246a49dd6d4c1 Reviewed-on: https://go-review.googlesource.com/c/go/+/295369 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Trust: Cherry Zhang <cherryyz@google.com>
577 lines
10 KiB
Go
577 lines
10 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Test cases for cgo.
|
|
// Both the import "C" prologue and the main file are sorted by issue number.
|
|
// This file contains //export directives on Go functions
|
|
// and so it must NOT contain C definitions (only declarations).
|
|
// See test.go for C definitions.
|
|
|
|
package cgotest
|
|
|
|
import (
|
|
"runtime"
|
|
"runtime/cgo"
|
|
"runtime/debug"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
/*
|
|
// threads
|
|
extern void doAdd(int, int);
|
|
|
|
// issue 1328
|
|
extern void BackIntoGo(void);
|
|
void IntoC(void);
|
|
|
|
// issue 1560
|
|
// mysleep returns the absolute start time in ms.
|
|
long long mysleep(int seconds);
|
|
|
|
// twoSleep returns the absolute start time of the first sleep
|
|
// in ms.
|
|
long long twoSleep(int);
|
|
|
|
// issue 3775
|
|
void lockOSThreadCallback(void);
|
|
inline static void lockOSThreadC(void)
|
|
{
|
|
lockOSThreadCallback();
|
|
}
|
|
int usleep(unsigned usec);
|
|
|
|
// issue 4054 part 2 - part 1 in test.go
|
|
typedef enum {
|
|
A = 0,
|
|
B,
|
|
C,
|
|
D,
|
|
E,
|
|
F,
|
|
G,
|
|
H,
|
|
II,
|
|
J,
|
|
} issue4054b;
|
|
|
|
// issue 5548
|
|
|
|
extern int issue5548_in_c(void);
|
|
|
|
// issue 6833
|
|
|
|
extern unsigned long long issue6833Func(unsigned int, unsigned long long);
|
|
|
|
// issue 6907
|
|
|
|
extern int CheckIssue6907C(_GoString_);
|
|
|
|
// issue 7665
|
|
|
|
extern void f7665(void);
|
|
|
|
// issue 7978
|
|
// Stack tracing didn't work during cgo code after calling a Go
|
|
// callback. Make sure GC works and the stack trace is correct.
|
|
|
|
#include <stdint.h>
|
|
|
|
void issue7978cb(void);
|
|
|
|
// use ugly atomic variable sync since that doesn't require calling back into
|
|
// Go code or OS dependencies
|
|
static void issue7978c(uint32_t *sync) {
|
|
while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 0)
|
|
;
|
|
__atomic_add_fetch(sync, 1, __ATOMIC_SEQ_CST);
|
|
while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 2)
|
|
;
|
|
issue7978cb();
|
|
__atomic_add_fetch(sync, 1, __ATOMIC_SEQ_CST);
|
|
while(__atomic_load_n(sync, __ATOMIC_SEQ_CST) != 6)
|
|
;
|
|
}
|
|
|
|
// issue 8331 part 2 - part 1 in test.go
|
|
// A typedef of an unnamed struct is the same struct when
|
|
// #include'd twice. No runtime test; just make sure it compiles.
|
|
#include "issue8331.h"
|
|
|
|
// issue 8945
|
|
|
|
typedef void (*PFunc8945)();
|
|
extern PFunc8945 func8945; // definition is in test.go
|
|
|
|
// issue 20910
|
|
void callMulti(void);
|
|
|
|
// issue 28772 part 2 - part 1 in issuex.go
|
|
#define issue28772Constant2 2
|
|
|
|
|
|
// issue 31891
|
|
typedef struct {
|
|
long obj;
|
|
} Issue31891A;
|
|
|
|
typedef struct {
|
|
long obj;
|
|
} Issue31891B;
|
|
|
|
void callIssue31891(void);
|
|
|
|
typedef struct {
|
|
int i;
|
|
} Issue38408, *PIssue38408;
|
|
|
|
*/
|
|
import "C"
|
|
|
|
// exports
|
|
|
|
//export ReturnIntLong
|
|
func ReturnIntLong() (int, C.long) {
|
|
return 1, 2
|
|
}
|
|
|
|
//export gc
|
|
func gc() {
|
|
runtime.GC()
|
|
}
|
|
|
|
// threads
|
|
|
|
var sum struct {
|
|
sync.Mutex
|
|
i int
|
|
}
|
|
|
|
//export Add
|
|
func Add(x int) {
|
|
defer func() {
|
|
recover()
|
|
}()
|
|
sum.Lock()
|
|
sum.i += x
|
|
sum.Unlock()
|
|
var p *int
|
|
*p = 2
|
|
}
|
|
|
|
func testCthread(t *testing.T) {
|
|
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" {
|
|
t.Skip("the iOS exec wrapper is unable to properly handle the panic from Add")
|
|
}
|
|
sum.i = 0
|
|
C.doAdd(10, 6)
|
|
|
|
want := 10 * (10 - 1) / 2 * 6
|
|
if sum.i != want {
|
|
t.Fatalf("sum=%d, want %d", sum.i, want)
|
|
}
|
|
}
|
|
|
|
// issue 1328
|
|
|
|
//export BackIntoGo
|
|
func BackIntoGo() {
|
|
x := 1
|
|
|
|
for i := 0; i < 10000; i++ {
|
|
xvariadic(x)
|
|
if x != 1 {
|
|
panic("x is not 1?")
|
|
}
|
|
}
|
|
}
|
|
|
|
func xvariadic(x ...interface{}) {
|
|
}
|
|
|
|
func test1328(t *testing.T) {
|
|
C.IntoC()
|
|
}
|
|
|
|
// issue 1560
|
|
|
|
var sleepDone = make(chan int64)
|
|
|
|
// parallelSleep returns the absolute difference between the start time
|
|
// of the two sleeps.
|
|
func parallelSleep(n int) int64 {
|
|
t := int64(C.twoSleep(C.int(n))) - <-sleepDone
|
|
if t < 0 {
|
|
return -t
|
|
}
|
|
return t
|
|
}
|
|
|
|
//export BackgroundSleep
|
|
func BackgroundSleep(n int32) {
|
|
go func() {
|
|
sleepDone <- int64(C.mysleep(C.int(n)))
|
|
}()
|
|
}
|
|
|
|
func testParallelSleep(t *testing.T) {
|
|
sleepSec := 1
|
|
dt := time.Duration(parallelSleep(sleepSec)) * time.Millisecond
|
|
t.Logf("difference in start time for two sleep(%d) is %v", sleepSec, dt)
|
|
// bug used to run sleeps in serial, producing a 2*sleepSec-second delay.
|
|
// we detect if the start times of those sleeps are > 0.5*sleepSec-second.
|
|
if dt >= time.Duration(sleepSec)*time.Second/2 {
|
|
t.Fatalf("parallel %d-second sleeps slept for %f seconds", sleepSec, dt.Seconds())
|
|
}
|
|
}
|
|
|
|
// issue 2462
|
|
|
|
//export exportbyte
|
|
func exportbyte() byte {
|
|
return 0
|
|
}
|
|
|
|
//export exportbool
|
|
func exportbool() bool {
|
|
return false
|
|
}
|
|
|
|
//export exportrune
|
|
func exportrune() rune {
|
|
return 0
|
|
}
|
|
|
|
//export exporterror
|
|
func exporterror() error {
|
|
return nil
|
|
}
|
|
|
|
//export exportint
|
|
func exportint() int {
|
|
return 0
|
|
}
|
|
|
|
//export exportuint
|
|
func exportuint() uint {
|
|
return 0
|
|
}
|
|
|
|
//export exportuintptr
|
|
func exportuintptr() uintptr {
|
|
return (uintptr)(0)
|
|
}
|
|
|
|
//export exportint8
|
|
func exportint8() int8 {
|
|
return 0
|
|
}
|
|
|
|
//export exportuint8
|
|
func exportuint8() uint8 {
|
|
return 0
|
|
}
|
|
|
|
//export exportint16
|
|
func exportint16() int16 {
|
|
return 0
|
|
}
|
|
|
|
//export exportuint16
|
|
func exportuint16() uint16 {
|
|
return 0
|
|
}
|
|
|
|
//export exportint32
|
|
func exportint32() int32 {
|
|
return 0
|
|
}
|
|
|
|
//export exportuint32
|
|
func exportuint32() uint32 {
|
|
return 0
|
|
}
|
|
|
|
//export exportint64
|
|
func exportint64() int64 {
|
|
return 0
|
|
}
|
|
|
|
//export exportuint64
|
|
func exportuint64() uint64 {
|
|
return 0
|
|
}
|
|
|
|
//export exportfloat32
|
|
func exportfloat32() float32 {
|
|
return 0
|
|
}
|
|
|
|
//export exportfloat64
|
|
func exportfloat64() float64 {
|
|
return 0
|
|
}
|
|
|
|
//export exportcomplex64
|
|
func exportcomplex64() complex64 {
|
|
return 0
|
|
}
|
|
|
|
//export exportcomplex128
|
|
func exportcomplex128() complex128 {
|
|
return 0
|
|
}
|
|
|
|
// issue 3741
|
|
|
|
//export exportSliceIn
|
|
func exportSliceIn(s []byte) bool {
|
|
return len(s) == cap(s)
|
|
}
|
|
|
|
//export exportSliceOut
|
|
func exportSliceOut() []byte {
|
|
return []byte{1}
|
|
}
|
|
|
|
//export exportSliceInOut
|
|
func exportSliceInOut(s []byte) []byte {
|
|
return s
|
|
}
|
|
|
|
// issue 3775
|
|
|
|
func init() {
|
|
if runtime.GOOS == "android" {
|
|
return
|
|
}
|
|
// Same as test3775 but run during init so that
|
|
// there are two levels of internal runtime lock
|
|
// (1 for init, 1 for cgo).
|
|
// This would have been broken by CL 11663043.
|
|
C.lockOSThreadC()
|
|
}
|
|
|
|
func test3775(t *testing.T) {
|
|
if runtime.GOOS == "android" {
|
|
return
|
|
}
|
|
// Used to panic because of the UnlockOSThread below.
|
|
C.lockOSThreadC()
|
|
}
|
|
|
|
//export lockOSThreadCallback
|
|
func lockOSThreadCallback() {
|
|
runtime.LockOSThread()
|
|
runtime.UnlockOSThread()
|
|
go C.usleep(10000)
|
|
runtime.Gosched()
|
|
}
|
|
|
|
// issue 4054 part 2 - part 1 in test.go
|
|
|
|
var issue4054b = []int{C.A, C.B, C.C, C.D, C.E, C.F, C.G, C.H, C.II, C.J}
|
|
|
|
//export issue5548FromC
|
|
func issue5548FromC(s string, i int) int {
|
|
if len(s) == 4 && s == "test" && i == 42 {
|
|
return 12345
|
|
}
|
|
println("got", len(s), i)
|
|
return 9876
|
|
}
|
|
|
|
func test5548(t *testing.T) {
|
|
if x := C.issue5548_in_c(); x != 12345 {
|
|
t.Errorf("issue5548_in_c = %d, want %d", x, 12345)
|
|
}
|
|
}
|
|
|
|
// issue 6833
|
|
|
|
//export GoIssue6833Func
|
|
func GoIssue6833Func(aui uint, aui64 uint64) uint64 {
|
|
return aui64 + uint64(aui)
|
|
}
|
|
|
|
func test6833(t *testing.T) {
|
|
ui := 7
|
|
ull := uint64(0x4000300020001000)
|
|
v := uint64(C.issue6833Func(C.uint(ui), C.ulonglong(ull)))
|
|
exp := uint64(ui) + ull
|
|
if v != exp {
|
|
t.Errorf("issue6833Func() returns %x, expected %x", v, exp)
|
|
}
|
|
}
|
|
|
|
// issue 6907
|
|
|
|
const CString = "C string"
|
|
|
|
//export CheckIssue6907Go
|
|
func CheckIssue6907Go(s string) C.int {
|
|
if s == CString {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func test6907Go(t *testing.T) {
|
|
if got := C.CheckIssue6907C(CString); got != 1 {
|
|
t.Errorf("C.CheckIssue6907C() == %d, want %d", got, 1)
|
|
}
|
|
}
|
|
|
|
// issue 7665
|
|
|
|
//export f7665
|
|
func f7665() {}
|
|
|
|
var bad7665 unsafe.Pointer = C.f7665
|
|
var good7665 uintptr = uintptr(C.f7665)
|
|
|
|
func test7665(t *testing.T) {
|
|
if bad7665 == nil || uintptr(bad7665) != good7665 {
|
|
t.Errorf("ptrs = %p, %#x, want same non-nil pointer", bad7665, good7665)
|
|
}
|
|
}
|
|
|
|
// issue 7978
|
|
|
|
var issue7978sync uint32
|
|
|
|
func issue7978check(t *testing.T, wantFunc string, badFunc string, depth int) {
|
|
runtime.GC()
|
|
buf := make([]byte, 65536)
|
|
trace := string(buf[:runtime.Stack(buf, true)])
|
|
for _, goroutine := range strings.Split(trace, "\n\n") {
|
|
if strings.Contains(goroutine, "test.issue7978go") {
|
|
trace := strings.Split(goroutine, "\n")
|
|
// look for the expected function in the stack
|
|
for i := 0; i < depth; i++ {
|
|
if badFunc != "" && strings.Contains(trace[1+2*i], badFunc) {
|
|
t.Errorf("bad stack: found %s in the stack:\n%s", badFunc, goroutine)
|
|
return
|
|
}
|
|
if strings.Contains(trace[1+2*i], wantFunc) {
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("bad stack: didn't find %s in the stack:\n%s", wantFunc, goroutine)
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("bad stack: goroutine not found. Full stack dump:\n%s", trace)
|
|
}
|
|
|
|
func issue7978wait(store uint32, wait uint32) {
|
|
if store != 0 {
|
|
atomic.StoreUint32(&issue7978sync, store)
|
|
}
|
|
for atomic.LoadUint32(&issue7978sync) != wait {
|
|
runtime.Gosched()
|
|
}
|
|
}
|
|
|
|
//export issue7978cb
|
|
func issue7978cb() {
|
|
// Force a stack growth from the callback to put extra
|
|
// pressure on the runtime. See issue #17785.
|
|
growStack(64)
|
|
issue7978wait(3, 4)
|
|
}
|
|
|
|
func growStack(n int) int {
|
|
var buf [128]int
|
|
if n == 0 {
|
|
return 0
|
|
}
|
|
return buf[growStack(n-1)]
|
|
}
|
|
|
|
func issue7978go() {
|
|
C.issue7978c((*C.uint32_t)(&issue7978sync))
|
|
issue7978wait(7, 8)
|
|
}
|
|
|
|
func test7978(t *testing.T) {
|
|
if runtime.Compiler == "gccgo" {
|
|
t.Skip("gccgo can not do stack traces of C code")
|
|
}
|
|
debug.SetTraceback("2")
|
|
issue7978sync = 0
|
|
go issue7978go()
|
|
// test in c code, before callback
|
|
issue7978wait(0, 1)
|
|
issue7978check(t, "_Cfunc_issue7978c(", "", 1)
|
|
// test in go code, during callback
|
|
issue7978wait(2, 3)
|
|
issue7978check(t, "test.issue7978cb(", "test.issue7978go", 3)
|
|
// test in c code, after callback
|
|
issue7978wait(4, 5)
|
|
issue7978check(t, "_Cfunc_issue7978c(", "_cgoexpwrap", 1)
|
|
// test in go code, after return from cgo
|
|
issue7978wait(6, 7)
|
|
issue7978check(t, "test.issue7978go(", "", 3)
|
|
atomic.StoreUint32(&issue7978sync, 8)
|
|
}
|
|
|
|
// issue 8331 part 2
|
|
|
|
var issue8331Var C.issue8331
|
|
|
|
// issue 8945
|
|
|
|
//export Test8945
|
|
func Test8945() {
|
|
_ = C.func8945
|
|
}
|
|
|
|
// issue 20910
|
|
|
|
//export multi
|
|
func multi() (*C.char, C.int) {
|
|
return C.CString("multi"), 0
|
|
}
|
|
|
|
func test20910(t *testing.T) {
|
|
C.callMulti()
|
|
}
|
|
|
|
// issue 28772 part 2
|
|
|
|
const issue28772Constant2 = C.issue28772Constant2
|
|
|
|
// issue 31891
|
|
|
|
//export useIssue31891A
|
|
func useIssue31891A(c *C.Issue31891A) {}
|
|
|
|
//export useIssue31891B
|
|
func useIssue31891B(c *C.Issue31891B) {}
|
|
|
|
func test31891(t *testing.T) {
|
|
C.callIssue31891()
|
|
}
|
|
|
|
// issue 37033, check if cgo.Handle works properly
|
|
|
|
var issue37033 = 42
|
|
|
|
//export GoFunc37033
|
|
func GoFunc37033(handle C.uintptr_t) {
|
|
h := cgo.Handle(handle)
|
|
ch := h.Value().(chan int)
|
|
ch <- issue37033
|
|
}
|
|
|
|
// issue 38408
|
|
// A typedef pointer can be used as the element type.
|
|
// No runtime test; just make sure it compiles.
|
|
var _ C.PIssue38408 = &C.Issue38408{i: 1}
|