Skip to content

Commit b868616

Browse files
committed
cmd/cgo: special case C ptr types to use uintptr
Some C types are declared as pointers, but C code stores non-pointers in them. When the Go garbage collector sees such a pointer, it gets unhappy. Instead, for these types represent them on the Go side with uintptr. We need this change to handle Apple's CoreFoundation CF*Ref types. Users of these types might need to update their code like we do in root_cgo_darwin.go. The only change that is required under normal circumstances is converting some nils to 0. A go fix module is provided to help. Fixes #21897 RELNOTE=yes Change-Id: I9716cfb255dc918792625f42952aa171cd31ec1b Reviewed-on: https://go-review.googlesource.com/66332 Run-TryBot: Keith Randall <[email protected]> Reviewed-by: Robert Griesemer <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 644787c commit b868616

File tree

10 files changed

+549
-4
lines changed

10 files changed

+549
-4
lines changed

misc/cgo/test/cgo_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,6 @@ func Test21708(t *testing.T) { test21708(t) }
8585
func Test21809(t *testing.T) { test21809(t) }
8686
func Test6907(t *testing.T) { test6907(t) }
8787
func Test6907Go(t *testing.T) { test6907Go(t) }
88+
func Test21897(t *testing.T) { test21897(t) }
8889

8990
func BenchmarkCgoCall(b *testing.B) { benchCgoCall(b) }

misc/cgo/test/issue21897.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2017 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+
// +build darwin,cgo,!internal
6+
7+
package cgotest
8+
9+
/*
10+
#cgo LDFLAGS: -framework CoreFoundation
11+
#include <CoreFoundation/CoreFoundation.h>
12+
*/
13+
import "C"
14+
import (
15+
"runtime/debug"
16+
"testing"
17+
"unsafe"
18+
)
19+
20+
func test21897(t *testing.T) {
21+
// Please write barrier, kick in soon.
22+
defer debug.SetGCPercent(debug.SetGCPercent(1))
23+
24+
for i := 0; i < 10000; i++ {
25+
testCFNumberRef()
26+
testCFDateRef()
27+
testCFBooleanRef()
28+
// Allocate some memory, so eventually the write barrier is enabled
29+
// and it will see writes of bad pointers in the test* functions below.
30+
byteSliceSink = make([]byte, 1024)
31+
}
32+
}
33+
34+
var byteSliceSink []byte
35+
36+
func testCFNumberRef() {
37+
var v int64 = 0
38+
xCFNumberRef = C.CFNumberCreate(C.kCFAllocatorSystemDefault, C.kCFNumberSInt64Type, unsafe.Pointer(&v))
39+
//fmt.Printf("CFNumberRef: %x\n", uintptr(unsafe.Pointer(xCFNumberRef)))
40+
}
41+
42+
var xCFNumberRef C.CFNumberRef
43+
44+
func testCFDateRef() {
45+
xCFDateRef = C.CFDateCreate(C.kCFAllocatorSystemDefault, 0) // 0 value is 1 Jan 2001 00:00:00 GMT
46+
//fmt.Printf("CFDateRef: %x\n", uintptr(unsafe.Pointer(xCFDateRef)))
47+
}
48+
49+
var xCFDateRef C.CFDateRef
50+
51+
func testCFBooleanRef() {
52+
xCFBooleanRef = C.kCFBooleanFalse
53+
//fmt.Printf("CFBooleanRef: %x\n", uintptr(unsafe.Pointer(xCFBooleanRef)))
54+
}
55+
56+
var xCFBooleanRef C.CFBooleanRef

misc/cgo/test/issue21897b.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2017 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+
// +build !darwin !cgo internal
6+
7+
package cgotest
8+
9+
import "testing"
10+
11+
func test21897(t *testing.T) {
12+
t.Skip("test runs only on darwin+cgo")
13+
}

src/cmd/cgo/doc.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ C.complexfloat (complex float), and C.complexdouble (complex double).
128128
The C type void* is represented by Go's unsafe.Pointer.
129129
The C types __int128_t and __uint128_t are represented by [16]byte.
130130
131+
A few special C types which would normally be represented by a pointer
132+
type in Go are instead represented by a uintptr. See the Special
133+
cases section below.
134+
131135
To access a struct, union, or enum type directly, prefix it with
132136
struct_, union_, or enum_, as in C.struct_stat.
133137
@@ -334,6 +338,84 @@ and of course there is nothing stopping the C code from doing anything
334338
it likes. However, programs that break these rules are likely to fail
335339
in unexpected and unpredictable ways.
336340
341+
Special cases
342+
343+
A few special C types which would normally be represented by a pointer
344+
type in Go are instead represented by a uintptr. Those types are
345+
the CF*Ref types from the CoreFoundation library on Darwin, including:
346+
347+
CFAllocatorRef
348+
CFArrayRef
349+
CFAttributedStringRef
350+
CFBagRef
351+
CFBinaryHeapRef
352+
CFBitVectorRef
353+
CFBooleanRef
354+
CFBundleRef
355+
CFCalendarRef
356+
CFCharacterSetRef
357+
CFDataRef
358+
CFDateFormatterRef
359+
CFDateRef
360+
CFDictionaryRef
361+
CFErrorRef
362+
CFFileDescriptorRef
363+
CFFileSecurityRef
364+
CFLocaleRef
365+
CFMachPortRef
366+
CFMessagePortRef
367+
CFMutableArrayRef
368+
CFMutableAttributedStringRef
369+
CFMutableBagRef
370+
CFMutableBitVectorRef
371+
CFMutableCharacterSetRef
372+
CFMutableDataRef
373+
CFMutableDictionaryRef
374+
CFMutableSetRef
375+
CFMutableStringRef
376+
CFNotificationCenterRef
377+
CFNullRef
378+
CFNumberFormatterRef
379+
CFNumberRef
380+
CFPlugInInstanceRef
381+
CFPlugInRef
382+
CFPropertyListRef
383+
CFReadStreamRef
384+
CFRunLoopObserverRef
385+
CFRunLoopRef
386+
CFRunLoopSourceRef
387+
CFRunLoopTimerRef
388+
CFSetRef
389+
CFSocketRef
390+
CFStringRef
391+
CFStringTokenizerRef
392+
CFTimeZoneRef
393+
CFTreeRef
394+
CFTypeRef
395+
CFURLCreateFromFSRef
396+
CFURLEnumeratorRef
397+
CFURLGetFSRef
398+
CFURLRef
399+
CFUUIDRef
400+
CFUserNotificationRef
401+
CFWriteStreamRef
402+
CFXMLNodeRef
403+
CFXMLParserRef
404+
CFXMLTreeRef
405+
406+
These types are uintptr on the Go side because they would otherwise
407+
confuse the Go garbage collector; they are sometimes not really
408+
pointers but data structures encoded in a pointer type. All operations
409+
on these types must happen in C. The proper constant to initialize an
410+
empty such reference is 0, not nil.
411+
412+
This special case was introduced in Go 1.10. For auto-updating code
413+
from Go 1.9 and earlier, use the cftype rewrite in the Go fix tool:
414+
415+
go tool fix -r cftype <pkg>
416+
417+
It will replace nil with 0 in the appropriate places.
418+
337419
Using cgo directly
338420
339421
Usage:

src/cmd/cgo/gcc.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,6 +2057,12 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type {
20572057
name := c.Ident("_Ctype_" + dt.Name)
20582058
goIdent[name.Name] = name
20592059
sub := c.Type(dt.Type, pos)
2060+
if badPointerTypedef(dt.Name) {
2061+
// Treat this typedef as a uintptr.
2062+
s := *sub
2063+
s.Go = c.uintptr
2064+
sub = &s
2065+
}
20602066
t.Go = name
20612067
if unionWithPointer[sub.Go] {
20622068
unionWithPointer[t.Go] = true
@@ -2215,6 +2221,11 @@ func (c *typeConv) FuncArg(dtype dwarf.Type, pos token.Pos) *Type {
22152221
if _, void := base(ptr.Type).(*dwarf.VoidType); void {
22162222
break
22172223
}
2224+
// ...or the typedef is one in which we expect bad pointers.
2225+
// It will be a uintptr instead of *X.
2226+
if badPointerTypedef(dt.Name) {
2227+
break
2228+
}
22182229

22192230
t = c.Type(ptr, pos)
22202231
if t == nil {
@@ -2547,3 +2558,51 @@ func fieldPrefix(fld []*ast.Field) string {
25472558
}
25482559
return prefix
25492560
}
2561+
2562+
// badPointerTypedef reports whether t is a C typedef that should not be considered a pointer in Go.
2563+
// A typedef is bad if C code sometimes stores non-pointers in this type.
2564+
// TODO: Currently our best solution is to find these manually and list them as
2565+
// they come up. A better solution is desired.
2566+
func badPointerTypedef(t string) bool {
2567+
// The real bad types are CFNumberRef and CFTypeRef.
2568+
// Sometimes non-pointers are stored in these types.
2569+
// CFTypeRef is a supertype of those, so it can have bad pointers in it as well.
2570+
// We return true for the other CF*Ref types just so casting between them is easier.
2571+
// See comment below for details about the bad pointers.
2572+
return goos == "darwin" && strings.HasPrefix(t, "CF") && strings.HasSuffix(t, "Ref")
2573+
}
2574+
2575+
// Comment from Darwin's CFInternal.h
2576+
/*
2577+
// Tagged pointer support
2578+
// Low-bit set means tagged object, next 3 bits (currently)
2579+
// define the tagged object class, next 4 bits are for type
2580+
// information for the specific tagged object class. Thus,
2581+
// the low byte is for type info, and the rest of a pointer
2582+
// (32 or 64-bit) is for payload, whatever the tagged class.
2583+
//
2584+
// Note that the specific integers used to identify the
2585+
// specific tagged classes can and will change from release
2586+
// to release (that's why this stuff is in CF*Internal*.h),
2587+
// as can the definition of type info vs payload above.
2588+
//
2589+
#if __LP64__
2590+
#define CF_IS_TAGGED_OBJ(PTR) ((uintptr_t)(PTR) & 0x1)
2591+
#define CF_TAGGED_OBJ_TYPE(PTR) ((uintptr_t)(PTR) & 0xF)
2592+
#else
2593+
#define CF_IS_TAGGED_OBJ(PTR) 0
2594+
#define CF_TAGGED_OBJ_TYPE(PTR) 0
2595+
#endif
2596+
2597+
enum {
2598+
kCFTaggedObjectID_Invalid = 0,
2599+
kCFTaggedObjectID_Atom = (0 << 1) + 1,
2600+
kCFTaggedObjectID_Undefined3 = (1 << 1) + 1,
2601+
kCFTaggedObjectID_Undefined2 = (2 << 1) + 1,
2602+
kCFTaggedObjectID_Integer = (3 << 1) + 1,
2603+
kCFTaggedObjectID_DateTS = (4 << 1) + 1,
2604+
kCFTaggedObjectID_ManagedObjectID = (5 << 1) + 1, // Core Data
2605+
kCFTaggedObjectID_Date = (6 << 1) + 1,
2606+
kCFTaggedObjectID_Undefined7 = (7 << 1) + 1,
2607+
};
2608+
*/

src/cmd/dist/test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ func (t *tester) cgoTest(dt *distTest) error {
915915
t.addCmd(dt, "misc/cgo/test", "go", "test", t.tags(), "-ldflags", "-linkmode=auto", t.runFlag(""))
916916

917917
if t.internalLink() {
918-
t.addCmd(dt, "misc/cgo/test", "go", "test", "-ldflags", "-linkmode=internal", t.runFlag(""))
918+
t.addCmd(dt, "misc/cgo/test", "go", "test", "-tags", "internal", "-ldflags", "-linkmode=internal", t.runFlag(""))
919919
}
920920

921921
pair := gohostos + "-" + goarch

src/cmd/fix/cftype.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2017 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+
package main
6+
7+
import (
8+
"go/ast"
9+
"go/token"
10+
"reflect"
11+
"strings"
12+
)
13+
14+
func init() {
15+
register(cftypeFix)
16+
}
17+
18+
var cftypeFix = fix{
19+
name: "cftype",
20+
date: "2017-09-27",
21+
f: cftypefix,
22+
desc: `Fixes initializers of C.CF*Ptr types`,
23+
disabled: false,
24+
}
25+
26+
// Old state:
27+
// type CFTypeRef unsafe.Pointer
28+
// New state:
29+
// type CFTypeRef uintptr
30+
// and similar for other CF*Ref types.
31+
// This fix finds nils initializing these types and replaces the nils with 0s.
32+
func cftypefix(f *ast.File) bool {
33+
if !imports(f, "C") {
34+
return false
35+
}
36+
typeof, _ := typecheck(&TypeConfig{}, f)
37+
38+
// step 1: Find all the nils with the offending types.
39+
// Compute their replacement.
40+
badNils := map[interface{}]ast.Expr{}
41+
walk(f, func(n interface{}) {
42+
if i, ok := n.(*ast.Ident); ok && i.Name == "nil" && badPointerType(typeof[n]) {
43+
badNils[n] = &ast.BasicLit{ValuePos: i.NamePos, Kind: token.INT, Value: "0"}
44+
}
45+
})
46+
if len(badNils) == 0 {
47+
return false
48+
}
49+
50+
// step 2: find all uses of the bad nils, replace them with 0.
51+
// There's no easy way to map from an ast.Expr to all the places that use them, so
52+
// we use reflect to find all such references.
53+
exprType := reflect.TypeOf((*ast.Expr)(nil)).Elem()
54+
exprSliceType := reflect.TypeOf(([]ast.Expr)(nil))
55+
walk(f, func(n interface{}) {
56+
if n == nil {
57+
return
58+
}
59+
v := reflect.ValueOf(n)
60+
if v.Type().Kind() != reflect.Ptr {
61+
return
62+
}
63+
if v.IsNil() {
64+
return
65+
}
66+
v = v.Elem()
67+
if v.Type().Kind() != reflect.Struct {
68+
return
69+
}
70+
for i := 0; i < v.NumField(); i++ {
71+
f := v.Field(i)
72+
if f.Type() == exprType {
73+
if r := badNils[f.Interface()]; r != nil {
74+
f.Set(reflect.ValueOf(r))
75+
}
76+
}
77+
if f.Type() == exprSliceType {
78+
for j := 0; j < f.Len(); j++ {
79+
e := f.Index(j)
80+
if r := badNils[e.Interface()]; r != nil {
81+
e.Set(reflect.ValueOf(r))
82+
}
83+
}
84+
}
85+
}
86+
})
87+
88+
return true
89+
}
90+
91+
func badPointerType(s string) bool {
92+
return strings.HasPrefix(s, "C.CF") && strings.HasSuffix(s, "Ref")
93+
}

0 commit comments

Comments
 (0)