| // 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 || id.Name != "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 |
| } |