Skip to content

Commit 3a0b3ea

Browse files
committed
cgo: add centralized pointer handle manager
This also fixes a potential misuse of passing Go pointers to C.
1 parent b556b41 commit 3a0b3ea

File tree

4 files changed

+299
-18
lines changed

4 files changed

+299
-18
lines changed

clipboard_linux.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
#include <X11/Xlib.h>
1111
#include <X11/Xatom.h>
1212

13+
// syncStatus is a function from the Go side.
14+
extern void syncStatus(unsigned long long handle, int status);
15+
1316
int clipboard_test() {
1417
Display *d = XOpenDisplay(0);
1518
if (d == NULL) {
@@ -22,10 +25,10 @@ int clipboard_test() {
2225
// clipboard_write writes the given buf of size n as type typ.
2326
// if start is provided, the value of start will be changed to 1 to indicate
2427
// if the write is availiable for reading.
25-
int clipboard_write(char *typ, unsigned char *buf, size_t n, int *start) {
28+
int clipboard_write(char *typ, unsigned char *buf, size_t n, unsigned long long handle) {
2629
Display* d = XOpenDisplay(0);
2730
if (d == NULL) {
28-
if (start != NULL && *start == 0) *start = -1;
31+
if (handle != 0) syncStatus(handle, -1);
2932
return -1;
3033
}
3134

@@ -41,24 +44,25 @@ int clipboard_write(char *typ, unsigned char *buf, size_t n, int *start) {
4144
Atom target = XInternAtom(d, typ, True);
4245
if (target == None) {
4346
XCloseDisplay(d);
44-
if (start != NULL && *start == 0) *start = -2;
47+
if (handle != 0) syncStatus(handle, -2);
4548
return -2;
4649
}
4750

4851
XSetSelectionOwner(d, sel, w, CurrentTime);
4952
if (XGetSelectionOwner(d, sel) != w) {
5053
XCloseDisplay(d);
51-
if (start != NULL && *start == 0) *start = -3;
54+
if (handle != 0) syncStatus(handle, -3);
5255
return -3;
5356
}
5457

5558
XEvent event;
5659
XSelectionRequestEvent* xsr;
60+
int notified = 0;
5761
for (;;) {
58-
// FIXME: this should race with the code on the Go side, start
59-
// should use an atomic version, and use atomic_store.
60-
if (start != NULL && *start == 0)
61-
*start = 1; // notify Go side
62+
if (handle != 0 && notified == 0) {
63+
syncStatus(handle, 1); // notify Go side
64+
notified = 1;
65+
}
6266

6367
XNextEvent(d, &event);
6468
switch (event.type) {

clipboard_linux.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ int clipboard_write(
2323
char* typ,
2424
unsigned char* buf,
2525
size_t n,
26-
int* start // FIXME: should use atomic
26+
unsigned long long handle
2727
);
2828
unsigned long clipboard_read(char* typ, char **out);
2929
*/
@@ -36,6 +36,8 @@ import (
3636
"runtime"
3737
"time"
3838
"unsafe"
39+
40+
"golang.design/x/clipboard/internal/cgo"
3941
)
4042

4143
func init() {
@@ -90,7 +92,7 @@ func write(t Format, buf []byte) (<-chan struct{}, error) {
9092
s = "image/png"
9193
}
9294

93-
var start C.int
95+
start := make(chan int)
9496
done := make(chan struct{}, 1)
9597

9698
go func() { // surve as a daemon until the ownership is terminated.
@@ -100,11 +102,12 @@ func write(t Format, buf []byte) (<-chan struct{}, error) {
100102
cs := C.CString(s)
101103
defer C.free(unsafe.Pointer(cs))
102104

105+
h := cgo.NewHandle(start)
103106
var ok C.int
104107
if len(buf) == 0 {
105-
ok = C.clipboard_write(cs, nil, 0, &start)
108+
ok = C.clipboard_write(cs, nil, 0, C.ulonglong(h))
106109
} else {
107-
ok = C.clipboard_write(cs, (*C.uchar)(unsafe.Pointer(&(buf[0]))), C.size_t(len(buf)), &start)
110+
ok = C.clipboard_write(cs, (*C.uchar)(unsafe.Pointer(&(buf[0]))), C.size_t(len(buf)), C.ulonglong(h))
108111
}
109112
if ok != C.int(0) {
110113
fmt.Fprintf(os.Stderr, "write failed with status: %d\n", int(ok))
@@ -113,12 +116,8 @@ func write(t Format, buf []byte) (<-chan struct{}, error) {
113116
close(done)
114117
}()
115118

116-
// FIXME: this should race with the code on the C side, start
117-
// should use an atomic version, and use atomic_load.
118-
for start == 0 {
119-
}
120-
121-
if start < 0 {
119+
status := <-start
120+
if status < 0 {
122121
return nil, errInvalidOperation
123122
}
124123
// wait until enter event loop
@@ -149,3 +148,14 @@ func watch(ctx context.Context, t Format) <-chan []byte {
149148
}()
150149
return recv
151150
}
151+
152+
type syncChan struct {
153+
c chan int
154+
}
155+
156+
//export syncStatus
157+
func syncStatus(h uintptr, val int) {
158+
v := cgo.Handle(h).Value().(chan int)
159+
v <- val
160+
cgo.Handle(h).Delete()
161+
}

internal/cgo/handle.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2021 The golang.design Initiative Authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a MIT license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
// Package cgo is an implementation of golang.org/issue/37033.
8+
//
9+
// See golang.org/cl/294670 for code review discussion.
10+
package cgo
11+
12+
import (
13+
"reflect"
14+
"sync"
15+
)
16+
17+
// Handle provides a safe representation to communicate Go values between
18+
// C and Go. The zero value of a handle is not a valid handle, and thus
19+
// safe to use as a sentinel in C APIs.
20+
//
21+
// The underlying type of Handle may change, but the value is guaranteed
22+
// to fit in an integer type that is large enough to hold the bit pattern
23+
// of any pointer. For instance, on the Go side:
24+
//
25+
// package main
26+
//
27+
// /*
28+
// extern void GoPrint(unsigned long long handle);
29+
// void printFromGo(unsigned long long handle);
30+
// */
31+
// import "C"
32+
// import "runtime/cgo"
33+
//
34+
// //export GoPrint
35+
// func GoPrint(handle C.ulonglong) {
36+
// h := cgo.Handle(handle)
37+
// val := h.Value().(int)
38+
// println(val)
39+
// h.Delete()
40+
// }
41+
//
42+
// func main() {
43+
// val := 42
44+
//
45+
// C.printFromGo(C.ulonglong(cgo.NewHandle(val))) // prints 42
46+
// }
47+
//
48+
// and on the C side:
49+
//
50+
// // This function is from Go side.
51+
// extern void GoPrint(unsigned long long handle);
52+
//
53+
// // A C function
54+
// void printFromGo(unsigned long long handle) {
55+
// GoPrint(handle);
56+
// }
57+
type Handle uintptr
58+
59+
// NewHandle returns a handle for a given value. If a given value is a
60+
// pointer, slice, map, channel, or function that refers to the same
61+
// object, the returned handle will also be the same. Besides, nil value
62+
// must not be used.
63+
//
64+
// The handle is valid until the program calls Delete on it. The handle
65+
// uses resources, and this package assumes that C code may hold on to
66+
// the handle, so a program must explicitly call Delete when the handle
67+
// is no longer needed.
68+
//
69+
// The intended use is to pass the returned handle to C code, which
70+
// passes it back to Go, which calls Value. See an example in the
71+
// comments of the Handle definition.
72+
func NewHandle(v interface{}) Handle {
73+
var k uintptr
74+
75+
rv := reflect.ValueOf(v)
76+
switch rv.Kind() {
77+
case reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
78+
reflect.Map, reflect.Chan, reflect.Func:
79+
if rv.IsNil() {
80+
panic("cannot use handle for nil value")
81+
}
82+
83+
k = rv.Pointer()
84+
default:
85+
k = reflect.ValueOf(&v).Pointer()
86+
}
87+
88+
// v escapes to the heap, always. As Go do not have a moving GC (and
89+
// possibly lasts true for a long future), it is safe to use its
90+
// pointer address as the key of the global map at this moment.
91+
// The implementation must be reconsidered if moving GC is
92+
// introduced internally.
93+
actual, loaded := m.LoadOrStore(k, v)
94+
if !loaded {
95+
return Handle(k)
96+
}
97+
arv := reflect.ValueOf(actual)
98+
switch arv.Kind() {
99+
case reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
100+
reflect.Map, reflect.Chan, reflect.Func:
101+
// The underlying object of the given Go value already have
102+
// its existing handle.
103+
if arv.Pointer() == k {
104+
return Handle(k)
105+
}
106+
107+
// If the loaded actual value is inconsistent with the new
108+
// value, it means the address has been used for different
109+
// objects, and we should fallthrough, see comments below.
110+
fallthrough
111+
default:
112+
// If a Go value is garbage collected and its address is reused
113+
// for a new Go value, meaning that the Handle does not call
114+
// Delete explicitly when the old Go value is not needed.
115+
// Consider this as a misuse of a handle, do panic.
116+
panic("misuse of a handle")
117+
}
118+
}
119+
120+
// Delete invalidates a handle. This method must be called when C code no
121+
// longer has a copy of the handle, and the program no longer needs the
122+
// Go value that associated with the handle.
123+
//
124+
// The method panics if the handle is invalid already.
125+
func (h Handle) Delete() {
126+
_, ok := m.LoadAndDelete(uintptr(h))
127+
if !ok {
128+
panic("misuse of a handle")
129+
}
130+
}
131+
132+
// Value returns the associated Go value for a valid handle.
133+
//
134+
// The method panics if the handle is invalid already.
135+
func (h Handle) Value() interface{} {
136+
v, ok := m.Load(uintptr(h))
137+
if !ok {
138+
panic("misuse of a handle")
139+
}
140+
return v
141+
}
142+
143+
var m = &sync.Map{} // map[uintptr]interface{}

internal/cgo/handle_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package cgo
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestValueHandle(t *testing.T) {
8+
v := 42
9+
10+
h1 := NewHandle(v)
11+
h2 := NewHandle(v)
12+
13+
if uintptr(h1) == uintptr(h2) {
14+
t.Fatalf("duplicated Go values should have different handles")
15+
}
16+
17+
h1v := h1.Value().(int)
18+
h2v := h2.Value().(int)
19+
if h1v != h2v {
20+
t.Fatalf("the Value of duplicated Go values are different: want %d, got %d", h1v, h2v)
21+
}
22+
if h1v != v {
23+
t.Fatalf("the Value of a handle does not match origin: want %v, got %v", v, h1v)
24+
}
25+
26+
h1.Delete()
27+
h2.Delete()
28+
29+
siz := 0
30+
m.Range(func(k, v interface{}) bool {
31+
siz++
32+
return true
33+
})
34+
if siz != 0 {
35+
t.Fatalf("handles are not deleted, want: %d, got %d", 0, siz)
36+
}
37+
}
38+
39+
func TestPointerHandle(t *testing.T) {
40+
v := 42
41+
42+
p1 := &v
43+
p2 := &v
44+
45+
h1 := NewHandle(p1)
46+
h2 := NewHandle(p2)
47+
48+
if uintptr(h1) != uintptr(h2) {
49+
t.Fatalf("pointers to the same value should have same handle")
50+
}
51+
52+
h1v := h1.Value().(*int)
53+
h2v := h2.Value().(*int)
54+
if h1v != h2v {
55+
t.Fatalf("the Value of a handle does not match origin: want %v, got %v", v, h1v)
56+
}
57+
58+
h1.Delete()
59+
60+
siz := 0
61+
m.Range(func(k, v interface{}) bool {
62+
siz++
63+
return true
64+
})
65+
if siz != 0 {
66+
t.Fatalf("handles are not deleted: want %d, got %d", 0, siz)
67+
}
68+
69+
defer func() {
70+
if r := recover(); r != nil {
71+
return
72+
}
73+
t.Fatalf("double Delete on a same handle did not trigger a panic")
74+
}()
75+
76+
h2.Delete()
77+
}
78+
79+
func TestNilHandle(t *testing.T) {
80+
var v *int
81+
82+
defer func() {
83+
if r := recover(); r != nil {
84+
return
85+
}
86+
t.Fatalf("nil should not be created as a handle successfully")
87+
}()
88+
89+
_ = NewHandle(v)
90+
}
91+
92+
func f1() {}
93+
func f2() {}
94+
95+
type foo struct{}
96+
97+
func (f *foo) bar() {}
98+
func (f *foo) wow() {}
99+
100+
func TestFuncHandle(t *testing.T) {
101+
h1 := NewHandle(f1)
102+
h2 := NewHandle(f2)
103+
h3 := NewHandle(f2)
104+
105+
if h1 == h2 {
106+
t.Fatalf("different functions should have different handles")
107+
}
108+
if h2 != h3 {
109+
t.Fatalf("same functions should have same handles")
110+
}
111+
112+
f := foo{}
113+
h4 := NewHandle(f.bar)
114+
h5 := NewHandle(f.bar)
115+
h6 := NewHandle(f.wow)
116+
117+
if h4 != h5 {
118+
t.Fatalf("same methods should have same handles")
119+
}
120+
121+
if h5 == h6 {
122+
t.Fatalf("different methods should have different handles")
123+
}
124+
}

0 commit comments

Comments
 (0)