"os"
"os/exec"
"path/filepath"
+ "regexp"
"slices"
"strings"
"sync/atomic"
// ptrTest is the tests without the boilerplate.
type ptrTest struct {
- name string // for reporting
- c string // the cgo comment
- c1 string // cgo comment forced into non-export cgo file
- imports []string // a list of imports
- support string // supporting functions
- body string // the body of the main function
- extra []extra // extra files
- fail bool // whether the test should fail
- expensive bool // whether the test requires the expensive check
+ name string // for reporting
+ c string // the cgo comment
+ c1 string // cgo comment forced into non-export cgo file
+ imports []string // a list of imports
+ support string // supporting functions
+ body string // the body of the main function
+ extra []extra // extra files
+ fail bool // whether the test should fail
+ expensive bool // whether the test requires the expensive check
+ errTextRegexp string // error text regexp; if empty, use the pattern `.*unpinned Go.*`
}
type extra struct {
body: `i := 0; a := &[2]unsafe.Pointer{nil, unsafe.Pointer(&i)}; C.f45(&a[0])`,
fail: true,
},
+ {
+ // Passing a Go map as argument to C.
+ name: "argmap",
+ c: `void f46(void* p) {}`,
+ imports: []string{"unsafe"},
+ body: `m := map[int]int{0: 1,}; C.f46(unsafe.Pointer(&m))`,
+ fail: true,
+ errTextRegexp: `.*argument of cgo function has Go pointer to unpinned Go map`,
+ },
+ {
+ // Returning a Go map to C.
+ name: "retmap",
+ c: `extern void f47();`,
+ support: `//export GoMap47
+ func GoMap47() map[int]int { return map[int]int{0: 1,} }`,
+ body: `C.f47()`,
+ c1: `extern void* GoMap47();
+ void f47() { GoMap47(); }`,
+ fail: true,
+ errTextRegexp: `.*result of Go function GoMap47 called from cgo is unpinned Go map or points to unpinned Go map.*`,
+ },
}
func TestPointerChecks(t *testing.T) {
// after testOne finishes.
var pending int32
for _, pt := range ptrTests {
- pt := pt
t.Run(pt.name, func(t *testing.T) {
atomic.AddInt32(&pending, +1)
defer func() {
}
buf, err := runcmd(cgocheck)
+
+ var pattern string = pt.errTextRegexp
+ if pt.errTextRegexp == "" {
+ pattern = `.*unpinned Go.*`
+ }
+
if pt.fail {
if err == nil {
t.Logf("%s", buf)
t.Fatalf("did not fail as expected")
- } else if !bytes.Contains(buf, []byte("Go pointer")) {
+ } else if ok, _ := regexp.Match(pattern, buf); !ok {
t.Logf("%s", buf)
t.Fatalf("did not print expected error (failed with %v)", err)
}
cgoCheckArg(t, ep.data, !t.IsDirectIface(), top, cgoCheckPointerFail)
}
-const cgoCheckPointerFail = "cgo argument has Go pointer to unpinned Go pointer"
-const cgoResultFail = "cgo result is unpinned Go pointer or points to unpinned Go pointer"
+type cgoErrorMsg int
+const (
+ cgoCheckPointerFail cgoErrorMsg = iota
+ cgoResultFail
+)
// cgoCheckArg is the real work of cgoCheckPointer and cgoCheckResult.
// The argument p is either a pointer to the value (of type t), or the value
// itself, depending on indir. The top parameter is whether we are at the top
// level, where Go pointers are allowed. Go pointers to pinned objects are
// allowed as long as they don't reference other unpinned pointers.
-func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg string) {
+func cgoCheckArg(t *_type, p unsafe.Pointer, indir, top bool, msg cgoErrorMsg) {
if !t.Pointers() || p == nil {
// If the type has no pointers there is nothing to do.
return
// These types contain internal pointers that will
// always be allocated in the Go heap. It's never OK
// to pass them to C.
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, t.Kind()))
case abi.Func:
if indir {
p = *(*unsafe.Pointer)(p)
if !cgoIsGoPointer(p) {
return
}
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, t.Kind()))
case abi.Interface:
it := *(**_type)(p)
if it == nil {
// constant. A type not known at compile time will be
// in the heap and will not be OK.
if inheap(uintptr(unsafe.Pointer(it))) {
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, t.Kind()))
}
p = *(*unsafe.Pointer)(add(p, goarch.PtrSize))
if !cgoIsGoPointer(p) {
return
}
if !top && !isPinned(p) {
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, t.Kind()))
}
cgoCheckArg(it, p, !it.IsDirectIface(), false, msg)
case abi.Slice:
return
}
if !top && !isPinned(p) {
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, t.Kind()))
}
if !st.Elem.Pointers() {
return
return
}
if !top && !isPinned(ss.str) {
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, t.Kind()))
}
case abi.Struct:
st := (*structtype)(unsafe.Pointer(t))
return
}
if !top && !isPinned(p) {
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, t.Kind()))
}
cgoCheckUnknownPointer(p, msg)
// memory. It checks whether that Go memory contains any other
// pointer into unpinned Go memory. If it does, we panic.
// The return values are unused but useful to see in panic tracebacks.
-func cgoCheckUnknownPointer(p unsafe.Pointer, msg string) (base, i uintptr) {
+func cgoCheckUnknownPointer(p unsafe.Pointer, msg cgoErrorMsg) (base, i uintptr) {
if inheap(uintptr(p)) {
b, span, _ := findObject(uintptr(p), 0, 0)
base = b
}
pp := *(*unsafe.Pointer)(unsafe.Pointer(addr))
if cgoIsGoPointer(pp) && !isPinned(pp) {
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, abi.Pointer))
}
}
return
if cgoInRange(p, datap.data, datap.edata) || cgoInRange(p, datap.bss, datap.ebss) {
// We have no way to know the size of the object.
// We have to assume that it might contain a pointer.
- panic(errorString(msg))
+ panic(cgoFormatErr(msg, abi.Pointer))
}
// In the text or noptr sections, we know that the
// pointer does not point to a Go pointer.
t := ep._type
cgoCheckArg(t, ep.data, !t.IsDirectIface(), false, cgoResultFail)
}
+
+// cgoFormatErr is called by cgoCheckArg and cgoCheckUnknownPointer
+// to format panic error messages.
+func cgoFormatErr(error cgoErrorMsg, kind abi.Kind) errorString {
+ var msg, kindname string
+ var cgoFunction string = "unknown"
+ var offset int
+ var buf [20]byte
+
+ // We expect one of these abi.Kind from cgoCheckArg
+ switch kind {
+ case abi.Chan:
+ kindname = "channel"
+ case abi.Func:
+ kindname = "function"
+ case abi.Interface:
+ kindname = "interface"
+ case abi.Map:
+ kindname = "map"
+ case abi.Pointer:
+ kindname = "pointer"
+ case abi.Slice:
+ kindname = "slice"
+ case abi.String:
+ kindname = "string"
+ case abi.Struct:
+ kindname = "struct"
+ case abi.UnsafePointer:
+ kindname = "unsafe pointer"
+ default:
+ kindname = "pointer"
+ }
+
+ // The cgo function name might need an offset to be obtained
+ if error == cgoResultFail {
+ offset = 21
+ }
+
+ // Relatively to cgoFormatErr, this is the stack frame:
+ // 0. cgoFormatErr
+ // 1. cgoCheckArg or cgoCheckUnknownPointer
+ // 2. cgoCheckPointer or cgoCheckResult
+ // 3. cgo function
+ pc, path, line, ok := Caller(3)
+ if ok && error == cgoResultFail {
+ function := FuncForPC(pc)
+
+ if function != nil {
+ // Expected format of cgo function name:
+ // - caller: _cgoexp_3c910ddb72c4_foo
+ if offset > len(function.Name()) {
+ cgoFunction = function.Name()
+ } else {
+ cgoFunction = function.Name()[offset:]
+ }
+ }
+ }
+
+ switch error {
+ case cgoResultFail:
+ msg = path + ":" + string(itoa(buf[:], uint64(line)))
+ msg += ": result of Go function " + cgoFunction + " called from cgo"
+ msg += " is unpinned Go " + kindname + " or points to unpinned Go " + kindname
+ case cgoCheckPointerFail:
+ msg += "argument of cgo function has Go pointer to unpinned Go " + kindname
+ }
+
+ return errorString(msg)
+}