// Copyright 2015 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. // Check for invalid cgo pointer passing. // This looks for code that uses cgo to call C code passing values // whose types are almost always invalid according to the cgo pointer // sharing rules. // Specifically, it warns about attempts to pass a Go chan, map, func, // or slice to C, either directly, or via a pointer, array, or struct. package main import ( "go/ast" "go/token" "go/types" ) func init() { register("cgocall", "check for types that may not be passed to cgo calls", checkCgoCall, callExpr) } func checkCgoCall(f *File, node ast.Node) { x := node.(*ast.CallExpr) // We are only looking for calls to functions imported from // the "C" package. sel, ok := x.Fun.(*ast.SelectorExpr) if !ok { return } id, ok := sel.X.(*ast.Ident) if !ok { return } pkgname, ok := f.pkg.uses[id].(*types.PkgName) if !ok || pkgname.Imported().Path() != "C" { return } // A call to C.CBytes passes a pointer but is always safe. if sel.Sel.Name == "CBytes" { return } for _, arg := range x.Args { if !typeOKForCgoCall(cgoBaseType(f, arg), make(map[types.Type]bool)) { f.Badf(arg.Pos(), "possibly passing Go type with embedded pointer to C") } // Check for passing the address of a bad type. if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 && f.hasBasicType(conv.Fun, types.UnsafePointer) { arg = conv.Args[0] } if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND { if !typeOKForCgoCall(cgoBaseType(f, u.X), make(map[types.Type]bool)) { f.Badf(arg.Pos(), "possibly passing Go type with embedded pointer to C") } } } } // cgoBaseType tries to look through type conversions involving // unsafe.Pointer to find the real type. It converts: // unsafe.Pointer(x) => x // *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x func cgoBaseType(f *File, arg ast.Expr) types.Type { switch arg := arg.(type) { case *ast.CallExpr: if len(arg.Args) == 1 && f.hasBasicType(arg.Fun, types.UnsafePointer) { return cgoBaseType(f, arg.Args[0]) } case *ast.StarExpr: call, ok := arg.X.(*ast.CallExpr) if !ok || len(call.Args) != 1 { break } // Here arg is *f(v). t := f.pkg.types[call.Fun].Type if t == nil { break } ptr, ok := t.Underlying().(*types.Pointer) if !ok { break } // Here arg is *(*p)(v) elem, ok := ptr.Elem().Underlying().(*types.Basic) if !ok || elem.Kind() != types.UnsafePointer { break } // Here arg is *(*unsafe.Pointer)(v) call, ok = call.Args[0].(*ast.CallExpr) if !ok || len(call.Args) != 1 { break } // Here arg is *(*unsafe.Pointer)(f(v)) if !f.hasBasicType(call.Fun, types.UnsafePointer) { break } // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v)) u, ok := call.Args[0].(*ast.UnaryExpr) if !ok || u.Op != token.AND { break } // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v)) return cgoBaseType(f, u.X) } return f.pkg.types[arg].Type } // typeOKForCgoCall reports whether the type of arg is OK to pass to a // C function using cgo. This is not true for Go types with embedded // pointers. m is used to avoid infinite recursion on recursive types. func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool { if t == nil || m[t] { return true } m[t] = true switch t := t.Underlying().(type) { case *types.Chan, *types.Map, *types.Signature, *types.Slice: return false case *types.Pointer: return typeOKForCgoCall(t.Elem(), m) case *types.Array: return typeOKForCgoCall(t.Elem(), m) case *types.Struct: for i := 0; i < t.NumFields(); i++ { if !typeOKForCgoCall(t.Field(i).Type(), m) { return false } } } return true }