Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/cgo: panic while checking **unsafe.Pointer in cgo call #13830

Closed
xlab opened this issue Jan 5, 2016 · 13 comments
Closed

cmd/cgo: panic while checking **unsafe.Pointer in cgo call #13830

xlab opened this issue Jan 5, 2016 · 13 comments
Milestone

Comments

@xlab
Copy link

xlab commented Jan 5, 2016

What version of Go are you using (go version)?

$ go version
go version go1.6beta1 darwin/amd64

What operating system and processor architecture are you using?

$ uname -a
Darwin xlab-dev.local 15.2.0 Darwin Kernel Version 15.2.0: Fri Nov 13 19:56:56 PST 2015; root:xnu-3248.20.55~2/RELEASE_X86_64 x86_64

What did you do?

I'm trying to pass a pointer into a cgo call. The program below is a full demo.

package main

import "unsafe"

// typedef void Bar;
// void Foo(Bar** ptr) {};
import "C"

type Bar [0]byte

func Foo(arg **Bar) {
    ptr := (**C.Bar)(unsafe.Pointer(arg))
    C.Foo(ptr)
}

func main() {
    var ptr *Bar
    Foo(&ptr)
}

What did you expect to see?

The pointer successfully passed or a failure because of Go 1.6 restrictions against passing a pointer to Go allocated memory that has pointers to Go allocated memory.

What did you see instead?

$ go run main.go
panic: interface conversion: interface {} is **main._Ctype_Bar, not *unsafe.Pointer

goroutine 1 [running]:
main._cgoCheckPointer0(0x40552e0, 0xc82002a008, 0x0, 0x0, 0x0, 0x4056420)
    command-line-arguments/_obj/_cgo_gotypes.go:40 +0x91
main.Foo(0xc82002a008)
    cgofail/main.go:13 +0x48
main.main()
    cgofail/main.go:18 +0x31
exit status 2

The helper has been generated as:

func _cgoCheckPointer0(p interface{}, args ...interface{}) *unsafe.Pointer {
    return _cgoCheckPointer(p, args...).(*unsafe.Pointer)
}
@xlab
Copy link
Author

xlab commented Jan 5, 2016

Surprisingly enough, I'm getting a compilation error instead with Go 1.5.2:

$ go version
go version go1.5.2 darwin/amd64

$ go run main.go
# command-line-arguments
./main.go:13: cannot use ptr (type **C.Bar) as type *unsafe.Pointer in argument to _Cfunc_Foo

The wrapper has been generated as:

func _Cfunc_Foo(p0 *unsafe.Pointer) (r1 _Ctype_void)

@xlab
Copy link
Author

xlab commented Jan 5, 2016

The workaround is to pass it as ptr := (*unsafe.Pointer)(unsafe.Pointer(arg)) which looks strange but works. In either 1.5.2 or 1.6beta1.

@ianlancetaylor ianlancetaylor changed the title runtime: panic while checking **unsafe.Pointer in cgo call cmd/cgo: panic while checking **unsafe.Pointer in cgo call Jan 5, 2016
@ianlancetaylor ianlancetaylor added this to the Go1.6 milestone Jan 5, 2016
@ianlancetaylor ianlancetaylor self-assigned this Jan 5, 2016
@ianlancetaylor
Copy link
Contributor

cgo treats this as a type error because a pointer to void is handled as unsafe.Pointer. Since you have typedef void Bar, the C type Bar* is the Go type unsafe.Pointer, and the C type Bar** is the Go type *unsafe.Pointer.

The fix for Go 1.6 is to report an error at compile time rather than at run time, as Go 1.5 does.

@ianlancetaylor
Copy link
Contributor

Turns out to be hard to check at compile time, for exactly the reason that helper cgoCheckPointer functions were introduced, so postponing to 1.7.

@ianlancetaylor ianlancetaylor modified the milestones: Go1.7, Go1.6 Jan 6, 2016
@rsc
Copy link
Contributor

rsc commented May 17, 2016

@ianlancetaylor, should this be postponed to Go 1.8?

@ianlancetaylor
Copy link
Contributor

Dropping to unplanned. I would like to have better compile time checking but I'm not sure how to do it.

@ianlancetaylor ianlancetaylor modified the milestones: Unplanned, Go1.7 May 17, 2016
@bcmills
Copy link
Contributor

bcmills commented May 31, 2016

This doesn't appear to even require a double-pointer. A plain old void* - as for C.free - suffices. I think this implies that every program that calls C.free on a typed parameter ends up triggering the panic.

For example, this program panics:

package main

/*
#include <stdlib.h>
*/
import "C"
import "unsafe"

func main() {
    var dt *C.div_t
    dt = (*C.div_t)(C.malloc(C.size_t(unsafe.Sizeof(*dt))))
    defer C.free(dt)
}

whereas this one does not:

package main

/*
#include <stdlib.h>
*/
import "C"
import "unsafe"

func main() {
    var dt *C.div_t
    dt = (*C.div_t)(C.malloc(C.size_t(unsafe.Sizeof(*dt))))
    defer C.free(unsafe.Pointer(dt))
}

@ianlancetaylor
Copy link
Contributor

Yes. The first program is invalid, the second is not. The way to fix this bug is to catch the invalid case at compile time rather than execution time. In Go 1.5 and earlier we caught the invalid case at compile time, but unfortunately the cgo checks broke that. And, even more unfortunately, it's hard to fix. At least, I don't see how to fix it.

@bcmills
Copy link
Contributor

bcmills commented Jun 1, 2016

I'm sure I'm missing something, but why do the _cgoCheckPointer calls need to be inline in the function parameters at all?

For the example above, cgo generates this code:

func _cgoCheckPointer0(p interface{}, args ...interface{}) unsafe.Pointer {
        return _cgoCheckPointer(p, args...).(unsafe.Pointer)
}

func _Cfunc_free(p0 unsafe.Pointer) (r1 _Ctype_void) {
        _cgo_runtime_cgocall(_cgo_188eb01e039f_Cfunc_free, uintptr(unsafe.Pointer(&p0)))
        [...]
}

[...]

func main() {
        var dt *_Ctype_struct___0
        dt = (*_Ctype_struct___0)(_Cfunc__CMalloc(_Ctype_size_t(unsafe.Sizeof(*dt))))
        defer _Cfunc_free(_cgoCheckPointer0(unsafe.Pointer(dt)))
}

Why does the _cgoCheckPointer call need to occur on the caller side of _Cfunc_free? It seems like we could fix both this issue and #15921 by instead emitting:

func _Cfunc_free(p0 unsafe.Pointer) (r1 _Ctype_void) {
        _cgoCheckPointer0(p0)
        _cgo_runtime_cgocall(_cgo_188eb01e039f_Cfunc_free, uintptr(unsafe.Pointer(&p0)))
        [...]
}

[...]

func main() {
        var dt *_Ctype_struct___0
        dt = (*_Ctype_struct___0)(_Cfunc__CMalloc(_Ctype_size_t(unsafe.Sizeof(*dt))))
        defer _Cfunc_free(dt)
}

Is the problem that the generated function generally accept C types but we need/want to elide the pointer checks based on the corresponding Go types? If that's the case, perhaps we could move the checks to the _Cfunc side only for parameter types that must be transformed between the two.

@ianlancetaylor
Copy link
Contributor

The problem with your suggestion is that given a pointer to an element in a slice, we only want to check that slice. Given a pointer to a field, we only want to check that field. We only know the extent of the area of memory to check at the call site, not in the wrapper. This extent is expressed as additional arguments to cgoCheckPointer. See #14210 for relevant examples.

@bcmills
Copy link
Contributor

bcmills commented Jun 2, 2016

Ok. So it's not just type information, but also information about the origins of the values: a *foo that points to an element of a []foo needs a different check from a *foo that points to a member of a struct.

And presumably we need call sites to remain expressions (not statements), because the result of a C function call could itself be a function parameter. So we can't pull the _cgoCheckPointer calls out into separate statements unless they're in a func literal.

...and we can't use func literals because we can't necessarily name the return-type in the current file (if the type happens to include unsafe.Pointer). So now I understand where the _cgoCheckPointer0 function comes from.

So what about the definition of _cgoCheckPointer0 itself? Is there a reason the first arg needs to be an interface{} when we know we're just going to pass it through the runtime and type-assert it to something else?

In src/cmd/cgo/out.go, could we change

  fmt.Fprintf(fgo2, "\nfunc %s(p interface{}, args ...interface{}) %s {\n", n, t)

to

  fmt.Fprintf(fgo2, "\nfunc %s(p %s, args ...interface{}) %s {\n", n, t, t)

?

@ianlancetaylor
Copy link
Contributor

Yes, I think we could change the _cgoCheckPointer0 function to specify the type of the first parameter. I think I didn't do that because I thought of _cgoCheckPointer0 as essentially the same as the generic function _cgoCheckPointer, and the latter function necessarily has a first argument of type interface{}.

Ah, I see what you mean; that fixes this issue. Thanks! I'll try that--maybe I've forgotten some reason it won't work.

@gopherbot
Copy link

CL https://golang.org/cl/23675 mentions this issue.

@ianlancetaylor ianlancetaylor modified the milestones: Go1.7, Unplanned Jun 9, 2016
xlab added a commit to xlab/android-go that referenced this issue Feb 26, 2017
@golang golang locked and limited conversation to collaborators Jun 9, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants