mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/cgo: always use a function literal for pointer checking
The pointer checking code needs to know the exact type of the parameter expected by the C function, so that it can use a type assertion to convert the empty interface returned by cgoCheckPointer to the correct type. Previously this was done by using a type conversion, but that meant that the code accepted arguments that were convertible to the parameter type, rather than arguments that were assignable as in a normal function call. In other words, some code that should not have passed type checking was accepted. This CL changes cgo to always use a function literal for pointer checking. Now the argument is passed to the function literal, which has the correct argument type, so type checking is performed just as for a function call as it should be. Since we now always use a function literal, simplify the checking code to run as a statement by itself. It now no longer needs to return a value, and we no longer need a type assertion. This does have the cost of introducing another function call into any call to a C function that requires pointer checking, but the cost of the additional call should be minimal compared to the cost of pointer checking. Fixes #16591. Change-Id: I220165564cf69db9fd5f746532d7f977a5b2c989 Reviewed-on: https://go-review.googlesource.com/31233 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
parent
e32ac7978d
commit
a16954b8a7
6 changed files with 121 additions and 94 deletions
17
misc/cgo/errors/issue16591.go
Normal file
17
misc/cgo/errors/issue16591.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Issue 16591: Test that we detect an invalid call that was being
|
||||||
|
// hidden by a type conversion inserted by cgo checking.
|
||||||
|
|
||||||
|
package p
|
||||||
|
|
||||||
|
// void f(int** p) { }
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type x *C.int
|
||||||
|
|
||||||
|
func F(p *x) {
|
||||||
|
C.f(p) // ERROR HERE
|
||||||
|
}
|
||||||
|
|
@ -46,6 +46,7 @@ check issue13423.go
|
||||||
expect issue13635.go C.uchar C.schar C.ushort C.uint C.ulong C.longlong C.ulonglong C.complexfloat C.complexdouble
|
expect issue13635.go C.uchar C.schar C.ushort C.uint C.ulong C.longlong C.ulonglong C.complexfloat C.complexdouble
|
||||||
check issue13830.go
|
check issue13830.go
|
||||||
check issue16116.go
|
check issue16116.go
|
||||||
|
check issue16591.go
|
||||||
|
|
||||||
if ! go build issue14669.go; then
|
if ! go build issue14669.go; then
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,7 @@ func testCallbackCallers(t *testing.T) {
|
||||||
"runtime.asmcgocall",
|
"runtime.asmcgocall",
|
||||||
"runtime.cgocall",
|
"runtime.cgocall",
|
||||||
"test._Cfunc_callback",
|
"test._Cfunc_callback",
|
||||||
|
"test.nestedCall.func1",
|
||||||
"test.nestedCall",
|
"test.nestedCall",
|
||||||
"test.testCallbackCallers",
|
"test.testCallbackCallers",
|
||||||
"test.TestCallbackCallers",
|
"test.TestCallbackCallers",
|
||||||
|
|
|
||||||
|
|
@ -639,40 +639,29 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool {
|
||||||
|
|
||||||
// We need to rewrite this call.
|
// We need to rewrite this call.
|
||||||
//
|
//
|
||||||
// We are going to rewrite C.f(p) to C.f(_cgoCheckPointer(p)).
|
// We are going to rewrite C.f(p) to
|
||||||
// If the call to C.f is deferred, that will check p at the
|
// func (_cgo0 ptype) {
|
||||||
// point of the defer statement, not when the function is called, so
|
// _cgoCheckPointer(_cgo0)
|
||||||
// rewrite to func(_cgo0 ptype) { C.f(_cgoCheckPointer(_cgo0)) }(p)
|
// C.f(_cgo0)
|
||||||
|
// }(p)
|
||||||
|
// Using a function literal like this lets us do correct
|
||||||
|
// argument type checking, and works correctly if the call is
|
||||||
|
// deferred.
|
||||||
needsUnsafe := false
|
needsUnsafe := false
|
||||||
var dargs []ast.Expr
|
params := make([]*ast.Field, len(name.FuncType.Params))
|
||||||
if call.Deferred {
|
args := make([]ast.Expr, len(name.FuncType.Params))
|
||||||
dargs = make([]ast.Expr, len(name.FuncType.Params))
|
var stmts []ast.Stmt
|
||||||
}
|
|
||||||
for i, param := range name.FuncType.Params {
|
for i, param := range name.FuncType.Params {
|
||||||
|
// params is going to become the parameters of the
|
||||||
|
// function literal.
|
||||||
|
// args is going to become the list of arguments to the
|
||||||
|
// function literal.
|
||||||
|
// nparam is the parameter of the function literal that
|
||||||
|
// corresponds to param.
|
||||||
|
|
||||||
origArg := call.Call.Args[i]
|
origArg := call.Call.Args[i]
|
||||||
darg := origArg
|
args[i] = origArg
|
||||||
|
nparam := ast.NewIdent(fmt.Sprintf("_cgo%d", i))
|
||||||
if call.Deferred {
|
|
||||||
dargs[i] = darg
|
|
||||||
darg = ast.NewIdent(fmt.Sprintf("_cgo%d", i))
|
|
||||||
call.Call.Args[i] = darg
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.needsPointerCheck(f, param.Go, origArg) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &ast.CallExpr{
|
|
||||||
Fun: ast.NewIdent("_cgoCheckPointer"),
|
|
||||||
Args: []ast.Expr{
|
|
||||||
darg,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add optional additional arguments for an address
|
|
||||||
// expression.
|
|
||||||
c.Args = p.checkAddrArgs(f, c.Args, origArg)
|
|
||||||
|
|
||||||
// The Go version of the C type might use unsafe.Pointer,
|
// The Go version of the C type might use unsafe.Pointer,
|
||||||
// but the file might not import unsafe.
|
// but the file might not import unsafe.
|
||||||
|
|
@ -682,69 +671,90 @@ func (p *Package) rewriteCall(f *File, call *Call, name *Name) bool {
|
||||||
needsUnsafe = true
|
needsUnsafe = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// In order for the type assertion to succeed, we need
|
|
||||||
// it to match the actual type of the argument. The
|
|
||||||
// only type we have is the type of the function
|
|
||||||
// parameter. We know that the argument type must be
|
|
||||||
// assignable to the function parameter type, or the
|
|
||||||
// code would not compile, but there is nothing
|
|
||||||
// requiring that the types be exactly the same. Add a
|
|
||||||
// type conversion to the argument so that the type
|
|
||||||
// assertion will succeed.
|
|
||||||
c.Args[0] = &ast.CallExpr{
|
|
||||||
Fun: ptype,
|
|
||||||
Args: []ast.Expr{
|
|
||||||
c.Args[0],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
call.Call.Args[i] = &ast.TypeAssertExpr{
|
|
||||||
X: c,
|
|
||||||
Type: ptype,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if call.Deferred {
|
|
||||||
params := make([]*ast.Field, len(name.FuncType.Params))
|
|
||||||
for i, param := range name.FuncType.Params {
|
|
||||||
ptype := p.rewriteUnsafe(param.Go)
|
|
||||||
if ptype != param.Go {
|
|
||||||
needsUnsafe = true
|
|
||||||
}
|
|
||||||
params[i] = &ast.Field{
|
params[i] = &ast.Field{
|
||||||
Names: []*ast.Ident{
|
Names: []*ast.Ident{nparam},
|
||||||
ast.NewIdent(fmt.Sprintf("_cgo%d", i)),
|
|
||||||
},
|
|
||||||
Type: ptype,
|
Type: ptype,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
call.Call.Args[i] = nparam
|
||||||
|
|
||||||
|
if !p.needsPointerCheck(f, param.Go, origArg) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dbody := &ast.CallExpr{
|
// Run the cgo pointer checks on nparam.
|
||||||
|
|
||||||
|
// Change the function literal to call the real function
|
||||||
|
// with the parameter passed through _cgoCheckPointer.
|
||||||
|
c := &ast.CallExpr{
|
||||||
|
Fun: ast.NewIdent("_cgoCheckPointer"),
|
||||||
|
Args: []ast.Expr{
|
||||||
|
nparam,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add optional additional arguments for an address
|
||||||
|
// expression.
|
||||||
|
c.Args = p.checkAddrArgs(f, c.Args, origArg)
|
||||||
|
|
||||||
|
stmt := &ast.ExprStmt{
|
||||||
|
X: c,
|
||||||
|
}
|
||||||
|
stmts = append(stmts, stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fcall := &ast.CallExpr{
|
||||||
Fun: call.Call.Fun,
|
Fun: call.Call.Fun,
|
||||||
Args: call.Call.Args,
|
Args: call.Call.Args,
|
||||||
}
|
}
|
||||||
call.Call.Fun = &ast.FuncLit{
|
ftype := &ast.FuncType{
|
||||||
Type: &ast.FuncType{
|
|
||||||
Params: &ast.FieldList{
|
Params: &ast.FieldList{
|
||||||
List: params,
|
List: params,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
Body: &ast.BlockStmt{
|
var fbody ast.Stmt
|
||||||
List: []ast.Stmt{
|
if name.FuncType.Result == nil {
|
||||||
&ast.ExprStmt{
|
fbody = &ast.ExprStmt{
|
||||||
X: dbody,
|
X: fcall,
|
||||||
},
|
}
|
||||||
|
} else {
|
||||||
|
fbody = &ast.ReturnStmt{
|
||||||
|
Results: []ast.Expr{fcall},
|
||||||
|
}
|
||||||
|
rtype := p.rewriteUnsafe(name.FuncType.Result.Go)
|
||||||
|
if rtype != name.FuncType.Result.Go {
|
||||||
|
needsUnsafe = true
|
||||||
|
}
|
||||||
|
ftype.Results = &ast.FieldList{
|
||||||
|
List: []*ast.Field{
|
||||||
|
&ast.Field{
|
||||||
|
Type: rtype,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
call.Call.Args = dargs
|
}
|
||||||
|
call.Call.Fun = &ast.FuncLit{
|
||||||
|
Type: ftype,
|
||||||
|
Body: &ast.BlockStmt{
|
||||||
|
List: append(stmts, fbody),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
call.Call.Args = args
|
||||||
call.Call.Lparen = token.NoPos
|
call.Call.Lparen = token.NoPos
|
||||||
call.Call.Rparen = token.NoPos
|
call.Call.Rparen = token.NoPos
|
||||||
|
|
||||||
// There is a Ref pointing to the old call.Call.Fun.
|
// There is a Ref pointing to the old call.Call.Fun.
|
||||||
for _, ref := range f.Ref {
|
for _, ref := range f.Ref {
|
||||||
if ref.Expr == &call.Call.Fun {
|
if ref.Expr == &call.Call.Fun {
|
||||||
ref.Expr = &dbody.Fun
|
ref.Expr = &fcall.Fun
|
||||||
|
|
||||||
|
// If this call expects two results, we have to
|
||||||
|
// adjust the results of the function we generated.
|
||||||
|
if ref.Context == "call2" {
|
||||||
|
ftype.Results.List = append(ftype.Results.List,
|
||||||
|
&ast.Field{
|
||||||
|
Type: ast.NewIdent("error"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1379,14 +1379,14 @@ func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32
|
||||||
func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, uintptr)
|
func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, uintptr)
|
||||||
|
|
||||||
//go:linkname _cgoCheckPointer runtime.cgoCheckPointer
|
//go:linkname _cgoCheckPointer runtime.cgoCheckPointer
|
||||||
func _cgoCheckPointer(interface{}, ...interface{}) interface{}
|
func _cgoCheckPointer(interface{}, ...interface{})
|
||||||
|
|
||||||
//go:linkname _cgoCheckResult runtime.cgoCheckResult
|
//go:linkname _cgoCheckResult runtime.cgoCheckResult
|
||||||
func _cgoCheckResult(interface{})
|
func _cgoCheckResult(interface{})
|
||||||
`
|
`
|
||||||
|
|
||||||
const gccgoGoProlog = `
|
const gccgoGoProlog = `
|
||||||
func _cgoCheckPointer(interface{}, ...interface{}) interface{}
|
func _cgoCheckPointer(interface{}, ...interface{})
|
||||||
|
|
||||||
func _cgoCheckResult(interface{})
|
func _cgoCheckResult(interface{})
|
||||||
`
|
`
|
||||||
|
|
@ -1566,18 +1566,17 @@ typedef struct __go_empty_interface {
|
||||||
void *__object;
|
void *__object;
|
||||||
} Eface;
|
} Eface;
|
||||||
|
|
||||||
extern Eface runtimeCgoCheckPointer(Eface, Slice)
|
extern void runtimeCgoCheckPointer(Eface, Slice)
|
||||||
__asm__("runtime.cgoCheckPointer")
|
__asm__("runtime.cgoCheckPointer")
|
||||||
__attribute__((weak));
|
__attribute__((weak));
|
||||||
|
|
||||||
extern Eface localCgoCheckPointer(Eface, Slice)
|
extern void localCgoCheckPointer(Eface, Slice)
|
||||||
__asm__("GCCGOSYMBOLPREF._cgoCheckPointer");
|
__asm__("GCCGOSYMBOLPREF._cgoCheckPointer");
|
||||||
|
|
||||||
Eface localCgoCheckPointer(Eface ptr, Slice args) {
|
void localCgoCheckPointer(Eface ptr, Slice args) {
|
||||||
if(runtimeCgoCheckPointer) {
|
if(runtimeCgoCheckPointer) {
|
||||||
return runtimeCgoCheckPointer(ptr, args);
|
runtimeCgoCheckPointer(ptr, args);
|
||||||
}
|
}
|
||||||
return ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void runtimeCgoCheckResult(Eface)
|
extern void runtimeCgoCheckResult(Eface)
|
||||||
|
|
|
||||||
|
|
@ -370,10 +370,10 @@ var racecgosync uint64 // represents possible synchronization in C code
|
||||||
// pointers.)
|
// pointers.)
|
||||||
|
|
||||||
// cgoCheckPointer checks if the argument contains a Go pointer that
|
// cgoCheckPointer checks if the argument contains a Go pointer that
|
||||||
// points to a Go pointer, and panics if it does. It returns the pointer.
|
// points to a Go pointer, and panics if it does.
|
||||||
func cgoCheckPointer(ptr interface{}, args ...interface{}) interface{} {
|
func cgoCheckPointer(ptr interface{}, args ...interface{}) {
|
||||||
if debug.cgocheck == 0 {
|
if debug.cgocheck == 0 {
|
||||||
return ptr
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ep := (*eface)(unsafe.Pointer(&ptr))
|
ep := (*eface)(unsafe.Pointer(&ptr))
|
||||||
|
|
@ -386,7 +386,7 @@ func cgoCheckPointer(ptr interface{}, args ...interface{}) interface{} {
|
||||||
p = *(*unsafe.Pointer)(p)
|
p = *(*unsafe.Pointer)(p)
|
||||||
}
|
}
|
||||||
if !cgoIsGoPointer(p) {
|
if !cgoIsGoPointer(p) {
|
||||||
return ptr
|
return
|
||||||
}
|
}
|
||||||
aep := (*eface)(unsafe.Pointer(&args[0]))
|
aep := (*eface)(unsafe.Pointer(&args[0]))
|
||||||
switch aep._type.kind & kindMask {
|
switch aep._type.kind & kindMask {
|
||||||
|
|
@ -397,7 +397,7 @@ func cgoCheckPointer(ptr interface{}, args ...interface{}) interface{} {
|
||||||
}
|
}
|
||||||
pt := (*ptrtype)(unsafe.Pointer(t))
|
pt := (*ptrtype)(unsafe.Pointer(t))
|
||||||
cgoCheckArg(pt.elem, p, true, false, cgoCheckPointerFail)
|
cgoCheckArg(pt.elem, p, true, false, cgoCheckPointerFail)
|
||||||
return ptr
|
return
|
||||||
case kindSlice:
|
case kindSlice:
|
||||||
// Check the slice rather than the pointer.
|
// Check the slice rather than the pointer.
|
||||||
ep = aep
|
ep = aep
|
||||||
|
|
@ -415,7 +415,6 @@ func cgoCheckPointer(ptr interface{}, args ...interface{}) interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
cgoCheckArg(t, ep.data, t.kind&kindDirectIface == 0, top, cgoCheckPointerFail)
|
cgoCheckArg(t, ep.data, t.kind&kindDirectIface == 0, top, cgoCheckPointerFail)
|
||||||
return ptr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cgoCheckPointerFail = "cgo argument has Go pointer to Go pointer"
|
const cgoCheckPointerFail = "cgo argument has Go pointer to Go pointer"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue