Skip to content

Commit 2c2770c

Browse files
committed
cmd/cgo: make sure pointers passed to C escape to heap
Fixes #10303. Change-Id: Ia68d3566ba3ebeea6e18e388446bd9b8c431e156 Reviewed-on: https://go-review.googlesource.com/10814 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent a3b9797 commit 2c2770c

File tree

4 files changed

+100
-1
lines changed

4 files changed

+100
-1
lines changed

misc/cgo/test/cgo_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,6 @@ func TestReturnAfterGrow(t *testing.T) { testReturnAfterGrow(t) }
6363
func TestReturnAfterGrowFromGo(t *testing.T) { testReturnAfterGrowFromGo(t) }
6464
func Test9026(t *testing.T) { test9026(t) }
6565
func Test9557(t *testing.T) { test9557(t) }
66+
func Test10303(t *testing.T) { test10303(t, 10) }
6667

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

misc/cgo/test/issue10303.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2015 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 10303. Pointers passed to C were not marked as escaping (bug in cgo).
6+
7+
package cgotest
8+
9+
/*
10+
typedef int *intptr;
11+
12+
void setintstar(int *x) {
13+
*x = 1;
14+
}
15+
16+
void setintptr(intptr x) {
17+
*x = 1;
18+
}
19+
20+
void setvoidptr(void *x) {
21+
*(int*)x = 1;
22+
}
23+
24+
typedef struct Struct Struct;
25+
struct Struct {
26+
int *P;
27+
};
28+
29+
void setstruct(Struct s) {
30+
*s.P = 1;
31+
}
32+
33+
*/
34+
import "C"
35+
36+
import (
37+
"testing"
38+
"unsafe"
39+
)
40+
41+
func test10303(t *testing.T, n int) {
42+
// Run at a few different stack depths just to avoid an unlucky pass
43+
// due to variables ending up on different pages.
44+
if n > 0 {
45+
test10303(t, n-1)
46+
}
47+
if t.Failed() {
48+
return
49+
}
50+
var x, y, z, v, si C.int
51+
var s C.Struct
52+
C.setintstar(&x)
53+
C.setintptr(&y)
54+
C.setvoidptr(unsafe.Pointer(&v))
55+
s.P = &si
56+
C.setstruct(s)
57+
58+
if uintptr(unsafe.Pointer(&x))&^0xfff == uintptr(unsafe.Pointer(&z))&^0xfff {
59+
t.Error("C int* argument on stack")
60+
}
61+
if uintptr(unsafe.Pointer(&y))&^0xfff == uintptr(unsafe.Pointer(&z))&^0xfff {
62+
t.Error("C intptr argument on stack")
63+
}
64+
if uintptr(unsafe.Pointer(&v))&^0xfff == uintptr(unsafe.Pointer(&z))&^0xfff {
65+
t.Error("C void* argument on stack")
66+
}
67+
if uintptr(unsafe.Pointer(&si))&^0xfff == uintptr(unsafe.Pointer(&z))&^0xfff {
68+
t.Error("C struct field pointer on stack")
69+
}
70+
}

src/cmd/cgo/out.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ func (p *Package) writeDefs() {
7979
}
8080
fmt.Fprintf(fgo2, "func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }\n\n")
8181

82+
if !*gccgo {
83+
fmt.Fprintf(fgo2, "//go:linkname _Cgo_always_false runtime.cgoAlwaysFalse\n")
84+
fmt.Fprintf(fgo2, "var _Cgo_always_false bool\n")
85+
fmt.Fprintf(fgo2, "//go:linkname _Cgo_use runtime.cgoUse\n")
86+
fmt.Fprintf(fgo2, "func _Cgo_use(interface{})\n")
87+
}
88+
8289
typedefNames := make([]string, 0, len(typedef))
8390
for name := range typedef {
8491
typedefNames = append(typedefNames, name)
@@ -428,7 +435,7 @@ func (p *Package) writeDefsFunc(fgo2 io.Writer, n *Name) {
428435
return
429436
}
430437

431-
// C wrapper calls into gcc, passing a pointer to the argument frame.
438+
// Wrapper calls into gcc, passing a pointer to the argument frame.
432439
fmt.Fprintf(fgo2, "//go:cgo_import_static %s\n", cname)
433440
fmt.Fprintf(fgo2, "//go:linkname __cgofn_%s %s\n", cname, cname)
434441
fmt.Fprintf(fgo2, "var __cgofn_%s byte\n", cname)
@@ -463,6 +470,11 @@ func (p *Package) writeDefsFunc(fgo2 io.Writer, n *Name) {
463470
if n.AddError {
464471
fmt.Fprintf(fgo2, "\tif errno != 0 { r2 = syscall.Errno(errno) }\n")
465472
}
473+
fmt.Fprintf(fgo2, "\tif _Cgo_always_false {\n")
474+
for i := range d.Type.Params.List {
475+
fmt.Fprintf(fgo2, "\t\t_Cgo_use(p%d)\n", i)
476+
}
477+
fmt.Fprintf(fgo2, "\t}\n")
466478
fmt.Fprintf(fgo2, "\treturn\n")
467479
fmt.Fprintf(fgo2, "}\n")
468480
}

src/runtime/cgo.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,19 @@ var iscgo bool
3232
// cgoHasExtraM is set on startup when an extra M is created for cgo.
3333
// The extra M must be created before any C/C++ code calls cgocallback.
3434
var cgoHasExtraM bool
35+
36+
// cgoUse is called by cgo-generated code (using go:linkname to get at
37+
// an unexported name). The calls serve two purposes:
38+
// 1) they are opaque to escape analysis, so the argument is considered to
39+
// escape to the heap.
40+
// 2) they keep the argument alive until the call site; the call is emitted after
41+
// the end of the (presumed) use of the argument by C.
42+
// cgoUse should not actually be called (see cgoAlwaysFalse).
43+
func cgoUse(interface{}) { throw("cgoUse should not be called") }
44+
45+
// cgoAlwaysFalse is a boolean value that is always false.
46+
// The cgo-generated code says if cgoAlwaysFalse { cgoUse(p) }.
47+
// The compiler cannot see that cgoAlwaysFalse is always false,
48+
// so it emits the test and keeps the call, giving the desired
49+
// escape analysis result. The test is cheaper than the call.
50+
var cgoAlwaysFalse bool

0 commit comments

Comments
 (0)